6 Commits
Author SHA1 Message Date
jcoffey-dev 6ed4556f01 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 44s
ci / release (push) Successful in 44s
2026-09-22 16:11:21 +00:00
jcoffey-dev 3fd909cba3 Point links at git.coffeylabs.org after the move from GitHub
ci / test (pull_request) Successful in 58s
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:08 -07:00
jcoffey-dev d5b911f8b2 Merge pull request 'ci: run socket-free jobs on the light label' (#4) from ci/light-runner into main
ci / test (push) Successful in 1m6s
ci / release (push) Skipped
2026-09-22 13:51:42 +00:00
jcoffey-dev 0f52bd498a ci: run socket-free jobs on the light label
ci / test (pull_request) Successful in 39s
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:32 -07:00
jcoffey-dev 98b120f3a5 Merge branch 'ci/gitea-actions' into 'main'
ci / test (push) Successful in 1m13s
ci / release (push) Skipped
ci: add Gitea Actions workflow

See merge request coffey-labs/ihasmail-oneshot!3
2026-09-21 23:21:11 -07:00
jcoffey-dev 7d3c4d4524 ci: add Gitea Actions workflow ported from .gitlab-ci.yml
ci / test (pull_request) Successful in 35s
ci / release (pull_request) Skipped
ci / test (push) Successful in 1m19s
ci / release (push) Failing after 16s
2026-09-21 22:44:22 -07:00
9 changed files with 119 additions and 16 deletions
+103
View File
@@ -0,0 +1,103 @@
# 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.
#
# e2e.yml is deliberately NOT ported. e2e/public.sh publishes 25, 80, 443,
# 465, 993, 995 and 4190 on the machine it runs on, which on GitHub was a
# throwaway VM and here would be the CI host -- where 80 and 443 are nginx
# and the mail ports belong to the mail netns. It stays a manual check on a
# disposable host until there is a runner that can safely be given those
# ports.
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
+1 -1
View File
@@ -70,7 +70,7 @@ upgrade migrates its data with no way back, so its version only changes in a
release of this tool. release of this tool.
Binaries for `linux/amd64` and `linux/arm64` and a `SHA256SUMS` file are Binaries for `linux/amd64` and `linux/arm64` and a `SHA256SUMS` file are
attached to every [release](https://github.com/Coffey-Labs/ihasmail-oneshot/releases). attached to every [release](https://git.coffeylabs.org/coffey-labs/ihasmail-oneshot/releases).
Every release is built by the [release workflow](.github/workflows/release.yml) 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 from a tagged commit on `main`, after the tests and a known-vulnerabilities
+4 -4
View File
@@ -1,12 +1,12 @@
# ihasmail-oneshot # ihasmail-oneshot
[![Latest release](https://img.shields.io/github/v/release/Coffey-Labs/ihasmail-oneshot?sort=date)](https://github.com/Coffey-Labs/ihasmail-oneshot/releases/latest) [![Latest release](https://img.shields.io/gitea/v/release/coffey-labs/ihasmail-oneshot?gitea_url=https%3A%2F%2Fgit.coffeylabs.org)](https://git.coffeylabs.org/coffey-labs/ihasmail-oneshot/releases/latest)
[![License: AGPL-3.0-or-later](https://img.shields.io/badge/license-AGPL--3.0--or--later-blue)](LICENSE) [![License: AGPL-3.0-or-later](https://img.shields.io/badge/license-AGPL--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/oneshot/) [![Docs: docs.ihasmail.org](https://img.shields.io/badge/docs-docs.ihasmail.org-0ea5e9)](https://docs.ihasmail.org/install/oneshot/)
**One command that turns a Linux Docker host into a working mail server with **One command that turns a Linux Docker host into a working mail server with
webmail.** It deploys a fresh [Stalwart](https://stalw.art) mail server and a webmail.** It deploys a fresh [Stalwart](https://stalw.art) mail server and a
fresh [ihasmail](https://github.com/Coffey-Labs/ihasmail) webmail, links them fresh [ihasmail](https://git.coffeylabs.org/coffey-labs/ihasmail) webmail, links them
together, gets them certificates, and hands you the administrator password and together, gets them certificates, and hands you the administrator password and
the DNS records to publish. the DNS records to publish.
@@ -50,8 +50,8 @@ The full list is in [docs/reference.md](docs/reference.md#requirements).
```bash ```bash
ARCH=amd64 # or arm64 ARCH=amd64 # or arm64
curl -fsSLO https://github.com/Coffey-Labs/ihasmail-oneshot/releases/latest/download/ihasmail-oneshot-linux-$ARCH.tar.gz curl -fsSLO https://git.coffeylabs.org/coffey-labs/ihasmail-oneshot/releases/download/latest/ihasmail-oneshot-linux-$ARCH.tar.gz
curl -fsSLO https://github.com/Coffey-Labs/ihasmail-oneshot/releases/latest/download/SHA256SUMS curl -fsSLO https://git.coffeylabs.org/coffey-labs/ihasmail-oneshot/releases/download/latest/SHA256SUMS
sha256sum --ignore-missing -c SHA256SUMS sha256sum --ignore-missing -c SHA256SUMS
tar -xzf ihasmail-oneshot-linux-$ARCH.tar.gz tar -xzf ihasmail-oneshot-linux-$ARCH.tar.gz
sudo install -m 0755 ihasmail-oneshot /usr/local/bin/ sudo install -m 0755 ihasmail-oneshot /usr/local/bin/
+1 -1
View File
@@ -39,5 +39,5 @@ Out of scope, and best reported upstream:
- Vulnerabilities in Stalwart itself - Vulnerabilities in Stalwart itself
- Vulnerabilities in ihasmail itself — see - Vulnerabilities in ihasmail itself — see
[its security policy](https://github.com/Coffey-Labs/ihasmail/security/policy) [its security policy](https://git.coffeylabs.org/coffey-labs/ihasmail/src/branch/main/SECURITY.md)
- Vulnerabilities in Caddy or Docker - Vulnerabilities in Caddy or Docker
+1 -1
View File
@@ -50,7 +50,7 @@ It deploys three containers:
| Container | Image | Role | | Container | Image | Role |
| --- | --- | --- | | --- | --- | --- |
| **Stalwart** | `stalwartlabs/stalwart` | The mail server: SMTP, IMAP, POP3, JMAP, CalDAV, CardDAV, spam filtering, DKIM. It holds all the mail and all the accounts | | **Stalwart** | `stalwartlabs/stalwart` | The mail server: SMTP, IMAP, POP3, JMAP, CalDAV, CardDAV, spam filtering, DKIM. It holds all the mail and all the accounts |
| **ihasmail** | `ghcr.io/coffey-labs/ihasmail` | The webmail: mail, calendars, contacts, files and filters in the browser, talking to Stalwart over JMAP. It holds nothing but sessions | | **ihasmail** | `registry.coffeylabs.org/coffey-labs/ihasmail` | The webmail: mail, calendars, contacts, files and filters in the browser, talking to Stalwart over JMAP. It holds nothing but sessions |
| **Caddy** | `caddy` | The HTTPS front: certificates and TLS for the webmail and for Stalwart's web side | | **Caddy** | `caddy` | The HTTPS front: certificates and TLS for the webmail and for Stalwart's web side |
It has two shapes: It has two shapes:
+4 -4
View File
@@ -39,8 +39,8 @@ unpack:
```bash ```bash
ARCH=amd64 # or arm64 ARCH=amd64 # or arm64
curl -fsSLO https://github.com/Coffey-Labs/ihasmail-oneshot/releases/latest/download/ihasmail-oneshot-linux-$ARCH.tar.gz curl -fsSLO https://git.coffeylabs.org/coffey-labs/ihasmail-oneshot/releases/download/latest/ihasmail-oneshot-linux-$ARCH.tar.gz
curl -fsSLO https://github.com/Coffey-Labs/ihasmail-oneshot/releases/latest/download/SHA256SUMS curl -fsSLO https://git.coffeylabs.org/coffey-labs/ihasmail-oneshot/releases/download/latest/SHA256SUMS
sha256sum --ignore-missing -c SHA256SUMS sha256sum --ignore-missing -c SHA256SUMS
tar -xzf ihasmail-oneshot-linux-$ARCH.tar.gz tar -xzf ihasmail-oneshot-linux-$ARCH.tar.gz
sudo install -m 0755 ihasmail-oneshot /usr/local/bin/ sudo install -m 0755 ihasmail-oneshot /usr/local/bin/
@@ -57,7 +57,7 @@ same bytes from the same commit.
With Go 1.26.8 or newer: With Go 1.26.8 or newer:
```bash ```bash
git clone https://github.com/Coffey-Labs/ihasmail-oneshot.git git clone https://git.coffeylabs.org/coffey-labs/ihasmail-oneshot.git
cd ihasmail-oneshot cd ihasmail-oneshot
go build -o ihasmail-oneshot ./cmd/ihasmail-oneshot go build -o ihasmail-oneshot ./cmd/ihasmail-oneshot
``` ```
@@ -168,7 +168,7 @@ docker compose pull && docker compose up -d
- **ihasmail** is safe to move to any newer release that supports your - **ihasmail** is safe to move to any newer release that supports your
Stalwart version. Its release notes say which. The newest is on Stalwart version. Its release notes say which. The newest is on
[ihasmail's releases](https://github.com/Coffey-Labs/ihasmail/releases); its [ihasmail's releases](https://git.coffeylabs.org/coffey-labs/ihasmail/releases); its
image tag is the version with `+` written as `-`, e.g. `2026.9.13-pr344`. image tag is the version with `+` written as `-`, e.g. `2026.9.13-pr344`.
- **Stalwart**: read its upgrade notes before changing versions. Check that the - **Stalwart**: read its upgrade notes before changing versions. Check that the
ihasmail version you run supports the new Stalwart release first, since ihasmail version you run supports the new Stalwart release first, since
+1 -1
View File
@@ -37,7 +37,7 @@ const (
DefaultStalwartImage = "stalwartlabs/stalwart:v0.16.22" DefaultStalwartImage = "stalwartlabs/stalwart:v0.16.22"
DefaultCaddyImage = "caddy:2.11.4" DefaultCaddyImage = "caddy:2.11.4"
IhasmailRepository = "ghcr.io/coffey-labs/ihasmail" IhasmailRepository = "registry.coffeylabs.org/coffey-labs/ihasmail"
// NewestIhasmail is the default --ihasmail-image. Only full releases move // NewestIhasmail is the default --ihasmail-image. Only full releases move
// this tag; prereleases never do. // this tag; prereleases never do.
NewestIhasmail = IhasmailRepository + ":latest" NewestIhasmail = IhasmailRepository + ":latest"
+3 -3
View File
@@ -104,17 +104,17 @@ func TestIhasmailFollowsTheNewestReleaseUnlessNamed(t *testing.T) {
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
if !p.FollowsNewestIhasmail() || p.IhasmailImage != "ghcr.io/coffey-labs/ihasmail:latest" { if !p.FollowsNewestIhasmail() || p.IhasmailImage != "registry.coffeylabs.org/coffey-labs/ihasmail:latest" {
t.Errorf("default ihasmail image %q", p.IhasmailImage) t.Errorf("default ihasmail image %q", p.IhasmailImage)
} }
named, err := Options{Domain: "example.com", IhasmailImage: "ghcr.io/coffey-labs/ihasmail:2026.9.13-pr344"}.Validate() named, err := Options{Domain: "example.com", IhasmailImage: "registry.coffeylabs.org/coffey-labs/ihasmail:2026.9.13-pr344"}.Validate()
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
if named.FollowsNewestIhasmail() { if named.FollowsNewestIhasmail() {
t.Error("a named image is treated as the newest release") t.Error("a named image is treated as the newest release")
} }
byDigest := "ghcr.io/coffey-labs/ihasmail@sha256:" + strings.Repeat("a", 64) byDigest := "registry.coffeylabs.org/coffey-labs/ihasmail@sha256:" + strings.Repeat("a", 64)
if _, err := (Options{Domain: "example.com", IhasmailImage: byDigest}).Validate(); err != nil { if _, err := (Options{Domain: "example.com", IhasmailImage: byDigest}).Validate(); err != nil {
t.Errorf("a by-digest image is refused: %v", err) t.Errorf("a by-digest image is refused: %v", err)
} }
+1 -1
View File
@@ -66,7 +66,7 @@ func envValue(env, name string) string {
} }
// RepoDigest is an image's by-digest reference in one repository, e.g. // RepoDigest is an image's by-digest reference in one repository, e.g.
// ghcr.io/coffey-labs/ihasmail@sha256:..., which names exactly that image for // registry.coffeylabs.org/coffey-labs/ihasmail@sha256:..., which names exactly that image for
// as long as the registry keeps it. // as long as the registry keeps it.
func RepoDigest(ctx context.Context, image, repository string) (string, error) { func RepoDigest(ctx context.Context, image, repository string) (string, error) {
out, err := Output(ctx, "image", "inspect", "--format", "{{range .RepoDigests}}{{println .}}{{end}}", image) out, err := Output(ctx, "image", "inspect", "--format", "{{range .RepoDigests}}{{println .}}{{end}}", image)