1 ---
2 name: smbcloud-deploy-rust
3 description: Use when deploying or debugging Rust services on smbCloud with `kind = "rust"`, which cross-compiles a Linux binary locally, uploads it with rsync, and restarts it over SSH (nohup by default, or a git-owned `systemctl --user` service when `process_manager = "systemd"`), with a health check and automatic rollback to the previous binary.
4 ---
5
6 # smbCloud Deploy Rust
7
8 Use this skill when work touches `kind = "rust"` deploy targets.
9
10 ## What `smb deploy` does
11
12 1. Resolves `process_manager` and `health_path` (a bad value fails before the build).
13 2. Builds `--release --bin <binary_name>` for `rust_target` (cargo, `cargo-zigbuild` or `cross`).
14 3. rsyncs the binary to `<path>/<binary>.new`.
15 4. Runs a remote script that copies the live binary to `<binary>.previous`, moves `.new` into place, restarts the service, and checks it.
16 5. If the check fails and `.previous` exists, restores it, restarts, prints the last 20 log lines, and exits non-zero. The deployment is marked `Failed`.
17
18 ## Config
19
20 ```toml
21 [project]
22 kind = "rust"
23 path = "apps/rest-api/my-service" # remote app directory
24 binary_name = "my-service" # optional; defaults to the Cargo package name
25 rust_target = "x86_64-unknown-linux-gnu" # optional
26 process_manager = "systemd" # optional; "nohup" (default) or "systemd"
27 port = 8080 # optional; used by the health check
28 health_path = "/health" # optional; must start with "/"
29 ```
30
31 - `process_manager`: unset, `""` and `"nohup"` keep the kill-and-nohup start. `"systemd"` restarts `<binary_name>.service`. Any other value is an error, so a typo cannot start a second process next to a supervised one.
32 - `health_path`: with `port` set, the script polls `http://127.0.0.1:<port><health_path>` with `curl -fsS` once a second for up to 30 seconds. Without both, the check is `systemctl --user is-active` after a short sleep (systemd) or `pidof` (nohup). Only letters, digits and `/ - _ . ~ %` are accepted.
33
34 ## Remote layout
35
36 ```
37 <path>/<binary> live binary
38 <path>/<binary>.new upload staging, moved into place by the deploy
39 <path>/<binary>.previous copy of the binary that was live before this deploy
40 <path>/<binary>.log nohup mode only
41 ```
42
43 ## Process manager: systemd --user
44
45 The deploy SSHes as the app user and runs `systemctl --user restart <binary_name>.service` with `XDG_RUNTIME_DIR=/run/user/$(id -u)`. No sudo. The unit is created out of band; a missing unit fails the deploy before the binary is swapped.
46
47 The graceful stop time is the unit's `TimeoutStopSec`. Set it above the longest stream the service holds.
48
49 Prerequisites, once per app:
50
51 1. Enable linger for the app user so the unit runs without a login and starts at boot: `sudo loginctl enable-linger <user>`.
52 2. Create the env file and the unit as that user:
53
54 ```sh
55 export XDG_RUNTIME_DIR="/run/user/$(id -u)"
56 APP=my-service # == binary_name == the unit name
57 APP_PATH=~/apps/rest-api/my-service # == `path` in .smb/config.toml
58 mkdir -p ~/.config/systemd/user
59
60 install -m 600 /dev/null ~/.config/systemd/user/$APP.env
61 # add the service's environment variables, one KEY=value per line
62
63 cat > ~/.config/systemd/user/$APP.service <<EOF2
64 [Unit]
65 Description=$APP
66 After=network-online.target
67
68 [Service]
69 Type=simple
70 WorkingDirectory=$APP_PATH
71 EnvironmentFile=%h/.config/systemd/user/$APP.env
72 ExecStart=$APP_PATH/$APP
73 Restart=on-failure
74 RestartSec=2
75 TimeoutStopSec=60
76
77 [Install]
78 WantedBy=default.target
79 EOF2
80
81 systemctl --user daemon-reload
82 ```
83
84 3. Set `process_manager = "systemd"` in `.smb/config.toml`.
85
86 ## Cutting over from nohup or a system unit
87
88 From a nohup process: stop it (`kill $(pidof <binary>)`), then `systemctl --user enable --now <binary>.service` and confirm `systemctl --user is-active`. Never run both; they fight over the port.
89
90 From a system unit (`/etc/systemd/system`): this mode does not use sudo, so move the service to a user unit first: stop and disable the system unit, create the user unit as above, enable it, then switch `process_manager`. Until then, keep deploying that service manually.
91
92 ## Troubleshooting
93
94 - `unit ... not found`: the user unit does not exist or `daemon-reload` was not run.
95 - `Failed to connect to bus`: linger is not enabled for the app user.
96 - Health check times out: check `journalctl --user -u <binary>.service -n 50`, and that `port` matches what the service binds.
97 - Rolled back: the previous binary is serving again; the failed build is gone from the server, fix and redeploy.
98 - No `.previous` on the first deploy, so a failed first deploy cannot roll back.