aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFuwn <[email protected]>2026-02-26 17:18:01 -0800
committerFuwn <[email protected]>2026-02-26 17:18:01 -0800
commitf893796ef6aadcf0796d64e3afe3e2e72eab05d9 (patch)
tree992dbd926b06c6f4c07c6be37999aa6fff9a85de
parentfix: use non-prefixed semver docker image tag (diff)
downloadplutia-test-f893796ef6aadcf0796d64e3afe3e2e72eab05d9.tar.xz
plutia-test-f893796ef6aadcf0796d64e3afe3e2e72eab05d9.zip
chore: replace Makefile workflow with Taskfile tasks
-rw-r--r--Makefile16
-rw-r--r--README.md21
-rw-r--r--Taskfile.yaml340
3 files changed, 352 insertions, 25 deletions
diff --git a/Makefile b/Makefile
deleted file mode 100644
index f71992c..0000000
--- a/Makefile
+++ /dev/null
@@ -1,16 +0,0 @@
-VERSION ?= $(shell cat VERSION 2>/dev/null || echo dev)
-COMMIT ?= $(shell git rev-parse --short HEAD 2>/dev/null || echo unknown)
-BUILD_DATE ?= $(shell date -u +%Y-%m-%dT%H:%M:%SZ)
-
-LDFLAGS := -s -w \
- -X main.version=$(VERSION) \
- -X main.commit=$(COMMIT) \
- -X main.buildDate=$(BUILD_DATE)
-
-.PHONY: build test
-
-build:
- go build -trimpath -ldflags "$(LDFLAGS)" -o ./bin/plutia ./cmd/plutia
-
-test:
- go test ./...
diff --git a/README.md b/README.md
index 5db420f..2270aa7 100644
--- a/README.md
+++ b/README.md
@@ -28,9 +28,9 @@ Plutia is a deterministic, verifiable PLC mirror for `plc.directory` with signed
## Quick Start
```bash
-go test ./...
-go build ./cmd/plutia
-./plutia serve --config=config.default.yaml
+task build
+task verify:small
+task bench
```
### CLI Commands
@@ -64,7 +64,11 @@ go build -trimpath \
-o ./bin/plutia ./cmd/plutia
```
-`make build` provides the same pattern.
+Task runner equivalent:
+
+```bash
+task build
+```
## HTTP API
@@ -155,14 +159,13 @@ See [`config.default.yaml`](./config.default.yaml). Core knobs:
```yaml
services:
plutia:
- image: golang:1.25
- working_dir: /app
- command: sh -lc "go build -trimpath -o /app/bin/plutia ./cmd/plutia && /app/bin/plutia serve --config=/app/config.default.yaml"
+ image: ghcr.io/fuwn/plutia:0.1.0
+ command: ["plutia", "serve", "--config=/etc/plutia/config.yaml"]
ports:
- "8080:8080"
volumes:
- - ./:/app
- - ./data:/app/data
+ - ./config.default.yaml:/etc/plutia/config.yaml:ro
+ - ./data:/var/lib/plutia
restart: unless-stopped
```
diff --git a/Taskfile.yaml b/Taskfile.yaml
new file mode 100644
index 0000000..9bdc355
--- /dev/null
+++ b/Taskfile.yaml
@@ -0,0 +1,340 @@
+version: "3"
+
+vars:
+ BIN: ./bin/plutia
+ VERSION:
+ sh: |
+ if [ ! -f VERSION ]; then
+ echo "VERSION file is required" >&2
+ exit 1
+ fi
+ tr -d '\r\n' < VERSION
+ VERSION_TAG:
+ sh: |
+ if [ ! -f VERSION ]; then
+ echo "VERSION file is required" >&2
+ exit 1
+ fi
+ v="$(tr -d '\r\n' < VERSION)"
+ echo "${v#v}"
+ COMMIT:
+ sh: |
+ if ! command -v git >/dev/null 2>&1; then
+ echo "git is required to compute build metadata" >&2
+ exit 1
+ fi
+ if ! git rev-parse --is-inside-work-tree >/dev/null 2>&1; then
+ echo "git repository context is required" >&2
+ exit 1
+ fi
+ git rev-parse --short HEAD
+ BUILD_DATE:
+ sh: date -u +%Y-%m-%dT%H:%M:%SZ
+ LDFLAGS: -s -w -X main.version={{.VERSION}} -X main.commit={{.COMMIT}} -X main.buildDate={{.BUILD_DATE}}
+ TEST_MIRROR_KEY_HEX: 0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef
+
+tasks:
+ default:
+ desc: Show available tasks.
+ cmds:
+ - task --list
+
+ env:git:
+ internal: true
+ desc: Ensure git metadata is available for version injection.
+ preconditions:
+ - sh: command -v git >/dev/null 2>&1
+ msg: git is required but was not found in PATH
+ - sh: git rev-parse --is-inside-work-tree >/dev/null 2>&1
+ msg: task must run from inside the plutia git repository
+ cmds:
+ - echo "git metadata ready"
+
+ _reset-dir:
+ internal: true
+ requires:
+ vars: [DIR]
+ preconditions:
+ - sh: command -v trash >/dev/null 2>&1
+ msg: "trash command is required for safe cleanup"
+ cmds:
+ - |
+ if [ -e "{{.DIR}}" ]; then
+ trash "{{.DIR}}"
+ fi
+ - mkdir -p "{{.DIR}}"
+
+ _write-mirror-key:
+ internal: true
+ requires:
+ vars: [DIR]
+ cmds:
+ - mkdir -p "{{.DIR}}"
+ - printf "%s\n" "{{.TEST_MIRROR_KEY_HEX}}" > "{{.DIR}}/mirror.key"
+
+ build:
+ desc: Build plutia with version metadata and trimpath.
+ deps: [env:git]
+ cmds:
+ - mkdir -p ./bin
+ - go build -trimpath -ldflags '{{.LDFLAGS}}' -o {{.BIN}} ./cmd/plutia
+
+ build:release:
+ desc: Build a static release binary at ./bin/plutia.
+ deps: [env:git]
+ cmds:
+ - mkdir -p ./bin
+ - CGO_ENABLED=0 go build -trimpath -ldflags '{{.LDFLAGS}}' -o {{.BIN}} ./cmd/plutia
+
+ clean:
+ desc: Remove build outputs and runtime test data.
+ cmds:
+ - |
+ for path in \
+ ./bin \
+ ./tmp-test-data \
+ ./tmp-test-data-resolver \
+ ./tmp-test-data-corruption \
+ ./scale-test-data \
+ ./scale-test-data-resolver \
+ ./scale-test-data-resolver-stateonly \
+ ./scale-test-data-1m \
+ ./bench-data-mirror \
+ ./bench-data-resolver; do
+ if [ -e "$path" ]; then
+ rm -rf "$path"
+ fi
+ done
+
+ test:
+ desc: Run all tests once with no cache.
+ cmds:
+ - go test ./... -count=1
+
+ test:race:
+ desc: Run tests with the race detector.
+ cmds:
+ - go test -race ./...
+
+ lint:
+ desc: Run vet and staticcheck (if installed).
+ cmds:
+ - go vet ./...
+ - |
+ if command -v staticcheck >/dev/null 2>&1; then
+ staticcheck ./...
+ else
+ echo "staticcheck not installed; skipping"
+ fi
+
+ verify:small:
+ desc: Mirror/full verification on first 10k ops with checkpoint assertion.
+ deps: [build:release]
+ env:
+ PLUTIA_MODE: mirror
+ PLUTIA_VERIFY: full
+ PLUTIA_DATA_DIR: ./tmp-test-data
+ PLUTIA_CHECKPOINT_INTERVAL: "1000"
+ PLUTIA_BLOCK_SIZE_MB: "4"
+ PLUTIA_ZSTD_LEVEL: "9"
+ PLUTIA_MIRROR_PRIVATE_KEY_PATH: ./tmp-test-data/mirror.key
+ cmds:
+ - task: _reset-dir
+ vars: { DIR: ./tmp-test-data }
+ - task: _write-mirror-key
+ vars: { DIR: ./tmp-test-data }
+ - "{{.BIN}} replay --config=config.default.yaml --max-ops=10000 | tee ./tmp-test-data/verify-small.log"
+ - |
+ checkpoint_count="$(find ./tmp-test-data/checkpoints -maxdepth 1 -type f -name '*.json' ! -name '*.state.json' | wc -l | awk '{print $1}')"
+ if [ "${checkpoint_count}" -lt 1 ]; then
+ echo "verify:small failed: no checkpoint files generated" >&2
+ exit 1
+ fi
+ global_seq="$(awk '/replay complete at sequence/ {print $5}' ./tmp-test-data/verify-small.log | tail -n 1)"
+ latest_state="$(ls -1 ./tmp-test-data/checkpoints/*.state.json 2>/dev/null | sort | tail -n 1)"
+ did_count="0"
+ if [ -n "${latest_state}" ]; then
+ did_count="$(grep -o '"DID":"' "${latest_state}" | wc -l | awk '{print $1}')"
+ fi
+ echo "verify:small summary"
+ echo " global_seq=${global_seq:-unknown}"
+ echo " checkpoint_files=${checkpoint_count}"
+ echo " did_count=${did_count}"
+ echo " disk_total=$(du -sh ./tmp-test-data | awk '{print $1}')"
+
+ verify:scale:
+ desc: Mirror/full 200k verification with throughput and storage metrics.
+ deps: [build:release]
+ env:
+ PLUTIA_MODE: mirror
+ PLUTIA_VERIFY: full
+ PLUTIA_DATA_DIR: ./scale-test-data
+ PLUTIA_MIRROR_PRIVATE_KEY_PATH: ./scale-test-data/mirror.key
+ cmds:
+ - task: _reset-dir
+ vars: { DIR: ./scale-test-data }
+ - task: _write-mirror-key
+ vars: { DIR: ./scale-test-data }
+ - "{{.BIN}} bench --config=config.default.yaml --max-ops=200000 --interval=15s | tee ./scale-test-data/verify-scale.log"
+ - |
+ latest_state="$(ls -1 ./scale-test-data/checkpoints/*.state.json 2>/dev/null | sort | tail -n 1)"
+ did_count="0"
+ if [ -n "${latest_state}" ]; then
+ did_count="$(grep -o '"DID":"' "${latest_state}" | wc -l | awk '{print $1}')"
+ fi
+ echo "verify:scale summary"
+ grep 'bench_total' ./scale-test-data/verify-scale.log | tail -n 1
+ grep 'bench_disk' ./scale-test-data/verify-scale.log | tail -n 1
+ echo "did_count=${did_count}"
+ echo "ops_dir=$(du -sh ./scale-test-data/ops | awk '{print $1}')"
+ echo "index_dir=$(du -sh ./scale-test-data/index | awk '{print $1}')"
+ echo "checkpoints_dir=$(du -sh ./scale-test-data/checkpoints | awk '{print $1}')"
+
+ verify:1m:
+ desc: Mirror/full 1M replay with 50k checkpoint interval and timing extraction.
+ deps: [build:release]
+ env:
+ PLUTIA_MODE: mirror
+ PLUTIA_VERIFY: full
+ PLUTIA_DATA_DIR: ./scale-test-data-1m
+ PLUTIA_CHECKPOINT_INTERVAL: "50000"
+ PLUTIA_MIRROR_PRIVATE_KEY_PATH: ./scale-test-data-1m/mirror.key
+ cmds:
+ - task: _reset-dir
+ vars: { DIR: ./scale-test-data-1m }
+ - task: _write-mirror-key
+ vars: { DIR: ./scale-test-data-1m }
+ - "{{.BIN}} replay --config=config.default.yaml --max-ops=1000000 2>&1 | tee ./scale-test-data-1m/verify-1m.log"
+ - |
+ echo "verify:1m checkpoint timing metrics"
+ if ! grep -q 'checkpoint_metrics' ./scale-test-data-1m/verify-1m.log; then
+ echo "verify:1m failed: no checkpoint metrics emitted" >&2
+ exit 1
+ fi
+ grep 'checkpoint_metrics' ./scale-test-data-1m/verify-1m.log
+
+ verify:resolver:
+ desc: Resolver/full 200k replay with no block archive assertion.
+ deps: [build:release]
+ env:
+ PLUTIA_MODE: resolver
+ PLUTIA_VERIFY: full
+ PLUTIA_DATA_DIR: ./scale-test-data-resolver
+ PLUTIA_CHECKPOINT_INTERVAL: "1000000000"
+ cmds:
+ - task: _reset-dir
+ vars: { DIR: ./scale-test-data-resolver }
+ - "{{.BIN}} replay --config=config.default.yaml --max-ops=200000 | tee ./scale-test-data-resolver/verify-resolver.log"
+ - |
+ block_files="$(find ./scale-test-data-resolver/ops -maxdepth 1 -type f -name '*.zst' | wc -l | awk '{print $1}')"
+ if [ "${block_files}" -ne 0 ]; then
+ echo "verify:resolver failed: expected zero block files, found ${block_files}" >&2
+ exit 1
+ fi
+ echo "verify:resolver summary"
+ awk '/replay complete at sequence/ {print}' ./scale-test-data-resolver/verify-resolver.log | tail -n 1
+ echo "block_files=${block_files}"
+ echo "ops_dir=$(du -sh ./scale-test-data-resolver/ops | awk '{print $1}')"
+ echo "index_dir=$(du -sh ./scale-test-data-resolver/index | awk '{print $1}')"
+ echo "checkpoints_dir=$(du -sh ./scale-test-data-resolver/checkpoints | awk '{print $1}')"
+ echo "total_dir=$(du -sh ./scale-test-data-resolver | awk '{print $1}')"
+
+ verify:corruption:
+ desc: Validate corruption detection by tampering with a persisted block file.
+ deps: [build:release]
+ env:
+ PLUTIA_MODE: mirror
+ PLUTIA_VERIFY: full
+ PLUTIA_DATA_DIR: ./tmp-test-data-corruption
+ PLUTIA_CHECKPOINT_INTERVAL: "1000"
+ PLUTIA_BLOCK_SIZE_MB: "4"
+ PLUTIA_MIRROR_PRIVATE_KEY_PATH: ./tmp-test-data-corruption/mirror.key
+ cmds:
+ - task: _reset-dir
+ vars: { DIR: ./tmp-test-data-corruption }
+ - task: _write-mirror-key
+ vars: { DIR: ./tmp-test-data-corruption }
+ - "{{.BIN}} replay --config=config.default.yaml --max-ops=5000 > ./tmp-test-data-corruption/initial.log"
+ - |
+ block_file="$(find ./tmp-test-data-corruption/ops -maxdepth 1 -type f -name '*.zst' | sort | head -n 1)"
+ if [ -z "${block_file}" ]; then
+ echo "verify:corruption failed: no block file was created" >&2
+ exit 1
+ fi
+ printf 'tamper' >> "${block_file}"
+ - |
+ if {{.BIN}} replay --config=config.default.yaml --max-ops=5000 > ./tmp-test-data-corruption/restart.log 2>&1; then
+ echo "verify:corruption failed: replay succeeded after tampering" >&2
+ exit 1
+ fi
+ if ! grep -qi 'corruption' ./tmp-test-data-corruption/restart.log; then
+ echo "verify:corruption failed: corruption was not reported" >&2
+ cat ./tmp-test-data-corruption/restart.log
+ exit 1
+ fi
+ echo "verify:corruption passed (corruption detected)"
+
+ bench:
+ desc: Run the default benchmark (mirror/full, 200k ops).
+ cmds:
+ - task: bench:mirror
+
+ bench:mirror:
+ desc: Mirror/full benchmark at 200k ops.
+ deps: [build:release]
+ env:
+ PLUTIA_MODE: mirror
+ PLUTIA_VERIFY: full
+ PLUTIA_DATA_DIR: ./bench-data-mirror
+ PLUTIA_MIRROR_PRIVATE_KEY_PATH: ./bench-data-mirror/mirror.key
+ cmds:
+ - task: _reset-dir
+ vars: { DIR: ./bench-data-mirror }
+ - task: _write-mirror-key
+ vars: { DIR: ./bench-data-mirror }
+ - "{{.BIN}} bench --config=config.default.yaml --max-ops=200000 --interval=10s"
+
+ bench:resolver:
+ desc: Resolver/full benchmark at 200k ops.
+ deps: [build:release]
+ env:
+ PLUTIA_MODE: resolver
+ PLUTIA_VERIFY: full
+ PLUTIA_DATA_DIR: ./bench-data-resolver
+ PLUTIA_CHECKPOINT_INTERVAL: "1000000000"
+ cmds:
+ - task: _reset-dir
+ vars: { DIR: ./bench-data-resolver }
+ - "{{.BIN}} bench --config=config.default.yaml --max-ops=200000 --interval=10s"
+
+ release:local:
+ desc: Build a static binary, print sha256, and report version metadata.
+ deps: [build:release]
+ cmds:
+ - |
+ if command -v sha256sum >/dev/null 2>&1; then
+ sha256sum {{.BIN}}
+ elif command -v shasum >/dev/null 2>&1; then
+ shasum -a 256 {{.BIN}}
+ else
+ echo "sha256 tool unavailable (need sha256sum or shasum)" >&2
+ exit 1
+ fi
+ - "{{.BIN}} version"
+
+ docker:build:
+ desc: Build the Plutia container image with the current semantic version.
+ preconditions:
+ - sh: command -v docker >/dev/null 2>&1
+ msg: docker CLI is required for docker:build
+ cmds:
+ - docker build -t ghcr.io/fuwn/plutia:{{.VERSION_TAG}} .
+
+ docker:run:
+ desc: Run Plutia container with mounted config and persistent data.
+ preconditions:
+ - sh: command -v docker >/dev/null 2>&1
+ msg: docker CLI is required for docker:run
+ cmds:
+ - mkdir -p ./data
+ - docker run --rm -p 8080:8080 -v "${PWD}/config.default.yaml:/etc/plutia/config.yaml:ro" -v "${PWD}/data:/var/lib/plutia" ghcr.io/fuwn/plutia:{{.VERSION_TAG}} plutia serve --config=/etc/plutia/config.yaml