Compare commits
6
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6ed4556f01 | ||
|
|
3fd909cba3 | ||
|
|
d5b911f8b2 | ||
|
|
0f52bd498a | ||
|
|
98b120f3a5 | ||
|
|
7d3c4d4524 |
@@ -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
@@ -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
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
# ihasmail-oneshot
|
# ihasmail-oneshot
|
||||||
|
|
||||||
[](https://github.com/Coffey-Labs/ihasmail-oneshot/releases/latest)
|
[](https://git.coffeylabs.org/coffey-labs/ihasmail-oneshot/releases/latest)
|
||||||
[](LICENSE)
|
[](LICENSE)
|
||||||
[](https://docs.ihasmail.org/install/oneshot/)
|
[](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
@@ -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
|
||||||
|
|||||||
@@ -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
@@ -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
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -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)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
Reference in New Issue
Block a user