@or3/cloud 0.1.106 → 0.1.112
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/assets/Caddyfile +27 -2
- package/assets/compose.yaml +2 -2
- package/assets/dashboard-lifecycle.mjs +182 -5
- package/assets/dashboard-operator.mjs +17 -0
- package/dist/cli.mjs +1997 -403
- package/package.json +4 -4
package/assets/Caddyfile
CHANGED
|
@@ -3,15 +3,40 @@
|
|
|
3
3
|
# site handler runs, so Strict-Transport-Security is only ever emitted on
|
|
4
4
|
# HTTPS responses (it is meaningless on plain HTTP).
|
|
5
5
|
header {
|
|
6
|
+
# Apply after upstream headers to avoid duplicate security-policy values.
|
|
7
|
+
defer
|
|
6
8
|
Strict-Transport-Security "max-age=31536000"
|
|
7
9
|
X-Content-Type-Options "nosniff"
|
|
8
|
-
X-Frame-Options "DENY"
|
|
9
10
|
Referrer-Policy "strict-origin-when-cross-origin"
|
|
10
11
|
Permissions-Policy "camera=(), microphone=(), geolocation=()"
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
# Only the two host-owned bridge documents can be embedded. Ordinary app
|
|
15
|
+
# pages still deny every ancestor, including same-origin frames.
|
|
16
|
+
@hostDocument {
|
|
17
|
+
not path /or3/portable-frame /streamsaver/mitm.html
|
|
18
|
+
}
|
|
19
|
+
header @hostDocument {
|
|
20
|
+
defer
|
|
21
|
+
X-Frame-Options "DENY"
|
|
11
22
|
# Nuxt needs same-origin scripts plus inline hydration styles/scripts. The
|
|
12
23
|
# browser exchanges OpenRouter PKCE authorization codes directly with the
|
|
13
24
|
# OpenRouter API, so that origin must remain available to connect-src.
|
|
14
|
-
Content-Security-Policy "default-src 'self'; base-uri 'self'; object-src 'none'; frame-ancestors 'none'; form-action 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob:; font-src 'self' data:; connect-src 'self' https://openrouter.ai; media-src 'self' blob:; worker-src 'self' blob:; frame-src '
|
|
25
|
+
Content-Security-Policy "default-src 'self'; base-uri 'self'; object-src 'none'; frame-ancestors 'none'; form-action 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob:; font-src 'self' data:; connect-src 'self' https://openrouter.ai; media-src 'self' blob:; worker-src 'self' blob:; frame-src 'self'; upgrade-insecure-requests"
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
# Preserve the portable route's hash-authorized, opaque-origin sandbox CSP.
|
|
29
|
+
# Applying the app policy here would block its worker or weaken containment.
|
|
30
|
+
@portableFrame path /or3/portable-frame
|
|
31
|
+
header @portableFrame >X-Frame-Options "SAMEORIGIN"
|
|
32
|
+
|
|
33
|
+
# The fallback streaming exporter uses this bundled same-origin bridge to
|
|
34
|
+
# register its download service worker; it cannot use an opaque origin.
|
|
35
|
+
@downloadBridge path /streamsaver/mitm.html
|
|
36
|
+
header @downloadBridge {
|
|
37
|
+
defer
|
|
38
|
+
X-Frame-Options "SAMEORIGIN"
|
|
39
|
+
Content-Security-Policy "default-src 'none'; script-src 'unsafe-inline'; connect-src 'self'; worker-src 'self'; frame-ancestors 'self'; base-uri 'none'; form-action 'none'"
|
|
15
40
|
}
|
|
16
41
|
|
|
17
42
|
encode zstd gzip
|
package/assets/compose.yaml
CHANGED
|
@@ -71,9 +71,9 @@ services:
|
|
|
71
71
|
- CMD-SHELL
|
|
72
72
|
- >-
|
|
73
73
|
if [ -x /nodejs/bin/node ]; then
|
|
74
|
-
exec /nodejs/bin/node -e "const fs=require('node:fs');try{fs.accessSync('/data',fs.constants.R_OK|fs.constants.W_OK);
|
|
74
|
+
exec /nodejs/bin/node -e "const fs=require('node:fs');try{const a=process.env.AUTH_PROVIDER||process.env.OR3_AUTH_PROVIDER||'basic-auth',s=process.env.NUXT_PUBLIC_STORAGE_PROVIDER||'fs',q=process.env.OR3_SYNC_PROVIDER||'sqlite';if(q!=='sqlite'||!(a==='basic-auth'&&s==='fs'||a==='clerk'&&s==='s3')||process.env.AUTH_PROVIDER&&process.env.OR3_AUTH_PROVIDER&&process.env.AUTH_PROVIDER!==process.env.OR3_AUTH_PROVIDER)throw Error('profile');fs.accessSync('/data',fs.constants.R_OK|fs.constants.W_OK);const paths=a==='basic-auth'?[process.env.OR3_BASIC_AUTH_DB_PATH,process.env.OR3_SQLITE_DB_PATH]:[process.env.OR3_SQLITE_DB_PATH];if(paths.some(p=>!p))throw Error('database path');for(const p of paths){const fd=fs.openSync(p,'r+');fs.closeSync(fd)}}catch{process.exit(1)}fetch('http://127.0.0.1:3000/api/health?deep=true').then(async r=>{const b=await r.json().catch(()=>({}));if(!r.ok||b.status!=='ok'||b.providers?.auth?.provider!==(process.env.AUTH_PROVIDER||process.env.OR3_AUTH_PROVIDER||'basic-auth')||b.providers?.sync?.provider!=='sqlite'||b.providers?.storage?.provider!==(process.env.NUXT_PUBLIC_STORAGE_PROVIDER||'fs'))process.exit(1)}).catch(()=>process.exit(1))";
|
|
75
75
|
elif [ -x /usr/local/bin/node ]; then
|
|
76
|
-
exec /usr/local/bin/node -e "const fs=require('node:fs');try{fs.accessSync('/data',fs.constants.R_OK|fs.constants.W_OK);
|
|
76
|
+
exec /usr/local/bin/node -e "const fs=require('node:fs');try{const a=process.env.AUTH_PROVIDER||process.env.OR3_AUTH_PROVIDER||'basic-auth',s=process.env.NUXT_PUBLIC_STORAGE_PROVIDER||'fs',q=process.env.OR3_SYNC_PROVIDER||'sqlite';if(q!=='sqlite'||!(a==='basic-auth'&&s==='fs'||a==='clerk'&&s==='s3')||process.env.AUTH_PROVIDER&&process.env.OR3_AUTH_PROVIDER&&process.env.AUTH_PROVIDER!==process.env.OR3_AUTH_PROVIDER)throw Error('profile');fs.accessSync('/data',fs.constants.R_OK|fs.constants.W_OK);const paths=a==='basic-auth'?[process.env.OR3_BASIC_AUTH_DB_PATH,process.env.OR3_SQLITE_DB_PATH]:[process.env.OR3_SQLITE_DB_PATH];if(paths.some(p=>!p))throw Error('database path');for(const p of paths){const fd=fs.openSync(p,'r+');fs.closeSync(fd)}}catch{process.exit(1)}fetch('http://127.0.0.1:3000/api/health?deep=true').then(async r=>{const b=await r.json().catch(()=>({}));if(!r.ok||b.status!=='ok'||b.providers?.auth?.provider!==(process.env.AUTH_PROVIDER||process.env.OR3_AUTH_PROVIDER||'basic-auth')||b.providers?.sync?.provider!=='sqlite'||b.providers?.storage?.provider!==(process.env.NUXT_PUBLIC_STORAGE_PROVIDER||'fs'))process.exit(1)}).catch(()=>process.exit(1))";
|
|
77
77
|
else exit 127; fi
|
|
78
78
|
interval: 10s
|
|
79
79
|
timeout: 5s
|
|
@@ -20354,6 +20354,8 @@ async function writeEnvFileDetailed(updates, options = {}) {
|
|
|
20354
20354
|
|
|
20355
20355
|
// ../../shared/admin/configuration.ts
|
|
20356
20356
|
var siteSettingPaths = {
|
|
20357
|
+
OR3_STORAGE_CAPACITY_WARNING_PERCENT: ["storageCapacityWarningPercent"],
|
|
20358
|
+
OR3_STORAGE_CAPACITY_CRITICAL_PERCENT: ["storageCapacityCriticalPercent"],
|
|
20357
20359
|
OR3_CHAT_TOOL_ITERATIONS: ["chatToolIterations"],
|
|
20358
20360
|
OR3_CHAT_GENERATED_IMAGES: ["chatGeneratedImages"],
|
|
20359
20361
|
OR3_SITE_NAME: ["or3", "site", "name"],
|
|
@@ -20558,9 +20560,23 @@ function quote(value) {
|
|
|
20558
20560
|
return `'${value.replaceAll("'", "'\\''")}'`;
|
|
20559
20561
|
}
|
|
20560
20562
|
function redact(text, secrets = []) {
|
|
20561
|
-
|
|
20562
|
-
for (const secret of secrets.filter(Boolean))
|
|
20563
|
-
|
|
20563
|
+
const mask = new Uint8Array(text.length);
|
|
20564
|
+
for (const secret of secrets.filter(Boolean)) {
|
|
20565
|
+
for (let index = text.indexOf(secret);index !== -1; index = text.indexOf(secret, index + 1))
|
|
20566
|
+
mask.fill(1, index, index + secret.length);
|
|
20567
|
+
}
|
|
20568
|
+
let output2 = "", cursor = 0;
|
|
20569
|
+
for (let index = 0;index < mask.length; ) {
|
|
20570
|
+
if (!mask[index]) {
|
|
20571
|
+
index += 1;
|
|
20572
|
+
continue;
|
|
20573
|
+
}
|
|
20574
|
+
output2 += `${text.slice(cursor, index)}[REDACTED]`;
|
|
20575
|
+
while (index < mask.length && mask[index])
|
|
20576
|
+
index += 1;
|
|
20577
|
+
cursor = index;
|
|
20578
|
+
}
|
|
20579
|
+
output2 += text.slice(cursor);
|
|
20564
20580
|
output2 = output2.replace(/((?:PASSWORD|SECRET|TOKEN|JWT)\s*[=:]\s*)(?!\[REDACTED\])([^\r\n]+)/gi, "$1[REDACTED]");
|
|
20565
20581
|
return output2;
|
|
20566
20582
|
}
|
|
@@ -20817,10 +20833,128 @@ var SECRET_KEYS = [
|
|
|
20817
20833
|
"OR3_BASIC_AUTH_BOOTSTRAP_PASSWORD",
|
|
20818
20834
|
"OR3_AUTH_INVITE_TOKEN_SECRET",
|
|
20819
20835
|
"OR3_STORAGE_FS_TOKEN_SECRET",
|
|
20836
|
+
"OR3_STORAGE_S3_ACCESS_KEY_ID",
|
|
20837
|
+
"OR3_STORAGE_S3_SECRET_ACCESS_KEY",
|
|
20838
|
+
"OR3_STORAGE_S3_SESSION_TOKEN",
|
|
20839
|
+
"NUXT_CLERK_SECRET_KEY",
|
|
20840
|
+
"NUXT_AUTH_INVITE_TOKEN_SECRET",
|
|
20841
|
+
"NUXT_ADMIN_AUTH_PASSWORD",
|
|
20842
|
+
"NUXT_ADMIN_AUTH_JWT_SECRET",
|
|
20843
|
+
"OR3_LIBRARY_LINK_SECRET",
|
|
20844
|
+
"NUXT_ADMIN_LIBRARY_LINK_SECRET",
|
|
20845
|
+
"OR3_PLUGIN_CONNECTION_SECRET",
|
|
20846
|
+
"NUXT_ADMIN_PLUGIN_CONNECTION_SECRET",
|
|
20847
|
+
"OR3_BACKGROUND_ENCRYPTION_KEY",
|
|
20848
|
+
"NUXT_BACKGROUND_JOBS_ENCRYPTION_KEY",
|
|
20849
|
+
"OR3_WEBHOOKS_ENCRYPTION_KEY",
|
|
20850
|
+
"NUXT_WEBHOOKS_ENCRYPTION_KEY",
|
|
20851
|
+
"OR3_CONNECT_ENCRYPTION_KEY",
|
|
20852
|
+
"NUXT_CONNECT_ENCRYPTION_KEY",
|
|
20853
|
+
"OR3_CONNECT_CLOUDFLARE_API_TOKEN",
|
|
20854
|
+
"NUXT_CONNECT_CLOUDFLARE_API_TOKEN",
|
|
20855
|
+
"OPENROUTER_API_KEY",
|
|
20856
|
+
"NUXT_OPENROUTER_API_KEY",
|
|
20857
|
+
"OR3_OPENAI_API_KEY",
|
|
20858
|
+
"NUXT_OPENAI_API_KEY",
|
|
20859
|
+
"OR3_BILLING_STRIPE_SECRET_KEY",
|
|
20860
|
+
"OR3_BILLING_STRIPE_WEBHOOK_SECRET",
|
|
20861
|
+
"CONVEX_SELF_HOSTED_ADMIN_KEY",
|
|
20862
|
+
"CONVEX_DEPLOY_KEY",
|
|
20820
20863
|
"OR3_ADMIN_JWT_SECRET",
|
|
20821
20864
|
"OR3_ADMIN_PASSWORD"
|
|
20822
20865
|
];
|
|
20866
|
+
function managedProviderProfile(env) {
|
|
20867
|
+
const auth = env.AUTH_PROVIDER ?? env.OR3_AUTH_PROVIDER ?? "basic-auth";
|
|
20868
|
+
const sync = env.OR3_SYNC_PROVIDER ?? "sqlite";
|
|
20869
|
+
const storage = env.NUXT_PUBLIC_STORAGE_PROVIDER ?? "fs";
|
|
20870
|
+
if (env.AUTH_PROVIDER && env.OR3_AUTH_PROVIDER && env.AUTH_PROVIDER !== env.OR3_AUTH_PROVIDER || sync !== "sqlite") {
|
|
20871
|
+
throw new Error("backup-profile-mismatch: unsupported or inconsistent managed provider profile.");
|
|
20872
|
+
}
|
|
20873
|
+
if (auth === "basic-auth" && storage === "fs")
|
|
20874
|
+
return "basic-sqlite-fs";
|
|
20875
|
+
if (auth === "clerk" && storage === "s3")
|
|
20876
|
+
return "clerk-sqlite-s3";
|
|
20877
|
+
throw new Error("backup-profile-mismatch: unsupported managed provider profile.");
|
|
20878
|
+
}
|
|
20879
|
+
function assertLegacyManagedProfile(env) {
|
|
20880
|
+
const overrides = {
|
|
20881
|
+
SSR_AUTH_ENABLED: "true",
|
|
20882
|
+
OR3_SYNC_ENABLED: "true",
|
|
20883
|
+
OR3_CLOUD_SYNC_ENABLED: "true",
|
|
20884
|
+
OR3_STORAGE_ENABLED: "true",
|
|
20885
|
+
OR3_CLOUD_STORAGE_ENABLED: "true",
|
|
20886
|
+
OR3_SQLITE_DRIVER: "better-sqlite3",
|
|
20887
|
+
OR3_STORAGE_PROVIDER: "fs",
|
|
20888
|
+
NUXT_AUTH_ENABLED: "true",
|
|
20889
|
+
NUXT_AUTH_PROVIDER: "basic-auth",
|
|
20890
|
+
NUXT_PUBLIC_AUTH_PROVIDER: "basic-auth",
|
|
20891
|
+
NUXT_PUBLIC_SSR_AUTH_ENABLED: "true",
|
|
20892
|
+
NUXT_SYNC_ENABLED: "true",
|
|
20893
|
+
NUXT_PUBLIC_SYNC_ENABLED: "true",
|
|
20894
|
+
NUXT_SYNC_PROVIDER: "sqlite",
|
|
20895
|
+
NUXT_PUBLIC_SYNC_PROVIDER: "sqlite",
|
|
20896
|
+
NUXT_STORAGE_ENABLED: "true",
|
|
20897
|
+
NUXT_PUBLIC_STORAGE_ENABLED: "true",
|
|
20898
|
+
NUXT_STORAGE_PROVIDER: "fs"
|
|
20899
|
+
};
|
|
20900
|
+
const ambiguous = /^(?:RUNTIME_CONFIG$|NUXT_(?:AUTH|SYNC|STORAGE|PUBLIC)$|NITRO_ENV_(?:PREFIX|EXPANSION)$|NITRO_(?:AUTH|SYNC|STORAGE|PUBLIC)(?:_|$))/;
|
|
20901
|
+
if (managedProviderProfile(env) !== "basic-sqlite-fs" || Object.entries(overrides).some(([key, expected]) => env[key] !== undefined && env[key] !== expected) || Object.keys(env).some((key) => ambiguous.test(key))) {
|
|
20902
|
+
throw new Error("Legacy managed state supports only the Basic/SQLite/filesystem profile; provider conversion requires a separate qualified deployment.");
|
|
20903
|
+
}
|
|
20904
|
+
}
|
|
20905
|
+
var ADOPTION_SECRET_KEYS = [
|
|
20906
|
+
"OR3_BASIC_AUTH_JWT_SECRET",
|
|
20907
|
+
"OR3_BASIC_AUTH_REFRESH_SECRET",
|
|
20908
|
+
"OR3_BASIC_AUTH_BOOTSTRAP_PASSWORD",
|
|
20909
|
+
"OR3_AUTH_INVITE_TOKEN_SECRET",
|
|
20910
|
+
"OR3_STORAGE_FS_TOKEN_SECRET",
|
|
20911
|
+
"OR3_ADMIN_PASSWORD",
|
|
20912
|
+
"OR3_ADMIN_JWT_SECRET",
|
|
20913
|
+
"NUXT_AUTH_INVITE_TOKEN_SECRET",
|
|
20914
|
+
"NUXT_ADMIN_AUTH_JWT_SECRET",
|
|
20915
|
+
"OR3_LIBRARY_LINK_SECRET",
|
|
20916
|
+
"NUXT_ADMIN_LIBRARY_LINK_SECRET",
|
|
20917
|
+
"OR3_PLUGIN_CONNECTION_SECRET",
|
|
20918
|
+
"NUXT_ADMIN_PLUGIN_CONNECTION_SECRET",
|
|
20919
|
+
"OR3_BACKGROUND_ENCRYPTION_KEY",
|
|
20920
|
+
"NUXT_BACKGROUND_JOBS_ENCRYPTION_KEY",
|
|
20921
|
+
"OR3_WEBHOOKS_ENCRYPTION_KEY",
|
|
20922
|
+
"NUXT_WEBHOOKS_ENCRYPTION_KEY",
|
|
20923
|
+
"OR3_CONNECT_ENCRYPTION_KEY",
|
|
20924
|
+
"NUXT_CONNECT_ENCRYPTION_KEY",
|
|
20925
|
+
"OR3_CONNECT_CLOUDFLARE_API_TOKEN",
|
|
20926
|
+
"NUXT_CONNECT_CLOUDFLARE_API_TOKEN",
|
|
20927
|
+
"OPENROUTER_API_KEY",
|
|
20928
|
+
"NUXT_OPENROUTER_API_KEY",
|
|
20929
|
+
"OR3_OPENAI_API_KEY",
|
|
20930
|
+
"NUXT_OPENAI_API_KEY"
|
|
20931
|
+
];
|
|
20932
|
+
var ADOPTION_RUNTIME_KEYS = [
|
|
20933
|
+
"OR3_MARKETPLACE_REGISTRY_ORIGIN",
|
|
20934
|
+
"OR3_MARKETPLACE_INSTALL_ENABLED",
|
|
20935
|
+
"OR3_MARKETPLACE_RELEASE_KEYS",
|
|
20936
|
+
"OR3_INSTANCE_ID",
|
|
20937
|
+
"OR3_MARKETPLACE_MAX_ARTIFACT_BYTES",
|
|
20938
|
+
"OR3_MARKETPLACE_RESERVE_BYTES",
|
|
20939
|
+
"OR3_BACKGROUND_STREAMING_ENABLED",
|
|
20940
|
+
"OR3_BACKGROUND_STREAMING_PROVIDER",
|
|
20941
|
+
"OR3_BACKGROUND_MAX_JOBS",
|
|
20942
|
+
"OR3_BACKGROUND_MAX_JOBS_PER_USER",
|
|
20943
|
+
"OR3_BACKGROUND_JOB_TIMEOUT",
|
|
20944
|
+
"NUXT_BACKGROUND_JOBS_ENABLED",
|
|
20945
|
+
"NUXT_PUBLIC_BACKGROUND_STREAMING_ENABLED",
|
|
20946
|
+
"NUXT_BACKGROUND_JOBS_STORAGE_PROVIDER",
|
|
20947
|
+
"NUXT_BACKGROUND_JOBS_MAX_CONCURRENT_JOBS",
|
|
20948
|
+
"NUXT_BACKGROUND_JOBS_MAX_CONCURRENT_JOBS_PER_USER",
|
|
20949
|
+
"NUXT_BACKGROUND_JOBS_JOB_TIMEOUT_MS",
|
|
20950
|
+
"OR3_PLUGIN_ALLOWED_MODELS",
|
|
20951
|
+
"OR3_PLUGIN_MODEL_PRICES",
|
|
20952
|
+
"NUXT_ADMIN_PLUGIN_ALLOWED_MODELS",
|
|
20953
|
+
"NUXT_ADMIN_PLUGIN_MODEL_PRICES"
|
|
20954
|
+
];
|
|
20823
20955
|
var ALLOWED_ENV_KEYS = new Set([
|
|
20956
|
+
...ADOPTION_SECRET_KEYS,
|
|
20957
|
+
...ADOPTION_RUNTIME_KEYS,
|
|
20824
20958
|
"SSR_AUTH_ENABLED",
|
|
20825
20959
|
"NUXT_SECRETS_PROVIDER",
|
|
20826
20960
|
"NUXT_SECRETS_NAMESPACE",
|
|
@@ -20837,6 +20971,7 @@ var ALLOWED_ENV_KEYS = new Set([
|
|
|
20837
20971
|
"OR3_BASIC_AUTH_REFRESH_SECRET",
|
|
20838
20972
|
"OR3_BASIC_AUTH_ACCESS_TTL_SECONDS",
|
|
20839
20973
|
"OR3_BASIC_AUTH_REFRESH_TTL_SECONDS",
|
|
20974
|
+
"OR3_SQLITE_DRIVER",
|
|
20840
20975
|
"OR3_BASIC_AUTH_DB_PATH",
|
|
20841
20976
|
"OR3_BASIC_AUTH_BOOTSTRAP_EMAIL",
|
|
20842
20977
|
"OR3_BASIC_AUTH_BOOTSTRAP_PASSWORD",
|
|
@@ -20853,6 +20988,8 @@ var ALLOWED_ENV_KEYS = new Set([
|
|
|
20853
20988
|
"OR3_CLOUD_STORAGE_ENABLED",
|
|
20854
20989
|
"NUXT_PUBLIC_STORAGE_PROVIDER",
|
|
20855
20990
|
"OR3_STORAGE_FS_ROOT",
|
|
20991
|
+
"OR3_STORAGE_FS_CLEANUP_NAMESPACE_ID",
|
|
20992
|
+
"OR3_STORAGE_RETENTION_WORKSPACES",
|
|
20856
20993
|
"OR3_STORAGE_FS_TOKEN_SECRET",
|
|
20857
20994
|
"OR3_STORAGE_FS_URL_TTL_SECONDS",
|
|
20858
20995
|
"OR3_ADMIN_USERNAME",
|
|
@@ -20893,7 +21030,9 @@ function dashboardUpdatesEnabled(directory) {
|
|
|
20893
21030
|
}
|
|
20894
21031
|
}
|
|
20895
21032
|
function secretValues(env) {
|
|
20896
|
-
|
|
21033
|
+
const known = new Set(SECRET_KEYS);
|
|
21034
|
+
const secretName = /(?:^|_)(?:PASSWORD|SECRET|TOKEN|PRIVATE_KEY|API_KEY|ACCESS_KEY|AUTH_KEY)(?:_|$)/i;
|
|
21035
|
+
return Object.entries(env).filter(([key, value]) => Boolean(value) && (known.has(key) || secretName.test(key))).map(([, value]) => value);
|
|
20897
21036
|
}
|
|
20898
21037
|
|
|
20899
21038
|
// src/deployment/state-store.ts
|
|
@@ -20955,6 +21094,7 @@ import { dirname as dirname6, join as join8 } from "node:path";
|
|
|
20955
21094
|
var execFile = promisify(execFileCallback);
|
|
20956
21095
|
var COMMAND_TIMEOUT_MS = 180000;
|
|
20957
21096
|
var STREAM_COMMAND_TIMEOUT_MS = 15 * 60000;
|
|
21097
|
+
var MAX_STREAM_COMMAND_TIMEOUT_MS = 4 * 60 * 60000;
|
|
20958
21098
|
var PROCESS_ENV_PASSTHROUGH = [
|
|
20959
21099
|
"PATH",
|
|
20960
21100
|
"HOME",
|
|
@@ -21161,6 +21301,41 @@ async function resolvedComposeConfig(directory, mode, deadline = performance.now
|
|
|
21161
21301
|
${result.stderr}`);
|
|
21162
21302
|
return result.stdout;
|
|
21163
21303
|
}
|
|
21304
|
+
async function assertLegacyComposeProfile(directory, mode, env, deadline) {
|
|
21305
|
+
assertLegacyManagedProfile(env);
|
|
21306
|
+
let resolved2;
|
|
21307
|
+
try {
|
|
21308
|
+
resolved2 = await resolvedComposeConfig(directory, mode, deadline);
|
|
21309
|
+
} catch (error62) {
|
|
21310
|
+
throw new Error(redact(error62 instanceof Error ? error62.message : String(error62), secretValues(env)));
|
|
21311
|
+
}
|
|
21312
|
+
let service;
|
|
21313
|
+
try {
|
|
21314
|
+
service = parseComposeConfig(resolved2).services?.or3;
|
|
21315
|
+
} catch {
|
|
21316
|
+
throw new Error("Resolved Compose provider configuration is unreadable.");
|
|
21317
|
+
}
|
|
21318
|
+
if (!service || !service.environment || typeof service.environment !== "object")
|
|
21319
|
+
throw new Error("Resolved Compose provider environment is missing.");
|
|
21320
|
+
const entries = Array.isArray(service.environment) ? service.environment.map((entry) => {
|
|
21321
|
+
if (typeof entry !== "string" || !/^[A-Za-z_][A-Za-z0-9_]*=/.test(entry))
|
|
21322
|
+
throw new Error("Resolved Compose provider environment is ambiguous.");
|
|
21323
|
+
const index = entry.indexOf("=");
|
|
21324
|
+
return [entry.slice(0, index), entry.slice(index + 1)];
|
|
21325
|
+
}) : Object.entries(service.environment);
|
|
21326
|
+
const effective = {};
|
|
21327
|
+
const seen = new Set;
|
|
21328
|
+
for (const [key, value] of entries) {
|
|
21329
|
+
if (typeof key !== "string" || typeof value !== "string" || seen.has(key))
|
|
21330
|
+
throw new Error("Resolved Compose provider environment is ambiguous.");
|
|
21331
|
+
seen.add(key);
|
|
21332
|
+
effective[key] = value;
|
|
21333
|
+
}
|
|
21334
|
+
assertLegacyManagedProfile(effective);
|
|
21335
|
+
if ((effective.OR3_AUTH_PROVIDER || effective.AUTH_PROVIDER) !== "basic-auth" || effective.SSR_AUTH_ENABLED !== "true" || (effective.OR3_CLOUD_SYNC_ENABLED || effective.OR3_SYNC_ENABLED) !== "true" || effective.OR3_SYNC_PROVIDER !== "sqlite" || (effective.OR3_CLOUD_STORAGE_ENABLED || effective.OR3_STORAGE_ENABLED) !== "true" || (effective.OR3_STORAGE_PROVIDER || effective.NUXT_PUBLIC_STORAGE_PROVIDER) !== "fs") {
|
|
21336
|
+
throw new Error("Resolved Compose provider profile is missing required Basic/SQLite/filesystem settings.");
|
|
21337
|
+
}
|
|
21338
|
+
}
|
|
21164
21339
|
async function assertSafeComposeBinding(directory, mode, env, deadline) {
|
|
21165
21340
|
const config2 = await resolvedComposeConfig(directory, mode, deadline);
|
|
21166
21341
|
const parsed = parseComposeConfig(config2);
|
|
@@ -21234,7 +21409,7 @@ async function assertRunningAppImage(directory, mode, expectedImage, deadline =
|
|
|
21234
21409
|
|
|
21235
21410
|
// src/runtime/health.ts
|
|
21236
21411
|
var DEEP_HEALTH_TIMEOUT_MS = 180000;
|
|
21237
|
-
var HEALTH_SCRIPT = "const fs=require('node:fs');try{fs.accessSync('/data',fs.constants.R_OK|fs.constants.W_OK);
|
|
21412
|
+
var HEALTH_SCRIPT = "const fs=require('node:fs');try{const auth=process.env.AUTH_PROVIDER||process.env.OR3_AUTH_PROVIDER||'basic-auth',storage=process.env.NUXT_PUBLIC_STORAGE_PROVIDER||'fs',sync=process.env.OR3_SYNC_PROVIDER||'sqlite';if(sync!=='sqlite'||!(auth==='basic-auth'&&storage==='fs'||auth==='clerk'&&storage==='s3')||process.env.AUTH_PROVIDER&&process.env.OR3_AUTH_PROVIDER&&process.env.AUTH_PROVIDER!==process.env.OR3_AUTH_PROVIDER)throw Error('profile');fs.accessSync('/data',fs.constants.R_OK|fs.constants.W_OK);const paths=auth==='basic-auth'?[process.env.OR3_BASIC_AUTH_DB_PATH,process.env.OR3_SQLITE_DB_PATH]:[process.env.OR3_SQLITE_DB_PATH];if(paths.some(p=>!p))throw Error('database path');for(const path of paths){const fd=fs.openSync(path,'r+');fs.closeSync(fd)}}catch{process.exit(1)}fetch('http://127.0.0.1:3000/api/health?deep=true').then(async response=>{const body=await response.json().catch(()=>({}));if(!response.ok||body.status!=='ok'||body.providers?.auth?.provider!==(process.env.AUTH_PROVIDER||process.env.OR3_AUTH_PROVIDER||'basic-auth')||body.providers?.sync?.provider!=='sqlite'||body.providers?.storage?.provider!==(process.env.NUXT_PUBLIC_STORAGE_PROVIDER||'fs'))process.exit(1)}).catch(()=>process.exit(1))";
|
|
21238
21413
|
var CONTAINER_NODE = "/nodejs/bin/node";
|
|
21239
21414
|
var LEGACY_CONTAINER_NODE = "/usr/local/bin/node";
|
|
21240
21415
|
var CONTAINER_NODE_SHELL = `if [ -x ${CONTAINER_NODE} ]; then exec ${CONTAINER_NODE} -e "$1"; elif [ -x ${LEGACY_CONTAINER_NODE} ]; then exec ${LEGACY_CONTAINER_NODE} -e "$1"; else exit 127; fi`;
|
|
@@ -21290,6 +21465,8 @@ async function restartCommand(directory, lifecycle) {
|
|
|
21290
21465
|
const loaded = await loadManaged(directory);
|
|
21291
21466
|
assertDeploymentDirectoryIdentity(directory, loaded.state);
|
|
21292
21467
|
assertPhaseAllowsCommand("restart", loaded.state);
|
|
21468
|
+
assertLegacyManagedProfile(loaded.env);
|
|
21469
|
+
await assertLegacyComposeProfile(loaded.directory, loaded.state.mode, loaded.env, lifecycle?.deadline);
|
|
21293
21470
|
if (lifecycle) {
|
|
21294
21471
|
await assertSafeComposeBinding(directory, loaded.state.mode, loaded.env, lifecycle.deadline);
|
|
21295
21472
|
await assertRunningAppImage(directory, loaded.state.mode, loaded.state.image, lifecycle.deadline);
|
|
@@ -317,6 +317,22 @@ async function readLastReceipt() {
|
|
|
317
317
|
}
|
|
318
318
|
}
|
|
319
319
|
|
|
320
|
+
/** Read-only allowlist: a recorded snapshot is not current checksum evidence. */
|
|
321
|
+
export function projectRecoveryState(state) {
|
|
322
|
+
const safeId = value => typeof value === 'string' && /^[A-Za-z0-9_-]{1,100}$/.test(value) ? value : null;
|
|
323
|
+
const safeTime = value => typeof value === 'string' && value.length <= 64 && Number.isFinite(Date.parse(value)) ? value : null;
|
|
324
|
+
const snapshotId = safeId(state?.rollback?.backupId);
|
|
325
|
+
const pending = state?.incompleteOperation;
|
|
326
|
+
return {
|
|
327
|
+
observedAt: new Date().toISOString(), source: 'managed_operator',
|
|
328
|
+
snapshot: snapshotId ? { id: snapshotId, capturedAt: safeTime(state.rollback.createdAt), verification: 'operator_reported' } : null,
|
|
329
|
+
coverage: ['canonical', 'auth', 'files', 'configuration', 'billing_trials_support', 'credential_escrow', 'independent_copy'].map(component => ({ component, status: 'unknown', verifiedAt: null })),
|
|
330
|
+
lastRecoveryTestAt: null,
|
|
331
|
+
incompleteOperation: pending && safeId(pending.id) && ['init', 'update', 'restore', 'adopt', 'credentials-reset', 'backup', 'rollback'].includes(pending.operation)
|
|
332
|
+
? { id: pending.id, operation: pending.operation, startedAt: safeTime(pending.startedAt) } : null,
|
|
333
|
+
};
|
|
334
|
+
}
|
|
335
|
+
|
|
320
336
|
async function status(includeReceipt = false) {
|
|
321
337
|
const version = await currentVersion();
|
|
322
338
|
const [job, checked] = await Promise.all([readJob(), readReleaseCheck()]);
|
|
@@ -992,6 +1008,7 @@ async function main() {
|
|
|
992
1008
|
if (await service.handle(service.controller, request, response)) return;
|
|
993
1009
|
} catch { return send(response, 503, { code: 'controller_unavailable' }); }
|
|
994
1010
|
}
|
|
1011
|
+
if (request.method === 'GET' && request.url === '/v2/recovery') return send(response, 200, { protocolVersion: 2, recovery: projectRecoveryState(await managedState()) });
|
|
995
1012
|
if (request.method === 'GET' && route === '/status') return reply(200, await status(isV2));
|
|
996
1013
|
if (request.method === 'POST' && route === '/check') {
|
|
997
1014
|
if (!consumeRateLimit(checkAttempts, Date.now(), 30, 60_000)) {
|