openpalm 0.11.0-beta.3 → 0.11.0-beta.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/package.json +7 -4
- package/src/lib/env.ts +58 -6
- package/src/main.ts +1 -2
- package/src/commands/service.ts +0 -76
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "openpalm",
|
|
3
|
-
"version": "0.11.0-beta.
|
|
3
|
+
"version": "0.11.0-beta.6",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"license": "MPL-2.0",
|
|
6
6
|
"description": "OpenPalm CLI — install and manage a self-hosted OpenPalm stack",
|
|
@@ -16,8 +16,8 @@
|
|
|
16
16
|
"start": "bun run src/main.ts",
|
|
17
17
|
"test": "bun test",
|
|
18
18
|
"test:e2e": "npx playwright test",
|
|
19
|
-
"wizard:
|
|
20
|
-
"_build_note": "
|
|
19
|
+
"wizard:local": "bun run src/main.ts install --no-start --force",
|
|
20
|
+
"_build_note": "CI runs 'bun run ui:build:tar' from repo root before any build:* target. The prebuild hook below is npm-only; Bun does not run lifecycle hooks.",
|
|
21
21
|
"prebuild": "cd ../ui && npm run build && npm run build:tar",
|
|
22
22
|
"build": "bun build src/main.ts --compile --outfile dist/openpalm-cli",
|
|
23
23
|
"build:linux-x64": "bun build src/main.ts --compile --target=bun-linux-x64 --outfile dist/openpalm-cli-linux-x64",
|
|
@@ -27,8 +27,11 @@
|
|
|
27
27
|
"build:windows-x64": "bun build src/main.ts --compile --target=bun-windows-x64 --outfile dist/openpalm-cli-windows-x64.exe",
|
|
28
28
|
"build:windows-arm64": "bun build src/main.ts --compile --target=bun-windows-arm64 --outfile dist/openpalm-cli-windows-arm64.exe"
|
|
29
29
|
},
|
|
30
|
+
"engines": {
|
|
31
|
+
"bun": ">=1.0.0"
|
|
32
|
+
},
|
|
30
33
|
"dependencies": {
|
|
31
|
-
"@openpalm/lib": "0.11.0-beta.1",
|
|
34
|
+
"@openpalm/lib": ">=0.11.0-beta.5 <1.0.0",
|
|
32
35
|
"citty": "^0.2.1",
|
|
33
36
|
"yaml": "^2.8.0"
|
|
34
37
|
}
|
package/src/lib/env.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { join } from 'node:path';
|
|
2
|
-
import { mkdirSync } from 'node:fs';
|
|
3
|
-
import { reconcileStackEnvImageTag, resolveRequestedImageTag } from '@openpalm/lib';
|
|
2
|
+
import { mkdirSync, writeFileSync } from 'node:fs';
|
|
3
|
+
import { reconcileStackEnvImageTag, resolveRequestedImageTag, resolveOperatorIds, hasUsableOperatorId } from '@openpalm/lib';
|
|
4
4
|
import { defaultDockerSock } from './paths.ts';
|
|
5
5
|
|
|
6
6
|
/**
|
|
@@ -32,21 +32,25 @@ export async function ensureStackEnv(
|
|
|
32
32
|
const explicitImageTag = imageTagOverride ?? process.env.OP_IMAGE_TAG;
|
|
33
33
|
const hasExplicitImageTag = explicitImageTag !== undefined && explicitImageTag !== '';
|
|
34
34
|
mkdirSync(stackDir, { recursive: true });
|
|
35
|
+
// Operator UID/GID — auto-detect from OP_HOME owner (or process UID).
|
|
36
|
+
// Returns null on Windows; in that case we omit OP_UID/OP_GID and let
|
|
37
|
+
// compose fall back to its `${OP_UID:-1000}` default (containers run
|
|
38
|
+
// in WSL2 Linux where this doesn't matter).
|
|
39
|
+
const ids = resolveOperatorIds(homeDir);
|
|
35
40
|
if (!(await Bun.file(systemEnvPath).exists())) {
|
|
36
41
|
const defaultImageTag = hasExplicitImageTag
|
|
37
42
|
? explicitImageTag
|
|
38
43
|
: (resolveRequestedImageTag(repoRef) || 'latest');
|
|
44
|
+
const idLines = ids ? `OP_UID=${ids.uid}\nOP_GID=${ids.gid}\n` : '';
|
|
39
45
|
const content = `# OpenPalm System Environment — system-managed, do not edit
|
|
40
46
|
OP_HOME=${homeDir}
|
|
41
47
|
OP_WORK_DIR=${workDir}
|
|
42
|
-
|
|
43
|
-
OP_GID=${process.getgid?.() ?? 1000}
|
|
44
|
-
OP_IMAGE_NAMESPACE=${process.env.OP_IMAGE_NAMESPACE || 'openpalm'}
|
|
48
|
+
${idLines}OP_IMAGE_NAMESPACE=${process.env.OP_IMAGE_NAMESPACE || 'openpalm'}
|
|
45
49
|
OP_IMAGE_TAG=${defaultImageTag}
|
|
46
50
|
`;
|
|
47
51
|
await Bun.write(systemEnvPath, content);
|
|
48
52
|
} else {
|
|
49
|
-
|
|
53
|
+
let current = await Bun.file(systemEnvPath).text();
|
|
50
54
|
const reconciled = reconcileStackEnvImageTag(
|
|
51
55
|
current,
|
|
52
56
|
repoRef,
|
|
@@ -54,6 +58,54 @@ OP_IMAGE_TAG=${defaultImageTag}
|
|
|
54
58
|
);
|
|
55
59
|
if (reconciled !== current) {
|
|
56
60
|
await Bun.write(systemEnvPath, reconciled);
|
|
61
|
+
current = reconciled;
|
|
57
62
|
}
|
|
63
|
+
|
|
64
|
+
// Backfill OP_UID/OP_GID for installs created by older CLI versions
|
|
65
|
+
// that hard-coded 1000. Only fill missing/zero values — never override
|
|
66
|
+
// a value the operator may have set explicitly.
|
|
67
|
+
if (ids) {
|
|
68
|
+
backfillOperatorIds(systemEnvPath, current, ids);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function readEnvKey(content: string, key: string): string | undefined {
|
|
74
|
+
// Minimal key=value reader for OP_UID/OP_GID. Stack.env is system-managed
|
|
75
|
+
// and these lines are always plain numerics (no quotes, no interpolation),
|
|
76
|
+
// so this avoids pulling in a full env parser.
|
|
77
|
+
const re = new RegExp(`^\\s*${key}\\s*=\\s*(.*?)\\s*$`, 'm');
|
|
78
|
+
const m = content.match(re);
|
|
79
|
+
return m?.[1];
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function backfillOperatorIds(
|
|
83
|
+
path: string,
|
|
84
|
+
current: string,
|
|
85
|
+
ids: { uid: number; gid: number },
|
|
86
|
+
): void {
|
|
87
|
+
const parsed: Record<string, string> = {};
|
|
88
|
+
const uidValue = readEnvKey(current, 'OP_UID');
|
|
89
|
+
const gidValue = readEnvKey(current, 'OP_GID');
|
|
90
|
+
if (uidValue !== undefined) parsed.OP_UID = uidValue;
|
|
91
|
+
if (gidValue !== undefined) parsed.OP_GID = gidValue;
|
|
92
|
+
|
|
93
|
+
const patches: Array<[string, string]> = [];
|
|
94
|
+
if (!hasUsableOperatorId(parsed, 'OP_UID')) patches.push(['OP_UID', String(ids.uid)]);
|
|
95
|
+
if (!hasUsableOperatorId(parsed, 'OP_GID')) patches.push(['OP_GID', String(ids.gid)]);
|
|
96
|
+
if (patches.length === 0) return;
|
|
97
|
+
|
|
98
|
+
let next = current;
|
|
99
|
+
for (const [key, value] of patches) {
|
|
100
|
+
const re = new RegExp(`^${key}=.*$`, 'm');
|
|
101
|
+
if (re.test(next)) {
|
|
102
|
+
next = next.replace(re, `${key}=${value}`);
|
|
103
|
+
} else {
|
|
104
|
+
next = (next.endsWith('\n') ? next : next + '\n') + `${key}=${value}\n`;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
if (next !== current) {
|
|
108
|
+
writeFileSync(path, next);
|
|
58
109
|
}
|
|
59
110
|
}
|
|
111
|
+
|
package/src/main.ts
CHANGED
|
@@ -10,7 +10,7 @@ export type { HostInfo } from './lib/host-info.ts';
|
|
|
10
10
|
|
|
11
11
|
const SUBCOMMAND_NAMES = new Set([
|
|
12
12
|
'install', 'uninstall', 'update', 'self-update', 'addon',
|
|
13
|
-
'start', 'stop', 'restart', 'logs', 'status',
|
|
13
|
+
'start', 'stop', 'restart', 'logs', 'status',
|
|
14
14
|
'validate', 'scan', 'rollback', 'automations',
|
|
15
15
|
'--help', '-h', 'help',
|
|
16
16
|
]);
|
|
@@ -114,7 +114,6 @@ export const mainCommand = defineCommand({
|
|
|
114
114
|
restart: () => import('./commands/restart.ts').then((m) => m.default),
|
|
115
115
|
logs: () => import('./commands/logs.ts').then((m) => m.default),
|
|
116
116
|
status: () => import('./commands/status.ts').then((m) => m.default),
|
|
117
|
-
service: () => import('./commands/service.ts').then((m) => m.default),
|
|
118
117
|
validate: () => import('./commands/validate.ts').then((m) => m.default),
|
|
119
118
|
scan: () => import('./commands/scan.ts').then((m) => m.default),
|
|
120
119
|
rollback: () => import('./commands/rollback.ts').then((m) => m.default),
|
package/src/commands/service.ts
DELETED
|
@@ -1,76 +0,0 @@
|
|
|
1
|
-
import { defineCommand } from 'citty';
|
|
2
|
-
import { buildManagedServices } from '@openpalm/lib';
|
|
3
|
-
import { ensureValidState } from '../lib/cli-state.ts';
|
|
4
|
-
import { runComposeWithPreflight, runComposeReadOnly } from '../lib/cli-compose.ts';
|
|
5
|
-
import { runLogsAction } from './logs.ts';
|
|
6
|
-
import { runStartAction } from './start.ts';
|
|
7
|
-
import { runStopAction } from './stop.ts';
|
|
8
|
-
import { runRestartAction } from './restart.ts';
|
|
9
|
-
|
|
10
|
-
const startCmd = defineCommand({
|
|
11
|
-
meta: { name: 'start', description: 'Start services' },
|
|
12
|
-
args: {
|
|
13
|
-
services: { type: 'positional', description: 'Service names', required: false },
|
|
14
|
-
},
|
|
15
|
-
async run({ args }) { await runStartAction(args._ ?? []); },
|
|
16
|
-
});
|
|
17
|
-
|
|
18
|
-
const stopCmd = defineCommand({
|
|
19
|
-
meta: { name: 'stop', description: 'Stop services' },
|
|
20
|
-
args: {
|
|
21
|
-
services: { type: 'positional', description: 'Service names', required: false },
|
|
22
|
-
},
|
|
23
|
-
async run({ args }) { await runStopAction(args._ ?? []); },
|
|
24
|
-
});
|
|
25
|
-
|
|
26
|
-
const restartCmd = defineCommand({
|
|
27
|
-
meta: { name: 'restart', description: 'Restart services' },
|
|
28
|
-
args: {
|
|
29
|
-
services: { type: 'positional', description: 'Service names', required: false },
|
|
30
|
-
},
|
|
31
|
-
async run({ args }) { await runRestartAction(args._ ?? []); },
|
|
32
|
-
});
|
|
33
|
-
|
|
34
|
-
const logsCmd = defineCommand({
|
|
35
|
-
meta: { name: 'logs', description: 'View service logs' },
|
|
36
|
-
args: {
|
|
37
|
-
services: { type: 'positional', description: 'Service names', required: false },
|
|
38
|
-
},
|
|
39
|
-
async run({ args }) { await runLogsAction(args._ ?? []); },
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
const updateCmd = defineCommand({
|
|
43
|
-
meta: { name: 'update', description: 'Pull latest images' },
|
|
44
|
-
async run() {
|
|
45
|
-
const state = ensureValidState();
|
|
46
|
-
const managedServices = await buildManagedServices(state);
|
|
47
|
-
console.log('Pulling latest images...');
|
|
48
|
-
await runComposeWithPreflight(state, ['pull', ...managedServices]);
|
|
49
|
-
console.log('Recreating containers...');
|
|
50
|
-
await runComposeWithPreflight(state, ['up', '-d', '--force-recreate', ...managedServices]);
|
|
51
|
-
console.log('Update complete.');
|
|
52
|
-
},
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
const statusCmd = defineCommand({
|
|
56
|
-
meta: { name: 'status', description: 'Show container status' },
|
|
57
|
-
async run() {
|
|
58
|
-
const state = ensureValidState();
|
|
59
|
-
await runComposeReadOnly(state, ['ps', '--format', 'table']);
|
|
60
|
-
},
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
export default defineCommand({
|
|
64
|
-
meta: {
|
|
65
|
-
name: 'service',
|
|
66
|
-
description: 'Service lifecycle operations (start|stop|restart|logs|update|status)',
|
|
67
|
-
},
|
|
68
|
-
subCommands: {
|
|
69
|
-
start: startCmd,
|
|
70
|
-
stop: stopCmd,
|
|
71
|
-
restart: restartCmd,
|
|
72
|
-
logs: logsCmd,
|
|
73
|
-
update: updateCmd,
|
|
74
|
-
status: statusCmd,
|
|
75
|
-
},
|
|
76
|
-
});
|