@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
|
|
5
|
-
#
|
|
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 $
|
|
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
|
-
|
|
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
|
-
|
|
121
|
-
|
|
122
|
-
|
|
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
|
-
#
|
|
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
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
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
|
|
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
|