Files
DoGaMa-serv/docs/contributing/development.md
T

8.0 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/template views, 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 and DOGAMA_AGENT_TOKEN_FILE. The two agent settings must either both be present or both be absent; lifecycle routes remain disabled when developing without an agent. 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.

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?