API Documentation
Complete reference for the Town OS systemcontroller API — 77 endpoints across accounts, storage, repositories, packages, systemd, settings, audit, pages, DNS, monitoring, system services, locales, VM images, and status.
Overview
The systemcontroller is the central backend service for Town OS. It is built on
Echo v5 and listens on port 5309 (TCP) or a Unix domain
socket in production. All request and response bodies use JSON. Errors follow
RFC 9457
(application/problem+json).
CORS is enabled for development. In production the API is served behind the same origin as the UI.
Authentication
Authenticate by calling POST /account/authenticate with a username and
password. The response contains a Bearer token. Include it in subsequent requests:
Authorization: Bearer <token> Sessions expire after 7 days of inactivity. There are three auth levels:
| Level | Description |
|---|---|
| Public | No token required. |
| Authenticated | Any valid session token. |
| Admin | Session token belonging to an admin account. |
Pagination
All list endpoints accept the following query parameters and return a common envelope:
| Parameter | Type | Description |
|---|---|---|
sort_by | string | Field name to sort on. |
sort_order | string | asc or desc. |
limit | int | Page size (default 20). |
offset | int | Pagination offset. |
search | string | Case-insensitive substring match across all string fields. |
Response Envelope
{
"entries": [...],
"has_more": true,
"total_pages": 5,
"total_count": 97
} Status
/status/ping Public
Health check and system overview. Unauthenticated callers receive a minimal response
with status and needs_setup. Authenticated callers receive
the full dashboard payload including filesystem count, package counts, unit status
summary, disk usage, external/internal IP, and upgrade availability.
Accounts
/account/authenticate Public Authenticate with username and password. Returns a session token and the account object.
| Field | Type | Description |
|---|---|---|
username | string | Required. Account username. |
password | string | Required. Account password. |
/account/create Public
/
Admin Create a new account. In bootstrap mode (no enabled admin accounts exist), this endpoint is public. Otherwise, admin authentication is required. The first account created becomes the administrator. Password must be at least 8 characters. Email, phone, and real name are required.
| Field | Type | Description |
|---|---|---|
username | string | Required. |
password | string | Required. Minimum 8 characters. |
email | string | Required. |
phone | string | Required. |
real_name | string | Required. |
admin | boolean | Whether the account has admin privileges. |
/account Authenticated
Get a single account by username. Request body: {"username": "alice"}.
/account Authenticated List all accounts. Supports pagination parameters.
/account/update Authenticated
Update account fields. Send username to identify the account and a
fields object with any combination of password,
email, phone, real_name, and admin.
Only provided fields are changed.
/account/me Authenticated Returns the username associated with the token in the Authorization header.
/account/sessions Authenticated List all active sessions for the authenticated user. Each session includes its ID, username, creation time, and last-used time.
/account/session/revoke Authenticated
Revoke a session by ID. Request body: {"session_id": "..."}.
/account/disable Admin
Disable an account. Request body: {"username": "bob"}.
/account/enable Admin
Re-enable a disabled account. Request body: {"username": "bob"}.
Storage
/storage Authenticated
List filesystems. Accepts pagination parameters plus optional name
(prefix filter) and state (user, installed,
or uninstalled) in the request body.
/storage/create Authenticated
Create a new btrfs subvolume. Send name and optional quota
(bytes). If quota is 0 or omitted, the system default (50 GB) is used. Reserved names
(installed, uninstalled, archives) are rejected.
/storage/modify Authenticated
Modify an existing filesystem. Send name to identify it and a
filesystem object with the updated name and/or quota.
/storage/remove Authenticated
Remove a filesystem. Request body: {"name": "mydata"}.
/storage/upload-archive Admin
Upload and unpack an archive into a target subvolume. Accepts multipart/form-data
with a subvolume field and an archive file. Supports
.tar.gz, .tgz, .tar.bz2, .tbz2,
.tar.xz, .txz, .tar, .zip, and
.7z.
| Field | Type | Description |
|---|---|---|
subvolume | string | Required. Target subvolume path. |
archive | file | Required. Archive file to upload. |
subpath | string | Optional. Relative path within the volume for unpacking; created on demand. |
stop_service | string | Optional. Systemd unit name to stop before unpacking and restart after completion. |
| Setting | Default | Description |
|---|---|---|
max_archive_size | 1 GB | Maximum upload size. |
archive_unpack_timeout | 600 seconds | Maximum time for unpacking. |
/storage/download-archive Admin Download an archive of subvolume contents. Returns a streamed archive in the requested format.
| Field | Type | Description |
|---|---|---|
subvolume | string | Required. Source subvolume path. |
paths | string[] | Optional. Array of specific paths within the subvolume to include. |
stop_service | string | Optional. Systemd unit name to stop during archiving and restart after. |
format | string | Optional. Compression format: tar.gz (default), tar.bz2, or tar.xz. |
filename | string | Optional. Custom base name for the downloaded file. The server appends the appropriate extension. Defaults to download. |
/storage/package-volumes Authenticated List package volumes grouped by package, with optional inclusion of uninstalled volumes.
/storage/remove-package-volume Admin Delete a specific package volume by internal name.
Repositories
/repository Authenticated List all configured package repositories with name, URL, and any error status. Supports pagination parameters.
/repository/add Authenticated Add a new package repository. Triggers an immediate refresh.
| Field | Type | Description |
|---|---|---|
name | string | Required. Display name for the repository. |
url | string | Required. Git URL of the repository. |
username | string | Optional. Auth username for private repos. |
password | string | Optional. Auth password for private repos. |
/repository/remove Authenticated
Remove a repository by name. Triggers an immediate refresh.
Request body: {"name": "my-repo"}.
/repository/move Admin
Reorder a repository to a new zero-based position. Later repositories override earlier
ones when package names collide.
Request body: {"name": "my-repo", "position": 0}.
/repository/refresh Authenticated Force an immediate refresh of all repository metadata. Returns an empty body on success, or a JSON object mapping repository names to error strings if any fail.
Packages
/packages Authenticated List all available packages across all repositories. Each entry includes repo, name, version, description, supplies tags, installation status, and whether an upgrade is available. Supports pagination parameters.
/packages/by-repo Authenticated
List packages grouped by repository. Accepts an optional search query
parameter. Returns an array of {"repo": "...", "packages": [...]} groups.
/packages/timezones Authenticated List all available IANA timezone names for use in package configuration.
/packages/installed Authenticated List installed package identifiers. Supports pagination parameters.
/packages/installed/info Authenticated
Get detailed info for an installed package. Send repo, name,
and version. Returns questions, user responses, notes, and note types.
/packages/responses Authenticated
Get the saved question responses for an installed package. Send repo,
name, and version. Returns a key-value map of responses.
/packages/versions Authenticated
List available versions for a package. Request body: {"name": "nginx"}.
Returns a string array of version identifiers.
/packages/children Authenticated
List child packages. Send repo and name. Returns a string array.
/packages/questions Admin
Get the installation questions for a package. Request body: {"name": "nginx"}.
Returns a map of question key to {"query": "...", "type": "..."}.
/packages/questions/identity Admin
Get questions for a specific package version. Send repo, name,
and version.
/packages/install-preview Admin
Preview what an installation will do before committing. Send repo,
name, and version. Returns volume details, port mappings,
disk usage, quota information, upgrade source version, and a human-readable summary.
/packages/install Admin Install a package.
| Field | Type | Description |
|---|---|---|
repo | string | Required. Repository name. |
name | string | Required. Package name. |
version | string | Required. Version to install. |
responses | object | Required. Key-value answers to installation questions. |
reuse_volumes | boolean | Reuse existing data volumes from a previous installation. |
import_from_version | string | Version to import volumes from during upgrade. |
/packages/uninstall Admin
Uninstall a package. Send repo, name, version,
and optional purge_volumes (boolean) to delete associated data.
/packages/disable Admin
Disable an installed package (stop its service). Send repo and name.
/packages/enable Admin
Re-enable a disabled package (start its service). Send repo and name.
/packages/purge-volumes Admin
Delete all data volumes for an installed package. Send repo and name.
/packages/uninstalled-volumes Admin
Check whether a package has leftover volumes from a previous installation. Send
repo and name. Returns has_uninstalled_volumes,
uninstalled_versions, and installed_versions.
/packages/purge-uninstalled-volumes Admin
Delete leftover volumes from previously uninstalled versions. Send repo
and name.
/packages/upgrades Authenticated
List available upgrades for installed packages. Each entry includes
installed_version, latest_version, and whether the package
definition has changed.
/packages/upgrades/dismiss Admin Dismiss the current upgrade notifications. Send an empty JSON object.
/packages/manifest Authenticated
Returns the raw YAML package definition. Send repo, name,
and version. Returns the file content with
Content-Type: text/x-yaml. Returns 404 if the package file does not exist.
/packages/featured Authenticated List featured packages across all repositories.
/packages/last-responses Authenticated
Retrieve cached last responses for a package. Send repo and
name. Returns the saved responses from a previous uninstall for reuse
during reinstallation.
/packages/clear-last-responses Admin
Delete the cached last responses file for a package. Send repo and
name.
/packages/rebuild-git Admin
Pull latest changes for git-seeded volumes of an installed package and restart
the dependent service. Send repo, name, and
version. Template variables are re-evaluated against saved responses
before rebuilding.
Systemd
/systemd/units Authenticated List systemd units managed by Town OS. Each entry includes unit name, description, load/active/sub states, the associated package identifier and description, and a failure flag. Supports pagination parameters.
/systemd/status Admin
Control a systemd unit. Send name (unit name) and action
(start, stop, restart, enable,
or disable).
/systemd/logs Authenticated
Stream journal entries for a unit in real time via Server-Sent Events. Pass the
unit query parameter; empty or __system__ returns system-wide
logs. Each SSE event contains a JSON-encoded journal entry with fields like
Message, Priority, RealtimeTimestamp, and
SystemdUnit.
/systemd/logs/tail Authenticated Fetch a page of journal entries with cursor-based pagination and filtering.
| Parameter | Type | Description |
|---|---|---|
unit | string | Systemd unit name. Empty or __system__ for system-wide logs. |
lines | int | Number of entries to return (default 100). |
before | string | Cursor — return entries before this position. |
after | string | Cursor — return entries after this position. |
grep | string | Case-insensitive substring filter on message text. |
since | int | Unix timestamp — return entries from this time forward. |
until | int | Unix timestamp — stop collecting at this time. |
priority | int | Syslog severity filter (0 = no filter). |
Returns entries, cursor (first entry), and
end_cursor (last entry) for subsequent pagination.
Settings
/settings Admin Get all settings as a key-value object.
/settings/get Admin
Get a single setting. Request body: {"key": "default_quota"}.
Returns key and value.
/settings/set Admin
Set a setting value. Request body: {"key": "default_quota", "value": "107374182400"}.
Default Settings
| Key | Default | Description |
|---|---|---|
default_quota | 53687091200 (50 GB) | Default quota for new filesystems. |
max_archive_size | 1073741824 (1 GB) | Maximum archive upload size. |
archive_unpack_timeout | 600 (seconds) | Maximum time for archive unpacking. |
locale | en-US | System-wide locale for internationalization. |
proton_image | quay.io/town/proton:latest | Proton/Wine runner container image. |
dns_tld | home | Top-level domain for local DNS resolution. |
Audit Log
/audit/log Admin List audit log entries. All fields in the request body are optional.
| Field | Type | Description |
|---|---|---|
before_id | int | Keyset pagination — return entries with ID less than this. |
account | string | Filter by account username. |
sort_by | string | Field to sort on. |
sort_order | string | asc or desc. |
limit | int | Page size. |
offset | int | Pagination offset. |
search | string | Search filter. |
Each audit entry contains id, account, action,
path, detail, success, error,
and created_at. Audited actions include: authenticate, create/update/disable
account, revoke session, install/uninstall/disable/enable package, create/modify/remove
filesystem, add/remove/move/refresh repository, upload/download archive, update setting,
dismiss upgrades, and purge volumes.
Pages
Static site hosting supporting three content source types: archive uploads, container images, and git repositories. Users assign a domain, and the system serves the content via a Caddy container. All mutation endpoints require admin authentication; the list endpoint requires regular authentication.
/pages Authenticated List all pages with sorting, search, and pagination. Sortable by name, repo URL, branch, domain, source type, status, and timestamps.
/pages/create Admin
Create a new page. Accepts name, source type (archive,
container_image, or git), repo URL, branch, domain,
container image, and image directory. Source type defaults to archive.
Git and container image pages are provisioned asynchronously.
/pages/upload Admin
Upload a tar archive of content for an archive-type page. Accepts multipart form with
name and archive file. Only valid for pages with source type
archive; returns 400 for other source types.
/pages/update Admin Partial update of a page's repo URL, branch, domain, source type, container image, or image directory. Only provided fields are changed.
/pages/remove Admin Delete a page from the database, remove the webroot symlink, and delete the btrfs subvolume.
/pages/rebuild Admin
Rebuild page content from source. Git pages pull latest changes; container image pages
re-extract from the image. Archive pages return 400 (re-upload via
/pages/upload instead).
DNS
Integrated local DNS resolver powered by a rolodex-dns container. Manages
zone files and records for installed packages, providing local name resolution via a
gRPC Unix socket interface.
/dns/status Authenticated Returns DNS status including enabled flag, running state, TLD, and record count.
/dns/records Authenticated List all DNS records.
/dns/records/add Admin Add a DNS record. Accepts name, record type, value, and TTL.
/dns/records/remove Admin Remove a DNS record by name and type.
/dns/tld Authenticated Get the current top-level domain setting.
/dns/tld Admin Set the TLD. Changes the existing TLD and re-registers all installed packages.
/dns/setup Admin Initialize or restart the DNS server and register all installed packages.
Monitoring
Integrated Prometheus, Node Exporter, and Grafana stack for system monitoring. The
stack runs as systemd-supervised podman containers with Restart=always.
/monitoring/status Authenticated
Returns container status (name, image, running state, port) for each monitoring
service. Returns {"status": "disabled"} when monitoring
is not configured.
/monitoring/grafana/* Public
Reverse proxy to the local Grafana instance, stripping the
/monitoring/grafana prefix from the URL path. Returns 503 if monitoring
is not configured. This endpoint bypasses authentication.
System Services
System services are systemd-managed infrastructure containers (distinct from
user-installed package services). They use the town-os-system-- unit
name prefix.
/system-services Public
/
Authenticated List system services with live unit status. Accessible from localhost without authentication. Each entry includes key, display name, image, port, and systemd unit status fields.
/system-services/status Admin
Control a system service. Accepts key and action
(start, stop, or restart).
/system-services/refresh Admin Refresh system service unit files and status.
Locales
Internationalization locale information for the system.
/locales Authenticated Returns the current locale, list of populated locales, common languages (with native-script names), and extended locales. Uses BCP 47 locale codes.
VM Images
Management of cached VM disk images used by VM packages. Remote images are downloaded
and converted to raw format via qemu-img convert; the converted image is
cached in the vm-images subvolume.
/vm-images Authenticated List cached VM disk images. Returns name and file size for each image.
/vm-images/upload Admin
Download a VM image from a URL and convert it to raw format. Accepts a URL and
optional name. The name defaults to the URL's filename with a .raw
extension. Downloads have a 30-minute timeout.
/vm-images/delete Admin Remove a cached VM image by name.
Client Libraries
Town OS ships with Go and JavaScript client libraries that provide full API coverage. Both clients throw typed errors on non-200 responses using RFC 9457 problem detail.
Go Client
The Go client lives in src/svc/systemcontroller/client.go and implements
the Client interface. It supports both Unix socket and HTTP connections.
// Connect via Unix domain socket (production)
client := systemcontroller.InitClient("/run/town-os/systemcontroller.sock")
// Connect via HTTP (development / testing)
client := systemcontroller.FromClient(http.DefaultClient, "http://localhost:5309")
Set client.Token after authenticating. All methods accept a
context.Context as their first parameter.
Storage
| Method | Description |
|---|---|
CreateFilesystem(ctx, fs) | Create a new btrfs subvolume. |
ModifyFilesystem(ctx, name, fs) | Rename or resize a filesystem. |
RemoveFilesystem(ctx, name) | Delete a filesystem by name. |
ListFilesystems(ctx, prefix, state, params) | Paginated list filtered by name prefix and state ("user", "installed", "uninstalled"). |
Repositories
| Method | Description |
|---|---|
AddRepository(ctx, name, rawURL, username, password) | Register a package repository with optional credentials. |
RemoveRepository(ctx, name) | Remove a repository by name. |
MoveRepository(ctx, name, position) | Change priority (0 = highest). |
RefreshRepositories(ctx) | Refresh all metadata. Returns map of errors. |
ListRepositories(ctx, params) | Paginated list of repositories. |
Packages
| Method | Description |
|---|---|
ListPackages(ctx, params) | Paginated list of available packages. |
ListPackagesByRepo(ctx, params) | Packages grouped by repository. |
ListPackageVersions(ctx, name) | Available versions of a package. |
GetPackageQuestions(ctx, name) | Configuration questions by name. |
GetPackageQuestionsByIdentity(ctx, repo, name, version) | Questions for a specific version. |
ListChildren(ctx, repo, name) | Child package names. |
InstallPreview(ctx, repo, name, version) | Preview volumes and ports without installing. |
InstallPackage(ctx, name, version, responses, reuseVolumes, importFromVersion, skipResponseReuse) | Install a package. Name uses "repo/package" format. |
UninstallPackage(ctx, repo, name, version, purgeVolumes) | Remove an installed package. |
DisablePackage(ctx, repo, name) | Stop services without uninstalling. |
EnablePackage(ctx, repo, name) | Re-enable a disabled package. |
PurgeVolumes(ctx, repo, name) | Delete all data volumes for a package. |
ListUninstalledVolumes(ctx, repo, name) | Check for leftover volumes. |
PurgeUninstalledVolumes(ctx, repo, name) | Delete leftover volumes. |
ListInstalled(ctx, params) | Installed packages as "repo/name@version". |
GetResponses(ctx, repo, name, version) | Stored configuration responses. |
GetInstalledInfo(ctx, repo, name, version) | Detailed info including questions, responses, and notes. |
ListTimezones(ctx) | Available IANA timezone names. |
Systemd
| Method | Description |
|---|---|
ListUnits(ctx, params) | Paginated list of systemd units. |
SetUnitStatus(ctx, name, action) | Apply "start", "stop", or "restart". |
LogReplay(ctx, name) | Stream journal entries via SSE. Returns a channel. |
LogTail(ctx, params) | Page of journal entries with cursor-based pagination, grep, time range, and priority filtering. |
Accounts
| Method | Description |
|---|---|
Authenticate(ctx, username, password) | Returns session token and account. |
CreateAccount(ctx, username, password, email, phone, realName, admin) | Create a user. Password minimum 8 characters. |
GetAccount(ctx, username) | Retrieve account by username. |
UpdateAccount(ctx, username, fields) | Modify account fields (password, email, phone, real_name, admin). |
ListAccounts(ctx, params) | Paginated list of accounts. |
DisableAccount(ctx, username) | Prevent authentication. |
EnableAccount(ctx, username) | Re-enable a disabled account. |
ListSessions(ctx, token) | Active sessions for the token's user. |
SessionUsername(ctx, token) | Username for a session token. |
RevokeSession(ctx, sessionID) | Invalidate a session. |
Audit, Settings & Upgrades
| Method | Description |
|---|---|
ListAuditLog(ctx, opts, token) | Paginated audit log with filters. |
GetSettings(ctx) | All settings as key-value map. |
GetSetting(ctx, key) | Single setting by key. |
SetSetting(ctx, key, value) | Update a setting. |
ListUpgrades(ctx) | Packages with newer versions available. |
DismissUpgrades(ctx) | Mark pending upgrades as dismissed. |
Archives
| Method | Description |
|---|---|
UploadArchive(ctx, subvolume, archiveReader, filename, subpath, stopService) | Upload and extract an archive into a subvolume. Formats: tar.gz, tar.bz2, tar.xz. |
DownloadArchive(ctx, subvolume, paths, stopService, format) | Create an archive of subvolume contents. Returns an io.ReadCloser. |
Health
| Method | Description |
|---|---|
Ping(ctx) | Service health and summary counts. |
JavaScript Client
The JavaScript client lives in ui/src/api/ and is used by the Town OS
dashboard UI. It is built as a modular set of mixins on the
SystemControllerClient class. Non-200 responses throw ApiError
with the parsed RFC 9457 problem detail.
import SystemControllerClient from './api/client.js';
const client = new SystemControllerClient('http://localhost:5309');
// After authentication
const result = await client.authenticate('admin', 'password');
client.setToken(result.token); Storage
| Method | Description |
|---|---|
createFilesystem(fs) | Create a new btrfs subvolume. |
modifyFilesystem(name, fs) | Rename or resize a filesystem. |
removeFilesystem(name) | Delete a filesystem by name. |
listFilesystems(prefix, sortBy, sortOrder, state, limit, offset, search) | Paginated list with filtering. |
Repositories
| Method | Description |
|---|---|
addRepository(name, url, username?, password?) | Register a repository with optional credentials. |
removeRepository(name) | Remove a repository by name. |
moveRepository(name, position) | Change priority (0 = highest). |
refreshRepositories() | Refresh all metadata. Returns error map or null. |
listRepositories(sortBy, sortOrder, limit, offset, search) | Paginated list. |
Packages
| Method | Description |
|---|---|
listPackages(sortBy, sortOrder, limit, offset, search) | Paginated list of available packages. |
listPackagesByRepo(search) | Packages grouped by repository. |
listPackageVersions(name) | Available versions of a package. |
getPackageQuestions(name) | Configuration questions by name. |
getPackageQuestionsByIdentity(repo, name, version) | Questions for a specific version. |
installPreview(repo, name, version) | Preview volumes and ports without installing. |
installPackage(repo, name, version, responses, reuseVolumes?, importFromVersion?) | Install a package with configuration answers. |
uninstallPackage(repo, name, version, purgeVolumes?) | Remove an installed package. |
disablePackage(repo, name) | Stop services without uninstalling. |
enablePackage(repo, name) | Re-enable a disabled package. |
purgeVolumes(repo, name) | Delete all data volumes for a package. |
listUninstalledVolumes(repo, name) | Check for leftover volumes. |
purgeUninstalledVolumes(repo, name) | Delete leftover volumes. |
listInstalled(sortBy, sortOrder, limit, offset, search) | Installed packages as "repo/name@version". |
getResponses(repo, name, version) | Stored configuration responses. |
getInstalledInfo(repo, name, version) | Detailed info including questions, responses, and notes. |
Systemd
| Method | Description |
|---|---|
listUnits(sortBy, sortOrder, limit, offset, search) | Paginated list of systemd units. |
setUnitStatus(name, action) | Apply "start", "stop", or "restart". |
logReplay(unit) | Stream journal entries via SSE. Returns an AsyncGenerator. |
logTail(unit, lines?, before?, after?, grep?, since?, until?, priority?) | Page of journal entries with cursor-based pagination, grep, time range, and priority filtering. |
Accounts
| Method | Description |
|---|---|
authenticate(username, password) | Returns session token and account. |
createAccount(username, password, email, phone, realName, admin) | Create a user. Password minimum 8 characters. |
getAccount(username) | Retrieve account by username. |
updateAccount(username, fields) | Modify account fields. |
listAccounts(sortBy, sortOrder, limit, offset, search) | Paginated list of accounts. |
disableAccount(username) | Prevent authentication. |
enableAccount(username) | Re-enable a disabled account. |
listSessions(token) | Active sessions for the token's user. |
sessionUsername(token) | Username for a session token. |
revokeSession(sessionID) | Invalidate a session. |
Audit, Settings & Upgrades
| Method | Description |
|---|---|
listAuditLog(opts) | Paginated audit log with filters. |
getSettings() | All settings as key-value object. |
getSetting(key) | Single setting by key. |
setSetting(key, value) | Update a setting. |
listUpgrades() | Packages with newer versions available. |
dismissUpgrades() | Mark pending upgrades as dismissed. |
Archives
| Method | Description |
|---|---|
uploadArchive(subvolume, file, subpath?, stopService?) | Upload and extract an archive via FormData. Returns {needs_restart, message}. |
downloadArchive(subvolume, paths?, stopService?, format?) | Download a subvolume archive. Returns raw Response for streaming. |
Health
| Method | Description |
|---|---|
ping() | Service health and summary counts. |
Development Reference
The Town OS backend runs on port 5309 with a Vite dev server on port 5173.
Use make dev to start the full development environment.
Core Targets
| Target | Description |
|---|---|
make dev | Start the full dev environment (backend + Vite dev server). |
make dev-stop | Stop and remove the dev backend container. |
make dev-logs | Tail journalctl inside the running dev container. |
make dev-clean | Stop the container and tear down the dev btrfs volume. |
Testing Targets
| Target | Description |
|---|---|
make test | Run lint, Go unit tests, and JS unit tests. |
make test-integration | Run Go integration tests in a privileged Podman container. |
make test-ui-integration | Run Bun UI integration tests against a backend container. |
make test-full | Run all test suites in sequence. |
make auto-test | Watch for file changes and re-run tests automatically. |
Build Targets
| Target | Description |
|---|---|
make production-image | Build the production container image. |
make test-image | Build the test container image. |
make pull-images | Pull base container images from Docker Hub. |
Prerequisites
- Go 1.25+
- Bun — JavaScript runtime
- Podman — rootful, with
sudo - btrfs-progs —
mkfs.btrfs - golangci-lint
Create a .env file with repository credentials:
TOWN_OS_REPO_USERNAME=<username>
TOWN_OS_REPO_PASSWORD=<password>
After installing prerequisites, run make pull-images before any other targets.