import type { CloudDriver, ComputeStackOutputs, ComputeTarget, FindComputeTargetsOptions, ProvisionComputeOptions, RemoteDeployResult, RunRemoteDeployOptions, UploadReleaseOptions, UploadReleaseResult } from '@ts-cloud/core';
import { HetznerClient } from './client';
export declare function formatSshFailure(error: unknown): string;
export interface HetznerDriverOptions {
    apiToken?: string;
    sshPrivateKeyPath?: string;
    sshPublicKeyPath?: string;
    sshUser?: string;
    location?: string;
    client?: HetznerClient;
    /**
     * After the server reports `running`, block until SSH is reachable and
     * cloud-init has finished before returning from provisioning. Disable in
     * tests (or fast-path provisioning) to avoid real network waits.
     * @default true
     */
    waitForBoot?: boolean;
    /**
     * Tunables for the SSH-readiness / cloud-init wait loops. Overridable so
     * tests can use tiny intervals.
     */
    bootWait?: {
        /** Delay between SSH probe attempts (ms). @default 5000 */
        sshIntervalMs?: number;
        /** Max time to wait for SSH to accept connections (ms). @default 300000 */
        sshTimeoutMs?: number;
        /** Delay between `cloud-init status` polls (ms). @default 5000 */
        cloudInitIntervalMs?: number;
        /** Max time to wait for cloud-init to finish (ms). @default 600000 */
        cloudInitTimeoutMs?: number;
    };
}
export declare class HetznerDriver implements CloudDriver {
    readonly name: 'hetzner';
    readonly usesCloudFormation = false;
    private client;
    private sshPrivateKeyPath;
    private sshPublicKeyPath;
    private sshUser;
    private location;
    private waitForBoot;
    private bootWait;
    constructor(options?: HetznerDriverOptions);
    provisionComputeInfrastructure(options: ProvisionComputeOptions): Promise<ComputeStackOutputs>;
    /**
     * Provision a load-balanced fleet: a private network, a dedicated services
     * box (DB/cache/search), N app servers (nginx + php-fpm), and a load
     * balancer fronting the app servers. App servers connect to the services box
     * over the private network (wired into their `.env` at deploy time).
     */
    private provisionFleet;
    /**
     * Provision a load-balanced **bun/node/deno** fleet: a private network, N
     * app servers running the runtime directly (no local rpx gateway — the LB
     * box reaches them over the private network), and ONE dedicated box running
     * only the rpx gateway in load-balancing mode, fronting every `server-app`
     * site's port across all app boxes (rpx v0.11.24+ multi-upstream routes with
     * health-check failover — see {@link buildRpxLbConfig}).
     *
     * Mirrors {@link provisionFleet}'s idempotency/reconciliation/teardown
     * patterns (reuse-by-label, create only the delta, destroy extras on
     * scale-down), but fronts the app boxes with rpx instead of Hetzner's native
     * Load Balancer product — this is the bun/rpx analogue of that PHP path.
     *
     * Edge case: a bun app that sets `compute.servicesServer` explicitly (wants
     * a dedicated DB/cache box) but only ever runs one app server still lands
     * here (see the `topology.dedicatedServices` dispatch condition) — in that
     * case we still provision the dedicated services box (provider/runtime
     * -agnostic infra, reusing the same PHP-fleet services-box mechanism is
     * unnecessary since bun app servers need no PHP/nginx either way) but only
     * stand up the rpx LB box when `topology.loadBalancer` is actually true
     * (i.e. more than one app server, or `compute.server?.loadBalancer`).
     */
    private provisionBunFleet;
    /**
     * Tear down the compute — single server or full fleet (load balancer, all
     * app + services servers, firewalls, and the private network) — and clear
     * local state.
     */
    destroyCompute(options: ProvisionComputeOptions): Promise<{
        destroyed: string[];
    }>;
    getComputeOutputs(options: ProvisionComputeOptions): Promise<ComputeStackOutputs>;
    uploadRelease(options: UploadReleaseOptions): Promise<UploadReleaseResult>;
    findComputeTargets(options: FindComputeTargetsOptions): Promise<ComputeTarget[]>;
    /**
     * Attach this project to compute owned by another ts-cloud project.
     *
     * The owner's Hetzner labels are the source of truth. We write a tenant-local
     * state pin so all later deploy operations resolve the same app/LB targets,
     * even when several unrelated managed boxes exist in the Hetzner project.
     */
    private attachToComputeInfrastructure;
    runRemoteDeploy(options: RunRemoteDeployOptions): Promise<RemoteDeployResult>;
    /**
     * Ensure the local SSH public key is registered in the Hetzner project and
     * return its id, so the freshly created server authorizes the same key the
     * deploy step (SCP/SSH) uses. Without this, deploys fail with "Permission
     * denied (publickey)" because the server has no authorized keys.
     */
    private ensureSshKey;
    /** getServer that returns null instead of throwing when the server is gone. */
    private tryGetServer;
    /**
     * Rewrite a bun-fleet LB box's rpx route fragment (`sites.d/<slug>.json`)
     * from the CURRENT sites model and the CURRENT app-box upstreams, then
     * restart the gateway so the assembler re-merges. The LB box long outlives
     * any single provision — without this refresh, the routes frozen at its
     * first boot never learn about sites added/removed later or a changed
     * app-fleet upstream set.
     *
     * App boxes without a routable address are dropped from the upstream list;
     * when NO app box is routable the refresh is skipped entirely — rewriting
     * the fragment with zero real upstreams would turn every route into a
     * guaranteed 502, strictly worse than leaving the stale fragment in place.
     * An SSH failure propagates: a deploy that cannot reach its LB to update
     * routes must fail loudly, not silently serve stale ones.
     */
    private refreshLbRoutes;
    /**
     * Look up an existing ts-cloud server for this project/environment by labels
     * (falling back to name match). Used for idempotency when local state is
     * missing, so re-running deploy doesn't spin up a duplicate server.
     */
    private findExistingServer;
    /**
     * Idempotent firewall: reuse an existing firewall with the same name rather
     * than creating a duplicate on every deploy. When found, its rules are
     * updated to the desired set so config changes (new ports) still apply.
     */
    private ensureFirewall;
    /**
     * Collect the upstream app ports that must be reachable on the box. ts-cloud
     * does not front traffic with its own proxy, so each site's app port is
     * opened directly. Drops 80/443 (always handled by the firewall base rules).
     */
    private collectUpstreamPorts;
    private sleep;
    /**
     * Probe SSH (a trivial `true` over the connection) with backoff until the box
     * accepts connections. A freshly booted server refuses SSH for a few seconds
     * while sshd starts; without this, the very next deploy command races it and
     * fails with "Connection refused".
     */
    private waitForSshReady;
    /**
     * Block until cloud-init finishes (`cloud-init status --wait`). cloud-init is
     * what installs the runtime; deploying before it completes leaves the
     * release pointing at a half-provisioned box (missing `bun`).
     */
    private waitForCloudInit;
    private outputsFromState;
    /**
     * SSH options for connecting to freshly-created cloud servers. Host-key
     * pinning is disabled (`StrictHostKeyChecking=no` + `UserKnownHostsFile`
     * `/dev/null`): the box is identified + trusted via the Hetzner API, and
     * providers recycle public IPs, so a stale `known_hosts` entry from a prior
     * (now-deleted) server would otherwise abort the deploy with
     * `REMOTE HOST IDENTIFICATION HAS CHANGED`.
     */
    private static readonly SSH_HOST_KEY_OPTS;
    /**
     * Keepalive + connect timeout for every ssh/scp. Without these a connection
     * that stalls mid-transfer (a flaky network, an sshd hiccup) hangs the deploy
     * FOREVER — execSync has no timeout, so a dead socket blocks the whole run.
     * ServerAlive probes abort a silent connection after ~60s (15s × 4) so the
     * transfer fails loudly (and, for scp, is retried) instead of wedging.
     */
    private static readonly SSH_KEEPALIVE_OPTS;
    private sshBaseArgs;
    private scpToHost;
    private sshExec;
}
export declare function resolveHetznerDeployBucketName(slug: string, environment: string): string;
