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.
Arbor uses local username/password authentication, with optional TOTP at login via ARBOR_AUTH_MODE=totp.
Privileged operations use a separate control, ARBOR_APPROVAL_MODE, so login requirements and root-action approval can be configured independently.
The mode applies to install, uninstall, world update, sync, preserved-rebuild, depclean, overlay changes, and other root-backed admin operations.
Minimal /etc/arbor/arbor.env examples:
# local-first HTTP on loopback (bootstrap default)
ARBOR_TLS=0
# password login + CLI approval for privileged operations (default)
ARBOR_APPROVAL_MODE=cli
# direct HTTPS on Arbor itself (optional)
# ARBOR_TLS=1
# ARBOR_CERT=/etc/arbor/cert.pem
# ARBOR_KEY=/etc/arbor/key.pem
#
# password + TOTP at login, no extra approval prompt per operation
ARBOR_AUTH_MODE=totp
ARBOR_APPROVAL_MODE=none
ARBOR_TOTP_SECRET_FILE=/etc/arbor/totp.secret
# password + TOTP at login, plus CLI approval for privileged operations
ARBOR_AUTH_MODE=totp
ARBOR_APPROVAL_MODE=cli
ARBOR_TOTP_SECRET_FILE=/etc/arbor/totp.secret
# Optional
# ARBOR_TOTP_ISSUER=Arbor
# ARBOR_TOTP_ACCOUNT_NAME=arbor@my-host
# no extra approval after login
ARBOR_APPROVAL_MODE=noneWhen 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: the authenticated session can start privileged actions immediatelyARBOR_APPROVAL_MODE=cli: the authenticated session still needs root-shell confirmation viaarbor-approveARBOR_APPROVAL_MODE=totp: accepted for compatibility, but treated likenonefor per-action approval behavior
This is the original shell-first model and remains the safest mode for Arbor's intended local-first deployment.
- 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.
If you answer No in arbor-approve, the request is cancelled, the frontend unlocks, and you can retry without reloading the page.
This means:
- the browser can request dangerous actions, but it does not directly self-authorize them
- the approval decision happens in a root shell
- refreshing the UI does not lose a pending approval request; Arbor restores it and reopens the relevant page
In none mode, Arbor skips the extra approval step entirely and starts privileged actions immediately after the authenticated browser request.
This exists for fully trusted environments only. Arbor logs a startup warning when ARBOR_APPROVAL_MODE=none is enabled.
- 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
- Autounmask flow — for masked install targets, Arbor can write accepted keywords to
/etc/portage/package.accept_keywords - 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 - Overlays — list configured overlays, sync them, remove them, and optionally add new ones with explicit danger acknowledgement plus approval
- 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.1over plain HTTP on port8443, and it is not intended for internet exposure. - 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. totpmode 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 internet; a valid session plus the shared TOTP secret is still not the same as a hardened internet-facing auth design.nonemode removes the secondary approval gate entirely and should be treated as equivalent to trusting any authenticated session with direct root-backed action approval.- Safer defaults are enabled out of the box: localhost bind, tighter key handling, response security headers, and overlay add disabled by default.
- Overlay add remains a dangerous admin action. If you enable
ARBOR_ENABLE_OVERLAY_ADD=1, Arbor requires an explicit approval flow, but adding an untrusted overlay still means trusting it with root-level package build execution. - The etc-update resolve path now 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.
- Local auth DB ownership is auto-healed on system path (
/var/lib/arbor/auth.db) when initialized by root. This behavior is enabled by default and can be disabled withARBOR_AUTH_AUTOHEAL_PERMS=0if you prefer setup/package-hook-only permission management.
- Install and uninstall runs now keep the browser-boundary checks aligned with the actual default loopback deployment: WebSocket/CORS allow
localhost,127.0.0.1, and[::1]on port8443by default. - The Alpine frontend was migrated to the CSP-safe build and the template surface was refactored away from unsupported inline syntax such as template literals, optional chaining, and nullish coalescing in
x-*expressions. - Overlay removal now requires an explicit dangerous-action acknowledgement, and overlay add remains opt-in behind
ARBOR_ENABLE_OVERLAY_ADD=1. - Background job recovery now records checkpoints and PID identity metadata so daemon restarts report
orphaned/unknownstates honestly instead of pretending a lost job is still fully attached. - OpenRC services now use respawn supervision by default; systemd already had restart-on-failure behavior.
- 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 systemd' >> /etc/portage/package.use/arbor # or: openrc
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.shChoose your init system via USE flag before installing, then start the services as shown below.
git 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. If ARBOR_AUTH_MODE=totp is enabled, the login form also requires the current TOTP code.
Arbor uses local username/password auth only (ARBOR_AUTH_BACKEND=local). Rerun setup after upgrades so owner bootstrap and auth DB permissions are applied:
bash /usr/share/arbor/setup.shFor 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.
When you start a privileged action from the UI, Arbor's behavior depends on ARBOR_APPROVAL_MODE:
cli: Arbor pauses and waits forarbor-approve approve <request_id>from a root shell.totp: accepted for backward compatibility, but treated likenone; TOTP is enforced at login whenARBOR_AUTH_MODE=totp.none: Arbor starts the privileged action immediately with no second prompt.
For cli, use:
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 operatorOpenRC:
rc-update add arbor-daemon default
rc-update add arbor defaultsystemd:
systemctl enable arbor-daemon arborBackend 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.
emaint sync -r arbor-overlay
emerge app-admin/arborFor the live ebuild instead:
emaint sync -r arbor-overlay
emerge =app-admin/arbor-9999Then 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/arbor/var/log/arbor/daemon.log # arbor-daemon output
/var/log/arbor/web.log # arbor web server output
/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 |
unset (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 |
cli |
Login auth mode; set to totp to require a TOTP code during login |
ARBOR_APPROVAL_MODE |
derived from ARBOR_AUTH_MODE |
Privileged operation approval mode; use cli for root-shell confirmation or none for no extra prompt |
ARBOR_TOTP_SECRET |
unset | Inline base32 TOTP secret; supported, but prefer the file-based option below |
ARBOR_TOTP_SECRET_FILE |
/etc/arbor/totp.secret when configured |
File containing the base32 TOTP secret for totp mode |
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_ALLOW_PLAINTEXT |
unset | Legacy fallback: allow plain HTTP when ARBOR_TLS is unset and cert/key are missing |
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.
For TOTP login, prefer storing the secret in ARBOR_TOTP_SECRET_FILE instead of ARBOR_TOTP_SECRET so it stays out of process listings and service unit overrides. Arbor manages this file when an owner enables TOTP from the web UI.
ARBOR_APPROVAL_MODE=none is intentionally noisy: Arbor prints a startup warning because authenticated browser access can immediately trigger privileged actions in that mode.
LAN access exists, but it is still not the recommended deployment mode. Arbor now ships with several hardening changes for safer local use, but it still binds only to loopback by default and should not be treated as an internet-facing service.
Even with ARBOR_AUTH_MODE=totp, Arbor is not designed to become safe for public internet exposure. TOTP here is a usability-oriented login-time second factor for a trusted operator, not a substitute for a hardened multi-user internet auth model.
For LAN access you must configure both:
ARBOR_HOSTso the web server listens on a LAN-reachable address.ARBOR_CORS_ORIGINSso browser requests and WebSocket origins from that LAN address are accepted.
Example /etc/arbor/arbor.env:
ARBOR_HOST=0.0.0.0
ARBOR_CORS_ORIGINS=https://arbor.lan:8443,https://192.168.1.10:8443Then restart the services and open:
https://<hostname>:8443You will need to accept the certificate warning unless you import the certificate into your browser trust store.
Create and use a local owner account instead of token sharing for remote/LAN access patterns.





