Skip to content

feat: native Alpine Linux (OpenRC/busybox) support#23

Merged
the-bokya merged 40 commits into
frappe:mainfrom
the-bokya:alpine-support
Jun 22, 2026
Merged

feat: native Alpine Linux (OpenRC/busybox) support#23
the-bokya merged 40 commits into
frappe:mainfrom
the-bokya:alpine-support

Conversation

@the-bokya

@the-bokya the-bokya commented Jun 5, 2026

Copy link
Copy Markdown
Member

This PR adds native support for Alpine Linux to bench-cli. Alpine uses OpenRC instead of systemd, apk instead of apt, and busybox sh -- all of which differ from the Debian/Ubuntu-centric defaults.

Summary of changes

Platform detection & base tooling

  • New platform.py helper that detects Alpine, exposes apk as a package manager, and provides OpenRC service utilities
  • POSIX-compatible install.sh that bootstraps the environment on Alpine (installs python3, curl, etc. via apk)

Process manager parity

  • New openrc_process_manager.py -- a full OpenRC backend equivalent to the systemd/supervisor process managers
  • Factory and reader logic updated so the admin UI and CLI correctly query/control services via OpenRC on Alpine
  • Status, start, stop, restart, and dev-mode messages all reflect OpenRC service names

MariaDB

  • Dedicated per-bench MariaDB instances on Alpine with OpenRC service control
  • Proper init, socket handling (binds via db_socket), and log directory setup
  • Fix: install python3-dev so C-extension wheels (e.g. mysqlclient) compile on Alpine

Nginx

  • Config path auto-detected for Alpine (/etc/nginx) rather than assuming Debian paths
  • Proper default-vhost removal scoped to Alpine only
  • Privilege-aware service reload via OpenRC

Let's Encrypt

  • Reload hook updated to use rc-service nginx reload on Alpine instead of systemctl

Volumes & ZFS

  • Volume manager discovers Alpine block devices and checks for ZFS kernel module, falling back to installing zfs packages with a clear error if unavailable

Installer & init

  • Alpine build dependencies installed automatically (gcc, musl-dev, etc.) during init
  • python3-dev pulled in so uv/pip can compile native extensions

Admin UI

  • NewBenchDialog and SettingsModal reflect OpenRC as the process manager on Alpine
  • Setup page shows native process manager in config and status views

Tests

  • New tests for MariaDB manager (Alpine paths), volume discovery, and command-layer behavior

@the-bokya the-bokya changed the base branch from prod-setup to main June 7, 2026 20:15
the-bokya and others added 7 commits June 8, 2026 12:38
… helpers

Add is_alpine(), a _privileged() wrapper (skips sudo when already root, as
on Alpine images), and service_command/service_enable_command/service_running
helpers that dispatch to rc-service/rc-update on Alpine and systemctl elsewhere.
Add ApkPackageManager with canonical->apk package aliases, and route
get_package_manager() to it on Alpine.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
MariaDB: install mariadb/mariadb-client via apk, initialise the empty data
dir with mariadb-install-db and enable the service; start/stop via
service_command (rc-service on Alpine, systemctl elsewhere).
Node.js: install nodejs/npm via apk on Alpine instead of the nodesource apt repo.
nginx: default config dir to /etc/nginx/http.d on Alpine, start/enable the
service on first reload, and use _privileged for symlink + nginx -t so it works
as root without sudo.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add OpenRCProcessManager, which renders one supervise-daemon init script per
bench process into config/openrc/, symlinks them into /etc/init.d/, and enables
them in the default runlevel. Add a shared ProcessManager._split_command helper
to map Procfile entries onto init-script env/directory/command fields.

Wire 'openrc' through ProductionConfig, the ProcessManagerFactory (create +
detect_running), and the init / setup production dispatch.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
bench init installs the full musl build header set (linux-headers, libffi-dev,
openssl-dev, libxml2/xslt, image libs) plus bash and tzdata so frappe's wheels
compile and its runtime assumptions hold on Alpine.

install.sh is now POSIX sh (runs under Alpine's busybox ash with no bash
preinstalled), apk-installs its own base deps on Alpine, tolerates a
non-tracking clone on re-run, and writes PATH to ~/.profile for ash/sh shells.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Alpine ships /etc/nginx/http.d/default.conf with a 'listen 80/[::]:80
default_server' that returns 404 to block stray virtualhosts. It sits in the
same include dir bench uses and owns the IPv6 default_server, so it shadows the
bench site for IPv6 clients (e.g. curl localhost -> ::1). Remove it on Alpine so
the bench server block is the sole :80 listener.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
bench status hardcoded a systemd/supervisor label and shelled out to
'systemctl is-active' for nginx, which errors on Alpine (no systemd). Map the
process-manager label for openrc and probe service state via
platform.service_running (rc-service on Alpine, systemctl elsewhere).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@rmehta

rmehta commented Jun 20, 2026

Copy link
Copy Markdown
Member

hey can you re base and fix this? it has diverged too much?

seems straightforward, maybe merge it as well after fixing.

@the-bokya

Copy link
Copy Markdown
Member Author

hey can you re base and fix this? it has diverged too much?

seems straightforward, maybe merge it as well after fixing.

Okay. I'll pull and merge it myself tomorrow.

the-bokya and others added 14 commits June 21, 2026 16:06
Upstream advanced ~200 commits since the merge base (f3c67a0), heavily
refactoring the process managers, production setup, mariadb/redis, nginx,
and the CLI command structure. The seven Alpine/OpenRC commits were built
against the old tree and no longer apply.

This merge adopts upstream/main's tree wholesale as the new base; the
Alpine/OpenRC support is re-applied, adapted to the new abstractions, in
the small atomic commits that follow.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… helpers

Re-applies the Alpine platform layer on top of upstream's refactored
platform.py:

- is_alpine()/is_root() detection and a _privileged() helper that drops the
  sudo prefix when already root (Alpine commonly runs as root).
- service_command/service_enable_command/service_disable_command/
  service_running dispatch to rc-service/rc-update on Alpine, systemctl
  elsewhere — the single seam every manager goes through.
- default_nginx_config_dir() returns /etc/nginx/http.d on Alpine.
- A package-alias mechanism plus ApkPackageManager, selected by
  get_package_manager() on Alpine.

has_passwordless_sudo() now short-circuits to True when root and tolerates a
missing sudo binary, so the init pre-flight check passes on a root Alpine box.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…g dir

- VALID_PROCESS_MANAGERS gains "openrc", the Alpine counterpart of systemd.
- NginxConfig.config_dir defaults via platform.default_nginx_config_dir(), so
  Alpine benches write server blocks to /etc/nginx/http.d.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…visor

OpenRCProcessManager is the Alpine counterpart of SystemdProcessManager: it
generates one supervise-daemon-backed init script per process under
config/openrc/, symlinks them into /etc/init.d/, and enables them with
rc-update. supervise-daemon provides the keep-alive that systemd's
Restart=on-failure / supervisor's autorestart give elsewhere.

Like the other production managers it separates the workload from the admin
control plane, so:
- stop() stops only the workload; the admin keeps serving,
- stop_admin() stops the admin,
- setup_admin() brings up just the admin for the pre-init setup wizard,
- start()/restart()/is_running()/admin_is_running()/reload_web() mirror the
  systemd/supervisor semantics,
- remove_services() tears everything down for `bench remove production`.

OpenRC has no socket activation, so the admin runs as a plain supervised
Flask process on admin.port (the dev/supervisor definition); nginx proxies
straight to it. generate_config() regenerates the redis and gunicorn configs
just like the other managers, so upgrades self-heal.

Wired into ProcessManagerFactory.create/detect_running and the explicit
manager selection in `bench start`.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Route MariaDB through the platform service helpers and an Alpine install path:

- install() on Alpine apk-installs mariadb + mariadb-client (no MariaDB apt
  repo there), runs mariadb-install-db to seed the empty datadir, and enables
  the service — Alpine's package does none of that automatically. Idempotent.
- start/stop/service_is_active/stop_shared go through service_command /
  service_running / service_disable_command, so they speak rc-service on
  Alpine and systemctl elsewhere.
- secure_installation's superuser mariadb client runs via _privileged, so it
  works as root on Alpine and via sudo otherwise.

Dedicated mariadb@<instance> deployments stay systemd-only (they rely on the
mariadb@.service template); Alpine benches use the shared server, like macOS.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- reload() enables nginx and starts it the first time on Alpine (the package
  doesn't auto-start), then reloads in place; uses service_command, so it's
  rc-service on Alpine and systemctl on other Linux. macOS still uses
  `nginx -s reload`.
- The catch-all default_server and its error pages install into the distro's
  include dir (http.d on Alpine, conf.d on Debian) via default_nginx_config_dir.
- The stock default vhost that would conflict with our default_server is now
  removed on both distros — Debian's sites-enabled/default and Alpine's
  http.d/default.conf.
- All privileged file ops (ln/unlink/cp/install/rm/test) go through _privileged
  so they run sudo-less as root on Alpine and via sudo otherwise.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- python_env_manager installs Node via apk (nodejs + npm) on Alpine; the
  nodesource setup ships no musl builds.
- letsencrypt_manager's certbot --deploy-hook reloads nginx with rc-service on
  Alpine, systemctl elsewhere, and all privileged certbot/openssl/mkdir calls
  go through _privileged.
- volume.py stops/starts the shared mariadb via service_command instead of a
  hardcoded systemctl call.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…nches

- _install_system_packages installs the full Alpine build/runtime header set
  (musl has no manylinux wheels) instead of the Debian build-essential set.
- _setup_process_manager handles openrc (install + enable the init scripts)
  and registers an openrc rollback that tears the services back down on a
  failed init.
- _remove_nginx_symlink rolls back via _privileged so it works as root.
- `bench new` skips the dedicated mariadb@<name> instance on Alpine (no
  systemd template units), falling back to the shared server like macOS.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- `bench setup production` accepts --process-manager openrc, defaults to
  openrc on Alpine (systemd elsewhere), deploys via _setup_openrc, and detects
  an existing openrc deployment for migration. _installed_manager probes only
  OpenRC on Alpine (the systemd/supervisor CLIs aren't there).
- `bench remove production` tears down an openrc deployment via remove_services.
- `bench status` reports service state through platform.service_running, so the
  Nginx line works without systemctl on Alpine.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- /api/benches/new validates the process manager against VALID_PROCESS_MANAGERS
  (so openrc is accepted) and, on Alpine, deploys with OpenRC regardless of the
  UI's systemd/supervisor choice — neither exists there, and the prebuilt UI
  can't offer openrc.
- The new-bench admin bring-up picks OpenRCProcessManager when configured.
- ProcessReader reads OpenRC service state (rc-service + /run/<svc>.pid) so the
  monitor page shows live processes for openrc benches instead of falling back
  to the empty dev pid reader.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…penRC

install.sh:
- POSIX sh (#!/bin/sh) with no bashisms ([[ ]] -> case, source -> ., the
  password read uses stty instead of `read -s`), so a bare Alpine box can
  bootstrap via `wget -qO- ... | sh` before bash exists.
- Alpine bootstrap: apk-installs git, curl, bash, sudo, shadow (for
  useradd/usermod/visudo), python3 + build deps up front.
- ~/.profile gets the PATH entry for ash/sh login shells.

docs/README:
- Document Alpine support, the busybox bootstrap, and OpenRC as the Alpine
  process manager across README, production.md, config.md and commands.md.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
rc-service lives in /sbin, which a non-root login PATH omits, and it reads
root-owned run state. service_running() ran it bare, so as the bench user every
rc-service call was "command not found" and every service looked stopped —
`bench status` reported a running production bench as down. Route the status
probe through _privileged like the start/stop/enable calls.

Found while testing the OpenRC production flow end-to-end on Alpine.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Narrow pid before computing stats so the type checker is satisfied and the
intent (no stats for a stopped/pidless service) is explicit.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
_stock_default_sites only added http.d/default.conf for Alpine; on Debian
default_nginx_config_dir() is conf.d, so the prior version would have removed
/etc/nginx/conf.d/default.conf where upstream removed only sites-enabled/default.
Keep non-Alpine behaviour identical to upstream.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@the-bokya the-bokya marked this pull request as draft June 21, 2026 11:30
the-bokya and others added 4 commits June 21, 2026 23:02
Single source of truth for the host's production init system: OpenRC on
Alpine, systemd elsewhere. Lets the UI and deploy paths offer the right
default per platform instead of assuming systemd.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Saving production.process_manager=openrc was rejected (only none/supervisor/
systemd were accepted) and a settings change that needs a restart did nothing
under OpenRC. Accept the full set of valid managers, restart the OpenRC
workload via the manager, and surface the host's native manager in the
settings response so the UI can offer it.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The setup wizard (/api/setup/config) and the new-bench dialog (/api/status)
now report the host's native process manager so the UI defaults to OpenRC on
Alpine. The Alpine coercion in /api/benches/new becomes a safety net for
stale clients (only a stray systemd request is rewritten).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The setup wizard, new-bench dialog and settings modal hardcoded Systemd as
the recommended manager, so Alpine users were shown an unavailable option.
Each now reads native_process_manager from the backend and offers OpenRC
(recommended) on Alpine, systemd elsewhere, with supervisor as the
cross-platform alternative — full UI parity with systemd.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
the-bokya and others added 5 commits June 21, 2026 23:20
systemd benches get their own mariadb@<name> instance; Alpine benches were
forced onto the shared server because provision_instance was systemd-only
(mariadb@.service template, systemd drop-in, Debian conf paths). The wizard
still offered 'Dedicated (recommended)' on Alpine, so choosing it failed at
init.

Add an OpenRC provisioning path: a supervise-daemon init script runs a second
mariadbd with the bench's datadir/socket/port (explicit flags override the
shared [mariadbd] group, so no fragile include ordering). service_unit() now
yields mariadb-<name> on Alpine, _install_alpine skips shared setup for a
dedicated bench, and 'bench new' provisions a per-bench instance on Alpine too
— full parity with systemd. Only macOS stays on the shared server.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Dedicated MariaDB now works on Alpine too (mariadb-<instance> under OpenRC),
and the admin Process Manager dropdown offers the host's native manager
(OpenRC on Alpine). Update config.md, admin.md and the README so the docs match.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
bench init compiled frappe's wheels against the system python that `uv venv`
reuses, but never installed its dev headers — so mysqlclient (and any C
extension) failed with 'Python.h: No such file or directory' on a clean box.
It only worked when install.sh's bootstrap had already pulled python3-dev in.

Add python3-dev to the Alpine build deps and the Debian set so init is
self-sufficient regardless of how the host was bootstrapped.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… module

Volume setup hardcoded the Debian package name 'zfsutils-linux', so enabling
volumes on Alpine crashed with a raw 'no such package' apk error. Alpine splits
ZFS across 'zfs' (userland), 'zfs-openrc' (import/mount services) and a
per-kernel module package 'zfs-<flavor>' (zfs-lts, zfs-virt, …).

Add an Alpine path that installs those, loads the module and enables the boot
services, and verifies ZFS is actually usable — raising a clear, actionable
VolumeError (boot a ZFS-capable kernel or disable volumes) when the module
can't load, instead of crashing. Debian still uses zfsutils-linux.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@the-bokya the-bokya changed the title feat: Alpine feat: native Alpine Linux (OpenRC/busybox) support Jun 21, 2026
@the-bokya the-bokya marked this pull request as ready for review June 21, 2026 20:26
@the-bokya the-bokya requested review from Copilot and rmehta June 21, 2026 20:26
@the-bokya

Copy link
Copy Markdown
Member Author

@rmehta more or less done.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds first-class Alpine Linux support to bench-cli by introducing Alpine detection, apk package management, OpenRC service control, and an OpenRC-backed production process manager—plus aligned MariaDB/Nginx/Let’s Encrypt/ZFS behavior and admin UI updates.

Changes:

  • Introduces bench_cli.platform helpers for Alpine detection, apk package management, and init/service utilities (OpenRC vs systemd).
  • Adds OpenRCProcessManager and routes production setup/start/restart/status/admin UI to the correct native process manager per host.
  • Extends Alpine-specific behavior for ZFS installation checks, per-bench MariaDB instances, nginx config paths, and certbot reload hooks; adds regression tests.

Reviewed changes

Copilot reviewed 34 out of 34 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
tests/test_volume_discovery.py Adds tests for Alpine ZFS install routing and kernel flavor parsing.
tests/test_mariadb_manager.py Adds tests for Alpine/OpenRC MariaDB instance naming and init script generation.
tests/test_commands.py Adds regression test ensuring Alpine build deps include python3-dev and Alpine dedicated MariaDB defaults.
README.md Documents Alpine requirements and installer bootstrap via `wget
install.sh Switches installer to /bin/sh and adds Alpine bootstrap + POSIX-ish adjustments.
docs/production.md Documents openrc as a production process manager and describes OpenRC behavior.
docs/config.md Updates config docs to include openrc and Alpine MariaDB instance semantics.
docs/commands.md Extends bench setup production --process-manager docs to include openrc.
docs/admin.md Updates admin/settings behavior docs to include OpenRC/native process manager selection.
bench_cli/platform.py Adds is_alpine, OpenRC/systemd service helpers, apk package manager, nginx config dir defaulting.
bench_cli/managers/volume_manager.py Adds Alpine ZFS install path and kernel/module usability checks.
bench_cli/managers/python_env_manager.py Uses Alpine nodejs/npm packages (no NodeSource musl builds).
bench_cli/managers/process_manager.py Adds factory support for OpenRCProcessManager.
bench_cli/managers/openrc_process_manager.py New OpenRC backend: generates supervise-daemon init scripts and manages lifecycle.
bench_cli/managers/nginx_manager.py Uses distro-specific nginx include dir; Alpine service enable/start/reload via OpenRC.
bench_cli/managers/mariadb_manager.py Adds Alpine install/init logic and OpenRC per-bench MariaDB instance provisioning.
bench_cli/managers/letsencrypt_manager.py Uses OpenRC nginx reload hook on Alpine; privileges certbot/openssl invocations via _privileged.
bench_cli/config/production_config.py Adds openrc to valid production process managers.
bench_cli/config/nginx_config.py Defaults nginx config dir via platform-aware helper.
bench_cli/commands/volume.py Uses platform service helpers to start/stop MariaDB.
bench_cli/commands/status.py Uses platform service detection for service status display.
bench_cli/commands/start.py Adds OpenRC branch for production start path.
bench_cli/commands/setup/production.py Supports openrc in production setup and uses Alpine-specific defaults/probing.
bench_cli/commands/restart.py Updates restart messaging to include OpenRC.
bench_cli/commands/remove/production.py Removes OpenRC services when removing production.
bench_cli/commands/new.py Clarifies Linux per-bench MariaDB instance behavior incl. Alpine OpenRC naming.
bench_cli/commands/init.py Adds Alpine build deps and OpenRC service teardown hook on rollback.
admin/frontend/src/pages/Setup.vue Wizard now uses backend-provided native_process_manager to offer correct defaults.
admin/frontend/src/components/SettingsModal.vue Settings modal now shows native process manager option per host (systemd/OpenRC).
admin/frontend/src/components/NewBenchDialog.vue New-bench dialog now defaults to host-native process manager.
admin/backend/views/setup.py Exposes native_process_manager to setup wizard defaults.
admin/backend/views/settings.py Validates openrc, exposes native_process_manager, restarts OpenRC workload safely.
admin/backend/readers/process_reader.py Adds OpenRC process reading path using service status + pidfiles.
admin/backend/app.py Validates openrc, coerces stale systemd requests on Alpine, and sets up admin via correct PM.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread install.sh
Comment on lines +1 to +6
#!/bin/sh
set -e

# POSIX sh (not bash) so a bare Alpine box can bootstrap via `wget -qO- ... | sh`
# before bash exists. The Ubuntu one-liner still pipes to bash explicitly.

Comment thread install.sh Outdated
@@ -123,7 +138,10 @@ authenticate_sudo() {
local pass
Comment thread bench_cli/managers/volume_manager.py Outdated
Comment on lines +325 to +330
flavor = self._alpine_kernel_flavor()
if flavor:
try:
pkg.install(f"zfs-{flavor}")
except Exception:
pass # no matching module package — caught by the usability check
Comment thread docs/production.md Outdated
- The Alpine counterpart of systemd. Writes one `supervise-daemon` init script per process to `config/openrc/`, symlinks them into `/etc/init.d/`, and enables them with `rc-update`.
- `supervise-daemon` keeps each process alive (the equivalent of systemd's `Restart=on-failure`).
- The admin runs as a plain supervised Flask process on `admin.port` (no socket activation); nginx proxies straight to it. The workload and admin are separate services, so `bench stop` stops the workload while the admin keeps serving.
- Selected automatically on Alpine; `mariadb` stays on the shared system server (no `mariadb@.service` template units).
Comment thread bench_cli/managers/volume_manager.py Outdated
Comment on lines 285 to 286
if shutil.which("zfs") and (not is_alpine() or self._zfs_usable()):
return
Comment thread bench_cli/managers/volume_manager.py Outdated
Comment on lines +337 to +338
if not (shutil.which("zfs") and self._zfs_usable()):
raise VolumeError(

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 35 out of 35 changed files in this pull request and generated 5 comments.

Comment thread install.sh
Comment on lines +43 to +48
if [ -f /etc/alpine-release ] && command -v apk >/dev/null 2>&1; then
echo "Alpine detected — installing base dependencies via apk..."
if [ "$(id -u)" -eq 0 ]; then APK_SUDO=""; else APK_SUDO="sudo"; fi
$APK_SUDO apk add --no-cache \
git curl bash sudo shadow python3 python3-dev build-base linux-headers tzdata
fi
Comment on lines +369 to +378
def _patch_zfs_install(monkeypatch, *, zpool_rc: int, installed: list) -> None:
pkg = SimpleNamespace(install=lambda *p: installed.extend(p))
monkeypatch.setattr(volume_manager, "get_package_manager", lambda: pkg)
monkeypatch.setattr(volume_manager, "service_enable_command", lambda s: ["rc-update", "add", s])
monkeypatch.setattr(volume_manager, "_privileged", lambda c: c)
monkeypatch.setattr(volume_manager.shutil, "which", lambda n: "/usr/sbin/zfs")
monkeypatch.setattr(
volume_manager.subprocess, "run",
lambda *a, **k: SimpleNamespace(returncode=zpool_rc, stdout=b"", stderr=b""),
)
Comment on lines +177 to +183
service = self._service_name(pd.name)
lines = [
"#!/sbin/openrc-run",
f"# {self.bench.config.name} {pd.name} — generated by bench, do not edit",
f'description="{self.bench.config.name} {pd.name}"',
"supervisor=supervise-daemon",
]
Comment on lines +88 to 92
default_pm = "openrc" if is_alpine() else "systemd"
pm = BenchConfig._normalize_process_manager(self._pm_arg or self.bench.config.production.process_manager) or default_pm
if pm not in VALID_PROCESS_MANAGERS:
raise BenchError(f"Invalid process manager '{pm}'. Must be one of {', '.join(VALID_PROCESS_MANAGERS)}.")
self.bench.config.production.process_manager = pm
Comment on lines 139 to 147
if "process_manager" in production:
from bench_cli.config.production_config import VALID_PROCESS_MANAGERS

process_manager = str(production["process_manager"])
if process_manager not in ("none", "supervisor", "systemd"):
return "process_manager must be none, supervisor, or systemd"
valid = ("none", *VALID_PROCESS_MANAGERS)
if process_manager not in valid:
return f"process_manager must be one of: {', '.join(valid)}"
pm = "" if process_manager == "none" else process_manager
self.config.production.process_manager = pm
the-bokya and others added 5 commits June 22, 2026 15:40
platform.which() calls shutil.which(name, path=...) with a keyword
argument. The test mocks used single-arg lambdas (lambda n: ...) that
don't accept keyword arguments, causing TypeError.

Signed-off-by: ayushschaudhari1904@gmail.com <ayushschaudhari1904@gmail.com>
The Alpine bootstrap set APK_SUDO="sudo" for any non-root user and ran it
immediately. On a bare Alpine box sudo isn't installed yet, so this failed
with a confusing `sudo: not found` before the later explicit sudo check.
Guard the apk call: if a non-root user has no sudo, point them at the root
bootstrap path (which installs sudo and prepares the bench user) and exit.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
`bench setup production --process-manager systemd` was accepted on Alpine and
proceeded into the systemd path, which shells out to systemctl (absent there)
and yields an unmanageable deployment. Reject it with a clear message pointing
to OpenRC, the native Alpine manager.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The settings endpoint accepted any VALID_PROCESS_MANAGERS value, including
systemd. On Alpine a client could persist production.process_manager =
"systemd", which breaks later because the systemd backend shells out to
systemctl (absent there). Coerce systemd to OpenRC on Alpine, matching the
new-bench endpoint.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
OpenRC init scripts are started as root, so supervise-daemon ran every bench
process (web, workers, scheduler, socketio, redis, node, admin) as root —
unlike the systemd (--user) and supervisor backends, which run the workload
unprivileged. That leaves root-owned files under the bench dir/logs and an
unnecessary privilege for the web/admin processes. Render command_user so
supervise-daemon drops to the bench user, matching the other managers.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
the-bokya and others added 4 commits June 22, 2026 15:43
shutil.which only searches the caller's PATH, so on Alpine (and other minimal
hosts) a non-root user whose PATH lacks /sbin /usr/sbin sees an installed ZFS
as "missing", triggering needless reinstalls/errors. Use platform.which, which
also searches the sbin dirs. Also drop the bare `except Exception: pass` around
the zfs-<flavor> module install so a real apk failure surfaces instead of being
mistaken for a missing kernel module later.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The script targets /bin/sh, but `local` is a bashism that errors under dash
(Debian/Ubuntu's /bin/sh) with `local: not found`. Replace the declarations in
write_sudoers, authenticate_sudo and add_to_path with plain assignments so the
installer runs under POSIX shells too.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The OpenRC section claimed MariaDB stays on the shared system server on Alpine,
but the Alpine path now provisions dedicated per-bench `mariadb-<instance>`
OpenRC services (see mariadb_manager). Update the doc to match.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@the-bokya the-bokya merged commit aad8e2c into frappe:main Jun 22, 2026
2 checks passed
@the-bokya the-bokya deleted the alpine-support branch June 22, 2026 11:03
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.

3 participants