Laravel Hetzner Storagebox

PHP MIT

Production-ready Laravel package for integrating Hetzner Storage Box into Laravel applications using the native Storage facade and filesystem API.

Stars
19
Forks
0
Downloads
2,357
Open Issues
0
Files main

Repository Files

Loading file structure...
README.md
<p align="center">
  <img src="https://res.cloudinary.com/djgvfl1tv/image/upload/v1780666791/logo_mqnqn4.png" alt="Laravel Hetzner Storage Box" width="180">
</p>

<h1 align="center">Laravel Hetzner Storage Box SDK</h1>

<p align="center">
  A premium, feature-rich PHP SDK and Laravel integration for the Hetzner Storage Box API, featuring rate-limit handling, automatic retries, and concurrent batch operations.
</p>

<p align="center">
  <img src="https://img.shields.io/badge/Laravel-11%20%7C%2012%20%7C%2013-FF2D20?style=for-the-badge&logo=laravel&logoColor=white" alt="Laravel">
  <img src="https://img.shields.io/badge/PHP-8.2%20to%208.5-777BB4?style=for-the-badge&logo=php&logoColor=white" alt="PHP Version">
  <img src="https://img.shields.io/badge/Hetzner--Storage--Box-API-D85012?style=for-the-badge" alt="Hetzner Storage Box API">
  <img src="https://img.shields.io/badge/Built%20By-Ghost%20Compiler-0F172A?style=for-the-badge" alt="Ghost Compiler">
</p>

<p align="center">
    <img src="https://img.shields.io/github/stars/ghostcompiler/laravel-hetzner-storagebox?style=for-the-badge&logo=github" />
    <img src="https://img.shields.io/packagist/dt/ghostcompiler/laravel-hetzner-storagebox?style=for-the-badge&logo=packagist" />
</p>

---

## Features

- **100% Endpoint Coverage**: Complete implementation of all storage boxes, subaccounts, snapshots, actions, and locations.
- **Fail-Safe Retries & Backoff**: Robust exponential backoff and rate-limit parsing handling `RateLimit-Reset` response headers automatically.
- **Concurrently Pooled Processing**: Execute calls asynchronously or concurrently in batches.
- **Dynamic Filter Builder**: Fluent query-building for filtering, page indexing, and sorting.
- **Type-Safe DTOs**: Automated data hydration into standard PHP DTO structures.
- **Custom Exceptions**: Specialized mapping of API status codes.

---

## Installation

Install the package via Composer:

```bash
composer require ghostcompiler/laravel-hetzner-storagebox
```

Publish the configuration file using our simplified command installer:

```bash
php artisan ghost:storagebox install
```

Add your Hetzner Storage Box Console API Token to your `.env` file:

```env
HETZNER_STORAGEBOX_TOKEN=your_api_token_here
HETZNER_STORAGEBOX_TIMEOUT=30
HETZNER_STORAGEBOX_RETRIES=3
HETZNER_STORAGEBOX_RETRY_BACKOFF=100
HETZNER_STORAGEBOX_LOGGING_ENABLED=true
```

---

## Usage Examples

You can access the SDK in three ways:

1. **Facade (Recommended for Laravel environments)**
```php
use GhostCompiler\Hetzner\StorageBox\Facades\HetznerStorageBox;

$boxes = HetznerStorageBox::storageBoxes()->get();
```

2. **Global Helper (Convenience function)**
```php
$boxes = HetznerStorageBox()->storageBoxes()->get();
```

3. **Dynamic Token Authentication (For multi-tenant or multi-account systems)**
```php
$boxes = HetznerStorageBox('your_dynamic_token_here')->storageBoxes()->get();
```

---

### Storage Boxes

#### Listing and Filtering Storage Boxes
```php
use GhostCompiler\Hetzner\StorageBox\Facades\HetznerStorageBox;

// List the first 50 storage boxes matching a name
$boxes = HetznerStorageBox::storageBoxes()
    ->filter(['name' => 'box-01'])
    ->perPage(50)
    ->page(1)
    ->get();

foreach ($boxes as $box) {
    echo $box->name . ': ' . $box->status . "\n";
}
```

#### Paginated Results
```php
$paginated = HetznerStorageBox::storageBoxes()->paginate(25, 2);

$boxes = $paginated->items; // StorageBoxCollection
$meta = $paginated->pagination; // PaginationMeta DTO

echo "Page: " . $meta->page . " of " . $meta->lastPage;
```

#### Creating and Deleting a Storage Box
```php
// Create a new storage box
$response = HetznerStorageBox::storageBoxes()->create([
    'name' => 'backups-prod',
    'storage_box_type' => 'bx11',
    'location' => 'fsn1',
    'password' => 'secure_password_here'
]);

$box = $response->storageBox;
$action = $response->action;

echo "Provisioned Storage Box ID: " . $box->id . "\n";

// Delete the storage box
$deleteAction = HetznerStorageBox::storageBoxes()->delete($box->id);
if ($deleteAction) {
    echo "Deletion status: " . $deleteAction->status;
}
```

#### Triggering Storage Box Actions
```php
// Reset Storage Box password
$action = HetznerStorageBox::storageBoxes()->resetPassword($boxId, 'new_secure_password_123');
echo "Action status: " . $action->status; // running / success

// Change delete protection
HetznerStorageBox::storageBoxes()->changeProtection($boxId, true);
```

#### Listing Storage Box Folders
```php
$folders = HetznerStorageBox::storageBoxes()->folders($boxId, '/backups');
foreach ($folders['folders'] as $folder) {
    echo "Folder: " . $folder . "\n";
}
```

---

### Subaccounts

Manage directories and access protocols for subaccounts.

```php
$subaccountsManager = HetznerStorageBox::storageBoxes()->subaccounts($boxId);

// List subaccounts
$subaccounts = $subaccountsManager->all();

// Create a subaccount
$sub = $subaccountsManager->create([
    'name' => 'upload-sub',
    'description' => 'Subaccount for uploads',
    'home_directory' => '/uploads',
    'access_settings' => [
        'samba_enabled' => false,
        'ssh_enabled' => true,
        'webdav_enabled' => true,
        'read_only' => false
    ]
]);

// Reset subaccount password
$subaccountsManager->resetPassword($sub->id, 'SecurePassword123!');
```

---

### Snapshots

Manage point-in-time filesystem snapshots.

```php
$snapshotsManager = HetznerStorageBox::storageBoxes()->snapshots($boxId);

// List snapshots
$snapshots = $snapshotsManager->all();

// Create a snapshot
$snapshot = $snapshotsManager->create([
    'description' => 'Daily backup snapshot',
    'labels' => [
        'env' => 'production'
    ]
]);

// Delete a snapshot
$snapshotsManager->delete($snapshot->id);
```

---

### Asynchronous Requests
```php
// Return a Guzzle Promise immediately
$promise = HetznerStorageBox::storageBoxes()->async()->all();

// Resolve promise when ready
$boxes = $promise->wait();
```

---

### Batch Concurrent Operations
```php
// Execute multiple queries concurrently
$results = HetznerStorageBox::batch([
    fn () => HetznerStorageBox::storageBoxes()->find(1),
    fn () => HetznerStorageBox::storageBoxes()->find(2),
    fn () => HetznerStorageBox::storageBoxTypes()->find(4),
]);

$box1 = $results[0];
$box2 = $results[1];
$type = $results[2];
```

---

### Exception Handling
All exceptions inherit from `GhostCompiler\Hetzner\StorageBox\Exceptions\HetznerException`.

```php
use GhostCompiler\Hetzner\StorageBox\Exceptions\AuthenticationException;
use GhostCompiler\Hetzner\StorageBox\Exceptions\ValidationException;
use GhostCompiler\Hetzner\StorageBox\Exceptions\RateLimitException;
use GhostCompiler\Hetzner\StorageBox\Exceptions\HetznerException;

try {
    HetznerStorageBox::storageBoxes()->create(['name' => '']);
} catch (AuthenticationException $e) {
    // 401 Unauthorized
} catch (ValidationException $e) {
    // 422 Unprocessable Entity
    $errors = $e->getErrors(); // Get field-specific validation errors
} catch (RateLimitException $e) {
    // 429 Rate Limit Exceeded
    $secondsToWait = $e->getSecondsUntilReset();
} catch (HetznerException $e) {
    // Base exception handler
}
```

---

## Static Analysis & Linting

Run PHPStan static analysis:
```bash
vendor/bin/phpstan analyse
```

Run Psalm static analysis:
```bash
vendor/bin/psalm
```

Format code with Pint:
```bash
vendor/bin/pint
```

---

## Development Environment

Built using **ServBay**

<p align="center">
  <img src="https://res.cloudinary.com/djgvfl1tv/image/upload/v1780667063/servbay_edc7jz.png" alt="ServBay" width="120">
</p>

- Mac M4 Tested
- macOS Apple Silicon
- Powered by ServBay