Files

148 lines
8.6 KiB
Markdown

# 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:
```text
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:
```sh
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:
```sh
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?