@danypops/pi-packed 0.19.7 → 0.19.10
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/dist/client.d.ts +109 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +1 -0
- package/dist/protocol.d.ts +221 -0
- package/dist/protocol.d.ts.map +1 -0
- package/dist/protocol.js +1 -0
- package/extension/src/{permission.ts → approval/permission.ts} +1 -1
- package/extension/src/index.ts +1 -1
- package/extension/src/packed.ts +2 -2
- package/extension/src/{discover.ts → tabs/discover.ts} +4 -4
- package/extension/src/{resource-config.ts → tabs/resource-config.ts} +4 -4
- package/extension/src/{security-tui.ts → tabs/security-tui.ts} +3 -3
- package/extension/src/tool-output.ts +1 -1
- package/extension/src/tools.ts +2 -2
- package/extension/src/tui.ts +4 -4
- package/package.json +31 -8
- package/service/schema/pi-setup-v1.schema.json +70 -0
- package/service/setup/danypops-ecosystem.pi-setup.json +15 -0
- package/service/src/adoption/advisories.ts +268 -0
- package/service/src/adoption/check.ts +872 -0
- package/service/src/adoption/commit-freshness.ts +167 -0
- package/service/src/adoption/doctor.ts +135 -0
- package/service/src/adoption/install-validation.ts +187 -0
- package/service/src/adoption/pack.ts +291 -0
- package/service/src/adoption/score.ts +466 -0
- package/service/src/adoption/smoke-child.ts +113 -0
- package/service/src/adoption/smoke.ts +282 -0
- package/service/src/cli/cli.ts +926 -0
- package/service/src/daemon/cleanup.ts +76 -0
- package/service/src/daemon/client.ts +412 -0
- package/service/src/daemon/daemon-service.ts +249 -0
- package/service/src/daemon/daemon.ts +110 -0
- package/service/src/daemon/service.ts +664 -0
- package/service/src/daemon/watcher.ts +92 -0
- package/service/src/index/build-index.ts +256 -0
- package/service/src/packages/catalog.ts +61 -0
- package/service/src/packages/db.ts +224 -0
- package/service/src/packages/install.ts +60 -0
- package/service/src/packages/installed.ts +123 -0
- package/service/src/packages/package.ts +141 -0
- package/service/src/packages/resources.ts +203 -0
- package/service/src/pi/pi-version.ts +171 -0
- package/service/src/public/atomic-json.ts +32 -0
- package/service/src/public/client.ts +277 -0
- package/service/src/public/protocol.ts +169 -0
- package/service/src/publish/publish.ts +855 -0
- package/service/src/registry/registry.ts +246 -0
- package/service/src/security/security.ts +128 -0
- package/service/src/self-update/self-update.ts +148 -0
- package/service/src/setup/setup.ts +761 -0
- package/service/src/shared/atomic-json.ts +33 -0
- package/service/src/shared/cache.ts +21 -0
- package/service/src/shared/constants.ts +73 -0
- package/service/src/shared/log.ts +21 -0
- package/service/src/shared/paths.ts +88 -0
- package/service/src/shared/state.ts +15 -0
- package/service/src/shared/version.ts +46 -0
- package/service/test/advisories.test.ts +287 -0
- package/service/test/check.test.ts +368 -0
- package/service/test/cleanup.test.ts +220 -0
- package/service/test/cli.test.ts +1303 -0
- package/service/test/core.test.ts +181 -0
- package/service/test/daemon-kit-migration.test.ts +181 -0
- package/service/test/daemon-service.test.ts +238 -0
- package/service/test/db.test.ts +178 -0
- package/service/test/doctor.test.ts +234 -0
- package/service/test/domain.test.ts +291 -0
- package/service/test/fixtures/install-validation/broken-package/extension/index.ts +3 -0
- package/service/test/fixtures/install-validation/broken-package/package.json +8 -0
- package/service/test/fixtures/install-validation/healthy-package/extension/index.ts +3 -0
- package/service/test/fixtures/install-validation/healthy-package/package.json +8 -0
- package/service/test/fixtures/install-validation/no-manifest-package/package.json +5 -0
- package/service/test/index.test.ts +353 -0
- package/service/test/install-validation.test.ts +114 -0
- package/service/test/install.test.ts +113 -0
- package/service/test/log.test.ts +42 -0
- package/service/test/pack-score.test.ts +513 -0
- package/service/test/pi-version.test.ts +318 -0
- package/service/test/public-boundary.test.ts +54 -0
- package/service/test/public-client.test.ts +127 -0
- package/service/test/public-consumer.ts +8 -0
- package/service/test/publish.test.ts +333 -0
- package/service/test/registry-contract.test.ts +148 -0
- package/service/test/resources.test.ts +255 -0
- package/service/test/security.test.ts +89 -0
- package/service/test/self-update.test.ts +257 -0
- package/service/test/service.test.ts +555 -0
- package/service/test/setup.test.ts +375 -0
- package/service/test/smoke.test.ts +118 -0
- package/service/test/version.test.ts +37 -0
- package/service/tsconfig.consumer.json +13 -0
- package/service/tsconfig.public.json +12 -0
- /package/extension/src/{reload.ts → approval/reload.ts} +0 -0
- /package/extension/src/{discover-model.ts → tabs/discover-model.ts} +0 -0
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* daemon-service.ts — resolves an installed npm package's own persistent-
|
|
3
|
+
* daemon declaration into a real vehicle-server ServiceSpec, and installs it
|
|
4
|
+
* through the exact same systemd/launchd/Registry mechanism that
|
|
5
|
+
* package's own `<bin> service install` command already uses (daemon-
|
|
6
|
+
* kit's installUserService()), so the two are fully interchangeable for
|
|
7
|
+
* the same package.
|
|
8
|
+
*
|
|
9
|
+
* Answers the gap every daemon-backed pi package shared: `pi install
|
|
10
|
+
* npm:@danypops/web-spider-daemon` (or any equivalent) already gets you a
|
|
11
|
+
* daemon that auto-spawns lazily on first use (see each package's own
|
|
12
|
+
* connectWithPolicy/ensure*Client wiring) -- but surviving a reboot always
|
|
13
|
+
* required separately discovering and running that package's own `service
|
|
14
|
+
* install` command by hand. This lets Packed do it as one more explicitly
|
|
15
|
+
* approved mutation, the same way it already gates install/update/remove.
|
|
16
|
+
*
|
|
17
|
+
* Resolution order: an explicit `packed.daemonService` manifest always
|
|
18
|
+
* wins (covers a non-standard bin name or args), then falls back to
|
|
19
|
+
* detectVehicleDaemonService() -- reading conventions every real
|
|
20
|
+
* daemon-backed package already follows (a `bin` entry, a `serve`
|
|
21
|
+
* subcommand, a real dependency on @danypops/vehicle-server or the
|
|
22
|
+
* legacy @danypops/daemon-kit) instead of requiring every package author
|
|
23
|
+
* to redeclare them. Confirmed live: as of this module's introduction,
|
|
24
|
+
* zero installed packages had adopted the manifest -- an unused opt-in
|
|
25
|
+
* step isn't a real convention, so detection reads what's already true
|
|
26
|
+
* on disk instead of asking for one more declaration.
|
|
27
|
+
*/
|
|
28
|
+
import { readFileSync } from "node:fs";
|
|
29
|
+
import { basename, join } from "node:path";
|
|
30
|
+
import { resolveDaemonPaths } from "@danypops/vehicle-server/paths";
|
|
31
|
+
import {
|
|
32
|
+
createNodeServiceInstallDeps,
|
|
33
|
+
installUserService,
|
|
34
|
+
isServiceInstalled,
|
|
35
|
+
type ServiceInstallDeps,
|
|
36
|
+
type ServiceInstallResult,
|
|
37
|
+
type ServiceSpec,
|
|
38
|
+
} from "@danypops/vehicle-server/service";
|
|
39
|
+
import { npmPackageName } from "../packages/installed.ts";
|
|
40
|
+
|
|
41
|
+
export interface DaemonServiceManifest {
|
|
42
|
+
/** Relative to the installed package's own root directory. */
|
|
43
|
+
binPath: string;
|
|
44
|
+
args?: string[];
|
|
45
|
+
/** Defaults to the bare npm package name (scope stripped). Used for the state-directory/unit name, matching each package's own service-install convention (e.g. web-spider.service). */
|
|
46
|
+
name?: string;
|
|
47
|
+
displayName?: string;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export type ResolveDaemonServiceResult =
|
|
51
|
+
| { ok: true; spec: ServiceSpec }
|
|
52
|
+
/** notADaemon: this package (and nothing one level into its own dependencies) is Vehicle-shaped at all -- the overwhelmingly common case for a non-daemon Pi package, distinct from a real failure (systemctl unavailable, spec resolved but install itself failed). A caller composing install + install-service (see cli.ts's `install` command) uses this to stay silent instead of surfacing a false alarm on every ordinary package install. */
|
|
53
|
+
| { ok: false; reason: string; notADaemon?: boolean };
|
|
54
|
+
|
|
55
|
+
function unscopedName(packageName: string): string {
|
|
56
|
+
const slash = packageName.indexOf("/");
|
|
57
|
+
return slash === -1 ? packageName : packageName.slice(slash + 1);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** A resolved daemon entrypoint, already an absolute binPath -- the common shape both an explicit manifest and detection converge to before a ServiceSpec is built. */
|
|
61
|
+
interface ResolvedDaemonEntrypoint {
|
|
62
|
+
binPath: string;
|
|
63
|
+
args?: string[];
|
|
64
|
+
name: string;
|
|
65
|
+
displayName?: string;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
interface InstalledPackageJson {
|
|
69
|
+
name?: string;
|
|
70
|
+
bin?: string | Record<string, string>;
|
|
71
|
+
dependencies?: Record<string, string>;
|
|
72
|
+
packed?: { daemonService?: DaemonServiceManifest };
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function readPackageJson(dir: string): InstalledPackageJson | undefined {
|
|
76
|
+
try {
|
|
77
|
+
return JSON.parse(readFileSync(join(dir, "package.json"), "utf8")) as InstalledPackageJson;
|
|
78
|
+
} catch {
|
|
79
|
+
return undefined;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function firstBinPath(pkg: InstalledPackageJson): string | undefined {
|
|
84
|
+
if (typeof pkg.bin === "string") return pkg.bin;
|
|
85
|
+
if (pkg.bin && typeof pkg.bin === "object") return Object.values(pkg.bin)[0];
|
|
86
|
+
return undefined;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/** @danypops/vehicle-server is the current substrate; @danypops/daemon-kit is the pre-absorption package name -- a real, live signal for any not-yet-migrated ecosystem package (or an older installed version of an already-migrated one), not just a historical artifact. */
|
|
90
|
+
const VEHICLE_DEPENDENCY_NAMES = ["@danypops/vehicle-server", "@danypops/daemon-kit"];
|
|
91
|
+
|
|
92
|
+
function dependsOnVehicle(pkg: InstalledPackageJson): boolean {
|
|
93
|
+
const deps = pkg.dependencies ?? {};
|
|
94
|
+
return VEHICLE_DEPENDENCY_NAMES.some((depName) => depName in deps);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Detects a Vehicle-shaped (or legacy daemon-kit-shaped) daemon without
|
|
99
|
+
* requiring an explicit packed.daemonService manifest. Checks the
|
|
100
|
+
* installed package itself first (the "I am the daemon" case, e.g.
|
|
101
|
+
* @danypops/papyrus installed directly), then one level into its own
|
|
102
|
+
* `dependencies` (the common case: a Pi extension like @danypops/
|
|
103
|
+
* pi-papyrus has no daemon of its own, but npm already resolved its real
|
|
104
|
+
* daemon dependency during `pi install`). That dependency can land in
|
|
105
|
+
* either of two real, both-observed-live layouts: nested directly under
|
|
106
|
+
* the installing package's own node_modules, or hoisted by npm to the
|
|
107
|
+
* single flat node_modules root every Packed-managed install shares --
|
|
108
|
+
* confirmed live for @danypops/pi-papyrus + @danypops/papyrus, which npm
|
|
109
|
+
* hoists to siblings rather than nesting. `hoistedNodeModulesDir` (when
|
|
110
|
+
* given) is checked as a fallback whenever the nested path misses.
|
|
111
|
+
* `args` defaults to `["serve"]` -- confirmed as a real, universal
|
|
112
|
+
* convention across every daemon-backed package checked (papyrus,
|
|
113
|
+
* web-spider, packed, enigma all expose a `serve` subcommand on the same
|
|
114
|
+
* bin their `service install` command already points at).
|
|
115
|
+
*/
|
|
116
|
+
export function detectVehicleDaemonService(
|
|
117
|
+
packageDir: string,
|
|
118
|
+
fallbackName: string,
|
|
119
|
+
hoistedNodeModulesDir?: string,
|
|
120
|
+
): ResolvedDaemonEntrypoint | undefined {
|
|
121
|
+
const own = readPackageJson(packageDir);
|
|
122
|
+
if (!own) return undefined;
|
|
123
|
+
|
|
124
|
+
const ownBin = firstBinPath(own);
|
|
125
|
+
if (ownBin && dependsOnVehicle(own)) {
|
|
126
|
+
return { binPath: join(packageDir, ownBin), args: ["serve"], name: unscopedName(own.name ?? fallbackName) };
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
for (const depName of Object.keys(own.dependencies ?? {})) {
|
|
130
|
+
const candidateDirs = [
|
|
131
|
+
join(packageDir, "node_modules", depName),
|
|
132
|
+
...(hoistedNodeModulesDir ? [join(hoistedNodeModulesDir, depName)] : []),
|
|
133
|
+
];
|
|
134
|
+
for (const depDir of candidateDirs) {
|
|
135
|
+
const dep = readPackageJson(depDir);
|
|
136
|
+
if (!dep) continue;
|
|
137
|
+
const depBin = firstBinPath(dep);
|
|
138
|
+
if (depBin && dependsOnVehicle(dep)) {
|
|
139
|
+
return { binPath: join(depDir, depBin), args: ["serve"], name: unscopedName(dep.name ?? depName) };
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
return undefined;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function buildSpec(entry: ResolvedDaemonEntrypoint): ServiceSpec {
|
|
147
|
+
const paths = resolveDaemonPaths({
|
|
148
|
+
stateDirectoryName: entry.name,
|
|
149
|
+
// Only serviceDescriptor is used below; these three exist purely to
|
|
150
|
+
// satisfy resolveDaemonPaths()'s shared shape for a package this
|
|
151
|
+
// module never opens the db/token/handle of.
|
|
152
|
+
databaseFilename: "unused.db",
|
|
153
|
+
tokenFilename: "unused-token",
|
|
154
|
+
handleFilename: "unused-handle.json",
|
|
155
|
+
systemdUnitName: `${entry.name}.service`,
|
|
156
|
+
});
|
|
157
|
+
return {
|
|
158
|
+
name: entry.name,
|
|
159
|
+
displayName: entry.displayName,
|
|
160
|
+
binPath: entry.binPath,
|
|
161
|
+
args: entry.args,
|
|
162
|
+
descriptorPath: paths.serviceDescriptor,
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Resolves the installed package's daemon entrypoint -- an explicit
|
|
168
|
+
* `packed.daemonService` manifest first, then detectVehicleDaemonService()
|
|
169
|
+
* -- and builds the exact ServiceSpec its own `service install` command
|
|
170
|
+
* would build. npm sources only for now; git:/local sources resolve to a
|
|
171
|
+
* different on-disk layout not yet supported here.
|
|
172
|
+
*/
|
|
173
|
+
export function resolveDaemonServiceSpec(piHome: string, source: string): ResolveDaemonServiceResult {
|
|
174
|
+
const packageName = npmPackageName(source);
|
|
175
|
+
if (!packageName) return { ok: false, reason: "daemon-service installation only supports npm: sources today" };
|
|
176
|
+
|
|
177
|
+
const packageDir = join(piHome, "npm", "node_modules", packageName);
|
|
178
|
+
const pkg = readPackageJson(packageDir);
|
|
179
|
+
if (!pkg) return { ok: false, reason: `could not read ${join(packageDir, "package.json")} -- is ${packageName} installed?` };
|
|
180
|
+
|
|
181
|
+
const manifest = pkg.packed?.daemonService;
|
|
182
|
+
if (manifest && typeof manifest.binPath === "string" && manifest.binPath.length > 0) {
|
|
183
|
+
return {
|
|
184
|
+
ok: true,
|
|
185
|
+
spec: buildSpec({
|
|
186
|
+
binPath: join(packageDir, manifest.binPath),
|
|
187
|
+
args: manifest.args,
|
|
188
|
+
name: manifest.name ?? unscopedName(packageName),
|
|
189
|
+
displayName: manifest.displayName,
|
|
190
|
+
}),
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
const detected = detectVehicleDaemonService(packageDir, packageName, join(piHome, "npm", "node_modules"));
|
|
195
|
+
if (detected) return { ok: true, spec: buildSpec(detected) };
|
|
196
|
+
|
|
197
|
+
return {
|
|
198
|
+
ok: false,
|
|
199
|
+
notADaemon: true,
|
|
200
|
+
reason: `${packageName} does not declare a packed.daemonService manifest and no Vehicle-shaped daemon dependency was detected`,
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
export interface DaemonServiceInstaller {
|
|
205
|
+
install(
|
|
206
|
+
piHome: string,
|
|
207
|
+
source: string,
|
|
208
|
+
): { ok: true; result: ServiceInstallResult; spec: ServiceSpec } | { ok: false; reason: string; notADaemon?: boolean };
|
|
209
|
+
restart(
|
|
210
|
+
piHome: string,
|
|
211
|
+
source: string,
|
|
212
|
+
): { ok: true; restarted: boolean; reason?: string; spec: ServiceSpec } | { ok: false; reason: string; notADaemon?: boolean };
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
export class RealDaemonServiceInstaller implements DaemonServiceInstaller {
|
|
216
|
+
install(
|
|
217
|
+
piHome: string,
|
|
218
|
+
source: string,
|
|
219
|
+
): { ok: true; result: ServiceInstallResult; spec: ServiceSpec } | { ok: false; reason: string; notADaemon?: boolean } {
|
|
220
|
+
const resolved = resolveDaemonServiceSpec(piHome, source);
|
|
221
|
+
if (!resolved.ok) return resolved;
|
|
222
|
+
const result = installUserService(resolved.spec, createNodeServiceInstallDeps());
|
|
223
|
+
return { ok: true, result, spec: resolved.spec };
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
restart(
|
|
227
|
+
piHome: string,
|
|
228
|
+
source: string,
|
|
229
|
+
): { ok: true; restarted: boolean; reason?: string; spec: ServiceSpec } | { ok: false; reason: string; notADaemon?: boolean } {
|
|
230
|
+
const resolved = resolveDaemonServiceSpec(piHome, source);
|
|
231
|
+
if (!resolved.ok) return resolved;
|
|
232
|
+
const deps = createNodeServiceInstallDeps();
|
|
233
|
+
if (!isServiceInstalled(resolved.spec, deps)) {
|
|
234
|
+
return { ok: true, restarted: false, reason: `no persistent service is registered for ${resolved.spec.name}`, spec: resolved.spec };
|
|
235
|
+
}
|
|
236
|
+
const result = restartUserService(resolved.spec, deps);
|
|
237
|
+
return { ok: true, restarted: result.restarted, reason: result.reason, spec: resolved.spec };
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/** Linux/systemd only, matching installUserService's platform coverage order -- macOS/Windows report unsupported rather than guessing at launchctl/reg.exe restart equivalents this module doesn't implement. */
|
|
242
|
+
export function restartUserService(spec: ServiceSpec, deps: ServiceInstallDeps): { restarted: boolean; reason?: string } {
|
|
243
|
+
const platform = deps.platform ?? process.platform;
|
|
244
|
+
if (platform !== "linux")
|
|
245
|
+
return { restarted: false, reason: `restart is not supported on platform "${platform}" yet -- restart ${spec.name} manually` };
|
|
246
|
+
const result = deps.runCommand("systemctl", ["--user", "restart", basename(spec.descriptorPath)]);
|
|
247
|
+
if (!result.ok) return { restarted: false, reason: `systemctl --user restart failed: ${result.output}` };
|
|
248
|
+
return { restarted: true };
|
|
249
|
+
}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { dirname } from "node:path";
|
|
2
|
+
import {
|
|
3
|
+
type MaintenanceTask,
|
|
4
|
+
type RunningDaemon,
|
|
5
|
+
runDaemonProcess,
|
|
6
|
+
type StartDaemonOptions,
|
|
7
|
+
startDaemon,
|
|
8
|
+
} from "@danypops/vehicle-server/daemon";
|
|
9
|
+
import { ensureAuthToken } from "@danypops/vehicle-server/paths";
|
|
10
|
+
import { generateIndex, indexPath, indexStatus } from "../index/build-index.ts";
|
|
11
|
+
import { catalogStatus, syncCatalog } from "../packages/catalog.ts";
|
|
12
|
+
import { latestVersion, openDb } from "../packages/db.ts";
|
|
13
|
+
import { ExecInstaller } from "../packages/install.ts";
|
|
14
|
+
import { defaultPiHome, readInstalledPackages } from "../packages/installed.ts";
|
|
15
|
+
import type { Installer, Registry } from "../packages/package.ts";
|
|
16
|
+
import { HttpRegistry } from "../registry/registry.ts";
|
|
17
|
+
import {
|
|
18
|
+
CATALOG_INTERVAL_DEFAULT_MS,
|
|
19
|
+
ENV,
|
|
20
|
+
IDLE_BUDGET_DEFAULT_MS,
|
|
21
|
+
INDEX_INTERVAL_DEFAULT_MS,
|
|
22
|
+
WATCH_INTERVAL_DEFAULT_MS,
|
|
23
|
+
WATCHDOG_TICK_MS,
|
|
24
|
+
} from "../shared/constants.ts";
|
|
25
|
+
import { createLogger } from "../shared/log.ts";
|
|
26
|
+
import { legacyPackedStateDirectory, migrateLegacyPackedState, type PackedPaths, resolvePackedPaths } from "../shared/paths.ts";
|
|
27
|
+
import { envMs } from "../shared/state.ts";
|
|
28
|
+
import { createApp } from "./service.ts";
|
|
29
|
+
import { checkUpdates, saveUpdates } from "./watcher.ts";
|
|
30
|
+
|
|
31
|
+
const logger = createLogger("daemon");
|
|
32
|
+
|
|
33
|
+
export interface StartPackedDaemonOptions {
|
|
34
|
+
paths?: PackedPaths;
|
|
35
|
+
reg?: Registry;
|
|
36
|
+
inst?: Installer;
|
|
37
|
+
piHome?: string;
|
|
38
|
+
maintenanceTasks?: MaintenanceTask[];
|
|
39
|
+
idleBudgetMs?: number;
|
|
40
|
+
migrateLegacy?: boolean;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function daemonOptions(options: StartPackedDaemonOptions): StartDaemonOptions {
|
|
44
|
+
const paths = options.paths ?? resolvePackedPaths();
|
|
45
|
+
if (options.migrateLegacy ?? options.paths === undefined) migrateLegacyPackedState(paths, legacyPackedStateDirectory());
|
|
46
|
+
const token = ensureAuthToken(paths.token, "Packed");
|
|
47
|
+
const reg = options.reg ?? new HttpRegistry();
|
|
48
|
+
const inst = options.inst ?? new ExecInstaller();
|
|
49
|
+
const piHome = options.piHome ?? defaultPiHome();
|
|
50
|
+
const database = openDb(paths.database);
|
|
51
|
+
const configuredMaintenanceTasks = options.maintenanceTasks ?? [
|
|
52
|
+
{
|
|
53
|
+
name: "package-update-check",
|
|
54
|
+
intervalMs: envMs(ENV.WATCH_SECS, WATCH_INTERVAL_DEFAULT_MS),
|
|
55
|
+
run: async () => {
|
|
56
|
+
const updates = checkUpdates((name) => latestVersion(database, name), readInstalledPackages(piHome));
|
|
57
|
+
await saveUpdates(paths.stateDirectory, { checkedAt: new Date().toISOString(), updates });
|
|
58
|
+
},
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
name: "catalog-sync",
|
|
62
|
+
intervalMs: envMs(ENV.CATALOG_SECS, CATALOG_INTERVAL_DEFAULT_MS),
|
|
63
|
+
run: async () => {
|
|
64
|
+
const ttlMs = envMs(ENV.CATALOG_SECS, CATALOG_INTERVAL_DEFAULT_MS);
|
|
65
|
+
const dataDirectory = dirname(paths.database);
|
|
66
|
+
if (catalogStatus(dataDirectory, ttlMs).stale) await syncCatalog(reg, dataDirectory);
|
|
67
|
+
},
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
name: "index-build",
|
|
71
|
+
intervalMs: envMs(ENV.INDEX_SECS, INDEX_INTERVAL_DEFAULT_MS),
|
|
72
|
+
run: async () => {
|
|
73
|
+
const ttlMs = envMs(ENV.INDEX_SECS, INDEX_INTERVAL_DEFAULT_MS);
|
|
74
|
+
const dataDirectory = dirname(paths.database);
|
|
75
|
+
if (indexStatus(indexPath(dataDirectory), ttlMs).stale) await generateIndex(reg, dataDirectory, indexPath(dataDirectory));
|
|
76
|
+
},
|
|
77
|
+
},
|
|
78
|
+
];
|
|
79
|
+
const maintenanceTasks = configuredMaintenanceTasks.filter((task) => task.intervalMs > 0);
|
|
80
|
+
|
|
81
|
+
if (options.maintenanceTasks === undefined) {
|
|
82
|
+
for (const task of maintenanceTasks) {
|
|
83
|
+
void Promise.resolve(task.run()).catch((error) =>
|
|
84
|
+
logger.error(`maintenance task failed: ${task.name}`, { error: error instanceof Error ? error.message : String(error) }),
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return {
|
|
90
|
+
daemonLabel: "Packed",
|
|
91
|
+
handlePath: paths.handle,
|
|
92
|
+
logger,
|
|
93
|
+
maintenanceTasks,
|
|
94
|
+
idleBudgetMs: options.idleBudgetMs ?? envMs(ENV.IDLE_SECS, IDLE_BUDGET_DEFAULT_MS),
|
|
95
|
+
idleTickMs: WATCHDOG_TICK_MS,
|
|
96
|
+
buildApp: () => createApp({ reg, inst, token, stateDir: paths.stateDirectory, dataDir: dirname(paths.database), piHome }),
|
|
97
|
+
onShutdown: () => database.close(),
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export function startPackedDaemon(options: StartPackedDaemonOptions = {}): Promise<RunningDaemon> {
|
|
102
|
+
return startDaemon(daemonOptions(options));
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export function serveMain(): void {
|
|
106
|
+
runDaemonProcess({
|
|
107
|
+
...daemonOptions({}),
|
|
108
|
+
onListen: ({ host, port }) => logger.info("listening", { host, port }),
|
|
109
|
+
});
|
|
110
|
+
}
|