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
119 changes: 85 additions & 34 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,31 @@ on:
push:
tags:
- "v*"
workflow_dispatch:
inputs:
candidate_tag:
description: 'Validate candidate archives without publishing a tag or release'
required: true
default: 'v0.5.0-rc.1'
type: string

permissions:
contents: write
contents: read

concurrency:
group: release-${{ github.ref }}
cancel-in-progress: false

env:
GO_VERSION: "1.27.0"
GOTOOLCHAIN: local
GOVULNCHECK_VERSION: "v1.7.0"
RELEASE_TAG: ${{ inputs.candidate_tag || github.ref_name }}

jobs:
release:
build:
runs-on: ubuntu-24.04
timeout-minutes: 30
timeout-minutes: 40
steps:
- name: Check out source
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
Expand All @@ -26,6 +38,25 @@ jobs:
go-version: ${{ env.GO_VERSION }}
cache: true

# Build the same API-enabled export as Docker, not the Pages-only demo.
# Native binaries otherwise embed only out/.gitkeep and serve 404 at /.
- name: Set up Node for the embedded interface
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version: '24.21.0'
cache: npm
cache-dependency-path: web/package-lock.json

- name: Build and embed the existing interface
working-directory: web
env:
NEXT_TELEMETRY_DISABLED: '1'
NEXT_PUBLIC_STATIC_SITE: '0'
run: |
npm ci --ignore-scripts
npm run export
npm run check

- name: Verify source
shell: bash
run: |
Expand All @@ -48,40 +79,60 @@ jobs:
AGE_BINARY="${RUNNER_TEMP}/age-bin/age" AGE_KEYGEN_BINARY="${RUNNER_TEMP}/age-bin/age-keygen" python3 -m unittest discover -s scripts -p 'test_*.py' -v

- name: Build Windows, Linux and macOS previews
env:
RELEASE_TAG: ${{ github.ref_name }}
run: |
set -euo pipefail
[[ "${RELEASE_TAG}" =~ ^v[0-9]+\.[0-9]+\.[0-9]+(-[0-9A-Za-z.-]+)?$ ]]
version="${RELEASE_TAG#v}"
for target in windows/amd64 linux/amd64 linux/arm64 darwin/amd64 darwin/arm64; do
goos="${target%/*}"
goarch="${target#*/}"
package="movemailbox-${goos}-${goarch}-${RELEASE_TAG}"
suffix=""
if [[ "${goos}" == windows ]]; then suffix=".exe"; fi
mkdir -p "dist/${package}/operations"
GOOS="${goos}" GOARCH="${goarch}" CGO_ENABLED=0 go build -trimpath \
-ldflags="-s -w -X github.com/Anton-Babaskin/MoveMailbox/internal/api.Version=${version}" \
-o "dist/${package}/movemailbox${suffix}" ./cmd/mailbox-migrator
cp README.md SECURITY.md docs/LICENSING.md "dist/${package}/"
cp docs/WORKER.md docs/BACKUP-RUNBOOK.md "dist/${package}/operations/"
cp scripts/backup_archive.py scripts/backup_validation.py "dist/${package}/operations/"
printf 'Version: %s\nCommit: %s\nPlatform: %s\n' "${RELEASE_TAG}" "${GITHUB_SHA}" "${target}" > "dist/${package}/BUILD-INFO.txt"
if [[ "${goos}" == windows ]]; then
cp scripts/windows/START-DEMO.cmd scripts/windows/START-DEMO-DEBUG.cmd scripts/windows/START-REAL.cmd scripts/windows/README-RU.txt "dist/${package}/"
(cd dist && zip -qr "${package}.zip" "${package}")
else
cp scripts/README-UNIX.txt "dist/${package}/"
tar -czf "dist/${package}.tar.gz" -C dist "${package}"
fi
done
cd dist
sha256sum *.zip *.tar.gz > SHA256SUMS.txt
run: bash scripts/build-release.sh

- name: Smoke test packaged Linux binary
run: |
python3 scripts/smoke-remote-worker.py --binary "dist/movemailbox-linux-amd64-${GITHUB_REF_NAME}/movemailbox"
python3 scripts/smoke-remote-worker.py --binary "dist/movemailbox-linux-amd64-${RELEASE_TAG}/movemailbox"

- name: Store candidate archives for native acceptance
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: native-release
path: |
dist/*.zip
dist/*.tar.gz
dist/SHA256SUMS.txt
if-no-files-found: error
retention-days: 7

native-acceptance:
needs: build
timeout-minutes: 10
strategy:
fail-fast: false
matrix:
include:
- os: windows-2025
target: windows/amd64
- os: ubuntu-24.04
target: linux/amd64
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: native-release
path: dist
- name: Verify archives and run the packaged native client
env:
TARGET: ${{ matrix.target }}
shell: bash
run: python scripts/smoke-release.py --dist dist --tag "$RELEASE_TAG" --commit "$GITHUB_SHA" --target "$TARGET"

publish:
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v')
needs: [build, native-acceptance]
runs-on: ubuntu-24.04
timeout-minutes: 10
permissions:
contents: write
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: native-release
path: dist

- name: Publish GitHub release
env:
Expand Down
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ __pycache__/
/mailbox-migrator.exe
/movemailbox
/movemailbox.exe
/internal/web/out/*
!/internal/web/out/.gitkeep

# Local configuration and data
.env
Expand Down
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,13 @@ Preview tags are immutable and are not silently promoted to stable.

## Unreleased

- Native packages now export/embed the current interface before compiling;
avoid a homepage 404 when releasing from the integrated UI source.
- Publication waits for packaged Windows/Linux native launch, assets/CSP,
demo completion and SQLite restart checks; all five archives are validated.
- Executable build/dependency inventory and manual candidate-only release gate.
- Stable release packaging requires an owner-approved project LICENSE.
- SQLite updated to 1.59.0 with green backend CI.
- New repository presentation, original SVG artwork and English/Russian overviews.
- Separate setup/configuration reference and documentation directory.
- Contribution/support guides, structured issue forms and a PR checklist.
Expand Down
10 changes: 9 additions & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@ The module requires Go 1.25+ and pins Go 1.27.0. Full backup harness testing use
Python 3.11+ and age 1.3.2 on Linux/WSL. Docker is required for container drills.

```sh
cd web
npm ci --ignore-scripts
npm run export
cd ..
go run ./cmd/mailbox-migrator --demo --open
go test ./...
go vet ./...
Expand All @@ -32,6 +36,9 @@ Format changed Go files with gofmt. Linux CI additionally runs the race detector
dependency vulnerability checks, five cross-builds and Docker fault drills.
Some local age tests skip if tools are missing: report skips as skips, not passes.
Follow [.github/workflows/ci.yml](.github/workflows/ci.yml) for the full setup.
The UI build needs Node 24; Go-only unit tests can still use the checked-in
`internal/web/out/.gitkeep`. A runnable UI needs the export step above.
See [release acceptance](docs/RELEASE-CHECKLIST.md) for candidate packaging.

Do not use a real mailbox unless its owner explicitly authorizes the exact test.
Start with demo fixtures. Destructive mirror requires separate test intent and
Expand All @@ -58,7 +65,8 @@ a disposable destination; generic permission to test is not permission to erase.
| `internal/jobs/` | Lifecycle, events, cancellation and history |
| `internal/migrator/` | IMAP preflight and imapsync adapter |
| `internal/worker/` | Independent and embedded worker runtimes |
| `internal/webui/dist/` | Embedded browser interface |
| `internal/web/out/` | Generated interface export embedded by Go; not committed |
| `web/` | Website/interface source, maintained by Claude |
| `scripts/` | Launchers, drills and release helpers |
| `docs/` | Architecture, operator guides and evidence |

Expand Down
9 changes: 8 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,10 +80,17 @@ Go 1.25+ is required; `go.mod` pins the Go 1.27.0 toolchain.
```sh
git clone https://github.com/Anton-Babaskin/MoveMailbox.git
cd MoveMailbox
cd web
npm ci --ignore-scripts
npm run export
cd ..
go run ./cmd/mailbox-migrator --demo --open
```

Open **http://127.0.0.1:8080**. For real work, install imapsync and omit `--demo`.
Node 24 is needed only to build the existing interface. `npm run export` puts it
in `internal/web/out` before Go embeds it; skipping this step produces an API-only
build with no homepage. Open **http://127.0.0.1:8080**. For real work, install
imapsync and omit `--demo`. Native release archives include the exported interface.

### Docker / private deployment

Expand Down
10 changes: 8 additions & 2 deletions docs/GETTING-STARTED.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,19 @@ but never IMAP passwords. Use `--database` to select another location.

The module requires Go 1.25 or newer and declares a reproducible Go
toolchain in `go.mod`.
Build the interface once with Node 24 before running the application:

```bash
cd web
npm ci --ignore-scripts
npm run export
cd ..
go run ./cmd/mailbox-migrator --demo --open
```

Open <http://127.0.0.1:8080>. Demo credentials are filled automatically and no
external IMAP servers are contacted.
Open <http://127.0.0.1:8080>. The demo engine does not contact external IMAP
servers. Re-export after interface changes. Release archives and Docker images
already embed the interface; users do not need Node.

### Real migration with imapsync

Expand Down
36 changes: 35 additions & 1 deletion docs/HANDOFF.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,38 @@
# Engineering handoff — 2026-09-13
# Engineering handoff — 2026-09-30

## Native release candidate pipeline (2026-09-30, technical scope)

- Synchronized main; PR #45 was already merged. Reviewed backend dependency PR
#51 at 99d99f5 (seven successful CI checks) and merged it; SQLite is 1.59.0.
Website PRs were not changed or merged by this agent. Claude's subsequent
already-merged changes were received by a safe fast-forward.
- Active branch: `release/embedded-native-rc`. Found a release-path regression:
current Go API serves `internal/web/out`, but native packaging did not export
it. A current-source native build would serve homepage 404. Published
v0.4.0-preview uses the legacy UI and is not affected. Docker already exports
the new interface.
- Native release now exports the existing API-enabled interface before build,
records executable dependencies and waits for Windows/Linux native archive
acceptance. All five archives/checksums are inspected; loopback demo verifies
pages/assets/CSP, selected-folder result, SQLite restart and password absence.
ARM/macOS are cross-built, not natively exercised. No website design changed.
- Manual candidate validation does not publish; stable packaging refuses a
missing owner-approved LICENSE. The planned candidate is v0.5.0-rc.1; it is
not described here as published before the workflow succeeds.
- Local checks: pinned Go 1.27.0 `go test -race ./...` and `go vet ./...` passed;
export/check passed (247 rendered pages, 243 sitemap URLs before Claude's latest
link-only update); six SDK tests passed; Linux/WSL Python harness suite passed
with one missing-age integration skip; seven release validation tests passed.
Full operator harnesses use Linux/WSL, not native Windows (POSIX operations).
CI installs age and remains required for the exact pushed SHA.
- VM API and worker were healthy, readiness 200, still staging-5b55549. No VM
image, secrets, external routing or VPN configuration changed during these
pre-release checks. No new real mailbox test was performed yet.
- Next two steps: (1) after green technical CI, merge and publish the candidate
only after native acceptance succeeds; (2) promote the reviewed Docker commit
to idle staging with the transactional updater, then check HTTPS and one
authorized isolated mail fixture. Licensing, automatic certificate renewal,
paid identity/billing and public-launch gates remain separate pending work.

## Website: internal links and IndexNow (2026-09-30, website scope)

Expand Down
1 change: 1 addition & 0 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
## Build it

- [Contributing](../CONTRIBUTING.md) — development workflow and checks.
- [Release acceptance](RELEASE-CHECKLIST.md) — native artifacts and staging promotion.
- [MVP scope](MVP.md) · [Readiness roadmap](ROADMAP.md).
- [Engineering handoff](HANDOFF.md) · [Two-computer synchronization](TWO-COMPUTERS.md).
- [Licensing](LICENSING.md) · [Third-party inventory](../THIRD_PARTY_NOTICES.md).
Expand Down
71 changes: 71 additions & 0 deletions docs/RELEASE-CHECKLIST.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
# Release candidate acceptance

The current release scope is the local utility and single-VM, invite-only hosted
pilot. Paid accounts, payment entitlements and public self-service are separate
stages, not implied by a release-candidate tag. Website source and SEO stay with
Claude; this process builds that source without redesigning it.

## Immutable artifacts

1. Start from a reviewed commit in `main` with successful CI for that exact SHA.
2. `.github/workflows/release.yml` builds the API-enabled interface with the npm
lockfile, runs Go/race/vet/vulnerability and Python checks, then cross-builds
five native targets. Missing exported assets stop packaging.
3. Each archive contains `BUILD-INFO.txt` (tag, full commit, target),
`BUILD-DEPENDENCIES.txt` (the executable's Go/module build information),
launch instructions, security and licensing information. A chosen `LICENSE`
is included if present; this process does not choose licensing for the owner.
Stable tags are blocked until an owner-approved `LICENSE` exists.
4. Native Windows amd64 and Linux amd64 runners validate all five archives and
SHA-256 checksums, unpack only the intended executable into a temporary
directory, and start it on loopback with a fresh SQLite database. Tests cover
embedded RU/EN/UK pages and their assets/CSP, a demo job, history after restart
and absence of synthetic passwords in the resulting files. No browser or real
IMAP connection is opened.
5. The existing independent API/worker recovery test also runs against the built
Linux executable. Publication depends on both native acceptance jobs, not
just successful compilation.

The workflow's manual `candidate_tag` input validates and retains artifacts for
seven days **without publishing a tag/release**. Push an approved `v*-rc.*` tag
to publish a prerelease after the same gates. Do not overwrite an existing tag
or publish failed/unverified artifacts. `scripts/build-release.sh` intentionally
refuses an existing `dist` directory instead of deleting or mixing older output.

## What these checks do not prove

- Linux arm64 and both macOS targets are compiled and archive-checked, not run
on those operating systems. Do not call them native acceptance-tested.
- Demo launch on Windows is not a real Windows IMAP migration test. imapsync is
installed separately in native packages; only Docker bundles the pinned engine.
- Checksums detect a difference from the published archive, not a publisher
signature. These artifacts are not signed/notarized.
- The test fetches rendered pages and assets; it does not replace Claude's
browser/UI integration acceptance or prove that the public Pages site has API.

## Staging promotion

Build a Docker image from the same reviewed commit and version. Keep the old
immutable image on the VM. Use the dedicated transactional updater after it
confirms no active API/worker jobs; it snapshots the metadata pair and rolls back
both image and pair if readiness fails. This is a deployment safeguard, not the
deferred off-site backup project. Do not alter the owner-managed host routing or
VPN, or recreate Compose with a different project name.

After promotion check image IDs, `/api/health`, `/api/ready`, the trusted HTTPS
invitation gate, and one explicitly authorized disposable-mail fixture including
repeat without duplicates. Report deployed version separately from published
native version. Reference [pilot evidence](PILOT.md) and the latest
[handoff](HANDOFF.md); do not reinterpret older provider tests as a fresh run.

## Before a stable/public launch

- Owner chooses the project distribution license; unsigned artifact warnings
and the imapsync installation requirement remain explicit.
- Complete native real-migration acceptance for every advertised platform, or
label unverified platforms as previews.
- Configure automatic HTTPS renewal and verify it; a manual DNS certificate is
not unattended renewal. Confirm operational alerts have an owner.
- Finish the invited-user acceptance with the same-origin UI/API deployment.
Account/payment access and broader provider coverage have their own gates in
the [roadmap](ROADMAP.md), not mandatory infrastructure for a local demo.
26 changes: 26 additions & 0 deletions docs/releases/v0.5.0-rc.1.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# MoveMailbox v0.5.0-rc.1

Release candidate for the local utility and closed hosted pilot, not a paid or
public-cloud launch.

- Fixed native packaging: the current RU/EN/UK interface is exported and embedded
before Go builds. After the UI integration, the old native workflow would
start the API but serve a homepage 404 when built from current source. The
published v0.4.0-preview still uses the old interface and is not affected by
this packaging regression. Docker already exported the new interface.
- Release publication now waits for actual packaged Windows amd64 and Linux
amd64 demo startup, pages/assets/CSP, selected-folder job completion and SQLite
history restoration after restart. All five archives are checksum- and
inventory-checked; runtime files, links and path traversal are rejected.
- Added executable dependency/build information to every archive, and a manual
candidate-validation workflow that does not publish a release.
- Includes technical work since v0.4.0-preview: HTTP cancellation/read deadlines,
bounded transient-work protection, closed HTTPS pilot tooling and verified
disposable Mail-in-a-Box transfer evidence recorded in the handoff.
- Updated SQLite to 1.59.0 after green backend CI.

Native packages do not bundle imapsync. Linux arm64/macOS binaries are
cross-built, not natively run by the release workflow. Artifacts remain unsigned;
licensing still requires the owner's choice. Paid identity/billing, automatic
certificate renewal and public-launch gates remain pending. See
[release acceptance](../RELEASE-CHECKLIST.md) for exact evidence and limits.
Loading