@cybermem/cli 0.9.12 → 0.13.3

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.
Files changed (56) hide show
  1. package/dist/commands/install.js +430 -0
  2. package/dist/commands/reset.js +18 -2
  3. package/dist/commands/uninstall.js +145 -0
  4. package/dist/commands/upgrade.js +91 -52
  5. package/dist/index.js +18 -5
  6. package/dist/templates/ansible/playbooks/deploy-cybermem.yml +201 -24
  7. package/dist/templates/ansible/playbooks/reset-db.yml +44 -0
  8. package/dist/templates/auth-sidecar/Dockerfile +2 -10
  9. package/dist/templates/auth-sidecar/package.json +1 -3
  10. package/dist/templates/auth-sidecar/server.js +149 -110
  11. package/dist/templates/charts/cybermem/.helmignore +13 -0
  12. package/dist/templates/charts/cybermem/templates/dashboard-deployment.yaml +31 -7
  13. package/dist/templates/charts/cybermem/templates/dashboard-service.yaml +4 -4
  14. package/dist/templates/charts/cybermem/templates/openmemory-deployment.yaml +14 -8
  15. package/dist/templates/charts/cybermem/templates/openmemory-service.yaml +3 -3
  16. package/dist/templates/charts/cybermem/templates/secret.yaml +9 -0
  17. package/dist/templates/charts/cybermem/templates/traefik-config.yaml +67 -0
  18. package/dist/templates/charts/cybermem/templates/traefik-deployment.yaml +53 -0
  19. package/dist/templates/charts/cybermem/templates/traefik-service.yaml +17 -0
  20. package/dist/templates/charts/cybermem/values-vps.yaml +8 -4
  21. package/dist/templates/charts/cybermem/values.yaml +17 -9
  22. package/dist/templates/docker-compose.yml +103 -78
  23. package/dist/templates/monitoring/log_exporter/exporter.py +22 -29
  24. package/dist/templates/monitoring/traefik/traefik.yml +1 -4
  25. package/package.json +9 -3
  26. package/templates/ansible/playbooks/deploy-cybermem.yml +201 -24
  27. package/templates/ansible/playbooks/reset-db.yml +44 -0
  28. package/templates/auth-sidecar/Dockerfile +2 -10
  29. package/templates/auth-sidecar/package.json +1 -3
  30. package/templates/auth-sidecar/server.js +149 -110
  31. package/templates/charts/cybermem/.helmignore +13 -0
  32. package/templates/charts/cybermem/templates/dashboard-deployment.yaml +31 -7
  33. package/templates/charts/cybermem/templates/dashboard-service.yaml +4 -4
  34. package/templates/charts/cybermem/templates/openmemory-deployment.yaml +14 -8
  35. package/templates/charts/cybermem/templates/openmemory-service.yaml +3 -3
  36. package/templates/charts/cybermem/templates/secret.yaml +9 -0
  37. package/templates/charts/cybermem/templates/traefik-config.yaml +67 -0
  38. package/templates/charts/cybermem/templates/traefik-deployment.yaml +53 -0
  39. package/templates/charts/cybermem/templates/traefik-service.yaml +17 -0
  40. package/templates/charts/cybermem/values-vps.yaml +8 -4
  41. package/templates/charts/cybermem/values.yaml +17 -9
  42. package/templates/docker-compose.yml +103 -78
  43. package/templates/monitoring/log_exporter/exporter.py +22 -29
  44. package/templates/monitoring/traefik/traefik.yml +1 -4
  45. package/dist/commands/__tests__/backup.test.js +0 -75
  46. package/dist/commands/__tests__/restore.test.js +0 -70
  47. package/dist/commands/deploy.js +0 -239
  48. package/dist/commands/init.js +0 -362
  49. package/dist/commands/login.js +0 -165
  50. package/dist/templates/envs/local.example +0 -27
  51. package/dist/templates/envs/rpi.example +0 -27
  52. package/dist/templates/envs/vps.example +0 -25
  53. package/dist/templates/monitoring/instructions_injector/Dockerfile +0 -15
  54. package/dist/templates/monitoring/instructions_injector/injector.py +0 -137
  55. package/dist/templates/monitoring/instructions_injector/requirements.txt +0 -3
  56. 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 SQLite access_keys table
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 sqlite3.
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 sqlite3 = require("sqlite3").verbose();
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
- // Verify token against SQLite access_keys table
61
- async function verifyToken(token) {
20
+ // Load token from secret file once at startup (SSoT)
21
+ function loadSecret() {
62
22
  try {
63
- const db = new sqlite3.Database(DB_PATH);
64
-
65
- const tokenHash = await hashToken(token);
66
-
67
- return new Promise((resolve, reject) => {
68
- db.get(
69
- "SELECT user_id, name FROM access_keys WHERE key_hash = ? AND is_active = 1",
70
- [tokenHash],
71
- (err, row) => {
72
- db.close();
73
- if (err) {
74
- console.log("DB error:", err.message);
75
- resolve(null);
76
- } else if (row) {
77
- // Update last_used_at
78
- const updateDb = new sqlite3.Database(DB_PATH);
79
- updateDb.run(
80
- "UPDATE access_keys SET last_used_at = datetime('now') WHERE key_hash = ?",
81
- [tokenHash],
82
- );
83
- updateDb.close();
84
- resolve({ userId: row.user_id, name: row.name });
85
- } else {
86
- resolve(null);
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.log("Token verification error:", err.message);
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
- // IP-based local check
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
- // Host-based local check (raspberrypi.local, localhost, *.local)
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. Local bypass - no auth required for localhost
135
- if (isLocalRequest(req)) {
136
- console.log("Auth OK: Local bypass");
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
- // 2. Check Bearer token (sk-xxx format)
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 (token.startsWith("sk-")) {
150
- const result = await verifyToken(token);
151
-
152
- if (result) {
153
- console.log(`Auth OK: Token (${result.name || result.userId})`);
154
- res.writeHead(200, {
155
- "X-User-Id": result.userId,
156
- "X-Auth-Method": "token",
157
- "X-Token-Name": result.name || "",
158
- });
159
- res.end();
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 verifyToken(apiKeyHeader);
168
-
206
+ const result = await verifyRequestToken(apiKeyHeader);
169
207
  if (result) {
170
- console.log(`Auth OK: API-Key Header (${result.name || result.userId})`);
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
- // 4. Unauthorized
182
- console.log("Auth FAILED: No valid token");
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
  });
@@ -0,0 +1,13 @@
1
+ # Patterns to ignore when building packages.
2
+ # This supports shell glob matching, same as .gitignore.
3
+ # ...
4
+ .DS_Store
5
+ *.swp
6
+ *.bak
7
+ *.tmp
8
+ *.orig
9
+ *~
10
+ .git
11
+ .gitignore
12
+ .idea
13
+ .vscode
@@ -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: 3000
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:8080"
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.service.port }}
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.service.port }}
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: 8080
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: DB_PATH
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.service.port }}
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,9 @@
1
+ apiVersion: v1
2
+ kind: Secret
3
+ metadata:
4
+ name: "{{ .Chart.Name }}-api-key"
5
+ labels:
6
+ app.kubernetes.io/name: "{{ .Chart.Name }}"
7
+ type: Opaque
8
+ stringData:
9
+ om_api_key: "{{ .Values.env.OPENMEMORY_API_KEY }}"
@@ -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
- # VPS Overrides
1
+ global:
2
+ traefik:
3
+ port: 8625
2
4
 
3
5
  openmemory:
4
6
  image:
5
- repository: ghcr.io/mikhailkogan17/cybermem-openmemory
6
- pullPolicy: Always
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: Always
16
+ pullPolicy: IfNotPresent
17
+ tag: "v0.12.5-local"
14
18
 
15
19
  env:
16
20
  OPENMEMORY_API_KEY: "prod-secret-key-CHANGE-ME"