@aifabrix/server-setup 1.2.0 → 1.3.0

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/README.md CHANGED
@@ -59,7 +59,7 @@ After install, your builder-server is up. Use the **AI Fabrix Builder CLI** for
59
59
 
60
60
  | Command | Description |
61
61
  | -------- | ----------- |
62
- | `af-server install [ user@host ] [ -d DATA_DIR ] [ --dev-domain DOMAIN ] [ --ssl-dir PATH ] [ -i SSH_KEY ]` | Install: Docker, nginx, SSL proxy, sync user, cron. |
62
+ | `af-server install [ user@host ] [ -d DATA_DIR ] [ --dev-domain DOMAIN ] [ --ssl-dir PATH ] [ -i SSH_KEY ]` | Install or update: Docker, nginx (builder vhost always updated from template), SSL proxy, sync user, cron. Re-run to apply latest config. |
63
63
  | `af-server backup [ user@host ] [ -d DATA_DIR ] [ -o output.zip ] [ -i SSH_KEY ]` | On-demand backup (config + DB + keys). |
64
64
  | `af-server backup [ user@host ] --schedule [ --backup-dir PATH ] [ --keep-days N ] [ -i SSH_KEY ]` | Cron backup (daily 02:00, keep last N, default 7). |
65
65
  | `af-server restore backup.zip [ user@host ] [ -d DATA_DIR ] [ --force ] [ -i SSH_KEY ]` | Restore backup to DATA_DIR. |
@@ -1,12 +1,8 @@
1
1
  # Nginx snippet for builder-server onboarding API (https://DEV_DOMAIN_PLACEHOLDER).
2
2
  # Generated from template by af-server install (substitutes DEV_DOMAIN, SSL_DIR, BUILDER_SERVER_PORT, DATA_DIR).
3
3
  # SSL cert and key from SSL_DIR_PLACEHOLDER (wildcard.crt, wildcard.key).
4
- # Client cert: ssl_client_certificate uses DATA_DIR_PLACEHOLDER/ca.crt (Builder CA; created by builder-server on first run).
5
- # If the backend returns 400 Bad Request for requests with client cert, nginx may be sending $ssl_client_cert with
6
- # literal newlines (header folding). Use njs to send cert on one line: load_module modules/ngx_http_js_module.so;
7
- # then js_set $client_cert_escaped cert.oneline; js_include cert-escaped.js; and proxy_set_header X-Client-Cert $client_cert_escaped;
8
- # (cert-escaped.js: function cert(r){return r.variables.ssl_client_cert?r.variables.ssl_client_cert.replace(/\n/g,'\\n'):'';}).
9
- # Reload nginx after placing: sudo nginx -t && sudo systemctl reload nginx.
4
+ # Client cert: ssl_client_certificate uses DATA_DIR_PLACEHOLDER/ca.crt (Builder CA).
5
+ # Client sends X-Client-Cert as base64-encoded PEM; nginx forwards it to the backend.
10
6
 
11
7
  server {
12
8
  listen 443 ssl;
@@ -25,6 +21,6 @@ server {
25
21
  proxy_set_header X-Real-IP $remote_addr;
26
22
  proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
27
23
  proxy_set_header X-Forwarded-Proto $scheme;
28
- proxy_set_header X-Client-Cert $ssl_client_cert;
24
+ proxy_set_header X-Client-Cert $http_x_client_cert;
29
25
  }
30
26
  }
@@ -1,6 +1,7 @@
1
1
  #!/bin/sh
2
2
  # Idempotent dev server setup script (no Node/Builder on server). Safe to run multiple times.
3
3
  # Run via af-server install user@host. REPO_ROOT must be set to the dir containing builder/builder-server/nginx-builder-server.conf.template.
4
+ # On empty server: installs Docker, nginx, admin user, optional Portainer/Mutagen; then updates nginx config and ensures builder-server container.
4
5
  # Optional env: DEV_DOMAIN, SSL_DIR, DATA_DIR, SETUP_ADMIN_USER, SYNC_USER, BUILDER_SERVER_PORT, NGINX_CONF_DIR, SETUP_HOSTNAME, INSTALL_PORTAINER=1, SKIP_DOCKER_TLS=1.
5
6
 
6
7
  set -e
@@ -101,25 +102,23 @@ if ! command -v nginx >/dev/null 2>&1; then
101
102
  systemctl enable nginx
102
103
  systemctl start nginx
103
104
  fi
104
-
105
105
  NGINX_CONF="$NGINX_CONF_DIR/$DEV_DOMAIN.conf"
106
106
  NGINX_TEMPLATE="$REPO_ROOT/builder/builder-server/nginx-builder-server.conf.template"
107
- if [ ! -f "$NGINX_CONF" ] && [ -f "$NGINX_TEMPLATE" ]; then
107
+ # Always update builder vhost from template so re-running install applies latest config.
108
+ if [ -f "$NGINX_TEMPLATE" ]; then
108
109
  sed -e "s|DEV_DOMAIN_PLACEHOLDER|$DEV_DOMAIN|g" \
109
110
  -e "s|SSL_DIR_PLACEHOLDER|$SSL_DIR|g" \
110
111
  -e "s|BUILDER_SERVER_PORT_PLACEHOLDER|$BUILDER_SERVER_PORT|g" \
111
112
  -e "s|DATA_DIR_PLACEHOLDER|$DATA_DIR|g" \
112
113
  "$NGINX_TEMPLATE" > "$NGINX_CONF"
113
- if nginx -t 2>/dev/null; then
114
- systemctl reload nginx
115
- fi
116
114
  elif [ ! -f "$NGINX_CONF" ]; then
117
115
  echo "Warning: SSL prereqs required. Place $NGINX_CONF (see SETUP.md) and ensure $SSL_DIR/wildcard.crt and $SSL_DIR/wildcard.key exist."
118
116
  fi
119
- if command -v nginx >/dev/null 2>&1; then
120
- if nginx -t 2>/dev/null; then
121
- systemctl reload nginx
122
- fi
117
+ if command -v nginx >/dev/null 2>&1 && nginx -t 2>/dev/null; then
118
+ systemctl reload nginx
119
+ elif [ -f "$NGINX_TEMPLATE" ] && command -v nginx >/dev/null 2>&1; then
120
+ echo "Warning: nginx -t failed (config was still written to $NGINX_CONF). Nginx was NOT reloaded."
121
+ nginx -t 2>&1 || true
123
122
  fi
124
123
 
125
124
  # --- Mutagen ---
@@ -171,17 +170,60 @@ DOCKER_EOF
171
170
  fi
172
171
 
173
172
  # --- Builder-server data dir and container ---
174
- # Container runs as uid 1001 (nodejs); data dir must be writable for bootstrap (key, CA, DB).
173
+ # Paths match AI Fabrix Builder: aifabrix build + resolve use DATA_DIR=/mnt/data in container; host DATA_DIR (e.g. /opt/aifabrix/builder-server/data) is the HDD/mount. See builder/builder-server/README.md and env.template.
174
+ # Nginx uses DATA_DIR/ca.crt for ssl_client_certificate; container must use the same host path so CA matches.
175
+ CONTAINER_DATA_PATH="/mnt/data"
176
+ BUILDER_IMAGE="aifabrix/builder-server:latest"
175
177
  mkdir -p "$DATA_DIR"
176
178
  chown -R 1001:65533 "$DATA_DIR"
177
179
  chmod 755 "$DATA_DIR"
180
+ DATA_DIR_ABS=$(cd "$DATA_DIR" && pwd)
178
181
  if command -v docker >/dev/null 2>&1; then
179
- if ! docker ps -a --format '{{.Names}}' 2>/dev/null | grep -q '^builder-server$'; then
180
- echo "Builder-server container not found. Build and run manually (see builder/builder-server/README.md):"
181
- echo " docker build -t builder-server:latest -f builder/builder-server/Dockerfile ."
182
- echo " docker run -d --name builder-server --restart unless-stopped -p ${BUILDER_SERVER_PORT}:3000 -v $DATA_DIR:/data -e PORT=3000 builder-server:latest"
182
+ CONTAINER_NAME=""
183
+ for n in builder-server aifabrix-builder-server; do
184
+ if docker ps -a --format '{{.Names}}' 2>/dev/null | grep -q "^${n}$"; then
185
+ CONTAINER_NAME="$n"
186
+ break
187
+ fi
188
+ done
189
+ if [ -n "$CONTAINER_NAME" ]; then
190
+ # Ensure container uses DATA_DIR as bind mount so nginx and app share same ca.crt (check /mnt/data first, then /data)
191
+ DATA_MOUNT_SOURCE=$(docker inspect --format '{{range .Mounts}}{{if eq .Destination "/mnt/data"}}{{.Source}}{{end}}{{end}}' "$CONTAINER_NAME" 2>/dev/null)
192
+ if [ -z "$DATA_MOUNT_SOURCE" ]; then
193
+ DATA_MOUNT_SOURCE=$(docker inspect --format '{{range .Mounts}}{{if eq .Destination "/data"}}{{.Source}}{{end}}{{end}}' "$CONTAINER_NAME" 2>/dev/null)
194
+ fi
195
+ MOUNT_SOURCE_ABS=""
196
+ if [ -n "$DATA_MOUNT_SOURCE" ] && [ -d "$DATA_MOUNT_SOURCE" ]; then
197
+ MOUNT_SOURCE_ABS=$(cd "$DATA_MOUNT_SOURCE" && pwd)
198
+ fi
199
+ if [ "$MOUNT_SOURCE_ABS" != "$DATA_DIR_ABS" ]; then
200
+ echo "Recreating builder-server container to use DATA_DIR bind mount ($DATA_DIR_ABS -> $CONTAINER_DATA_PATH) so nginx and container share the same CA."
201
+ BUILDER_IMAGE=$(docker inspect --format '{{.Config.Image}}' "$CONTAINER_NAME" 2>/dev/null) || true
202
+ [ -z "$BUILDER_IMAGE" ] && BUILDER_IMAGE="aifabrix/builder-server:latest"
203
+ docker stop "$CONTAINER_NAME" 2>/dev/null || true
204
+ docker rm "$CONTAINER_NAME" 2>/dev/null || true
205
+ CONTAINER_NAME=""
206
+ fi
207
+ fi
208
+ if [ -z "$CONTAINER_NAME" ]; then
209
+ IMG_TO_USE="$BUILDER_IMAGE"
210
+ if ! docker images -q "$IMG_TO_USE" 2>/dev/null | grep -q .; then
211
+ IMG_TO_USE="builder-server:latest"
212
+ fi
213
+ if docker images -q "$IMG_TO_USE" 2>/dev/null | grep -q .; then
214
+ docker run -d --name aifabrix-builder-server --restart unless-stopped \
215
+ -p "${BUILDER_SERVER_PORT}:3000" \
216
+ -v "$DATA_DIR_ABS:$CONTAINER_DATA_PATH" \
217
+ -e PORT=3000 \
218
+ -e DATA_DIR="$CONTAINER_DATA_PATH" \
219
+ -e ENCRYPTION_KEY_PATH="${CONTAINER_DATA_PATH}/secrets-encryption.key" \
220
+ "$IMG_TO_USE"
221
+ else
222
+ echo "Builder-server image not found. Get the image from the AI Fabrix Builder (aifabrix build builder-server; then push/deploy to this host). No source or docker build on server. See builder/builder-server/README.md."
223
+ echo "After the image is on this host, re-run af-server install. Install will start the container with: -v $DATA_DIR_ABS:$CONTAINER_DATA_PATH and DATA_DIR=$CONTAINER_DATA_PATH."
224
+ fi
183
225
  else
184
- docker start builder-server 2>/dev/null || true
226
+ docker start "$CONTAINER_NAME" 2>/dev/null || true
185
227
  fi
186
228
  fi
187
229
 
package/dist/install.js CHANGED
@@ -65,7 +65,7 @@ export function runInstallLocal(options = {}) {
65
65
  try {
66
66
  fs.writeFileSync(path.join(tmpDir, 'setup.sh'), getSetupScript(), { mode: 0o755 });
67
67
  fs.writeFileSync(path.join(builderSubdir, 'nginx-builder-server.conf.template'), getNginxTemplate());
68
- const env = `REPO_ROOT=${tmpDir} DATA_DIR=${dataDir} DEV_DOMAIN=${devDomain} SSL_DIR=${sslDir} BUILDER_SERVER_PORT=${builderServerPort}`;
68
+ const env = [`REPO_ROOT=${tmpDir}`, `DATA_DIR=${dataDir}`, `DEV_DOMAIN=${devDomain}`, `SSL_DIR=${sslDir}`, `BUILDER_SERVER_PORT=${builderServerPort}`].join(' ');
69
69
  execSync(`sudo ${env} ${tmpDir}/setup.sh`, { stdio: 'inherit' });
70
70
  }
71
71
  finally {
package/dist/restore.js CHANGED
@@ -45,7 +45,7 @@ export async function runRestore(options) {
45
45
  await exec(conn, `chown 1001:65533 ${dataDir}/${k}`);
46
46
  }
47
47
  }
48
- const restart = await exec(conn, 'docker restart builder-server 2>/dev/null || true');
48
+ const restart = await exec(conn, 'docker restart builder-server 2>/dev/null; docker restart aifabrix-builder-server 2>/dev/null || true');
49
49
  if (restart.stderr && !restart.stderr.includes('No such container')) {
50
50
  process.stderr.write(restart.stderr);
51
51
  }
@@ -89,7 +89,7 @@ export async function runRestoreLocal(options) {
89
89
  }
90
90
  }
91
91
  try {
92
- execSync('docker restart builder-server 2>/dev/null || true', { stdio: 'inherit' });
92
+ execSync('docker restart builder-server 2>/dev/null; docker restart aifabrix-builder-server 2>/dev/null || true', { stdio: 'inherit' });
93
93
  }
94
94
  catch {
95
95
  // container may not exist
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aifabrix/server-setup",
3
- "version": "1.2.0",
3
+ "version": "1.3.0",
4
4
  "description": "CLI to install, backup, and restore AI Fabrix builder-server (config + DB) over SSH",
5
5
  "type": "module",
6
6
  "main": "dist/cli.js",