Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,12 @@ jobs:
- name: Backup and operations contract tests
run: python scripts/test_operations.py

- name: Release workflow contract tests
run: python scripts/test_release_workflow.py

- name: Upgrade and rollback contract tests
run: python scripts/test_upgrade.py

- name: Dependency vulnerability audit
run: python -m pip_audit --no-deps -r mailbox-app/requirements/locked.txt --progress-spinner off

Expand Down Expand Up @@ -125,5 +131,5 @@ jobs:
- name: Release verification
run: |
python scripts/verify_release.py \
dist/mailstack-1.3.0-rc.4-source.zip \
--checksum dist/mailstack-1.3.0-rc.4-source.zip.sha256
dist/mailstack-1.3.1-source.zip \
--checksum dist/mailstack-1.3.1-source.zip.sha256
94 changes: 89 additions & 5 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,22 @@ name: Release artifacts
tags:
- "v*"

permissions:
contents: read
permissions: {}

jobs:
build-verified-source:
runs-on: ubuntu-24.04
timeout-minutes: 30
permissions:
contents: read
outputs:
version: ${{ steps.identity.outputs.version }}
tag: ${{ steps.identity.outputs.tag }}
prerelease: ${{ steps.identity.outputs.prerelease }}
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: actions/setup-python@v5
with:
python-version: "3.12"
Expand All @@ -25,7 +32,21 @@ jobs:
sudo apt-get update
sudo apt-get install -y --no-install-recommends default-libmysqlclient-dev build-essential pkg-config
- name: Development dependencies
run: python -m pip install --disable-pip-version-check -r mailbox-app/requirements/development.txt
run: |
python -m pip install --disable-pip-version-check -r mailbox-app/requirements/development.txt
python -m pip check
- name: Validate release identity
id: identity
run: |
python scripts/release_gate.py \
--root . \
--event-name "$GITHUB_EVENT_NAME" \
--ref-type "$GITHUB_REF_TYPE" \
--ref-name "$GITHUB_REF_NAME" \
--sha "$GITHUB_SHA" \
--github-output "$GITHUB_OUTPUT"
- name: Release workflow contract tests
run: python scripts/test_release_workflow.py
- name: Full forensic gate
run: python scripts/forensic_audit.py --root . --full
- name: Build source archive
Expand All @@ -38,10 +59,73 @@ jobs:
- name: Upload verified artifacts
uses: actions/upload-artifact@v4
with:
name: mailstack-source-${{ github.ref_name }}
name: mailstack-source-${{ github.run_id }}
path: |
dist/*.zip
dist/*.sha256
docs/RELEASE_NOTES_1.3.0.md
docs/RELEASE_NOTES_1.3.1.md
docs/FORENSIC_AUDIT_REPORT.md
if-no-files-found: error

publish-github-release:
if: github.event_name == 'push' && github.ref_type == 'tag'
needs: build-verified-source
runs-on: ubuntu-24.04
timeout-minutes: 10
permissions:
actions: read
contents: write
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Prove publication eligibility
env:
GITHUB_TOKEN: ${{ github.token }}
run: |
python scripts/release_gate.py \
--root . \
--event-name "$GITHUB_EVENT_NAME" \
--ref-type "$GITHUB_REF_TYPE" \
--ref-name "$GITHUB_REF_NAME" \
--sha "$GITHUB_SHA" \
--repository "$GITHUB_REPOSITORY" \
--default-branch "main" \
--remote
- name: Download verified build artifact
env:
GH_TOKEN: ${{ github.token }}
run: |
mkdir -p release-artifacts
gh run download "$GITHUB_RUN_ID" \
--repo "$GITHUB_REPOSITORY" \
--name "mailstack-source-${GITHUB_RUN_ID}" \
--dir release-artifacts
- name: Publish immutable-intent GitHub Release
env:
GH_TOKEN: ${{ github.token }}
RELEASE_VERSION: ${{ needs.build-verified-source.outputs.version }}
RELEASE_TAG: ${{ needs.build-verified-source.outputs.tag }}
RELEASE_PRERELEASE: ${{ needs.build-verified-source.outputs.prerelease }}
run: |
ZIP="release-artifacts/dist/mailstack-${RELEASE_VERSION}-source.zip"
SHA="release-artifacts/dist/mailstack-${RELEASE_VERSION}-source.zip.sha256"
test -f "$ZIP"
test -f "$SHA"

args=(
release create "$RELEASE_TAG"
"$ZIP"
"$SHA"
--repo "$GITHUB_REPOSITORY"
--verify-tag
--target "$GITHUB_SHA"
--title "MailStack ${RELEASE_VERSION}"
--notes-file docs/RELEASE_NOTES_1.3.1.md
)
if [[ "$RELEASE_PRERELEASE" == "true" ]]; then
args+=(--prerelease --latest=false)
else
args+=(--latest)
fi
gh "${args[@]}"
35 changes: 35 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,41 @@

All notable repository-level changes are recorded here. Application history before the open-source conversion remains in `mailbox-app/CHANGELOG.md`.

## 1.3.1 — Unpublished source baseline

### Documentation and forensic baseline

- Finalized the published RC4 evidence in the forensic and test reports using the successful `main`, tag-CI, and release-artifact workflow results instead of the pre-release RC3/RC4-pending wording.
- Established `MAILSTACK-1.3.0-RC4-OFFICIAL-SOURCE-BASELINE-001` as the current official source baseline, anchored to tag `v1.3.0-rc.4`, commit `896dbcc2ed1f38d9c618bf0b712efe5923f92e56`, tree `0d845b3d975949894c24581e6834aff7b33c30b4`, and deterministic source SHA-256 `58f06adea7c813e9861799d20e392441367bf64f6513d6e0634455d2011d4eac`.
- Preserved the earlier RC1 documentation baseline as historical provenance instead of treating its source commit as the current release anchor.
- Added the PHASE-004 release, upgrade, and operational-reliability record; PHASE-004A changes documentation, release metadata, and generated manifests only.

### Release automation

- Added a fail-closed release gate that requires tag/version/package-version agreement, the exact current `main` head, successful `main` push CI for the tagged SHA, and absence of an existing GitHub Release before publication.
- Split tag release handling into a read-only verified-build job and a write-scoped publication job; manual `workflow_dispatch` remains validation/build-only.
- Automated GitHub Release creation with deterministic source ZIP and SHA-256 assets, RC pre-release classification, stable latest classification, and overwrite/clobber prevention.
- Added focused release-workflow contract tests and made them blocking in CI and the forensic audit.

### Existing-server upgrade and rollback

- Added a generic fail-closed existing-server upgrade driver that requires a deterministic source ZIP plus matching SHA-256, validates canonical archive/manifest/version integrity, stages outside the live tree, and rejects downgrade, same-version, removed-migration, or modified-migration targets.
- Added explicit migration acknowledgement, a non-blocking runtime lock, a verified pre-mutation consistent data backup, source/runtime rollback snapshots, application/public-site staged replacement, dependency convergence, post-upgrade contract checks, and installation-marker provenance.
- Preserved Postfix and Dovecot during the source mutation window after the consistent backup has completed, allowing accepted inbound mail to accumulate safely in Maildir while ingestion is paused.
- Added migration-aware fail-closed recovery: automatic source/runtime rollback is allowed only when no new schema migration has begun; migration-capable failures require reviewed schema/data reconciliation instead of an automatic database restore that could discard newly accepted mail.
- Added focused non-destructive upgrade/archive/rollback contract tests and made them blocking in CI and the full forensic audit.

### Verification and CI correction

- Corrected the five PHASE-004C Ruff findings in `verify_upgrade_archive.py` without changing archive, migration, upgrade, rollback, service, or data semantics.
- Recorded GitHub Actions run `32097491341` as a failed qualification attempt: source safety, documentation, upgrade/rollback contracts, and dependency audit passed before Ruff stopped the workflow; downstream runtime/release gates were therefore not executed in that run.
- Marked the next source-baseline identity as `1.3.1`, synchronized deterministic build/release examples, and moved automated publication to version-matched `docs/RELEASE_NOTES_1.3.1.md`. The version mark is not itself a production-readiness or GitHub-release claim.

### Compatibility

- PHASE-004C changes maintained operational tooling only; it does not add a database migration, application route, authorization/UI/mail-flow behavior, installer behavior, deployment-template rewrite, DNS/TLS change, or automatic host-configuration migration.
- `v1.3.0-rc.4` and its published source identity remain immutable. `1.3.1` is the next frozen source-baseline version mark and remains unpublished until its GitHub/main CI and operational release gates are accepted.

## Unreleased — MailStack repository bootstrap

### Changed
Expand Down
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@

It combines **Postfix**, **Dovecot LMTP**, **Django**, **MariaDB**, **Maildir**, **Gunicorn**, and **Nginx** in a hardened single-node reference deployment. MailStack provides a private browser-based inbox, administrator-managed users, object-level mailbox access, safe email rendering, protected attachments, live inbox updates, operational audit logs, backup and restore tooling, a public website, and an isolated contact service.

> **Release status:** `v1.3.0-rc.4` remains a release candidate. Clean Ubuntu 24.04 installation and real external SMTP/LMTP delivery have been exercised during the PHASE-003 hardening campaign; backup/restore, restart-recovery, legal, and final release-owner acceptance still gate stable promotion.
> **Release status:** `v1.3.0-rc.4` remains the latest published release candidate. The repository source baseline is now marked `1.3.1` for the PHASE-004C verification/fix freeze; this version is not yet a published GitHub release or a production-readiness claim. PHASE-003 staging validated real external SMTP/LMTP delivery after the accepted fixes; backup/restore, restart/reboot, clean-host, legal, and final release-owner acceptance remain separate operational gates.

## Why MailStack

Expand Down Expand Up @@ -211,8 +211,8 @@ Build and verify a deterministic source release:
```bash
python scripts/build_release.py --root .
python scripts/verify_release.py \
dist/mailstack-1.3.0-rc.4-source.zip \
--checksum dist/mailstack-1.3.0-rc.4-source.zip.sha256
dist/mailstack-1.3.1-source.zip \
--checksum dist/mailstack-1.3.1-source.zip.sha256
```

## Security
Expand Down
2 changes: 1 addition & 1 deletion VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
1.3.0-rc.4
1.3.1
2 changes: 1 addition & 1 deletion design/DESIGN_MANIFEST.json
Original file line number Diff line number Diff line change
Expand Up @@ -561,7 +561,7 @@
"unsupported_features_inactive": true
},
"project": "MailStack",
"release_version": "1.3.0-rc.4",
"release_version": "1.3.1",
"schema_version": 1,
"source_archive": {
"archive_crc": "PASS",
Expand Down
14 changes: 14 additions & 0 deletions docs/BACKUP_RESTORE.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,3 +41,17 @@ The built-in application backup intentionally does not duplicate the release arc
- an encrypted off-host copy of the built-in backup.

Backups contain credentials, user email, attachments, and message content. Restrict access, encrypt at rest and in transit, verify checksums after transfer, and test restoration on an isolated host.

## Upgrade rollback snapshots

PHASE-004C upgrades create a source/runtime rollback snapshot under
`/var/backups/vibmail/upgrades/` and nest a normal consistent data backup beneath that snapshot before
any source mutation. The snapshot contains the pre-upgrade application tree, previous public-site
pointer, installation marker when present, metadata, and SHA-256 checksums. The nested data backup
retains its own `SHA256SUMS` contract.

The upgrade rollback command intentionally does **not** restore MariaDB or Maildir automatically.
When an upgrade introduces new migrations, schema/data rollback requires a reviewed reconciliation
plan because restoring a pre-upgrade database/Maildir snapshot can discard or desynchronize mail
accepted after the backup. Use `restore.sh` only when that coordinated recovery decision has been
made and the post-backup mail window has been accounted for.
16 changes: 13 additions & 3 deletions docs/BUILD.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,22 @@ python scripts/forensic_audit.py --root . --full
```bash
python scripts/build_release.py --root .
python scripts/verify_release.py \
dist/mailstack-1.3.0-rc.4-source.zip \
--checksum dist/mailstack-1.3.0-rc.4-source.zip.sha256
dist/mailstack-1.3.1-source.zip \
--checksum dist/mailstack-1.3.1-source.zip.sha256
```

The builder normalizes archive timestamps, preserves executable permissions, writes a source manifest, excludes generated/runtime artifacts and emits a SHA-256 checksum.

## Automated GitHub publication

The deterministic builder remains the canonical artifact producer. After the intended release commit
is merged to `main` and exact-SHA `main` CI passes, push a matching `v<version>` tag. The release
workflow re-runs the release gate/full forensic/build/verification path, stores the verified Actions
artifact, and publishes the GitHub Release with the ZIP and `.sha256` asset.

Manual workflow dispatch validates/builds only. Tag/version/package mismatches, non-current-main tags,
missing successful `main` CI, and pre-existing releases fail closed.

## Stable promotion

Do not change the version to `1.3.0` until every required human gate in `docs/PUBLIC_RELEASE_CHECKLIST.md` passes.
The `1.3.1` source baseline is an unpublished version mark. Do not create or push a stable release tag until every required human/operational gate in `docs/PUBLIC_RELEASE_CHECKLIST.md` passes.
6 changes: 4 additions & 2 deletions docs/DEPENDENCY_REVIEW.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,5 +30,7 @@ GitHub Actions run `32053931714` on PHASE-003 RC2 passed the structural and repo
blocking `pip-audit` step found CVE-2026-71491, CVE-2026-59894, CVE-2026-59893, and CVE-2026-54284
in `sqlparse==0.5.5`. RC3 introduced and RC4 preserves `sqlparse==0.6.0`, the upstream security release fixing those findings.
Django 5.2.16 permits `sqlparse>=0.3.1`, and sqlparse 0.6.0 supports Python 3.10+, including
MailStack's required Python 3.12 runtime. `pip check` and the network-enabled blocking `pip-audit`
gate remain mandatory for RC4 qualification; no advisory is ignored or suppressed.
MailStack's required Python 3.12 runtime. RC4 subsequently passed `pip check` and the blocking
network-enabled `pip-audit` gate in the qualified branch/main/tag workflows with no known
vulnerabilities reported. The same gates remain mandatory for RC5 and later changes; no advisory is
ignored or suppressed.
Loading
Loading