Compare commits
4
Commits
2db5915061
..
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
448bb59672 | ||
|
|
a6719c6018 | ||
|
|
1bcbe4d774 | ||
|
|
a3e071bb03 |
No files matched your search
@@ -18,8 +18,46 @@ env:
|
||||
REGISTRY: git.lucasrguerra.dev.br
|
||||
|
||||
jobs:
|
||||
# Nothing is published unless the full suite passes first.
|
||||
# The images are built from services/, migrations/ and config/ and nothing
|
||||
# else, so those three trees identify them. When an image for the same trees
|
||||
# is already in the registry (a docs-only merge, a workflow change, or a tag on
|
||||
# a commit that was just released), there is nothing to build: the new tags
|
||||
# are pointed at the existing manifest. This is what keeps tagging a release
|
||||
# from compiling the webmail a second time.
|
||||
plan:
|
||||
name: Plano (reaproveitar as imagens?)
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
inputs: ${{ steps.plan.outputs.inputs }}
|
||||
reuse: ${{ steps.plan.outputs.reuse }}
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
- id: plan
|
||||
env:
|
||||
REGISTRY: ${{ env.REGISTRY }}
|
||||
REPO: ${{ gitea.repository }}
|
||||
run: |
|
||||
inputs=$(git rev-parse HEAD:services HEAD:migrations HEAD:config | sha256sum | cut -c1-16)
|
||||
ns="${REPO,,}"
|
||||
reuse=true
|
||||
for s in protocols auth storage webmail migrations rspamd; do
|
||||
token=$(curl -fsS "https://$REGISTRY/v2/token?service=container_registry&scope=repository:$ns/$s:pull" | jq -r .token)
|
||||
code=$(curl -s -o /dev/null -w '%{http_code}' -I \
|
||||
-H "Authorization: Bearer $token" \
|
||||
-H 'Accept: application/vnd.oci.image.index.v1+json,application/vnd.docker.distribution.manifest.list.v2+json' \
|
||||
"https://$REGISTRY/v2/$ns/$s/manifests/inputs-$inputs")
|
||||
echo "$s: inputs-$inputs -> HTTP $code"
|
||||
[ "$code" = 200 ] || reuse=false
|
||||
done
|
||||
echo "inputs=$inputs" >> "$GITHUB_OUTPUT"
|
||||
echo "reuse=$reuse" >> "$GITHUB_OUTPUT"
|
||||
echo "reuse the published images: $reuse"
|
||||
|
||||
# Nothing is published unless the full suite passes first. Skipped when the
|
||||
# images are reused: the same trees already went through it.
|
||||
ci:
|
||||
needs: plan
|
||||
if: ${{ needs.plan.outputs.reuse != 'true' }}
|
||||
uses: ./.gitea/workflows/ci.yml
|
||||
|
||||
# Registry paths must be lowercase, and this repository is "lucasrguerra/LambdaMail".
|
||||
@@ -42,7 +80,8 @@ jobs:
|
||||
# magnitude slower.
|
||||
build:
|
||||
name: Build ${{ matrix.service }} (${{ matrix.platform }})
|
||||
needs: [ci, prepare]
|
||||
needs: [plan, ci, prepare]
|
||||
if: ${{ needs.plan.outputs.reuse != 'true' }}
|
||||
runs-on: ${{ matrix.runner }}
|
||||
permissions:
|
||||
contents: read
|
||||
@@ -104,7 +143,9 @@ jobs:
|
||||
|
||||
publish:
|
||||
name: Publica ${{ matrix.service }}
|
||||
needs: [build, prepare]
|
||||
needs: [plan, build, prepare]
|
||||
# build is skipped, not successful, when the images are reused.
|
||||
if: ${{ !cancelled() && needs.plan.result == 'success' && needs.prepare.result == 'success' && (needs.build.result == 'success' || needs.plan.outputs.reuse == 'true') }}
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
@@ -141,13 +182,20 @@ jobs:
|
||||
type=sha,format=long
|
||||
|
||||
# Joins both architectures under one manifest list, so `docker pull`
|
||||
# resolves to the right one automatically.
|
||||
- name: Cria o manifesto multi-arquitetura
|
||||
# resolves to the right one automatically. The inputs-<hash> tag is what
|
||||
# lets a later run recognise these images and skip the build.
|
||||
- name: Aplica as tags
|
||||
run: |
|
||||
image='${{ env.REGISTRY }}/${{ needs.prepare.outputs.namespace }}/${{ matrix.service }}'
|
||||
docker buildx imagetools create \
|
||||
$(jq -cr '.tags | map("-t " + .) | join(" ")' <<< '${{ steps.meta.outputs.json }}') \
|
||||
tags=$(jq -cr '.tags | map("-t " + .) | join(" ")' <<< '${{ steps.meta.outputs.json }}')
|
||||
inputs='${{ needs.plan.outputs.inputs }}'
|
||||
if [ '${{ needs.plan.outputs.reuse }}' = true ]; then
|
||||
# Same manifest under new names: no layer is read or written.
|
||||
docker buildx imagetools create $tags "$image:inputs-$inputs"
|
||||
else
|
||||
docker buildx imagetools create $tags -t "$image:inputs-$inputs" \
|
||||
"$image:build-${{ gitea.sha }}-amd64" "$image:build-${{ gitea.sha }}-arm64"
|
||||
fi
|
||||
|
||||
- name: Confere o resultado
|
||||
run: |
|
||||
@@ -172,7 +220,9 @@ jobs:
|
||||
gitea-release:
|
||||
name: Cria a release no Gitea
|
||||
needs: [publish, prepare]
|
||||
if: startsWith(gitea.ref, 'refs/tags/v')
|
||||
# Explicit, like publish: the build job upstream is skipped when the images
|
||||
# are reused, and the implicit success() would then skip this job too.
|
||||
if: ${{ !cancelled() && needs.publish.result == 'success' && startsWith(gitea.ref, 'refs/tags/v') }}
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
@@ -185,21 +235,7 @@ jobs:
|
||||
API: ${{ gitea.server_url }}/api/v1/repos/${{ gitea.repository }}
|
||||
NAMESPACE: ${{ env.REGISTRY }}/${{ needs.prepare.outputs.namespace }}
|
||||
run: |
|
||||
prev=$(git describe --tags --abbrev=0 "$TAG^" 2>/dev/null || true)
|
||||
range=${prev:+$prev..}$TAG
|
||||
{
|
||||
echo "## Imagens"
|
||||
echo
|
||||
echo '```'
|
||||
for s in protocols auth storage webmail migrations rspamd; do
|
||||
echo "$NAMESPACE/$s:${TAG#v}"
|
||||
done
|
||||
echo '```'
|
||||
echo
|
||||
echo "## Mudanças${prev:+ desde $prev}"
|
||||
echo
|
||||
git log --no-merges --format='- %s (%h)' "$range"
|
||||
} > notes.md
|
||||
scripts/release-notes.sh "$TAG" "$NAMESPACE" > notes.md
|
||||
pre=false; case "$TAG" in *-*) pre=true ;; esac
|
||||
jq -n --arg tag "$TAG" --arg name "LambdaMail ${TAG#v}" --rawfile body notes.md --argjson pre "$pre" \
|
||||
'{tag_name:$tag, name:$name, body:$body, prerelease:$pre}' > release.json
|
||||
|
||||
+10
-1
@@ -6,18 +6,23 @@ budget_tokens: 2000
|
||||
|
||||
> OpenWolf's learning memory. Updated automatically as the AI learns from interactions.
|
||||
> Do not edit manually unless correcting an error.
|
||||
> Last updated: 2026-10-06
|
||||
> Last updated: 2026-10-30
|
||||
|
||||
## User Preferences
|
||||
|
||||
<!-- How the user likes things done. Code style, tools, patterns, communication. -->
|
||||
- Writes in Portuguese (pt-BR); answer in Portuguese. Wants things finished end to end: merged, released, deployed and checked in production, without being asked again (said so on 2026-10-22).
|
||||
- Likes a clean interface with every function reachable (Apple / Anthropic as references, Outlook over Gmail), dark theme by default with a light one.
|
||||
- [2026-10-30] CI must be efficient: never redo work already done for the same content. Hash what goes into the artifact (the `inputs-<hash>` image tag, or a filtered `git ls-tree` hash), ask the registry, and skip CI and build when it exists; a version tag or a docs/workflow-only merge only re-points tags. Do not run `ci.yml` on `main` by push when `release.yml` already calls it. Use `paths-ignore` for docs and tool state (`.wolf`, `.claude`, `.superset`). The user repeated this several times and was angry when a tag was about to recompile the Next.js build.
|
||||
- [2026-10-30] Keep repositories clean: after a branch is merged, delete it on Gitea and locally (`git push gitea --delete <b>`, `git branch -d <b>`). Never touch the user's own unmerged branches.
|
||||
- [2026-10-30] Every tool gets semver tags and a Gitea Release (Releases tab, not just container images) with notes from the matching `CHANGELOG.md` section (`scripts/release-notes.sh`). Keep the changelog updated under Unreleased. A tag job that creates the release must use an explicit `if: ${{ !cancelled() && needs.<job>.result == 'success' && startsWith(gitea.ref, 'refs/tags/v') }}`.
|
||||
- [2026-10-30] Source of truth is Gitea (`git.lucasrguerra.dev.br`, remote `gitea`, SSH on port 22 through the host). The GitHub account is suspended (404): never push to or depend on GitHub. Workflows live in `.gitea/workflows` and use the `gitea.*` context.
|
||||
|
||||
## Key Learnings
|
||||
|
||||
- **Project:** LambdaMail
|
||||
- **Description:** A self-hosted mail server with an integrated webmail and admin console, built to
|
||||
- [2026-10-30] Release flow: `plan` job hashes `services/ migrations/ config/` → `inputs-<hash>`; reuse takes ~1 min instead of 30. A job downstream of a skipped `build` needs explicit status checks in its `if`. Images: `git.lucasrguerra.dev.br/lucasrguerra/lambdamail/<service>`. v1.0.0 was the first tag (no history survived from GitHub). Production deploy is blocked until the Coolify app's source is switched from Public GitHub to Gitea in the Coolify UI (API cannot); deploy only when the user is assured everything is right. Single `.env.example` + `scripts/init-env.sh` (`make env`).
|
||||
|
||||
## Do-Not-Repeat
|
||||
|
||||
@@ -31,6 +36,10 @@ budget_tokens: 2000
|
||||
- [2026-10-22] A `<Button>` inside a `<form>` submits it unless `type="button"` (Undo send used to resend the message).
|
||||
- [2026-10-22] Never run side effects inside a React state updater (it can run twice).
|
||||
- [2026-10-23] Inside a JS template literal `\\d` silently becomes `d`. For scripts embedded in template strings write `[0-9]` instead of `\\d`, and test the script's behaviour, not only that it is present.
|
||||
- [2026-10-30] On the Gitea runners never mount the job workspace into a container with `-v "$PWD:..."`: the Docker daemon (DinD) cannot see it and mounts an empty directory. Use `docker create` + `docker cp` + `docker start -a`.
|
||||
- [2026-10-30] Do not use `aquasecurity/trivy-action` (clones Trivy from the Gitea server), `actions/upload-artifact` v4+, the `gha` buildx cache or `rustsec/audit-check`. Run Trivy from `aquasec/trivy` with `-v trivy-cache:/root/.cache/`, use the registry for cache, `cargo audit` for Rust. The runner sets `GITHUB_OUTPUT`/`GITHUB_STEP_SUMMARY` but NOT `GITEA_OUTPUT` etc.
|
||||
- [2026-10-30] Pushing old tags to Gitea triggers the `.github/workflows/release.yml` found in those old commits (Gitea falls back to `.github` where `.gitea` is absent). They queue forever on missing macOS/Windows runners: cancel them (`POST /repos/{o}/{r}/actions/runs/{id}/cancel`) right after pushing tags.
|
||||
- [2026-10-30] The runner Docker daemons persist between jobs: steps that create networks/containers must be idempotent and clean up with `if: always()`.
|
||||
|
||||
## Decision Log
|
||||
|
||||
|
||||
+3
-1
@@ -87,7 +87,9 @@ remain are listed in the README under
|
||||
`git.lucasrguerra.dev.br/lucasrguerra/lambdamail/<service>` and the `v*` tag
|
||||
creates a release with notes. See [docs/RELEASING.md](docs/RELEASING.md).
|
||||
- CI caches npm, Go modules, the embedded Postgres, Trivy's database and image
|
||||
layers.
|
||||
layers. Release recognises images it already published for the same sources
|
||||
(`inputs-<hash>` tag), so a documentation change or a version tag never
|
||||
rebuilds them.
|
||||
- One `.env.example` for development and production, and `scripts/init-env.sh`
|
||||
to create `.env` with random secrets (`make env`). It replaces
|
||||
`.env.production.example`.
|
||||
|
||||
+3
-1
@@ -87,7 +87,9 @@ Gitea. O que ainda falta está no README, em
|
||||
`git.lucasrguerra.dev.br/lucasrguerra/lambdamail/<serviço>`, e a tag `v*` cria
|
||||
uma release com notas. Veja [docs/RELEASING.md](docs/RELEASING.md).
|
||||
- O CI guarda em cache o npm, os módulos Go, o Postgres embutido, o banco do
|
||||
Trivy e as camadas das imagens.
|
||||
Trivy e as camadas das imagens. O Release reconhece imagens que já publicou
|
||||
para os mesmos fontes (tag `inputs-<hash>`), então uma mudança de documentação
|
||||
ou uma tag de versão nunca as reconstrói.
|
||||
- Um único `.env.example` para desenvolvimento e produção, e o
|
||||
`scripts/init-env.sh` para criar o `.env` com segredos aleatórios
|
||||
(`make env`). Ele substitui o `.env.production.example`.
|
||||
|
||||
+18
-5
@@ -11,10 +11,22 @@ the Oracle server.
|
||||
| Workflow | Runs on | Does |
|
||||
| :--- | :--- | :--- |
|
||||
| `ci.yml` | every push and pull request | language lint, Go and TypeScript suites against a real Postgres, webmail tests, a Trivy scan of the repository and of each image, and the whole stack brought up with compose |
|
||||
| `release.yml` | a push to `main`, a `v*` tag, or by hand | runs `ci.yml` again, builds the six images for `linux/amd64` and `linux/arm64`, publishes them, and on a tag creates the release |
|
||||
| `release.yml` | a push to `main`, a `v*` tag, or by hand | builds the six images for `linux/amd64` and `linux/arm64` after `ci.yml` passes, publishes them, and on a tag creates the release |
|
||||
|
||||
Nothing is published unless the full suite passes first.
|
||||
|
||||
### Images are reused, not rebuilt
|
||||
|
||||
The images are built from `services/`, `migrations/` and `config/` and nothing
|
||||
else. `release.yml` hashes those three trees (the `inputs-<hash>` tag) and asks
|
||||
the registry whether images for that hash already exist. If they do, it skips
|
||||
the CI run and the build and only points the new tags (`latest`, `sha-<commit>`,
|
||||
the version) at the existing manifests, which takes about a minute.
|
||||
|
||||
So these never recompile anything: a documentation or workflow change merged
|
||||
to `main`, and a version tag pushed on a commit whose images were just
|
||||
published. A change under the three trees rebuilds everything, as it should.
|
||||
|
||||
Images go to the Gitea registry, one package per service:
|
||||
|
||||
```
|
||||
@@ -39,10 +51,11 @@ logging in.
|
||||
git push gitea v1.2.3
|
||||
```
|
||||
|
||||
The tag triggers `release.yml` once more. It republishes the images under
|
||||
`1.2.3`, `1.2` and `1` (layers are cached, so this is quick) and creates the
|
||||
entry under **Releases** with the image list and the commit log since the
|
||||
previous tag. A tag with a hyphen (`v1.3.0-rc.1`) is marked as a pre-release.
|
||||
The tag triggers `release.yml` once more. Because the commit was already
|
||||
released from `main`, the images are reused: it adds the tags `1.2.3`, `1.2` and
|
||||
`1` to them without building, and creates the entry under **Releases** with the
|
||||
image list and the commit log since the previous tag. Tagging a commit whose
|
||||
`main` run has not finished (or never ran) builds it from scratch. A tag with a hyphen (`v1.3.0-rc.1`) is marked as a pre-release.
|
||||
|
||||
Versions follow [Semantic Versioning](https://semver.org/): a change to a
|
||||
migration, a required environment variable, or a published port is at least a
|
||||
|
||||
Executable
+39
@@ -0,0 +1,39 @@
|
||||
#!/usr/bin/env bash
|
||||
# Prints the release notes for a tag: the images it publishes, then the matching
|
||||
# section of CHANGELOG.md (the commit log since the previous tag if the changelog
|
||||
# has no section for that version).
|
||||
#
|
||||
# scripts/release-notes.sh v1.2.3 git.example.com/owner/project
|
||||
set -euo pipefail
|
||||
|
||||
tag=${1:?usage: release-notes.sh <tag> <image-namespace>}
|
||||
namespace=${2:?usage: release-notes.sh <tag> <image-namespace>}
|
||||
version=${tag#v}
|
||||
|
||||
echo "## Images"
|
||||
echo
|
||||
echo '```'
|
||||
for service in protocols auth storage webmail migrations rspamd; do
|
||||
echo "$namespace/$service:$version"
|
||||
done
|
||||
echo '```'
|
||||
echo
|
||||
|
||||
section=""
|
||||
if [ -f CHANGELOG.md ]; then
|
||||
section=$(awk -v head="## [$version]" '
|
||||
index($0, head) == 1 { found = 1; next }
|
||||
found && /^## \[/ { exit }
|
||||
found && /^\[[^]]+\]: / { next }
|
||||
found { print }
|
||||
' CHANGELOG.md)
|
||||
fi
|
||||
|
||||
if [ -n "$(printf '%s' "$section" | tr -d '[:space:]')" ]; then
|
||||
printf '%s\n' "$section"
|
||||
else
|
||||
previous=$(git describe --tags --abbrev=0 "$tag^" 2>/dev/null || true)
|
||||
echo "## Changes${previous:+ since $previous}"
|
||||
echo
|
||||
git log --no-merges --format='- %s (%h)' "${previous:+$previous..}$tag"
|
||||
fi
|
||||
Reference in new issue
Block a user