This commit is contained in:
Steve Rodriguez 2026-01-29 21:53:34 -05:00 committed by GitHub
commit 3f8c5a0a13
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

View File

@ -1,4 +1,7 @@
import { spawn } from "node:child_process";
import { existsSync, mkdirSync, symlinkSync, lstatSync } from "node:fs";
import { homedir } from "node:os";
import { join } from "node:path";
import type { RuntimeEnv } from "../runtime.js";
export type SignalDaemonOpts = {
@ -18,6 +21,84 @@ export type SignalDaemonHandle = {
stop: () => void;
};
/**
* Setup Signal data directory for containerized environments.
* Auto-detects persistent storage and creates symlink if needed.
*
* Priority order:
* 1. SIGNAL_CLI_DATA_DIR env var (if set)
* 2. /data/.local/share/signal-cli (Fly.io convention)
* 3. /var/lib/clawdbot/.local/share/signal-cli (alternative persistent location)
* 4. Default: ~/.local/share/signal-cli (no symlink needed)
*/
export function setupSignalDataPersistence(runtime?: RuntimeEnv): void {
const log = runtime?.log ?? (() => {});
const defaultPath = join(homedir(), ".local", "share", "signal-cli");
// Check if already a symlink or regular directory exists
if (existsSync(defaultPath)) {
try {
const stats = lstatSync(defaultPath);
if (stats.isSymbolicLink()) {
log(`signal-cli data already symlinked to persistent storage`);
return;
}
// Regular directory exists, don't touch it
return;
} catch {
// Ignore errors, will try to create below
}
}
// Determine persistent storage location
let persistentPath: string | null = null;
// Priority 1: Environment variable
if (process.env.SIGNAL_CLI_DATA_DIR) {
persistentPath = process.env.SIGNAL_CLI_DATA_DIR;
log(`signal-cli: using SIGNAL_CLI_DATA_DIR=${persistentPath}`);
}
// Priority 2: /data (Fly.io convention)
else if (existsSync("/data")) {
persistentPath = "/data/.local/share/signal-cli";
}
// Priority 3: /var/lib/clawdbot
else if (existsSync("/var/lib/clawdbot")) {
persistentPath = "/var/lib/clawdbot/.local/share/signal-cli";
}
// No persistent storage detected, use default
if (!persistentPath) {
return;
}
// Create persistent directory structure
try {
mkdirSync(persistentPath, { recursive: true });
mkdirSync(join(persistentPath, "data"), { recursive: true });
} catch (err) {
runtime?.error?.(`signal-cli: failed to create persistent directory: ${String(err)}`);
return;
}
// Create parent directory for symlink
try {
const parentDir = join(homedir(), ".local", "share");
mkdirSync(parentDir, { recursive: true });
} catch (err) {
runtime?.error?.(`signal-cli: failed to create parent directory: ${String(err)}`);
return;
}
// Create symlink
try {
symlinkSync(persistentPath, defaultPath);
log(`signal-cli: linked ${defaultPath} -> ${persistentPath} for persistence`);
} catch (err) {
runtime?.error?.(`signal-cli: failed to create symlink: ${String(err)}`);
}
}
export function classifySignalCliLogLine(line: string): "log" | "error" | null {
const trimmed = line.trim();
if (!trimmed) return null;
@ -48,6 +129,9 @@ function buildDaemonArgs(opts: SignalDaemonOpts): string[] {
}
export function spawnSignalDaemon(opts: SignalDaemonOpts): SignalDaemonHandle {
// Setup persistent storage for containerized environments
setupSignalDataPersistence(opts.runtime);
const args = buildDaemonArgs(opts);
const child = spawn(opts.cliPath, args, {
stdio: ["ignore", "pipe", "pipe"],