@ebowwa/seedinstallation 0.2.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,85 @@
1
+ /**
2
+ * Generic device-code authentication flow polling.
3
+ * Handles CLI tools that use device-code auth (Doppler, GitHub, Tailscale, etc.).
4
+ * Works with both local and SSH contexts via ExecContext from sudo.ts.
5
+ */
6
+ import type { SudoOptions } from "./sudo.js";
7
+ export interface DeviceAuthConfig {
8
+ /** CLI command name (e.g. "doppler", "gh", "tailscale") */
9
+ cli: string;
10
+ /** Login command (default: "{cli} login" or "{cli} login --device-code") */
11
+ loginCmd?: string;
12
+ /** Status check command (default: "{cli} status") */
13
+ statusCmd?: string;
14
+ /** Patterns to extract auth URL and code from login output */
15
+ patterns: {
16
+ /** Regex to find the auth URL (first capture group) */
17
+ url: RegExp;
18
+ /** Regex to find the auth code (first capture group) */
19
+ code: RegExp;
20
+ /** Regex to detect successful login in status output */
21
+ success: RegExp;
22
+ };
23
+ /** Log file where background login writes output (e.g. "/tmp/doppler-login.log") */
24
+ logFile: string;
25
+ /** PID file for the background login process */
26
+ pidFile?: string;
27
+ /** Extra flags for status command */
28
+ statusFlags?: string[];
29
+ }
30
+ export interface DeviceAuthResult {
31
+ /** Whether login was successful */
32
+ success: boolean;
33
+ /** Auth URL for the user to visit */
34
+ url?: string;
35
+ /** Auth code to enter */
36
+ code?: string;
37
+ /** Status output from the CLI */
38
+ status?: string;
39
+ /** Error message if failed */
40
+ error?: string;
41
+ }
42
+ export interface DeviceAuthPollOptions {
43
+ /** Execution context for running commands */
44
+ context: SudoOptions["context"];
45
+ /** Max poll attempts (default: 60) */
46
+ maxAttempts?: number;
47
+ /** Poll interval in ms (default: 2000) */
48
+ intervalMs?: number;
49
+ /** Callback called on each poll attempt */
50
+ onPoll?: (attempt: number, result: DeviceAuthResult) => void;
51
+ }
52
+ /** Remove ANSI escape codes from a string. */
53
+ export declare function stripAnsi(text: string): string;
54
+ /**
55
+ * Initiate device-code authentication and poll for completion.
56
+ *
57
+ * @example
58
+ * const result = await deviceAuth(
59
+ * {
60
+ * cli: "doppler",
61
+ * patterns: {
62
+ * url: /Go to:\s+(https:\/\/[^\s]+)/,
63
+ * code: /code:\s+([A-Z0-9-]+)/,
64
+ * success: /Status:\s*authenticated/i,
65
+ * },
66
+ * logFile: "/tmp/doppler-login.log",
67
+ * },
68
+ * { context: { type: "ssh", host: "1.2.3.4" } }
69
+ * );
70
+ */
71
+ export declare function deviceAuth(config: DeviceAuthConfig, opts: DeviceAuthPollOptions): Promise<DeviceAuthResult>;
72
+ /**
73
+ * Check if a CLI is already authenticated.
74
+ */
75
+ export declare function isAuthed(config: Pick<DeviceAuthConfig, "cli" | "statusCmd" | "patterns">, opts: SudoOptions): Promise<boolean>;
76
+ /**
77
+ * Kill the background login process and clean up files.
78
+ */
79
+ export declare function cleanupDeviceAuth(config: Pick<DeviceAuthConfig, "logFile" | "pidFile">, opts: SudoOptions): Promise<void>;
80
+ /** Doppler CLI device-auth config */
81
+ export declare const dopplerConfig: DeviceAuthConfig;
82
+ /** GitHub CLI (gh) device-auth config */
83
+ export declare const githubConfig: DeviceAuthConfig;
84
+ /** Tailscale CLI device-auth config */
85
+ export declare const tailscaleConfig: DeviceAuthConfig;
@@ -0,0 +1,176 @@
1
+ /**
2
+ * Generic device-code authentication flow polling.
3
+ * Handles CLI tools that use device-code auth (Doppler, GitHub, Tailscale, etc.).
4
+ * Works with both local and SSH contexts via ExecContext from sudo.ts.
5
+ */
6
+ // Re-export helpers
7
+ async function exec(args, opts) {
8
+ const { sudo } = await import("./sudo.js");
9
+ return sudo(args, opts);
10
+ }
11
+ // ---------------------------------------------------------------------------
12
+ // ANSI stripping
13
+ // ---------------------------------------------------------------------------
14
+ /** Remove ANSI escape codes from a string. */
15
+ export function stripAnsi(text) {
16
+ return text.replace(/[\u001b\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]/g, "");
17
+ }
18
+ // ---------------------------------------------------------------------------
19
+ // Device-code auth flow
20
+ // ---------------------------------------------------------------------------
21
+ /**
22
+ * Initiate device-code authentication and poll for completion.
23
+ *
24
+ * @example
25
+ * const result = await deviceAuth(
26
+ * {
27
+ * cli: "doppler",
28
+ * patterns: {
29
+ * url: /Go to:\s+(https:\/\/[^\s]+)/,
30
+ * code: /code:\s+([A-Z0-9-]+)/,
31
+ * success: /Status:\s*authenticated/i,
32
+ * },
33
+ * logFile: "/tmp/doppler-login.log",
34
+ * },
35
+ * { context: { type: "ssh", host: "1.2.3.4" } }
36
+ * );
37
+ */
38
+ export async function deviceAuth(config, opts) {
39
+ const { cli, loginCmd, statusCmd, patterns, logFile, pidFile, statusFlags, } = config;
40
+ const maxAttempts = opts.maxAttempts ?? 60;
41
+ const intervalMs = opts.intervalMs ?? 2000;
42
+ // Step 1: Start login in background
43
+ const loginCmdStr = loginCmd ?? `${cli} login`;
44
+ const startResult = await exec([`bash`, `-lc`, `nohup ${loginCmdStr} > ${logFile} 2>&1 & echo $!`], opts);
45
+ if (!startResult.ok) {
46
+ return { success: false, error: `Failed to start login: ${startResult.stderr}` };
47
+ }
48
+ const pid = startResult.stdout.trim();
49
+ // Save PID if configured
50
+ if (pidFile) {
51
+ await exec(["bash", `-lc`, `echo ${pid} > ${pidFile}`], opts);
52
+ }
53
+ // Step 2: Wait a moment for log file to populate
54
+ await sleep(1000);
55
+ // Step 3: Extract URL and code from log file
56
+ const extractResult = await exec(["cat", logFile], { ...opts, quiet: false });
57
+ const logContent = stripAnsi(extractResult.stdout);
58
+ const urlMatch = patterns.url.exec(logContent);
59
+ const codeMatch = patterns.code.exec(logContent);
60
+ const url = urlMatch?.[1];
61
+ const code = codeMatch?.[1];
62
+ if (!url || !code) {
63
+ return {
64
+ success: false,
65
+ error: `Could not extract auth URL/code from log output. Log content:\n${logContent}`,
66
+ };
67
+ }
68
+ // Step 4: Poll for completion
69
+ for (let attempt = 1; attempt <= maxAttempts; attempt++) {
70
+ const statusCmdStr = statusCmd ?? `${cli} status`;
71
+ const statusArgs = ["bash", "-lc", statusCmdStr];
72
+ if (statusFlags?.length)
73
+ statusArgs.push(...statusFlags);
74
+ const statusResult = await exec(statusArgs, { ...opts, quiet: false });
75
+ const statusOutput = stripAnsi(statusResult.stdout);
76
+ if (patterns.success.test(statusOutput)) {
77
+ return {
78
+ success: true,
79
+ url,
80
+ code,
81
+ status: statusOutput,
82
+ };
83
+ }
84
+ opts.onPoll?.(attempt, { success: false, url, code, status: statusOutput });
85
+ await sleep(intervalMs);
86
+ }
87
+ return {
88
+ success: false,
89
+ url,
90
+ code,
91
+ error: `Login did not complete after ${maxAttempts} attempts. Last status: ${await getStatusOutput(config, opts)}`,
92
+ };
93
+ }
94
+ async function getStatusOutput(config, opts) {
95
+ const statusCmdStr = config.statusCmd ?? `${config.cli} status`;
96
+ const result = await exec(["bash", "-lc", statusCmdStr], { ...opts, quiet: false });
97
+ return stripAnsi(result.stdout);
98
+ }
99
+ // ---------------------------------------------------------------------------
100
+ // Status check
101
+ // ---------------------------------------------------------------------------
102
+ /**
103
+ * Check if a CLI is already authenticated.
104
+ */
105
+ export async function isAuthed(config, opts) {
106
+ const statusCmdStr = config.statusCmd ?? `${config.cli} status`;
107
+ const result = await exec(["bash", "-lc", statusCmdStr], { ...opts, quiet: false });
108
+ const output = stripAnsi(result.stdout);
109
+ return config.patterns.success.test(output);
110
+ }
111
+ // ---------------------------------------------------------------------------
112
+ // Cleanup
113
+ // ---------------------------------------------------------------------------
114
+ /**
115
+ * Kill the background login process and clean up files.
116
+ */
117
+ export async function cleanupDeviceAuth(config, opts) {
118
+ if (config.pidFile) {
119
+ // Read PID and kill process
120
+ const pidResult = await exec(["cat", config.pidFile], { ...opts, quiet: true });
121
+ if (pidResult.ok) {
122
+ const pid = pidResult.stdout.trim();
123
+ await exec(["kill", pid], { ...opts, quiet: true });
124
+ }
125
+ await exec(["rm", "-f", config.pidFile], { ...opts, quiet: true });
126
+ }
127
+ // Remove log file
128
+ await exec(["rm", "-f", config.logFile], { ...opts, quiet: true });
129
+ }
130
+ // ---------------------------------------------------------------------------
131
+ // Common CLI presets
132
+ // ---------------------------------------------------------------------------
133
+ /** Doppler CLI device-auth config */
134
+ export const dopplerConfig = {
135
+ cli: "doppler",
136
+ loginCmd: "doppler login -y",
137
+ statusCmd: "doppler configure get token --scope /",
138
+ patterns: {
139
+ url: /https:\/\/(?:cli|dashboard)\.doppler\.com\/[a-z\-\/]+/i,
140
+ code: /(?:Your authentication code is:|Your auth code is:)\s*([A-Z0-9_]+(?:-[A-Z0-9_]+)*)/i,
141
+ success: /.{10,}/, // Any output >10 chars means token exists (authenticated)
142
+ },
143
+ logFile: "/tmp/doppler-login.log",
144
+ pidFile: "/tmp/doppler-login.pid",
145
+ };
146
+ /** GitHub CLI (gh) device-auth config */
147
+ export const githubConfig = {
148
+ cli: "gh",
149
+ loginCmd: "GH_BROWSER=echo sh -c 'gh auth login -p https -h github.com < /dev/null'",
150
+ statusCmd: "gh auth status",
151
+ patterns: {
152
+ url: /(https:\/\/github\.com\/login\/device)/i,
153
+ code: /one-time code:\s*([A-Z0-9]{4}-[A-Z0-9]{4})/i,
154
+ success: /Logged in (?:as|to)/i,
155
+ },
156
+ logFile: "/tmp/gh-login.log",
157
+ pidFile: "/tmp/gh-login.pid",
158
+ };
159
+ /** Tailscale CLI device-auth config */
160
+ export const tailscaleConfig = {
161
+ cli: "tailscale",
162
+ loginCmd: "tailscale up --authkey",
163
+ patterns: {
164
+ url: /https:\/\/login\.tailscale\.com\/[a-z0-9\/]+/,
165
+ code: /https:\/\/login\.tailscale\.com\/[a-z0-9\/]+/,
166
+ success: /Tailscale is running/i,
167
+ },
168
+ logFile: "/tmp/tailscale-login.log",
169
+ pidFile: "/tmp/tailscale-login.pid",
170
+ };
171
+ // ---------------------------------------------------------------------------
172
+ // Utilities
173
+ // ---------------------------------------------------------------------------
174
+ function sleep(ms) {
175
+ return new Promise((resolve) => setTimeout(resolve, ms));
176
+ }
@@ -0,0 +1,12 @@
1
+ export { clone } from "./clone.js";
2
+ export type { CloneOptions, CloneResult } from "./clone.js";
3
+ export { sudo, pkgInstall, writeFile, service, serviceEnable } from "./sudo.js";
4
+ export type { ExecContext, SudoOptions, ExecResult, PkgOptions, PackageManager, WriteFileOptions, ServiceAction, } from "./sudo.js";
5
+ export { installBun, addSystemPath, symlink, linkBinaries, getBunVersion, commandExists, } from "./runtime.js";
6
+ export type { InstallBunOptions, PathOptions, SymlinkOptions, } from "./runtime.js";
7
+ export { generateServiceUnit, createServiceUnit, enableService, disableService, startService, stopService, restartService, reloadService, getServiceStatus, enableAndStartService, serviceExists, getServiceLogs, } from "./systemd.js";
8
+ export type { ServiceUnitOptions, ServiceResult, ServiceStatus, } from "./systemd.js";
9
+ export { deviceAuth, isAuthed, cleanupDeviceAuth, stripAnsi, dopplerConfig, githubConfig, tailscaleConfig, } from "./device-auth.js";
10
+ export type { DeviceAuthConfig, DeviceAuthResult, DeviceAuthPollOptions, } from "./device-auth.js";
11
+ export { getBootstrapStatus, parseBootstrapStatus, initBootstrap, startPhase, completePhase, failPhase, completeBootstrap, failBootstrap, checkMarker, setMarker, removeMarker, waitForBootstrap, waitForMarker, } from "./bootstrap.js";
12
+ export type { BootstrapPhase, BootstrapStatus, BootstrapPollOptions, } from "./bootstrap.js";
package/dist/index.js ADDED
@@ -0,0 +1,10 @@
1
+ export { clone } from "./clone.js";
2
+ export { sudo, pkgInstall, writeFile, service, serviceEnable } from "./sudo.js";
3
+ // Runtime installation (Bun, Node, PATH, symlinks)
4
+ export { installBun, addSystemPath, symlink, linkBinaries, getBunVersion, commandExists, } from "./runtime.js";
5
+ // Systemd service management
6
+ export { generateServiceUnit, createServiceUnit, enableService, disableService, startService, stopService, restartService, reloadService, getServiceStatus, enableAndStartService, serviceExists, getServiceLogs, } from "./systemd.js";
7
+ // Device-code authentication flow (Doppler, GitHub, Tailscale)
8
+ export { deviceAuth, isAuthed, cleanupDeviceAuth, stripAnsi, dopplerConfig, githubConfig, tailscaleConfig, } from "./device-auth.js";
9
+ // Bootstrap status tracking and polling
10
+ export { getBootstrapStatus, parseBootstrapStatus, initBootstrap, startPhase, completePhase, failPhase, completeBootstrap, failBootstrap, checkMarker, setMarker, removeMarker, waitForBootstrap, waitForMarker, } from "./bootstrap.js";
@@ -0,0 +1,60 @@
1
+ /**
2
+ * Runtime installation and PATH management for edge servers.
3
+ * Works with both local and SSH contexts via ExecContext from sudo.ts.
4
+ */
5
+ import type { SudoOptions } from "./sudo.js";
6
+ export declare function exec(args: string[], opts: SudoOptions): Promise<{
7
+ stdout: string;
8
+ stderr: string;
9
+ ok: boolean;
10
+ }>;
11
+ export interface InstallBunOptions {
12
+ /** Execution context for running commands */
13
+ context: SudoOptions["context"];
14
+ /** Bun version to install (default: latest) */
15
+ version?: string;
16
+ /** Add bun to /etc/environment for all users */
17
+ systemWide?: boolean;
18
+ /** Installation directory (default: /root/.bun) */
19
+ installDir?: string;
20
+ }
21
+ /**
22
+ * Install Bun runtime via the official install script.
23
+ * Works on Debian/Ubuntu, Alpine, and Fedora-based systems.
24
+ */
25
+ export declare function installBun(opts: InstallBunOptions): Promise<void>;
26
+ export interface PathOptions {
27
+ /** Execution context for running commands */
28
+ context: SudoOptions["context"];
29
+ /** Target file (/etc/environment for system-wide, ~/.bashrc for user) */
30
+ target?: "system" | "user";
31
+ /** Prepend or append to PATH */
32
+ position?: "prepend" | "append";
33
+ }
34
+ /**
35
+ * Add a directory to PATH in /etc/environment (system) or ~/.bashrc (user).
36
+ * Checks for duplicates before adding.
37
+ */
38
+ export declare function addSystemPath(dir: string, opts: PathOptions): Promise<void>;
39
+ export interface SymlinkOptions {
40
+ /** Execution context for running commands */
41
+ context: SudoOptions["context"];
42
+ /** Force overwrite if target exists */
43
+ force?: boolean;
44
+ }
45
+ /**
46
+ * Create a symlink from source to target.
47
+ */
48
+ export declare function symlink(source: string, target: string, opts: SymlinkOptions): Promise<void>;
49
+ /**
50
+ * Create symlinks for all binaries in a bin directory to /usr/local/bin.
51
+ */
52
+ export declare function linkBinaries(sourceDir: string, opts: SymlinkOptions): Promise<void>;
53
+ /**
54
+ * Get the currently installed Bun version.
55
+ */
56
+ export declare function getBunVersion(opts: SudoOptions): Promise<string | null>;
57
+ /**
58
+ * Check if a command is available in PATH.
59
+ */
60
+ export declare function commandExists(cmd: string, opts: SudoOptions): Promise<boolean>;
@@ -0,0 +1,122 @@
1
+ /**
2
+ * Runtime installation and PATH management for edge servers.
3
+ * Works with both local and SSH contexts via ExecContext from sudo.ts.
4
+ */
5
+ // Re-export sudo helper for runtime use
6
+ export async function exec(args, opts) {
7
+ const sudo = await import("./sudo.js").then((m) => m.sudo);
8
+ return sudo(args, opts);
9
+ }
10
+ /**
11
+ * Install Bun runtime via the official install script.
12
+ * Works on Debian/Ubuntu, Alpine, and Fedora-based systems.
13
+ */
14
+ export async function installBun(opts) {
15
+ const installCmd = opts.version
16
+ ? `curl -fsSL https://bun.sh/install | bash -s "bun-${opts.version}"`
17
+ : `curl -fsSL https://bun.sh/install | bash`;
18
+ await execShell(installCmd, opts);
19
+ if (opts.systemWide) {
20
+ const bunPath = opts.installDir ?? "/root/.bun";
21
+ await addSystemPath(`${bunPath}/bin`, { context: opts.context });
22
+ }
23
+ }
24
+ /**
25
+ * Add a directory to PATH in /etc/environment (system) or ~/.bashrc (user).
26
+ * Checks for duplicates before adding.
27
+ */
28
+ export async function addSystemPath(dir, opts) {
29
+ const { writeFile } = await import("./sudo.js");
30
+ const target = opts.target ?? "system";
31
+ const position = opts.position ?? "prepend";
32
+ // Read existing PATH
33
+ const existingPath = target === "system"
34
+ ? await getSystemPath(opts)
35
+ : await getUserPath(opts);
36
+ // Check if already in PATH
37
+ const paths = existingPath.split(":");
38
+ if (paths.includes(dir)) {
39
+ return; // Already present
40
+ }
41
+ // Add to PATH
42
+ const newPath = position === "prepend"
43
+ ? `${dir}:${existingPath}`
44
+ : `${existingPath}:${dir}`;
45
+ if (target === "system") {
46
+ // Write to /etc/environment
47
+ const content = `PATH="${newPath}"\n`;
48
+ await writeFile("/etc/environment", content, { ...opts, append: false });
49
+ }
50
+ else {
51
+ // Append to ~/.bashrc
52
+ const exportLine = `\nexport PATH="${newPath}"\n`;
53
+ await writeFile("/root/.bashrc", exportLine, { ...opts, append: true });
54
+ }
55
+ }
56
+ async function getSystemPath(opts) {
57
+ const result = await exec(["grep", "^PATH=", "/etc/environment"], { ...opts, quiet: true });
58
+ if (result.ok && result.stdout) {
59
+ const match = result.stdout.match(/^PATH="([^"]+)"/);
60
+ if (match?.[1])
61
+ return match[1];
62
+ }
63
+ return "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"; // default
64
+ }
65
+ async function getUserPath(opts) {
66
+ const result = await exec(["bash", "-lc", "echo $PATH"], { ...opts, quiet: true });
67
+ return result.stdout.trim();
68
+ }
69
+ /**
70
+ * Create a symlink from source to target.
71
+ */
72
+ export async function symlink(source, target, opts) {
73
+ const args = ["ln", "-s"];
74
+ if (opts.force)
75
+ args.push("-f");
76
+ args.push(source, target);
77
+ await exec(args, opts);
78
+ }
79
+ /**
80
+ * Create symlinks for all binaries in a bin directory to /usr/local/bin.
81
+ */
82
+ export async function linkBinaries(sourceDir, opts) {
83
+ // List binaries in source dir
84
+ const result = await exec(["ls", "-1", sourceDir], { ...opts, quiet: true });
85
+ if (!result.ok)
86
+ return;
87
+ const binaries = result.stdout.trim().split("\n").filter(Boolean);
88
+ for (const bin of binaries) {
89
+ const source = `${sourceDir}/${bin}`;
90
+ const target = `/usr/local/bin/${bin}`;
91
+ await symlink(source, target, { ...opts, force: true });
92
+ }
93
+ }
94
+ // ---------------------------------------------------------------------------
95
+ // Version detection
96
+ // ---------------------------------------------------------------------------
97
+ /**
98
+ * Get the currently installed Bun version.
99
+ */
100
+ export async function getBunVersion(opts) {
101
+ const result = await exec(["bash", "-lc", "bun --version"], { ...opts, quiet: true });
102
+ if (result.ok) {
103
+ return result.stdout.trim();
104
+ }
105
+ return null;
106
+ }
107
+ /**
108
+ * Check if a command is available in PATH.
109
+ */
110
+ export async function commandExists(cmd, opts) {
111
+ const result = await exec(["which", cmd], { ...opts, quiet: true });
112
+ return result.ok;
113
+ }
114
+ // ---------------------------------------------------------------------------
115
+ // Internals
116
+ // ---------------------------------------------------------------------------
117
+ async function execShell(cmd, opts) {
118
+ const result = await exec(["bash", "-lc", cmd], opts);
119
+ if (!result.ok) {
120
+ throw new Error(`Shell command failed: ${result.stderr}`);
121
+ }
122
+ }
package/dist/sudo.d.ts ADDED
@@ -0,0 +1,57 @@
1
+ /**
2
+ * Composable sudo command runner for local and remote (SSH) contexts.
3
+ * Supports arbitrary commands, package installs, file writes, and service management.
4
+ */
5
+ export type ExecContext = {
6
+ type: "local";
7
+ } | {
8
+ type: "ssh";
9
+ host: string;
10
+ user?: string;
11
+ keyPath?: string;
12
+ key?: string;
13
+ port?: number;
14
+ };
15
+ export interface SudoOptions {
16
+ /** Where to run: locally or over SSH */
17
+ context: ExecContext;
18
+ /** Environment variables to pass through sudo */
19
+ env?: Record<string, string>;
20
+ /** Timeout in ms (default: 30_000) */
21
+ timeout?: number;
22
+ /** Suppress stdout in result (default: false) */
23
+ quiet?: boolean;
24
+ }
25
+ export interface ExecResult {
26
+ stdout: string;
27
+ stderr: string;
28
+ exitCode: number;
29
+ ok: boolean;
30
+ }
31
+ /** Run an arbitrary command with sudo. */
32
+ export declare function sudo(cmd: string | string[], opts: SudoOptions): Promise<ExecResult>;
33
+ export type PackageManager = "apt" | "dnf" | "apk";
34
+ export interface PkgOptions extends SudoOptions {
35
+ /** Package manager to use (default: "apt") */
36
+ pm?: PackageManager;
37
+ /** Run non-interactively (default: true) */
38
+ nonInteractive?: boolean;
39
+ }
40
+ /** Install one or more system packages. */
41
+ export declare function pkgInstall(packages: string[], opts: PkgOptions): Promise<ExecResult>;
42
+ export interface WriteFileOptions extends SudoOptions {
43
+ /** File permissions (e.g. "644", "755") */
44
+ mode?: string;
45
+ /** File owner (e.g. "root:root") */
46
+ owner?: string;
47
+ /** Append instead of overwrite */
48
+ append?: boolean;
49
+ }
50
+ /** Write content to a privileged file path using tee. */
51
+ export declare function writeFile(path: string, content: string, opts: WriteFileOptions): Promise<ExecResult>;
52
+ export type ServiceAction = "start" | "stop" | "restart" | "enable" | "disable" | "status";
53
+ /** Manage a systemd service. */
54
+ export declare function service(name: string, action: ServiceAction, opts: SudoOptions): Promise<ExecResult>;
55
+ /** Enable and start a service in one call. */
56
+ export declare function serviceEnable(name: string, opts: SudoOptions): Promise<ExecResult>;
57
+ export declare function exec(args: string[], opts: SudoOptions): Promise<ExecResult>;