openpalm 0.2.0 → 0.2.6
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.
- package/LICENSE +121 -0
- package/dist/openpalm.js +1856 -0
- package/package.json +6 -18
- package/src/commands/create-channel.ts +0 -262
- package/src/commands/extensions.ts +0 -122
- package/src/commands/install.ts +0 -326
- package/src/commands/logs.ts +0 -7
- package/src/commands/preflight.ts +0 -49
- package/src/commands/restart.ts +0 -10
- package/src/commands/start.ts +0 -10
- package/src/commands/status.ts +0 -9
- package/src/commands/stop.ts +0 -10
- package/src/commands/uninstall.ts +0 -124
- package/src/commands/update.ts +0 -12
- package/src/main.ts +0 -210
- package/src/types.ts +0 -18
package/src/commands/install.ts
DELETED
|
@@ -1,326 +0,0 @@
|
|
|
1
|
-
import { join } from "node:path";
|
|
2
|
-
import { chmod, writeFile, rm } from "node:fs/promises";
|
|
3
|
-
import type { InstallOptions } from "../types.ts";
|
|
4
|
-
import type { ComposeConfig } from "@openpalm/lib/types.ts";
|
|
5
|
-
import { detectOS, detectArch, detectRuntime, resolveSocketPath, resolveComposeBin, validateRuntime } from "@openpalm/lib/runtime.ts";
|
|
6
|
-
import { resolveXDGPaths, createDirectoryTree } from "@openpalm/lib/paths.ts";
|
|
7
|
-
import { upsertEnvVars } from "@openpalm/lib/env.ts";
|
|
8
|
-
import { generateToken } from "@openpalm/lib/tokens.ts";
|
|
9
|
-
import { composePull, composeUp } from "@openpalm/lib/compose.ts";
|
|
10
|
-
import { seedConfigFiles } from "@openpalm/lib/assets.ts";
|
|
11
|
-
import { runPreflightChecks, noRuntimeGuidance, noComposeGuidance } from "@openpalm/lib/preflight.ts";
|
|
12
|
-
import { log, info, warn, error, bold, green, cyan, yellow, dim, spinner } from "@openpalm/lib/ui.ts";
|
|
13
|
-
|
|
14
|
-
export async function install(options: InstallOptions): Promise<void> {
|
|
15
|
-
// ============================================================================
|
|
16
|
-
// Phase 1: Setup infrastructure
|
|
17
|
-
// ============================================================================
|
|
18
|
-
|
|
19
|
-
log(bold("\nOpenPalm Installation\n"));
|
|
20
|
-
|
|
21
|
-
// 1. Detect OS
|
|
22
|
-
const os = detectOS();
|
|
23
|
-
if (os === "unknown") {
|
|
24
|
-
error("Unable to detect operating system. Installation aborted.");
|
|
25
|
-
process.exit(1);
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
// 2. Detect arch
|
|
29
|
-
const arch = detectArch();
|
|
30
|
-
|
|
31
|
-
// 3. Detect or use overridden container runtime
|
|
32
|
-
const platform = options.runtime ?? await detectRuntime(os);
|
|
33
|
-
if (!platform) {
|
|
34
|
-
error(noRuntimeGuidance(os));
|
|
35
|
-
process.exit(1);
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
// 4. Resolve compose bin/subcommand
|
|
39
|
-
const { bin, subcommand } = resolveComposeBin(platform);
|
|
40
|
-
|
|
41
|
-
// 5. Run pre-flight checks (daemon running, disk space, port 80)
|
|
42
|
-
const preflightWarnings = await runPreflightChecks(bin, platform);
|
|
43
|
-
for (const w of preflightWarnings) {
|
|
44
|
-
warn(w.message);
|
|
45
|
-
if (w.detail) {
|
|
46
|
-
for (const line of w.detail.split("\n")) {
|
|
47
|
-
info(` ${line}`);
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
log("");
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
// Daemon not running is fatal — we can't proceed
|
|
54
|
-
const daemonWarning = preflightWarnings.find((w) =>
|
|
55
|
-
w.message.includes("daemon is not running")
|
|
56
|
-
);
|
|
57
|
-
if (daemonWarning) {
|
|
58
|
-
process.exit(1);
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
// 6. Validate compose works
|
|
62
|
-
const isValid = await validateRuntime(bin, subcommand);
|
|
63
|
-
if (!isValid) {
|
|
64
|
-
error(noComposeGuidance(platform));
|
|
65
|
-
process.exit(1);
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
// 7. Print detected info
|
|
69
|
-
log(bold("Detected environment:"));
|
|
70
|
-
info(` OS: ${cyan(os)}`);
|
|
71
|
-
info(` Architecture: ${cyan(arch)}`);
|
|
72
|
-
info(` Container runtime: ${cyan(platform)}`);
|
|
73
|
-
info(` Compose command: ${cyan(`${bin} ${subcommand}`)}\n`);
|
|
74
|
-
|
|
75
|
-
// 8. Resolve XDG paths, print them
|
|
76
|
-
const xdg = resolveXDGPaths();
|
|
77
|
-
log(bold("\nXDG paths:"));
|
|
78
|
-
info(` Data: ${dim(xdg.data)}`);
|
|
79
|
-
info(` Config: ${dim(xdg.config)}`);
|
|
80
|
-
info(` State: ${dim(xdg.state)}\n`);
|
|
81
|
-
|
|
82
|
-
// 9. Check if .env exists in CWD, generate if not
|
|
83
|
-
const envPath = join(process.cwd(), ".env");
|
|
84
|
-
const envExists = await Bun.file(envPath).exists();
|
|
85
|
-
|
|
86
|
-
let generatedAdminToken = "";
|
|
87
|
-
if (!envExists) {
|
|
88
|
-
const spin2 = spinner("Generating .env file...");
|
|
89
|
-
generatedAdminToken = generateToken();
|
|
90
|
-
const overrides: Record<string, string> = {
|
|
91
|
-
ADMIN_TOKEN: generatedAdminToken,
|
|
92
|
-
POSTGRES_PASSWORD: generateToken(),
|
|
93
|
-
CHANNEL_CHAT_SECRET: generateToken(),
|
|
94
|
-
CHANNEL_DISCORD_SECRET: generateToken(),
|
|
95
|
-
CHANNEL_VOICE_SECRET: generateToken(),
|
|
96
|
-
CHANNEL_TELEGRAM_SECRET: generateToken(),
|
|
97
|
-
};
|
|
98
|
-
const envSeed = Object.entries(overrides).map(([key, value]) => `${key}=${value}`).join("\n") + "\n";
|
|
99
|
-
await writeFile(envPath, envSeed, "utf8");
|
|
100
|
-
spin2.stop(green(".env file created"));
|
|
101
|
-
|
|
102
|
-
// Display admin token prominently
|
|
103
|
-
log("");
|
|
104
|
-
log(bold(green(" YOUR ADMIN PASSWORD (save this!)")));
|
|
105
|
-
log("");
|
|
106
|
-
log(` ${yellow(generatedAdminToken)}`);
|
|
107
|
-
log("");
|
|
108
|
-
info(" You will need this password to log in to the admin dashboard.");
|
|
109
|
-
info(` It is also saved in: ${dim(envPath)}`);
|
|
110
|
-
log("");
|
|
111
|
-
} else {
|
|
112
|
-
info("Using existing .env file");
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
// 10. Upsert runtime config vars into .env (single read-write cycle)
|
|
116
|
-
const socketPath = resolveSocketPath(platform, os);
|
|
117
|
-
await upsertEnvVars(envPath, [
|
|
118
|
-
["OPENPALM_DATA_HOME", xdg.data],
|
|
119
|
-
["OPENPALM_CONFIG_HOME", xdg.config],
|
|
120
|
-
["OPENPALM_STATE_HOME", xdg.state],
|
|
121
|
-
["OPENPALM_CONTAINER_PLATFORM", platform],
|
|
122
|
-
["OPENPALM_COMPOSE_BIN", bin],
|
|
123
|
-
["OPENPALM_COMPOSE_SUBCOMMAND", subcommand],
|
|
124
|
-
["OPENPALM_CONTAINER_SOCKET_PATH", socketPath],
|
|
125
|
-
["OPENPALM_CONTAINER_SOCKET_IN_CONTAINER", "/var/run/docker.sock"],
|
|
126
|
-
["OPENPALM_CONTAINER_SOCKET_URI", "unix:///var/run/docker.sock"],
|
|
127
|
-
["OPENPALM_IMAGE_TAG", `latest-${arch}`],
|
|
128
|
-
["OPENPALM_ENABLED_CHANNELS", ""],
|
|
129
|
-
]);
|
|
130
|
-
|
|
131
|
-
// 11. Create XDG directory tree
|
|
132
|
-
const spin3 = spinner("Creating directory structure...");
|
|
133
|
-
await createDirectoryTree(xdg);
|
|
134
|
-
spin3.stop(green("Directory structure created"));
|
|
135
|
-
|
|
136
|
-
// 12. Copy .env to state home
|
|
137
|
-
const stateEnvFile = join(xdg.state, ".env");
|
|
138
|
-
await Bun.write(stateEnvFile, Bun.file(envPath));
|
|
139
|
-
|
|
140
|
-
// 13. Seed config files (embedded templates — no network needed)
|
|
141
|
-
const spin4 = spinner("Seeding configuration files...");
|
|
142
|
-
await seedConfigFiles(xdg.config);
|
|
143
|
-
spin4.stop(green("Configuration files seeded"));
|
|
144
|
-
|
|
145
|
-
// 14. Reset setup wizard state so every install/reinstall starts from first boot
|
|
146
|
-
await rm(join(xdg.data, "admin", "setup-state.json"), { force: true });
|
|
147
|
-
|
|
148
|
-
// 15. Write uninstall script to state home
|
|
149
|
-
const uninstallDst = join(xdg.state, "uninstall.sh");
|
|
150
|
-
await writeFile(uninstallDst, "#!/usr/bin/env bash\nopenpalm uninstall\n", "utf8");
|
|
151
|
-
try {
|
|
152
|
-
await chmod(uninstallDst, 0o755);
|
|
153
|
-
} catch {
|
|
154
|
-
// chmod may fail on Windows — non-critical
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
// 16. Write minimal setup-only Caddy JSON config (admin routes only)
|
|
158
|
-
const minimalCaddyJson = JSON.stringify({
|
|
159
|
-
admin: { disabled: true },
|
|
160
|
-
apps: {
|
|
161
|
-
http: {
|
|
162
|
-
servers: {
|
|
163
|
-
main: {
|
|
164
|
-
listen: [":80"],
|
|
165
|
-
routes: [
|
|
166
|
-
{
|
|
167
|
-
match: [{ path: ["/admin*"] }],
|
|
168
|
-
handle: [{
|
|
169
|
-
handler: "subroute",
|
|
170
|
-
routes: [
|
|
171
|
-
{
|
|
172
|
-
match: [{ path: ["/admin/api*"] }],
|
|
173
|
-
handle: [
|
|
174
|
-
{ handler: "rewrite", uri_substring: [{ find: "/admin/api", replace: "/admin" }] },
|
|
175
|
-
{ handler: "reverse_proxy", upstreams: [{ dial: "admin:8100" }] },
|
|
176
|
-
],
|
|
177
|
-
terminal: true,
|
|
178
|
-
},
|
|
179
|
-
{
|
|
180
|
-
handle: [
|
|
181
|
-
{ handler: "rewrite", strip_path_prefix: "/admin" },
|
|
182
|
-
{ handler: "reverse_proxy", upstreams: [{ dial: "admin:8100" }] },
|
|
183
|
-
],
|
|
184
|
-
},
|
|
185
|
-
],
|
|
186
|
-
}],
|
|
187
|
-
terminal: true,
|
|
188
|
-
},
|
|
189
|
-
{
|
|
190
|
-
handle: [{
|
|
191
|
-
handler: "static_response",
|
|
192
|
-
body: "OpenPalm is starting... Please visit /admin/ to complete setup.",
|
|
193
|
-
status_code: "503",
|
|
194
|
-
}],
|
|
195
|
-
},
|
|
196
|
-
],
|
|
197
|
-
},
|
|
198
|
-
},
|
|
199
|
-
},
|
|
200
|
-
},
|
|
201
|
-
}, null, 2) + "\n";
|
|
202
|
-
const caddyJsonPath = join(xdg.state, "rendered", "caddy", "caddy.json");
|
|
203
|
-
await writeFile(caddyJsonPath, minimalCaddyJson, "utf8");
|
|
204
|
-
|
|
205
|
-
// ============================================================================
|
|
206
|
-
// Phase 2: Early UI access
|
|
207
|
-
// ============================================================================
|
|
208
|
-
|
|
209
|
-
log(bold("\nDownloading OpenPalm services (this may take a few minutes on first install)...\n"));
|
|
210
|
-
|
|
211
|
-
// The compose file is generated by the admin on first stack apply.
|
|
212
|
-
// For initial install, we need a minimal compose file to start core services.
|
|
213
|
-
// The admin server will generate the full compose file when the stack is applied.
|
|
214
|
-
const stateComposeFile = join(xdg.state, "docker-compose.yml");
|
|
215
|
-
|
|
216
|
-
const composeConfig: ComposeConfig = {
|
|
217
|
-
bin,
|
|
218
|
-
subcommand,
|
|
219
|
-
composeFile: stateComposeFile,
|
|
220
|
-
envFile: stateEnvFile,
|
|
221
|
-
};
|
|
222
|
-
|
|
223
|
-
const coreServices = ["caddy", "admin"];
|
|
224
|
-
|
|
225
|
-
const spin6 = spinner("Pulling core service images...");
|
|
226
|
-
await composePull(composeConfig, coreServices);
|
|
227
|
-
spin6.stop(green("Core images pulled"));
|
|
228
|
-
|
|
229
|
-
const spin7 = spinner("Starting core services...");
|
|
230
|
-
await composeUp(composeConfig, coreServices, { detach: true });
|
|
231
|
-
spin7.stop(green("Core services started"));
|
|
232
|
-
|
|
233
|
-
// Wait for admin health check
|
|
234
|
-
const adminUrl = "http://localhost/admin";
|
|
235
|
-
const healthUrl = `${adminUrl}/api/setup/status`;
|
|
236
|
-
const spin8 = spinner("Waiting for admin interface...");
|
|
237
|
-
|
|
238
|
-
let healthy = false;
|
|
239
|
-
for (let i = 0; i < 90; i++) {
|
|
240
|
-
try {
|
|
241
|
-
const response = await fetch(healthUrl, { signal: AbortSignal.timeout(3000) });
|
|
242
|
-
if (response.ok) {
|
|
243
|
-
healthy = true;
|
|
244
|
-
break;
|
|
245
|
-
}
|
|
246
|
-
} catch {
|
|
247
|
-
// Service not ready yet
|
|
248
|
-
}
|
|
249
|
-
await Bun.sleep(2000);
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
if (!healthy) {
|
|
253
|
-
spin8.stop(yellow("Admin interface did not become healthy in time"));
|
|
254
|
-
} else {
|
|
255
|
-
spin8.stop(green("Admin interface ready"));
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
// Open browser
|
|
259
|
-
if (!options.noOpen && healthy) {
|
|
260
|
-
try {
|
|
261
|
-
if (os === "macos") {
|
|
262
|
-
Bun.spawn(["open", adminUrl]);
|
|
263
|
-
} else if (os === "linux") {
|
|
264
|
-
Bun.spawn(["xdg-open", adminUrl]);
|
|
265
|
-
} else {
|
|
266
|
-
// Windows — use cmd /c start
|
|
267
|
-
Bun.spawn(["cmd", "/c", "start", adminUrl]);
|
|
268
|
-
}
|
|
269
|
-
} catch {
|
|
270
|
-
// Ignore — we print the URL below
|
|
271
|
-
}
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
// ============================================================================
|
|
275
|
-
// Final output
|
|
276
|
-
// ============================================================================
|
|
277
|
-
|
|
278
|
-
if (healthy) {
|
|
279
|
-
log("");
|
|
280
|
-
log(bold(green(" OpenPalm setup wizard is ready!")));
|
|
281
|
-
log("");
|
|
282
|
-
info(` Setup wizard: ${cyan(adminUrl)}`);
|
|
283
|
-
log("");
|
|
284
|
-
if (generatedAdminToken) {
|
|
285
|
-
info(` Admin password: ${yellow(generatedAdminToken)}`);
|
|
286
|
-
log("");
|
|
287
|
-
}
|
|
288
|
-
log(bold(" What happens next:"));
|
|
289
|
-
info(" 1. The setup wizard opens in your browser");
|
|
290
|
-
info(" 2. Enter your AI provider API key (e.g. from console.anthropic.com)");
|
|
291
|
-
info(" 3. The wizard will download and start remaining services automatically");
|
|
292
|
-
info(" 4. Pick which channels to enable (chat, Discord, etc.)");
|
|
293
|
-
info(" 5. Done! Start chatting with your assistant");
|
|
294
|
-
log("");
|
|
295
|
-
if (!options.noOpen) {
|
|
296
|
-
info(" Opening setup wizard in your browser...");
|
|
297
|
-
} else {
|
|
298
|
-
info(` Open this URL in your browser to continue: ${adminUrl}`);
|
|
299
|
-
}
|
|
300
|
-
} else {
|
|
301
|
-
log("");
|
|
302
|
-
log(bold(yellow(" Setup did not come online within 90 seconds")));
|
|
303
|
-
log("");
|
|
304
|
-
info(" This usually means containers are still starting. Try these steps:");
|
|
305
|
-
log("");
|
|
306
|
-
info(` 1. Wait a minute, then open: ${adminUrl}`);
|
|
307
|
-
log("");
|
|
308
|
-
info(" 2. Check if containers are running:");
|
|
309
|
-
info(" openpalm status");
|
|
310
|
-
log("");
|
|
311
|
-
info(" 3. Check logs for errors:");
|
|
312
|
-
info(" openpalm logs");
|
|
313
|
-
log("");
|
|
314
|
-
info(" 4. Common fixes:");
|
|
315
|
-
info(" - Make sure port 80 is not used by another service");
|
|
316
|
-
info(" - Restart Docker/Podman and try again");
|
|
317
|
-
info(" - Check that you have internet access (images need to download)");
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
log("");
|
|
321
|
-
log(bold(" Useful commands:"));
|
|
322
|
-
info(" View logs: openpalm logs");
|
|
323
|
-
info(" Stop: openpalm stop");
|
|
324
|
-
info(" Uninstall: openpalm uninstall");
|
|
325
|
-
log("");
|
|
326
|
-
}
|
package/src/commands/logs.ts
DELETED
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
import { composeLogs } from "@openpalm/lib/compose.ts";
|
|
2
|
-
import { loadComposeConfig } from "@openpalm/lib/config.ts";
|
|
3
|
-
|
|
4
|
-
export async function logs(services?: string[]): Promise<void> {
|
|
5
|
-
const config = await loadComposeConfig();
|
|
6
|
-
await composeLogs(config, services?.length ? services : undefined, { follow: true, tail: 50 });
|
|
7
|
-
}
|
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
import { statSync } from "node:fs";
|
|
2
|
-
import { info } from "@openpalm/lib/ui.ts";
|
|
3
|
-
|
|
4
|
-
const devDir = ".dev";
|
|
5
|
-
const envFile = ".env";
|
|
6
|
-
|
|
7
|
-
const requiredDirs = [
|
|
8
|
-
"config",
|
|
9
|
-
"data/postgres",
|
|
10
|
-
"data/qdrant",
|
|
11
|
-
"data/openmemory",
|
|
12
|
-
"data/assistant",
|
|
13
|
-
"state/gateway",
|
|
14
|
-
"state/caddy",
|
|
15
|
-
"state/rendered/caddy",
|
|
16
|
-
];
|
|
17
|
-
|
|
18
|
-
export function preflight(): void {
|
|
19
|
-
const issues: string[] = [];
|
|
20
|
-
|
|
21
|
-
try {
|
|
22
|
-
statSync(envFile);
|
|
23
|
-
} catch {
|
|
24
|
-
issues.push(`Missing ${envFile}. Run: bun run dev:setup`);
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
try {
|
|
28
|
-
statSync(devDir);
|
|
29
|
-
} catch {
|
|
30
|
-
issues.push(`Missing ${devDir}/ directory. Run: bun run dev:setup`);
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
if (issues.length === 0) {
|
|
34
|
-
for (const dir of requiredDirs) {
|
|
35
|
-
try {
|
|
36
|
-
statSync(`${devDir}/${dir}`);
|
|
37
|
-
} catch {
|
|
38
|
-
issues.push(`Missing ${devDir}/${dir}. Run: bun run dev:setup`);
|
|
39
|
-
break;
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
if (issues.length > 0) {
|
|
45
|
-
throw new Error(`Pre-flight check failed:\n\n${issues.map((issue) => ` - ${issue}`).join("\n")}\n\nRun 'bun run dev:setup' first, then try again.`);
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
info("Pre-flight check passed.");
|
|
49
|
-
}
|
package/src/commands/restart.ts
DELETED
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
import { composeRestart } from "@openpalm/lib/compose.ts";
|
|
2
|
-
import { loadComposeConfig } from "@openpalm/lib/config.ts";
|
|
3
|
-
import { info, green } from "@openpalm/lib/ui.ts";
|
|
4
|
-
|
|
5
|
-
export async function restart(services?: string[]): Promise<void> {
|
|
6
|
-
const config = await loadComposeConfig();
|
|
7
|
-
info("Restarting services...");
|
|
8
|
-
await composeRestart(config, services);
|
|
9
|
-
info(green("Services restarted."));
|
|
10
|
-
}
|
package/src/commands/start.ts
DELETED
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
import { composeUp } from "@openpalm/lib/compose.ts";
|
|
2
|
-
import { loadComposeConfig } from "@openpalm/lib/config.ts";
|
|
3
|
-
import { info, green } from "@openpalm/lib/ui.ts";
|
|
4
|
-
|
|
5
|
-
export async function start(services?: string[]): Promise<void> {
|
|
6
|
-
const config = await loadComposeConfig();
|
|
7
|
-
info("Starting services...");
|
|
8
|
-
await composeUp(config, services);
|
|
9
|
-
info(green("Services started."));
|
|
10
|
-
}
|
package/src/commands/status.ts
DELETED
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
import { composePs } from "@openpalm/lib/compose.ts";
|
|
2
|
-
import { loadComposeConfig } from "@openpalm/lib/config.ts";
|
|
3
|
-
import { log } from "@openpalm/lib/ui.ts";
|
|
4
|
-
|
|
5
|
-
export async function status(): Promise<void> {
|
|
6
|
-
const config = await loadComposeConfig();
|
|
7
|
-
const output = await composePs(config);
|
|
8
|
-
log(output);
|
|
9
|
-
}
|
package/src/commands/stop.ts
DELETED
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
import { composeStop } from "@openpalm/lib/compose.ts";
|
|
2
|
-
import { loadComposeConfig } from "@openpalm/lib/config.ts";
|
|
3
|
-
import { info, green } from "@openpalm/lib/ui.ts";
|
|
4
|
-
|
|
5
|
-
export async function stop(services?: string[]): Promise<void> {
|
|
6
|
-
const config = await loadComposeConfig();
|
|
7
|
-
info("Stopping services...");
|
|
8
|
-
await composeStop(config, services);
|
|
9
|
-
info(green("Services stopped."));
|
|
10
|
-
}
|
|
@@ -1,124 +0,0 @@
|
|
|
1
|
-
import { rm, unlink } from "node:fs/promises";
|
|
2
|
-
import { join } from "node:path";
|
|
3
|
-
import type { UninstallOptions, ContainerPlatform } from "../types.ts";
|
|
4
|
-
import type { ComposeConfig } from "@openpalm/lib/types.ts";
|
|
5
|
-
import { composeDown } from "@openpalm/lib/compose.ts";
|
|
6
|
-
import { readEnvFile } from "@openpalm/lib/env.ts";
|
|
7
|
-
import { resolveXDGPaths } from "@openpalm/lib/paths.ts";
|
|
8
|
-
import { resolveComposeBin, detectRuntime, detectOS } from "@openpalm/lib/runtime.ts";
|
|
9
|
-
import { log, info, warn, error, bold, green, red, yellow, confirm } from "@openpalm/lib/ui.ts";
|
|
10
|
-
|
|
11
|
-
export async function uninstall(options: UninstallOptions): Promise<void> {
|
|
12
|
-
// 1. Resolve XDG paths
|
|
13
|
-
const xdg = resolveXDGPaths();
|
|
14
|
-
|
|
15
|
-
// 2. Try to read .env from state home, falling back to CWD .env
|
|
16
|
-
let env: Record<string, string> = {};
|
|
17
|
-
const stateEnvPath = join(xdg.state, ".env");
|
|
18
|
-
try {
|
|
19
|
-
env = await readEnvFile(stateEnvPath);
|
|
20
|
-
} catch {
|
|
21
|
-
try {
|
|
22
|
-
env = await readEnvFile(".env");
|
|
23
|
-
} catch {
|
|
24
|
-
// No env file found, continue with empty env
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
// 3. Determine container platform
|
|
29
|
-
let platform: ContainerPlatform | null = null;
|
|
30
|
-
if (options.runtime) {
|
|
31
|
-
platform = options.runtime;
|
|
32
|
-
} else if (env.OPENPALM_CONTAINER_PLATFORM) {
|
|
33
|
-
platform = env.OPENPALM_CONTAINER_PLATFORM as ContainerPlatform;
|
|
34
|
-
} else {
|
|
35
|
-
platform = await detectRuntime(detectOS());
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
// 4. Resolve compose bin/subcommand if platform found
|
|
39
|
-
let composeBin: { bin: string; subcommand: string } | null = null;
|
|
40
|
-
if (platform) {
|
|
41
|
-
composeBin = resolveComposeBin(platform);
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
// 5. Print planned actions summary
|
|
45
|
-
log("");
|
|
46
|
-
log(bold("Uninstall Summary:"));
|
|
47
|
-
log(`Runtime platform: ${platform || "not detected"}`);
|
|
48
|
-
log("Stop/remove containers: yes");
|
|
49
|
-
log(`Remove images: ${options.removeImages ? "yes" : "no"}`);
|
|
50
|
-
log(`Remove all data/config/state: ${options.removeAll ? "yes" : "no"}`);
|
|
51
|
-
log("");
|
|
52
|
-
log(`Data directory: ${xdg.data}`);
|
|
53
|
-
log(`Config directory: ${xdg.config}`);
|
|
54
|
-
log(`State directory: ${xdg.state}`);
|
|
55
|
-
log("");
|
|
56
|
-
|
|
57
|
-
// 6. Prompt for confirmation if not --yes
|
|
58
|
-
if (!options.yes) {
|
|
59
|
-
const shouldContinue = await confirm("Continue?");
|
|
60
|
-
if (!shouldContinue) {
|
|
61
|
-
log("Aborted.");
|
|
62
|
-
return;
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
// 7. Stop and remove containers if compose is available
|
|
67
|
-
const composeFilePath = join(xdg.state, "docker-compose.yml");
|
|
68
|
-
if (composeBin && platform) {
|
|
69
|
-
try {
|
|
70
|
-
// Check if compose file exists by attempting to read env (simple existence check)
|
|
71
|
-
await Bun.file(composeFilePath).text();
|
|
72
|
-
|
|
73
|
-
const config: ComposeConfig = {
|
|
74
|
-
bin: composeBin.bin,
|
|
75
|
-
subcommand: composeBin.subcommand,
|
|
76
|
-
envFile: stateEnvPath,
|
|
77
|
-
composeFile: composeFilePath,
|
|
78
|
-
};
|
|
79
|
-
|
|
80
|
-
await composeDown(config, {
|
|
81
|
-
removeOrphans: true,
|
|
82
|
-
removeImages: options.removeImages,
|
|
83
|
-
});
|
|
84
|
-
} catch {
|
|
85
|
-
// 8. Compose file not found or other error
|
|
86
|
-
warn("Compose runtime or file not found; skipping container shutdown.");
|
|
87
|
-
}
|
|
88
|
-
} else {
|
|
89
|
-
// 8. No platform/compose bin detected
|
|
90
|
-
warn("Compose runtime or file not found; skipping container shutdown.");
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
// 9. Remove all data/config/state if requested
|
|
94
|
-
if (options.removeAll) {
|
|
95
|
-
try {
|
|
96
|
-
await rm(xdg.data, { recursive: true, force: true });
|
|
97
|
-
} catch {
|
|
98
|
-
// Directory may not exist, continue
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
try {
|
|
102
|
-
await rm(xdg.config, { recursive: true, force: true });
|
|
103
|
-
} catch {
|
|
104
|
-
// Directory may not exist, continue
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
try {
|
|
108
|
-
await rm(xdg.state, { recursive: true, force: true });
|
|
109
|
-
} catch {
|
|
110
|
-
// Directory may not exist, continue
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
try {
|
|
114
|
-
await unlink(".env");
|
|
115
|
-
} catch {
|
|
116
|
-
// .env may not exist in CWD, continue
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
info("Removed OpenPalm data/config/state and local .env.");
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
// 10. Success message
|
|
123
|
-
info(green("Uninstall complete."));
|
|
124
|
-
}
|
package/src/commands/update.ts
DELETED
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
import { composePull, composeUp } from "@openpalm/lib/compose.ts";
|
|
2
|
-
import { loadComposeConfig } from "@openpalm/lib/config.ts";
|
|
3
|
-
import { info, green } from "@openpalm/lib/ui.ts";
|
|
4
|
-
|
|
5
|
-
export async function update(): Promise<void> {
|
|
6
|
-
const config = await loadComposeConfig();
|
|
7
|
-
info("Pulling latest images...");
|
|
8
|
-
await composePull(config);
|
|
9
|
-
info("Recreating containers with updated images...");
|
|
10
|
-
await composeUp(config, undefined, { pull: "always" });
|
|
11
|
-
info(green("Update complete."));
|
|
12
|
-
}
|