/**
 * Generate the rpx reverse-proxy gateway config + provisioning from the `sites`
 * model.
 *
 * ts-cloud's per-site deploy model resolves each site to one of three kinds
 * (see {@link import('../../deploy/site-target').resolveSiteKind}):
 *  - `server-app`    — a dynamic app running on a port (systemd service);
 *  - `server-static` — a static site shipped to `/var/www/<name>`;
 *  - `bucket`        — object storage + CDN (not on the box; ignored here).
 *
 * The rpx gateway fronts :80/:443 on the box and routes by host **and path**:
 * several sites can share one `domain` on different `path`s (e.g.
 * `stacksjs.com/api/*` → app on :3000, `stacksjs.com/docs*` → `/var/www/docs`,
 * `stacksjs.com/` → `/var/www/public`). This module maps the sites model to the
 * rpx `proxies` array so `buddy deploy` can ship the config + wire the gateway.
 *
 * It replaces the old Caddyfile generation — pantry/stacks use rpx (their own
 * tooling), so the gateway is rpx, not Caddy.
 */
import type { ComputeProxyConfig, RpxLoadBalancerConfig, SiteConfig, SiteRedirectConfig } from '@ts-cloud/core';
/** Default directory on the box that holds real per-domain TLS certs. */
export declare const DEFAULT_RPX_CERTS_DIR = "/etc/rpx/certs";
/** Default webroot the gateway serves ACME http-01 challenges from on `:80`. */
export declare const DEFAULT_ACME_WEBROOT = "/var/www/acme-challenge";
/** A normalized redirect target on an {@link RpxRoute} (see rpx's `redirect`). */
export interface RpxRedirect {
    to: string;
    status?: 301 | 302 | 307 | 308;
    preservePath?: boolean;
}
/**
 * Normalize a site's `redirect` (string shorthand or object) into the minimal
 * {@link RpxRedirect} the gateway config carries. Optional fields are omitted
 * when unset so rpx applies its own defaults (status `301`, path-preserving).
 */
export declare function normalizeSiteRedirect(input: string | SiteRedirectConfig): RpxRedirect;
/** A single rpx proxy route, mapped from one site. */
export interface RpxRoute {
    /** Public host this route is served under (the site's `domain`). */
    to: string;
    /** Path prefix within the host this route owns (e.g. `/api`). Omitted = `/`. */
    path?: string;
    /**
     * Upstream(s) for a `server-app` route: a single `host:port` for a co-located
     * (single-box) deploy, or an array of `host:port` — one per app box — when the
     * route is fronted by a dedicated load-balancer box (see
     * {@link buildRpxLbConfig}). rpx turns an array into a real load-balanced pool
     * with automatic health-check failover (see rpx's `ProxyFrom`/`UpstreamTarget`).
     */
    from?: string | string[];
    /**
     * Static serving for a `server-static` route. rpx reads `spa` and
     * `pathRewriteStyle` ONLY from the object form (`{ dir, spa, pathRewriteStyle }`);
     * a bare string disables both (rpx forces `spa: false`, `pathRewriteStyle:
     * 'directory'`). So SPA fallback + flat-URL sites MUST use the object form —
     * see `buildRpxConfig`. The string shorthand remains valid for a plain
     * directory served with the route-level `cleanUrls`.
     */
    static?: string | {
        dir: string;
        spa?: boolean;
        pathRewriteStyle?: 'directory' | 'flat';
        maxAge?: number;
    };
    /**
     * Redirect target for a `redirect` site — the gateway answers `to` (the host)
     * with an HTTP redirect here instead of proxying/serving. The request path +
     * query are appended unless `preservePath` is `false`.
     */
    redirect?: RpxRedirect;
    /** Strip `.html` and resolve clean URLs (set for static sites). */
    cleanUrls?: boolean;
    /** SPA fallback for static sites. */
    spa?: boolean;
    /**
     * HTTP Basic auth gate for this route (from the site's `auth`). rpx challenges
     * every request to the route until valid credentials are supplied — this is
     * how the management dashboard (and other protected sites) stay private behind
     * rpx, the same way the nginx driver applies htpasswd.
     */
    auth?: {
        username: string;
        password: string;
        realm?: string;
    };
    /**
     * Load-balancing strategy/health-check tuning for a multi-upstream `from`
     * (see {@link ComputeProxyConfig.loadBalancer}). Only meaningful when `from`
     * is an array — rpx ignores it for a single-upstream route.
     */
    loadBalancer?: RpxLoadBalancerConfig;
    /** Stable id used when rpx registers the route. Derived from `to`+`path`. */
    id: string;
}
/**
 * Resolve a site's `auth` into the rpx route auth shape, or `undefined` when the
 * site is public. Mirrors the management-dashboard preset: auth applies only when
 * enabled (default) AND a password is present — no password is ever invented.
 */
export declare function resolveRouteAuth(site: SiteConfig): RpxRoute['auth'];
/** The rpx daemon/proxy config produced from a sites model. */
export interface RpxGatewayConfig {
    /** Multi-proxy route list (host + path keyed). */
    proxies: RpxRoute[];
    /**
     * Production per-domain SNI certs: rpx serves a real PEM per server name from
     * this directory (`<domain>.crt` / `<domain>.key`).
     */
    productionCerts: {
        certsDir: string;
    };
    /**
     * On-demand TLS (opt-in): lazily issue a real cert for an approved host the
     * first time it's needed. The site domains form the allowlist.
     */
    onDemandTls?: {
        enabled: true;
        allowedSuffixes: string[];
        email?: string;
        certsDir: string;
        staging: boolean;
    };
    /**
     * Directory the gateway serves ACME http-01 challenge tokens from on `:80`
     * before redirecting to HTTPS. Set when ts-cloud manages certs so the renewal
     * cron (`tlsx acme:renew --webroot`) can issue/renew without taking the gateway
     * down to free `:80`. Omitted ⇒ the `:80` server only redirects.
     */
    acmeChallengeWebroot?: string;
    /** Always `true` — the gateway terminates TLS on the box. */
    https: true;
    /** Never touch `/etc/hosts` on a real server with real DNS. */
    hostsManagement: false;
    /** Don't remove certs/hosts on exit. */
    cleanup: {
        hosts: false;
        certs: false;
    };
    /**
     * Origin lockdown (from `proxy.cdn` when a `secret` is set): rpx rejects
     * direct hits to the CDN-fronted hosts that lack the shared-secret header.
     */
    originGuard?: {
        header: string;
        value: string;
        hosts: string[];
    };
}
export interface BuildRpxConfigOptions {
    /** Proxy config from `infrastructure.compute.proxy`. */
    proxy: ComputeProxyConfig;
    /** Directory static sites are shipped to. @default '/var/www' */
    wwwRoot?: string;
    /**
     * Project slug — server-static routes are served from the slug-namespaced
     * install dir (`<wwwRoot>/<slug>-<name>/current`), matching where the deploy
     * ships them (see {@link siteInstallBase}). Omitted ⇒ `app` (single-tenant
     * back-compat). MUST be set on a shared box or static routes point at the
     * wrong directory.
     */
    slug?: string;
}
/**
 * Normalize a path prefix to a leading-slash, no-trailing-slash form, or
 * `undefined` for the host default. Mirrors rpx's `normalizePathPrefix`.
 */
export declare function normalizeRoutePath(path: string | undefined): string | undefined;
/** Derive a stable, filesystem/registry-safe id from a host (+ optional path). */
export declare function deriveRouteId(to: string, path?: string): string;
/**
 * Map the sites model to an rpx gateway config. Each non-bucket site with a
 * `domain` becomes a route:
 *  - `server-app`    → `{ to: domain, path, from: 'localhost:<port>' }`
 *  - `server-static` → `{ to: domain, path, static: '<wwwRoot>/<name>' }`
 *
 * Routes are grouped by domain so rpx's path-based routing can serve an app +
 * several static dirs under one host. Bucket sites and sites without a `domain`
 * (or a `server-app` without a `port`) are skipped.
 *
 * This is the single-box path: every `server-app` route always resolves to
 * `localhost:<port>` — unchanged, byte-for-byte, from before load-balanced
 * fleets existed. Use {@link buildRpxLbConfig} for a dedicated LB box fronting
 * more than one app box.
 */
export declare function buildRpxConfig(sites: Record<string, SiteConfig | undefined>, options: BuildRpxConfigOptions): RpxGatewayConfig;
/** An app box's addresses, as known to the LB box building routes to it. */
export interface RpxLbAppBox {
    /** Private IP of the app box, reachable from the LB over the fleet's private network. Preferred. */
    privateIp?: string;
    /** Public IP of the app box — used only when no private IP is available. */
    publicIp?: string;
}
/**
 * Build the rpx gateway config for a **dedicated load-balancer box**: like
 * {@link buildRpxConfig}, but every `server-app` route's `from` is an array of
 * `host:port` — one per entry in `appBoxes` (private IP preferred, public IP as
 * fallback) — instead of `localhost:<port>`. rpx turns that array into a real
 * load-balanced pool with health-check failover (see rpx's `ProxyFrom`).
 *
 * `server-static`/`redirect` routes are unaffected (the LB box doesn't serve
 * static files or own redirects itself in the primary bun-fleet flow — those
 * kinds simply pass through unchanged if present in `sites`).
 */
export declare function buildRpxLbConfig(sites: Record<string, SiteConfig | undefined>, appBoxes: RpxLbAppBox[], options: BuildRpxConfigOptions): RpxGatewayConfig;
/**
 * Render the rpx gateway config as a self-contained launcher TS module. The
 * systemd unit runs `bun <file>`, which imports `startProxies` from the
 * managed `/opt/rpx-gateway` install and starts the gateway with the generated
 * options. We ship a runnable launcher (not a bare config) because rpx's CLI
 * resolves its own config from its install dir, not an arbitrary path.
 */
export declare function renderRpxLauncher(config: RpxGatewayConfig): string;
/**
 * True when this compute is fronted by the rpx gateway — either explicitly
 * (`webServer: 'rpx'`) or implicitly by opting into the rpx proxy engine
 * (`proxy.engine: 'rpx'`). Setting `proxy.engine: 'rpx'` alone provisions and
 * runs the gateway on :80/:443, so the deploy MUST NOT also stand up nginx +
 * certbot for a site — that races the gateway for :80 and the certbot HTTP-01
 * challenge fails with "Address already in use". Treating either signal as
 * "rpx mode" keeps the nginx/SSL path and the gateway path from contradicting.
 */
export declare function usesRpxProxy(compute?: {
    webServer?: string;
    proxy?: {
        engine?: string;
    };
}): boolean;
/** Default install location for the gateway launcher + config on the box. */
export declare const RPX_DIR = "/etc/rpx";
export declare const RPX_INSTALL_DIR = "/opt/rpx-gateway";
export declare const RPX_LAUNCHER_PATH = "/etc/rpx/gateway.ts";
export declare const RPX_SERVICE_NAME = "rpx-gateway.service";
/**
 * Per-app gateway registry. Each project's deploy writes ONLY its own fragment
 * (`<slug>.json`) here; the launcher ({@link renderRpxAssembler}) merges every
 * fragment at startup. So several independent apps share one box's gateway
 * Forge-style — one app's deploy never clobbers another's routes.
 */
export declare const RPX_SITES_DIR = "/etc/rpx/sites.d";
/** A registry fragment: one project's gateway config, tagged with its slug. */
export type RpxFragment = RpxGatewayConfig & {
    slug: string;
};
/**
 * Merge per-app fragments into one gateway config (the runtime equivalent of
 * what {@link renderRpxAssembler} does on the box, exported for testing).
 *
 * Routes are concatenated (deduped by `id`, first writer wins); on-demand
 * suffixes and origin-guard hosts are unioned; the first non-empty email /
 * certsDir / acmeChallengeWebroot / origin-guard header+secret wins. Fragments
 * are applied in the given order (the box sorts them by filename).
 */
export declare function mergeRpxFragments(fragments: RpxGatewayConfig[]): RpxGatewayConfig;
/**
 * Render the stable assembler launcher. Its content is identical for every app
 * and every deploy — it reads all `<slug>.json` fragments from {@link RPX_SITES_DIR}
 * at startup, merges them (same algorithm as {@link mergeRpxFragments}), and
 * starts the gateway. A malformed fragment is skipped, not fatal.
 */
export declare function renderRpxAssembler(sitesDir?: string, defaultCertsDir?: string): string;
export interface BuildRpxProvisionOptions {
    config: RpxGatewayConfig;
    proxy: ComputeProxyConfig;
    /**
     * This project's slug — the registry fragment is written to
     * `<sites.d>/<slug>.json`. So a box can host several independent apps and each
     * deploy only rewrites its own fragment. Defaults to `'app'` for single-app
     * boxes / backward compatibility.
     */
    slug?: string;
    /** Absolute path to the `bun` binary on the box. @default '/usr/local/bin/bun' */
    bunBin?: string;
}
export declare const RPX_CERT_RENEW_SCRIPT = "/etc/rpx/renew-certs.sh";
export declare const RPX_CERT_RENEW_SERVICE = "rpx-cert-renew.service";
export declare const RPX_CERT_RENEW_TIMER = "rpx-cert-renew.timer";
export declare function rpxCertRenewServiceName(slug: string): string;
/** The routable FQDNs in a gateway config — each terminates TLS so each needs a cert. */
export declare function certDomainsForConfig(config: RpxGatewayConfig): string[];
/**
 * Commands that make ts-cloud manage the gateway's TLS certs end-to-end: install
 * tlsx, issue a Let's Encrypt cert for every routed domain via http-01 (the
 * running gateway serves the challenge from {@link RpxGatewayConfig.acmeChallengeWebroot}
 * on `:80`, so no downtime), and a daily systemd timer that renews anything
 * expiring within 30 days and reloads the gateway only when a cert changed.
 *
 * Returns `[]` (no-op) unless on-demand/managed TLS is enabled and there's at
 * least one routable domain. Must run AFTER the gateway is started so the
 * challenge listener is live.
 */
export declare function buildCertManagementCommands(options: BuildRpxProvisionOptions): string[];
/**
 * Build the idempotent, re-runnable shell commands that install rpx as the
 * gateway, write the generated launcher + ensure the certs dir, install the
 * systemd unit, and enable + (re)start it on :80/:443.
 *
 * Safe to run at first boot (cloud-init) and again on every deploy — the unit
 * write + `systemctl restart` reloads the regenerated routes so new
 * server-app/server-static sites appear in the gateway automatically.
 */
export declare function buildRpxProvisionScript(options: BuildRpxProvisionOptions): string[];
export interface BuildRpxFragmentRefreshOptions {
    /** The gateway config this deploy resolved (single-box or LB multi-upstream). */
    config: RpxGatewayConfig;
    /**
     * This project's slug — the registry fragment is rewritten at
     * `<sites.d>/<slug>.json`, exactly as in {@link buildRpxProvisionScript}.
     * Defaults to `'app'`.
     */
    slug?: string;
}
/**
 * Build the shell commands that rewrite ONLY this app's rpx route fragment
 * (`<sites.d>/<slug>.json`) and restart the gateway, so the running assembler
 * re-merges every fragment and the new routes go live.
 *
 * Unlike {@link buildRpxProvisionScript} this does NOT reinstall rpx/tlsx,
 * rewrite the assembler launcher, or touch the systemd unit — it is the cheap
 * reload for a box whose gateway is already provisioned. That is exactly the
 * bun-fleet load-balancer situation: the LB box is created once (cloud-init
 * writes the fragment at first boot) but long outlives any single deploy, so
 * its routes must be refreshed from the CURRENT sites model + CURRENT app-box
 * upstreams on every provision/deploy — otherwise sites added later 404 and
 * app-fleet scale changes never reach the gateway. The `systemctl restart`
 * re-runs the stable assembler, which re-reads all of {@link RPX_SITES_DIR}.
 */
export declare function buildRpxFragmentRefreshScript(options: BuildRpxFragmentRefreshOptions): string[];
