Skip to content

Autostart via systemd units instead of /etc/rc.local - #32

Open
kkaazzuukkii4 wants to merge 1 commit into
Windermere-Technology:mainfrom
kkaazzuukkii4:fix/autostart-rc-local-shebang
Open

kkaazzuukkii4 wants to merge 1 commit into
Windermere-Technology:mainfrom
kkaazzuukkii4:fix/autostart-rc-local-shebang

Conversation

@kkaazzuukkii4

@kkaazzuukkii4 kkaazzuukkii4 commented Aug 12, 2026

Copy link
Copy Markdown

Problem

On Raspberry Pi OS Bookworm and later, the camera never starts after a reboot. fn_autostart appends to /etc/rc.local, which Debian 12+ no longer ships, so the file is created without a shebang and systemd cannot execute it (status=203/EXEC).

The first version of this PR added the missing shebang. @MichaIng pointed out that this is a dead end — Debian 14 removes rc-local.service entirely. He's right, so this replaces the mechanism instead.

Change

Two units written by fn_autostart: raspimjpeg.service (backend) and raspimjpeg-schedule.service (scheduler, BindsTo the backend).

  • /dev/shm is a tmpfs emptied on every boot, so the working directory is created by an ExecStartPre
  • The two sleep 4 delays are gone; ordering is a dependency, and Restart=on-failure covers a backend that isn't ready yet
  • fn_autostart strips any leftover #START RASPIMJPEG SECTION from /etc/rc.local, or an upgraded system starts the camera twice
  • fn_stop, start.sh and stop.sh go through systemd. killall php is dropped — it killed unrelated PHP processes
  • systemd only, no SysV fallback

ExecStart deliberately does not use the /usr/bin/raspimjpeg symlink: that wrapper backgrounds the process and exits, so Type=simple tears down the cgroup 1.269s after start (measured, status=0/SUCCESS, so Restart=on-failure never fires). Type=forking with its PIDFile fails differently. The reasoning for this and the other non-obvious choices is recorded in comments in the diff, so they don't get "tidied" back into bugs later.

Verification

Raspberry Pi 3 Model B / Bookworm 64-bit / Camera Module 3 — deliberately the slower board, so removing the sleeps is actually exercised. Units generated by the installer, then rebooted:

Result
Both services after reboot active, untouched
Process count main.py 1, schedule.php 1 — no duplicates
/dev/shm/mjpeg 777 www-data:www-data
Capture 4608x2592
Legacy rc.local block removed
stop.sh then start.sh 0 processes, then 1
systemd-analyze verify clean

Disclosure: investigated and written with the help of Claude and Codex. All testing was run on my own hardware and the output above is real, not generated.

@MichaIng

Copy link
Copy Markdown

Keep in mind that Debian 14 Forky (the respective systemd version), removes support for /etc/rc.local entirely, the rc-local.service won't exist anymore. It was deprecated a long time ago. The same applies to /etc/init.d services, if that comes to peoples' mind.

A future-prove autostart method should use an own systemd service, or it should be make clear that the SysV init system is required, which will exclude like 99% of relevant production systems.

rc.local is a dead end. Debian 14 (Forky) drops rc-local.service entirely, and
it has been deprecated for a long time before that, so patching the rc.local
path only buys a release or two.

Replace it with two units written by fn_autostart:

  raspimjpeg.service           the camera backend
  raspimjpeg-schedule.service  the scheduler, BindsTo the backend

/dev/shm is a tmpfs and is emptied on every boot, so the working directory is
created by an ExecStartPre. It needs the "+" prefix to run as root, since the
unit itself runs as www-data and could not chown otherwise. The two "sleep 4"
delays are gone; ordering is expressed as a dependency and Restart=on-failure
covers a backend that is not ready yet.

ExecStart invokes the Python entry point directly rather than the /usr/bin/
raspimjpeg symlink. That symlink points at a wrapper which backgrounds the
process and exits, so under Type=simple systemd considers the service finished
and tears down the cgroup about a second after boot. Type=forking with the
wrapper's PIDFile was tried too and is worse: the wrapper sleeps before writing
the pid file, so systemd races it and reads a stale pid, which breaks every
stop/start cycle after the first. The direct invocation is what the project
itself intends -- an identical, commented-out line already exists in this
installer. It also puts the backend's output in the journal instead of a
private log file.

fn_autostart strips any leftover "#START RASPIMJPEG SECTION" block from
/etc/rc.local, otherwise an upgraded system starts the camera twice. fn_stop
now stops the services through systemd before falling back to the pid file,
because SIGKILL on a running unit is a failure and Restart=on-failure would
resurrect the camera in the middle of an install. start.sh and stop.sh go
through systemctl for the same reason, and their "killall php" is dropped -- it
killed unrelated PHP processes and the scheduler is a managed service now.

Verified on Raspberry Pi 3 Model B / Raspberry Pi OS Bookworm 64-bit with
Camera Module 3, deliberately on the slower board so the removal of the sleeps
is exercised. After a reboot both services come up with exactly one process
each, /dev/shm/mjpeg has the right ownership, capture works, the legacy
rc.local block is gone, and stop.sh followed by start.sh leaves a single
process rather than a duplicate.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@kkaazzuukkii4
kkaazzuukkii4 force-pushed the fix/autostart-rc-local-shebang branch from 6ed9645 to 04ff216 Compare August 16, 2026 14:31
@kkaazzuukkii4 kkaazzuukkii4 changed the title Fix autostart on Bookworm and later: /etc/rc.local is created without a shebang Autostart via systemd units instead of /etc/rc.local Aug 16, 2026
@kkaazzuukkii4

Copy link
Copy Markdown
Author

@MichaIng You're right — patching the rc.local path would only have bought a release or two. Replaced the mechanism instead; the PR is updated.

Two units now, with /dev/shm/mjpeg created by an ExecStartPre since the tmpfs is emptied every boot, and the sleep 4 delays replaced by ordering plus Restart=on-failure. The installer strips any leftover rc.local block so upgrades don't start the camera twice, and start/stop go through systemctl instead of killall php. No SysV fallback, per your point.

One thing that isn't obvious from the diff, so I've put it in a comment there too: ExecStart skips the /usr/bin/raspimjpeg symlink, because that wrapper backgrounds the process and exits — under Type=simple the cgroup is torn down 1.269s after start, with status=0/SUCCESS so Restart=on-failure never fires. Type=forking with its PIDFile is worse; it writes the pid after a sleep, so systemd reads a stale one and every stop/start after the first fails.

Verified through reboots on a Pi 3 — the slower board, so the removed sleeps actually get exercised.

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.

2 participants