Commit Graph
3 Commits
Author SHA1 Message Date
Lucas RayanandClaude Opus 5 13fd2a9cee #21: ship the migrations and the Rspamd config inside images
The deployment failed on the migrate service. Coolify rewrites a relative
bind mount to a path under its own application directory, and when that
resolves to an empty directory golang-migrate finds no migrations and
exits 1 - with nothing useful in the deployment log to say why.

A deployment that pulls its images has no business also depending on
files being at a particular path on the host. Both mounts are now baked
into images built from the repository root:

  migrations - the schema, so migrate always has it
  rspamd     - actions.conf and ratelimit.conf

The Rspamd one mattered for a quieter reason: Rspamd starts happily with
an empty local.d, so a mount that resolved to nothing would have left the
rate limits and score thresholds unapplied. The defence would have looked
configured and done nothing.

Both images join the CI and release matrices, building from the root
context. Verified by building them, checking their contents, and bringing
the whole stack up - Rspamd reports its nine rate directives loaded from
the image.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-02 14:37:26 -03:00
Lucas RayanandClaude Opus 5 43dcea1ade #19: silence the CI annotations and stop cancelling a run on every merge
Concurrency. release.yml calls ci.yml, so a push to main started the
called workflow and the standalone one, both landing in the concurrency
group ci-<ref>. One cancelled the other and every merge left a cancelled
run behind. main is now excluded from the push trigger: release.yml is
already the entry point there, and pull_request still covers changes
before they land.

Node 20 deprecation (23 warnings). Every action was pinned to a major
that still declares node20. Each replacement was checked by reading the
action.yml at the new tag and confirming "using: node24", and the inputs
in use were confirmed to still exist there:

  checkout v4->v7, setup-node v4->v7, setup-go v5->v7,
  upload-artifact v4->v7, download-artifact v4->v7,
  setup-buildx-action v3->v4, build-push-action v6->v7,
  login-action v3->v4, metadata-action v5->v6

download-artifact has a v8, but v7 is also node24 and matches the upload
side, so the pair stays on one major. trivy-action is a composite action
and has no Node runtime to deprecate.

Go cache (1 warning). setup-go looked for go.sum at the repository root
and skipped its cache: defaults.run.working-directory applies to run
steps, not to actions. cache-dependency-path now points at
services/protocols/go.sum.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-02 08:20:19 -03:00
Lucas RayanandClaude Opus 5 46d421c874 #19: add Trivy scanning and multi-architecture GHCR publishing
CI gains a security job (Trivy fs: vuln, secret and misconfig) and an
image job that builds each of the four services and scans the result. The
secret scanner earns its place here: a DKIM key or Cloudflare token
committed by accident is a full compromise of every domain served.

release.yml publishes on a merge into main and on v* tags. Four services
on two architectures is eight build jobs pushing by digest, then four
publish jobs joining each pair into one manifest list, tagged latest, sha
and semver. github.repository is lowercased first because GHCR rejects an
uppercase path and this repository is lucasrguerra/LambdaMail.

Both scanners failed on first run, so this commit also fixes what they
found rather than shipping a workflow that is red on arrival:

  - All four images ran as root (DS-0002), which section 17 risk R5
    explicitly argues against. They now run unprivileged; protocols keeps
    its eight ports below 1024 through cap_net_bind_service on the binary
    rather than through being root.

  - A HIGH advisory in postcss, reached only through Next. "npm audit fix
    --force" would have resolved it by downgrading Next from 15 to 9, so
    it is pinned with an override instead.

  - The Node images copied the build stage's node_modules wholesale,
    shipping typescript, eslint and vitest into production, and the base
    image's own bundled npm accounted for the rest of the CVEs. Runtime
    dependencies now come from npm ci --omit=dev and npm is removed from
    the final layer.

  - compose-up could never have passed: the proxy network is external
    because Coolify owns it on the deploy host, and nothing creates it in
    CI, so compose refused to start the stack at all.

Verified locally: actionlint clean, all ten action refs confirmed to
exist, Trivy fs and all four image scans exit 0, the full stack reaches
Healthy on every container, and the containers run as uid 10001/1000 with
all eight privileged ports bound and SMTP answering on 25. The arm64
build itself is unverified - this machine has no buildx - and will first
be exercised by release.yml.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-02 07:58:02 -03:00