10 Commits
Author SHA1 Message Date
jcoffey-dev 42900ae1d5 Merge pull request 'Point links at git.coffeylabs.org after the move from GitHub' (#5) from fix/links-after-move into main
ci / test (push) Successful in 2m34s
ci / release (push) Skipped
2026-09-22 16:11:59 +00:00
jcoffey-dev f1aff4ba07 Point links at git.coffeylabs.org after the move from GitHub
ci / test (pull_request) Successful in 2m4s
ci / release (pull_request) Skipped
GitHub took the organization's repos and GHCR offline on 2026-09-20. Repo,
release, raw-file and clone links now go to Gitea at git.coffeylabs.org,
container images to registry.coffeylabs.org, and GitLab-style /-/blob paths
to Gitea's /src/branch form. Go module paths are identifiers and stay as
they are; links to GitHub issues and pull requests are left as history.
2026-09-22 09:08:15 -07:00
jcoffey-dev f5478cb50e Merge pull request 'ci: run socket-free jobs on the light label' (#4) from ci/light-runner into main
ci / test (push) Successful in 1m0s
ci / release (push) Skipped
2026-09-22 13:52:18 +00:00
jcoffey-dev f45ef16d98 ci: run socket-free jobs on the light label
ci / test (pull_request) Successful in 1m8s
ci / release (pull_request) Skipped
Both runners carry `light` (host1, and host2 over the wg-hosts link), so
these jobs run on whichever host is free. Jobs that mount the docker socket
keep `runs-on: docker`, which only host1 has.
2026-09-22 06:45:29 -07:00
jcoffey-dev 139f7e73da Merge branch 'ci/gitea-actions' into 'main'
ci / test (push) Successful in 4m6s
ci / release (push) Skipped
ci: add Gitea Actions workflow

See merge request coffey-labs/stalwart-migrator!3
2026-09-21 23:22:30 -07:00
jcoffey-dev 8ba5d58f76 ci: add Gitea Actions workflow ported from .gitlab-ci.yml
ci / test (pull_request) Successful in 2m48s
ci / release (pull_request) Skipped
ci / test (push) Successful in 3m4s
ci / release (push) Failing after 1m59s
2026-09-21 22:44:22 -07:00
jcoffey-dev 5760eed824 Merge branch 'ci/release-permalink' into 'main'
Give releases a stable latest-download URL

See merge request coffey-labs/stalwart-migrator!2
2026-09-20 21:15:20 -07:00
jcoffey-dev c4e496d3e1 Give releases a stable latest-download URL
The install guide tells people to curl
  .../releases/latest/download/<file>
which is a GitHub URL shape. GitLab's equivalent is
  /-/releases/permalink/latest/downloads/<path>
but it only resolves for assets that declare direct_asset_path, and the
release job was creating plain links to the package registry. Those carry
the tag in the URL, so they can never be a "latest" link.

Each asset now also declares /binaries/<file>, which is what the docs will
point at. The path is load-bearing: changing it breaks a documented install
command.
2026-09-20 20:58:31 -07:00
jcoffey-dev a83dc51158 Merge branch 'ci/gitlab-pipeline' into 'main'
Run CI on the self-hosted GitLab

See merge request coffey-labs/stalwart-migrator!1
2026-09-20 20:36:05 -07:00
jcoffey-dev 7b07bd6bbb Run releases on the self-hosted GitLab
Ports .github/workflows/release.yml after the GitHub account was suspended.
Same shape: tag-driven, amd64 and arm64, SOURCE_DATE_EPOCH for reproducible
tarballs, and the same refusal to release a tag that is not an ancestor of
the default branch.

The publishing half had to change. There is no `gh release`, so the
tarballs go to the project's generic package registry and the Release is
created with release-cli pointing at them. The install guide sends people
straight at release asset URLs, so uploading before creating the Release is
deliberate: a Release whose assets 404 is worse than no Release.

The Actions workflow stays in the tree as the reference.
2026-09-20 20:12:41 -07:00
4 changed files with 209 additions and 5 deletions
+96
View File
@@ -0,0 +1,96 @@
# CI on the self-hosted Gitea, ported from .gitlab-ci.yml during the move off
# GitLab (2026-09-22). Gitea reads .gitea/workflows and ignores .github/ once
# this directory exists; .github/workflows stays as it was for GitHub.
#
# Every job runs in an image pinned by digest (tag in the trailing comment),
# and the only action used is coffey-labs/actions/checkout pinned by SHA. The
# instance resolves short `uses:` against itself, never GitHub, so nothing
# unreviewed can be pulled in.
#
# The shape is the same as before -- tag-driven, amd64 and arm64,
# reproducible. GitLab needed a generic package registry plus release-cli
# links; Gitea attaches the tarballs to the Release itself, as GitHub did, so
# the build and the release are one job and nothing is handed between jobs.
name: ci
on:
push:
branches: [main]
tags: ["v*"]
pull_request:
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
test:
runs-on: light
container:
image: golang:1.26-bookworm@sha256:a688600ca24f8a4d3ca77f95b0dd40704a9fc787c826660eb7ba0b641b8b175d # 1.26-bookworm
steps:
- uses: coffey-labs/actions/checkout@fab0c4d45e0162963965f1555df27b7bed5e20ec
- run: go vet ./...
- run: go test ./...
# Kept as `go run ...@latest` exactly as the workflow had it: the point
# of a vulnerability check is to use today's database, not a pinned copy
# of last month's.
- run: go run golang.org/x/vuln/cmd/govulncheck@latest ./...
release:
if: startsWith(github.ref, 'refs/tags/')
needs: [test]
runs-on: light
container:
image: golang:1.26-bookworm@sha256:a688600ca24f8a4d3ca77f95b0dd40704a9fc787c826660eb7ba0b641b8b175d # 1.26-bookworm
steps:
# Full history: the ancestry check below cannot be answered from a
# shallow clone. The checkout also fetches every branch as origin/*.
- uses: coffey-labs/actions/checkout@fab0c4d45e0162963965f1555df27b7bed5e20ec
with:
fetch-depth: 0
# The workflow refused to release a tag that is not an ancestor of main,
# so that a release can never describe code that was never reviewed onto
# the default branch.
- shell: bash
env:
TAG: ${{ github.ref_name }}
run: |
git merge-base --is-ancestor "$(git rev-parse "${TAG}^{commit}")" origin/main \
|| { echo "!! $TAG is not on main"; exit 1; }
# SOURCE_DATE_EPOCH is what makes the tarballs reproducible: without it
# every build stamps a new mtime and two builds of one tag differ.
- shell: bash
env:
TAG: ${{ github.ref_name }}
run: |
SOURCE_DATE_EPOCH="$(git log -1 --format=%ct "$TAG")" scripts/build-release.sh "$TAG" dist
sha256sum dist/*.tar.gz
# Create the Release, then attach every file. Archive names carry no
# version, so /releases/latest/download/<name> always means the newest.
# The API is reached on the internal address so the uploads never cross
# Cloudflare. If an upload fails the half-made Release is deleted: a
# Release whose assets 404 is worse than no Release, since the install
# guide sends people straight at these URLs.
- shell: bash
env:
TAG: ${{ github.ref_name }}
TOKEN: ${{ secrets.GITHUB_TOKEN }}
REPO: ${{ github.repository }}
run: |
set -euo pipefail
# CI_SERVER_INTERNAL is set on every job container by the runner.
API="$CI_SERVER_INTERNAL/api/v1/repos/$REPO"
auth=(--header "Authorization: token $TOKEN")
id=$(curl --fail --silent --show-error "${auth[@]}" \
--header "Content-Type: application/json" \
--data "{\"tag_name\":\"$TAG\",\"name\":\"$TAG\",\"body\":\"Binaries for linux/amd64 and linux/arm64. Verify with SHA256SUMS.\"}" \
"$API/releases" | grep -o '^{"id":[0-9]*' | cut -d: -f2)
[ -n "$id" ] || { echo "!! could not create the release"; exit 1; }
for f in dist/*; do
n=$(basename "$f")
echo "uploading $n"
curl --fail --silent --show-error --output /dev/null "${auth[@]}" \
--form "attachment=@$f" "$API/releases/$id/assets?name=$n" \
|| { curl --silent "${auth[@]}" -X DELETE "$API/releases/$id"; exit 1; }
done
+108
View File
@@ -0,0 +1,108 @@
# CI on the self-hosted GitLab, ported from .github/workflows/release.yml when
# the GitHub account was suspended on 2026-09-20. The Actions file stays in the
# tree: it is the reference this was written from and works unchanged if the
# appeal succeeds.
#
# The shape is the same -- tag-driven, amd64 and arm64, reproducible -- but the
# publishing half is necessarily different. There is no `gh release`, so the
# tarballs go to this project's generic package registry and the Release is
# created with release-cli, linking to them. The docs guide installs from
# release assets, so those links are the part that has to keep working.
#
# Images are pinned by digest, with the tag in the trailing comment: the
# replacement for the workflow's SHA-pinned actions, since GitLab has no
# action allowlist.
stages: [test, build, release]
variables:
PKG: "${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/packages/generic/stalwart-migrate"
default:
interruptible: true
.go: &go
image: golang:1.26-bookworm@sha256:a688600ca24f8a4d3ca77f95b0dd40704a9fc787c826660eb7ba0b641b8b175d # 1.26-bookworm
cache:
key: go-mod
paths: [.gocache/]
variables:
GOPATH: "$CI_PROJECT_DIR/.gocache"
test:
<<: *go
stage: test
script:
- go vet ./...
- go test ./...
# Kept as `go run ...@latest` exactly as the workflow had it: the point of
# a vulnerability check is to use today's database, not a pinned copy of
# last month's.
- go run golang.org/x/vuln/cmd/govulncheck@latest ./...
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
- if: $CI_COMMIT_TAG
build:
<<: *go
stage: build
needs: [test]
script:
# The workflow refused to release a tag that is not an ancestor of main,
# so that a release can never describe code that was never reviewed onto
# the default branch. GIT_DEPTH is unset below to make the ancestry
# available -- a shallow clone cannot answer this.
- git fetch --quiet origin "$CI_DEFAULT_BRANCH"
- |
git merge-base --is-ancestor "$(git rev-parse "${CI_COMMIT_TAG}^{commit}")" "origin/$CI_DEFAULT_BRANCH" \
|| { echo "!! $CI_COMMIT_TAG is not on $CI_DEFAULT_BRANCH"; exit 1; }
# SOURCE_DATE_EPOCH is what makes the tarballs reproducible: without it
# every build stamps a new mtime and two builds of one tag differ.
- SOURCE_DATE_EPOCH="$(git log -1 --format=%ct "$CI_COMMIT_TAG")" scripts/build-release.sh "$CI_COMMIT_TAG" dist
- sha256sum dist/*.tar.gz
variables:
GIT_DEPTH: "0"
artifacts:
paths: [dist/]
expire_in: 1 week
rules:
- if: $CI_COMMIT_TAG
release:
stage: release
image: registry.gitlab.com/gitlab-org/cli:latest@sha256:3f0a591b3b96c39ac8e28480ee99bb93201b7bcea1fbca7ed50c034098111db2 # latest
needs: [build]
script:
# Upload first, then create the Release pointing at what was uploaded. A
# Release whose assets 404 is worse than no Release: the install guide
# sends people straight at these URLs.
- |
set -eu
for f in dist/*; do
n=$(basename "$f")
echo "uploading $n"
curl --fail --silent --show-error \
--header "JOB-TOKEN: ${CI_JOB_TOKEN}" \
--upload-file "$f" \
"${PKG}/${CI_COMMIT_TAG}/${n}"
done
- |
set -eu
args=""
for f in dist/*; do
n=$(basename "$f")
# direct_asset_path is what makes the permalink work. Without it the
# only stable URL is the package registry one, which carries the tag
# and so cannot be a "latest" link. With it, every release exposes
# /-/releases/permalink/latest/downloads/binaries/<file>
# which is the GitLab equivalent of the GitHub
# /releases/latest/download/<file> URL the install guide has always
# used. Changing this path breaks documented install commands.
args="$args --assets-link {\"name\":\"${n}\",\"url\":\"${PKG}/${CI_COMMIT_TAG}/${n}\",\"direct_asset_path\":\"/binaries/${n}\"}"
done
# shellcheck disable=SC2086
release-cli create --name "$CI_COMMIT_TAG" --tag-name "$CI_COMMIT_TAG" \
--description "Binaries for linux/amd64 and linux/arm64. Verify with SHA256SUMS." $args
rules:
- if: $CI_COMMIT_TAG
+1 -1
View File
@@ -27,7 +27,7 @@ write access to the checkpoint directory.
Releases are tagged by date, like ihasmail's: `v2026.9.15`, with `.1`, `.2`
added for another release the same day. Binaries for `linux/amd64` and
`linux/arm64` and a `SHA256SUMS` file are attached to every
[release](https://github.com/Coffey-Labs/stalwart-migrator/releases).
[release](https://git.coffeylabs.org/coffey-labs/stalwart-migrator/releases).
Every release is built by the [release workflow](.github/workflows/release.yml)
from a tagged commit on `main`, after the tests and a known-vulnerabilities
+4 -4
View File
@@ -1,6 +1,6 @@
# stalwart-migrator
[![Latest release](https://img.shields.io/github/v/release/Coffey-Labs/stalwart-migrator?sort=date)](https://github.com/Coffey-Labs/stalwart-migrator/releases/latest)
[![Latest release](https://img.shields.io/gitea/v/release/coffey-labs/stalwart-migrator?gitea_url=https%3A%2F%2Fgit.coffeylabs.org)](https://git.coffeylabs.org/coffey-labs/stalwart-migrator/releases/latest)
[![License: GPL-3.0-or-later](https://img.shields.io/badge/license-GPL--3.0--or--later-blue)](LICENSE)
[![Docs: docs.ihasmail.org](https://img.shields.io/badge/docs-docs.ihasmail.org-0ea5e9)](https://docs.ihasmail.org/install/stalwart-migrator/)
@@ -9,7 +9,7 @@ checkpoint at every step so an interrupted run resumes instead of restarting,
and automated validation that the server still works afterwards. Go, standard
library only.
A companion to [**ihasmail**](https://github.com/Coffey-Labs/ihasmail), a
A companion to [**ihasmail**](https://git.coffeylabs.org/coffey-labs/ihasmail), a
JMAP-first webmail client for Stalwart. That one is what you read your mail in;
this one gets the server underneath it onto a version that speaks the protocol
it needs.
@@ -60,8 +60,8 @@ Details: [Known Stalwart problems](docs/known-stalwart-problems.md).
```sh
ARCH=amd64 # or arm64
curl -fsSLO https://github.com/Coffey-Labs/stalwart-migrator/releases/latest/download/stalwart-migrate-linux-$ARCH.tar.gz
curl -fsSLO https://github.com/Coffey-Labs/stalwart-migrator/releases/latest/download/SHA256SUMS
curl -fsSLO https://git.coffeylabs.org/coffey-labs/stalwart-migrator/releases/download/latest/stalwart-migrate-linux-$ARCH.tar.gz
curl -fsSLO https://git.coffeylabs.org/coffey-labs/stalwart-migrator/releases/download/latest/SHA256SUMS
sha256sum --ignore-missing -c SHA256SUMS
tar -xzf stalwart-migrate-linux-$ARCH.tar.gz
sudo install -m 0755 stalwart-migrate /usr/local/bin/