Start the program at boot through fgc, so the dashboard still works
This commit is contained in:
parent
12b071c668
commit
53fa96fb54
|
|
@ -221,37 +221,53 @@ point at a different lockfile.
|
||||||
|
|
||||||
## 7. Install the systemd service
|
## 7. Install the systemd service
|
||||||
|
|
||||||
An **alternative** to section 6b, not a companion to it: systemd gives you start-at-boot and
|
`scripts/install-tower` does this (it asks first, and shows the unit it will
|
||||||
restart-on-crash, but no attachable dashboard — you get `journalctl` instead. Pick one. If you run both by
|
write). The unit is the template `scripts/fire-gimbal-control.service`, and it
|
||||||
accident the second one to start fails on the single-instance lock (section 6c), so the failure is loud
|
runs the program **through `scripts/fgc`, inside tmux** - so a tower started at
|
||||||
rather than a pair of processes corrupting each other's state.
|
boot still has its dashboard (`scripts/fgc attach`), and `fgc status` / `peek` /
|
||||||
|
`stop` work from any ssh session. It is not an alternative to the tmux workflow
|
||||||
|
any more; it is how that workflow starts at boot.
|
||||||
|
|
||||||
|
By hand, fill in `@USER@`, `@REPO@` and `@ARGS@` (normally `--init --start`) and:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
sudo cp scripts/fire-gimbal-control.service /etc/systemd/system/
|
sudo cp fire-gimbal-control.service /etc/systemd/system/
|
||||||
sudoedit /etc/systemd/system/fire-gimbal-control.service
|
sudo loginctl enable-linger ggs # NOT optional - see below
|
||||||
|
sudo systemctl daemon-reload
|
||||||
|
sudo systemctl enable fire-gimbal-control
|
||||||
```
|
```
|
||||||
|
|
||||||
Confirm/adjust these lines:
|
**Lingering is required.** Without it `/run/user/<uid>` does not exist until
|
||||||
|
someone logs in, so a program started at boot takes its single-instance lock in
|
||||||
|
`/tmp`; a later ssh session looks in `/run/user`, finds nothing, reports "not
|
||||||
|
running", and a second copy could start beside the first.
|
||||||
|
|
||||||
```ini
|
**`--init` homes the gimbal unattended at every boot.** That is what a tower in
|
||||||
User=ggs
|
service needs - the firmware keeps no position across a power cycle, so without
|
||||||
WorkingDirectory=/opt/fire_gimbal_control
|
it the gimbal sits un-homed and captures nothing - but it is a deliberate choice,
|
||||||
ExecStart=/opt/fire_gimbal_control/fire_gimbal_control --start
|
and the installer asks for the arguments.
|
||||||
Environment=FGC_CONFIG=/opt/fire_gimbal_control/config.ini
|
|
||||||
EnvironmentFile=/etc/fire_gimbal_control/credentials.env
|
|
||||||
```
|
|
||||||
|
|
||||||
(Use `ExecStart=... --init --start` for the first power-on if endstops must be found at boot.)
|
MQTT credentials go in `/etc/fire_gimbal_control/credentials.env`
|
||||||
|
(`FGC_MQTT_USER=` / `FGC_MQTT_PW=`, mode 0640 root:ggs). `fgc` reads it inside
|
||||||
|
the tmux pane rather than taking it from the unit's environment: a tmux server
|
||||||
|
that already exists hands a new session its own environment, not the caller's.
|
||||||
|
|
||||||
## 8. Enable and verify
|
## 8. Enable and verify
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
sudo systemctl daemon-reload
|
sudo systemctl start fire-gimbal-control
|
||||||
sudo systemctl enable --now fire-gimbal-control
|
scripts/fgc status # running, lockfile under /run/user/<uid>
|
||||||
systemctl status fire-gimbal-control
|
scripts/install-tower --verify # every check, including test hardware
|
||||||
journalctl -u fire-gimbal-control -f # live logs
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
`[verified]` on tower 121, 2026-09-23: the service came up by itself after a
|
||||||
|
reboot with nobody logged in, with its lock under `/run/user/1000`; `fgc status`
|
||||||
|
and the dashboard worked from a later ssh session; credentials reached the
|
||||||
|
program intact (a password with `$`, a quote and a space); `systemctl stop` shut
|
||||||
|
it down cleanly; and `install-tower --verify` passed after the reboot, USB
|
||||||
|
preconditions included. 121 runs it with no arguments, so it does not home at
|
||||||
|
boot - see the note on `--init` above.
|
||||||
|
|
||||||
Confirm end to end:
|
Confirm end to end:
|
||||||
- Images accumulating under the configured `[Paths] output_dir` (default
|
- Images accumulating under the configured `[Paths] output_dir` (default
|
||||||
`~ggs/.local/share/fire_gimbal_control/images/RGB/`).
|
`~ggs/.local/share/fire_gimbal_control/images/RGB/`).
|
||||||
|
|
|
||||||
|
|
@ -954,5 +954,6 @@ checks that the two files load together, and changes nothing.
|
||||||
| Endstop survives homing | inspect the stop after a home; the axis must stall against it, not push through | ✅ at 189 mA, after failing at full torque |
|
| Endstop survives homing | inspect the stop after a home; the axis must stall against it, not push through | ✅ at 189 mA, after failing at full torque |
|
||||||
| Degree scale derived | startup logs `counts_per_deg` agreeing with the homed travel | ✅ 3552.87, saved with `;prev` history |
|
| Degree scale derived | startup logs `counts_per_deg` agreeing with the homed travel | ✅ 3552.87, saved with `;prev` history |
|
||||||
| Images land | `.jxl` files appear under the configured output dir | ❌ needs a capture run |
|
| Images land | `.jxl` files appear under the configured output dir | ❌ needs a capture run |
|
||||||
| Survives a disconnect | `scripts/fgc start`, close ssh, reconnect, `scripts/fgc status` | ❌ not attempted |
|
| Survives a disconnect | `scripts/fgc start`, close ssh, reconnect, `scripts/fgc status` | ✅ runs under the boot service, independent of any ssh session |
|
||||||
|
| Starts at boot | reboot with nobody logged in; `scripts/fgc status` from a later ssh | ✅ 2026-09-23, lock under `/run/user/1000` (lingering on) |
|
||||||
| Reports to a broker | topics arrive at the configured broker | ❌ no broker on this tower yet |
|
| Reports to a broker | topics arrive at the configured broker | ❌ no broker on this tower yet |
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,27 @@
|
||||||
# systemd unit template for Fire Gimbal Control.
|
# systemd unit for Fire Gimbal Control - a TEMPLATE. scripts/install-tower fills
|
||||||
# Copy to /etc/systemd/system/, adjust the paths/user, then:
|
# in the @...@ fields and installs it; to do it by hand, replace them and copy it
|
||||||
# sudo systemctl daemon-reload
|
# to /etc/systemd/system/, then:
|
||||||
# sudo systemctl enable --now fire-gimbal-control
|
# sudo loginctl enable-linger <user> # see below - not optional
|
||||||
|
# sudo systemctl daemon-reload && sudo systemctl enable fire-gimbal-control
|
||||||
#
|
#
|
||||||
# Credentials are passed via the environment (not the config file). Prefer an
|
# The unit runs the program through scripts/fgc, inside tmux, so a tower started
|
||||||
# EnvironmentFile with 0600 perms over inline Environment= lines.
|
# at boot still has the dashboard: `scripts/fgc attach` from any ssh session.
|
||||||
|
# fgc returns once the program is up, hence oneshot + RemainAfterExit; stopping
|
||||||
|
# the unit runs `fgc stop`, which shuts the camera and motors down cleanly.
|
||||||
|
#
|
||||||
|
# Lingering: without it /run/user/<uid> does not exist until someone logs in, so
|
||||||
|
# a program started at boot takes its single-instance lock in /tmp, and a later
|
||||||
|
# ssh session looks in /run/user, finds nothing, and `fgc status` says it is not
|
||||||
|
# running - while a second copy could start beside it.
|
||||||
|
#
|
||||||
|
# @ARGS@ is normally `--init --start`: the firmware keeps no position across a
|
||||||
|
# power cycle, so without --init the gimbal sits un-homed and captures nothing.
|
||||||
|
# --init also means the gimbal HOMES UNATTENDED at every boot.
|
||||||
|
#
|
||||||
|
# MQTT credentials: /etc/fire_gimbal_control/credentials.env (FGC_MQTT_USER,
|
||||||
|
# FGC_MQTT_PW), read by fgc inside the tmux pane rather than passed through this
|
||||||
|
# unit - a tmux server that already exists would not hand a new session the
|
||||||
|
# unit's environment.
|
||||||
|
|
||||||
[Unit]
|
[Unit]
|
||||||
Description=Fire Gimbal Control
|
Description=Fire Gimbal Control
|
||||||
|
|
@ -12,17 +29,14 @@ After=network-online.target
|
||||||
Wants=network-online.target
|
Wants=network-online.target
|
||||||
|
|
||||||
[Service]
|
[Service]
|
||||||
Type=simple
|
Type=oneshot
|
||||||
User=ggs
|
RemainAfterExit=yes
|
||||||
WorkingDirectory=/opt/fire_gimbal_control
|
User=@USER@
|
||||||
# --init is REQUIRED, not a convenience: the firmware keeps no state across a
|
WorkingDirectory=@REPO@
|
||||||
# power cycle, so both axes boot into RESET and refuse to move until homed. Without
|
ExecStart=@REPO@/scripts/fgc start @ARGS@
|
||||||
# it the service comes up and sits there, capturing nothing, with no obvious cause.
|
ExecStop=@REPO@/scripts/fgc stop
|
||||||
ExecStart=/opt/fire_gimbal_control/fire_gimbal_control --init --start
|
TimeoutStartSec=120
|
||||||
Environment=FGC_CONFIG=/opt/fire_gimbal_control/config.ini
|
TimeoutStopSec=60
|
||||||
# EnvironmentFile=/etc/fire_gimbal_control/credentials.env # FGC_MQTT_USER / FGC_MQTT_PW
|
|
||||||
Restart=on-failure
|
|
||||||
RestartSec=5
|
|
||||||
|
|
||||||
[Install]
|
[Install]
|
||||||
WantedBy=multi-user.target
|
WantedBy=multi-user.target
|
||||||
|
|
|
||||||
|
|
@ -621,34 +621,32 @@ fi
|
||||||
plan test run "test hardware (moves nothing)"
|
plan test run "test hardware (moves nothing)"
|
||||||
plan homing ask "asked at the end: home the gimbal and take a short capture"
|
plan homing ask "asked at the end: home the gimbal and take a short capture"
|
||||||
|
|
||||||
UNIT_TEXT="[Unit]
|
# The unit is scripts/fire-gimbal-control.service with its @...@ fields filled.
|
||||||
Description=Fire Gimbal Control
|
render_unit() {
|
||||||
After=network-online.target
|
sed -e "s|@USER@|$USER|g" -e "s|@REPO@|$REPO|g" -e "s|@ARGS@|$1|g" \
|
||||||
Wants=network-online.target
|
"$REPO/scripts/fire-gimbal-control.service"
|
||||||
|
}
|
||||||
[Service]
|
# What an installed unit starts the program with, so a re-run keeps it.
|
||||||
# fgc runs the program in a tmux session, so 'scripts/fgc attach' still opens
|
installed_args() {
|
||||||
# the dashboard. oneshot + RemainAfterExit: fgc start returns once it is up.
|
[[ -r $UNIT ]] && sed -n 's|^ExecStart=.*/scripts/fgc start *||p' "$UNIT" | head -1
|
||||||
Type=oneshot
|
}
|
||||||
RemainAfterExit=yes
|
UNIT_TEXT=""
|
||||||
User=$USER
|
SVC_ARGS_DEFAULT="--init --start"
|
||||||
WorkingDirectory=$REPO
|
[[ -r $UNIT ]] && SVC_ARGS_DEFAULT="$(installed_args)"
|
||||||
# --init homes at every start: the firmware keeps no position across a power cycle.
|
[[ -z $SVC_ARGS_DEFAULT ]] && SVC_ARGS_DEFAULT=none
|
||||||
ExecStart=$REPO/scripts/fgc start --init --start
|
if [[ -r $UNIT && $S_SVC_ENABLED == enabled && $S_LINGER == yes && -z ${ANS[service_args]+x} ]] &&
|
||||||
ExecStop=$REPO/scripts/fgc stop
|
[[ "$(cat "$UNIT")" == "$(render_unit "$(installed_args)")" ]]; then
|
||||||
TimeoutStartSec=120
|
plan service skip "installed and enabled (fgc start ${SVC_ARGS_DEFAULT/#none/})"
|
||||||
TimeoutStopSec=60
|
|
||||||
|
|
||||||
[Install]
|
|
||||||
WantedBy=multi-user.target"
|
|
||||||
if [[ -r $UNIT ]] && [[ "$(cat "$UNIT")" == "$UNIT_TEXT" ]] && [[ $S_SVC_ENABLED == enabled && $S_LINGER == yes ]]; then
|
|
||||||
plan service skip "installed and enabled"
|
|
||||||
else
|
else
|
||||||
echo
|
echo
|
||||||
echo "The boot service starts the program at every boot WITH --init: it homes the"
|
echo "The boot service starts the program at every boot. With --init it HOMES THE"
|
||||||
echo "gimbal unattended each time the tower powers up."
|
echo "GIMBAL UNATTENDED each time the tower powers up - which is what a tower in"
|
||||||
|
echo "service needs, since the firmware keeps no position across a power cycle."
|
||||||
if ask_yn service "Install and enable it?" y; then
|
if ask_yn service "Install and enable it?" y; then
|
||||||
plan service change "write $UNIT, enable it, enable lingering for $USER"
|
ask service_args "Arguments it starts the program with ('none' for none)" "$SVC_ARGS_DEFAULT"
|
||||||
|
svc_args="$REPLY_VAL"; [[ $svc_args == none ]] && svc_args=""
|
||||||
|
UNIT_TEXT="$(render_unit "$svc_args")"
|
||||||
|
plan service change "write $UNIT (fgc start $svc_args), enable it, enable lingering for $USER"
|
||||||
else plan service skip "declined"; fi
|
else plan service skip "declined"; fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|
@ -820,7 +818,9 @@ if [[ $TEST_RC != 0 ]]; then
|
||||||
elif ask_yn home "Someone is at the rig with a hand on the power - home now and take a short capture?" n; then
|
elif ask_yn home "Someone is at the rig with a hand on the power - home now and take a short capture?" n; then
|
||||||
stop_app
|
stop_app
|
||||||
before="$(latest_log)"
|
before="$(latest_log)"
|
||||||
if [[ $SVC_ON -eq 1 ]]; then sudo systemctl start fire-gimbal-control
|
# Through the service when the service is what homes it, so the tower ends
|
||||||
|
# up running under it; otherwise the same run by hand.
|
||||||
|
if [[ $SVC_ON -eq 1 && " $(installed_args) " == *" --init "* ]]; then sudo systemctl start fire-gimbal-control
|
||||||
else ( cd "$REPO" && "$FGC" start --init --start ) > /dev/null; fi
|
else ( cd "$REPO" && "$FGC" start --init --start ) > /dev/null; fi
|
||||||
say "homing (this can take a few minutes)"
|
say "homing (this can take a few minutes)"
|
||||||
log=""; for _ in $(seq 1 30); do log="$(latest_log)"; [[ -n $log && $log != "$before" ]] && break; sleep 1; done
|
log=""; for _ in $(seq 1 30); do log="$(latest_log)"; [[ -n $log && $log != "$before" ]] && break; sleep 1; done
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue