Run locally¶
The local-dev harness is a reproducible, single-machine environment for running
and exercising AWC end to end on a developer laptop. It stands up the external
dependencies the AWC services rely on (PostgreSQL and an S3-compatible object
store) in containers, runs the AWC binaries themselves on the host (so the code
under development is what executes), generates the credentials and configuration
they need, and runs a smoke script that proves the real cross-process paths
work, including a byte-for-byte read of an imported snapshot through a real
/dev/fuse base mount.
It targets the single-machine variant of the reference topology.
Topology¶
host
├── docker compose ──────────────────────────────────────
│ postgres:16 control-plane metadata store (awc-db)
│ minio S3-compatible object store (tenant CAS target)
│ minio-init one-shot: creates the awc-cas bucket
│ ────────────────────────────────────────────────────────
├── awc-control control plane: REST + gRPC, migrates schema, dev-token
├── awc-worker background worker: drains the job queue over the s3:// CAS
├── awcd host daemon: Unix-socket gRPC + health HTTP + real FUSE base mount
└── awcctl CLI — drives the control plane + host daemon
All container ports bind to 127.0.0.1, so nothing is exposed off the host. The
four AWC binaries are built once with their object-store and FUSE cargo features
and then invoked directly. Cargo's default feature set would rebuild the wrong
variants.
Prerequisites¶
- Docker Engine and the
docker composeplugin, with a reachable daemon. - The Rust toolchain pinned by
rust-toolchain.toml(for the build step). python3,curl, and common coreutils on the host.- Linux with a usable FUSE stack:
/dev/fusepresent, andfusermount(orfusermount3) onPATH.awcdmounts a real read-only FUSE base filesystem. - For non-root use:
user_allow_otherenabled in/etc/fuse.conf. The FUSE base mount usesAutoUnmount, which adds theallow_otherFUSE option. FUSE only honoursallow_otherfor non-root mounters whenuser_allow_otheris set. As root this is not required.
Quick start¶
cd deploy/local-dev
bin/generate-credentials.sh # writes .env, config/{awcd.toml,worker.env,control.env}
sudo ./smoke.sh # writable OverlayFS needs mount privilege; verifies end to end
A green smoke prints == SMOKE PASS == and leaves PostgreSQL, MinIO, and the
three host services running for further use. Tear everything down with:
./clean.sh # stop containers, keep volumes + config
./clean.sh -v # also remove the postgres + minio volumes
./clean.sh --purge # full reset: volumes, generated config, and runtime data
First time? Read the tutorial
For a guided, step-by-step walk that explains each step, see Your first workspace. This page is the reference procedure for repeating the local-dev smoke.
What the smoke verifies¶
Each step fails loudly if the real path does not work. There are no mocked successes.
cargo buildofawc-control,awc-worker,awcd,awcctlwith theirs3andfusefeatures (CARGO_INCREMENTAL=0keeps the build off the disk cliff).- PostgreSQL and MinIO become healthy. The
awc-casbucket is created. awc-controlconnects to PostgreSQL, applies theawc-dbschema migrations, serves REST and gRPC, and the dev bootstrap mints a service token (captured from the log).- The seed identity rows (
seed/seed.sql) load with the Admin role on the dev project. awc-workerconnects to PostgreSQL over the durables3://MinIO CAS and starts its run loop.awcdstarts from the generatedconfig/awcd.toml, mounts a real read-only FUSE base filesystem over MinIO, and serves its host API. The kernel is polled until it reports afuse*filesystem at the basefs mountpoint./healthzcorroborates that the base-fuse component is up.awcctl snapshot createruns the full import protocol (session over the control plane, objects uploaded straight to the MinIO CAS, completion over the control plane) and reports the snapshot id, manifest digest, and file count.awcctl snapshot inspectandlistround-trip against the control plane.- The just-imported snapshot is read through the FUSE base mount. Bytes are fetched from MinIO on demand, content-verified, served, and compared byte-for-byte with the source fixture.
- The worker validates the snapshot object closure by reading it back from the
CAS, and advances it
VALIDATING -> READY. The smoke polls until READY. awcctl workspace forkfrom the READY snapshot, thenmount(the ticket is issued over the control plane and redeemed at the host daemon; the session is registered), thenunmount.
Inspect intermediate output under run/ (build.log, control.log,
worker.log, awcd.log, bucket.log, seed.log, awcctl.log).
Generated credentials and configuration¶
bin/generate-credentials.sh produces:
| File | Contents |
|---|---|
.env |
Random POSTGRES_PASSWORD, MINIO_ROOT_PASSWORD, and AWC_SERVICE_TOKEN_KEY, plus reproducible identifiers and ports |
config/awcd.toml |
awcd config: host_id, roots under data/, loopback health port, cache/checkpoint/lease limits, and the control_plane_endpoint at the control gRPC port |
config/worker.env |
DATABASE_URL, AWC_CAS_URL=s3://…, worker tuning, and the S3 staging variables |
config/control.env |
awc-control env: DATABASE_URL, REST and gRPC listen addresses, the shared MinIO CAS and S3 variables, the service-token key, and the dev-bootstrap identity |
Secrets are random. Identifiers (for example host_01arz3ndektsv4rrffq69g5fav)
are fixed and reproducible so the seeded host row matches awcd's configured
host_id. Re-running the generator reuses an existing .env so its password
keeps matching an initialized PostgreSQL volume. Pass --force to regenerate
secrets, then run ./clean.sh -v, since a new password does not match the old
volume. Generated files are gitignored and never committed.
awcctl is driven through environment variables in local-dev. The smoke sources
config/worker.env (for the MinIO CAS upload) and sets the control-plane and
host endpoints and the dev token:
set -a; . config/worker.env; set +a
export AWC_CONTROL_ENDPOINT="http://127.0.0.1:8080"
export AWC_HOST_SOCKET="$PWD/run/awcd.sock"
export AWC_TENANT AWC_PROJECT # the seeded dev tenant/project
export AWC_TOKEN="<dev service token>" # minted by awc-control on startup
awcctl snapshot create --from-dir seed/source --name demo
awcctl status # host health via awcd
See configuration and environment variables for the full key and variable reference.
Manual bring-up¶
The smoke automates all of this. The individual steps for a manual run:
bin/generate-credentials.sh
docker compose up -d postgres minio
docker compose run --rm minio-init # create the CAS bucket
# awc-control migrates the schema on startup and mints the dev token.
set -a; . config/control.env; set +a
awc-control & CONTROL_PID=$!
DEV_TOKEN=$(grep -m1 'awc-control dev token:' run/control.log | sed 's/.*awc-control dev token: //')
docker compose exec -T postgres psql -U awc -d awc < seed/seed.sql
set -a; . config/worker.env; set +a
awc-worker &
awcd config/awcd.toml run/awcd.sock &
export AWC_CONTROL_ENDPOINT="http://127.0.0.1:8080" AWC_HOST_SOCKET="$PWD/run/awcd.sock"
export AWC_TENANT AWC_PROJECT AWC_TOKEN="$DEV_TOKEN"
awcctl snapshot create --from-dir seed/source --name demo
Current scope (honest)¶
This environment wires every component that is implemented today and is explicit about what is not, rather than faking a working service.
Verified working end to end by the smoke:
awc-controlis a live service (REST and gRPC). It migrates the schema, serves/v1/*, verifies bearer service tokens, and writes imports into the durable MinIO CAS. Completing an import enqueues asnapshot_validatejob, so the worker advances snapshotsVALIDATING -> READY.- The worker reads from MinIO (
AWC_CAS_URL=s3://awc-cas), not an in-process store. It drains the queue and validates snapshot object closure against the real CAS. awcdmounts a real read-only FUSE base filesystem, not a no-op. Objects are fetched from MinIO on demand, content-verified, and served. The smoke reads an imported snapshot through$BASEFS/tenants/<tenant>/snapshots/<manifest>/…and compares bytes with the source.
The privileged smoke also verifies the writable path. It checks that the target is OverlayFS, mutates and deletes files, publishes a cooperative checkpoint, unmounts, immediately reacquires the writer lease, and verifies the changes from the new immutable head after remount.
Not exercised by this smoke:
- Production mTLS. The smoke sets
AWC_DEV_MODE=1and uses loopback plaintext. Outside that mode,awcdandawc-controlrequire mutually authenticated TLS and fail startup on missing credentials. See the security model.
Files¶
local-dev/
├── docker-compose.yml postgres + minio + minio-init
├── .env.example tunables (committed); .env is generated
├── bin/
│ └── generate-credentials.sh
├── config/
│ ├── awcd.toml.example generated shape (committed); awcd.toml is generated
│ ├── worker.env.example generated shape (committed); worker.env is generated
│ └── control.env.example generated shape (committed); control.env is generated
├── seed/
│ ├── seed.sql deterministic identity rows (tenant/project/principal/host)
│ └── source/ fixture dir the snapshot-import flow uploads
├── smoke.sh end-to-end verification
├── clean.sh teardown
└── README.md in-tree pointer to this page