Install as a service
Run the control plane at login, check releases, and apply updates.
The service runs the web app and API together at http://localhost:3000, bound to 127.0.0.1. Email and password authentication stays enabled. It runs as your user, without elevation. macOS uses a launchd agent; Linux uses a systemd user unit. Windows service installation is not supported yet. Electron installers are separate and do not include this service.
Install
Install Node 24 LTS, Bun 1.3.14 or newer, and Git. For this private repository, authenticate Git for cloning and fetching, and sign in with gh auth login so the updater can read releases. A GH_TOKEN or GITHUB_TOKEN with repository read access also works for release checks; Git still uses its own credentials.
Clone into a permanent directory. The service keeps using this checkout and the Bun executable that installed it, so keep both in place.
git clone git@github.com:monopolo11/not-a-software-factory.git
cd not-a-software-factory
# Once releases exist, start with the latest stable:
git checkout "$(gh release view --json tagName --jq .tagName)"
bun run install:serviceBefore the first release, leave the checkout on main and omit git checkout. Installation installs locked dependencies, builds the backend and web app, creates private configuration, registers the login service, starts it, and waits for its health check. Stop any development server using port 3000 first. Open http://localhost:3000/login and sign up.
To select a different port on first installation:
PORT=7434 bun run install:serviceThe port and a random authentication secret live in config.json in the state directory. Reinstalling preserves them. To change the port later, stop the service, edit port in that file, and start the service. The web app follows the current serving origin.
To reach the service through a proxy such as tailscale serve, add publicOrigin to config.json (for example "publicOrigin": "https://host.tailnet.ts.net:3091") and restart. It must be http or https, and any path is dropped. That origin becomes the trusted Host and sign-in origin, and an https origin makes session cookies secure, so sign in through it rather than localhost. The service still binds to 127.0.0.1, and localhost on its own port is still accepted as a Host. Without it, proxied requests fail with Forbidden host.
Manage the service
Run commands from the installation checkout:
bun run service status
bun run service stop
bun run service start
bun run service restart
bun run service uninstallUninstall stops the service and removes its login registration. It preserves configuration, accounts, agent keys, workflows, and runs. Reinstall with bun run install:service to use the same state. To move to another checkout, uninstall from the old one first, then install from the new checkout.
The service starts at login and restarts after a crash. On Linux, run loginctl enable-linger if it should start at boot and continue after logout. That OS setting can require administrator approval; the installer does not change it.
start and restart retain the installed Bun path and environment. Re-run install:service after moving or replacing Bun, or to capture a changed PATH. A port conflict refuses startup instead of launching a second server.
State and logs
| Platform | State | Logs |
|---|---|---|
| macOS | ~/Library/Application Support/not-a-software-factory | ~/Library/Logs/not-a-software-factory/server.log |
| Linux | $XDG_STATE_HOME/not-a-software-factory, otherwise ~/.local/state/not-a-software-factory | journalctl --user -u not-a-software-factory.service |
The state directory contains control-plane.sqlite, config.json, installation metadata, and update backups. New state directories are owner-only; configuration files and the service database are owner-only. Keep the complete directory when moving or backing up an installation. Stop the service before manually copying SQLite files. Logs and update backups are not automatically pruned.
NASF_STATE_DIR overrides the state directory and must be an absolute path. Set it consistently for installation, service management, updates, and foreground execution. Only one login service per user is supported. Empty or relative XDG_STATE_HOME and XDG_CONFIG_HOME values use the default home-directory locations.
Installed services ignore development .env files and inherited development authentication/database settings. Development data in .data/control-plane.sqlite stays separate. Schema migrations run before each service startup. To run the installed configuration in the foreground, stop the login service, then run bun run start:service.
Check and apply updates
Open Settings → Updates to see the running version and available release. Choose Channel to save stable or nightly, then Check for updates to refresh. Checks are cached for 30 minutes between visits; a failed check can be retried immediately. A clean, untagged main installation moves only to a stable release that includes its commit, regardless of the selected channel.
Choose Update and restart to check again, check out the release, install locked dependencies, and rebuild. The service keeps running during the build. After the response, it exits so launchd or systemd restarts it. The page waits for a new server instance, then reloads. A checkout or build failure restores the previous checkout and rebuilds it before returning an error. Development builds, dirty checkouts, and foreground servers cannot update from Settings.
This web flow follows Mergeatron's update mechanism. Migrations run on restart. It does not back up the database or automatically roll back a failed startup. Use the CLI flow below for database backup and startup recovery. If the service does not return, inspect its logs and recover from the terminal.
From the terminal
bun run update check
bun run update applyUpdates are explicit. No background process installs releases. Stable is the default channel. To try nightly releases:
bun run update check nightly
bun run update apply nightlyAn apply saves the selected channel when it succeeds, including when no newer release is available. Stable selects stable releases only; nightly selects the highest stable or nightly version. Switching back to stable waits for a version newer than the installed nightly, so it never downgrades. check prints the installed version, requested channel, and available release.
A release tag or clean main checkout can update. Other development branches, tracked modifications, and untracked files refuse updates. A clean main checkout can move only to a release that includes its current commit. Fetching tags uses your Git credentials. Checking releases uses gh, with a token or public GitHub API as fallback. Discovery considers the 100 most recent releases; if all of them are nightlies, an older stable release is not offered.
Apply obtains a lock, checks again, stops a running login service, backs up SQLite, checks out the release tag, installs locked dependencies, and rebuilds. It then starts the service and checks health. A service that was stopped stays stopped. Build or startup failures restore the old checkout and database and restart the old service if it was running. Backups remain in backups/ for recovery. The installation is detached at its release tag after a successful update.
If a command is interrupted, inspect the checkout, service status, and logs before removing the state directory's update.lock. If rollback fails, the command prints the previous commit and backup location. Stop the service, restore that checkout and database, remove SQLite -wal and -shm sidecars while stopped, rebuild with bun install --frozen-lockfile and bun run build:service, then start it. Do not run a foreground service against the same database during an update.
See Contributing: releases for publishing a release and the implementation choices.