@vibe80/vibe80 0.1.2 → 0.1.7
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/bin/vibe80.js +5 -6
- package/package.json +11 -2
- package/server/src/index.js +2 -0
- package/server/src/middleware/auth.js +8 -1
- package/server/src/runAs.js +11 -3
- package/server/src/services/workspace.js +102 -1
- package/server/src/storage/sqlite.js +8 -1
package/bin/vibe80.js
CHANGED
|
@@ -7,7 +7,6 @@ const path = require("path");
|
|
|
7
7
|
const os = require("os");
|
|
8
8
|
|
|
9
9
|
const rootDir = path.resolve(__dirname, "..");
|
|
10
|
-
const npmCmd = process.platform === "win32" ? "npm.cmd" : "npm";
|
|
11
10
|
const homeDir = process.env.HOME || os.homedir();
|
|
12
11
|
const monoAuthUrlFile = path.join(
|
|
13
12
|
os.tmpdir(),
|
|
@@ -15,15 +14,14 @@ const monoAuthUrlFile = path.join(
|
|
|
15
14
|
);
|
|
16
15
|
const defaultEnv = {
|
|
17
16
|
DEPLOYMENT_MODE: "mono_user",
|
|
18
|
-
|
|
17
|
+
VIBE80_DATA_DIRECTORY: path.join(homeDir, ".vibe80"),
|
|
19
18
|
STORAGE_BACKEND: "sqlite",
|
|
20
|
-
SQLITE_PATH: path.join(homeDir, ".vibe80", "data.sqlite"),
|
|
21
19
|
};
|
|
22
20
|
const deploymentMode = process.env.DEPLOYMENT_MODE || defaultEnv.DEPLOYMENT_MODE;
|
|
23
21
|
const serverPort = process.env.PORT || "5179";
|
|
24
22
|
|
|
25
|
-
const spawnProcess = (args, label, extraEnv = {}) => {
|
|
26
|
-
const child = spawn(
|
|
23
|
+
const spawnProcess = (cmd, args, label, extraEnv = {}) => {
|
|
24
|
+
const child = spawn(cmd, args, {
|
|
27
25
|
cwd: rootDir,
|
|
28
26
|
env: {
|
|
29
27
|
...defaultEnv,
|
|
@@ -130,7 +128,8 @@ const shutdown = (code = 0) => {
|
|
|
130
128
|
const startServer = () => {
|
|
131
129
|
unlinkMonoAuthUrlFile();
|
|
132
130
|
server = spawnProcess(
|
|
133
|
-
|
|
131
|
+
process.execPath,
|
|
132
|
+
["server/src/index.js"],
|
|
134
133
|
"server",
|
|
135
134
|
{ VIBE80_MONO_AUTH_URL_FILE: monoAuthUrlFile }
|
|
136
135
|
);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vibe80/vibe80",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.7",
|
|
4
4
|
"private": false,
|
|
5
5
|
"workspaces": [
|
|
6
6
|
"server",
|
|
@@ -31,7 +31,16 @@
|
|
|
31
31
|
"prepack": "npm --workspace client run build"
|
|
32
32
|
},
|
|
33
33
|
"dependencies": {
|
|
34
|
-
"concurrently": "^8.2.2"
|
|
34
|
+
"concurrently": "^8.2.2",
|
|
35
|
+
"docker-names": "^1.2.1",
|
|
36
|
+
"express": "^4.19.2",
|
|
37
|
+
"express-rate-limit": "^7.4.0",
|
|
38
|
+
"jsonwebtoken": "^9.0.3",
|
|
39
|
+
"multer": "^1.4.5-lts.1",
|
|
40
|
+
"node-pty": "^1.0.0",
|
|
41
|
+
"redis": "^4.7.1",
|
|
42
|
+
"sqlite3": "^5.1.7",
|
|
43
|
+
"ws": "^8.17.1"
|
|
35
44
|
},
|
|
36
45
|
"license": "Apache-2.0"
|
|
37
46
|
}
|
package/server/src/index.js
CHANGED
|
@@ -45,6 +45,7 @@ import {
|
|
|
45
45
|
} from "./services/auth.js";
|
|
46
46
|
import {
|
|
47
47
|
ensureDefaultMonoWorkspace,
|
|
48
|
+
applyMonoUserProviderOverridesFromEnv,
|
|
48
49
|
isMonoUser,
|
|
49
50
|
getWorkspacePaths,
|
|
50
51
|
getWorkspaceSshPaths,
|
|
@@ -154,6 +155,7 @@ const sudoPath = process.env.VIBE80_SUDO_PATH || "sudo";
|
|
|
154
155
|
|
|
155
156
|
await storage.init();
|
|
156
157
|
await ensureDefaultMonoWorkspace();
|
|
158
|
+
await applyMonoUserProviderOverridesFromEnv();
|
|
157
159
|
|
|
158
160
|
// ---------------------------------------------------------------------------
|
|
159
161
|
// Middleware pipeline
|
|
@@ -1,9 +1,16 @@
|
|
|
1
1
|
import crypto from "crypto";
|
|
2
2
|
import fs from "fs";
|
|
3
|
+
import os from "os";
|
|
3
4
|
import path from "path";
|
|
4
5
|
import jwt from "jsonwebtoken";
|
|
5
6
|
|
|
6
|
-
const
|
|
7
|
+
const homeDir = process.env.HOME || os.homedir();
|
|
8
|
+
const isMonoUser = process.env.DEPLOYMENT_MODE === "mono_user";
|
|
9
|
+
const defaultDataDirectory = isMonoUser
|
|
10
|
+
? path.join(homeDir, ".vibe80")
|
|
11
|
+
: "/var/lib/vibe80";
|
|
12
|
+
const dataDirectory = process.env.VIBE80_DATA_DIRECTORY || defaultDataDirectory;
|
|
13
|
+
const jwtKeyPath = process.env.JWT_KEY_PATH || path.join(dataDirectory, "jwt.key");
|
|
7
14
|
const jwtIssuer = process.env.JWT_ISSUER || "vibe80";
|
|
8
15
|
const jwtAudience = process.env.JWT_AUDIENCE || "workspace";
|
|
9
16
|
const accessTokenTtlSeconds =
|
package/server/src/runAs.js
CHANGED
|
@@ -8,6 +8,8 @@ const SUDO_PATH = process.env.VIBE80_SUDO_PATH || "sudo";
|
|
|
8
8
|
const DEPLOYMENT_MODE = process.env.DEPLOYMENT_MODE;
|
|
9
9
|
const IS_MONO_USER = DEPLOYMENT_MODE === "mono_user";
|
|
10
10
|
const WORKSPACE_ROOT_DIRECTORY = process.env.WORKSPACE_ROOT_DIRECTORY || "/workspaces";
|
|
11
|
+
const MONO_USER_WORKSPACE_DIR =
|
|
12
|
+
process.env.MONO_USER_WORKSPACE_DIR || path.join(os.homedir(), "vibe80_workspace");
|
|
11
13
|
const ALLOWED_ENV_KEYS = new Set([
|
|
12
14
|
"GIT_SSH_COMMAND",
|
|
13
15
|
"GIT_CONFIG_GLOBAL",
|
|
@@ -151,15 +153,21 @@ export const getWorkspaceHome = (workspaceId) => {
|
|
|
151
153
|
|
|
152
154
|
export const getWorkspaceRoot = (workspaceId) =>
|
|
153
155
|
(IS_MONO_USER
|
|
154
|
-
?
|
|
156
|
+
? MONO_USER_WORKSPACE_DIR
|
|
155
157
|
: path.join(WORKSPACE_ROOT_DIRECTORY, workspaceId));
|
|
156
158
|
|
|
157
159
|
const validateCwd = (workspaceId, cwd) => {
|
|
158
160
|
const resolved = path.resolve(cwd);
|
|
159
161
|
const homeDir = getWorkspaceHome(workspaceId);
|
|
162
|
+
const workspaceRootDir = getWorkspaceRoot(workspaceId);
|
|
163
|
+
const inHome =
|
|
164
|
+
resolved === homeDir || resolved.startsWith(homeDir + path.sep);
|
|
165
|
+
const inWorkspaceRoot =
|
|
166
|
+
resolved === workspaceRootDir ||
|
|
167
|
+
resolved.startsWith(workspaceRootDir + path.sep);
|
|
160
168
|
if (
|
|
161
|
-
|
|
162
|
-
!
|
|
169
|
+
!inHome &&
|
|
170
|
+
!(IS_MONO_USER && inWorkspaceRoot)
|
|
163
171
|
) {
|
|
164
172
|
throw new Error("cwd outside workspace");
|
|
165
173
|
}
|
|
@@ -16,6 +16,8 @@ const isMonoUser = deploymentMode === "mono_user";
|
|
|
16
16
|
const workspaceHomeBase = process.env.WORKSPACE_HOME_BASE || "/home";
|
|
17
17
|
const workspaceRootBase = process.env.WORKSPACE_ROOT_DIRECTORY || "/workspaces";
|
|
18
18
|
const workspaceRootName = "vibe80_workspace";
|
|
19
|
+
const monoUserWorkspaceDir =
|
|
20
|
+
process.env.MONO_USER_WORKSPACE_DIR || path.join(os.homedir(), workspaceRootName);
|
|
19
21
|
const workspaceSessionsDirName = "sessions";
|
|
20
22
|
const rootHelperPath = process.env.VIBE80_ROOT_HELPER || "/usr/local/bin/vibe80-root";
|
|
21
23
|
const sudoPath = process.env.VIBE80_SUDO_PATH || "sudo";
|
|
@@ -37,7 +39,7 @@ const runRootCommand = (args, options = {}) => {
|
|
|
37
39
|
export const getWorkspacePaths = (workspaceId) => {
|
|
38
40
|
const home = isMonoUser ? os.homedir() : path.join(workspaceHomeBase, workspaceId);
|
|
39
41
|
const root = isMonoUser
|
|
40
|
-
?
|
|
42
|
+
? monoUserWorkspaceDir
|
|
41
43
|
: path.join(workspaceRootBase, workspaceId);
|
|
42
44
|
const sessionsDir = path.join(root, workspaceSessionsDirName);
|
|
43
45
|
return {
|
|
@@ -782,6 +784,105 @@ export const ensureDefaultMonoWorkspace = async () => {
|
|
|
782
784
|
}
|
|
783
785
|
};
|
|
784
786
|
|
|
787
|
+
const readEnvValue = (name) =>
|
|
788
|
+
typeof process.env[name] === "string" ? process.env[name].trim() : "";
|
|
789
|
+
|
|
790
|
+
const buildMonoUserProviderOverridesFromEnv = () => {
|
|
791
|
+
const codexApiKey = readEnvValue("CODEX_API_KEY");
|
|
792
|
+
const codexAuthJsonB64Raw = process.env.CODEX_AUTH_JSON_B64;
|
|
793
|
+
const hasCodexAuthJsonB64 = typeof codexAuthJsonB64Raw === "string";
|
|
794
|
+
const codexAuthJsonB64 = hasCodexAuthJsonB64 ? codexAuthJsonB64Raw.trim() : "";
|
|
795
|
+
const claudeApiKey = readEnvValue("CLAUDE_API_KEY");
|
|
796
|
+
const claudeSetupTokenRaw = process.env.CLAUDE_SETUP_TOKEN;
|
|
797
|
+
const hasClaudeSetupToken = typeof claudeSetupTokenRaw === "string";
|
|
798
|
+
const claudeSetupToken = hasClaudeSetupToken ? claudeSetupTokenRaw.trim() : "";
|
|
799
|
+
|
|
800
|
+
const overrides = {};
|
|
801
|
+
|
|
802
|
+
if (codexApiKey && hasCodexAuthJsonB64) {
|
|
803
|
+
console.warn(
|
|
804
|
+
"[warn] Both CODEX_API_KEY and CODEX_AUTH_JSON_B64 are set; using CODEX_AUTH_JSON_B64."
|
|
805
|
+
);
|
|
806
|
+
}
|
|
807
|
+
if (hasCodexAuthJsonB64) {
|
|
808
|
+
if (!codexAuthJsonB64) {
|
|
809
|
+
console.warn(
|
|
810
|
+
"[warn] Invalid CODEX_AUTH_JSON_B64 detected; ignoring codex preprovisioning."
|
|
811
|
+
);
|
|
812
|
+
} else {
|
|
813
|
+
try {
|
|
814
|
+
const decoded = decodeBase64(codexAuthJsonB64);
|
|
815
|
+
validateCodexAuthJson(decoded);
|
|
816
|
+
overrides.codex = {
|
|
817
|
+
enabled: true,
|
|
818
|
+
auth: { type: "auth_json_b64", value: codexAuthJsonB64 },
|
|
819
|
+
};
|
|
820
|
+
} catch {
|
|
821
|
+
console.warn(
|
|
822
|
+
"[warn] Invalid CODEX_AUTH_JSON_B64 detected; ignoring codex preprovisioning."
|
|
823
|
+
);
|
|
824
|
+
}
|
|
825
|
+
}
|
|
826
|
+
} else if (codexApiKey) {
|
|
827
|
+
overrides.codex = {
|
|
828
|
+
enabled: true,
|
|
829
|
+
auth: { type: "api_key", value: codexApiKey },
|
|
830
|
+
};
|
|
831
|
+
}
|
|
832
|
+
|
|
833
|
+
if (claudeApiKey && hasClaudeSetupToken) {
|
|
834
|
+
console.warn(
|
|
835
|
+
"[warn] Both CLAUDE_API_KEY and CLAUDE_SETUP_TOKEN are set; using CLAUDE_SETUP_TOKEN."
|
|
836
|
+
);
|
|
837
|
+
}
|
|
838
|
+
if (hasClaudeSetupToken) {
|
|
839
|
+
if (!claudeSetupToken) {
|
|
840
|
+
console.warn(
|
|
841
|
+
"[warn] Invalid CLAUDE_SETUP_TOKEN detected; ignoring claude preprovisioning."
|
|
842
|
+
);
|
|
843
|
+
} else {
|
|
844
|
+
overrides.claude = {
|
|
845
|
+
enabled: true,
|
|
846
|
+
auth: { type: "setup_token", value: claudeSetupToken },
|
|
847
|
+
};
|
|
848
|
+
}
|
|
849
|
+
} else if (claudeApiKey) {
|
|
850
|
+
overrides.claude = {
|
|
851
|
+
enabled: true,
|
|
852
|
+
auth: { type: "api_key", value: claudeApiKey },
|
|
853
|
+
};
|
|
854
|
+
}
|
|
855
|
+
|
|
856
|
+
return overrides;
|
|
857
|
+
};
|
|
858
|
+
|
|
859
|
+
export const applyMonoUserProviderOverridesFromEnv = async () => {
|
|
860
|
+
if (!isMonoUser) {
|
|
861
|
+
return;
|
|
862
|
+
}
|
|
863
|
+
const overrides = buildMonoUserProviderOverridesFromEnv();
|
|
864
|
+
if (Object.keys(overrides).length === 0) {
|
|
865
|
+
return;
|
|
866
|
+
}
|
|
867
|
+
const workspaceId = "default";
|
|
868
|
+
const existing = await storage.getWorkspace(workspaceId);
|
|
869
|
+
if (!existing) {
|
|
870
|
+
return;
|
|
871
|
+
}
|
|
872
|
+
const mergedProviders = mergeProvidersForUpdate(existing.providers || {}, overrides);
|
|
873
|
+
const ids = await getWorkspaceUserIds(workspaceId);
|
|
874
|
+
await writeWorkspaceProviderAuth(workspaceId, mergedProviders);
|
|
875
|
+
await persistWorkspaceRecord({
|
|
876
|
+
workspaceId,
|
|
877
|
+
providers: mergedProviders,
|
|
878
|
+
ids,
|
|
879
|
+
existing,
|
|
880
|
+
});
|
|
881
|
+
await appendAuditLog(workspaceId, "workspace_providers_preprovisioned_from_env", {
|
|
882
|
+
providers: Object.keys(overrides),
|
|
883
|
+
});
|
|
884
|
+
};
|
|
885
|
+
|
|
785
886
|
export const createWorkspace = async (providers) => {
|
|
786
887
|
const validationError = validateProvidersConfig(providers);
|
|
787
888
|
if (validationError) {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import fs from "fs";
|
|
2
|
+
import os from "os";
|
|
2
3
|
import path from "path";
|
|
3
4
|
import sqlite3 from "sqlite3";
|
|
4
5
|
|
|
@@ -70,7 +71,13 @@ const all = (db, sql, params = []) =>
|
|
|
70
71
|
});
|
|
71
72
|
|
|
72
73
|
export const createSqliteStorage = () => {
|
|
73
|
-
const
|
|
74
|
+
const homeDir = process.env.HOME || os.homedir();
|
|
75
|
+
const isMonoUser = process.env.DEPLOYMENT_MODE === "mono_user";
|
|
76
|
+
const defaultDataDirectory = isMonoUser
|
|
77
|
+
? path.join(homeDir, ".vibe80")
|
|
78
|
+
: "/var/lib/vibe80";
|
|
79
|
+
const dataDirectory = process.env.VIBE80_DATA_DIRECTORY || defaultDataDirectory;
|
|
80
|
+
const dbPath = process.env.SQLITE_PATH || path.join(dataDirectory, "base.sqlite");
|
|
74
81
|
const resolvedPath = path.resolve(dbPath);
|
|
75
82
|
const dir = path.dirname(resolvedPath);
|
|
76
83
|
fs.mkdirSync(dir, { recursive: true, mode: 0o750 });
|