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.
Siehe auch: REST-API-Referenz · MCP-Server · Changelog · API-Token holen
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#
REST: Projekt-Endpunkte
| Methode | Was es tut |
|---|---|
projects.list() / get(uuid) | Projekte; get enthält die Umgebungen |
projects.create({ name }) | Neues Projekt mit einer Umgebung production |
projects.update / delete | Umbenennen 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 / deleteEnvironment | Umgebungen verwalten |
projects.sharedEnvs / environmentSharedEnvs | Variablen, die alle Apps eines Projekts oder einer Umgebung teilen |
Anwendungen#
REST: Anwendungs-Endpunkte
| Methode | Was 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 / stop | Neu 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#
| Methode | Was 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 / updateEnv | Striktes 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#
REST: Deployment-Endpunkte
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.
| Methode | Was 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#
| Methode | Was 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.
| Methode | Was 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#
REST: Autoscaling-Endpunkte
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 });
| Methode | Was 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 / update | Vergröß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#
| Methode | Was 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 / deleteStorage | Eine ändern oder löschen |
applications.setVolumeBackup(uuid, storageUuid, { frequency: 'daily' }) | Ein Volume nach Zeitplan sichern |
applications.runVolumeBackup / deleteVolumeBackup | Jetzt sichern oder den Zeitplan entfernen |
Teams#
| Methode | Was 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 wievalidation_failed,unauthenticated,forbidden,not_found,rate_limited,confirm_required,deployment_failedodertimeout.quote: beiconfirm_requiredder 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 einendocs-Link.- Kurz-Flags:
isNotFound,isRateLimited,isValidation,isUnauthorizedundisConfirmRequired.
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#
| Datei | Verwendung |
|---|---|
https://app.hostwolf.net/dl/hostwolf-apps-sdk- | Eine feste Version. Nutze diese in package.json. |
https://app.hostwolf.net/dl/hostwolf-apps-sdk.tgz | Immer die neueste Version. Paketmanager cachen nach URL, nimm also lieber versionierte URLs. |
https://app.hostwolf.net/dl/index.json | Aktuelle Versionen mit SHA-256- und npm-Integritäts-Hashes. |
Zum Upgraden installierst du die neuere versionierte URL.
Lizenz#
MIT