@cybermem/cli 0.9.12 → 0.13.4
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/commands/install.js +430 -0
- package/dist/commands/reset.js +18 -2
- package/dist/commands/uninstall.js +145 -0
- package/dist/commands/upgrade.js +91 -52
- package/dist/index.js +18 -5
- package/dist/templates/ansible/playbooks/deploy-cybermem.yml +201 -24
- package/dist/templates/ansible/playbooks/reset-db.yml +44 -0
- package/dist/templates/auth-sidecar/Dockerfile +2 -10
- package/dist/templates/auth-sidecar/package.json +1 -3
- package/dist/templates/auth-sidecar/server.js +149 -110
- package/dist/templates/charts/cybermem/.helmignore +13 -0
- package/dist/templates/charts/cybermem/templates/dashboard-deployment.yaml +31 -7
- package/dist/templates/charts/cybermem/templates/dashboard-service.yaml +4 -4
- package/dist/templates/charts/cybermem/templates/openmemory-deployment.yaml +14 -8
- package/dist/templates/charts/cybermem/templates/openmemory-service.yaml +3 -3
- package/dist/templates/charts/cybermem/templates/secret.yaml +9 -0
- package/dist/templates/charts/cybermem/templates/traefik-config.yaml +67 -0
- package/dist/templates/charts/cybermem/templates/traefik-deployment.yaml +53 -0
- package/dist/templates/charts/cybermem/templates/traefik-service.yaml +17 -0
- package/dist/templates/charts/cybermem/values-vps.yaml +8 -4
- package/dist/templates/charts/cybermem/values.yaml +17 -9
- package/dist/templates/docker-compose.yml +103 -78
- package/dist/templates/monitoring/log_exporter/exporter.py +22 -29
- package/dist/templates/monitoring/traefik/traefik.yml +1 -4
- package/package.json +9 -3
- package/templates/ansible/playbooks/deploy-cybermem.yml +201 -24
- package/templates/ansible/playbooks/reset-db.yml +44 -0
- package/templates/auth-sidecar/Dockerfile +2 -10
- package/templates/auth-sidecar/package.json +1 -3
- package/templates/auth-sidecar/server.js +149 -110
- package/templates/charts/cybermem/.helmignore +13 -0
- package/templates/charts/cybermem/templates/dashboard-deployment.yaml +31 -7
- package/templates/charts/cybermem/templates/dashboard-service.yaml +4 -4
- package/templates/charts/cybermem/templates/openmemory-deployment.yaml +14 -8
- package/templates/charts/cybermem/templates/openmemory-service.yaml +3 -3
- package/templates/charts/cybermem/templates/secret.yaml +9 -0
- package/templates/charts/cybermem/templates/traefik-config.yaml +67 -0
- package/templates/charts/cybermem/templates/traefik-deployment.yaml +53 -0
- package/templates/charts/cybermem/templates/traefik-service.yaml +17 -0
- package/templates/charts/cybermem/values-vps.yaml +8 -4
- package/templates/charts/cybermem/values.yaml +17 -9
- package/templates/docker-compose.yml +103 -78
- package/templates/monitoring/log_exporter/exporter.py +22 -29
- package/templates/monitoring/traefik/traefik.yml +1 -4
- package/dist/commands/__tests__/backup.test.js +0 -75
- package/dist/commands/__tests__/restore.test.js +0 -70
- package/dist/commands/deploy.js +0 -239
- package/dist/commands/init.js +0 -362
- package/dist/commands/login.js +0 -165
- package/dist/templates/envs/local.example +0 -27
- package/dist/templates/envs/rpi.example +0 -27
- package/dist/templates/envs/vps.example +0 -25
- package/dist/templates/monitoring/instructions_injector/Dockerfile +0 -15
- package/dist/templates/monitoring/instructions_injector/injector.py +0 -137
- package/dist/templates/monitoring/instructions_injector/requirements.txt +0 -3
- package/dist/templates/openmemory/Dockerfile +0 -19
|
@@ -2,98 +2,71 @@
|
|
|
2
2
|
* CyberMem Auth Sidecar
|
|
3
3
|
*
|
|
4
4
|
* ForwardAuth service for Traefik that validates:
|
|
5
|
-
* 1. Bearer tokens (sk-xxx) against
|
|
5
|
+
* 1. Bearer tokens (sk-xxx) against Docker Secret file (SSoT)
|
|
6
6
|
* 2. Local requests bypass (localhost, *.local domains)
|
|
7
7
|
*
|
|
8
|
-
* NO EXTERNAL DEPENDENCIES - uses built-in crypto and
|
|
8
|
+
* NO EXTERNAL DEPENDENCIES - uses built-in crypto and fs.
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
11
|
const http = require("http");
|
|
12
12
|
const crypto = require("crypto");
|
|
13
13
|
const path = require("path");
|
|
14
|
-
const
|
|
14
|
+
const fs = require("fs");
|
|
15
|
+
const SECRET_PATH = process.env.API_KEY_FILE || "/run/secrets/om_api_key";
|
|
16
|
+
let cachedToken = null;
|
|
15
17
|
|
|
16
18
|
const PORT = process.env.PORT || 3001;
|
|
17
|
-
const DB_PATH = process.env.OM_DB_PATH || "/data/openmemory.sqlite";
|
|
18
|
-
|
|
19
|
-
// Ensure schema exists
|
|
20
|
-
function initSchema() {
|
|
21
|
-
const db = new sqlite3.Database(DB_PATH);
|
|
22
|
-
db.serialize(() => {
|
|
23
|
-
db.run(
|
|
24
|
-
`
|
|
25
|
-
CREATE TABLE IF NOT EXISTS access_keys (
|
|
26
|
-
key_id TEXT PRIMARY KEY,
|
|
27
|
-
key_hash TEXT UNIQUE NOT NULL,
|
|
28
|
-
user_id TEXT NOT NULL,
|
|
29
|
-
name TEXT,
|
|
30
|
-
is_active INTEGER DEFAULT 1,
|
|
31
|
-
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
|
32
|
-
)
|
|
33
|
-
`,
|
|
34
|
-
(err) => {
|
|
35
|
-
if (err) console.error("SCHEMA ERROR:", err.message);
|
|
36
|
-
else console.log("Schema verified for access_keys");
|
|
37
|
-
db.close();
|
|
38
|
-
},
|
|
39
|
-
);
|
|
40
|
-
});
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
initSchema();
|
|
44
|
-
|
|
45
|
-
// Hash token using same PBKDF2 as CLI (for verification)
|
|
46
|
-
function hashToken(token) {
|
|
47
|
-
return new Promise((resolve, reject) => {
|
|
48
|
-
const salt = crypto
|
|
49
|
-
.createHash("sha256")
|
|
50
|
-
.update("cybermem-salt-v1")
|
|
51
|
-
.digest("hex")
|
|
52
|
-
.slice(0, 16);
|
|
53
|
-
crypto.pbkdf2(token, salt, 100000, 64, "sha512", (err, key) => {
|
|
54
|
-
if (err) reject(err);
|
|
55
|
-
else resolve(key.toString("hex"));
|
|
56
|
-
});
|
|
57
|
-
});
|
|
58
|
-
}
|
|
59
19
|
|
|
60
|
-
//
|
|
61
|
-
|
|
20
|
+
// Load token from secret file once at startup (SSoT)
|
|
21
|
+
function loadSecret() {
|
|
62
22
|
try {
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
}
|
|
88
|
-
},
|
|
23
|
+
// 1. Check environment variable first (Direct)
|
|
24
|
+
if (process.env.OM_API_KEY) {
|
|
25
|
+
cachedToken = process.env.OM_API_KEY.trim();
|
|
26
|
+
console.log("SSoT Token loaded from OM_API_KEY env");
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// 2. Check secret/env file (Mapped)
|
|
31
|
+
if (fs.existsSync(SECRET_PATH)) {
|
|
32
|
+
const content = fs.readFileSync(SECRET_PATH, "utf8").trim();
|
|
33
|
+
|
|
34
|
+
// Check if it's a shell-formatted env file (OM_API_KEY=sk-...)
|
|
35
|
+
const envMatch = content.match(/OM_API_KEY=["']?(sk-[a-zA-Z0-9]+)["']?/);
|
|
36
|
+
if (envMatch) {
|
|
37
|
+
cachedToken = envMatch[1];
|
|
38
|
+
console.log(`SSoT Token extracted from env file: ${SECRET_PATH}`);
|
|
39
|
+
} else {
|
|
40
|
+
// Assume raw token file
|
|
41
|
+
cachedToken = content;
|
|
42
|
+
console.log(`SSoT Token loaded from raw file: ${SECRET_PATH}`);
|
|
43
|
+
}
|
|
44
|
+
} else {
|
|
45
|
+
console.warn(
|
|
46
|
+
`SECRET WARNING: ${SECRET_PATH} not found. Remote auth will fail.`,
|
|
89
47
|
);
|
|
90
|
-
}
|
|
48
|
+
}
|
|
91
49
|
} catch (err) {
|
|
92
|
-
console.
|
|
93
|
-
return null;
|
|
50
|
+
console.error("SECRET LOAD ERROR:", err.message);
|
|
94
51
|
}
|
|
95
52
|
}
|
|
96
53
|
|
|
54
|
+
loadSecret();
|
|
55
|
+
|
|
56
|
+
// Verify token against SSoT (file-based)
|
|
57
|
+
async function verifyToken(token) {
|
|
58
|
+
if (!cachedToken) {
|
|
59
|
+
// Try one more time if not loaded (e.g. race condition/debug)
|
|
60
|
+
loadSecret();
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (cachedToken && token === cachedToken) {
|
|
64
|
+
return { userId: "admin", name: "SSoT-Admin" };
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
|
|
97
70
|
// Check if request is from localhost or local network
|
|
98
71
|
function isLocalRequest(req) {
|
|
99
72
|
const forwarded = req.headers["x-forwarded-for"];
|
|
@@ -102,21 +75,41 @@ function isLocalRequest(req) {
|
|
|
102
75
|
const ip =
|
|
103
76
|
forwarded?.split(",")[0]?.trim() || realIp || req.socket.remoteAddress;
|
|
104
77
|
|
|
105
|
-
//
|
|
78
|
+
// 1. First reject Tailscale/Remote requests (.ts.net, 100.x.x.x)
|
|
79
|
+
// CRITICAL: Tailscale requests (via Funnel) must NEVER be treated as local.
|
|
80
|
+
// If host contains .ts.net, it's external.
|
|
81
|
+
// NOTE: Legacy CYBERMEM_TAILSCALE env-flag logic was removed on purpose.
|
|
82
|
+
// We now auto-detect Tailscale by host/IP (".ts.net" / "100.x.x.x"),
|
|
83
|
+
// so .local bypass works regardless of CYBERMEM_TAILSCALE being set.
|
|
84
|
+
if (
|
|
85
|
+
host.includes(".ts.net") ||
|
|
86
|
+
(typeof ip === "string" && ip.startsWith("100."))
|
|
87
|
+
) {
|
|
88
|
+
return false;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// 2. Allow .local (mDNS) bypass for RPi LAN access
|
|
92
|
+
const hostname = host.split(":")[0];
|
|
93
|
+
if (hostname.endsWith(".local")) {
|
|
94
|
+
return true;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Host-based check REMOVED for security (CVE-2026-001)
|
|
98
|
+
// We only trust loopback IP if not on Tailscale.
|
|
99
|
+
|
|
100
|
+
// Allow localhost bypass ONLY for local Dev environment (Docker Desktop)
|
|
101
|
+
const isDev = process.env.CYBERMEM_INSTANCE === "local";
|
|
102
|
+
if (isDev && (host.startsWith("localhost") || host.startsWith("127.0.0.1"))) {
|
|
103
|
+
return true;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
106
|
const isLocalIp =
|
|
107
107
|
ip === "127.0.0.1" ||
|
|
108
108
|
ip === "::1" ||
|
|
109
109
|
ip === "::ffff:127.0.0.1" ||
|
|
110
110
|
ip === "localhost";
|
|
111
111
|
|
|
112
|
-
|
|
113
|
-
const isLocalHost =
|
|
114
|
-
host.includes("localhost") ||
|
|
115
|
-
host.includes("127.0.0.1") ||
|
|
116
|
-
host.includes("raspberrypi.local") ||
|
|
117
|
-
host.match(/\.local(:\d+)?$/);
|
|
118
|
-
|
|
119
|
-
return isLocalIp || isLocalHost;
|
|
112
|
+
return isLocalIp;
|
|
120
113
|
}
|
|
121
114
|
|
|
122
115
|
// ForwardAuth handler
|
|
@@ -131,9 +124,47 @@ const server = http.createServer(async (req, res) => {
|
|
|
131
124
|
const authHeader = req.headers["authorization"];
|
|
132
125
|
const apiKeyHeader = req.headers["x-api-key"];
|
|
133
126
|
|
|
134
|
-
// 1.
|
|
135
|
-
|
|
136
|
-
|
|
127
|
+
// 1. Resolve URI
|
|
128
|
+
const requestUri = req.headers["x-forwarded-uri"] || req.url || "/";
|
|
129
|
+
console.log(`[Auth-Sidecar] Processing ${req.method} ${requestUri}`);
|
|
130
|
+
|
|
131
|
+
// 2. Public Paths Bypass
|
|
132
|
+
// We allow Dashboard roots, login paths and essential assets to bypass SSoT token check
|
|
133
|
+
// The Dashboard itself handles its own session auth.
|
|
134
|
+
const prefixPublicPaths = [
|
|
135
|
+
"/auth",
|
|
136
|
+
"/api/auth",
|
|
137
|
+
"/api/health",
|
|
138
|
+
// "/api/metrics", // LEAK FIXED
|
|
139
|
+
// "/api/stats", // LEAK FIXED
|
|
140
|
+
"/api/settings",
|
|
141
|
+
"/_next",
|
|
142
|
+
"/favicon",
|
|
143
|
+
"/static",
|
|
144
|
+
"/public",
|
|
145
|
+
"/health",
|
|
146
|
+
"/login",
|
|
147
|
+
// "/metrics", // LEAK FIXED
|
|
148
|
+
"/clients.json",
|
|
149
|
+
];
|
|
150
|
+
|
|
151
|
+
const exactPublicPaths = ["/"];
|
|
152
|
+
|
|
153
|
+
const isPublic =
|
|
154
|
+
exactPublicPaths.includes(requestUri) ||
|
|
155
|
+
prefixPublicPaths.some((p) => requestUri.startsWith(p));
|
|
156
|
+
|
|
157
|
+
if (isPublic) {
|
|
158
|
+
console.log(`[Auth-Sidecar] ✅ Public bypass: ${requestUri}`);
|
|
159
|
+
res.writeHead(200, { "X-Auth-Method": "public" });
|
|
160
|
+
res.end();
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// 3. Local bypass
|
|
165
|
+
const isK3d = process.env.CYBERMEM_INSTANCE === "k3d";
|
|
166
|
+
if (!isK3d && isLocalRequest(req)) {
|
|
167
|
+
console.log(`[Auth-Sidecar] ✅ Local bypass: ${requestUri}`);
|
|
137
168
|
res.writeHead(200, {
|
|
138
169
|
"X-Auth-Method": "local",
|
|
139
170
|
"X-User-Id": "local",
|
|
@@ -142,32 +173,41 @@ const server = http.createServer(async (req, res) => {
|
|
|
142
173
|
return;
|
|
143
174
|
}
|
|
144
175
|
|
|
145
|
-
//
|
|
176
|
+
// 4. Token Auth Verification Helper
|
|
177
|
+
const verifyRequestToken = async (received) => {
|
|
178
|
+
if (!received) return null;
|
|
179
|
+
const result = await verifyToken(received);
|
|
180
|
+
if (!result) {
|
|
181
|
+
console.log(
|
|
182
|
+
`[Auth-Sidecar] DEBUG Mismatch: received [${received.substring(0, 5)}...] (len:${received.length}) vs cached [${cachedToken?.substring(0, 5)}...] (len:${cachedToken?.length})`,
|
|
183
|
+
);
|
|
184
|
+
}
|
|
185
|
+
return result;
|
|
186
|
+
};
|
|
187
|
+
|
|
146
188
|
if (authHeader?.startsWith("Bearer ")) {
|
|
147
189
|
const token = authHeader.substring(7);
|
|
148
|
-
|
|
149
|
-
if (
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
return;
|
|
161
|
-
}
|
|
190
|
+
const result = await verifyRequestToken(token);
|
|
191
|
+
if (result) {
|
|
192
|
+
console.log(
|
|
193
|
+
`[Auth-Sidecar] ✅ Auth OK (Bearer): ${result.name || result.userId}`,
|
|
194
|
+
);
|
|
195
|
+
res.writeHead(200, {
|
|
196
|
+
"X-User-Id": result.userId,
|
|
197
|
+
"X-Auth-Method": "token",
|
|
198
|
+
"X-Token-Name": result.name || "",
|
|
199
|
+
});
|
|
200
|
+
res.end();
|
|
201
|
+
return;
|
|
162
202
|
}
|
|
163
203
|
}
|
|
164
204
|
|
|
165
|
-
// 3. Check X-API-Key header (sk-xxx format)
|
|
166
205
|
if (apiKeyHeader?.startsWith("sk-")) {
|
|
167
|
-
const result = await
|
|
168
|
-
|
|
206
|
+
const result = await verifyRequestToken(apiKeyHeader);
|
|
169
207
|
if (result) {
|
|
170
|
-
console.log(
|
|
208
|
+
console.log(
|
|
209
|
+
`[Auth-Sidecar] ✅ Auth OK (X-API-Key): ${result.name || result.userId}`,
|
|
210
|
+
);
|
|
171
211
|
res.writeHead(200, {
|
|
172
212
|
"X-User-Id": result.userId,
|
|
173
213
|
"X-Auth-Method": "api-key",
|
|
@@ -178,8 +218,8 @@ const server = http.createServer(async (req, res) => {
|
|
|
178
218
|
}
|
|
179
219
|
}
|
|
180
220
|
|
|
181
|
-
//
|
|
182
|
-
console.log(
|
|
221
|
+
// 5. Unauthorized
|
|
222
|
+
console.log(`[Auth-Sidecar] ❌ Auth FAILED: ${requestUri}`);
|
|
183
223
|
res.writeHead(401, { "Content-Type": "application/json" });
|
|
184
224
|
res.end(
|
|
185
225
|
JSON.stringify({
|
|
@@ -192,5 +232,4 @@ const server = http.createServer(async (req, res) => {
|
|
|
192
232
|
|
|
193
233
|
server.listen(PORT, () => {
|
|
194
234
|
console.log(`Auth sidecar (token-auth) listening on port ${PORT}`);
|
|
195
|
-
console.log(`DB path: ${DB_PATH}`);
|
|
196
235
|
});
|
|
@@ -1,29 +1,53 @@
|
|
|
1
1
|
apiVersion: apps/v1
|
|
2
2
|
kind: Deployment
|
|
3
3
|
metadata:
|
|
4
|
-
name: {{ .Chart.Name }}-dashboard
|
|
4
|
+
name: "{{ .Chart.Name }}-{{ .Values.global.dashboard.serviceName }}"
|
|
5
5
|
labels:
|
|
6
|
-
app.kubernetes.io/name: dashboard
|
|
6
|
+
app.kubernetes.io/name: "{{ .Values.global.dashboard.serviceName }}"
|
|
7
7
|
spec:
|
|
8
8
|
replicas: {{ .Values.dashboard.replicaCount }}
|
|
9
9
|
selector:
|
|
10
10
|
matchLabels:
|
|
11
|
-
app.kubernetes.io/name: dashboard
|
|
11
|
+
app.kubernetes.io/name: "{{ .Values.global.dashboard.serviceName }}"
|
|
12
12
|
template:
|
|
13
13
|
metadata:
|
|
14
14
|
labels:
|
|
15
|
-
app.kubernetes.io/name: dashboard
|
|
15
|
+
app.kubernetes.io/name: "{{ .Values.global.dashboard.serviceName }}"
|
|
16
16
|
spec:
|
|
17
17
|
containers:
|
|
18
|
-
- name: dashboard
|
|
18
|
+
- name: "{{ .Values.global.dashboard.serviceName }}"
|
|
19
19
|
image: "{{ .Values.dashboard.image.repository }}:{{ .Values.dashboard.image.tag }}"
|
|
20
20
|
imagePullPolicy: {{ .Values.dashboard.image.pullPolicy }}
|
|
21
21
|
ports:
|
|
22
22
|
- name: http
|
|
23
|
-
containerPort:
|
|
23
|
+
containerPort: {{ .Values.global.dashboard.port }}
|
|
24
24
|
protocol: TCP
|
|
25
25
|
env:
|
|
26
26
|
- name: NEXT_PUBLIC_API_URL
|
|
27
|
-
value: "http://{{ .Chart.Name }}-openmemory:
|
|
27
|
+
value: "http://{{ .Chart.Name }}-{{ .Values.global.openmemory.serviceName }}:{{ .Values.global.openmemory.port }}"
|
|
28
|
+
- name: CYBERMEM_INSTANCE
|
|
29
|
+
value: "vps"
|
|
30
|
+
- name: CYBERMEM_ENV
|
|
31
|
+
value: {{ .Values.env.CYBERMEM_ENV | default "prod" | quote }}
|
|
32
|
+
- name: POM_API_KEY
|
|
33
|
+
value: {{ .Values.env.OPENMEMORY_API_KEY | quote }}
|
|
34
|
+
- name: OM_API_KEY
|
|
35
|
+
value: {{ .Values.env.OPENMEMORY_API_KEY | quote }}
|
|
36
|
+
- name: PORT
|
|
37
|
+
value: {{ .Values.global.dashboard.port | quote }}
|
|
38
|
+
volumeMounts:
|
|
39
|
+
- name: data
|
|
40
|
+
mountPath: /data
|
|
41
|
+
- name: api-key
|
|
42
|
+
mountPath: /run/secrets/om_api_key
|
|
43
|
+
subPath: om_api_key
|
|
44
|
+
readOnly: true
|
|
28
45
|
resources:
|
|
29
46
|
{{- toYaml .Values.dashboard.resources | nindent 12 }}
|
|
47
|
+
volumes:
|
|
48
|
+
- name: data
|
|
49
|
+
persistentVolumeClaim:
|
|
50
|
+
claimName: "{{ .Chart.Name }}-{{ .Values.global.openmemory.serviceName }}-pvc"
|
|
51
|
+
- name: api-key
|
|
52
|
+
secret:
|
|
53
|
+
secretName: "{{ .Chart.Name }}-api-key"
|
|
@@ -1,20 +1,20 @@
|
|
|
1
1
|
apiVersion: v1
|
|
2
2
|
kind: Service
|
|
3
3
|
metadata:
|
|
4
|
-
name: {{ .Chart.Name }}-dashboard
|
|
4
|
+
name: {{ .Chart.Name }}-{{ .Values.global.dashboard.serviceName }}
|
|
5
5
|
spec:
|
|
6
6
|
type: {{ .Values.dashboard.service.type }}
|
|
7
7
|
ports:
|
|
8
|
-
- port: {{ .Values.dashboard.
|
|
8
|
+
- port: {{ .Values.global.dashboard.port }}
|
|
9
9
|
targetPort: http
|
|
10
10
|
protocol: TCP
|
|
11
11
|
name: http
|
|
12
12
|
{{- if eq .Values.dashboard.service.type "NodePort" }}
|
|
13
|
-
- port: {{ .Values.dashboard.
|
|
13
|
+
- port: {{ .Values.global.dashboard.port }}
|
|
14
14
|
targetPort: http
|
|
15
15
|
nodePort: {{ .Values.dashboard.service.nodePort }}
|
|
16
16
|
protocol: TCP
|
|
17
17
|
name: http-node
|
|
18
18
|
{{- end }}
|
|
19
19
|
selector:
|
|
20
|
-
app.kubernetes.io/name: dashboard
|
|
20
|
+
app.kubernetes.io/name: {{ .Values.global.dashboard.serviceName }}
|
|
@@ -1,34 +1,40 @@
|
|
|
1
1
|
apiVersion: apps/v1
|
|
2
2
|
kind: Deployment
|
|
3
3
|
metadata:
|
|
4
|
-
name: {{ .Chart.Name }}-openmemory
|
|
4
|
+
name: {{ .Chart.Name }}-{{ .Values.global.openmemory.serviceName }}
|
|
5
5
|
labels:
|
|
6
|
-
app.kubernetes.io/name: openmemory
|
|
6
|
+
app.kubernetes.io/name: {{ .Values.global.openmemory.serviceName }}
|
|
7
7
|
spec:
|
|
8
8
|
replicas: {{ .Values.openmemory.replicaCount }}
|
|
9
9
|
selector:
|
|
10
10
|
matchLabels:
|
|
11
|
-
app.kubernetes.io/name: openmemory
|
|
11
|
+
app.kubernetes.io/name: {{ .Values.global.openmemory.serviceName }}
|
|
12
12
|
template:
|
|
13
13
|
metadata:
|
|
14
14
|
labels:
|
|
15
|
-
app.kubernetes.io/name: openmemory
|
|
15
|
+
app.kubernetes.io/name: {{ .Values.global.openmemory.serviceName }}
|
|
16
16
|
spec:
|
|
17
17
|
containers:
|
|
18
|
-
- name: openmemory
|
|
18
|
+
- name: {{ .Values.global.openmemory.serviceName }}
|
|
19
19
|
image: "{{ .Values.openmemory.image.repository }}:{{ .Values.openmemory.image.tag }}"
|
|
20
20
|
imagePullPolicy: {{ .Values.openmemory.image.pullPolicy }}
|
|
21
21
|
ports:
|
|
22
22
|
- name: http
|
|
23
|
-
containerPort:
|
|
23
|
+
containerPort: {{ .Values.global.openmemory.port }}
|
|
24
24
|
protocol: TCP
|
|
25
25
|
env:
|
|
26
26
|
- name: OPENMEMORY_API_KEY
|
|
27
27
|
value: {{ .Values.env.OPENMEMORY_API_KEY | quote }}
|
|
28
28
|
- name: OPENAI_API_KEY
|
|
29
29
|
value: {{ .Values.env.OPENAI_API_KEY | quote }}
|
|
30
|
-
- name:
|
|
30
|
+
- name: OM_DB_PATH
|
|
31
31
|
value: "/data/openmemory.sqlite"
|
|
32
|
+
- name: OM_PORT
|
|
33
|
+
value: {{ .Values.global.openmemory.port | quote }}
|
|
34
|
+
- name: CYBERMEM_INSTANCE
|
|
35
|
+
value: "vps"
|
|
36
|
+
- name: CYBERMEM_ENV
|
|
37
|
+
value: "staging"
|
|
32
38
|
volumeMounts:
|
|
33
39
|
- name: data
|
|
34
40
|
mountPath: /data
|
|
@@ -37,4 +43,4 @@ spec:
|
|
|
37
43
|
volumes:
|
|
38
44
|
- name: data
|
|
39
45
|
persistentVolumeClaim:
|
|
40
|
-
claimName: {{ .Chart.Name }}-openmemory-pvc
|
|
46
|
+
claimName: {{ .Chart.Name }}-{{ .Values.global.openmemory.serviceName }}-pvc
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
apiVersion: v1
|
|
2
2
|
kind: Service
|
|
3
3
|
metadata:
|
|
4
|
-
name: {{ .Chart.Name }}-openmemory
|
|
4
|
+
name: {{ .Chart.Name }}-{{ .Values.global.openmemory.serviceName }}
|
|
5
5
|
spec:
|
|
6
6
|
type: {{ .Values.openmemory.service.type }}
|
|
7
7
|
ports:
|
|
8
|
-
- port: {{ .Values.openmemory.
|
|
8
|
+
- port: {{ .Values.global.openmemory.port }}
|
|
9
9
|
targetPort: http
|
|
10
10
|
protocol: TCP
|
|
11
11
|
name: http
|
|
12
12
|
selector:
|
|
13
|
-
app.kubernetes.io/name: openmemory
|
|
13
|
+
app.kubernetes.io/name: {{ .Values.global.openmemory.serviceName }}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
apiVersion: v1
|
|
2
|
+
kind: ConfigMap
|
|
3
|
+
metadata:
|
|
4
|
+
name: {{ .Chart.Name }}-traefik-config
|
|
5
|
+
data:
|
|
6
|
+
traefik.yml: |
|
|
7
|
+
accessLog: {}
|
|
8
|
+
entryPoints:
|
|
9
|
+
web:
|
|
10
|
+
address: ":{{ .Values.global.traefik.port }}"
|
|
11
|
+
providers:
|
|
12
|
+
file:
|
|
13
|
+
filename: /etc/traefik/dynamic.yml
|
|
14
|
+
dynamic.yml: |
|
|
15
|
+
http:
|
|
16
|
+
routers:
|
|
17
|
+
mcp-delete:
|
|
18
|
+
rule: "Method(`DELETE`)"
|
|
19
|
+
service: mcp-service
|
|
20
|
+
entryPoints:
|
|
21
|
+
- web
|
|
22
|
+
priority: 300
|
|
23
|
+
mcp-add:
|
|
24
|
+
rule: "PathPrefix(`/add`)"
|
|
25
|
+
service: mcp-service
|
|
26
|
+
entryPoints:
|
|
27
|
+
- web
|
|
28
|
+
priority: 250
|
|
29
|
+
mcp-query:
|
|
30
|
+
rule: "PathPrefix(`/query`)"
|
|
31
|
+
service: mcp-service
|
|
32
|
+
entryPoints:
|
|
33
|
+
- web
|
|
34
|
+
priority: 250
|
|
35
|
+
mcp-all:
|
|
36
|
+
rule: "PathPrefix(`/all`)"
|
|
37
|
+
service: mcp-service
|
|
38
|
+
entryPoints:
|
|
39
|
+
- web
|
|
40
|
+
priority: 250
|
|
41
|
+
mcp-api:
|
|
42
|
+
rule: "PathPrefix(`/mcp`) || PathPrefix(`/sse`)"
|
|
43
|
+
service: mcp-service
|
|
44
|
+
entryPoints:
|
|
45
|
+
- web
|
|
46
|
+
priority: 200
|
|
47
|
+
mcp-public:
|
|
48
|
+
rule: "PathPrefix(`/health`) || PathPrefix(`/metrics`)"
|
|
49
|
+
service: mcp-service
|
|
50
|
+
entryPoints:
|
|
51
|
+
- web
|
|
52
|
+
priority: 200
|
|
53
|
+
dashboard:
|
|
54
|
+
rule: "PathPrefix(`/`)"
|
|
55
|
+
service: dashboard-service
|
|
56
|
+
entryPoints:
|
|
57
|
+
- web
|
|
58
|
+
priority: 1
|
|
59
|
+
services:
|
|
60
|
+
mcp-service:
|
|
61
|
+
loadBalancer:
|
|
62
|
+
servers:
|
|
63
|
+
- url: "http://{{ .Chart.Name }}-{{ .Values.global.openmemory.serviceName }}:{{ .Values.global.openmemory.port }}"
|
|
64
|
+
dashboard-service:
|
|
65
|
+
loadBalancer:
|
|
66
|
+
servers:
|
|
67
|
+
- url: "http://{{ .Chart.Name }}-{{ .Values.global.dashboard.serviceName }}:{{ .Values.global.dashboard.port }}"
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
apiVersion: apps/v1
|
|
2
|
+
kind: Deployment
|
|
3
|
+
metadata:
|
|
4
|
+
name: "{{ .Chart.Name }}-traefik"
|
|
5
|
+
labels:
|
|
6
|
+
app.kubernetes.io/name: traefik
|
|
7
|
+
spec:
|
|
8
|
+
replicas: 1
|
|
9
|
+
selector:
|
|
10
|
+
matchLabels:
|
|
11
|
+
app.kubernetes.io/name: traefik
|
|
12
|
+
template:
|
|
13
|
+
metadata:
|
|
14
|
+
labels:
|
|
15
|
+
app.kubernetes.io/name: traefik
|
|
16
|
+
annotations:
|
|
17
|
+
# Standard k8s pattern to trigger restart when ConfigMap changes
|
|
18
|
+
checksum/config: {{ include (print $.Template.BasePath "/traefik-config.yaml") . | sha256sum }}
|
|
19
|
+
spec:
|
|
20
|
+
containers:
|
|
21
|
+
- name: traefik
|
|
22
|
+
image: traefik:v3.0
|
|
23
|
+
args:
|
|
24
|
+
- --api.dashboard=true
|
|
25
|
+
- --api.insecure=true
|
|
26
|
+
- --providers.kubernetes-ingress=false
|
|
27
|
+
- --providers.kubernetes-crd=false
|
|
28
|
+
- --entrypoints.web.address=:{{ .Values.global.traefik.port }}
|
|
29
|
+
ports:
|
|
30
|
+
- name: web
|
|
31
|
+
containerPort: {{ .Values.global.traefik.port }}
|
|
32
|
+
- name: admin
|
|
33
|
+
containerPort: 8080
|
|
34
|
+
volumeMounts:
|
|
35
|
+
- name: config
|
|
36
|
+
mountPath: /etc/traefik/traefik.yml
|
|
37
|
+
subPath: traefik.yml
|
|
38
|
+
- name: dynamic
|
|
39
|
+
mountPath: /etc/traefik/dynamic.yml
|
|
40
|
+
subPath: dynamic.yml
|
|
41
|
+
volumes:
|
|
42
|
+
- name: config
|
|
43
|
+
configMap:
|
|
44
|
+
name: {{ .Chart.Name }}-traefik-config
|
|
45
|
+
items:
|
|
46
|
+
- key: traefik.yml
|
|
47
|
+
path: traefik.yml
|
|
48
|
+
- name: dynamic
|
|
49
|
+
configMap:
|
|
50
|
+
name: {{ .Chart.Name }}-traefik-config
|
|
51
|
+
items:
|
|
52
|
+
- key: dynamic.yml
|
|
53
|
+
path: dynamic.yml
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
apiVersion: v1
|
|
2
|
+
kind: Service
|
|
3
|
+
metadata:
|
|
4
|
+
name: {{ .Chart.Name }}-lb
|
|
5
|
+
spec:
|
|
6
|
+
type: LoadBalancer
|
|
7
|
+
ports:
|
|
8
|
+
- port: 80
|
|
9
|
+
targetPort: web
|
|
10
|
+
protocol: TCP
|
|
11
|
+
name: http
|
|
12
|
+
- port: {{ .Values.global.traefik.port }}
|
|
13
|
+
targetPort: web
|
|
14
|
+
protocol: TCP
|
|
15
|
+
name: web
|
|
16
|
+
selector:
|
|
17
|
+
app.kubernetes.io/name: traefik
|
|
@@ -1,16 +1,20 @@
|
|
|
1
|
-
|
|
1
|
+
global:
|
|
2
|
+
traefik:
|
|
3
|
+
port: 8625
|
|
2
4
|
|
|
3
5
|
openmemory:
|
|
4
6
|
image:
|
|
5
|
-
repository: ghcr.io/mikhailkogan17/cybermem-
|
|
6
|
-
pullPolicy:
|
|
7
|
+
repository: ghcr.io/mikhailkogan17/cybermem-mcp
|
|
8
|
+
pullPolicy: IfNotPresent
|
|
9
|
+
tag: "v0.12.5-local"
|
|
7
10
|
service:
|
|
8
11
|
type: ClusterIP
|
|
9
12
|
|
|
10
13
|
dashboard:
|
|
11
14
|
image:
|
|
12
15
|
repository: ghcr.io/mikhailkogan17/cybermem-dashboard
|
|
13
|
-
pullPolicy:
|
|
16
|
+
pullPolicy: IfNotPresent
|
|
17
|
+
tag: "v0.12.5-local"
|
|
14
18
|
|
|
15
19
|
env:
|
|
16
20
|
OPENMEMORY_API_KEY: "prod-secret-key-CHANGE-ME"
|