8.6 KiB
Development conventions
Language and style
- Documentation and stable identifiers are English-first; UI strings are localizable from the start.
- Use idiomatic current stable Go, explicit errors and small interfaces at consumer boundaries.
- Prefer server-rendered
html/templateviews, progressive enhancement and small focused browser modules; do not introduce a large SPA framework without an accepted architectural reason. - Format and lint frontend and Go code with repository-pinned tools.
- Avoid hidden global state. Inject clock, ID generator and external interfaces for deterministic tests.
- Use UTC internally and IANA timezones at scheduling boundaries.
Repository shape
The implementation may refine this layout while preserving boundaries:
cmd/dogama/
cmd/dogama-agent/
internal/ non-public Go packages
web/ embedded UI source
migrations/ append-only SQLite migrations
specs/ schemas and normalized contracts
catalog/ reference templates
modules/ reference modules and fixtures
docs/
tests/integration/
Initial application development
The initial main application requires Go 1.25. SQLite is provided by the pure-Go modernc.org/sqlite driver, so neither cgo nor a system SQLite development library is required. It reads bootstrap settings from DOGAMA_LISTEN_ADDRESS (default :8080), DOGAMA_DATABASE_PATH (default dogama.db), DOGAMA_AGENT_URL, DOGAMA_AGENT_TOKEN_FILE and DOGAMA_MASTER_KEY_FILE. The two agent settings must either both be present or both be absent; lifecycle routes remain disabled when developing without an agent. The master-key file must contain exactly 32 bytes and enables encrypted notification-channel configuration; audit remains available without it. Run it with:
go run ./cmd/dogama
Browser sessions always use Secure, HttpOnly, and SameSite=Strict cookies. Place the application behind a trusted TLS reverse proxy for browser use, including development environments. The application does not accept forwarded client addresses as authoritative for authentication throttling.
The restricted agent is a separate binary:
go run ./cmd/dogama-agent
It fails closed unless DOGAMA_AGENT_TOKEN_FILE references a 32-byte-or-longer
secret and at least one of DOGAMA_ALLOWED_SERVER_ROOT or
DOGAMA_ALLOWED_BACKUP_ROOT is configured. Its bootstrap-only defaults are
:8081, /var/run/docker.sock, the fixed dogama-games Docker network and
/var/lib/dogama-agent/registry.json. The configured roots must already exist
and are canonicalized with symlinks resolved. For normal deployment, use the
secret file and private control network defined in compose.yaml; never publish
the agent port on the host.
The agent loads the same embedded validated catalog as the main application. Before Docker access it independently matches image, entrypoint, arguments, container ports, mount destinations, resource minimums and stop timeout against the pinned template snapshot. Mount sources are created one directory at a time below configured roots with symlinks refused. Docker containers always use the fixed restricted baseline; callers cannot provide labels, capabilities, devices, network modes or arbitrary Docker options.
Lifecycle API operations are authorized in the backend against the authenticated
identity and the target instance. Global administrators retain implicit access;
assigned users can inspect, view metrics, start and stop, while managers also
receive the documented operational baseline. Explicit deny overrides take
precedence over role baselines and allows. Install and container-only delete
remain administrator operations. Operations are serialized per instance and
recorded in instance_operations; desired and observed states are reconciled at
startup and every minute. Container-only delete removes neither the SQLite
intent nor host paths, so player data and backups remain untouched.
The authorization foundation exposes JSON APIs for local user creation, memberships, per-user overrides and installation requests. Mutations require the session CSRF token. User creation, membership changes, override changes and request review additionally require an administrator session authenticated in the previous ten minutes. Approving a request only records the decision and the requested values; it never creates a draft or contacts the restricted agent.
Game-data backups are written below DOGAMA_BACKUPS_ROOT (default
/srv/game-backups) and may only read instance mounts below
DOGAMA_SERVERS_ROOT (default /srv/game-servers). Untrusted uploads are
isolated below DOGAMA_IMPORTS_ROOT (default
/var/lib/dogama/imports/staging). These are bootstrap path boundaries, not
ordinary product settings. The same canonical server and backup roots are
mounted into the main application and restricted agent by compose.yaml.
The current backup engine conservatively stops a running instance before
archiving. Once the WebAssembly runtime is active, an enabled online_save
module can provide the documented flush-before-archive optimization without
moving traversal or archive ownership out of the main application. Archives
are finalized before SQLite marks them available; a metadata failure removes
the orphaned file. Scheduled retention considers only successful scheduled
backups. Restore verifies size, checksum, manifest and pinned template version,
creates a pre_restore backup, extracts into sibling staging and keeps the
instance stopped with intervention_required if readiness cannot be restored.
Validated imports are pinned to the selected template version. Import-backed
drafts require that opaque import ID, and installation atomically places the
normalized staged tree at the template-declared mount-relative destination
before the restricted agent creates the first container. Repeated installation
submission recognizes an already attached import instead of copying it twice.
At main-application startup, every embedded catalog/*/template.yaml is
validated against specs/template.schema.json, checked for cross-reference and
asset integrity, canonicalized deterministically and synchronized into SQLite.
An existing template ID/version is immutable: changing its digest fails startup
instead of silently replacing the snapshot. Deployment previews pin that digest
and redact secret defaults before a draft instance can enter the registry.
Contract changes
Template schema, manifest schema, normalized module API and agent deployment plan are versioned contracts.
- Backward-compatible additions do not change existing meanings.
- Breaking changes require a new schema/API major version and documented migration.
- Released examples are updated or retained as compatibility fixtures.
- Instances pin immutable versions; runtime behavior never depends on a mutable catalog file.
Testing pyramid
- Unit: domain policies, permission evaluation, cron/timezones, state transitions, retention and redaction.
- Property/fuzz: paths, archive entries, schema inputs, agent plans and normalized module payloads.
- Integration: SQLite migrations, disposable Docker agent, real archive workflows and WASM sandbox limits.
- End-to-end: bootstrap, Palworld dry/test fixture deployment, role views, backup/restore and update rollback.
Tests must include denial and interrupted-operation cases, not only happy paths. External game APIs use recorded or purpose-built fixtures in normal CI; live Palworld verification is a separate documented test.
Migrations and compatibility
Never rewrite a released migration. Migration execution is transactional where SQLite permits and creates a pre-migration database backup for release upgrades. Store application/schema version and test upgrade from the previous release.
Dependencies and supply chain
Prefer standard-library or mature focused dependencies. Pin build tooling, review transitive changes and generate an SBOM/reproducible checksums for releases. Do not dynamically download executable code at runtime except administrator-installed validated WASM packages.
Observability
Structured technical logs use stable event names and redaction. Metrics are operationally small. Correlation/operation IDs connect HTTP, job and agent errors without storing request secrets.
Review checklist
- Correct trust boundary and backend authorization?
- Can input influence a host path, Docker plan, URL or secret?
- Are retries/idempotency and restart recovery defined?
- Could failure delete or corrupt player data?
- Do documentation, schema and examples agree?
- Are UI capability checks backed by server checks?
- Are audit and notifications appropriately minimal?