Merge pull request #20 from Coffey-Labs/feat/release-binaries
Release binaries for linux amd64 and arm64
This commit is contained in:
@@ -0,0 +1,69 @@
|
|||||||
|
# Publish a release when a version tag is pushed: test, check for known
|
||||||
|
# vulnerabilities, build the Linux binaries, attach them with their checksums.
|
||||||
|
#
|
||||||
|
# Tags are the date of the commit, as ihasmail's are: v2026.9.15, and
|
||||||
|
# v2026.9.15.1 for a second release on the same day.
|
||||||
|
name: Release
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
tags: ["v*"]
|
||||||
|
# For a tag whose run never started. GitHub has queued and then orphaned
|
||||||
|
# runs before, and a pushed tag has no other way to trigger this again.
|
||||||
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
tag:
|
||||||
|
description: "Existing tag to release, e.g. v2026.9.15"
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: release-${{ github.event.inputs.tag || github.ref_name }}
|
||||||
|
cancel-in-progress: false
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
release:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
env:
|
||||||
|
TAG: ${{ github.event.inputs.tag || github.ref_name }}
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v7
|
||||||
|
with:
|
||||||
|
ref: ${{ github.event.inputs.tag || github.ref }}
|
||||||
|
fetch-depth: 0
|
||||||
|
- uses: actions/setup-go@v7
|
||||||
|
with:
|
||||||
|
go-version-file: go.mod
|
||||||
|
|
||||||
|
- name: Check the tag names a commit on main
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
git fetch --quiet origin main
|
||||||
|
git merge-base --is-ancestor "$(git rev-parse "$TAG^{commit}")" origin/main \
|
||||||
|
|| { echo "::error::$TAG is not on main"; exit 1; }
|
||||||
|
|
||||||
|
- name: Vet and test
|
||||||
|
run: |
|
||||||
|
go vet ./...
|
||||||
|
go test ./...
|
||||||
|
|
||||||
|
- name: Known vulnerabilities
|
||||||
|
run: go run golang.org/x/vuln/cmd/govulncheck@latest ./...
|
||||||
|
|
||||||
|
- name: Build
|
||||||
|
run: |
|
||||||
|
SOURCE_DATE_EPOCH="$(git log -1 --format=%ct "$TAG")" scripts/build-release.sh "$TAG" dist
|
||||||
|
|
||||||
|
- name: Publish
|
||||||
|
env:
|
||||||
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
if gh release view "$TAG" >/dev/null 2>&1; then
|
||||||
|
gh release upload "$TAG" dist/* --clobber
|
||||||
|
else
|
||||||
|
gh release create "$TAG" dist/* --verify-tag --title "$TAG" --generate-notes
|
||||||
|
fi
|
||||||
+20
-1
@@ -10,7 +10,7 @@ go build ./...
|
|||||||
go test ./...
|
go test ./...
|
||||||
```
|
```
|
||||||
|
|
||||||
Requires Go 1.26 or newer. The tool is Go standard library only — no external
|
Requires Go 1.26.8 or newer. The tool is Go standard library only — no external
|
||||||
dependencies — and nothing third-party is vendored.
|
dependencies — and nothing third-party is vendored.
|
||||||
|
|
||||||
To try a command from a checkout without installing it:
|
To try a command from a checkout without installing it:
|
||||||
@@ -22,6 +22,25 @@ sudo go run ./cmd/stalwart-migrate preflight
|
|||||||
[docs/rehearsal.md](docs/rehearsal.md) explains why even read-only commands need
|
[docs/rehearsal.md](docs/rehearsal.md) explains why even read-only commands need
|
||||||
write access to the checkpoint directory.
|
write access to the checkpoint directory.
|
||||||
|
|
||||||
|
## Releases
|
||||||
|
|
||||||
|
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).
|
||||||
|
|
||||||
|
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
|
||||||
|
check pass. The archives are reproducible: `scripts/build-release.sh` builds the
|
||||||
|
same bytes from the same commit, so a release can be checked before tagging:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
scripts/build-release.sh v2026.9.15 dist
|
||||||
|
```
|
||||||
|
|
||||||
|
If a pushed tag's run never starts, run the workflow by hand from the Actions
|
||||||
|
tab and give it the tag.
|
||||||
|
|
||||||
## Why not a shell script
|
## Why not a shell script
|
||||||
|
|
||||||
Stalwart's 0.15 → 0.16 boundary is not a drop-in binary swap: settings move,
|
Stalwart's 0.15 → 0.16 boundary is not a drop-in binary swap: settings move,
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
# stalwart-migrator
|
# stalwart-migrator
|
||||||
|
|
||||||
|
[](https://github.com/Coffey-Labs/stalwart-migrator/releases/latest)
|
||||||
[](LICENSE)
|
[](LICENSE)
|
||||||
[](https://docs.ihasmail.org/install/stalwart-migrator/)
|
[](https://docs.ihasmail.org/install/stalwart-migrator/)
|
||||||
|
|
||||||
@@ -30,17 +31,17 @@ it needs.
|
|||||||
| 🛟 **[Recovery](docs/recovery.md)** | Why recovery is your snapshot, what the tool keeps, never booting recovery mode again |
|
| 🛟 **[Recovery](docs/recovery.md)** | Why recovery is your snapshot, what the tool keeps, never booting recovery mode again |
|
||||||
| 📊 **[Status](docs/status.md)** | Command and package state, validation, field reports |
|
| 📊 **[Status](docs/status.md)** | Command and package state, validation, field reports |
|
||||||
| ⚙️ **[Architecture](ARCHITECTURE.md)** | The design: phases, checkpoints, and the reasoning behind them |
|
| ⚙️ **[Architecture](ARCHITECTURE.md)** | The design: phases, checkpoints, and the reasoning behind them |
|
||||||
| 🔧 **[Contributing](CONTRIBUTING.md)** | Building, testing, and where the design is written down |
|
| 🔧 **[Contributing](CONTRIBUTING.md)** | Building, testing, releases, and where the design is written down |
|
||||||
|
|
||||||
## Requirements
|
## Requirements
|
||||||
|
|
||||||
- Stalwart **0.15.5**, as a systemd service or a single Docker container
|
- Linux (amd64 or arm64) with Stalwart **0.15.5**, as a systemd service or a
|
||||||
|
single Docker container
|
||||||
- root on the mail server
|
- root on the mail server
|
||||||
- `python3`, for Stalwart's own `migrate_v016.py`
|
- `python3`, for Stalwart's own `migrate_v016.py`
|
||||||
- `stalwart-cli` **1.0.2 or later**, a separate download from the server
|
- `stalwart-cli` **1.0.2 or later**, a separate download from the server
|
||||||
- an administrator account **in Stalwart's directory** — not the
|
- an administrator account **in Stalwart's directory** — not the
|
||||||
`fallback-admin` from `config.toml`, which does not survive the migration
|
`fallback-admin` from `config.toml`, which does not survive the migration
|
||||||
- Go **1.26 or newer**, to build
|
|
||||||
|
|
||||||
## Fix these on the server first
|
## Fix these on the server first
|
||||||
|
|
||||||
@@ -55,14 +56,21 @@ Both stop a migration, and `preflight` refuses on both:
|
|||||||
|
|
||||||
Details: [Known Stalwart problems](docs/known-stalwart-problems.md).
|
Details: [Known Stalwart problems](docs/known-stalwart-problems.md).
|
||||||
|
|
||||||
## Build
|
## Install
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
git clone https://github.com/Coffey-Labs/stalwart-migrator.git
|
ARCH=amd64 # or arm64
|
||||||
cd stalwart-migrator
|
curl -fsSLO https://github.com/Coffey-Labs/stalwart-migrator/releases/latest/download/stalwart-migrate-linux-$ARCH.tar.gz
|
||||||
go build -o stalwart-migrate ./cmd/stalwart-migrate
|
curl -fsSLO https://github.com/Coffey-Labs/stalwart-migrator/releases/latest/download/SHA256SUMS
|
||||||
|
sha256sum --ignore-missing -c SHA256SUMS
|
||||||
|
tar -xzf stalwart-migrate-linux-$ARCH.tar.gz
|
||||||
|
sudo install -m 0755 stalwart-migrate /usr/local/bin/
|
||||||
|
stalwart-migrate version
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Or build from source with Go 1.26.8 or newer:
|
||||||
|
`go build -o stalwart-migrate ./cmd/stalwart-migrate`.
|
||||||
|
|
||||||
## Use
|
## Use
|
||||||
|
|
||||||
Give the admin password with `--admin-password` or
|
Give the admin password with `--admin-password` or
|
||||||
@@ -70,20 +78,20 @@ Give the admin password with `--admin-password` or
|
|||||||
|
|
||||||
```sh
|
```sh
|
||||||
# 1. Read-only checks and a migration plan
|
# 1. Read-only checks and a migration plan
|
||||||
sudo ./stalwart-migrate preflight --admin-url https://mail.example.com --admin-user [email protected]
|
sudo stalwart-migrate preflight --admin-url https://mail.example.com --admin-user [email protected]
|
||||||
|
|
||||||
# 2. Read-only: convert your settings and report what won't carry over
|
# 2. Read-only: convert your settings and report what won't carry over
|
||||||
sudo ./stalwart-migrate rehearse --admin-url https://mail.example.com --admin-user [email protected]
|
sudo stalwart-migrate rehearse --admin-url https://mail.example.com --admin-user [email protected]
|
||||||
|
|
||||||
# 3. Rehearse the real migration on a clone of the server (strongly recommended)
|
# 3. Rehearse the real migration on a clone of the server (strongly recommended)
|
||||||
|
|
||||||
# 4. Migrate, once you have a snapshot you have checked you can restore
|
# 4. Migrate, once you have a snapshot you have checked you can restore
|
||||||
sudo ./stalwart-migrate run --admin-url https://mail.example.com --admin-user [email protected] \
|
sudo stalwart-migrate run --admin-url https://mail.example.com --admin-user [email protected] \
|
||||||
--recovery-point-confirmed --yes
|
--recovery-point-confirmed --yes
|
||||||
|
|
||||||
# Afterwards
|
# Afterwards
|
||||||
sudo ./stalwart-migrate status <run-id> # which steps completed
|
sudo stalwart-migrate status <run-id> # which steps completed
|
||||||
sudo ./stalwart-migrate report <run-id> # what validation found
|
sudo stalwart-migrate report <run-id> # what validation found
|
||||||
```
|
```
|
||||||
|
|
||||||
A Docker container also needs `--container-path-unproven` and `--target-image`;
|
A Docker container also needs `--container-path-unproven` and `--target-image`;
|
||||||
|
|||||||
@@ -13,6 +13,9 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// version is set at build time: -ldflags "-X main.version=...".
|
||||||
|
var version = "dev"
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
if len(os.Args) < 2 {
|
if len(os.Args) < 2 {
|
||||||
usage()
|
usage()
|
||||||
@@ -33,6 +36,8 @@ func main() {
|
|||||||
err = runStatus(os.Args[2:])
|
err = runStatus(os.Args[2:])
|
||||||
case "report":
|
case "report":
|
||||||
err = runReport(os.Args[2:])
|
err = runReport(os.Args[2:])
|
||||||
|
case "version", "--version":
|
||||||
|
fmt.Println("stalwart-migrate", version)
|
||||||
default:
|
default:
|
||||||
usage()
|
usage()
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
@@ -53,5 +58,6 @@ commands:
|
|||||||
run perform the migration (needs --yes and --recovery-point-confirmed)
|
run perform the migration (needs --yes and --recovery-point-confirmed)
|
||||||
tenants show which tenant owns which domain, and what blocks a migration (read-only)
|
tenants show which tenant owns which domain, and what blocks a migration (read-only)
|
||||||
status show the state of an in-progress or completed run
|
status show the state of an in-progress or completed run
|
||||||
report print the validation report for a run`)
|
report print the validation report for a run
|
||||||
|
version print the version of this binary`)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
module github.com/Coffey-Labs/stalwart-migrator
|
module github.com/Coffey-Labs/stalwart-migrator
|
||||||
|
|
||||||
go 1.26.5
|
go 1.26.8
|
||||||
|
|||||||
Executable
+40
@@ -0,0 +1,40 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# SPDX-FileCopyrightText: 2026 Coffey Labs
|
||||||
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
#
|
||||||
|
# Build the release archives: one per architecture, plus SHA256SUMS.
|
||||||
|
#
|
||||||
|
# Usage: scripts/build-release.sh VERSION [OUTDIR]
|
||||||
|
# scripts/build-release.sh v2026.9.15 dist
|
||||||
|
#
|
||||||
|
# The release workflow runs exactly this, so a release can be reproduced -- or
|
||||||
|
# checked before tagging -- on any machine with Go. Archive names carry no
|
||||||
|
# version, so .../releases/latest/download/<name> always means the newest.
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
VERSION="${1:?usage: $0 VERSION [OUTDIR]}"
|
||||||
|
OUT="${2:-dist}"
|
||||||
|
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||||||
|
|
||||||
|
rm -rf "$OUT" && mkdir -p "$OUT"
|
||||||
|
OUT="$(cd "$OUT" && pwd)"
|
||||||
|
STAGE="$(mktemp -d)"
|
||||||
|
trap 'rm -rf "$STAGE"' EXIT
|
||||||
|
|
||||||
|
# Linux only: the tool runs as root on the mail server it upgrades, which is a
|
||||||
|
# systemd service or a Docker container there.
|
||||||
|
for arch in amd64 arm64; do
|
||||||
|
name="stalwart-migrate-linux-$arch"
|
||||||
|
mkdir -p "$STAGE/$name"
|
||||||
|
echo "==> building $name ($VERSION)"
|
||||||
|
(cd "$ROOT" && CGO_ENABLED=0 GOOS=linux GOARCH="$arch" go build -trimpath \
|
||||||
|
-ldflags "-s -w -X main.version=$VERSION" -o "$STAGE/$name/stalwart-migrate" ./cmd/stalwart-migrate)
|
||||||
|
cp "$ROOT/LICENSE" "$ROOT/README.md" "$STAGE/$name/"
|
||||||
|
# Fixed owner and time, so the same commit gives the same archive.
|
||||||
|
tar --sort=name --owner=0 --group=0 --numeric-owner --mtime="@${SOURCE_DATE_EPOCH:-0}" \
|
||||||
|
-C "$STAGE/$name" -czf "$OUT/$name.tar.gz" stalwart-migrate LICENSE README.md
|
||||||
|
done
|
||||||
|
|
||||||
|
(cd "$OUT" && sha256sum ./*.tar.gz | sed 's| \./| |' > SHA256SUMS)
|
||||||
|
echo "==> $OUT:"
|
||||||
|
(cd "$OUT" && cat SHA256SUMS)
|
||||||
Reference in New Issue
Block a user