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...
docs/index.html
<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Laravel Hetzner Storage Box SDK v1.0.0 Documentation</title>
  <meta name="description" content="Complete documentation reference for the GhostCompiler Hetzner Storage Box SDK Laravel package. Manage storage boxes, types, actions, and locations.">
  <meta name="author" content="Ghost Compiler">
  <meta name="robots" content="index,follow">
  <meta name="theme-color" content="#22c55e">
  <meta name="application-name" content="Hetzner Storage Box SDK Documentation">
  
  <!-- Google Fonts Connection -->
  <link rel="preconnect" href="https://fonts.googleapis.com">
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
  <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=Plus+Jakarta+Sans:wght@500;600;700;800&family=JetBrains+Mono:wght@400;500;600&display=swap" rel="stylesheet">
  
  <link rel="stylesheet" href="styles.css">
  <script type="application/ld+json">
  {
    "@context": "https://schema.org",
    "@type": "TechArticle",
    "headline": "Laravel Hetzner Storage Box SDK v1.0.0 Documentation",
    "about": "Laravel SDK for Hetzner Storage Box API",
    "author": {
      "@type": "Organization",
      "name": "Ghost Compiler"
    },
    "version": "1.0.0"
  }
  </script>
</head>
<body class="docs-page">
  <header class="site-header docs-header">
    <button id="mobileMenuBtn" class="mobile-menu-btn" aria-label="Toggle navigation">
      <svg viewBox="0 0 24 24" width="20" height="20" stroke="currentColor" stroke-width="2.2" fill="none" stroke-linecap="round" stroke-linejoin="round">
        <line x1="3" y1="12" x2="21" y2="12"></line>
        <line x1="3" y1="6" x2="21" y2="6"></line>
        <line x1="3" y1="18" x2="21" y2="18"></line>
      </svg>
    </button>
    <a class="brand" href="#overview">
      <img src="https://res.cloudinary.com/djgvfl1tv/image/upload/v1780666791/logo_mqnqn4.png" alt="Ghost Compiler logo">
      <span>Hetzner Storage Box</span>
    </a>
    <span class="version-pill">v1.0.0</span>
    <div class="doc-search">
      <input id="docSearch" type="search" placeholder="Search documentation">
    </div>
    <nav class="top-links">
      <a href="#overview">Overview</a>
      <a href="#installation">Install</a>
      <a href="#storage-boxes">Storage Boxes</a>
      <a href="#helper-cheatsheet">Cheatsheet</a>
      
      <!-- Segmented Theme Switcher Control -->
      <div class="theme-segmented-control" role="radiogroup" aria-label="Theme switcher">
        <button class="theme-control-btn" data-theme-val="light" aria-label="Light Mode" title="Light Mode">
          <svg viewBox="0 0 24 24"><circle cx="12" cy="12" r="5"></circle><line x1="12" y1="1" x2="12" y2="3"></line><line x1="12" y1="21" x2="12" y2="23"></line><line x1="4.22" y1="4.22" x2="5.64" y2="5.64"></line><line x1="18.36" y1="18.36" x2="19.78" y2="19.78"></line><line x1="1" y1="12" x2="3" y2="12"></line><line x1="21" y1="12" x2="23" y2="12"></line><line x1="4.22" y1="19.78" x2="5.64" y2="18.36"></line><line x1="18.36" y1="5.64" x2="19.78" y2="4.22"></line></svg>
        </button>
        <button class="theme-control-btn" data-theme-val="dark" aria-label="Dark Mode" title="Dark Mode">
          <svg viewBox="0 0 24 24"><path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"></path></svg>
        </button>
        <button class="theme-control-btn" data-theme-val="system" aria-label="System Mode" title="System Mode">
          <svg viewBox="0 0 24 24"><rect x="2" y="3" width="20" height="14" rx="2" ry="2"></rect><line x1="8" y1="21" x2="16" y2="21"></line><line x1="12" y1="17" x2="12" y2="21"></line></svg>
        </button>
      </div>
    </nav>
  </header>

  <!-- Overlay for mobile navigation drawer -->
  <div class="sidebar-backdrop" id="sidebarBackdrop"></div>

  <div class="docs-layout">
    <aside class="docs-sidebar" id="docsSidebar" aria-label="Documentation navigation">
      <div class="sidebar-header-mobile">
        <span>Navigation</span>
        <button id="closeSidebarBtn" class="close-sidebar-btn" aria-label="Close navigation">
          <svg viewBox="0 0 24 24" width="18" height="18" stroke="currentColor" stroke-width="2.5" fill="none" stroke-linecap="round" stroke-linejoin="round">
            <line x1="18" y1="6" x2="6" y2="18"></line>
            <line x1="6" y1="6" x2="18" y2="18"></line>
          </svg>
        </button>
      </div>

      <div class="sidebar-group">
        <p>Start</p>
        <a href="#overview">Overview</a>
        <a href="#installation">Installation</a>
        <a href="#configuration">Configuration</a>
        <a href="#service-provider">Service Provider</a>
        <a href="#facades">Facades</a>
        <a href="#global-helper">Global Helper</a>
      </div>
      <div class="sidebar-group">
        <p>Resources</p>
        <a href="#storage-boxes">Storage Boxes</a>
        <a href="#subaccounts">Subaccounts</a>
        <a href="#snapshots">Snapshots</a>
        <a href="#storage-box-types">Storage Box Types</a>
        <a href="#actions">Actions</a>
        <a href="#locations">Locations</a>
      </div>
      <div class="sidebar-group">
        <p>Advanced</p>
        <a href="#async-requests">Async Requests</a>
        <a href="#batch-operations">Batch Operations</a>
        <a href="#retries-limits">Retries & Limits</a>
        <a href="#exceptions">Exceptions</a>
      </div>
      <div class="sidebar-group">
        <p>Reference</p>
        <a href="#helper-cheatsheet">Helper Cheatsheet</a>
      </div>
    </aside>

    <main class="doc-article">
      
      <section id="overview" data-title="Overview">
        <p class="eyebrow">Documentation v1.0.0</p>
        <h1>Overview</h1>
        <p class="lead">The Laravel Hetzner Storage Box SDK provides a production-ready client interface for managing storage boxes, subaccounts, and schedules. Compatible with PHP 8.2 through 8.5, and Laravel 11 through 13.</p>
        <div class="callout">
          <strong>Fully Type-Safe DTOs.</strong>
          Every response matches structured class mappings with static `fromArray()` hydrators, allowing autocomplete and IDE indexing.
        </div>
      </section>

      <section id="installation" data-title="Installation">
        <h2>Installation</h2>
        <p>Install the package through Composer:</p>
        <pre><code>composer require ghostcompiler/laravel-hetzner-storagebox</code></pre>
        <p>Publish the configuration assets using the simplified command installer:</p>
        <pre><code>php artisan ghost:storagebox install</code></pre>
      </section>

      <section id="configuration" data-title="Configuration">
        <h2>Configuration</h2>
        <p>The published configuration file lives at <code>config/hetzner-storagebox.php</code>. You can generate your API Token from the <a href="https://console.hetzner.cloud/" target="_blank" rel="noopener">Hetzner Console</a> under Security settings.</p>
        <pre><code>return [
    'token' => env('HETZNER_STORAGEBOX_TOKEN'),
    'base_url' => env('HETZNER_STORAGEBOX_BASE_URL', 'https://api.hetzner.com/v1'),
    'timeout' => (int) env('HETZNER_STORAGEBOX_TIMEOUT', 30),
    'retries' => (int) env('HETZNER_STORAGEBOX_RETRIES', 3),
    'retry_backoff' => (int) env('HETZNER_STORAGEBOX_RETRY_BACKOFF', 100),
    'logging' => [
        'enabled' => (bool) env('HETZNER_STORAGEBOX_LOGGING_ENABLED', false),
        'channel' => env('HETZNER_STORAGEBOX_LOGGING_CHANNEL'),
    ],
];</code></pre>
      </section>

      <section id="service-provider" data-title="Service Provider">
        <h2>Service Provider</h2>
        <p>The service provider binds the singleton client and orchestrator to Laravel's service container.</p>
        <pre><code>use GhostCompiler\Hetzner\StorageBox\Managers\HetznerStorageBoxManager;

$manager = app(HetznerStorageBoxManager::class);
$boxes = $manager->storageBoxes()->all();</code></pre>
      </section>

      <section id="facades" data-title="Facades">
        <h2>Facades</h2>
        <p>Interact with the SDK fluently using the Facade accessor.</p>
        <pre><code>use GhostCompiler\Hetzner\StorageBox\Facades\HetznerStorageBox;

$box = HetznerStorageBox::storageBoxes()->find(123);</code></pre>
      </section>

      <section id="global-helper" data-title="Global Helper">
        <h2>Global Helper</h2>
        <p>Access the manager globally via the <code>HetznerStorageBox()</code> helper function. For multi-tenant setups, you can pass a dynamic token to generate an isolated, authenticated manager instance.</p>
        <pre><code>// Static/default container resolution
$boxes = HetznerStorageBox()->storageBoxes()->get();

// Dynamic authentication
$tenantBoxes = HetznerStorageBox('tenant_specific_token')->storageBoxes()->get();</code></pre>
      </section>

      <section id="storage-boxes" data-title="Storage Boxes">
        <h2>Storage Boxes</h2>
        <p>Create, paginate, update, delete, scale, reset passwords, change protection, and manage access parameters (Samba, SSH, WebDAV, etc.) for Storage Boxes.</p>
        <pre><code>use GhostCompiler\Hetzner\StorageBox\Facades\HetznerStorageBox;

// Create storage box
$response = HetznerStorageBox::storageBoxes()->create([
    'name' => 'backups-prod',
    'storage_box_type' => 'bx11',
    'location' => 'fsn1',
    'password' => 'secure_password_here'
]);

$box = $response->storageBox;

// Find storage box by ID
$box = HetznerStorageBox::storageBoxes()->find(12345);

// Paginate storage boxes
$paginated = HetznerStorageBox::storageBoxes()->paginate(15, 1);
$boxes = $paginated->items();
$meta = $paginated->meta();

// Update storage box details
$updatedBox = HetznerStorageBox::storageBoxes()->update(12345, [
    'name' => 'backups-prod-v2'
]);

// Reset password
HetznerStorageBox::storageBoxes()->resetPassword(12345, 'new_password_here_123');

// Update access settings
HetznerStorageBox::storageBoxes()->updateAccessSettings(12345, [
    'samba_enabled' => true,
    'ssh_enabled' => true,
    'webdav_enabled' => false,
    'zfs_enabled' => false,
    'reachable_externally' => true
]);

// Change delete protection
HetznerStorageBox::storageBoxes()->changeProtection(12345, true);

// Change storage box product type
HetznerStorageBox::storageBoxes()->changeType(12345, 'bx21');

// Rollback snapshot
HetznerStorageBox::storageBoxes()->rollbackSnapshot(12345, 'snap-2026-06-13');

// Enable snapshot plan
HetznerStorageBox::storageBoxes()->enableSnapshotPlan(12345, [
    'hour' => 4,
    'minute' => 30,
    'timezone' => 'Europe/Berlin'
]);

// Disable snapshot plan
HetznerStorageBox::storageBoxes()->disableSnapshotPlan(12345);

// List folders inside storage box
$folders = HetznerStorageBox::storageBoxes()->folders(12345, '/backups');

// Delete a storage box
HetznerStorageBox::storageBoxes()->delete(12345);</code></pre>
      </section>

      <section id="subaccounts" data-title="Subaccounts">
        <h2>Subaccounts</h2>
        <p>Create up to 100 subaccounts for a Storage Box. Restrict each subaccount to a specific directory with custom protocols and permissions.</p>
        <pre><code>// Get subaccount manager for a specific Storage Box
$subaccountsManager = HetznerStorageBox::storageBoxes()->subaccounts($boxId);

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

// Find a subaccount by ID
$sub = $subaccountsManager->find(10);

// Create a new subaccount
$newSub = $subaccountsManager->create([
    'name' => 'web-server-uploads',
    'description' => 'Storage for public uploads',
    'home_directory' => '/public/uploads',
    'access_settings' => [
        'samba_enabled' => false,
        'ssh_enabled' => true,
        'webdav_enabled' => true,
        'zfs_enabled' => false,
        'read_only' => false
    ]
]);

// Update subaccount details
$subaccountsManager->update(10, [
    'name' => 'updated-sub-name',
    'description' => 'Updated description'
]);

// Change subaccount home directory
$subaccountsManager->changeHomeDirectory(10, '/public/new-uploads');

// Reset subaccount password
$subaccountsManager->resetPassword(10, 'SecureSubPassword123!');

// Update subaccount access settings
$subaccountsManager->updateAccessSettings(10, [
    'samba_enabled' => true,
    'ssh_enabled' => true,
    'webdav_enabled' => false
]);

// Delete a subaccount
$subaccountsManager->delete(10);</code></pre>
      </section>

      <section id="snapshots" data-title="Snapshots">
        <h2>Snapshots</h2>
        <p>Capture point-in-time states of your Storage Box filesystems. List, find, create, edit descriptions, or delete snapshots.</p>
        <pre><code>// Get snapshot manager for a specific Storage Box
$snapshotsManager = HetznerStorageBox::storageBoxes()->snapshots($boxId);

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

// Find a snapshot by ID
$snapshot = $snapshotsManager->find(20);

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

// Update snapshot description and labels
$snapshotsManager->update(20, [
    'description' => 'Database backup snapshot - updated',
    'labels' => [
        'env' => 'production',
        'status' => 'archived'
    ]
]);

// Delete a snapshot
$snapshotsManager->delete(20);</code></pre>
      </section>

      <section id="storage-box-types" data-title="Storage Box Types">
        <h2>Storage Box Types</h2>
        <p>Query available products representing storage box capacities and pricing limits.</p>
        <pre><code>$types = HetznerStorageBox::storageBoxTypes()->all();

foreach ($types as $type) {
    echo "Type: {$type->name} | Limit: {$type->snapshotLimit} snapshots";
}</code></pre>
      </section>

      <section id="actions" data-title="Actions">
        <h2>Actions</h2>
        <p>Track the history and progress of asynchronous operations on Storage Boxes.</p>
        <pre><code>// List all actions
$actions = HetznerStorageBox::actions()->all();

// List actions for a specific Storage Box
$boxActions = HetznerStorageBox::storageBoxes()->actions($boxId);</code></pre>
      </section>

      <section id="locations" data-title="Locations">
        <h2>Locations</h2>
        <p>Retrieve the physical datacenters where Storage Boxes can be provisioned.</p>
        <pre><code>$locations = HetznerStorageBox::locations()->all();</code></pre>
      </section>

      <section id="async-requests" data-title="Async Requests">
        <h2>Async Requests</h2>
        <p>Run requests asynchronously to receive a Guzzle Promise which you can resolve later.</p>
        <pre><code>$promise = HetznerStorageBox::storageBoxes()->async()->all();

// Perform other operations...

$boxes = $promise->wait();</code></pre>
      </section>

      <section id="batch-operations" data-title="Batch Operations">
        <h2>Batch Concurrent Operations</h2>
        <p>Group operations to run concurrently using the batch processor.</p>
        <pre><code>$results = HetznerStorageBox::batch([
    fn () => HetznerStorageBox::storageBoxes()->find(1),
    fn () => HetznerStorageBox::storageBoxes()->find(2),
    fn () => HetznerStorageBox::storageBoxTypes()->find(4),
]);</code></pre>
      </section>

      <section id="retries-limits" data-title="Retries & Limits">
        <h2>Retries & Limits</h2>
        <p>The HTTP client automatically intercepts `429 Too Many Requests` status codes and delays subsequent calls until the timestamp supplied in the <code>RateLimit-Reset</code> header passes.</p>
        <pre><code>$rateLimit = HetznerStorageBox::rateLimit();
echo "Remaining calls: " . $rateLimit['remaining'];</code></pre>
      </section>

      <section id="exceptions" data-title="Exceptions">
        <h2>Exceptions</h2>
        <p>Exceptions map cleanly to HTTP status codes, inheriting from <code>HetznerException</code>.</p>
        <pre><code>use GhostCompiler\Hetzner\StorageBox\Exceptions\ValidationException;
use GhostCompiler\Hetzner\StorageBox\Exceptions\RateLimitException;

try {
    HetznerStorageBox::storageBoxes()->create(['name' => '']);
} catch (ValidationException $e) {
    $errors = $e->getErrors(); // field-specific errors
} catch (RateLimitException $e) {
    $seconds = $e->getSecondsUntilReset();
}</code></pre>
      </section>

      <section id="helper-cheatsheet" data-title="Helper Cheatsheet">
        <h2>Helper Cheatsheet</h2>
        <p>A quick-reference for the most commonly used methods.</p>
        <table>
          <thead>
            <tr>
              <th>Resource</th>
              <th>Action</th>
              <th>Method</th>
            </tr>
          </thead>
          <tbody>
            <tr>
              <td>Storage Boxes</td>
              <td>List all</td>
              <td><code>HetznerStorageBox::storageBoxes()->all()</code></td>
            </tr>
            <tr>
              <td>Storage Boxes</td>
              <td>Find one</td>
              <td><code>HetznerStorageBox::storageBoxes()->find($id)</code></td>
            </tr>
            <tr>
              <td>Storage Boxes</td>
              <td>Create</td>
              <td><code>HetznerStorageBox::storageBoxes()->create($data)</code></td>
            </tr>
            <tr>  
              <td>Subaccounts</td>
              <td>List all</td>
              <td><code>HetznerStorageBox::storageBoxes()->subaccounts($boxId)->all()</code></td>
            </tr>
            <tr>
              <td>Subaccounts</td>
              <td>Find one</td>
              <td><code>HetznerStorageBox::storageBoxes()->subaccounts($boxId)->find($id)</code></td>
            </tr>
            <tr>
              <td>Subaccounts</td>
              <td>Create</td>
              <td><code>HetznerStorageBox::storageBoxes()->subaccounts($boxId)->create($data)</code></td>
            </tr>
            <tr>
              <td>Snapshots</td>
              <td>List all</td>
              <td><code>HetznerStorageBox::storageBoxes()->snapshots($boxId)->all()</code></td>
            </tr>
            <tr>
              <td>Snapshots</td>
              <td>Find one</td>
              <td><code>HetznerStorageBox::storageBoxes()->snapshots($boxId)->find($id)</code></td>
            </tr>
            <tr>
              <td>Snapshots</td>
              <td>Create</td>
              <td><code>HetznerStorageBox::storageBoxes()->snapshots($boxId)->create($data)</code></td>
            </tr>
            <tr>
              <td>Storage Box Types</td>
              <td>List all</td>
              <td><code>HetznerStorageBox::storageBoxTypes()->all()</code></td>
            </tr>
            <tr>
              <td>Locations</td>
              <td>List all</td>
              <td><code>HetznerStorageBox::locations()->all()</code></td>
            </tr>
            <tr>
              <td>Actions</td>
              <td>List all</td>
              <td><code>HetznerStorageBox::actions()->all()</code></td>
            </tr>
          </tbody>
        </table>
      </section>
      
    </main>
  </div>

  <script src="app.js"></script>
</body>
</html>