@aifabrix/server-setup 1.3.0 → 1.5.2

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
@@ -17,7 +17,8 @@ This is the **one document** you need to get your builder-server **from zero to
17
17
  **The builder-server Docker image is not on a public registry.** You get it with the **AI Fabrix platform**.
18
18
 
19
19
  1. Install the **AI Fabrix Builder** CLI: `npm install -g @aifabrix/builder`
20
- 2. Use the CLI to run or deploy the platform (including builder-server). See [AI Fabrix Builder](https://github.com/esystemsdev/aifabrix-builder) and [docs/README.md](https://github.com/esystemsdev/aifabrix-builder/blob/main/docs/README.md).
20
+ 2. Install the **AI Fabrix server-setup** CLI (af-server): `npm install -g @aifabrix/server-setup`
21
+ 3. Use the Builder CLI to run or deploy the platform (including builder-server). See [AI Fabrix Builder](https://github.com/esystemsdev/aifabrix-builder) and [docs/README.md](https://github.com/esystemsdev/aifabrix-builder/blob/main/docs/README.md).
21
22
 
22
23
  Once you have the platform (and the builder-server image), use **af-server** to install that server on your own host.
23
24
 
@@ -39,19 +40,95 @@ Do the steps in [What you must do before running af-server](#what-you-must-do-be
39
40
 
40
41
  ## Install: from zero to running
41
42
 
42
- **From your PC** (SSH to server):
43
+ Complete the [manual prerequisites](#what-you-must-do-before-running-af-server) first (DNS, SSL directory, certificate and key on the server).
44
+
45
+ **Flow summary:** Only **step 1** runs over SSH from your PC. Steps **5** and **7** run **on the server** after you log in, so errors and output are visible directly there.
46
+
47
+ | Step | Where | Action |
48
+ | ---- | --------- | ------ |
49
+ | 1 | From PC | `af-server install-init $SSH` — only command over SSH; installs on server: SSH (if needed), Node 18+, npm, and `af-server` CLI. |
50
+ | 2 | One-time | Log in to the server once (e.g. with password) to approve passwordless SSH; then from PC: `af-server ssh-cert install $SSH`. |
51
+ | 3 | From PC | Copy SSL certificate and key to the server (see [SSL directory and certificates](#ssl-directory-and-certificates)); example commands below. |
52
+ | 4 | From PC | Log in to the server via SSH (passwordless). |
53
+ | 5 | **On server** | `sudo af-server install` — install all services (Docker, nginx package, Mutagen, cron, data dir); no builder-server container yet. |
54
+ | 6 | On server | Get the builder-server image (e.g. `az login` / `docker pull`). |
55
+ | 7 | **On server** | `sudo af-server install-server --dev-domain $DOMAIN` — nginx vhost, builder-server container, Docker TLS. |
56
+ | 8 | — | Done. |
57
+
58
+ ### Step 1: Bootstrap the server (from PC)
59
+
60
+ Set your target and run the only command that uses SSH from your PC:
61
+
62
+ ```bash
63
+ export SSH=serveradmin@builder02.aifabrix.dev
64
+ export DOMAIN=builder02.aifabrix.dev
65
+ af-server install-init $SSH
66
+ ```
67
+
68
+ This installs on the server: openssh-server (if needed), Node 18+, npm, and `@aifabrix/builder` + `@aifabrix/server-setup` so `af-server` is available there. No Docker, nginx, or builder-server yet.
69
+
70
+ ### Step 2: Passwordless SSH (from PC)
71
+
72
+ Log in to the server once (e.g. with password) to accept the host key and/or approve auth. Then from your PC:
73
+
74
+ ```bash
75
+ af-server ssh-cert install $SSH
76
+ ```
77
+
78
+ ### Step 3: Copy SSL (from PC)
79
+
80
+ Put `wildcard.crt` and `wildcard.key` in `/opt/aifabrix/ssl` on the server. Example (replace `$HDD` with the folder on your PC that has the cert and key):
81
+
82
+ ```bash
83
+ export HDD=/workspace/aifabrix-setup/certificates
84
+ ssh $SSH "sudo mkdir -p /opt/aifabrix/ssl"
85
+ scp $HDD/wildcard.crt $SSH:/tmp/wildcard.crt
86
+ scp $HDD/wildcard.key $SSH:/tmp/wildcard.key
87
+ ssh $SSH "sudo mv /tmp/wildcard.crt /tmp/wildcard.key /opt/aifabrix/ssl/ && sudo chmod 600 /opt/aifabrix/ssl/wildcard.key"
88
+ ```
89
+
90
+ ### Step 4: Log in to the server
43
91
 
44
92
  ```bash
45
- af-server install user@host
93
+ ssh $SSH
46
94
  ```
47
95
 
48
- **On the server itself** (Ubuntu, no target):
96
+ ### Step 5: Install services (on server)
49
97
 
50
98
  ```bash
51
- af-server install
99
+ sudo af-server install
52
100
  ```
53
101
 
54
- After install, your builder-server is up. Use the **AI Fabrix Builder CLI** for all usage (users, secrets, certs, etc.)—see [Builder documentation](https://github.com/esystemsdev/aifabrix-builder/blob/main/docs/developer-isolation.md).
102
+ This installs Docker, nginx (package only), Mutagen, data dir, apply-dev-users script and cron. It does **not** write the builder nginx vhost or start the builder-server container.
103
+
104
+ ### Step 6: Get the builder-server image (on server)
105
+
106
+ The image is not on a public registry. Use your platform’s method (e.g. Azure CLI or Docker login), then pull. Example:
107
+
108
+ ```bash
109
+ az login
110
+ az acr login --name aifabrixdevacr
111
+ docker pull aifabrixdevacr.azurecr.io/aifabrix/builder-server:latest
112
+ ```
113
+
114
+ Or with Docker login (username/password from your registry):
115
+
116
+ ```bash
117
+ docker login <registry> -u <user> -p <password>
118
+ docker pull <registry>/aifabrix/builder-server:latest
119
+ ```
120
+
121
+ ### Step 7: Install server (nginx vhost + container) (on server)
122
+
123
+ ```bash
124
+ sudo af-server install-server --dev-domain $DOMAIN
125
+ ```
126
+
127
+ Use the same domain as your DNS and SSL. Optional: `--ssl-dir /opt/aifabrix/ssl`, `--data-dir /opt/aifabrix/builder-server/data`, `--builder-port 3000`.
128
+
129
+ ### Step 8: Done
130
+
131
+ Your builder-server is up. Use the **AI Fabrix Builder CLI** for users, secrets, certs, etc.—see [Builder documentation](https://github.com/esystemsdev/aifabrix-builder/blob/main/docs/developer-isolation.md).
55
132
 
56
133
  ---
57
134
 
@@ -59,11 +136,14 @@ After install, your builder-server is up. Use the **AI Fabrix Builder CLI** for
59
136
 
60
137
  | Command | Description |
61
138
  | -------- | ----------- |
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. |
139
+ | `af-server install-init <user@host> [ -i SSH_KEY ]` | **From PC only.** One-time bootstrap over SSH: install on server SSH (if needed), Node 18+, npm, and af-server CLI. |
140
+ | `af-server install [ user@host ] [ -d DATA_DIR ] [ --dev-domain DOMAIN ] [ --ssl-dir PATH ] [ -i SSH_KEY ]` | **Run on server** (omit target): `sudo af-server install`. Infra only: Docker, nginx pkg, Mutagen, data dir, cron. No builder vhost or container. With target: same infra over SSH. |
141
+ | `af-server install-server --dev-domain DOMAIN [ -d DATA_DIR ] [ --ssl-dir PATH ] [ --builder-port PORT ]` | **On server only.** Nginx vhost, builder-server container, Docker TLS. Run after `sudo af-server install`. |
63
142
  | `af-server backup [ user@host ] [ -d DATA_DIR ] [ -o output.zip ] [ -i SSH_KEY ]` | On-demand backup (config + DB + keys). |
64
143
  | `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
144
  | `af-server restore backup.zip [ user@host ] [ -d DATA_DIR ] [ --force ] [ -i SSH_KEY ]` | Restore backup to DATA_DIR. |
66
145
  | `af-server ssh-cert install [ user@host ] [ -i SSH_KEY ]` | Add your SSH public key to server (passwordless auth). |
146
+ | `af-server install-ssh [ user@host ] [ -i SSH_KEY ]` | Activate SSH server (install openssh-server, enable and start ssh) without login. Omit target for local. |
67
147
 
68
148
  Backups contain secrets; store encrypted. Cron backup needs SQLite (`builder.db`) and `zip` on the server; default backup dir: `/opt/aifabrix/backups`.
69
149
 
@@ -114,18 +194,23 @@ If you SSH as a non-root user, that user must be able to sudo. The script will a
114
194
 
115
195
  ---
116
196
 
117
- ## High level: what the install does on your Ubuntu server
197
+ ## High level: what install vs install-server does
198
+
199
+ **`af-server install`** (step 5 — run on the server) does **infra only**:
200
+
201
+ - **System** — `apt update` and `apt upgrade`; optional hostname if `SETUP_HOSTNAME` is set.
202
+ - **Docker** — Installs Docker if missing; enables and starts it.
203
+ - **Admin user** — Adds the admin user (default `serveradmin`) to the `docker` group and grants passwordless sudo.
204
+ - **Nginx** — Installs the nginx **package** only; enables and starts it. Does **not** write the builder vhost yet.
205
+ - **Data dir** — Creates the data directory (default `/opt/aifabrix/builder-server/data`), workspace and ssh-keys subdirs, ownership for the container.
206
+ - **Apply-dev-users** — Installs the script and cron job (every 2 minutes) that sync per-developer OS users from builder-server state.
207
+ - **Mutagen** — Downloads and installs Mutagen; systemd service and daemon.
208
+ - **Optional** — If `INSTALL_PORTAINER=1`, installs the Portainer container.
118
209
 
119
- When you run `af-server install`, the script (as root) does the following on the server:
210
+ **`af-server install-server`** (step 7 run on the server) does the **server phase**:
120
211
 
121
- - **System** — `apt update` and `apt upgrade`; optional hostname change if `SETUP_HOSTNAME` is set.
122
- - **Docker** — Installs Docker if missing; enables and starts the Docker service.
123
- - **Admin user** — Adds the admin user (default `serveradmin`) to the `docker` group and grants passwordless sudo (`/etc/sudoers.d/<admin>`).
124
- - **Nginx** — Installs nginx if missing; enables and starts it. Generates a site config for your domain that proxies HTTPS to the builder-server container (using `wildcard.crt` and `wildcard.key` from your SSL dir).
125
- - **Builder-server data dir** — Creates the data directory (default `/opt/aifabrix/builder-server/data`), sets ownership for the container. Starts the `builder-server` container if the image already exists on the server; otherwise prints how to build and run it (you get the image from the AI Fabrix platform).
126
- - **Sync user** — Creates a system user (default `aifabrix-sync`) for Mutagen SSH sync; home under the data dir; creates `.ssh` and `authorized_keys`.
127
- - **Cron job** — Installs a cron job (every 2 minutes) that copies the managed `authorized_keys` file into the sync user’s `.ssh/authorized_keys`.
128
- - **Mutagen** — Downloads and installs the Mutagen binary; creates a systemd service and starts the daemon.
129
- - **Optional** — If `INSTALL_PORTAINER=1`, installs the Portainer container. If Docker TLS is not skipped, writes `/etc/docker/daemon.json`; you can use the **same certificate** from `/opt/aifabrix/ssl` (e.g. symlink or copy `wildcard.crt` and `wildcard.key` to the paths Docker expects: `/etc/docker/server-cert.pem`, `/etc/docker/server-key.pem`, and if needed `ca.pem` for the CA), or provide separate Docker TLS certs.
212
+ - **Nginx vhost** — Writes the builder site config from template (domain, SSL dir, proxy to builder-server), reloads nginx.
213
+ - **Builder-server container** — Creates data dir (if needed), starts the builder-server container (if the image is present).
214
+ - **Docker TLS** — Copies certs and configures `/etc/docker/daemon.json` for TLS (using website cert and builder-server CA).
130
215
 
131
- Result: your Ubuntu server has Docker, nginx (HTTPS for your domain), the builder-server container (if image present), the sync user and key sync, and Mutagen—ready for the Builder CLI to use.
216
+ Result: after both steps, the server has Docker, nginx (HTTPS for your domain), the builder-server container (if image present), and Mutagen—ready for the Builder CLI to use.
@@ -0,0 +1,123 @@
1
+ #!/bin/sh
2
+ # Apply per-developer OS users from builder-server state (DATA_DIR/ssh-keys, pending-removals).
3
+ # Run via cron every 2 minutes. Creates/updates dev<id> users, .ssh/authorized_keys, workspace symlink,
4
+ # and optional ~/.aifabrix/config.yaml when missing. Processes pending-removals then applies keys.
5
+ # Requires root. DATA_DIR must match builder-server (e.g. /opt/aifabrix/builder-server/data).
6
+
7
+ set -e
8
+
9
+ DATA_DIR="${DATA_DIR:-/opt/aifabrix/builder-server/data}"
10
+ SSH_KEYS_DIR="${DATA_DIR}/ssh-keys"
11
+ PENDING_REMOVALS="${DATA_DIR}/pending-removals"
12
+ # Defaults matching builder-server env.template (override via env or apply-dev-users-defaults)
13
+ AIFABRIX_SECRETS="${AIFABRIX_SECRETS:-/aifabrix-miso/builder/secrets.local.yaml}"
14
+ AIFABRIX_ENV_CONFIG="${AIFABRIX_ENV_CONFIG:-aifabrix-miso/builder/env-config.yaml}"
15
+
16
+ if [ ! -d "$DATA_DIR" ]; then
17
+ echo "DATA_DIR not found: $DATA_DIR"
18
+ exit 0
19
+ fi
20
+
21
+ # Read secrets-encryption key from server data dir (same as builder-server ENCRYPTION_KEY_PATH); do not log.
22
+ secrets_encryption_value=""
23
+ if [ -f "${DATA_DIR}/secrets-encryption.key" ]; then
24
+ secrets_encryption_value=$(cat "${DATA_DIR}/secrets-encryption.key" | tr -d '\n\r' | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')
25
+ fi
26
+
27
+ # --- 1. Process removals ---
28
+ if [ -f "$PENDING_REMOVALS" ]; then
29
+ while IFS= read -r dev_id || [ -n "$dev_id" ]; do
30
+ dev_id=$(echo "$dev_id" | tr -d '\r\n ')
31
+ [ -z "$dev_id" ] && continue
32
+ user_name="dev${dev_id}"
33
+ if getent passwd "$user_name" >/dev/null 2>&1; then
34
+ userdel -r "$user_name" 2>/dev/null || userdel "$user_name" 2>/dev/null || true
35
+ fi
36
+ done < "$PENDING_REMOVALS"
37
+ : > "$PENDING_REMOVALS"
38
+ fi
39
+
40
+ # --- 2. Process each developer that has keys ---
41
+ for key_file in "${SSH_KEYS_DIR}"/*/authorized_keys; do
42
+ [ -f "$key_file" ] || continue
43
+ dev_id=$(dirname "$key_file" | xargs basename)
44
+ [ -z "$dev_id" ] && continue
45
+ user_name="dev${dev_id}"
46
+ home_dir="/home/${user_name}"
47
+ workspace_target="${DATA_DIR}/workspace/${user_name}"
48
+
49
+ if ! getent passwd "$user_name" >/dev/null 2>&1; then
50
+ useradd -m -s /bin/bash "$user_name"
51
+ else
52
+ # Ensure shell is bash (e.g. after manual changes)
53
+ current_shell=$(getent passwd "$user_name" | cut -d: -f7)
54
+ if [ "$current_shell" != "/bin/bash" ]; then
55
+ usermod -s /bin/bash "$user_name" 2>/dev/null || true
56
+ fi
57
+ fi
58
+
59
+ mkdir -p "${home_dir}/.ssh"
60
+ cp "$key_file" "${home_dir}/.ssh/authorized_keys"
61
+ chown -R "${user_name}:${user_name}" "${home_dir}/.ssh"
62
+ chmod 700 "${home_dir}/.ssh"
63
+ chmod 600 "${home_dir}/.ssh/authorized_keys"
64
+
65
+ # .aifabrix/config.yaml only if missing (do not overwrite)
66
+ config_dir="${home_dir}/.aifabrix"
67
+ config_file="${config_dir}/config.yaml"
68
+ if [ ! -f "$config_file" ]; then
69
+ mkdir -p "$config_dir"
70
+ hostname_val=$(hostname -f 2>/dev/null || hostname 2>/dev/null || echo "localhost")
71
+ # Values from builder-server: secrets-encryption from DATA_DIR, paths from env (apply-dev-users-defaults or env.template)
72
+ yaml_escape() { echo "$1" | sed 's/\\/\\\\/g;s/"/\\"/g'; }
73
+ {
74
+ echo "user-mutagen-folder: ${workspace_target}"
75
+ printf "secrets-encryption: \"%s\"\n" "$(yaml_escape "$secrets_encryption_value")"
76
+ printf "aifabrix-secrets: \"%s\"\n" "$(yaml_escape "$AIFABRIX_SECRETS")"
77
+ printf "aifabrix-env-config: \"%s\"\n" "$(yaml_escape "$AIFABRIX_ENV_CONFIG")"
78
+ echo "remote-server: \"http://localhost:3000\""
79
+ echo "docker-endpoint: \"tcp://${hostname_val}:2376\""
80
+ echo "sync-ssh-user: \"${user_name}\""
81
+ echo "sync-ssh-host: \"${hostname_val}\""
82
+ } > "$config_file"
83
+ chown -R "${user_name}:${user_name}" "$config_dir"
84
+ chmod 700 "$config_dir"
85
+ chmod 600 "$config_file"
86
+ fi
87
+
88
+ # Workspace: ensure dir exists, owned by user; symlink from home
89
+ mkdir -p "$workspace_target"
90
+ chown -R "${user_name}:${user_name}" "$workspace_target"
91
+ if [ -L "${home_dir}/workspace" ]; then
92
+ current_target=$(readlink -f "${home_dir}/workspace" 2>/dev/null || true)
93
+ want_target=$(readlink -f "$workspace_target" 2>/dev/null || echo "$workspace_target")
94
+ if [ -n "$current_target" ] && [ -n "$want_target" ] && [ "$current_target" != "$want_target" ]; then
95
+ rm -f "${home_dir}/workspace"
96
+ ln -s "$workspace_target" "${home_dir}/workspace"
97
+ chown -h "${user_name}:${user_name}" "${home_dir}/workspace"
98
+ fi
99
+ elif [ ! -e "${home_dir}/workspace" ]; then
100
+ ln -s "$workspace_target" "${home_dir}/workspace"
101
+ chown -h "${user_name}:${user_name}" "${home_dir}/workspace"
102
+ fi
103
+
104
+ # Default directory on SSH login: cd to workspace
105
+ profile="${home_dir}/.profile"
106
+ if [ ! -f "$profile" ]; then
107
+ touch "$profile"
108
+ chown "${user_name}:${user_name}" "$profile"
109
+ fi
110
+ if ! grep -q 'cd.*workspace' "$profile" 2>/dev/null; then
111
+ echo '[ -d "$HOME/workspace" ] && cd "$HOME/workspace"' >> "$profile"
112
+ chown "${user_name}:${user_name}" "$profile"
113
+ fi
114
+ bashrc="${home_dir}/.bashrc"
115
+ if [ ! -f "$bashrc" ]; then
116
+ touch "$bashrc"
117
+ chown "${user_name}:${user_name}" "$bashrc"
118
+ fi
119
+ if ! grep -q 'cd.*workspace' "$bashrc" 2>/dev/null; then
120
+ echo '[ -d "$HOME/workspace" ] && cd "$HOME/workspace"' >> "$bashrc"
121
+ chown "${user_name}:${user_name}" "$bashrc"
122
+ fi
123
+ done