Phase 1: Foundation Types (100% complete)
- query_helpers: SQL LIKE sanitization with pipe operators
- numeric: Integer parsing with pattern matching guards
- result: Pure Elixir Result monad (map, and_then, unwrap_or)
Phase 2: Ecto Domain Types (100% complete)
- ip_address: IPv4/IPv6 validation using :inet directly
- mac_address: Multi-format MAC parsing (colon/hyphen/dot/compact)
- snmp_oid: OID parsing/manipulation with recursive pattern matching
All 198 tests passing across converted modules.
API changed from Gleam-style {:some/:none to idiomatic {:ok/:error.
Refactored parse_numeric_oid to use with statement, reducing nesting depth.
Reviewed-on: graham/towerops-web#196
The production deployment was failing with "operation not permitted" errors
when BuildKit tried to perform bind mounts during Docker image builds.
Root cause:
- The workflow starts Docker with --storage-driver=vfs for unprivileged runners
- VFS storage driver doesn't support the advanced mount operations needed by BuildKit
- The Dockerfile uses --mount=type=cache for build performance
- Using buildx driver=docker tried to use the daemon's VFS storage directly
Solution:
- Switch to driver=docker-container which runs BuildKit in an isolated container
- This container has its own storage layer that properly supports BuildKit features
- BuildKit container uses overlay2 internally regardless of host storage driver
This is the recommended approach for CI/CD environments with storage limitations.
Reviewed-on: graham/towerops-web#191
Dependency update PRs from Renovate don't need to be deployed to
staging. Uses job-level condition to check head branch prefix.
Reviewed-on: graham/towerops-web#124
Replace the protobuf hex package (agent.pb.ex) and hand-written validator
(validator.ex) with a pure Gleam implementation: wire format primitives,
all 23 message types, encode/decode functions, and validation merged into
decode. Thin Elixir wrappers in agent.ex preserve the existing struct API.
- Wire format: varint, length-delimited, double (IEEE 754 via FFI), tag encode/decode
- Types: all proto3 message types as Gleam custom types with enums and oneof
- Decode: field-level parsing with integrated validation (UUID, IP, hostname, limits)
- Encode: proto3 default omission, bytes_tree concatenation, map/repeated fields
- Wrappers: Elixir structs with encode/decode delegating to Gleam, enum atom conversion
- Remove {:protobuf, "~> 0.12"} dependency
Reviewed-on: graham/towerops-web#104
Set up mix_gleam archive, gleam_stdlib, and gleeunit deps. Add Gleam
compiler to all Dockerfiles, CI workflows, nix shell, and .tool-versions.
Rewrite Numeric, QueryHelpers, and URLValidator from Elixir to Gleam with
full ExUnit test coverage. Update all callers to use the Gleam modules
directly via :towerops@module syntax. Remove duplicate sanitize_like/1
private functions in snmp.ex and trace.ex.
Reviewed-on: graham/towerops-web#98
Moves e2e tests from production workflow into a separate workflow that requires manual triggering. This allows e2e tests to be run on-demand without blocking production deployments.
Changes:
- Created new .forgejo/workflows/e2e-tests.yaml with workflow_dispatch trigger
- Removed disabled e2e job from production.yaml workflow
- E2E tests can now be triggered manually from Actions tab with browser selection (all, chromium, firefox, webkit)
- Test results and videos uploaded as artifacts on all runs
Reviewed-on: graham/towerops-web#66
The docker buildx driver (required on unprivileged ci runner) does not
support registry cache export/import. Remove cache-from and cache-to
options to unblock builds.
Reviewed-on: graham/towerops-web#62
overlayfs requires mount/unmount capabilities not available on the
unprivileged ci runner. Switch to vfs storage driver and use buildx
docker driver (not docker-container) to avoid nested mount issues.
Reviewed-on: graham/towerops-web#61
The new CI server doesn't have Docker daemon running by default,
causing buildx setup to fail with:
ERROR: failed to connect to the docker API at unix:///var/run/docker.sock
This fix:
- Adds explicit Docker daemon startup check and initialization
- Configures buildx with docker-container driver
- Adds install: true to ensure proper buildx installation
This was already fixed in build-deploy.yml (commit 54280fd7) but
was missing from production.yaml.
Reviewed-on: graham/towerops-web#57
The styfle/cancel-workflow-action is not compatible with Forgejo Actions,
causing failures with 'Cannot read properties of undefined (reading id)'.
The pr-tests.yaml workflow already has concurrency control that cancels
in-progress runs when new commits are pushed, which handles most cases.
Removing this workflow to eliminate CI errors.
Reviewed-on: graham/towerops-web#38
The cancel-workflow-action requires an access_token to query and cancel
running workflows. Without it, the action fails with 'Cannot read properties
of undefined' error when trying to access workflow information.
Reviewed-on: graham/towerops-web#36
- Updated .tool-versions to use Node 22.17.1
- Updated CI workflow to use Node 22
- Rebuilt package-lock.json with Node 22
- Fixes engine compatibility warnings from @noble/hashes
The if: success() might be causing issues with Forgejo's secret handling.
Jobs with 'needs' automatically wait for dependencies to succeed, making
the condition redundant.
Playwright config:
- Added chromium-unauthenticated project for registration/login tests
- These tests run without saved auth state (as intended)
- Other tests continue using authenticated state
Workflow:
- build-and-deploy now only requires test-exunit to pass
- test-e2e runs in parallel but doesn't block deployment
- Allows faster deployments while e2e tests provide additional validation
Created seeds_e2e.exs to automatically create a test user with TOTP enabled
for Playwright e2e tests. This ensures tests can authenticate in CI without
requiring manual user setup.
Test credentials (for CI only):
- Email: test@example.com
- Password: TestPassword123!
- TOTP Secret: JBSWY3DPEHPK3PXP (fixed for reproducibility)
The workflow now:
1. Loads database structure
2. Seeds test user with TOTP
3. Passes credentials to e2e tests via environment variables
- Run ldconfig after installing OpenSSL packages
- Add verification steps to check OpenSSL installation
- Should resolve 'libcrypto.so.1.1: cannot open shared object file' error
Simplified CI/CD to two workflows:
- staging.yaml: Deploys to Dokku on main push (no tests)
- production.yaml: Full test suite + deploy on production push
Main branch deploys immediately to staging for fast iteration.
Production branch has comprehensive test gates before deployment.
E2E tests now only run before production deploys for faster feedback:
- test.yaml (main branch): ExUnit, formatting, Credo only
- staging.yaml (main branch): Immediate Dokku deploy
- production.yaml (production branch): Full test suite including e2e
This allows staging to deploy quickly while ensuring production
has comprehensive test coverage including browser automation.
- Renamed test.yml → test.yaml
- Renamed production.yml → production.yaml
- Split build.yaml into staging.yaml (Dokku only)
- Removed duplicate production build from build.yaml
Production deployments now use production.yaml which includes:
- Test gates (ExUnit + e2e tests must pass)
- Docker build and push
- k8s deployment.yaml update
- FluxCD auto-deployment
Staging deployments use staging.yaml:
- Deploys to Dokku on push to main
- No test gates (tests run via test.yaml)
This eliminates the duplicate production builds that were running
from both build.yaml and production.yml simultaneously.
FluxCD Changes:
- Updated GitRepository resource to watch 'production' branch instead of 'main'
- Deployment now automatically pulls from production branch where CI updates image tags
CI Changes:
- Added libssl-dev to system dependencies in both test.yml and production.yml
- Fixes "libcrypto.so.1.1: cannot open shared object file" error in CI runners
This ensures production deployments work end-to-end:
1. Push to production branch triggers CI
2. CI tests pass, builds image, updates k8s/deployment.yaml
3. FluxCD detects change in production branch and applies to cluster
Adds Forgejo Actions CI/CD workflows:
Production deployment (.forgejo/workflows/production.yml):
- Runs on push to 'production' branch
- Test gates (must pass before deploy):
* All ExUnit tests with coverage
* All e2e Playwright tests
- Only deploys if both test suites pass
- Builds Docker image
- Pushes to git.mcintire.me registry
- Updates k8s/deployment.yaml with new image tag
- FluxCD auto-applies changes
Main branch testing (.forgejo/workflows/test.yml):
- Runs on push to 'main' or pull requests
- Runs ExUnit tests with coverage
- Runs e2e tests
- No deployment (staging still via Dokku)
Updated CLAUDE.md with CI/CD pipeline documentation.