@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
@@ -10,26 +10,46 @@ const fs_1 = __importDefault(require("fs"));
10
10
  const inquirer_1 = __importDefault(require("inquirer"));
11
11
  const os_1 = __importDefault(require("os"));
12
12
  const path_1 = __importDefault(require("path"));
13
+ // Helper to handle and suggest fixes for common errors
14
+ function handleExecError(error, context) {
15
+ const stderr = error.stderr || "";
16
+ let suggestion = "";
17
+ if (stderr.includes("Permission denied") || stderr.includes("publickey")) {
18
+ suggestion =
19
+ "\nšŸ’” Next Step: Ensure your SSH keys are added to the remote host: `ssh-copy-id user@host`";
20
+ }
21
+ else if (stderr.includes("Connection timed out") ||
22
+ stderr.includes("Could not resolve host")) {
23
+ suggestion =
24
+ "\nšŸ’” Next Step: Check your network connection or the remote host's availability.";
25
+ }
26
+ else if (stderr.includes("ansible-playbook: command not found")) {
27
+ suggestion =
28
+ "\nšŸ’” Next Step: Install Ansible: `brew install ansible` (on macOS).";
29
+ }
30
+ else if (stderr.includes("docker-compose: command not found")) {
31
+ suggestion =
32
+ "\nšŸ’” Next Step: Install Docker and ensure docker-compose is in your PATH.";
33
+ }
34
+ console.error(chalk_1.default.red(`\nāŒ ${context} failed:`), error.message);
35
+ if (suggestion)
36
+ console.log(chalk_1.default.yellow(suggestion));
37
+ process.exit(1);
38
+ }
13
39
  async function upgrade(options) {
14
40
  let target = "local";
15
41
  if (options.rpi)
16
42
  target = "rpi";
17
43
  if (options.vps)
18
44
  target = "vps";
19
- console.log(chalk_1.default.blue(`Upgrading CyberMem (${target})...`));
45
+ const isStaging = !!options.staging;
46
+ const envType = isStaging ? "staging" : "prod";
47
+ const projectSuffix = isStaging ? "-staging" : "";
48
+ console.log(chalk_1.default.blue(`Upgrading CyberMem (${target}-${envType})...`));
20
49
  try {
21
50
  if (target === "local") {
22
51
  console.log(chalk_1.default.blue("Pulling latest Docker images..."));
23
- // Re-use logic: find compose file from template if needed, OR use installed ~/.cybermem one?
24
- // "upgrade" implies updating running instance.
25
- // running instance uses ~/.cybermem/docker-compose.yml (if init copied it?)
26
- // Wait, init uses template directly?
27
- // "deploy/init" logic for local:
28
- // "docker-compose -f composeFile ...". composeFile was from templateDir.
29
- // It did NOT copy compose file to ~/.cybermem for local.
30
- // It uses the installed package's template.
31
- // So for upgrade (which might be run from newer CLI version), we use the NEW CLI's template.
32
- // Resolve Template Directory (Same logic as init)
52
+ // Resolve Template Directory
33
53
  let templateDir = path_1.default.resolve(__dirname, "../../templates");
34
54
  if (!fs_1.default.existsSync(templateDir)) {
35
55
  templateDir = path_1.default.resolve(__dirname, "../../../templates");
@@ -47,25 +67,35 @@ async function upgrade(options) {
47
67
  const homeDir = os_1.default.homedir();
48
68
  const configDir = path_1.default.join(homeDir, ".cybermem");
49
69
  const envFile = path_1.default.join(configDir, ".env");
50
- const dataDir = path_1.default.join(configDir, "data");
70
+ const dataDir = path_1.default.join(configDir, isStaging ? "data-staging" : "data");
51
71
  // Pull images
52
- await (0, execa_1.default)("docker-compose", [
53
- "-f",
54
- composeFile,
55
- "--env-file",
56
- envFile,
57
- "--project-name",
58
- "cybermem",
59
- "pull",
60
- ], {
61
- stdio: "inherit",
62
- env: {
63
- ...process.env,
64
- DATA_DIR: dataDir,
65
- CYBERMEM_ENV_PATH: envFile,
66
- OM_API_KEY: "", // Local bypass
67
- },
68
- });
72
+ try {
73
+ await (0, execa_1.default)("docker-compose", [
74
+ "-f",
75
+ composeFile,
76
+ "--env-file",
77
+ envFile,
78
+ "--project-name",
79
+ `cybermem${projectSuffix}`,
80
+ "pull",
81
+ ], {
82
+ stdio: "inherit",
83
+ env: {
84
+ ...process.env,
85
+ DATA_DIR: dataDir,
86
+ CYBERMEM_ENV_PATH: envFile,
87
+ OM_API_KEY: "", // Local bypass
88
+ PROJECT_NAME: `cybermem${projectSuffix}`,
89
+ TRAEFIK_PORT: isStaging ? "8625" : "8626",
90
+ DASHBOARD_PORT: isStaging ? "3001" : "3000",
91
+ CYBERMEM_ENV: envType,
92
+ CYBERMEM_INSTANCE: target,
93
+ },
94
+ });
95
+ }
96
+ catch (e) {
97
+ handleExecError(e, "Local image pull");
98
+ }
69
99
  // Up (recreate)
70
100
  console.log(chalk_1.default.blue("Restarting services..."));
71
101
  await (0, execa_1.default)("docker-compose", [
@@ -74,7 +104,7 @@ async function upgrade(options) {
74
104
  "--env-file",
75
105
  envFile,
76
106
  "--project-name",
77
- "cybermem",
107
+ `cybermem${projectSuffix}`,
78
108
  "up",
79
109
  "-d",
80
110
  "--remove-orphans",
@@ -85,12 +115,17 @@ async function upgrade(options) {
85
115
  DATA_DIR: dataDir,
86
116
  CYBERMEM_ENV_PATH: envFile,
87
117
  OM_API_KEY: "", // Local bypass
118
+ PROJECT_NAME: `cybermem${projectSuffix}`,
119
+ TRAEFIK_PORT: isStaging ? "8625" : "8626",
120
+ DASHBOARD_PORT: isStaging ? "3001" : "3000",
121
+ CYBERMEM_ENV: envType,
122
+ CYBERMEM_INSTANCE: target,
88
123
  },
89
124
  });
90
125
  console.log(chalk_1.default.green("āœ… Upgrade complete!"));
91
126
  }
92
127
  else if (target === "rpi" || target === "vps") {
93
- // Remote upgrade
128
+ // Remote upgrade via Ansible
94
129
  let sshHost = options.host;
95
130
  if (!sshHost) {
96
131
  const answers = await inquirer_1.default.prompt([
@@ -103,9 +138,8 @@ async function upgrade(options) {
103
138
  ]);
104
139
  sshHost = answers.host;
105
140
  }
106
- console.log(chalk_1.default.blue(`Upgrading remote host ${sshHost}...`));
107
- // 1. Upload NEW docker-compose.yml (from this CLI version)
108
- // Resolve Template Directory
141
+ console.log(chalk_1.default.blue(`Upgrading remote host ${sshHost} via Ansible...`));
142
+ // 1. Resolve Template Directory
109
143
  let templateDir = path_1.default.resolve(__dirname, "../../templates");
110
144
  if (!fs_1.default.existsSync(templateDir)) {
111
145
  templateDir = path_1.default.resolve(__dirname, "../../../templates");
@@ -113,25 +147,30 @@ async function upgrade(options) {
113
147
  if (!fs_1.default.existsSync(templateDir)) {
114
148
  templateDir = path_1.default.resolve(process.cwd(), "packages/cli/templates");
115
149
  }
116
- if (!fs_1.default.existsSync(templateDir)) {
117
- templateDir = path_1.default.resolve(__dirname, "../templates");
150
+ const playbookPath = path_1.default.join(templateDir, "ansible/playbooks/deploy-cybermem.yml");
151
+ const ansibleDir = path_1.default.join(templateDir, "ansible");
152
+ const [sshUser, host] = sshHost.split("@");
153
+ // 2. Run Ansible Playbook
154
+ // For upgrade, the playbook's default state (started) will pull latest images
155
+ // if we ensure it performs a pull. Our playbook already pulls images.
156
+ try {
157
+ await (0, execa_1.default)("ansible-playbook", [
158
+ "-i",
159
+ `${host},`,
160
+ "-u",
161
+ sshUser,
162
+ playbookPath,
163
+ "--extra-vars",
164
+ `ansible_ssh_extra_args='-o StrictHostKeyChecking=no' CYBERMEM_ENV=${envType} TRAEFIK_PORT=${isStaging ? "8625" : "8626"} PROJECT_NAME=cybermem${projectSuffix} project_dir=/home/${sshUser}/cybermem${projectSuffix}`,
165
+ ], {
166
+ stdio: "inherit",
167
+ cwd: ansibleDir,
168
+ });
118
169
  }
119
- const composeFile = path_1.default.join(templateDir, "docker-compose.yml");
120
- console.log(chalk_1.default.blue("Uploading newest definitions..."));
121
- await (0, execa_1.default)("scp", [
122
- composeFile,
123
- `${sshHost}:~/.cybermem/docker-compose.yml`,
124
- ]);
125
- // 2. Pull and Up on Remote
126
- const remoteCmd = `
127
- export CYBERMEM_ENV_PATH=~/.cybermem/.env
128
- export DATA_DIR=~/.cybermem/data
129
- export DOCKER_DEFAULT_PLATFORM=linux/arm64
130
- docker-compose -f ~/.cybermem/docker-compose.yml pull
131
- docker-compose -f ~/.cybermem/docker-compose.yml up -d --remove-orphans
132
- `;
133
- await (0, execa_1.default)("ssh", [sshHost, remoteCmd], { stdio: "inherit" });
134
- console.log(chalk_1.default.green("āœ… Remote upgrade complete!"));
170
+ catch (e) {
171
+ handleExecError(e, "Remote upgrade");
172
+ }
173
+ console.log(chalk_1.default.green("āœ… Remote upgrade complete via Ansible!"));
135
174
  }
136
175
  }
137
176
  catch (error) {
package/dist/index.js CHANGED
@@ -4,23 +4,34 @@ Object.defineProperty(exports, "__esModule", { value: true });
4
4
  const commander_1 = require("commander");
5
5
  const backup_1 = require("./commands/backup");
6
6
  const dashboard_1 = require("./commands/dashboard");
7
- const init_1 = require("./commands/init");
7
+ const install_1 = require("./commands/install");
8
8
  const reset_1 = require("./commands/reset");
9
9
  const restore_1 = require("./commands/restore");
10
+ const uninstall_1 = require("./commands/uninstall");
10
11
  const upgrade_1 = require("./commands/upgrade");
11
12
  const program = new commander_1.Command();
12
13
  program
13
14
  .name("mcp")
14
15
  .description("CyberMem - Deploy your AI memory server in one command")
15
16
  .version("1.0.0");
16
- // Command: Init (formerly deploy)
17
+ // Command: Install
17
18
  program
18
- .command("init")
19
- .description("Initialize CyberMem (Scaffold & Start)")
19
+ .command("install")
20
+ .description("Install CyberMem (Scaffold & Start)")
20
21
  .option("--rpi", "Deploy to Raspberry Pi")
21
22
  .option("--vps", "Deploy to VPS/Cloud server")
23
+ .option("--staging", "Deploy to staging environment (different ports/data)")
22
24
  .option("--remote-access", "Enable Tailscale Funnel for HTTPS remote access")
23
- .action(init_1.init);
25
+ .option("--host <host>", "SSH host for remote deploy (e.g. pi@raspberrypi.local)")
26
+ .action(install_1.install);
27
+ // Command: Uninstall
28
+ program
29
+ .command("uninstall")
30
+ .description("Uninstall CyberMem (Teardown services)")
31
+ .option("--rpi", "Uninstall from Raspberry Pi")
32
+ .option("--vps", "Uninstall from VPS/Cloud server")
33
+ .option("--host <host>", "SSH host for remote uninstall")
34
+ .action(uninstall_1.uninstall);
24
35
  // Command: Upgrade
25
36
  program
26
37
  .command("upgrade")
@@ -28,6 +39,7 @@ program
28
39
  .option("--local", "Upgrade local instance (default)")
29
40
  .option("--rpi", "Upgrade remote RPi")
30
41
  .option("--vps", "Upgrade remote VPS")
42
+ .option("--staging", "Upgrade staging environment")
31
43
  .option("--host <host>", "SSH host for remote upgrade (e.g. pi@raspberrypi.local)")
32
44
  .action(upgrade_1.upgrade);
33
45
  program
@@ -42,6 +54,7 @@ program
42
54
  program
43
55
  .command("reset")
44
56
  .description("Reset (wipe) the CyberMem database - DESTRUCTIVE!")
57
+ .option("-f, --force", "Skip confirmation prompt")
45
58
  .action(reset_1.reset);
46
59
  program
47
60
  .command("dashboard")
@@ -1,15 +1,21 @@
1
1
  ---
2
2
  - name: Deploy CyberMem to Raspberry Pi
3
- hosts: rpi
4
- become: yes
3
+ hosts: all
4
+ become: true
5
5
  vars:
6
6
  project_dir: /home/{{ ansible_user }}/cybermem
7
7
  env_file: .env.rpi
8
+ # Default ports based on environment
9
+ TRAEFIK_PORT_STAGING: "8625"
10
+ TRAEFIK_PORT_PROD: "8626"
11
+ TRAEFIK_PORT_DEFAULT: "{{ TRAEFIK_PORT_STAGING if cybermem_env | default('prod') == 'staging' else TRAEFIK_PORT_PROD }}"
12
+ # Port to use consistently across all tasks
13
+ EFFECTIVE_TRAEFIK_PORT: "{{ TRAEFIK_PORT | default(TRAEFIK_PORT_DEFAULT) }}"
8
14
 
9
15
  tasks:
10
16
  - name: Update apt cache
11
17
  apt:
12
- update_cache: yes
18
+ update_cache: true
13
19
  cache_valid_time: 3600
14
20
 
15
21
  - name: Install dependencies
@@ -23,13 +29,13 @@
23
29
  service:
24
30
  name: docker
25
31
  state: started
26
- enabled: yes
32
+ enabled: true
27
33
 
28
34
  - name: Add user to docker group
29
35
  user:
30
36
  name: "{{ ansible_user }}"
31
37
  groups: docker
32
- append: yes
38
+ append: true
33
39
 
34
40
  - name: Create project directory
35
41
  file:
@@ -39,33 +45,204 @@
39
45
  group: "{{ ansible_user }}"
40
46
  mode: "0755"
41
47
 
42
- - name: Synchronize project files
48
+ - name: Ensure backup directory exists
49
+ file:
50
+ path: "{{ project_dir }}/backups"
51
+ state: directory
52
+ owner: "{{ ansible_user }}"
53
+ group: "{{ ansible_user }}"
54
+ mode: "0755"
55
+
56
+ - name: Ensure secrets directory exists
57
+ file:
58
+ path: "{{ project_dir }}/secrets"
59
+ state: directory
60
+ owner: "{{ ansible_user }}"
61
+ group: "{{ ansible_user }}"
62
+ mode: "0755"
63
+
64
+ - name: Write API Key Secret File
65
+ copy:
66
+ content: "{{ auth_token_value }}"
67
+ dest: "{{ project_dir }}/secrets/om_api_key"
68
+ owner: "{{ ansible_user }}"
69
+ group: "{{ ansible_user }}"
70
+ mode: "0600"
71
+ when: auth_token_value is defined
72
+ ignore_errors: true
73
+
74
+ - name: Ensure data directory exists
75
+ file:
76
+ path: "{{ project_dir }}/data"
77
+ state: directory
78
+ owner: "{{ ansible_user }}"
79
+ group: "{{ ansible_user }}"
80
+ mode: "0755"
81
+
82
+ - name: Check if database exists
83
+ stat:
84
+ path: "{{ project_dir }}/data/openmemory.sqlite"
85
+ register: db_file
86
+
87
+ - name: Create pre-deploy database backup
88
+ shell: "cp {{ project_dir }}/data/openmemory.sqlite {{ project_dir }}/backups/openmemory.sqlite.{{ ansible_facts['date_time']['iso8601_basic_short'] }}.bak"
89
+ when: db_file.stat.exists
90
+ ignore_errors: true
91
+
92
+ - name: Copy Docker Compose template
93
+ copy:
94
+ src: "../../docker-compose.yml"
95
+ dest: "{{ project_dir }}/docker-compose.yml"
96
+ owner: "{{ ansible_user }}"
97
+ group: "{{ ansible_user }}"
98
+ mode: "0644"
99
+
100
+ - name: Replace GHCR image tag (staging uses :staging, prod uses :latest)
101
+ replace:
102
+ path: "{{ project_dir }}/docker-compose.yml"
103
+ regexp: "(ghcr.io/[^/]+/cybermem-[^:]+):latest"
104
+ replace: '\1:{{ ghcr_tag | default("latest") }}'
105
+ when: ghcr_tag is defined and ghcr_tag != 'latest'
106
+
107
+ - name: Sync monitoring configuration
43
108
  synchronize:
44
- src: "{{ playbook_dir }}/../../"
45
- dest: "{{ project_dir }}"
46
- rsync_opts:
47
- - "--exclude=.git"
48
- - "--exclude=node_modules"
49
- - "--exclude=.next"
50
- - "--exclude=dashboard/.next"
51
- - "--exclude=*.log"
52
- - "--exclude=tmp"
53
- - "--exclude=__pycache__"
54
-
55
- - name: Copy environment file
109
+ src: "../../monitoring"
110
+ dest: "{{ project_dir }}/"
111
+ recursive: true
112
+ delete: true
113
+
114
+ # NOTE: build_from_source removed - all builds happen on GH Actions now
115
+
116
+ - name: Patch Traefik configuration
117
+ replace:
118
+ path: "{{ project_dir }}/monitoring/traefik/traefik.yml"
119
+ regexp: 'address: ":8626"'
120
+ replace: 'address: ":{{ EFFECTIVE_TRAEFIK_PORT }}"'
121
+
122
+ - name: Ensure .env is present (template if missing)
56
123
  copy:
57
- src: "../../{{ env_file }}"
124
+ content: |
125
+ CYBERMEM_INSTANCE=rpi
126
+ CYBERMEM_ENV={{ cybermem_env | default('prod') }}
127
+ PROJECT_NAME={{ PROJECT_NAME | default('cybermem') }}
128
+ TRAEFIK_PORT={{ EFFECTIVE_TRAEFIK_PORT }}
129
+ CYBERMEM_TAILSCALE={{ CYBERMEM_TAILSCALE | default('false') }}
130
+ SECRETS_DIR={{ project_dir }}/secrets
131
+ DATA_DIR={{ project_dir }}/data
132
+ CYBERMEM_ENV_PATH={{ project_dir }}/.env
133
+ CYBERMEM_HOME={{ project_dir }}
134
+ # Add other envs as needed
58
135
  dest: "{{ project_dir }}/.env"
136
+ force: true
59
137
  owner: "{{ ansible_user }}"
60
138
  group: "{{ ansible_user }}"
61
139
  mode: "0600"
62
140
 
63
- - name: Pull and start services
64
- command: docker-compose -f docker-compose.prod.yml --profile ollama up -d --pull always
141
+ - name: Load global version from package.json
142
+ set_fact:
143
+ global_version: "{{ (lookup('file', playbook_dir + '/../../../../../package.json') | from_json).version }}"
144
+
145
+ - name: Pull images from GHCR
146
+ command: docker-compose -p {{ PROJECT_NAME | default('cybermem') }} pull
147
+ args:
148
+ chdir: "{{ project_dir }}"
149
+ register: pull_out
150
+ changed_when: "'Downloaded newer image' in pull_out.stdout or 'Downloaded newer image' in pull_out.stderr"
151
+ when: not (skip_pull | default(false) | bool)
152
+
153
+ - name: Stop staging containers (prevent port conflicts, NEVER touch prod)
154
+ shell: |
155
+ docker-compose -p cybermem-staging down --remove-orphans 2>/dev/null || true
156
+ args:
157
+ chdir: "{{ project_dir }}"
158
+ changed_when: false
159
+ ignore_errors: true
160
+ when: cybermem_env | default('prod') == 'staging'
161
+
162
+ - name: Start services
163
+ command: docker-compose -p {{ PROJECT_NAME | default('cybermem') }} up -d --remove-orphans
65
164
  args:
66
165
  chdir: "{{ project_dir }}"
67
- register: compose_output
166
+ register: up_out
167
+ changed_when: "'Creating' in up_out.stdout or 'Recreating' in up_out.stdout or 'Starting' in up_out.stdout or 'Creating' in up_out.stderr or 'Recreating' in up_out.stderr or 'Starting' in up_out.stderr"
168
+
169
+ - name: Install sqlite3 for token injection
170
+ apt:
171
+ name: sqlite3
172
+ state: present
173
+
174
+ - name: Inject Authentication Token
175
+ shell: |
176
+ sqlite3 {{ project_dir }}/data/openmemory.sqlite "CREATE TABLE IF NOT EXISTS access_keys (
177
+ id TEXT PRIMARY KEY DEFAULT (lower(hex(randomblob(16)))),
178
+ key_hash TEXT NOT NULL,
179
+ name TEXT DEFAULT 'default',
180
+ user_id TEXT DEFAULT 'default',
181
+ created_at TEXT DEFAULT (datetime('now')),
182
+ last_used_at TEXT,
183
+ is_active INTEGER DEFAULT 1
184
+ );"
185
+ sqlite3 {{ project_dir }}/data/openmemory.sqlite "DELETE FROM access_keys WHERE name='{{ auth_token_name | default('ansible-generated') }}';"
186
+ sqlite3 {{ project_dir }}/data/openmemory.sqlite "INSERT INTO access_keys (id, key_hash, name, user_id) VALUES ('{{ auth_token_id }}', '{{ auth_token_hash }}', '{{ auth_token_name | default('ansible-generated') }}', 'admin');"
187
+ when: auth_token_hash is defined
188
+ ignore_errors: true
189
+
190
+ - name: Wait for Traefik to be ready
191
+ uri:
192
+ url: "http://localhost:{{ EFFECTIVE_TRAEFIK_PORT }}/health"
193
+ status_code: 200
194
+ register: traefik_ping
195
+ until: traefik_ping.status == 200
196
+ retries: 10
197
+ delay: 5
198
+
199
+ - name: Verify Overall System Health
200
+ uri:
201
+ url: "http://localhost:{{ EFFECTIVE_TRAEFIK_PORT }}/api/health"
202
+ return_content: true
203
+ register: health_status
204
+ until: "health_status.content is defined and (health_status.content | from_json).overall == 'ok'"
205
+ retries: 5
206
+ delay: 10
207
+ ignore_errors: true
208
+
209
+ - name: Configure Tailscale Funnel (after health check)
210
+ shell: |
211
+ # Reset any existing configuration for clean state
212
+ tailscale serve reset 2>/dev/null || true
213
+ tailscale funnel reset 2>/dev/null || true
214
+
215
+ # Configure port based on current environment only
216
+ if [ "{{ cybermem_env | default('prod') }}" = "staging" ]; then
217
+ tailscale funnel --bg --https=10000 http://127.0.0.1:{{ EFFECTIVE_TRAEFIK_PORT }}
218
+ else
219
+ tailscale funnel --bg --https=443 http://127.0.0.1:{{ EFFECTIVE_TRAEFIK_PORT }}
220
+ fi
221
+
222
+ tailscale funnel status
223
+ become: true
224
+ when: (CYBERMEM_TAILSCALE | default('false') | bool) and (health_status is defined and not health_status.failed and (health_status.content | from_json).overall == 'ok')
225
+ register: funnel_status
226
+ ignore_errors: true
227
+
228
+ - name: Display Tailscale Funnel Status
229
+ debug:
230
+ var: funnel_status.stdout_lines
231
+ when: funnel_status is defined and funnel_status.stdout_lines is defined
232
+
233
+ - name: Automated Rollback on Failure
234
+ block:
235
+ - name: Rollback message
236
+ debug:
237
+ msg: "Rollback logic triggered due to unhealthy state."
238
+
239
+ - name: Restart previous state containers
240
+ command: docker-compose -p {{ PROJECT_NAME | default('cybermem') }} up -d --remove-orphans
241
+ args:
242
+ chdir: "{{ project_dir }}"
243
+ ignore_errors: true
244
+ when: health_status.failed or (health_status.content | from_json).overall != 'ok'
68
245
 
69
- - name: Show deployment status
246
+ - name: Deployment Summary
70
247
  debug:
71
- var: compose_output.stdout_lines
248
+ msg: "CyberMem v{{ global_version }} successfully deployed. Health: {{ (health_status.content | from_json).overall | default('UNKNOWN') }}"
@@ -0,0 +1,44 @@
1
+ ---
2
+ - name: Reset CyberMem Database on Raspberry Pi
3
+ hosts: rpi
4
+ become: yes
5
+ vars:
6
+ project_dir: /home/{{ ansible_user }}/cybermem
7
+ db_path: "{{ project_dir }}/data/openmemory.sqlite"
8
+
9
+ tasks:
10
+ - name: Stop CyberMem services
11
+ command: docker-compose down
12
+ args:
13
+ chdir: "{{ project_dir }}"
14
+
15
+ - name: Wipe database file
16
+ file:
17
+ path: "{{ db_path }}"
18
+ state: absent
19
+
20
+ - name: Ensure data directory exists
21
+ file:
22
+ path: "{{ project_dir }}/data"
23
+ state: directory
24
+ owner: "{{ ansible_user }}"
25
+ group: "{{ ansible_user }}"
26
+ mode: "0755"
27
+
28
+ - name: Start CyberMem services
29
+ command: docker-compose up -d
30
+ args:
31
+ chdir: "{{ project_dir }}"
32
+
33
+ - name: Wait for Traefik to be ready
34
+ uri:
35
+ url: "http://localhost:8626/health"
36
+ status_code: 200
37
+ register: traefik_ping
38
+ until: traefik_ping.status == 200
39
+ retries: 10
40
+ delay: 5
41
+
42
+ - name: Database Reset Summary
43
+ debug:
44
+ msg: "CyberMem database at {{ db_path }} has been wiped and services are back online."
@@ -1,17 +1,9 @@
1
- # Builder stage for native modules
2
- FROM node:20-alpine AS builder
1
+ FROM node:20-alpine
3
2
  WORKDIR /app
4
- RUN apk add --no-cache python3 make g++
3
+ RUN apk add --no-cache libc6-compat
5
4
  COPY package.json ./
6
5
  RUN npm install
7
-
8
- # Production stage
9
- FROM node:20-alpine AS runner
10
- WORKDIR /app
11
- RUN apk add --no-cache libc6-compat
12
- COPY --from=builder /app/node_modules ./node_modules
13
6
  COPY server.js .
14
- COPY package.json .
15
7
 
16
8
  EXPOSE 3001
17
9
  CMD ["node", "server.js"]
@@ -3,7 +3,5 @@
3
3
  "version": "0.1.0",
4
4
  "description": "Auth sidecar for CyberMem",
5
5
  "main": "server.js",
6
- "dependencies": {
7
- "sqlite3": "5.1.7"
8
- }
6
+ "dependencies": {}
9
7
  }