This is a hobby project built to scratch my own itch. While I designed the architecture and heavily used AI (Claude) to speed up the boilerplate and implementation, the code has been thoroughly reviewed and tested on my own machine. It works for my workflow, but it is still an early release.
A local-first web UI for managing Portage from a browser on the same machine.
Designed for Gentoo systems in a local environment. Not intended to be exposed to the internet.
- Prerequisites
- Install
- First run
- Start at boot
- Update
- Uninstall
- Authentication and approval
- Local users and roles
- LAN access
- Configuration
- Features
- Screenshots
- Architecture
- Security hardening
- Development
- Logs
- Gentoo Linux
- Python 3.11+
openssl- OpenRC or systemd
eselect repository add arbor-overlay git https://github.com/gorecodes/arbor-overlay.git
emaint sync -r arbor-overlay
echo 'app-admin/arbor openrc' >> /etc/portage/package.use/arbor # or: systemd
emerge app-admin/arbor
bash /usr/share/arbor/setup.shAfter every package upgrade, run setup again:
bash /usr/share/arbor/setup.shThis keeps /etc/arbor assets and /var/lib/arbor permissions aligned with the current release (including local-auth DB ownership).
By default this installs the stable overlay version. If you want the live ebuild that tracks main:
echo '=app-admin/arbor-9999 **' >> /etc/portage/package.accept_keywords/arbor
emerge =app-admin/arbor-9999
bash /usr/share/arbor/setup.shgit clone https://github.com/gorecodes/Arbor
cd Arbor
sudo bash install.shThe installer will:
- Install the backend to
/usr/lib/arbor/ - Create a Python virtual environment with Arbor installed into it
- Install the Alpine frontend to
/usr/lib/arbor/frontend/ - Create
/usr/bin/arborand/usr/bin/arbor-daemon - Install OpenRC or systemd service files, depending on the detected init system
- Create the
arborsystem user - Enforce local auth mode in
/etc/arbor/arbor.env(ARBOR_AUTH_BACKEND=local) - Create
/etc/arbor/arbor.envif it does not already exist - Default Arbor to local-first plain HTTP (
ARBOR_TLS=0) - Generate an IPC key in
/etc/arbor/ipc.keyif one does not already exist
After script-based upgrades, run setup again to refresh runtime permissions:
sudo bash config/setup.shOpenRC:
rc-service arbor-daemon start
rc-service arbor startsystemd:
systemctl start arbor-daemon arborOpen http://localhost:8443 or http://127.0.0.1:8443 in your browser and sign in with the local owner username/password created during setup.
For a first install, keep Arbor on localhost until you are comfortable with the model: an authenticated local session unlocks root-backed package actions, and LAN exposure is still a deliberate tradeoff rather than the default.
OpenRC:
rc-update add arbor-daemon default
rc-update add arbor defaultsystemd:
systemctl enable arbor-daemon arboremaint sync -r arbor-overlay
emerge app-admin/arbor
bash /usr/share/arbor/setup.shFor the live ebuild instead:
emaint sync -r arbor-overlay
emerge =app-admin/arbor-9999
bash /usr/share/arbor/setup.shThen restart the services:
- OpenRC:
rc-service arbor-daemon restart && rc-service arbor restart - systemd:
systemctl restart arbor-daemon arbor
git pull
sudo bash install.shThen restart the services:
- OpenRC:
rc-service arbor-daemon restart && rc-service arbor restart - systemd:
systemctl restart arbor-daemon arbor
emerge --unmerge app-admin/arborThen stop and disable the services:
- OpenRC:
rc-service arbor stop && rc-service arbor-daemon stop && rc-update del arbor default && rc-update del arbor-daemon default - systemd:
systemctl stop arbor arbor-daemon && systemctl disable arbor-daemon arbor
userdel arborOpenRC:
rc-service arbor stop
rc-service arbor-daemon stop
rc-update del arbor default
rc-update del arbor-daemon default
rm -f /etc/init.d/arbor /etc/init.d/arbor-daemonsystemd:
systemctl stop arbor arbor-daemon
systemctl disable arbor arbor-daemon
rm -f /usr/lib/systemd/system/arbor.service /usr/lib/systemd/system/arbor-daemon.service
systemctl daemon-reloadrm -f /usr/bin/arbor /usr/bin/arbor-daemon /usr/bin/arbor-approve \
/usr/local/bin/arbor /usr/local/bin/arbor-daemon /usr/local/bin/arbor-approve
rm -rf /usr/lib/arbor
userdel arborConfiguration, runtime state, logs, and the persisted SQLite job history are not removed automatically:
rm -rf /etc/arbor /var/log/arbor /run/arbor /var/lib/arborOut of the box after a clean install: password-only login, and privileged actions (install, uninstall, sync, etc.) require a password re-prompt in the browser (step-up re-auth) before they start. No root shell needed.
Two independent knobs let you change this:
ARBOR_AUTH_MODE— what is required at login (password only, or password + TOTP)ARBOR_APPROVAL_MODE— what is required per privileged action after login
They are independent: you can have TOTP at login with CLI approval, or no TOTP with step-up re-auth, or any combination.
Important: ARBOR_AUTH_MODE=totp requires that TOTP has already been enabled from the Security page in the web UI, which generates /etc/arbor/totp.secret automatically. Do not add ARBOR_AUTH_MODE=totp to the config before doing this or Arbor will refuse to start.
Common /etc/arbor/arbor.env setups:
# Default: password login, browser step-up re-auth for privileged actions
ARBOR_TLS=0
ARBOR_APPROVAL_MODE=none
ARBOR_ALLOW_AUTO_APPROVAL=1
# Alternative: root-shell arbor-approve instead of browser re-prompt
# ARBOR_APPROVAL_MODE=cli
# (remove the two lines above)
# Add TOTP at login on top of either of the above:
# ARBOR_AUTH_MODE=totp
# ARBOR_TOTP_SECRET_FILE=/etc/arbor/totp.secret # created by the Security pageWhen ARBOR_AUTH_MODE=totp, Arbor requires a TOTP code during login in addition to username and password. Codes are generated by a standard TOTP app such as Google Authenticator, Aegis, or similar.
This is a login-time second factor. It is not a per-operation confirmation step.
TOTP starts disabled by default.
An authenticated owner enables or disables it from the Security page in the web UI:
- Open Security.
- Start TOTP enrollment.
- Scan the secret into your authenticator app.
- Confirm with the current 6-digit code.
- Sign in again with TOTP.
To disable TOTP, the owner must enter the current password and a fresh TOTP code in the same Security page. Arbor revokes existing sessions when login-time TOTP is enabled or disabled so the policy change takes effect cleanly.
Important: TOTP improves convenience for trusted local/LAN use, but it does not make Arbor suitable for internet exposure. The TOTP code is still a shared second factor for the whole instance, not a per-user hardware-bound proof.
After login, privileged operations follow ARBOR_APPROVAL_MODE:
ARBOR_APPROVAL_MODE=none(default): the authenticated session requires a password re-prompt in the browser (step-up, valid 120 s) before each privileged action.ARBOR_ALLOW_AUTO_APPROVAL=1must be set alongside this.ARBOR_APPROVAL_MODE=cli: the authenticated session needs root-shell confirmation viaarbor-approveinstead of the browser re-prompt.ARBOR_APPROVAL_MODE=totp: no longer supported. Refused at startup with a migration message; choosenoneorcli.
The browser prompts for the password before each privileged action. On success the action starts immediately — no root shell required.
This is the original shell-first model.
- Start the action in the browser as usual.
- Arbor creates a pending approval request and locks the UI.
- On a root shell, run
arbor-approve approve <request_id>. - The browser notices the approval and starts the action automatically.
arbor-approve list
arbor-approve approve <request_id>If you reject the prompt in arbor-approve, the request is cancelled and the browser unlocks immediately.
Arbor local auth supports three roles: owner, operator, viewer.
Examples:
# create additional users
arbor-auth create-user --username alice --role operator --password 'change-me-now'
arbor-auth create-user --username bob --role viewer --password 'change-me-now'
# list users
arbor-auth list-users
# change role
arbor-auth set-role --username bob --role operatorLAN access exists, but it is still not the recommended deployment mode. Arbor binds to loopback by default and should not be treated as an internet-facing service. Even with ARBOR_AUTH_MODE=totp, the TOTP secret is shared across the whole instance — useful as a login-time second factor for a trusted operator, not a substitute for a hardened multi-user internet auth model.
There are two supported patterns. The reverse-proxy pattern is the recommended one and the only one that works once Arbor refuses a public bind without TLS.
Arbor stays bound to 127.0.0.1 and a TLS-terminating front-end (Apache, Nginx, Caddy) faces the LAN. The front-end is responsible for the certificate, HSTS, and X-Forwarded-Proto: https so HSTS propagates through Arbor's response headers.
Apache example for https://casa.lan/ proxying to 127.0.0.1:8444:
<VirtualHost *:443>
ServerName casa.lan
SSLEngine on
SSLCertificateFile /etc/apache2/ssl/casa.lan-fullchain.pem
SSLCertificateKeyFile /etc/apache2/ssl/casa.lan-privkey.pem
ProxyPreserveHost On
RequestHeader set X-Forwarded-Proto "https"
ProxyPass /ws/ ws://127.0.0.1:8444/ws/
ProxyPassReverse /ws/ ws://127.0.0.1:8444/ws/
ProxyPass / http://127.0.0.1:8444/
ProxyPassReverse / http://127.0.0.1:8444/
</VirtualHost>Required Apache modules: mod_proxy, mod_proxy_http, mod_proxy_wstunnel, mod_headers, mod_ssl. ProxyPreserveHost On is essential — without it the Origin the browser sends will not match ARBOR_CORS_ORIGINS and WebSocket handshakes will be refused with 4403 origin not allowed.
In /etc/arbor/arbor.env:
ARBOR_HOST=127.0.0.1
ARBOR_PORT=8444
ARBOR_TLS=0
ARBOR_CORS_ORIGINS=https://casa.lanArbor terminates TLS itself. Required because a non-loopback bind without TLS is refused at startup.
ARBOR_HOST=0.0.0.0
ARBOR_TLS=1
ARBOR_CERT=/etc/arbor/cert.pem
ARBOR_KEY=/etc/arbor/key.pem
ARBOR_CORS_ORIGINS=https://arbor.lan:8443,https://192.168.1.10:8443Then restart the services and open https://<hostname>:8443. You will need to accept the certificate warning unless you import the certificate into your browser trust store.
If Arbor is reachable only through a VPN tunnel (e.g. WireGuard), the tunnel itself provides confidentiality and a self-signed certificate adds no real security. Use ARBOR_ALLOW_PLAINTEXT=1 to opt out of the TLS requirement:
ARBOR_HOST=0.0.0.0 # or the specific WireGuard interface IP
ARBOR_PORT=8444
ARBOR_TLS=0
ARBOR_ALLOW_PLAINTEXT=1 # opt-in: VPN tunnel provides confidentiality
ARBOR_CORS_ORIGINS=http://10.0.0.1:8444Arbor prints a startup warning. This mode is only safe when the bind address is reachable exclusively through a trusted private network or VPN — do not combine it with a public interface.
If you also want to keep the existing HTTPS reverse-proxy path (e.g. for access via a public domain), run two separate Arbor instances on different ports, or keep Apache terminating TLS on the public side while Arbor binds to the WireGuard IP directly.
Apache HTTP proxy in front of the VPN path (optional, e.g. to share port 80 with other services):
<VirtualHost *:80>
ServerName 10.0.0.1
ProxyPreserveHost On
RequestHeader set X-Forwarded-Proto "http"
ProxyPass /ws/ ws://127.0.0.1:8444/ws/
ProxyPassReverse /ws/ http://127.0.0.1:8444/ws/
ProxyPass /api/ http://127.0.0.1:8444/api/
ProxyPassReverse /api/ http://127.0.0.1:8444/api/
ProxyPass / http://127.0.0.1:8444/
ProxyPassReverse / http://127.0.0.1:8444/
</VirtualHost>Do not add ProxyPassReverseCookiePath — it rewrites the cookie Path attribute, which causes the browser to hold multiple cookies with the same name and breaks CSRF validation. If you previously had this directive and see CSRF errors after removing it, clear all arbor_* cookies in the browser and re-login.
- Sessions made before the change carry the old cookie attributes — log out + log in again to pick up
SameSite=Strictand the CSRF cookie. - Hard-refresh the browser to bypass cached
app.js. Otherwise mutating requests will fail with403 csrf token missing or invaliduntil the new JS is loaded. - Create and use a local owner account; do not share tokens or shells.
/etc/arbor/arbor.env is loaded by both the web service and the daemon:
| Variable | Default | Purpose |
|---|---|---|
ARBOR_HOST |
127.0.0.1 |
Bind address; change explicitly for LAN access |
ARBOR_PORT |
8443 |
Web server port |
ARBOR_TLS |
0 in the bootstrap config |
Set to 0 to disable TLS without checking cert files; set to 1 to require ARBOR_CERT and ARBOR_KEY |
ARBOR_CERT |
/etc/arbor/cert.pem |
TLS certificate path when ARBOR_TLS=1 |
ARBOR_KEY |
/etc/arbor/key.pem |
TLS key path when ARBOR_TLS=1 |
ARBOR_AUTH_MODE |
unset (password only) | Set to totp to require a TOTP code during login. Enable TOTP from the Security page first. |
ARBOR_APPROVAL_MODE |
none |
Privileged operation approval mode: none for browser step-up re-auth (requires ARBOR_ALLOW_AUTO_APPROVAL=1), cli for root-shell arbor-approve. The legacy totp value is refused at startup. |
ARBOR_ALLOW_AUTO_APPROVAL |
unset | Required alongside ARBOR_APPROVAL_MODE=none. Set to 1 to acknowledge that any authenticated session can trigger privileged operations. |
ARBOR_TOTP_SECRET |
unset | No longer accepted from the process environment (it would leak via /proc/<pid>/environ). Use ARBOR_TOTP_SECRET_FILE. |
ARBOR_TOTP_SECRET_FILE |
/etc/arbor/totp.secret when configured |
File containing the base32 TOTP secret for totp mode (mode 0600). Managed by the Security page. |
ARBOR_TOTP_ISSUER |
Arbor |
Issuer label embedded in the otpauth:// TOTP URI |
ARBOR_TOTP_ACCOUNT_NAME |
host-derived | Account label embedded in the otpauth:// TOTP URI |
ARBOR_ENABLE_OVERLAY_ADD |
0 |
Enable the dangerous overlay-add flow; overlays are disabled by default because new ebuilds run as root |
ARBOR_IPC_KEY |
unset | Optional env override for the shared HMAC key used to authenticate web-to-daemon IPC requests |
ARBOR_IPC_KEY_FILE |
/etc/arbor/ipc.key |
Shared HMAC key file, generated by setup by default |
ARBOR_IPC_ALLOWED_UIDS |
uid of user arbor |
Comma-separated peer uid allowlist for /run/arbor/daemon.sock. Anything outside this set is rejected via SO_PEERCRED. |
ARBOR_TRUSTED_PROXIES |
127.0.0.1 |
Comma-separated IPs passed to uvicorn's forwarded_allow_ips. Controls which proxy addresses are trusted to set X-Forwarded-For / X-Forwarded-Proto. |
ARBOR_ALLOW_PLAINTEXT |
unset | Allow plain HTTP on non-loopback interfaces (e.g. a WireGuard VPN address). Set to 1 alongside ARBOR_HOST=0.0.0.0 (or a specific VPN IP) and ARBOR_TLS=0. Only safe when the network itself provides confidentiality (VPN tunnel). A startup warning is printed. |
ARBOR_CORS_ORIGINS |
loopback http(s) on localhost, 127.0.0.1, [::1] (port 8443) |
Comma-separated allowed origins |
ARBOR_STATIC_DIR |
auto-detected | Override the frontend static directory |
Overlay add is disabled by default. To enable it, set ARBOR_ENABLE_OVERLAY_ADD=1 in /etc/arbor/arbor.env, restart the services, and use the two-step confirmation flow in the UI. Adding an overlay is equivalent to trusting that repository with root-level code execution during package builds.
- Local auth with roles — local users with
owner,operator, andviewerroles - Optional TOTP at login — require a 6-digit TOTP code during sign-in when
ARBOR_AUTH_MODE=totp - Dashboard — summary cards, recent job activity, compile time by category, source/binary mix, keyword posture, top enabled USE flags, and multi-slot package summaries
- Installed packages — filter installed packages, open package details, inspect metadata, USE state, and runtime dependencies
- Search packages — search the Portage tree and jump to the selected package
- USE flags — inspect global USE state, package-specific overrides, installed build state, and mismatch indicators
- Install / Uninstall — pretend first, stream live output, resume running jobs, and require approval before the real root action starts. After a successful pretend, a build-time ETA badge is shown using local
emerge.loghistory. The badge colour indicates confidence: green means all packages have been built on this machine before (reliable), yellow means some packages fall back to category averages (partial), grey means no local history exists and the figure is a rough estimate only. A legend is always visible alongside the badge. - Autounmask flow — for masked or USE-constrained install targets, Arbor detects both keyword masks and required USE flag changes from the pretend output, then writes the necessary entries to
/etc/portage/package.accept_keywords/arbor-acceptedand/etc/portage/package.use/arbor-acceptedrespectively, before re-running the pretend automatically - etc-update review — after successful installs, pending
._cfg*files can be reviewed and resolved in the UI - Maintenance — sync, check
@world, update@world, run preserved-rebuild, and depclean with approval on privileged steps; cache cleaner runseclean-dist/eclean-pkgin pretend mode first, then deletes with approval - Overlays — list configured overlays, sync them, remove them, and optionally add new ones with explicit danger acknowledgement plus approval
- Portage News — reads GLEP 42 news items from the local tree, tracks read state per-item, shows an unread count badge on the dashboard
- GLSA advisories — lists security advisories that affect the installed system via
glsa-check, with severity badges, affected packages, and a quick-fix button - Config snapshot — export
/etc/portage/(including themake.profilesymlink) and world files as a zip; import with an automatic timestamped backup of the current config before applying - Kernel management (beta) — full lifecycle wizard for source kernels: install or update via Portage with pretend preview and autounmask support; download vanilla kernels directly from kernel.org; browse and clean up
/usr/src/linux-*source directories; switch the active symlink; 7-step manual build wizard (kernel config with optional.configcopy from another source,make modules_install,make install, dracut initramfs generation,@module-rebuild, Limine bootloader auto-update); manage bootloaders including a Limine config editor; clean up old/bootfiles and orphaned/lib/modulesdirectories; reboot with double confirmation (typeREBOOT+ approval); wizard state persisted in the browser across page reloads and reconnects to in-progress build jobs after refresh. Functional but not yet fully stabilized — always keep a working fallback kernel in your bootloader. - Jobs — view active jobs, reopen live output, browse persisted history with log viewing, delete, and purge actions (stored in SQLite at
/var/lib/arbor/history.db), and surface recovered orphaned/unknown jobs after daemon restart
Two processes run with separate privileges:
arbor-daemon(root) — performs Portage operations, tracks long-running jobs, and listens on/run/arbor/daemon.sockarbor(unprivilegedarboruser) — FastAPI/uvicorn web server, serves the frontend, and proxies requests to the daemon
The frontend is a no-build Alpine.js app in frontend/alpine/.
That directory is the canonical UI source and the one served in development and install-script deployments; there is no separate frontend build step to run for normal development.
Arbor is still an early-release, local-first admin tool. The default install binds the web UI to 127.0.0.1 over plain HTTP on port 8443, and it is not intended for direct internet exposure. The recommended remote-access pattern is reverse-proxy with TLS termination (see LAN access) on a private network or VPN.
- Treat an authenticated Arbor session as root-equivalent intent: once logged in, the UI can request root-backed package actions (subject to approval mode and role checks).
- In
climode, root-backed actions are intentionally split into request in browser / approve in root shell. The browser cannot complete these actions on its own; approval must go througharbor-approve. nonemode requires a password re-prompt in the browser before each privileged action. It is refused at startup unlessARBOR_ALLOW_AUTO_APPROVAL=1is set, so the trade-off is always explicit.- TOTP at login is a convenience tradeoff for trusted local/LAN use. It adds a second factor in the browser, but it does not make Arbor safe to expose on the open internet — a valid session plus the shared TOTP secret is not the same as a per-user, phishing-resistant auth design.
- CSRF: every state-changing request (
POST,PUT,DELETE,PATCH) requires a matchingX-CSRF-Tokenheader that echoes thearbor_csrfcookie. The cookie is set at login, rotated on logout/TOTP changes, and verified by middleware before the handler runs. The first WebSocket auth frame must include the same token. The login endpoint itself is the only exemption. - Cookies: both
arbor_session(HttpOnly) andarbor_csrfareSameSite=Strict. TheSecureflag is set when the request is served over HTTPS (either Arbor's own TLS or via a reverse proxy that setsX-Forwarded-Proto: https); on plain HTTP (e.g. VPN access withARBOR_ALLOW_PLAINTEXT=1) cookies are set withoutSecureso the browser accepts and returns them. Cross-site navigations no longer carry the session, which closes most CSRF vectors at the browser level. - HSTS: emitted as
Strict-Transport-Security: max-age=63072000; includeSubDomainswhen the request is served over HTTPS (including via a reverse proxy that setsX-Forwarded-Proto: https). - TLS bind enforcement: a non-loopback bind (
ARBOR_HOSTother than127.0.0.1/::1/localhost) requires TLS to be active by default. Arbor refuses to start in plain HTTP on a public interface unlessARBOR_ALLOW_PLAINTEXT=1is set (opt-in escape hatch for VPN/private network deployments where the tunnel itself provides confidentiality). - WebSocket origin: a missing
Originheader is accepted only when bound to loopback. On any public bind, the connectingOriginmust be inARBOR_CORS_ORIGINS. - Security response headers: a strict CSP (
script-src 'self',object-src 'none',frame-ancestors 'none'),X-Frame-Options: DENY,X-Content-Type-Options: nosniff,Referrer-Policy: strict-origin-when-cross-origin./docs,/redoc, and/openapi.jsonare disabled.
- Authenticated channel: web → daemon over
/run/arbor/daemon.sock, signed with an HMAC-SHA256 key in/etc/arbor/ipc.key. - Protocol v2: every signed payload includes a 16-byte nonce and a unix timestamp. The daemon enforces freshness (
|now − ts| ≤ 30s) and rejects replays via a bounded LRU nonce cache (4096 entries, 5-minute TTL). Captured frames cannot be re-injected. - Peer credential check (
SO_PEERCRED): the daemon refuses any connection whose peer uid is not in the allowlist. Default:uid("arbor"). Override withARBOR_IPC_ALLOWED_UIDS. - Boot guards: the daemon (and web) refuse to start with
ARBOR_APPROVAL_MODE=totp(legacy, removed) and withARBOR_APPROVAL_MODE=noneunlessARBOR_ALLOW_AUTO_APPROVAL=1is set.ARBOR_TOTP_SECRETin the process environment is refused; useARBOR_TOTP_SECRET_FILE.
Every state-changing REST endpoint (POST, PUT, DELETE, PATCH) and every mutating WebSocket command requires a fresh password confirmation within the last 120 seconds when ARBOR_APPROVAL_MODE is not cli. A stolen session cookie alone is not enough to launch privileged actions. The frontend handles the re-prompt transparently via a modal; on success the original request is retried automatically.
Both services apply privilege reduction at exec time. The mechanism depends on what is available:
arbor-daemonkeeps only the capabilities Portage genuinely needs (CHOWN,DAC_OVERRIDE,DAC_READ_SEARCH,FOWNER,FSETID,SETGID,SETUID,SYS_CHROOT,MKNOD,KILL,SYS_ADMINfor sandbox mount namespaces,SYS_PTRACE,SETPCAP,SYS_RESOURCE) and drops everything else from the bounding set. A RCE in the daemon cannot load kernel modules, open raw sockets to scan the LAN, mess with the clock, reconfigure audit, or exec a setuid helper to climb back to full root.arbor(web) always runs asuid:gid arbor:arbor. Ifsetprivis present,no_new_privsis also applied.
On systemd the hardening is unconditional (NoNewPrivileges= and CapabilityBoundingSet= in the unit files). On OpenRC the init scripts use setpriv from sys-apps/util-linux when available. If setpriv is not installed (it requires USE=setpriv on Gentoo — the flag is not always set by default), the services start without the capability bounding set and log a warning. To enable full hardening:
USE=setpriv emerge sys-apps/util-linuxTwo draft profiles are shipped in apparmor/usr.bin.arbor-daemon and apparmor/usr.bin.arbor. They restrict the filesystem and capabilities reachable from each process — arbor-daemon to Portage paths only, arbor to /var/lib/arbor and /var/log/arbor plus the IPC socket. They are not enabled by default and have not been tested end-to-end against a full emerge workflow. Treat them as a starting draft for hardening, not as a guarantee.
To try them on a test box (Gentoo with the apparmor USE flag and kernel support for CONFIG_SECURITY_APPARMOR):
sudo cp apparmor/usr.bin.arbor-daemon /etc/apparmor.d/
sudo cp apparmor/usr.bin.arbor /etc/apparmor.d/
sudo apparmor_parser -r /etc/apparmor.d/usr.bin.arbor-daemon
sudo apparmor_parser -r /etc/apparmor.d/usr.bin.arbor
# Iterate in complain-mode first so failures land in dmesg / journalctl
# without breaking real installs:
sudo aa-complain /etc/apparmor.d/usr.bin.arbor-daemon
sudo aa-complain /etc/apparmor.d/usr.bin.arbor
# When happy:
sudo aa-enforce /etc/apparmor.d/usr.bin.arbor-daemon
sudo aa-enforce /etc/apparmor.d/usr.bin.arbor
sudo rc-service arbor-daemon restart # or: systemctl restart arbor-daemon
sudo rc-service arbor restart # or: systemctl restart arborIf you confirm the profiles work on your setup, please share back the corrections so the "untested" disclaimer can be removed.
config/logrotate.d/arbor is installed into /etc/logrotate.d/arbor by config/setup.sh. Daily rotation, 10 MB threshold, 14 rotations kept, gzip with delaycompress, recreated as 0640 arbor:arbor. Post-rotate triggers a soft restart on whichever supervisor is active (systemd or OpenRC).
- Local auth uses scrypt with strong parameters; password comparison and TOTP code comparison are timing-safe (
hmac.compare_digest). - Login throttle works on three scopes (IP, username, pair) with exponential backoff; failures are persisted in
auth.dbso a restart does not reset the counter. - Overlay add remains opt-in behind
ARBOR_ENABLE_OVERLAY_ADD=1. Even when enabled, adding an untrusted overlay still means trusting it with root-level code execution during package builds. - The etc-update resolve path refuses unsafe symlinked overwrite targets, and job handling is more honest after restarts: active jobs are snapshotted to disk and may come back as
orphanedorunknownrather than being treated as live. - Live job buffers and stored history logs are intentionally bounded. Very large jobs may show truncated live output or truncated saved logs.
CI runs pip-audit against the committed requirements.lock on every push, every PR, and weekly via cron. Bandit and Semgrep run on the same triggers (p/python, p/security-audit, p/owasp-top-ten rule packs). Dependabot opens PRs for dependency bumps on Mondays. A new CVE in a pinned transitive dep shows up as a red build within seven days even with zero code change.
Backend setup:
cd backend
python3 -m venv .venv
.venv/bin/pip install -e .Run the web server in local-first HTTP mode:
ARBOR_TLS=0 .venv/bin/arborThe frontend does not need a build step; it is served directly from frontend/alpine/, which is the canonical frontend source tree for this repository.
The daemon still requires root privileges and a working Portage environment.
Local-auth setup should create the owner account via arbor-auth. If no local users exist, login is intentionally unavailable until bootstrap is completed.
/var/log/arbor/daemon.log # arbor-daemon output
/var/log/arbor/web.log # arbor web server output





