Skip to content

Commit 4f96f43

Browse files
committed
Merge branch 'feature/smbcloud-deploy' into development
2 parents b82605e + 2aad9c1 commit 4f96f43

19 files changed

Lines changed: 828 additions & 206 deletions

File tree

.agents/skills/smbcloud-deploy-monorepo/SKILL.md

Lines changed: 68 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,18 @@ Applies to:
1818

1919
## What makes a monorepo
2020

21-
A monorepo is a repository where `[project]` has `runner = 255` (the `Monorepo` variant) and deployable sub-projects are listed as `[[projects]]` entries. The root project is never deployed directly — it is a container.
21+
A monorepo is a repository where `[project]` has `runner = 255` (the `Monorepo` variant) and deployable targets are listed as `[[projects]]` entries. The root project is never deployed directly — it is a container.
22+
23+
Keep the deploy hierarchy straight:
24+
25+
- root `[project]` = umbrella workspace in smbCloud (not deployable)
26+
- repo = the monorepo itself, represented by a `DeployRepo` record
27+
- each `[[projects]]` entry = one deployable `FrontendApp` inside that repo, identified by `frontend_app_id`
28+
- each deployment record = one release of one `FrontendApp`, linked via `frontend_app_id`
29+
30+
The canonical chain is: Deployment → FrontendApp → DeployRepo → Project
31+
32+
The config format still uses `[[projects]]` for historical reasons, but conceptually those entries are app targets, not separate umbrella workspaces. Each entry should include `frontend_app_id` and `deploy_repo_id` so deployments are attributed to the correct app.
2233

2334
The CLI detects this in `process_deploy.rs`:
2435

@@ -54,6 +65,8 @@ created_at = "2025-09-20T16:33:05.154Z"
5465
updated_at = "2025-09-20T16:33:05.154Z"
5566
```
5667

68+
The root `id` is the workspace ID. It is not a deployable app ID.
69+
5770
Key rules:
5871

5972
- `runner = 255` is mandatory — this is what activates monorepo behavior
@@ -62,11 +75,13 @@ Key rules:
6275

6376
### Sub-projects (deployable units)
6477

65-
Each `[[projects]]` entry is a self-contained deploy target with its own `kind`, `source`, `path`, `runner`, and strategy-specific fields.
78+
Each `[[projects]]` entry is a self-contained deployable app target with its own `kind`, `source`, `path`, `runner`, and strategy-specific fields.
6679

6780
```toml
6881
[[projects]]
69-
id = 54
82+
id = 38
83+
frontend_app_id = "a7f6d5d4-..."
84+
deploy_repo_id = 12
7085
name = "splitfireweb"
7186
repository = "splitfireweb"
7287
description = "SplitFire AI website."
@@ -110,25 +125,27 @@ updated_at = "2025-09-20T16:33:05.154Z"
110125

111126
## Sub-project field reference
112127

113-
| Field | Required | Description |
114-
| ------------------- | -------- | ------------------------------------------------------------------------------------------- |
115-
| `id` | yes | smbCloud project ID |
116-
| `name` | yes | unique identifier, used with `--project` flag and in the selection prompt |
117-
| `repository` | yes | remote repository name on the smbCloud server (used for git deploy and SSH paths) |
118-
| `description` | no | human-readable description |
119-
| `source` | yes | local path to the sub-project directory, relative to the monorepo root |
120-
| `path` | depends | remote directory on the server, relative to `~/` — required for `nextjs-ssr` and `vite-spa` |
121-
| `runner` | yes | server tier: `0` (NodeJs), `1` (Static), `2` (Ruby), `3` (Swift) |
122-
| `kind` | depends | deploy strategy: `"nextjs-ssr"`, `"vite-spa"`, `"rails"`, or omitted for generic deploy |
123-
| `package_manager` | depends | `"pnpm"` or `"npm"` — required for `nextjs-ssr` and `vite-spa` |
124-
| `pm2_app` | depends | PM2 process name — required for `nextjs-ssr` |
125-
| `port` | depends | runtime port — required for `nextjs-ssr`, defaults to `3000` if omitted |
126-
| `output` | depends | build output directory — required for `vite-spa`, typically `"dist"` |
127-
| `shared_lib` | no | path to shared library directory to rsync before deploy — used by `rails` |
128-
| `compile_cmd` | no | SSH command to run on the server after syncing shared libs — used by `rails` |
129-
| `deployment_method` | no | `0` (Git) or `1` (Rsync) — only matters when `kind` is not set |
130-
| `created_at` | yes | ISO 8601 timestamp |
131-
| `updated_at` | yes | ISO 8601 timestamp |
128+
| Field | Required | Description |
129+
| ------------------- | ----------- | ------------------------------------------------------------------------------------------- |
130+
| `id` | yes | umbrella workspace ID in smbCloud |
131+
| `frontend_app_id` | recommended | FrontendApp UUID — include so deployments are attributed to the correct app |
132+
| `deploy_repo_id` | recommended | DeployRepo ID — links the deploy target to its git repository record |
133+
| `name` | yes | unique identifier, used with `--project` flag and in the selection prompt |
134+
| `repository` | yes | remote repository name on the smbCloud server (used for git deploy and SSH paths) |
135+
| `description` | no | human-readable description |
136+
| `source` | yes | local path to the sub-project directory, relative to the monorepo root |
137+
| `path` | depends | remote directory on the server, relative to `~/` — required for `nextjs-ssr` and `vite-spa` |
138+
| `runner` | yes | server tier: `0` (NodeJs), `1` (Static), `2` (Ruby), `3` (Swift) |
139+
| `kind` | depends | deploy strategy: `"nextjs-ssr"`, `"vite-spa"`, `"rails"`, or omitted for generic deploy |
140+
| `package_manager` | depends | `"pnpm"` or `"npm"` — required for `nextjs-ssr` and `vite-spa` |
141+
| `pm2_app` | depends | PM2 process name — required for `nextjs-ssr` |
142+
| `port` | depends | runtime port — required for `nextjs-ssr`, defaults to `3000` if omitted |
143+
| `output` | depends | build output directory — required for `vite-spa`, typically `"dist"` |
144+
| `shared_lib` | no | path to shared library directory to rsync before deploy — used by `rails` |
145+
| `compile_cmd` | no | SSH command to run on the server after syncing shared libs — used by `rails` |
146+
| `deployment_method` | no | `0` (Git) or `1` (Rsync) — only matters when `kind` is not set |
147+
| `created_at` | yes | ISO 8601 timestamp |
148+
| `updated_at` | yes | ISO 8601 timestamp |
132149

133150
## Deploy routing
134151

@@ -163,7 +180,7 @@ smb deploy --project <name>
163180

164181
### Strategy: `nextjs-ssr`
165182

166-
Local build, rsync three directories, SSH PM2 restart. See `smbcloud-deploy-nextjs` skill for details.
183+
Local build, rsync three directories, then SSH delete + fresh PM2 start (prefer server `ecosystem.config.js` when present). See `smbcloud-deploy-nextjs` skill for details.
167184

168185
Required fields: `kind`, `source`, `path`, `pm2_app`, `package_manager`, `port`
169186

@@ -368,8 +385,12 @@ steps:
368385
ssh $GIT_USER@$GIT_HOST bash << 'EOF'
369386
set -e
370387
cd ~/apps/web/splitfireweb
388+
mkdir -p logs
371389
if pm2 describe "splitfire-web" > /dev/null 2>&1; then
372-
pm2 restart "splitfire-web" --update-env
390+
pm2 delete "splitfire-web"
391+
fi
392+
if [ -f ecosystem.config.js ]; then
393+
pm2 start ecosystem.config.js --only "splitfire-web" --env production
373394
else
374395
PORT=3010 HOSTNAME=127.0.0.1 pm2 start node --name "splitfire-web" -- server.js
375396
fi
@@ -427,7 +448,14 @@ All CI rsync commands must include:
427448

428449
### CI PM2 restart
429450

430-
The CI restart command must use `pm2 restart <app> --update-env` on subsequent deploys so environment variable changes from the ecosystem file are picked up. Without `--update-env`, PM2 keeps the environment from the original `pm2 start` invocation.
451+
Current CI and CLI parity should use delete + fresh start, not restart-in-place:
452+
453+
- `pm2 delete <app>` if it exists
454+
- `pm2 start ecosystem.config.js --only <app> --env production` when the server file exists
455+
- otherwise fall back to `PORT=<port> HOSTNAME=127.0.0.1 pm2 start node --name <app> -- server.js`
456+
- `pm2 save`
457+
458+
This matches the standalone Next.js deploy model and avoids stale PM2 command state from older deploy methods.
431459

432460
## Deployment tracking
433461

@@ -437,6 +465,11 @@ Every deploy records status in the smbCloud API regardless of strategy:
437465
2. `PATCH /deployments/:id` with `status: Done` on success
438466
3. `PATCH /deployments/:id` with `status: Failed` on failure
439467

468+
Current tracking model in the CLI:
469+
470+
- requests still route through the umbrella workspace `id`
471+
- when `frontend_app_id` is present in the deploy target config, the payload also carries it so the API can attribute the deploy to the correct app inside the repo or monorepo
472+
440473
For non-git deploys (rsync, nextjs-ssr, vite-spa), the `commit_hash` field is a UTC timestamp (`20250920T163305Z`) since there is no git commit on the deploy path.
441474

442475
## Adding a new sub-project to a monorepo
@@ -496,7 +529,13 @@ The Nginx `alias` directive for `/_next/static/` or the `root` for a Vite SPA po
496529

497530
### PM2 not picking up new environment variables
498531

499-
Use `pm2 restart <app> --update-env && pm2 save`. Without `--update-env`, PM2 reuses the environment from the original start.
532+
Current CLI parity is delete + fresh start. Check these in order:
533+
534+
1. the server's `ecosystem.config.js` was edited, not just the repo copy
535+
2. the deploy script is actually starting from `ecosystem.config.js`
536+
3. `pm2 save` ran after the fresh start
537+
538+
If you are changing env manually outside the deploy flow, `pm2 restart <app> --update-env && pm2 save` is still a valid operator command.
500539

501540
## Validation
502541

@@ -530,5 +569,7 @@ Use `pm2 restart <app> --update-env && pm2 save`. Without `--update-env`, PM2 re
530569
- forgetting `--mkpath` on rsync in CI workflows — first deploys fail because the remote directory does not exist
531570
- keeping stale Nginx configs pointing at old deploy paths after migrating to a new directory structure
532571
- mixing up `path` (remote destination) and `source` (local directory) — they serve different purposes
533-
- using `pm2 restart` without `--update-env` after changing ecosystem file variables
572+
- treating the root workspace id as the deployable app id
573+
- forgetting to add `frontend_app_id` on `[[projects]]` entries once the API exposes it
574+
- using `pm2 restart` without `--update-env` after changing ecosystem file variables in older restart-in-place workflows
534575
- assuming the CLI generates Nginx configs — it does not; Nginx is always configured manually on the server

.agents/skills/smbcloud-deploy-nextjs/SKILL.md

Lines changed: 52 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ Use this skill when work touches Next.js deployment on smbCloud.
99

1010
Applies to:
1111

12-
- `kind = "nextjs-ssr"` projects
12+
- `kind = "nextjs-ssr"` deploy targets
1313
- `.smb/config.toml` deploy configuration
1414
- `process_deploy_nextjs_ssr.rs`
1515
- PM2 process management for standalone builds
@@ -18,6 +18,15 @@ Applies to:
1818

1919
## Deployment model
2020

21+
Keep the deploy hierarchy straight:
22+
23+
- `Project` = umbrella workspace in smbCloud (not deployable)
24+
- `DeployRepo` = the git repository inside that workspace
25+
- `FrontendApp` = the deployable app unit, identified by `frontend_app_id` in config
26+
- `Deployment` = one release event, linked to a FrontendApp via `frontend_app_id`
27+
28+
The canonical chain is: Deployment → FrontendApp → DeployRepo → Project
29+
2130
For `nextjs-ssr`, smbCloud does not use git-push deploy.
2231

2332
The CLI route in `process_deploy.rs` short-circuits on:
@@ -62,20 +71,28 @@ Minimum `.smb/config.toml` for SSR:
6271

6372
```toml
6473
[project]
74+
id = 28 # workspace id (for API routing)
75+
frontend_app_id = "uuid" # FrontendApp id (for deployment tracking)
76+
deploy_repo_id = 123 # DeployRepo id (links to git repository record)
6577
deployment_method = 1
6678
kind = "nextjs-ssr"
6779
source = "."
6880
package_manager = "pnpm"
6981
pm2_app = "my-app"
7082
path = "apps/web/my-app"
83+
port = 3028
7184
```
7285

7386
Important rules:
7487

7588
- `kind = "nextjs-ssr"` is what activates the SSR deploy path
89+
- `id` is the umbrella workspace ID, used for auth and API routing
90+
- `frontend_app_id` identifies the FrontendApp record for this deploy target — include it so deployments are attributed to the correct app, not just the workspace
91+
- `deploy_repo_id` identifies the DeployRepo record backing this app — include it when available
7692
- `source` is the local Next.js app directory
7793
- `path` is the remote destination directory on the server
7894
- `pm2_app` is mandatory because the CLI restarts PM2 by name
95+
- `port` should be set explicitly and must match nginx
7996
- `deployment_method = 1` may still be present, but the `kind` routing is what matters most
8097

8198
## Expected remote layout
@@ -90,15 +107,23 @@ Note that `server.js` is expected at the remote root because the CLI uploads the
90107

91108
## PM2 behavior
92109

93-
The CLI's deploy path runs logic equivalent to:
110+
The CLI's deploy path now deletes and starts fresh, preferring the server's
111+
`ecosystem.config.js` when it exists.
112+
113+
Conceptually it behaves like:
94114

95115
```sh
96116
cd "$APP_PATH"
117+
mkdir -p logs
97118

98119
if pm2 describe "$PM2_APP" > /dev/null 2>&1; then
99-
pm2 restart "$PM2_APP"
120+
pm2 delete "$PM2_APP"
121+
fi
122+
123+
if [ -f ecosystem.config.js ]; then
124+
pm2 start ecosystem.config.js --only "$PM2_APP" --env production
100125
else
101-
PORT=3010 HOSTNAME=127.0.0.1 pm2 start node --name "$PM2_APP" -- server.js
126+
NODE_ENV=production PORT=<port> HOSTNAME=127.0.0.1 pm2 start node --name "$PM2_APP" -- server.js
102127
fi
103128

104129
pm2 save
@@ -107,7 +132,8 @@ pm2 save
107132
This has two consequences:
108133

109134
- old git-based `post-receive` hooks are irrelevant for the SSR path
110-
- the port is currently hardcoded to `3010` in the CLI for fresh starts unless patched
135+
- the configured `port` in `.smb/config.toml` is used for the fallback start path
136+
- the live server `ecosystem.config.js` is the runtime source of truth when present
111137

112138
### Environment variables — ecosystem file (standard pattern)
113139

@@ -141,7 +167,7 @@ The flow works like this:
141167
pm2 save
142168
```
143169
144-
2. Every subsequent CLI deploy runs `pm2 restart <pm2_app>`. Because `pm2 save` already persisted the env, PM2 restarts with the same environment — **the CLI does not need to know about the ecosystem file at all**.
170+
2. Every subsequent CLI deploy deletes the PM2 process and starts it fresh. If the server has `ecosystem.config.js`, the CLI starts PM2 from that file again. If not, it falls back to `node server.js` with inline `NODE_ENV`, `PORT`, and `HOSTNAME`.
145171
146172
3. To change or add env vars, SSH in, edit `ecosystem.config.js`, then:
147173
@@ -150,23 +176,26 @@ The flow works like this:
150176
pm2 save
151177
```
152178
153-
The ecosystem file is the server-side source of truth for each app’s runtime config. Future versions of the smbCloud web console will provide a UI to manage these vars per app, which will write to this file and trigger the restart remotely.
179+
The ecosystem file is the server-side source of truth for each app's runtime config. The CLI preserves the server copy during rsync and must not delete it.
154180
155181
### Manual PM2 operations (without ecosystem file)
156182
157-
If the app is already deployed and `server.js` exists:
183+
If you are operating without an ecosystem file, prefer the same fresh-start model the CLI uses:
158184
159185
```sh
160186
cd /home/git/apps/web/<app>
161-
pm2 restart <pm2_app> --update-env
187+
if pm2 describe <pm2_app> > /dev/null 2>&1; then
188+
pm2 delete <pm2_app>
189+
fi
190+
PORT=<port> HOSTNAME=127.0.0.1 pm2 start node --name <pm2_app> -- server.js
162191
pm2 save
163192
```
164193
165-
If starting from scratch without an ecosystem file:
194+
If you are using an ecosystem file and only changed env vars manually, this is still valid:
166195
167196
```sh
168197
cd /home/git/apps/web/<app>
169-
PORT=<port> HOSTNAME=127.0.0.1 pm2 start node --name <pm2_app> -- server.js
198+
pm2 restart <pm2_app> --update-env
170199
pm2 save
171200
```
172201
@@ -181,9 +210,11 @@ If an older `ecosystem.config.js` exists and contains both:
181210
182211
that is a broken configuration. Standardize on one port value.
183212
184-
For the current SSR deploy code, the real runtime is controlled by the environment passed to `node server.js`, not by `next start` or an ecosystem file.
213+
Current rules:
185214
186-
If the app must run on a port other than `3010`, patch the CLI or restart PM2 manually with the correct port.
215+
- fallback fresh-start path uses `port` from `.smb/config.toml`
216+
- ecosystem-managed apps should set the same port in `env_production.PORT`
217+
- nginx upstream must match that port
187218
188219
## Nginx for SSR
189220
@@ -253,12 +284,12 @@ If the project has no `public/` directory, deploy may fail. Either:
253284
254285
Cause:
255286
256-
- CLI hardcodes `PORT=3010` for fresh starts
287+
- `port` in `.smb/config.toml`, server `ecosystem.config.js`, and nginx upstream disagree
257288
258289
Fix:
259290
260-
- manually restart with the correct port
261-
- or patch the CLI to read a configurable port from `.smb/config.toml`
291+
- standardize the same port in all three places
292+
- if using ecosystem config, make sure PM2 is actually starting from that file
262293
263294
### Misleading deploy output
264295
@@ -325,6 +356,9 @@ Use the smallest checks that prove the contract.
325356
- forgetting `kind = "nextjs-ssr"`
326357
- keeping an old git `post-receive` hook and expecting it to manage SSR deploys
327358
- using static-file Nginx config for an SSR app
328-
- editing `ecosystem.config.js` but forgetting `pm2 restart <app> --update-env && pm2 save` — PM2 keeps the old env until explicitly refreshed
329-
- leaving port values inconsistent across PM2, Nginx, and CLI
359+
- editing the repo copy of `ecosystem.config.js` and expecting a normal CLI deploy to overwrite the server copy — it is preserved intentionally
360+
- editing the server `ecosystem.config.js` but the deploy script falling back to inline `node server.js` because the file path check is wrong
361+
- leaving port values inconsistent across PM2, Nginx, and `.smb/config.toml`
362+
- forgetting to set `frontend_app_id` on the deploy target once app-level deployment tracking is available
330363
- treating browser CORS failures as real internet outages
364+
- deploying without `frontend_app_id` in config — the API will try to infer the app, but explicit is better than implicit

0 commit comments

Comments
 (0)