EntwicklerTypeScript-SDK

Hostwolf Apps SDK für JavaScript & TypeScript

@hostwolf/apps-sdk ist der offizielle typisierte Client für die Hostwolf Apps API. Damit deployst und betreibst du deine Apps aus Code heraus: Projekte, Anwendungen, Deployments und Logs, Umgebungsvariablen, verwaltete Datenbanken, Autoscaling, Volumes und mehr.

npm install https://app.hostwolf.net/dl/hostwolf-apps-sdk-0.2.1.tgz

Das SDK kommt von app.hostwolf.net statt aus der npm-Registry. Der Befehl oben fügt es deiner package.json als "@hostwolf/apps-sdk": "https://app.hostwolf.net/dl/hostwolf-apps-sdk-0.2.1.tgz" hinzu, und du importierst es als @hostwolf/apps-sdk. pnpm, yarn und bun akzeptieren dieselbe URL. Jede Version hat ihre eigene URL; siehe Versionen.

Es läuft in Node 18+, Bun und Deno und hat keine Laufzeit-Abhängigkeiten.

Schnellstart#

Erstelle in der Konsole ein API-Token unter Schlüssel & Tokens → API-Tokens, dann:

import { HostwolfApps } from '@hostwolf/apps-sdk';

const hw = new HostwolfApps({ token: process.env.HOSTWOLF_APPS_TOKEN! });

const server = (await hw.servers.purchased()).find((s) => s.kind === 'app' && s.status === 'ready')!;
const { uuid: project } = await hw.projects.create({ name: 'shop' });

const app = await hw.applications.createFromGit({
    project_uuid: project,
    environment_name: 'production',
    server_uuid: server.server_uuid!,
    git_repository: 'https://github.com/acme/shop',
    git_branch: 'main',
    build_pack: 'nixpacks',
    ports_exposes: '3000',
});
await hw.applications.setEnvs(app.uuid, { NODE_ENV: 'production' });

const deployment = await hw.deployAndWait(app.uuid);
console.log(deployment.status, await hw.applications.logs(app.uuid, { lines: 20 }));

Ressourcen werden über ihre uuid angesprochen. Antwortobjekte verwenden die Feldnamen der API (snake_case), und ihre TypeScript-Typen werden exportiert, z. B. Application, Deployment und ManagedDatabase.

Optionen#

new HostwolfApps({
    token: '12|abc...',                              // required
    baseUrl: 'https://console.app.hostwolf.net',     // default
    timeoutMs: 30_000,                               // per request
    maxRetries: 2,                                   // 429s, and 502/503/504 on GET
    maxRetryWaitMs: 10_000,                          // longer Retry-After waits fail fast instead
    language: 'de',                                  // error messages in en, de, fr, es or fi
    fetch: customFetch,                              // optional
});

Projekte und Umgebungen#

MethodeWas es tut
projects.list() / get(uuid)Projekte; get enthält die Umgebungen
projects.create({ name })Neues Projekt mit einer Umgebung production
projects.update / deleteUmbenennen oder ein leeres Projekt löschen
projects.environments(uuid)Umgebungen eines Projekts
projects.environment(uuid, 'staging')Eine Umgebung (per Name oder uuid) mit ihren Ressourcen
projects.createEnvironment / updateEnvironment / deleteEnvironmentUmgebungen verwalten
projects.sharedEnvs / environmentSharedEnvsVariablen, die alle Apps eines Projekts oder einer Umgebung teilen

Anwendungen#

MethodeWas es tut
applications.list({ tag })Jede Anwendung des Teams
applications.get(uuid)Eine Anwendung mit ihren Einstellungen
applications.createFromGit(input)Aus einem öffentlichen Repository (nixpacks, railpack, dockerfile oder static)
applications.createFromPrivateRepo(input)Aus einem privaten Repository mit einem Deploy-Key (private_key_uuid)
applications.createFromGithubApp(input)Aus einem Repository einer verbundenen GitHub App (github_app_uuid); Push-Deployments funktionieren sofort
applications.createFromDockerfile(input)Aus Dockerfile-Text, kein Repository nötig
applications.createFromImage(input)Aus einem fertigen Image wie ghcr.io/acme/web
applications.update(uuid, changes)Name, Domains, Ports, Branch, Build- und Startbefehle, Health Check, Limits
applications.deploy(uuid, { force })Bauen und starten; liefert die deployment_uuid
applications.restart / stopNeu starten ohne Rebuild, oder stoppen
applications.logs(uuid, { lines })Aktuelle Container-Ausgabe (das Token braucht read:sensitive)
applications.metrics(uuid, { minutes })Verlauf von CPU und Arbeitsspeicher
applications.rollbackImages / rollback(uuid, tag)Ein früheres Image neu deployen
applications.delete(uuid, { deleteVolumes })Die App löschen (und standardmäßig ihre Volumes)

Apps laufen auf deinen App-Servern. server_uuid ist das Feld server_uuid aus servers.purchased() (oder die uuid aus servers.list()). Lass domains weg, um eine generierte Adresse unter *.app.hostwolf.net zu bekommen.

Umgebungsvariablen#

MethodeWas es tut
applications.envs(uuid)Variablen; Werte sind null, außer das Token hat read:sensitive
applications.setEnvs(uuid, { KEY: 'value' })Mehrere auf einmal anlegen oder ändern
applications.setEnv(uuid, { key, value, is_shown_once })Eine anlegen oder ändern
applications.createEnv / updateEnvStriktes Anlegen (409, wenn vorhanden) und Ändern
applications.deleteEnv(uuid, envUuid) / deleteEnvByKey(uuid, key)Eine Variable entfernen
teams.sharedEnvs() und Co.Teamweite Variablen, genutzt als {{team.KEY}}

Laufende Container sehen geänderte Variablen nach dem nächsten Deployment.

Deployments#

const { deployment_uuid } = await hw.applications.deploy(app.uuid);
const done = await hw.deployments.wait(deployment_uuid!, {
    timeoutMs: 10 * 60_000,
    onStatus: (status) => console.log(status), // queued, in_progress, finished
});

wait und deployAndWait werfen einen HostwolfAppsError mit dem Code deployment_failed (und den letzten Log-Zeilen in der Meldung), deployment_cancelled oder timeout. Übergib throwOnFailure: false, um stattdessen das fehlgeschlagene Deployment zurückzubekommen.

MethodeWas es tut
deployments.running()Wartende oder laufende Deployments im ganzen Team
deployments.get(uuid)Ein Deployment, mit seinem Build-Log in logs
parseDeploymentLogs(deployment)Das Build-Log als Zeilen ({ output, type, timestamp })
deployments.forApplication(uuid, { skip, take })Eine Seite der Deployments einer App, neueste zuerst
deployments.allForApplication(uuid)Asynchroner Iterator über alle
deployments.deploy({ uuid, tag, force })Mehrere Apps per uuid oder Tag deployen
deployments.cancel(uuid)Ein wartendes oder laufendes Deployment abbrechen

Server#

MethodeWas es tut
servers.purchased() / purchasedServer(uuid)Gekaufte App-Server und VPS: Größe, Stadt, Status, Preis und server_uuid
servers.list() / get(uuid)App-Server, auf denen Apps laufen können
servers.resources(uuid) / domains(uuid)Was auf einem Server läuft, und seine Domains
servers.metrics(uuid, { minutes })Verlauf von CPU und Arbeitsspeicher
servers.destinations(uuid)Die Docker-Netzwerke eines Servers (für Multi-Server)
servers.traffic(uuid, { month })Ausgehender Traffic eines gekauften Servers und seine bisherige Rechnung (1 TB pro Monat inklusive)
servers.teamTraffic({ month })Dasselbe für alle Server des Teams, mit der Summe
servers.regions({ size })Städte mit dem Monatspreis pro Größe und wo das Autoscaling Server hinzufügen darf

Server kaufen und kündigen geht nur in der Konsole, ein API-Token kann also nie einen Server kaufen.

Multi-Server#

Betreibe Kopien einer App auf mehreren deiner App-Server. Hostwolf baut das Image einmal, startet auf jedem Server eine Kopie und verteilt die Last zwischen ihnen.

await hw.applications.addServer(app.uuid, otherServer.server_uuid!);
await hw.deployAndWait(app.uuid);
console.log(await hw.applications.destinations(app.uuid)); // primary + copies

removeDestination(appUuid, destinationUuid) stoppt die Kopie auf diesem Server wieder.

Verwaltete Datenbanken#

PostgreSQL, MySQL/MariaDB und Redis auf Hostwolf-Datenbankservern, getrennt von deinen App-Servern, mit TLS, nächtlichen externen Backups und Point-in-Time-Recovery für PostgreSQL.

const plans = await hw.databases.catalog();             // engines, plans, pool sizes, regions, prices
const db = await hw.databases.create({ engine: 'postgres', plan: 'small', region: 'vultr:ams' });
await hw.databases.waitUntilReady(db.uuid);
await hw.databases.attach(db.uuid, app.uuid);           // DATABASE_URL (pooled) + DATABASE_DIRECT_URL
await hw.deployAndWait(app.uuid);

Eine Datenbank wird ab dem Moment ihrer Erstellung stündlich über dein Server-Abo abgerechnet. create sendet einen Idempotency-Key, sodass ein wiederholter Aufruf nie eine zweite Datenbank anlegt; übergib einen eigenen mit { idempotencyKey }.

connection(uuid) liefert zwei URLs. url läuft über den Connection-Pooler (PgBouncer auf 6432, ProxySQL auf 6033): Nutze sie in deiner App. direct_url erreicht die Datenbank selbst: Nutze sie für Migrationen, LISTEN/NOTIFY, Session-Einstellungen und lange Transaktionen. Redis hat eine URL.

MethodeWas es tut
databases.list() / get(uuid)Deine Datenbanken mit Engine, Tarif, Region, Status, Preis, Limits, upgrades und advice
databases.connection(uuid)Die gepoolte und die direkte URL mit Passwort (das Token braucht read:sensitive)
databases.attach(uuid, appUuid, { envKey, overwrite })Die gepoolte URL als DATABASE_URL und die direkte als DATABASE_DIRECT_URL speichern (Redis: REDIS_URL). Hat die App sie schon mit einem anderen Wert, wirft es conflict mit error.conflicts und error.suggestedEnvKey; übergib envKey oder overwrite: true
databases.upgrade(uuid, plan, { confirm: true })Auf einen größeren Tarif aus upgrades wechseln; die URL bleibt gleich
databases.setEvictionPolicy(uuid, policy)Redis: allkeys-lru für einen Cache, noeviction für Queues (sofort wirksam)
databases.backup(uuid)Jetzt ein zusätzliches Backup starten
databases.delete(uuid)Löschen und Abrechnung beenden; der Support kann sie 7 Tage lang wiederherstellen

upgrade ohne { confirm: true } ändert nichts: Es wirft einen HostwolfAppsError mit Code confirm_required, und error.quote enthält den neuen Monatspreis, den du zuerst anzeigst.

Autoscaling#

const { policy, blocker, status } = await hw.autoscaling.get(app.uuid);
await hw.autoscaling.enable(app.uuid, { min_copies: 1, max_copies: 4, monthly_cap_usd: 40 });

Autoscaling fügt App-Server in der Nähe deiner Besucher hinzu, wenn die CPU hoch bleibt, und entfernt sie, wenn sie sinkt. Hinzugefügte Server werden stündlich zum regulären Preis abgerechnet, bis zu monthly_cap_usd. blocker sagt, warum es sich noch nicht einschalten lässt (zum Beispiel kein aktives Server-Abo). Felder, die du weglässt, behalten ihren Wert; disable(uuid) schaltet es aus. cities listet die Regions-IDs, die allowed_cities für die App akzeptiert, mit dem dortigen Stundenpreis; servers.regions() listet jede Stadt.

Storage-Volumes#

Block Storage für deine App-Server und VPS: 10 bis 1000 GB, eingehängt unter /mnt/volume-<id> auf dem Server, stündlich abgerechnet. Aufrufe, die die Rechnung erhöhen, brauchen { confirm: true }; ohne das ändern sie nichts und werfen den Code confirm_required mit dem Preis in error.quote.

const offer = await hw.volumes.offer(server.uuid, { sizeGb: 50 });   // sizes allowed here and the price
const volume = await hw.volumes.create(server.uuid, { size_gb: 50, autogrow_max_gb: 200 }, { confirm: true });
await hw.volumes.waitFor(volume.id);                                  // mounted in about a minute
await hw.volumes.resize(server.uuid, volume.id, 100, { confirm: true });
MethodeWas es tut
volumes.list(serverUuid) / listAll({ status }) / get(id)Die Volumes eines Servers oder alle Volumes des Teams (status: 'detached' für abgehängte)
volumes.offer(serverUuid, { sizeGb })Ob die Stadt des Servers Volumes anbietet, die Größen und ein Preis
volumes.create(serverUuid, { size_gb, backups, autogrow_max_gb }, { confirm })Ein Volume hinzufügen (Backups standardmäßig an)
volumes.resize / setBackups / setAutogrow / updateVergrößern (nie verkleinern), Backups ein- oder ausschalten, das Auto-Grow-Limit setzen
volumes.detach(serverUuid, id)Aushängen und abhängen; es behält seine Dateien und wird weiter berechnet
volumes.attach(id, serverUuid, { confirm })Ein abgehängtes Volume an einen Server in derselben Stadt anhängen
volumes.backups(id) / backupNow(serverUuid, id)Wiederherstellbare Backups, oder jetzt eins anlegen
volumes.restore(serverUuid, id, backupId, 'same' | 'new', { confirm })In das Volume selbst oder in ein neues wiederherstellen
volumes.restoreDetached(id, backupId, serverUuid, { confirm })Das Backup eines abgehängten Volumes in ein neues Volume wiederherstellen
volumes.delete(id, { confirm: true })Es samt Dateien löschen

Wenn ein Server gelöscht wird, werden seine Volumes abgehängt und 7 Tage lang kostenlos aufbewahrt (kept_until); hänge eins in dieser Zeit an einen anderen Server in derselben Stadt an, sonst wird es gelöscht.

App-Volumes#

MethodeWas es tut
applications.storages(uuid)Persistente Volumes und Datei-Mounts
applications.createStorage(uuid, { type: 'persistent', name, mount_path })Ein Volume hinzufügen (neu deployen, um es einzuhängen)
applications.updateStorage / deleteStorageEine ändern oder löschen
applications.setVolumeBackup(uuid, storageUuid, { frequency: 'daily' })Ein Volume nach Zeitplan sichern
applications.runVolumeBackup / deleteVolumeBackupJetzt sichern oder den Zeitplan entfernen

Teams#

MethodeWas es tut
teams.current() / members()Das Team des Tokens und seine Mitglieder
teams.update({ name })Das Team umbenennen
teams.auditEvents({ page }) / allAuditEvents()Das Audit-Log des Teams (Admins und Owner)

Paginierung#

Listen, die die API seitenweise liefert, gibt es in zwei Formen: Eine Methode liefert eine einzelne Seite, und eine all…-Methode liefert einen asynchronen Iterator über jeden Eintrag. collect macht aus einem Iterator ein Array:

import { collect } from '@hostwolf/apps-sdk';

const deployments = await collect(hw.deployments.allForApplication(app.uuid), 100);
for await (const event of hw.teams.allAuditEvents({ action: 'deploy' })) console.log(event.description);

Fehler#

Fehlgeschlagene Aufrufe werfen einen HostwolfAppsError.

import { HostwolfAppsError } from '@hostwolf/apps-sdk';
try {
    await hw.applications.createFromDockerfile({ project_uuid, server_uuid, dockerfile });
} catch (e) {
    if (e instanceof HostwolfAppsError && e.isValidation) console.log(e.errors); // { field: ['message'] }
    else throw e;
}

Er hat diese Eigenschaften:

  • status: der HTTP-Status, oder 0 bei Netzwerkfehlern, Timeouts und fehlgeschlagenen Deployments.
  • code: ein kurzer Code wie validation_failed, unauthenticated, forbidden, not_found, rate_limited, confirm_required, deployment_failed oder timeout.
  • quote: bei confirm_required der Preis, den die API genannt hat (zeig ihn an und ruf dann erneut mit { confirm: true } auf).
  • retryAfter: bei einem 429 die Sekunden, bis der Aufruf wieder erlaubt ist. Kurze Wartezeiten werden für dich wiederholt; längere schlagen sofort fehl, mit diesem Feld gesetzt.
  • message: eine lesbare Beschreibung, inklusive Feldfehlern.
  • errors: Feldfehler eines 422, als { field: ['message'] }.
  • body: der Inhalt der Antwort. Ein 403 für etwas, das Hostwolf für dich erledigt (etwa Server hinzufügen), enthält einen docs-Link.
  • Kurz-Flags: isNotFound, isRateLimited, isValidation, isUnauthorized und isConfirmRequired.

Direkte Anfragen#

Wenn ein Endpunkt noch keinen Wrapper hat, ruf ihn direkt auf:

await hw.http.get(`/applications/${uuid}/scheduled-tasks`);
await hw.http.post(`/applications/${uuid}/scheduled-tasks`, { name: 'cleanup', command: 'npm run cleanup', frequency: '0 3 * * *' });

Pfade sind relativ zu /api/v1. Alle Endpunkte findest du in der API-Referenz.

Versionen#

DateiVerwendung
https://app.hostwolf.net/dl/hostwolf-apps-sdk-.tgzEine feste Version. Nutze diese in package.json.
https://app.hostwolf.net/dl/hostwolf-apps-sdk.tgzImmer die neueste Version. Paketmanager cachen nach URL, nimm also lieber versionierte URLs.
https://app.hostwolf.net/dl/index.jsonAktuelle Versionen mit SHA-256- und npm-Integritäts-Hashes.

Zum Upgraden installierst du die neuere versionierte URL.

Lizenz#

MIT

Kommst du nicht weiter, oder fehlt ein Endpunkt? Sag es uns unter app.hostwolf.net/contact oder [email protected].