Introduce bind presets for deployment setup

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
dotta
2026-04-10 07:32:16 -05:00
committed by Dotta
parent e1bf9d66a7
commit 2a84e53c1b
35 changed files with 915 additions and 176 deletions

View File

@@ -4,6 +4,7 @@ import { existsSync, mkdirSync, readdirSync, rmSync, statSync, writeFileSync } f
import path from "node:path";
import { createInterface } from "node:readline/promises";
import { stdin, stdout } from "node:process";
import { BIND_MODES, type BindMode } from "@paperclipai/shared";
import { createCapturedOutputBuffer, parseJsonResponseWithLimit } from "./dev-runner-output.mjs";
import { shouldTrackDevServerPath } from "./dev-runner-paths.mjs";
import { createDevServiceIdentity, repoRoot } from "./dev-service-profile.ts";
@@ -62,13 +63,36 @@ const tailscaleAuthFlagNames = new Set([
]);
let tailscaleAuth = false;
let bindMode: BindMode | null = null;
let bindHost: string | null = null;
const forwardedArgs: string[] = [];
for (const arg of cliArgs) {
for (let index = 0; index < cliArgs.length; index += 1) {
const arg = cliArgs[index];
if (tailscaleAuthFlagNames.has(arg)) {
tailscaleAuth = true;
continue;
}
if (arg === "--bind") {
const value = cliArgs[index + 1];
if (!value || value.startsWith("--") || !BIND_MODES.includes(value as BindMode)) {
console.error(`[paperclip] invalid --bind value. Use one of: ${BIND_MODES.join(", ")}`);
process.exit(1);
}
bindMode = value as BindMode;
index += 1;
continue;
}
if (arg === "--bind-host") {
const value = cliArgs[index + 1];
if (!value || value.startsWith("--")) {
console.error("[paperclip] --bind-host requires a value");
process.exit(1);
}
bindHost = value;
index += 1;
continue;
}
forwardedArgs.push(arg);
}
@@ -78,6 +102,16 @@ if (process.env.npm_config_tailscale_auth === "true") {
if (process.env.npm_config_authenticated_private === "true") {
tailscaleAuth = true;
}
if (!bindMode && process.env.npm_config_bind && BIND_MODES.includes(process.env.npm_config_bind as BindMode)) {
bindMode = process.env.npm_config_bind as BindMode;
}
if (!bindHost && process.env.npm_config_bind_host) {
bindHost = process.env.npm_config_bind_host;
}
if (bindMode === "custom" && !bindHost) {
console.error("[paperclip] --bind custom requires --bind-host <host>");
process.exit(1);
}
const env: NodeJS.ProcessEnv = {
...process.env,
@@ -94,13 +128,36 @@ if (mode === "watch") {
env.PAPERCLIP_MIGRATION_AUTO_APPLY ??= "true";
}
if (tailscaleAuth) {
env.PAPERCLIP_DEPLOYMENT_MODE = "authenticated";
env.PAPERCLIP_DEPLOYMENT_EXPOSURE = "private";
env.PAPERCLIP_AUTH_BASE_URL_MODE = "auto";
env.HOST = "0.0.0.0";
console.log("[paperclip] dev mode: authenticated/private (tailscale-friendly) on 0.0.0.0");
if (tailscaleAuth || bindMode) {
const effectiveBind = bindMode ?? "lan";
if (tailscaleAuth) {
console.log("[paperclip] note: --tailscale-auth/--authenticated-private are legacy aliases for --bind lan");
}
env.PAPERCLIP_BIND = effectiveBind;
if (bindHost) {
env.PAPERCLIP_BIND_HOST = bindHost;
} else {
delete env.PAPERCLIP_BIND_HOST;
}
if (effectiveBind === "loopback" && !tailscaleAuth) {
delete env.PAPERCLIP_DEPLOYMENT_MODE;
delete env.PAPERCLIP_DEPLOYMENT_EXPOSURE;
delete env.PAPERCLIP_AUTH_BASE_URL_MODE;
console.log("[paperclip] dev mode: local_trusted (bind=loopback)");
} else {
env.PAPERCLIP_DEPLOYMENT_MODE = "authenticated";
env.PAPERCLIP_DEPLOYMENT_EXPOSURE = "private";
env.PAPERCLIP_AUTH_BASE_URL_MODE = "auto";
console.log(
`[paperclip] dev mode: authenticated/private (bind=${effectiveBind}${bindHost ? `:${bindHost}` : ""})`,
);
}
} else {
delete env.PAPERCLIP_BIND;
delete env.PAPERCLIP_BIND_HOST;
delete env.PAPERCLIP_DEPLOYMENT_MODE;
delete env.PAPERCLIP_DEPLOYMENT_EXPOSURE;
delete env.PAPERCLIP_AUTH_BASE_URL_MODE;
console.log("[paperclip] dev mode: local_trusted (default)");
}
@@ -108,7 +165,7 @@ const serverPort = Number.parseInt(env.PORT ?? process.env.PORT ?? "3100", 10) |
const devService = createDevServiceIdentity({
mode,
forwardedArgs,
tailscaleAuth,
networkProfile: tailscaleAuth ? `legacy:${bindMode ?? "lan"}` : (bindMode ?? "default"),
port: serverPort,
});

View File

@@ -8,7 +8,7 @@ export const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)
export function createDevServiceIdentity(input: {
mode: "watch" | "dev";
forwardedArgs: string[];
tailscaleAuth: boolean;
networkProfile: string;
port: number;
}) {
const envFingerprint = createHash("sha256")
@@ -16,7 +16,7 @@ export function createDevServiceIdentity(input: {
JSON.stringify({
mode: input.mode,
forwardedArgs: input.forwardedArgs,
tailscaleAuth: input.tailscaleAuth,
networkProfile: input.networkProfile,
port: input.port,
}),
)

View File

@@ -237,6 +237,8 @@ async function main() {
server: {
deploymentMode: sourceConfig?.server?.deploymentMode ?? "local_trusted",
exposure: sourceConfig?.server?.exposure ?? "private",
...(sourceConfig?.server?.bind ? { bind: sourceConfig.server.bind } : {}),
...(sourceConfig?.server?.customBindHost ? { customBindHost: sourceConfig.server.customBindHost } : {}),
host: sourceConfig?.server?.host ?? "127.0.0.1",
port: serverPort,
allowedHostnames: sourceConfig?.server?.allowedHostnames ?? [],