vault-cortex 0.7.0 → 0.8.0
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/README.md +53 -0
- package/dist/docker.js +41 -1
- package/dist/env.js +4 -8
- package/dist/lifecycle.js +166 -0
- package/dist/main.js +14 -0
- package/dist/messages.js +7 -6
- package/dist/program.js +23 -0
- package/dist/upgrade.js +14 -49
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -23,6 +23,10 @@ manages the container so you don't have to.
|
|
|
23
23
|
token, start the server
|
|
24
24
|
- [`upgrade`](#upgrade) — pull the latest image and re-create the container;
|
|
25
25
|
your data stays
|
|
26
|
+
- [`restart`](#restart) — re-create the container so your `.env` edits take
|
|
27
|
+
effect; no image pull
|
|
28
|
+
- [`logs`](#logs) — show the server's logs, live or after the fact
|
|
29
|
+
- [`down`](#down) — stop and remove the container; your data stays
|
|
26
30
|
- [`get-sync-token`](#get-sync-token) — generate an Obsidian Sync auth token
|
|
27
31
|
for remote setups
|
|
28
32
|
|
|
@@ -88,6 +92,55 @@ include Compose files you can use directly. If you set up with Compose, stick
|
|
|
88
92
|
with Compose for updates too (`docker compose pull && docker compose up -d`)
|
|
89
93
|
— the CLI and Compose manage the container independently.
|
|
90
94
|
|
|
95
|
+
## restart
|
|
96
|
+
|
|
97
|
+
Re-create the container from your `.env` and verify health:
|
|
98
|
+
|
|
99
|
+
```bash
|
|
100
|
+
npx vault-cortex@latest restart
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
Use it after editing `.env` — settings are only read when the container is
|
|
104
|
+
created, so a plain `docker restart` won't pick them up, but this will. Unlike
|
|
105
|
+
[`upgrade`](#upgrade), it never pulls a new image: you get the same server
|
|
106
|
+
version back, with your current settings applied.
|
|
107
|
+
|
|
108
|
+
Use `--dir <path>` if your config isn't in `./vault-cortex`.
|
|
109
|
+
|
|
110
|
+
## logs
|
|
111
|
+
|
|
112
|
+
Show the server's logs:
|
|
113
|
+
|
|
114
|
+
```bash
|
|
115
|
+
npx vault-cortex@latest logs
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
Flags:
|
|
119
|
+
|
|
120
|
+
- `--follow` — keep streaming new output until you press ctrl-C
|
|
121
|
+
- `--since <time>` — only logs newer than this (e.g. `10m`, `2h`, or a
|
|
122
|
+
timestamp)
|
|
123
|
+
- `--dir <path>` — directory containing `.env` (default `./vault-cortex`)
|
|
124
|
+
|
|
125
|
+
```bash
|
|
126
|
+
npx vault-cortex@latest logs --follow --since 10m
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
## down
|
|
130
|
+
|
|
131
|
+
Stop and remove the container:
|
|
132
|
+
|
|
133
|
+
```bash
|
|
134
|
+
npx vault-cortex@latest down
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
Safe by design: your vault, search index, and `.env` settings all live
|
|
138
|
+
outside the container, so nothing is lost. Start again any time with
|
|
139
|
+
[`restart`](#restart). Running `down` when nothing is running is fine — it
|
|
140
|
+
just tells you there's nothing to stop.
|
|
141
|
+
|
|
142
|
+
Use `--dir <path>` if your config isn't in `./vault-cortex`.
|
|
143
|
+
|
|
91
144
|
## get-sync-token
|
|
92
145
|
|
|
93
146
|
Generate an [Obsidian Sync](https://obsidian.md/sync) auth token — needed for
|
package/dist/docker.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { spawnSync } from "node:child_process";
|
|
1
|
+
import { spawn, spawnSync } from "node:child_process";
|
|
2
2
|
export const LOCAL_IMAGE = "ghcr.io/aliasunder/vault-cortex:latest";
|
|
3
3
|
export const REMOTE_IMAGE = "ghcr.io/aliasunder/vault-cortex:remote";
|
|
4
4
|
export const CONTAINER_NAME = "vault-cortex";
|
|
@@ -94,6 +94,19 @@ export const buildDockerRunArgs = (params) => {
|
|
|
94
94
|
args.push(image);
|
|
95
95
|
return args;
|
|
96
96
|
};
|
|
97
|
+
/**
|
|
98
|
+
* Builds the `docker logs` args array. Pure function — no I/O — so it's
|
|
99
|
+
* testable without spawning processes.
|
|
100
|
+
*/
|
|
101
|
+
export const buildDockerLogsArgs = (params) => {
|
|
102
|
+
const { follow, since } = params;
|
|
103
|
+
return [
|
|
104
|
+
"logs",
|
|
105
|
+
...(follow ? ["--follow"] : []),
|
|
106
|
+
...(since ? ["--since", since] : []),
|
|
107
|
+
CONTAINER_NAME,
|
|
108
|
+
];
|
|
109
|
+
};
|
|
97
110
|
export const createDockerRunner = () => ({
|
|
98
111
|
isDaemonRunning: () => spawnSync("docker", ["info"], { timeout: 5_000 }).status === 0,
|
|
99
112
|
// stdout is discarded: `docker run -d` prints only the container ID there,
|
|
@@ -108,6 +121,33 @@ export const createDockerRunner = () => ({
|
|
|
108
121
|
}).status === 0,
|
|
109
122
|
pullImage: (image) => spawnSync("docker", ["pull", image], { stdio: "inherit" }).status === 0,
|
|
110
123
|
stopAndRemoveContainer: () => spawnSync("docker", ["rm", "-f", CONTAINER_NAME]).status === 0,
|
|
124
|
+
// `docker rm -f` on a missing container exits 1 on engines < 23 and 0 on
|
|
125
|
+
// >= 23, so stopAndRemoveContainer's status can't distinguish "already
|
|
126
|
+
// gone" from "failed" — callers needing idempotent messaging probe
|
|
127
|
+
// existence first. Output stays piped (discarded): this is a boolean probe.
|
|
128
|
+
containerExists: () => spawnSync("docker", ["container", "inspect", CONTAINER_NAME]).status === 0,
|
|
129
|
+
// Async spawn, not spawnSync: --follow streams until interrupted, and the
|
|
130
|
+
// exit code must be observable after the stream closes.
|
|
131
|
+
streamLogs: (params) => new Promise((resolveExitCode) => {
|
|
132
|
+
const child = spawn("docker", buildDockerLogsArgs(params), {
|
|
133
|
+
stdio: ["ignore", "inherit", "inherit"],
|
|
134
|
+
});
|
|
135
|
+
// ctrl-C delivers SIGINT to the whole foreground process group. Node's
|
|
136
|
+
// default disposition would kill this process before the child's
|
|
137
|
+
// "close" event fires; this no-op keep-alive lets the docker child
|
|
138
|
+
// exit on its own SIGINT, the streams flush, and the exit code
|
|
139
|
+
// propagate. `once` self-removes, so later ctrl-Cs behave normally.
|
|
140
|
+
process.once("SIGINT", () => { });
|
|
141
|
+
child.once("error", (spawnError) => {
|
|
142
|
+
// Event handler, not a catch — but the same "never swallow" rule
|
|
143
|
+
// applies: without this line a spawn failure is a bare exit 1.
|
|
144
|
+
process.stderr.write(`vault-cortex: could not run docker logs — ${spawnError.message}\n`);
|
|
145
|
+
resolveExitCode(1);
|
|
146
|
+
});
|
|
147
|
+
// A null code means the child died to a signal — report the shell
|
|
148
|
+
// convention for ctrl-C (128 + SIGINT = 130).
|
|
149
|
+
child.once("close", (code) => resolveExitCode(code ?? 130));
|
|
150
|
+
}),
|
|
111
151
|
runObsidianLogin: (configMountPath) => spawnSync("docker", buildObsidianLoginArgs({
|
|
112
152
|
configMountPath,
|
|
113
153
|
uid: process.getuid?.(),
|
package/dist/env.js
CHANGED
|
@@ -11,8 +11,8 @@
|
|
|
11
11
|
// sync:local-optional:begin
|
|
12
12
|
const LOCAL_OPTIONAL_BLOCK = `# Optional ──────────────────────────────────────────────────
|
|
13
13
|
# To change a setting: edit its value (uncommenting it first if needed),
|
|
14
|
-
# then apply with "npx vault-cortex
|
|
15
|
-
# re-read this file).
|
|
14
|
+
# then apply with "npx vault-cortex restart" (plain docker restart does
|
|
15
|
+
# not re-read this file).
|
|
16
16
|
|
|
17
17
|
# Public URL for OAuth issuer URL in discovery metadata (default: http://localhost:8000).
|
|
18
18
|
# Override if you expose the server on a different URL (e.g. via a reverse proxy).
|
|
@@ -101,8 +101,8 @@ WINDOWS_MODE=false
|
|
|
101
101
|
// sync:remote-optional:begin
|
|
102
102
|
const REMOTE_OPTIONAL_BLOCK = `# Optional ──────────────────────────────────────────────────
|
|
103
103
|
# To change a setting: edit its value (uncommenting it first if needed),
|
|
104
|
-
# then apply with "npx vault-cortex
|
|
105
|
-
# re-read this file).
|
|
104
|
+
# then apply with "npx vault-cortex restart" (plain docker restart does
|
|
105
|
+
# not re-read this file).
|
|
106
106
|
|
|
107
107
|
# Your IANA timezone — affects daily note resolution and memory timestamps.
|
|
108
108
|
# TZ=America/New_York
|
|
@@ -204,10 +204,6 @@ MCP_AUTH_TOKEN=${answers.mcpAuthToken}
|
|
|
204
204
|
# Absolute path to your Obsidian vault on this machine.
|
|
205
205
|
VAULT_PATH=${answers.vaultPath}
|
|
206
206
|
|
|
207
|
-
# Public URL for OAuth issuer URL in discovery metadata.
|
|
208
|
-
# Override if you expose the server on a different URL (e.g. via a reverse proxy).
|
|
209
|
-
PUBLIC_URL=http://localhost:8000
|
|
210
|
-
|
|
211
207
|
${LOCAL_OPTIONAL_BLOCK}`;
|
|
212
208
|
export const buildRemoteEnv = (answers) => {
|
|
213
209
|
const vaultPasswordLines = answers.vaultPassword === undefined
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
import { join, resolve } from "node:path";
|
|
2
|
+
import { CONTAINER_NAME, pollHealth } from "./docker.js";
|
|
3
|
+
import { detectMode, hasEnvPublicUrl, readEnvPort, readEnvVaultPath, } from "./scaffold.js";
|
|
4
|
+
import { expandTilde } from "./vault.js";
|
|
5
|
+
const DEFAULT_TARGET_DIR = "./vault-cortex";
|
|
6
|
+
/**
|
|
7
|
+
* Light gate for commands that operate on an existing deployment without
|
|
8
|
+
* starting a container: resolves --dir and confirms an init'd .env exists.
|
|
9
|
+
* Full start-readiness validation lives in resolveDeployment.
|
|
10
|
+
*/
|
|
11
|
+
const requireInitializedDir = (dirFlag, prompts) => {
|
|
12
|
+
const targetDir = resolve(expandTilde(dirFlag ?? DEFAULT_TARGET_DIR));
|
|
13
|
+
const envFilePath = join(targetDir, ".env");
|
|
14
|
+
const mode = detectMode(envFilePath);
|
|
15
|
+
if (!mode) {
|
|
16
|
+
prompts.error(`No .env found in ${targetDir} — run \`npx vault-cortex init\` first.`);
|
|
17
|
+
return undefined;
|
|
18
|
+
}
|
|
19
|
+
return { targetDir, envFilePath, mode };
|
|
20
|
+
};
|
|
21
|
+
/**
|
|
22
|
+
* Resolves --dir to the on-disk deployment and validates its .env for a
|
|
23
|
+
* container start: mode detection, VAULT_PATH (local), and PUBLIC_URL
|
|
24
|
+
* (local). Reports each failure via prompts.error and returns undefined.
|
|
25
|
+
* Shared by upgrade and restart so their precondition checks can't drift.
|
|
26
|
+
*/
|
|
27
|
+
export const resolveDeployment = (dirFlag, prompts) => {
|
|
28
|
+
const initialized = requireInitializedDir(dirFlag, prompts);
|
|
29
|
+
if (!initialized)
|
|
30
|
+
return undefined;
|
|
31
|
+
const { targetDir, envFilePath, mode } = initialized;
|
|
32
|
+
const port = readEnvPort(envFilePath);
|
|
33
|
+
const vaultPath = mode === "local" ? readEnvVaultPath(envFilePath) : undefined;
|
|
34
|
+
if (mode === "local" && !vaultPath) {
|
|
35
|
+
prompts.error(`VAULT_PATH is empty or missing in ${targetDir}/.env — cannot start the container.`);
|
|
36
|
+
return undefined;
|
|
37
|
+
}
|
|
38
|
+
if (mode === "local" && !hasEnvPublicUrl(envFilePath)) {
|
|
39
|
+
prompts.error(`PUBLIC_URL not found in ${targetDir}/.env — the server requires it.\n` +
|
|
40
|
+
`Add this line to your .env:\n PUBLIC_URL=http://localhost:${port}`);
|
|
41
|
+
return undefined;
|
|
42
|
+
}
|
|
43
|
+
return { mode, targetDir, envFilePath, port, vaultPath };
|
|
44
|
+
};
|
|
45
|
+
/**
|
|
46
|
+
* Verifies the container runtime is reachable, reporting the shared error
|
|
47
|
+
* message when it isn't. Callers early-return on false.
|
|
48
|
+
*/
|
|
49
|
+
export const ensureDaemonRunning = (docker, prompts) => {
|
|
50
|
+
if (docker.isDaemonRunning())
|
|
51
|
+
return true;
|
|
52
|
+
prompts.error("Container runtime not running — start Docker Desktop, Colima,\n" +
|
|
53
|
+
"OrbStack, or another Docker-compatible runtime.");
|
|
54
|
+
return false;
|
|
55
|
+
};
|
|
56
|
+
/**
|
|
57
|
+
* Stop-and-remove → docker run → health poll, with the shared messaging.
|
|
58
|
+
* Returns a process exit code. Callers own the daemon check and any
|
|
59
|
+
* image-pull step.
|
|
60
|
+
*/
|
|
61
|
+
export const recreateContainer = async (params, deps) => {
|
|
62
|
+
const { deployment, healthTimeoutMs } = params;
|
|
63
|
+
const { prompts, docker, fetchFn } = deps;
|
|
64
|
+
// `docker rm -f` on a missing container exits non-zero on engines < 23,
|
|
65
|
+
// so the bare return value can't distinguish "nothing to remove" from a
|
|
66
|
+
// real failure — only treat removal as failed when a container exists.
|
|
67
|
+
// Proceeding after a genuine failure would surface as a confusing
|
|
68
|
+
// "container name already in use" from docker run.
|
|
69
|
+
if (docker.containerExists() && !docker.stopAndRemoveContainer()) {
|
|
70
|
+
prompts.error(`Could not remove the existing container — check: docker rm -f ${CONTAINER_NAME}`);
|
|
71
|
+
return 1;
|
|
72
|
+
}
|
|
73
|
+
prompts.log("Starting container...");
|
|
74
|
+
const containerStarted = docker.dockerRun({
|
|
75
|
+
mode: deployment.mode,
|
|
76
|
+
envFilePath: deployment.envFilePath,
|
|
77
|
+
port: deployment.port,
|
|
78
|
+
vaultPath: deployment.vaultPath,
|
|
79
|
+
});
|
|
80
|
+
if (!containerStarted) {
|
|
81
|
+
prompts.error("docker run failed — see output above.");
|
|
82
|
+
return 1;
|
|
83
|
+
}
|
|
84
|
+
const spinner = prompts.spinner();
|
|
85
|
+
spinner.start("Waiting for the server to come up");
|
|
86
|
+
const healthy = await pollHealth({
|
|
87
|
+
url: `http://127.0.0.1:${deployment.port}/healthz`,
|
|
88
|
+
timeoutMs: healthTimeoutMs,
|
|
89
|
+
}, fetchFn);
|
|
90
|
+
if (!healthy) {
|
|
91
|
+
spinner.stop(`Server did not respond within 2 minutes — check: docker logs ${CONTAINER_NAME}`);
|
|
92
|
+
return 1;
|
|
93
|
+
}
|
|
94
|
+
spinner.stop("Server is up — health check passed.");
|
|
95
|
+
return 0;
|
|
96
|
+
};
|
|
97
|
+
/**
|
|
98
|
+
* Stops and removes the container. Vault data, the search index, and .env
|
|
99
|
+
* settings all live outside the container (bind mount, named volumes, host
|
|
100
|
+
* file), so this is always safe.
|
|
101
|
+
*/
|
|
102
|
+
export const runDown = async (flags, deps) => {
|
|
103
|
+
const { prompts, docker } = deps;
|
|
104
|
+
prompts.intro("vault-cortex down");
|
|
105
|
+
// Teardown only needs to confirm this is an init'd directory — the full
|
|
106
|
+
// .env validation (VAULT_PATH, PUBLIC_URL) guards container starts.
|
|
107
|
+
const initialized = requireInitializedDir(flags.dir, prompts);
|
|
108
|
+
if (!initialized)
|
|
109
|
+
return 1;
|
|
110
|
+
if (!ensureDaemonRunning(docker, prompts))
|
|
111
|
+
return 1;
|
|
112
|
+
if (!docker.containerExists()) {
|
|
113
|
+
prompts.log("No vault-cortex container found — nothing to stop.");
|
|
114
|
+
prompts.outro("Done.");
|
|
115
|
+
return 0;
|
|
116
|
+
}
|
|
117
|
+
if (!docker.stopAndRemoveContainer()) {
|
|
118
|
+
prompts.error(`Could not remove the container — check: docker rm -f ${CONTAINER_NAME}`);
|
|
119
|
+
return 1;
|
|
120
|
+
}
|
|
121
|
+
prompts.log("Container stopped and removed. Your vault data, search index, and settings are untouched.");
|
|
122
|
+
prompts.outro(`Start again with: npx vault-cortex restart --dir "${initialized.targetDir}"`);
|
|
123
|
+
return 0;
|
|
124
|
+
};
|
|
125
|
+
/**
|
|
126
|
+
* Streams `docker logs` for the vault-cortex container. The stream's exit
|
|
127
|
+
* code passes through as the command's exit code; no outro follows the raw
|
|
128
|
+
* docker output.
|
|
129
|
+
*/
|
|
130
|
+
export const runLogs = async (flags, deps) => {
|
|
131
|
+
const { prompts, docker } = deps;
|
|
132
|
+
prompts.intro("vault-cortex logs");
|
|
133
|
+
const initialized = requireInitializedDir(flags.dir, prompts);
|
|
134
|
+
if (!initialized)
|
|
135
|
+
return 1;
|
|
136
|
+
if (!ensureDaemonRunning(docker, prompts))
|
|
137
|
+
return 1;
|
|
138
|
+
if (!docker.containerExists()) {
|
|
139
|
+
prompts.error("No vault-cortex container — start it with `npx vault-cortex restart`.");
|
|
140
|
+
return 1;
|
|
141
|
+
}
|
|
142
|
+
return await docker.streamLogs({
|
|
143
|
+
follow: Boolean(flags.follow),
|
|
144
|
+
since: flags.since,
|
|
145
|
+
});
|
|
146
|
+
};
|
|
147
|
+
/**
|
|
148
|
+
* Re-creates the container from the .env on disk and verifies health.
|
|
149
|
+
* Unlike `docker restart`, this applies .env edits (the env-file is only
|
|
150
|
+
* read at container creation); unlike upgrade, it never pulls an image.
|
|
151
|
+
*/
|
|
152
|
+
export const runRestart = async (flags, deps) => {
|
|
153
|
+
const { prompts, docker, fetchFn } = deps;
|
|
154
|
+
prompts.intro("vault-cortex restart");
|
|
155
|
+
const deployment = resolveDeployment(flags.dir, prompts);
|
|
156
|
+
if (!deployment)
|
|
157
|
+
return 1;
|
|
158
|
+
if (!ensureDaemonRunning(docker, prompts))
|
|
159
|
+
return 1;
|
|
160
|
+
const exitCode = await recreateContainer({ deployment, healthTimeoutMs: deps.healthTimeoutMs }, { prompts, docker, fetchFn });
|
|
161
|
+
if (exitCode !== 0)
|
|
162
|
+
return exitCode;
|
|
163
|
+
prompts.log("Applied the current .env settings.");
|
|
164
|
+
prompts.outro("Restart complete.");
|
|
165
|
+
return 0;
|
|
166
|
+
};
|
package/dist/main.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { createDockerRunner } from "./docker.js";
|
|
2
2
|
import { runGetSyncToken } from "./get-sync-token.js";
|
|
3
3
|
import { runInit } from "./init.js";
|
|
4
|
+
import { runDown, runLogs, runRestart } from "./lifecycle.js";
|
|
4
5
|
import { buildProgram } from "./program.js";
|
|
5
6
|
import { createPrompts } from "./prompts.js";
|
|
6
7
|
import { runUpgrade } from "./upgrade.js";
|
|
@@ -17,6 +18,19 @@ export const run = async (version) => {
|
|
|
17
18
|
docker: createDockerRunner(),
|
|
18
19
|
fetchFn: fetch,
|
|
19
20
|
}),
|
|
21
|
+
runRestart: (flags) => runRestart(flags, {
|
|
22
|
+
prompts: createPrompts(),
|
|
23
|
+
docker: createDockerRunner(),
|
|
24
|
+
fetchFn: fetch,
|
|
25
|
+
}),
|
|
26
|
+
runLogs: (flags) => runLogs(flags, {
|
|
27
|
+
prompts: createPrompts(),
|
|
28
|
+
docker: createDockerRunner(),
|
|
29
|
+
}),
|
|
30
|
+
runDown: (flags) => runDown(flags, {
|
|
31
|
+
prompts: createPrompts(),
|
|
32
|
+
docker: createDockerRunner(),
|
|
33
|
+
}),
|
|
20
34
|
runGetSyncToken: (flags) => runGetSyncToken(flags, {
|
|
21
35
|
prompts: createPrompts(),
|
|
22
36
|
docker: createDockerRunner(),
|
package/dist/messages.js
CHANGED
|
@@ -110,9 +110,10 @@ ${smokeTest(`${baseUrl}/healthz`)}
|
|
|
110
110
|
|
|
111
111
|
${sectionRule("Settings")}
|
|
112
112
|
|
|
113
|
-
Optional settings (timezone, memory folder, port, logging)
|
|
114
|
-
|
|
115
|
-
"npx vault-cortex
|
|
113
|
+
Optional settings (timezone, memory folder, port, logging) live in
|
|
114
|
+
${targetDir}/.env — edit a value (uncommenting it first if needed), then
|
|
115
|
+
apply with "npx vault-cortex restart" (plain docker restart does not
|
|
116
|
+
re-read .env).
|
|
116
117
|
|
|
117
118
|
${updateGuidance(targetDir)}
|
|
118
119
|
|
|
@@ -172,9 +173,9 @@ ${smokeTest(`${publicUrl}/healthz`)}
|
|
|
172
173
|
${sectionRule("Settings")}
|
|
173
174
|
|
|
174
175
|
Optional settings (timezone, memory folder, port, logging, sync
|
|
175
|
-
behavior)
|
|
176
|
-
|
|
177
|
-
not re-read .env).
|
|
176
|
+
behavior) live in ${targetDir}/.env — edit a value (uncommenting it
|
|
177
|
+
first if needed), then apply with "npx vault-cortex restart" (plain
|
|
178
|
+
docker restart does not re-read .env).
|
|
178
179
|
|
|
179
180
|
${updateGuidance(targetDir)}
|
|
180
181
|
|
package/dist/program.js
CHANGED
|
@@ -22,6 +22,29 @@ export const buildProgram = (options) => {
|
|
|
22
22
|
.action(async (flags) => {
|
|
23
23
|
process.exitCode = await options.runUpgrade(flags);
|
|
24
24
|
});
|
|
25
|
+
program
|
|
26
|
+
.command("restart")
|
|
27
|
+
.description("Re-create the container from .env and verify health (applies .env edits; no image pull)")
|
|
28
|
+
.option("--dir <path>", "directory containing .env (default: ./vault-cortex)")
|
|
29
|
+
.action(async (flags) => {
|
|
30
|
+
process.exitCode = await options.runRestart(flags);
|
|
31
|
+
});
|
|
32
|
+
program
|
|
33
|
+
.command("logs")
|
|
34
|
+
.description("Show container logs")
|
|
35
|
+
.option("--dir <path>", "directory containing .env (default: ./vault-cortex)")
|
|
36
|
+
.option("--follow", "stream new log output until interrupted (ctrl-C)")
|
|
37
|
+
.option("--since <time>", 'only logs newer than this (e.g. "10m", "2h", or a timestamp)')
|
|
38
|
+
.action(async (flags) => {
|
|
39
|
+
process.exitCode = await options.runLogs(flags);
|
|
40
|
+
});
|
|
41
|
+
program
|
|
42
|
+
.command("down")
|
|
43
|
+
.description("Stop and remove the container — vault data and settings are preserved")
|
|
44
|
+
.option("--dir <path>", "directory containing .env (default: ./vault-cortex)")
|
|
45
|
+
.action(async (flags) => {
|
|
46
|
+
process.exitCode = await options.runDown(flags);
|
|
47
|
+
});
|
|
25
48
|
program
|
|
26
49
|
.command("get-sync-token")
|
|
27
50
|
.description("Generate an Obsidian Sync auth token via Docker and print it or write it to .env")
|
package/dist/upgrade.js
CHANGED
|
@@ -1,35 +1,19 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
1
|
+
import { LOCAL_IMAGE, REMOTE_IMAGE } from "./docker.js";
|
|
2
|
+
import { ensureDaemonRunning, recreateContainer, resolveDeployment, } from "./lifecycle.js";
|
|
3
|
+
/**
|
|
4
|
+
* Pulls the latest image, re-creates the container, and verifies health.
|
|
5
|
+
* The only lifecycle command that contacts the registry — restart re-creates
|
|
6
|
+
* from the image already on disk.
|
|
7
|
+
*/
|
|
6
8
|
export const runUpgrade = async (flags, deps) => {
|
|
7
9
|
const { prompts, docker, fetchFn } = deps;
|
|
8
10
|
prompts.intro("vault-cortex upgrade");
|
|
9
|
-
const
|
|
10
|
-
|
|
11
|
-
const mode = detectMode(envFilePath);
|
|
12
|
-
if (!mode) {
|
|
13
|
-
prompts.error(`No .env found in ${targetDir} — run \`npx vault-cortex init\` first.`);
|
|
11
|
+
const deployment = resolveDeployment(flags.dir, prompts);
|
|
12
|
+
if (!deployment)
|
|
14
13
|
return 1;
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
const vaultPath = mode === "local" ? readEnvVaultPath(envFilePath) : undefined;
|
|
18
|
-
if (mode === "local" && !vaultPath) {
|
|
19
|
-
prompts.error(`VAULT_PATH is empty or missing in ${targetDir}/.env — cannot start the container.`);
|
|
20
|
-
return 1;
|
|
21
|
-
}
|
|
22
|
-
if (mode === "local" && !hasEnvPublicUrl(envFilePath)) {
|
|
23
|
-
prompts.error(`PUBLIC_URL not found in ${targetDir}/.env — the server requires it.\n` +
|
|
24
|
-
`Add this line to your .env:\n PUBLIC_URL=http://localhost:${port}`);
|
|
25
|
-
return 1;
|
|
26
|
-
}
|
|
27
|
-
const image = mode === "local" ? LOCAL_IMAGE : REMOTE_IMAGE;
|
|
28
|
-
if (!docker.isDaemonRunning()) {
|
|
29
|
-
prompts.error("Container runtime not running — start Docker Desktop, Colima,\n" +
|
|
30
|
-
"OrbStack, or another Docker-compatible runtime.");
|
|
14
|
+
const image = deployment.mode === "local" ? LOCAL_IMAGE : REMOTE_IMAGE;
|
|
15
|
+
if (!ensureDaemonRunning(docker, prompts))
|
|
31
16
|
return 1;
|
|
32
|
-
}
|
|
33
17
|
const spinner = prompts.spinner();
|
|
34
18
|
spinner.start(`Pulling ${image}`);
|
|
35
19
|
const imagePulled = docker.pullImage(image);
|
|
@@ -38,28 +22,9 @@ export const runUpgrade = async (flags, deps) => {
|
|
|
38
22
|
return 1;
|
|
39
23
|
}
|
|
40
24
|
spinner.stop("Image pulled.");
|
|
41
|
-
docker
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
mode,
|
|
45
|
-
envFilePath,
|
|
46
|
-
port,
|
|
47
|
-
vaultPath,
|
|
48
|
-
});
|
|
49
|
-
if (!containerStarted) {
|
|
50
|
-
prompts.error("docker run failed — see output above.");
|
|
51
|
-
return 1;
|
|
52
|
-
}
|
|
53
|
-
spinner.start("Waiting for the server to come up");
|
|
54
|
-
const healthy = await pollHealth({
|
|
55
|
-
url: `http://127.0.0.1:${port}/healthz`,
|
|
56
|
-
timeoutMs: deps.healthTimeoutMs,
|
|
57
|
-
}, fetchFn);
|
|
58
|
-
if (!healthy) {
|
|
59
|
-
spinner.stop(`Server did not respond within 2 minutes — check: docker logs ${CONTAINER_NAME}`);
|
|
60
|
-
return 1;
|
|
61
|
-
}
|
|
62
|
-
spinner.stop("Server is up — health check passed.");
|
|
25
|
+
const exitCode = await recreateContainer({ deployment, healthTimeoutMs: deps.healthTimeoutMs }, { prompts, docker, fetchFn });
|
|
26
|
+
if (exitCode !== 0)
|
|
27
|
+
return exitCode;
|
|
63
28
|
prompts.log("Your vault data, search index, and settings are preserved.");
|
|
64
29
|
prompts.outro("Upgrade complete.");
|
|
65
30
|
return 0;
|