EntwicklerTypeScript-SDK
Hostwolf-SDK für JavaScript & TypeScript
@hostwolf/sdk ist der offizielle typisierte Client für die Hostwolf-API. Damit steuerst du deine Gameserver per Code: Power, Konsole, Dateien, Backups, Mods, Spieler, Zeitpläne und mehr.
npm install https://hostwolf.net/dl/hostwolf-sdk-0.1.8.tgz
Das SDK wird von hostwolf.net statt aus der npm-Registry ausgeliefert. Der Befehl oben trägt es in deine package.json als "@hostwolf/sdk": "https://hostwolf.net/dl/hostwolf-sdk-0.1.8.tgz" ein, und du importierst es als @hostwolf/sdk. pnpm, yarn und bun akzeptieren dieselbe URL. Jede Version hat ihre eigene URL; siehe Versionen.
Es läuft auf Node 18+, Bun und Deno und hat keine Laufzeitabhängigkeiten. Für die Live-Konsole auf Node-Versionen vor 22 zusätzlich npm install ws ausführen.
Siehe auch: REST-API-Referenz · MCP-Server · Änderungsprotokoll · API-Schlüssel erstellen
Schnellstart#
Erstelle einen API-Schlüssel unter panel.hostwolf.net/account/api, dann:
import { Hostwolf } from '@hostwolf/sdk';
const hw = new Hostwolf({ apiKey: process.env.HOSTWOLF_API_KEY! });
const { data: servers } = await hw.servers.list();
for (const s of servers) {
const res = await hw.servers.resources(s.identifier);
console.log(s.name, s.egg_name, res.current_state);
}
const id = servers[0].identifier;
await hw.servers.command(id, 'say Backup in 1 minute');
await hw.backups.create(id, { name: 'before-update' });
console.log(await hw.tail(id, { lines: 20 }));
Jede Server-Methode nimmt als erstes Argument den kurzen identifier des Servers (die 8 Zeichen in seiner Panel-URL) oder seine UUID. Antwortobjekte verwenden die Feldnamen der API (snake_case). Ihre TypeScript-Typen werden exportiert, z. B. Server, Backup und FileObject.
Optionen#
new Hostwolf({
apiKey: 'ptlc_...', // required
baseUrl: 'https://panel.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
fetch: customFetch, // optional
WebSocket: customWebSocket, // optional, for the console
});
Server#
REST: Server-Endpunkte
| Methode | Was sie tut |
|---|---|
servers.list({ page, per_page }) | Eine Seite deiner Server: { data, pagination } |
servers.listAll() | Async-Iterator über alle Server |
servers.get(id, ['egg', 'subusers']) | Serverdetails mit Zuweisungen und Startvariablen |
servers.resources(id) | Live-Status, CPU, Arbeitsspeicher, Speicherplatz, Netzwerk, Laufzeit |
servers.power(id, signal) / start / stop / restart / kill | Power-Aktionen |
servers.command(id, command) | Konsolenbefehl ausführen |
servers.activity(id) | Aktivitätsprotokoll (paginiert) |
servers.rename(id, name, description?) | Umbenennen |
servers.reinstall(id) | Installationsskript erneut ausführen |
servers.setDockerImage(id, image) | Laufzeit-Image wechseln (z. B. Java-Version) |
servers.websocket(id) | Konsolen-Token und Socket-URL (die Konsole unten nutzt sie) |
servers.waitForState(id, 'running', { timeoutMs }) | Wartet, bis der Server einen Zustand erreicht, z. B. nach start(). Liefert seine Ressourcen |
await hw.servers.start(id);
await hw.servers.waitForState(id, 'running'); // default timeout 5 minutes
const line = await hw.waitForConsole(id, /There are \d+ of a max/, { send: 'list' });
Konsole#
const c = await hw.console(id).connect();
c.on('output', (line) => console.log(line));
c.on('stats', (s) => console.log(s.state, s.memory_bytes));
c.requestLogs(); // replay recent output
c.sendCommand('list');
// later
c.close();
// Or just fetch the recent lines (ANSI colours stripped):
const lines = await hw.tail(id, { lines: 50 });
// Or wait for one line: send a command and get its reply.
const reply = await hw.waitForConsole(id, /There are \d+ of a max/, { send: 'list', timeoutMs: 20_000 });
waitForConsole(id, pattern, { send, includeRecent, timeoutMs }) liefert die erste Zeile, die zu einem String oder RegExp passt. includeRecent durchsucht auch das jüngste Log, und nach timeoutMs (standardmäßig 30 Sekunden) schlägt es mit dem Code Timeout fehl.
Die Konsole verbindet sich direkt mit dem Node deines Servers und erneuert ihr Token automatisch.
Events:
readyundcloseoutputstatusstatsinstall outputdaemon messageunddaemon errorerror
Dateien#
REST: Datei-Endpunkte
await hw.files.list(id, '/plugins');
const props = await hw.files.read(id, 'server.properties');
await hw.files.write(id, 'server.properties', props.replace('pvp=true', 'pvp=false'));
await hw.files.rename(id, '/', [{ from: 'world', to: 'world-old' }]);
await hw.files.delete(id, '/logs', ['old.log']);
await hw.files.createFolder(id, '/', 'maps');
await hw.files.compress(id, '/', ['world']); // returns the archive
await hw.files.decompress(id, '/', 'world.tar.gz');
await hw.files.pull(id, 'https://example.com/map.zip', { directory: '/maps' });
await hw.files.exists(id, 'plugins/LuckPerms.jar'); // true or false
// Binary files: upload and download go straight to the server's node.
await hw.files.upload(id, '/plugins', jarBytes, 'MyPlugin.jar'); // Blob, ArrayBuffer, Uint8Array or string
const buf = await hw.files.download(id, 'world.tar.gz'); // ArrayBuffer, in memory
// Or work with the signed URLs yourself, e.g. to stream a big file:
const url = await hw.files.downloadUrl(id, 'world.tar.gz');
const uploadUrl = await hw.files.uploadUrl(id); // POST multipart "files" to `${uploadUrl}&directory=/maps`
Das Lesen einer nicht vorhandenen Datei wirft einen HostwolfError mit Status 404 und Code NotFound. Außerdem gibt es files.copy(id, path) und files.chmod(id, root, [{ file, mode }]).
Backups#
REST: Backup-Endpunkte
const { data } = await hw.backups.list(id);
const b = await hw.backups.create(id, { name: 'nightly', is_locked: false });
await hw.backups.waitUntilDone(id, b.uuid); // polls until finished; rejects with code BackupFailed if it failed
await hw.backups.get(id, b.uuid);
await hw.backups.restore(id, b.uuid, { truncate: false });
await hw.backups.toggleLock(id, b.uuid);
await hw.backups.downloadUrl(id, b.uuid);
await hw.backups.delete(id, b.uuid);
await hw.backups.smartState(id);
await hw.backups.setSmart(id, true); // automatic backups before risky changes
Du kannst 2 Backups pro 10 Minuten erstellen. Darüber hinaus schlägt create() sofort fehl, mit gesetztem error.retryAfter.
Datenbanken#
REST: Datenbank-Endpunkte · Anleitung: Eine MySQL-Datenbank mit deinem Server nutzen
Jeder Server hat bis zu 5 MySQL-kompatible Datenbanken auf seinem eigenen Rechner. Die erste startet außerdem den Datenbankdienst, was etwa 40 Sekunden dauert: Die API antwortet mit 202 und create() gibt ein ausstehendes Ergebnis zurück, außer du übergibst wait.
import { isDatabasePending } from '@hostwolf/sdk';
// Simplest: wait until the database exists (up to 2 minutes), then use it
const db = await hw.databases.create(id, 'luckperms', { wait: true });
console.log(db.host.address, db.name, db.username, db.relationships?.password?.password);
// Or handle the pending answer yourself
const r = await hw.databases.create(id, 'coreprotect');
if (isDatabasePending(r)) {
const ready = await hw.databases.waitUntilReady(id, 'coreprotect', { timeoutMs: 120_000 });
}
await hw.databases.service(id); // { status, local_address, public_address, other_servers }
await hw.databases.setRemoteAccess(id, db.id, { remoteIps: ['203.0.113.7'], anywhere: false });
await hw.databases.rotatePassword(id, db.id);
const dump = await hw.databases.export(id, db.id); // .sql.gz bytes
await hw.databases.import(id, db.id, dump, { filename: 'luckperms.sql.gz' });
await hw.databases.delete(id, db.id);
Gameserver verbinden sich mit db.host (172.18.0.1:3306). db.name trägt ein Präfix, zum Beispiel s12_luckperms; waitUntilReady() nimmt den Namen, den du angefragt hast. Die anderen Methoden nehmen db.id. waitUntilReady() schlägt mit dem Code DatabaseSetupFailed fehl, wenn der Dienst nicht starten konnte, oder mit Timeout. Du kannst 2 Datenbanken pro Minute anlegen und 6 pro Minute exportieren oder importieren.
Spiel, Spieler und Einstellungen#
await hw.game.versions(id); // current + available versions
await hw.game.setVersion(id, '1.21.4'); // switches and reinstalls; { resetWorld, force } for downgrades
await hw.game.settings(id); // friendly settings, grouped by file ({ supported: false } on Java Minecraft)
await hw.game.updateSettings(id, { difficulty: 'hard' }, true);
await hw.game.diagnostics(id); // "Fix my server" checks
await hw.players.info(id); // online count (+ names on Minecraft)
await hw.players.list(id); // live list via RCON/query
await hw.players.kick(id, 'Steve');
await hw.players.ban(id, { steamid: '7656...' });
await hw.players.unban(id, 'Steve');
await hw.players.broadcast(id, 'Restarting in 5 minutes');
Mods, Plugins und Modpacks#
const profile = await hw.addons.profile(id); // sources, install dir, frameworks
const { items } = await hw.addons.search(id, 'modrinth', 'worldedit');
await hw.addons.install(id, 'modrinth', items[0].id); // newest compatible version
const installed = await hw.addons.installed(id, { checkUpdates: true });
await hw.addons.uninstall(id, installed.addons[0].id);
const packs = await hw.modpacks.search(id, 'cobblemon');
await hw.modpacks.install(id, packs.items[0].id, { freshWorld: false });
Weitere Addon-Aufrufe:
addons.versions(id, source, project): Versionen, als kompatibel markiert oder nicht.addons.installFeatured(id, project): empfohlene Addons mit einem Klick, ausprofile.featured.addons.upload(id, blob, name): wennprofile.uploadgesetzt ist. Sonst legst du Dateien mitfiles.upload()inprofile.directoryab.addons.removeManual(id, file): entfernt eine Datei, die du selbst hinzugefügt hast.addons.setFramework(id, framework): z. B. Oxide oder Carbon auf Rust.addons.workshop/setWorkshop/workshopPreview(Spiele mit Steam Workshop).addons.cs2Framework(id, { repair }): Metamod + CounterStrikeSharp auf CS2.modpacks.current/versions/remove.
Mehr#
| Namespace | Methoden |
|---|---|
startup | get(id), setVariable(id, key, value) |
schedules | list, get, create, update, execute, delete, createTask, updateTask, deleteTask |
databases | list(id, { includePassword }), create, waitUntilReady, service, setRemoteAccess, rotatePassword, export, import, delete. Siehe Datenbanken |
network | allocations, createAllocation, setNotes, setPrimary, deleteAllocation |
users | list, get, create(id, publicId, permissions), update, delete, permissions(). Konten fügst du über ihre öffentliche ID (HW-XXXXXX) hinzu, nicht per E-Mail |
notifications | Discord-Benachrichtigungen pro Server: get, update, test |
domain | Eigene Domain: get, check, set, remove |
mcNetwork | Velocity/BungeeCord-Netzwerke: get, link, unlink, installFabricProxy |
account | get, updateEmail, updatePassword, updateProfile, updateLanguage(locale, currency?), uploadAvatar, deleteAvatar, activity, getNotifications, updateNotifications, testNotification, getCurseforgeKey, setCurseforgeKey, getFactorioCredentials, setFactorioCredentials |
account.apiKeys | list, create(description, allowedIps?) (liefert den vollständigen Schlüssel einmalig als token), delete(identifier) |
account.sshKeys | list, create(name, publicKey), delete(fingerprint) |
store | Nur lesend: catalog(), orders(), billing() |
Server bestellen, kündigen und bezahlen geht nur im Panel, ein API-Schlüssel kann also nie Geld ausgeben.
Paginierung#
servers.list, backups.list und die activity-Methoden liefern { data, pagination }. So gehst du alle Server durch:
import { collect } from '@hostwolf/sdk';
const all = await collect(hw.servers.listAll());
Fehler#
Fehlgeschlagene Aufrufe werfen einen HostwolfError.
import { HostwolfError } from '@hostwolf/sdk';
try {
await hw.game.setVersion(id, '1.16.5');
} catch (e) {
if (e instanceof HostwolfError && e.code === 'downgrade_requires_reset') {
// older versions can't load a newer world: retry with { resetWorld: true } or { force: true }
}
}
Er hat diese Eigenschaften:
status: der HTTP-Status, oder 0 bei Netzwerkfehlern und Zeitüberschreitungen.code: der Fehlercode. Neben den Codes der API verwendet das SDKNetworkError(keine Antwort oder Zeitüberschreitung),Timeout(einwaitFor…- oderwaitUntil…-Helfer hat aufgegeben),BackupFailed,DatabaseSetupFailed,DatabaseNotCreatedundNotFound(eine fehlende Datei).retryAfter: bei einem 429 die Sekunden, bis die Aktion wieder erlaubt ist. Kurze Wartezeiten werden automatisch wiederholt; längere (wie das Limit von 2 Backups pro 10 Minuten) schlagen sofort fehl und setzen diesen Wert.message: eine lesbare Beschreibung.errors: die rohe Fehlerliste.body: der Inhalt der Antwort.- Kurzabfragen:
isNotFound,isRateLimitedundisValidation.
Häufige Fälle:
401: der Schlüssel ist falsch, unvollständig oder widerrufen.403mitThis IP address (…) does not have permission: der Schlüssel hat eine IP-Freigabeliste ohne deine Adresse.409 downgrade_requires_resetund422 unknown_version: vongame.setVersion().429: ein Ratenlimit; sieheretryAfter.- Ein Schreibaufruf mit Zeitüberschreitung (Status 0) kann auf dem Panel trotzdem fertig werden. Prüfe das, bevor du ihn wiederholst.
Direkte Anfragen#
Hat ein Endpunkt noch keine Methode, ruf ihn direkt auf:
await hw.http.get(`/servers/${id}/players`);
await hw.http.post(`/servers/${id}/command`, { command: 'list' });
Pfade sind relativ zu /api/client, und Antworten werden genauso entpackt wie bei den fertigen Methoden. Alle Endpunkte findest du in der API-Referenz.
Versionen#
| Datei | Verwendung |
|---|---|
https://hostwolf.net/dl/hostwolf-sdk- | Eine feste Version. Verwende diese in package.json. |
https://hostwolf.net/dl/hostwolf-sdk.tgz | Immer die neueste Version. Paketmanager cachen nach URL, daher sind versionierte URLs besser. |
https://hostwolf.net/dl/index.json | Aktuelle Versionen mit SHA-256- und npm-Integritäts-Hashes. |
Zum Aktualisieren installierst du die neuere versionierte URL.
Lizenz#
MIT