Skip to content

fix(freebsd): make the rc.d scripts start their services - #136

Merged
seitzbg merged 5 commits into
mainfrom
fix/freebsd-rcd
Oct 8, 2026
Merged

seitzbg merged 5 commits into
mainfrom
fix/freebsd-rcd

Conversation

@seitzbg

@seitzbg seitzbg commented Oct 8, 2026 •

Copy link
Copy Markdown
Owner

Problem

The contrib/freebsd rc.d scripts shipped in v2.2.0 could not start either service. There were three independent blockers, each confirmed by driving the scripts through the official FreeBSD 14.3 rc.subr with command execution intercepted:

  1. -t ${desc} was unquoted. desc="Heliograph hub" became -t Heliograph after rc.subr's eval, and hub became daemon(8)'s child program.
  2. ${name}_flags is rc.subr's reserved rc_flags, which is inserted between $command and $command_args. The hub's default smoked_flags=-downsample therefore went to /usr/sbin/daemon itself (illegal option -- d).
  3. ${name}_user=smoke makes rc.subr wrap the start in su -m smoke, so daemon(8) ran as smoke and could not create /var/run/<name>.pid, while the script also passed -u smoke.

There was also a fourth bug: : ${smoke_agent_spool:="…"} replaces an empty value too, so the documented smoke_agent_spool="" never disabled the spool.

The existing CI only ran sh -n, which cannot catch any of these.

Fix

This follows the ports convention used by sysutils/node_exporter and net-mgmt/prometheus2:

  • rc.subr's standard ${name}_user runs daemon(8) and the child unprivileged; there is no -u.
  • The supervisor pidfile (-P) lives in /var/run/<name>/, owned by the service user and created in start_precmd. The supervisor can create it and remove it on exit, and service stop signals the supervisor rather than the child, which -r would otherwise restart.
  • The process title and the syslog tag (-t/-T) are the single word ${name}. The default procname (/usr/sbin/daemon) matches the daemon: <name>[pid] title for status/stop.
  • Extra flags move to ${name}_args. A leftover ${name}_flags value from v2.2.0 is migrated to _args with a warning, so it no longer reaches daemon(8).
  • smoke_agent_spool="" and smoked_args="" now take effect: they use = rather than :=.
  • Unchanged: the DSN is passed via the SMOKED_DSN environment variable, never argv; the agent always gets an explicit -spool-dir=; the spool dir is created in precmd.

Emitted start line, before and after (rc.subr harness):

before: su -m smoke -c 'sh -c " /usr/sbin/daemon -downsample -r -S -P /var/run/smoked.pid -t Heliograph hub -u smoke /usr/local/bin/smoked ..."'
after:  su -m smoke -c 'sh -c " /usr/sbin/daemon  -r -T smoked -t smoked -P /var/run/smoked/smoked.pid /usr/local/bin/smoked -serve -addr 127.0.0.1:8087 -webdir /usr/local/share/heliograph/web -downsample"'

Real verification in CI

freebsd-test now also runs .github/scripts/freebsd-rc-test.sh inside the FreeBSD VM. It installs the binaries and rc.d scripts as the README describes, creates the smoke user, and checks through service(8):

  • smoked: onestart brings up /api/targets with the demo targets, and onestatus passes. The supervisor and the child both run as smoke, the child's argv is exact, and its output reaches syslog under the tag smoked.
  • Restart on crash: killing the child brings back a new one.
  • Leftover smoked_flags: onerestart with it set gives a new supervisor, the warning, and the flag on smoked.
  • DSN stays off argv: with a DSN set, procstat -e shows SMOKED_DSN in the child's environment and pgrep -f finds it on no command line.
  • smoked stop: after onestop, onestatus fails, no process is left, and the pidfile is gone.
  • smoke_agent: with a throwaway cert and an unreachable hub, the supervisor and child run as smoke, spool.lock is owned by smoke, argv and the syslog tag are right, smoke_agent_spool="" yields -spool-dir=, and onestop cleans up.

View guided diff Turn on auto-fix

The v2.2.0 contrib/freebsd scripts built a daemon(8) command line that
could never run:

- the unquoted two-word title (-t ${desc}) split after rc.subr's eval,
  so daemon(8) ran "hub" / "vantage" as the program;
- rc.subr passes ${name}_flags to $command, so smoked_flags
  (default -downsample) reached daemon(8), which rejected it;
- ${name}_user made rc.subr su to smoke, so the unprivileged daemon(8)
  could not create /var/run/<name>.pid, and it was also given -u smoke.

Follow the ports convention (sysutils/node_exporter,
net-mgmt/prometheus2): rc.subr runs daemon(8) as ${name}_user with no -u,
the supervisor pidfile (-P, so stop does not trigger a -r restart) lives
in a /var/run/<name>/ directory that user owns, made in start_precmd, and
the title and syslog tag are the single-word service name. Extra flags
move to smoked_args / smoke_agent_args; a leftover *_flags value is still
used, with a warning.

Also fix smoke_agent_spool="" (documented to disable the spool): the
":=" default replaced an empty value, so the agent always spooled.
smoked_args="" likewise now drops the default -downsample.

CI: the freebsd-test job now runs .github/scripts/freebsd-rc-test.sh in
its FreeBSD VM. It installs both scripts, drives start/status/restart/
stop through service(8) as the smoke account, probes smoked's API, and
checks process users, argv, the DSN-in-environment path, syslog tags,
daemon(8) restart-on-crash and pidfile cleanup.
@coderabbitai

coderabbitai Bot commented Oct 8, 2026 •

Copy link
Copy Markdown

Warning

Review limit reached

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Next included review available in 35 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used the included review currently available.

Learn how review limits work.

Review configuration:

⚙️ Run configuration
  • Configuration used: defaults
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 4e180cd4-383b-4422-9f09-54141e69b4ae
📥 Commits

Reviewing files that changed from the base of the PR and between 4da4fa9 and e0de0ac.

📒 Files selected for processing (8)
  • .github/scripts/freebsd-rc-test.sh
  • .github/workflows/ci.yml
  • AGENTS.md
  • CHANGELOG.md
  • README.md
  • contrib/freebsd/README.md
  • contrib/freebsd/smoke_agent
  • contrib/freebsd/smoked
  • Autopilot · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@seitzbg

seitzbg commented Oct 8, 2026

Copy link
Copy Markdown
Owner Author

Review of f660584 against main

The new restart-output capture can hang the FreeBSD CI job. No builds or tests were run on this host; runtime confirmation requires the throwaway FreeBSD VM.

Review comment:

  • [P1] Detach supervisor stdio before capturing restart output — .github/scripts/freebsd-rc-test.sh:88-88
    The smoked script starts FreeBSD daemon(8) without -f, so the long-lived supervisor retains its inherited stdout/stderr; -T redirects the child's output, not the supervisor's descriptors. This command substitution therefore keeps waiting for EOF on a pipe the supervisor still holds open, hanging freebsd-test until its timeout and blocking releases that require that job. Add -f to the daemon invocation or avoid capturing restart output through this pipe.

Without -f, daemon(8) passes noclose=1 to daemon(3), so the long-lived
supervisor keeps the stdout/stderr of whatever ran `service <name> start`.
A caller capturing that output (the CI rc test's $(service smoked
onerestart 2>&1), or any script/pipe) then blocks until the service stops.
-f points the supervisor's stdio at /dev/null; -T still sends the child's
output to syslog. Matches the node_exporter/prometheus ports.
@seitzbg

seitzbg commented Oct 8, 2026

Copy link
Copy Markdown
Owner Author

Codex P1 (freebsd-rc-test.sh:88, supervisor stdio keeps $(service smoked onerestart 2>&1) open): confirmed and fixed in 84b40f7. daemon(8) passes noclose = keep_fds_open to daemon(3), and that defaults to 1 unless -f is given (daemon.c case 'f': keep_fds_open = 0 → daemon(keep_cur_workdir, keep_fds_open)). Without -f, the supervisor therefore holds the caller's stdout/stderr until it exits. Both scripts now pass -f, as the node_exporter/prometheus ports do, and -T still sends the child's output to syslog. The start line is now daemon -f -r -T <name> -t <name> -P /var/run/<name>/<name>.pid .... The freebsd-test CI run on this push is the runtime proof.

@seitzbg
seitzbg merged commit 74c266f into main Oct 8, 2026
12 checks passed
@seitzbg
seitzbg deleted the fix/freebsd-rcd branch October 8, 2026 04:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant