Akupara is a local-first web service with a browser UI, user, session and API-key authentication, a signed and hash-verified plugin library, internal and external plugin interactions, audio feedback, and update and integrity checks.
Developer documentation: README-DEV.md.
-
Local-first service with a browser UI — the service binds to
127.0.0.1on the port inresources/configuration.jsonand serves a single-page UI whose navigation switches between the home, settings and plugins pages. It exists so the service is reachable only from the device that runs it. Start it (see Run) and openhttp://127.0.0.1:<port>/in a browser; every page except the login page and the static assets requires a session, and a protected/api/*endpoint without a session returns401so the frontend redirects to/login. -
Accounts, sessions and roles — login credentials live in the
USERS.envvariable as peppered verifiers: the browser stretches the password with Argon2id using a per-account salt (m=65536, t=2, p=1) and the server stores onlyakupara$2$<salt>$<tag>, an HMAC-SHA256 tag keyed byresources/password-pepper. The pepper belongs to one installation (one setup run), so re-running setup replaces it and every stored verifier must then be regenerated. A leaked.envcannot be attacked offline and its contents cannot be replayed at/api/login; an entry that is not in this format is refused, so every account must be generated withscripts/generate-password-hash.js. On a start that findsUSERSempty, a root accountadminwith the passwordadmin123is created so a fresh installation can be entered; change that password immediately, because it is public. A successful login issues a slidingakupara-refreshcookie (renewed on every authenticated response; 15 minutes of validity) whose session is held server-side per user, so several users can be logged in at the same time. It exists to keep the service restricted to known users. Log in at/login, and manage the accounts from the Users card: register a user, rename it, switch it between Standard and Admin, or delete it; a user marked"root": truein.envcannot lose admin status or be deleted, and only the root account may rename itself. A standard user may view the settings page but not administer it. -
API keys — with
API_KEYS_ENABLEDon, the API Keys card generates keys and the service accepts them in theX-Api-Keyheader on the management and external-interaction endpoints (api_key_or_admin_authenticated). Keys exist so plugins and scripts can act without a browser session. The.envstores each key Fernet-encrypted underAPI_KEYS, withAPI_KEY_ENCRYPTION_KEYgenerated on first run; the plaintext key is shown once in the card so it can be copied. -
Signed, hash-verified plugin library —
resources/plugins-lib/holds the catalogue of installable plugins as hash-range JSON files (<low>-<high>.json) plus areverse-index.jsonword index, andhashis the library's signed content hash. It exists to certify which plugin versions Akupara may run. A plugin is{"hash","name","description","repo","version","akupara_version","content_hash","trust_mark"}, and at startup the service verifies the stored hash's PGP signature and recomputes the folder hash, so a modified or uncertified library stops the service; search it withPOST /api/plugins/searchor the Plugins page. -
Plugin discovery and upgrades — installed plugins live in
plugins/<hash>folders; the service verifies each folder's trust mark and content hash, compares the installed version with the plugin's GitHub releases, and offers or applies upgrades. It exists to keep installed plugins valid and current. Use the Plugins page (Check for Updates,Update All, per-rowUpdateandDelete) or the/api/installed-plugins*endpoints;dev-*folders skip every check and load only in development mode. -
Internal interactions (shared variables) — a
SHARED_MEMORYentry is{"name","type","value","editor","reader"}, whereeditorandreaderare plugin-name lists; a plugin present in both is kept ineditor. It exists so plugins can share values without a direct link. Register and edit variables from the Internal Interactions card (at least one plugin is required; updates are partial, so omitted fields are left untouched); each plugin's access is enforced per variable, and only an API key or an admin session may create or delete a variable. -
Plugin event subscriptions —
PLUGIN_EVENT_SUBSCRIPTIONSmaps an event name ([A-Za-z0-9_.-]+) to the sorted list of subscribed plugin names. It exists so plugins can address one another by event. Manage the subscriptions from the Internal Interactions card or through the/api/plugin-events*endpoints while internal interactions are enabled. -
External interactions (IP firewall and network worker) — with external interactions enabled,
ExternalInteractionsWorkerserves the marked endpoints on the device's first non-loopback IPv4 address on the same port, in parallel with the loopback server; the incoming IP list (allow,unknown,block) andEXTERNAL_INTERACTIONS_ALLOW_NEWdecide which remote clients pass, and a parallel outgoing list andEXTERNAL_INTERACTIONS_ALLOW_NEW_OUTGOINGexist for outgoing requests. It exists to expose the service to selected devices without opening it to everyone. Manage both lists from the External Interactions card; new incoming IPs are recorded automatically asunknownscoped toakupara. Opening it to other machines over plain HTTP is not advisable: the worker serves the same endpoints without TLS, so the login exchange and theakupara-refreshcookie travel unencrypted and a captured password hash can be replayed. Put the service behind a TLS reverse proxy or inside a VPN or SSH tunnel, or keep external interactions disabled. While external interactions are enabled and the page is not reached over TLS, the card border turns warn-yellow and hovering the card shows a "Plain HTTP Usage for External Interactions is Discouraged" tooltip just outside its right edge, height-centred on the card and kept within the scrollable content area. -
Audio feedback — the service plays a configurable sound for six events (
startup,acknowledge,warn,process,success,error); each event's file is set by its.envvariable or from the Customisation card, and playback requiresPLAY_AUDIOS. It exists to confirm actions audibly. Toggle playback, the startup sound and the log sounds from the Customisation card. -
Update checks — the service compares the signed project hash with the hash asset of the latest GitHub release, and the plugin-library hash with the hash at the latest commit, at startup and on demand. It exists to keep the deployment at a certified version. Check manually from the General and Plugins cards (
Check for Updates) or through/api/check-for-updatesand/api/check-for-plugin-updates;AUTOMATIC_UPDATE,AUTOMATIC_PLUGIN_LIBRARY_UPDATEandAUTOMATIC_PLUGIN_UPGRADEapply the result automatically. SetGITHUB_TOKENto raise the GitHub API rate limit. -
Integrity verification — the repo-root
hashandresources/plugins-lib/hashfiles each carry a hash plus an armoured PGP signature, and the service verifies the signature and recomputes the hash from the tracked files at every start. It exists to detect an illicit modification or a partial installation. The plugin library must always match its hash; the project may differ from its hash in development mode only, where it logs a warning instead of stopping. The General and Plugins cards show the effective and indicated version hashes side by side. -
Server-rendered settings by role — the settings page is rendered from card fragments under
ui/cards/settings/and sent with the session's role, so standard users see the General, Account and Customisation cards while admin-only cards are loaded only for administrators. It exists to avoid exposing administrative content to standard users. Open/ui/pages/settings.htmlfrom the UI.
- Install Python 3.10 or newer and make it available on
PATH. - Run
scripts\setup.baton Windows, orbash scripts/setup.shon Unix-like systems. The script creates a.venvvirtual environment and installsrequirements.txtinto it. To do it manually, create a virtual environment and runpip install -r requirements.txt. To discard a previous installation's per-installation artifacts (.env,resources/password-pepper,resources/installation.json,resources/trust/,logs/) and start fresh, runscripts\reset-installation.batorbash scripts/reset-installation.shbefore re-running setup. - Install GnuPG (
gpg) and make it available onPATH(C:\Program Files\GnuPG\binon Windows). It is required: the shippedhashfiles and plugin trust marks are verified withgpg --verifyagainstresources/lorenbll-akupara-pub, and the service refuses to start when the signature cannot be verified. - Copy
.env.exampleto.env. You can leaveUSERS=[]: the first start creates a root accountadminwith the passwordadmin123, which you should change immediately from the General card. To add another account, or to replace the default one, runnode scripts/generate-password-hash.js <username> <password>(Node.js 18 or newer) and paste the printed entry intoUSERS, a JSON list of{"username","password","client_salt","admin","root","id"}wherepasswordis the stored verifier andclient_saltthe browser salt for that account. The generator scopes the pepper to the current installation: it createsresources/password-pepperon a new installation, replaces a pepper left by a previous one, and records the installation so the first start keeps that pepper. Back the pepper up and keep it out of version control, because losing it makes every stored password unusable. An entry holding a raw Argon2id hash, or missingclient_salt, is refused at login. - Review
resources/configuration.jsonto set the port, whether the UI routes are enabled (guiEnabled) and whether the service runs in development mode (development). - Keep the project structure intact so the service can find
resources/,src/andui/, and leave the signedhashfiles unmodified.
Start the service:
- Windows:
scripts\run.bat. - Unix-like:
bash scripts/run.sh.
Both scripts check for Python 3.10+, create or reuse .venv, install requirements.txt, and start src/main.py. Without --debug the service starts in the background with no console output; with --debug it runs in the foreground and writes a short trace of its progress.
A start with no resources/installation.json is a new installation: it creates or replaces the pepper and records the marker. A start that finds the marker but no usable pepper logs the reason, plays the error sound and exits without serving. A start whose USERS list is empty creates the default root account admin with the password admin123; a value that is not a valid JSON list is left untouched, and logins are refused while no usable user exists. Registering a user is refused for the same reason instead of replacing that value, and so is changing or deleting a user while the list holds an entry the service cannot read.
To start it directly, run python src/main.py from the project root.
Add --debug to write DEBUG events to the log; without it, DEBUG events are not written. The log itself is described in README-DEV.md.
Configuration is split between two files: resources/configuration.json for the service itself and .env at the project root for runtime behaviour. Copy .env.example to .env; the service reads .env on every start and mirrors every change made through the UI back into it.
resources/password-pepper is scoped to one installation, meaning one setup.bat/setup.sh run. resources/installation.json records that the installation has started and is deleted by the setup scripts. A new installation creates a pepper, or replaces one left by the previous installation; an installation that has already started keeps its pepper and refuses to start, logging the error, when the pepper is missing or unusable. Neither file is a .env key, and both are excluded from the project hash. Back the pepper up, because without it no stored password can be verified; after a re-setup regenerates it, every USERS entry must be regenerated too.
resources/configuration.json:
| Key | Default | Effect |
|---|---|---|
port |
49150 |
Port the service binds to on 127.0.0.1. Must be free and, on Unix-like systems, at least 1024 unless the process has elevated privileges. |
guiEnabled |
true |
When true, the UI routes (/, /login, /ui/*) are registered. Set to false for headless operation. |
development |
false |
When true, the project may differ from its recorded hash (a warning is logged and the update checks are skipped) and the manual and automatic project updates are refused. The plugin library is still verified strictly. |
.env:
| Key | Default | Effect |
|---|---|---|
USERS |
[] |
JSON list of {"username","password","client_salt","admin","root","id"}, where password is the stored peppered verifier (akupara$2$…), client_salt the Argon2 salt the browser used, and admin (default false) grants the administrator role. Generate every entry with scripts/generate-password-hash.js; a raw hash or a missing client_salt is refused. When the list is empty at startup, a root account admin with the password admin123 is created; change that password immediately. A malformed value is left untouched, and logins are refused while no usable user exists. |
INTERNAL_INTERACTIONS |
false |
Enables internal interactions: shared variables and plugin event subscriptions, and their cards. |
SHARED_MEMORY_ENABLED |
false |
Switches shared variables on within internal interactions. Both this and INTERNAL_INTERACTIONS must be on for the shared-variable endpoints to work. When the key is absent from .env, the reader falls back to true. |
SHARED_MEMORY |
[] |
JSON list of {"name","type","value","editor","reader"} shared variables. type is one of string, list, dictionary, integer, float, boolean. |
PLUGIN_EVENT_SUBSCRIPTIONS |
{} |
JSON object mapping an event name to its sorted list of subscribed plugin names. |
API_KEYS_ENABLED |
false |
Enables API keys: the API Keys card, /api/api-keys*, and authentication through the X-Api-Key header. |
API_KEYS |
[] |
JSON list of {"name","key","id"}, where key is the Fernet-encrypted API key. Written by the service; the plaintext key is never stored, logged or returned after creation. |
API_KEY_ENCRYPTION_KEY |
generated on first run | Fernet key that encrypts and decrypts API_KEYS. Losing it makes the stored keys undecryptable. |
DISPLAY_PROMOTION |
true |
Shows the promotion line at the bottom of the UI. |
PLAY_AUDIOS |
true |
Enables audio playback for every event. When it is off, setting a sound file, toggling the startup or log sounds, or playing an event returns 403. |
PLAY_LOG_SOUNDS |
false |
Plays a sound for each logged event while PLAY_AUDIOS is on. |
PLAY_STARTUP_SOUND |
true |
Plays the startup sound once after the loading operations, while PLAY_AUDIOS is on. |
STARTUP_SOUND |
logo-reveal.wav |
Audio file from resources/audios/ played for the startup event; empty plays nothing. |
ACKNOWLEDGE_SOUND |
acknowledge.wav |
Audio file played for the acknowledge event; empty plays nothing. |
WARN_SOUND |
warn.wav |
Audio file played for the warn event; empty plays nothing. |
PROCESS_SOUND |
process.wav |
Audio file played for the process event; empty plays nothing. |
SUCCESS_SOUND |
success.wav |
Audio file played for the success event; empty plays nothing. |
ERROR_SOUND |
error.wav |
Audio file played for the error event; empty plays nothing. |
FFMPEG_PATH |
empty | Path to an ffmpeg binary; its neighbouring ffplay is used for playback when present. Without it, playback falls back to PowerShell's SoundPlayer on Windows or aplay elsewhere. |
EXTERNAL_INTERACTIONS |
false |
Enables external interactions and starts the network worker. When off, the firewall endpoints return 403. |
EXTERNAL_INTERACTIONS_INCOMING_IPS |
[] |
JSON list of {"ip","plugins","action","Note"} incoming firewall entries, where action is allow, unknown or block and plugins is a plugin-name list. |
EXTERNAL_INTERACTIONS_OUTGOING_IPS |
[] |
Outgoing firewall entries, in the same shape. |
EXTERNAL_INTERACTIONS_ALLOW_NEW |
false |
Lets through remote clients that are in the incoming list as unknown or absent from it. Requests from allow clients always pass and from block clients are always refused. |
EXTERNAL_INTERACTIONS_ALLOW_NEW_OUTGOING |
false |
Default policy for outgoing requests with no matching entry. |
AUTOMATIC_UPDATE |
false |
Applies an available project update (and restart) at startup. Refused in development mode. |
AUTOMATIC_PLUGIN_LIBRARY_UPDATE |
false |
Applies an available plugin-library update (and restart) at startup. |
AUTOMATIC_PLUGIN_UPGRADE |
false |
Upgrades installed plugins that have a newer release at startup. |
GITHUB_TOKEN |
empty | Optional token sent to GitHub hosts as Authorization: token <token>. It exists to raise the API rate limit (~60 to ~5000 requests per hour). Generate one with no scopes and no expiration, and add it to .env; without it, frequent checks may hit the limit, in which case version checks are skipped rather than failing, while operations that need GitHub data log an error and keep running. |
The sound-file defaults above are the values shipped in .env.example; when a sound key is absent from .env, the service falls back to the same file for every event, so no event is silently muted. A key present with an empty value plays nothing for that event.
- Open an issue on GitHub for bug reports, feature requests or help. Security issues should be reported privately as described in SECURITY.md.