790 lines
22 KiB
Markdown
790 lines
22 KiB
Markdown
# Release Pipeline Implementation Plan (Plan 4c)
|
|
|
|
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
|
|
|
**Goal:** Ship versioned Archduino releases to self-hosted Gitea with one-command `make release`, and let users install via `curl | bash`.
|
|
|
|
**Architecture:** `bootstrap.sh` (curl-piped installer) + `.goreleaser.yaml` (cross-compile/archive/publish) + `Makefile` (svu-driven bump) + `.gitea/workflows/release.yml` (tag-triggered CI). Binary version injected via `-ldflags` into existing `main.version` var.
|
|
|
|
**Tech Stack:** Go 1.x, goreleaser v2, svu, Gitea Actions, cobra (already in tree).
|
|
|
|
**Spec:** `docs/superpowers/specs/2026-04-14-release-pipeline-design.md`
|
|
|
|
---
|
|
|
|
## File Structure
|
|
|
|
**New files:**
|
|
- `bootstrap.sh` — curl-piped installer
|
|
- `.goreleaser.yaml` — goreleaser config
|
|
- `Makefile` — release/snapshot/completions/build targets
|
|
- `.gitea/workflows/release.yml` — tag-triggered release workflow
|
|
- `LICENSE` — repo license (MIT; confirm with user during Task 7)
|
|
- `.gitignore` additions: `dist/`, `completions/`
|
|
|
|
**Modified files:**
|
|
- `cmd/archduino/main.go` — add `commit`/`date` ldflag-injected vars; ensure `version` cobra subcommand prints them; ensure `completion` subcommand registered
|
|
- `README.md` — install-via-bootstrap section, `svu` dev note
|
|
|
|
**Removed/archived files:**
|
|
- `install.sh` → `install.sh.legacy` (kept for reference, renamed so it's not confused with entry point)
|
|
|
|
---
|
|
|
|
## Task 1: Build-info ldflag wiring + version subcommand
|
|
|
|
**Files:**
|
|
- Modify: `cmd/archduino/main.go`
|
|
- Test: manual smoke (no Go test — version output is a trivial glue)
|
|
|
|
- [ ] **Step 1: Add commit/date vars and `version` subcommand**
|
|
|
|
Edit `cmd/archduino/main.go`. Change the `version` declaration and add two new package-level vars just below it:
|
|
|
|
```go
|
|
var (
|
|
version = "dev"
|
|
commit = "none"
|
|
date = "unknown"
|
|
)
|
|
```
|
|
|
|
Add a `newVersionCmd` function at the bottom of the file:
|
|
|
|
```go
|
|
func newVersionCmd() *cobra.Command {
|
|
return &cobra.Command{
|
|
Use: "version",
|
|
Short: "Print version, commit, and build date",
|
|
Run: func(cmd *cobra.Command, args []string) {
|
|
fmt.Printf("archduino %s\ncommit: %s\ndate: %s\n", version, commit, date)
|
|
},
|
|
}
|
|
}
|
|
```
|
|
|
|
Register it in `main()` alongside the other `root.AddCommand` calls:
|
|
|
|
```go
|
|
root.AddCommand(newVersionCmd())
|
|
```
|
|
|
|
- [ ] **Step 2: Verify `completion` subcommand is available**
|
|
|
|
Cobra registers a `completion` command automatically unless disabled. Run:
|
|
|
|
```bash
|
|
cd /home/marvin/git-clones/ArduinoArchSetup
|
|
go run ./cmd/archduino completion bash | head -5
|
|
```
|
|
|
|
Expected: bash completion script output (starts with `# bash completion V2 for archduino`).
|
|
|
|
If the command is missing, it means `CompletionOptions.DisableDefaultCmd` was set somewhere — grep for it and remove.
|
|
|
|
- [ ] **Step 3: Smoke-test the version command**
|
|
|
|
```bash
|
|
go run ./cmd/archduino version
|
|
```
|
|
|
|
Expected output:
|
|
```
|
|
archduino dev
|
|
commit: none
|
|
date: unknown
|
|
```
|
|
|
|
Then verify ldflag injection works:
|
|
```bash
|
|
go run -ldflags "-X main.version=v0.9.9 -X main.commit=abc123 -X main.date=2026-04-14" ./cmd/archduino version
|
|
```
|
|
|
|
Expected:
|
|
```
|
|
archduino v0.9.9
|
|
commit: abc123
|
|
date: 2026-04-14
|
|
```
|
|
|
|
- [ ] **Step 4: Commit**
|
|
|
|
```bash
|
|
git add cmd/archduino/main.go
|
|
git commit -m "feat(cmd): add version subcommand with ldflag-injected build info"
|
|
```
|
|
|
|
---
|
|
|
|
## Task 2: .gitignore + LICENSE + completions dir
|
|
|
|
**Files:**
|
|
- Modify: `.gitignore`
|
|
- Create: `LICENSE`
|
|
- Create: `completions/.gitkeep`
|
|
|
|
- [ ] **Step 1: Update .gitignore**
|
|
|
|
Append to `.gitignore` (create the file if missing):
|
|
|
|
```
|
|
# goreleaser output
|
|
dist/
|
|
|
|
# generated shell completions
|
|
completions/
|
|
```
|
|
|
|
- [ ] **Step 2: Ask user for LICENSE choice**
|
|
|
|
Before writing `LICENSE`, pause and ask the user which license (MIT, Apache-2.0, GPL-3.0, etc.). Default suggestion: MIT. Do not proceed with a guess.
|
|
|
|
Once confirmed, write `LICENSE` with the standard text for the chosen license. For MIT:
|
|
|
|
```
|
|
MIT License
|
|
|
|
Copyright (c) 2026 MDietermann
|
|
|
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
of this software and associated documentation files (the "Software"), to deal
|
|
in the Software without restriction, including without limitation the rights
|
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
copies of the Software, and to permit persons to whom the Software is
|
|
furnished to do so, subject to the following conditions:
|
|
|
|
The above copyright notice and this permission notice shall be included in all
|
|
copies or substantial portions of the Software.
|
|
|
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
SOFTWARE.
|
|
```
|
|
|
|
- [ ] **Step 3: Commit**
|
|
|
|
```bash
|
|
git add .gitignore LICENSE
|
|
git commit -m "chore: add LICENSE and ignore dist/ and completions/"
|
|
```
|
|
|
|
---
|
|
|
|
## Task 3: .goreleaser.yaml
|
|
|
|
**Files:**
|
|
- Create: `.goreleaser.yaml`
|
|
|
|
- [ ] **Step 1: Write the goreleaser config**
|
|
|
|
Create `.goreleaser.yaml`:
|
|
|
|
```yaml
|
|
version: 2
|
|
|
|
project_name: archduino
|
|
|
|
before:
|
|
hooks:
|
|
- mkdir -p completions
|
|
- sh -c "go run ./cmd/archduino completion bash > completions/archduino.bash"
|
|
- sh -c "go run ./cmd/archduino completion zsh > completions/_archduino"
|
|
- sh -c "go run ./cmd/archduino completion fish > completions/archduino.fish"
|
|
|
|
builds:
|
|
- id: archduino
|
|
main: ./cmd/archduino
|
|
binary: archduino
|
|
env:
|
|
- CGO_ENABLED=0
|
|
goos:
|
|
- linux
|
|
goarch:
|
|
- amd64
|
|
- arm64
|
|
ldflags:
|
|
- -s -w
|
|
- -X main.version={{ .Version }}
|
|
- -X main.commit={{ .Commit }}
|
|
- -X main.date={{ .Date }}
|
|
|
|
archives:
|
|
- id: default
|
|
name_template: "archduino_{{ .Version }}_linux_{{ .Arch }}"
|
|
formats: [tar.gz]
|
|
files:
|
|
- README.md
|
|
- LICENSE
|
|
- src: completions/archduino.bash
|
|
dst: completions/archduino.bash
|
|
- src: completions/_archduino
|
|
dst: completions/_archduino
|
|
- src: completions/archduino.fish
|
|
dst: completions/archduino.fish
|
|
|
|
checksum:
|
|
name_template: "checksums.txt"
|
|
algorithm: sha256
|
|
|
|
changelog:
|
|
use: git
|
|
sort: asc
|
|
filters:
|
|
exclude:
|
|
- '^chore:'
|
|
- '^docs:'
|
|
- '^test:'
|
|
- '^ci:'
|
|
- '^style:'
|
|
- '^refactor:'
|
|
- 'Merge pull request'
|
|
- 'Merge branch'
|
|
groups:
|
|
- title: Features
|
|
regexp: '^feat(\(.*\))?:'
|
|
order: 0
|
|
- title: Bug fixes
|
|
regexp: '^(fix|perf)(\(.*\))?:'
|
|
order: 1
|
|
- title: Others
|
|
order: 999
|
|
|
|
release:
|
|
gitea_urls:
|
|
api: https://gitlab.hive-brain.de/api/v1
|
|
download: https://gitlab.hive-brain.de
|
|
draft: false
|
|
prerelease: auto
|
|
```
|
|
|
|
- [ ] **Step 2: Install goreleaser locally if not present**
|
|
|
|
```bash
|
|
command -v goreleaser || go install github.com/goreleaser/goreleaser/v2@latest
|
|
goreleaser --version
|
|
```
|
|
|
|
Expected: prints a version ≥ v2.
|
|
|
|
- [ ] **Step 3: Validate the config**
|
|
|
|
```bash
|
|
cd /home/marvin/git-clones/ArduinoArchSetup
|
|
goreleaser check
|
|
```
|
|
|
|
Expected: `• config is valid`. If errors, fix them before proceeding.
|
|
|
|
- [ ] **Step 4: Run a snapshot build**
|
|
|
|
```bash
|
|
goreleaser release --snapshot --clean
|
|
```
|
|
|
|
Expected: `dist/` populated with two tar.gz archives (`archduino_*_linux_amd64.tar.gz`, `archduino_*_linux_arm64.tar.gz`), `checksums.txt`, and extracted binary subdirs. No errors.
|
|
|
|
- [ ] **Step 5: Inspect archive contents**
|
|
|
|
```bash
|
|
tar -tzf dist/archduino_*_linux_amd64.tar.gz
|
|
```
|
|
|
|
Expected file list: `archduino`, `README.md`, `LICENSE`, `completions/archduino.bash`, `completions/_archduino`, `completions/archduino.fish`.
|
|
|
|
- [ ] **Step 6: Verify binary has injected version**
|
|
|
|
```bash
|
|
tar -xzf dist/archduino_*_linux_amd64.tar.gz -C /tmp/archduino-smoke --one-top-level
|
|
/tmp/archduino-smoke/archduino_*_linux_amd64/archduino version
|
|
```
|
|
|
|
Expected: prints a snapshot version (looks like `0.0.0-next-<commit>` or similar) and a real commit + date.
|
|
|
|
- [ ] **Step 7: Commit**
|
|
|
|
```bash
|
|
git add .goreleaser.yaml
|
|
git commit -m "build: add goreleaser config for Gitea releases"
|
|
```
|
|
|
|
---
|
|
|
|
## Task 4: Makefile
|
|
|
|
**Files:**
|
|
- Create: `Makefile`
|
|
|
|
- [ ] **Step 1: Install svu locally**
|
|
|
|
```bash
|
|
command -v svu || go install github.com/caarlos0/svu@latest
|
|
svu --version
|
|
```
|
|
|
|
Expected: prints a version.
|
|
|
|
- [ ] **Step 2: Write the Makefile**
|
|
|
|
Create `Makefile`:
|
|
|
|
```makefile
|
|
.PHONY: build snapshot release completions clean help
|
|
|
|
GO ?= go
|
|
BIN := archduino
|
|
|
|
help: ## Show this help
|
|
@awk 'BEGIN {FS = ":.*?## "} /^[a-zA-Z_-]+:.*?## / {printf " %-14s %s\n", $$1, $$2}' $(MAKEFILE_LIST)
|
|
|
|
build: ## Local dev build into ./archduino
|
|
$(GO) build -o $(BIN) ./cmd/archduino
|
|
|
|
completions: ## Regenerate shell completions into completions/
|
|
@mkdir -p completions
|
|
$(GO) run ./cmd/archduino completion bash > completions/archduino.bash
|
|
$(GO) run ./cmd/archduino completion zsh > completions/_archduino
|
|
$(GO) run ./cmd/archduino completion fish > completions/archduino.fish
|
|
|
|
snapshot: ## Local goreleaser dry run into dist/
|
|
goreleaser release --snapshot --clean
|
|
|
|
clean: ## Remove build artifacts
|
|
rm -rf dist/ completions/ $(BIN)
|
|
|
|
release: ## Compute next SemVer from commits, tag, and push
|
|
@command -v svu >/dev/null || { echo "svu not found. Run: go install github.com/caarlos0/svu@latest"; exit 1; }
|
|
@test -z "$$(git status --porcelain)" || { echo "working tree is dirty; commit or stash first"; exit 1; }
|
|
@branch=$$(git rev-parse --abbrev-ref HEAD); \
|
|
if [ "$$branch" != "master" ]; then echo "must be on master (currently on $$branch)"; exit 1; fi
|
|
@current=$$(svu current 2>/dev/null || echo "none"); \
|
|
next=$$(svu next); \
|
|
echo "Current: $$current"; \
|
|
echo "Next: $$next"; \
|
|
echo; \
|
|
echo "Commits since $$current:"; \
|
|
if [ "$$current" = "none" ]; then git log --oneline; else git log --oneline $$current..HEAD; fi; \
|
|
echo; \
|
|
printf "Tag %s and push? [y/N] " "$$next"; read ans; \
|
|
if [ "$$ans" != "y" ] && [ "$$ans" != "Y" ]; then echo "aborted"; exit 1; fi; \
|
|
git tag "$$next" && git push origin "$$next"
|
|
```
|
|
|
|
- [ ] **Step 3: Smoke-test non-destructive targets**
|
|
|
|
```bash
|
|
make help
|
|
make build && ./archduino version
|
|
make completions && ls completions/
|
|
```
|
|
|
|
Expected: `help` lists targets; `build` produces `./archduino`; `completions` generates three files.
|
|
|
|
- [ ] **Step 4: Dry-run the release guard**
|
|
|
|
```bash
|
|
# With dirty tree (touch a file first if tree is clean)
|
|
touch /tmp/dirt && make release || echo "guard worked as expected"
|
|
```
|
|
|
|
Expected: aborts with "working tree is dirty". Do NOT actually run `make release` to completion yet — that creates a real tag.
|
|
|
|
- [ ] **Step 5: Commit**
|
|
|
|
```bash
|
|
git add Makefile
|
|
git commit -m "build: add Makefile with release, snapshot, completions targets"
|
|
```
|
|
|
|
---
|
|
|
|
## Task 5: bootstrap.sh
|
|
|
|
**Files:**
|
|
- Create: `bootstrap.sh`
|
|
|
|
- [ ] **Step 1: Write bootstrap.sh**
|
|
|
|
Create `bootstrap.sh`:
|
|
|
|
```bash
|
|
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
|
|
REPO="MDietermann/Archduino"
|
|
BASE_URL="${ARCHDUINO_RELEASE_BASE:-https://gitlab.hive-brain.de}"
|
|
API_URL="${BASE_URL}/api/v1"
|
|
|
|
err() { echo "bootstrap: error: $*" >&2; exit 1; }
|
|
msg() { echo "==> $*"; }
|
|
|
|
# Arch gate
|
|
[ -f /etc/arch-release ] || err "Archduino v1 supports Arch Linux only."
|
|
|
|
# Arch detect
|
|
case "$(uname -m)" in
|
|
x86_64) ARCH="amd64" ;;
|
|
aarch64) ARCH="arm64" ;;
|
|
*) err "unsupported architecture: $(uname -m)" ;;
|
|
esac
|
|
|
|
# Prereqs
|
|
msg "Installing prerequisites (sudo, git, curl, ca-certificates)"
|
|
sudo pacman -Sy --needed --noconfirm sudo git curl ca-certificates
|
|
|
|
# Discover latest release
|
|
msg "Querying latest release from Gitea"
|
|
API_ENDPOINT="${API_URL}/repos/${REPO}/releases/latest"
|
|
RELEASE_JSON=$(curl -fsSL "$API_ENDPOINT") || err "failed to fetch ${API_ENDPOINT}"
|
|
|
|
# Extract tag_name without requiring jq
|
|
TAG=$(printf '%s' "$RELEASE_JSON" | grep -o '"tag_name":[[:space:]]*"[^"]*"' | head -1 | sed 's/.*"\([^"]*\)"$/\1/')
|
|
[ -n "$TAG" ] || err "could not parse tag_name from release metadata"
|
|
VERSION="${TAG#v}"
|
|
msg "Latest release: ${TAG}"
|
|
|
|
# Download
|
|
TMP=$(mktemp -d)
|
|
trap 'rm -rf "$TMP"' EXIT
|
|
TARBALL="archduino_${VERSION}_linux_${ARCH}.tar.gz"
|
|
CHECKSUMS="checksums.txt"
|
|
DL_BASE="${BASE_URL}/${REPO}/releases/download/${TAG}"
|
|
|
|
msg "Downloading ${TARBALL}"
|
|
curl -fsSL -o "${TMP}/${TARBALL}" "${DL_BASE}/${TARBALL}" || err "tarball download failed"
|
|
curl -fsSL -o "${TMP}/${CHECKSUMS}" "${DL_BASE}/${CHECKSUMS}" || err "checksums download failed"
|
|
|
|
# Verify
|
|
msg "Verifying checksum"
|
|
(cd "$TMP" && sha256sum --check --ignore-missing "$CHECKSUMS") || err "checksum verification failed"
|
|
|
|
# Extract
|
|
msg "Extracting"
|
|
tar -xzf "${TMP}/${TARBALL}" -C "$TMP"
|
|
|
|
# Install
|
|
msg "Installing to /usr/local/bin (sudo)"
|
|
sudo install -m 0755 "${TMP}/archduino" /usr/local/bin/archduino
|
|
|
|
msg "Installing shell completions"
|
|
sudo install -Dm 0644 "${TMP}/completions/archduino.bash" /usr/share/bash-completion/completions/archduino
|
|
sudo install -Dm 0644 "${TMP}/completions/_archduino" /usr/share/zsh/site-functions/_archduino
|
|
sudo install -Dm 0644 "${TMP}/completions/archduino.fish" /usr/share/fish/vendor_completions.d/archduino.fish
|
|
|
|
msg "Launching: archduino install $*"
|
|
exec /usr/local/bin/archduino install "$@"
|
|
```
|
|
|
|
- [ ] **Step 2: Make it executable and shellcheck it**
|
|
|
|
```bash
|
|
chmod +x bootstrap.sh
|
|
command -v shellcheck && shellcheck bootstrap.sh || echo "(shellcheck not installed — skipping)"
|
|
```
|
|
|
|
Expected: `shellcheck` passes with no warnings. If warnings appear, fix them (common ones: quote expansions, etc.).
|
|
|
|
- [ ] **Step 3: Smoke-test the non-root path in a container**
|
|
|
|
This step requires a snapshot tarball. Skip if Docker is unavailable — the workflow will validate end-to-end.
|
|
|
|
```bash
|
|
make snapshot
|
|
# Serve dist/ locally
|
|
(cd dist && python3 -m http.server 8000 &) ; SERVER_PID=$!
|
|
sleep 1
|
|
# In a throwaway Arch container, verify the prereq + download logic.
|
|
# Since Gitea API isn't reachable in the container, we only eyeball the
|
|
# script behavior up to the API call:
|
|
docker run --rm -v "$PWD/bootstrap.sh:/bootstrap.sh:ro" archlinux bash -c \
|
|
'head -40 /bootstrap.sh && echo "--- script is syntactically valid shell ---" && bash -n /bootstrap.sh'
|
|
kill $SERVER_PID 2>/dev/null || true
|
|
```
|
|
|
|
Expected: `bash -n` reports no syntax errors. (Full e2e test is gated on Task 8.)
|
|
|
|
- [ ] **Step 4: Commit**
|
|
|
|
```bash
|
|
git add bootstrap.sh
|
|
git commit -m "feat: add bootstrap.sh curl-piped installer"
|
|
```
|
|
|
|
---
|
|
|
|
## Task 6: .gitea/workflows/release.yml
|
|
|
|
**Files:**
|
|
- Create: `.gitea/workflows/release.yml`
|
|
|
|
- [ ] **Step 1: Write the workflow**
|
|
|
|
Create `.gitea/workflows/release.yml`:
|
|
|
|
```yaml
|
|
name: release
|
|
|
|
on:
|
|
push:
|
|
tags:
|
|
- 'v*'
|
|
|
|
jobs:
|
|
goreleaser:
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- name: Checkout
|
|
uses: actions/checkout@v4
|
|
with:
|
|
fetch-depth: 0
|
|
|
|
- name: Setup Go
|
|
uses: actions/setup-go@v5
|
|
with:
|
|
go-version-file: go.mod
|
|
cache: true
|
|
|
|
- name: Run goreleaser
|
|
uses: goreleaser/goreleaser-action@v6
|
|
with:
|
|
distribution: goreleaser
|
|
version: '~> v2'
|
|
args: release --clean
|
|
env:
|
|
GITEA_TOKEN: ${{ secrets.RELEASE_TOKEN }}
|
|
```
|
|
|
|
- [ ] **Step 2: Lint the YAML**
|
|
|
|
```bash
|
|
command -v yamllint && yamllint .gitea/workflows/release.yml || python3 -c "import yaml,sys; yaml.safe_load(open('.gitea/workflows/release.yml'))" && echo "yaml ok"
|
|
```
|
|
|
|
Expected: no errors.
|
|
|
|
- [ ] **Step 3: Commit**
|
|
|
|
```bash
|
|
git add .gitea/workflows/release.yml
|
|
git commit -m "ci: add Gitea Actions release workflow"
|
|
```
|
|
|
|
---
|
|
|
|
## Task 7: Retire install.sh and update README
|
|
|
|
**Files:**
|
|
- Rename: `install.sh` → `install.sh.legacy`
|
|
- Modify: `README.md`
|
|
|
|
- [ ] **Step 1: Rename install.sh**
|
|
|
|
```bash
|
|
git mv install.sh install.sh.legacy
|
|
```
|
|
|
|
Add a one-line header to the top of `install.sh.legacy` explaining the rename. Use Edit with these exact lines (the first original line is `#!/bin/bash` or similar — verify and preserve):
|
|
|
|
Read the first 3 lines first to know exact content:
|
|
```bash
|
|
head -3 install.sh.legacy
|
|
```
|
|
|
|
Then use Edit to prepend:
|
|
```bash
|
|
# DEPRECATED: This is the legacy Bash installer. The active installer is
|
|
# bootstrap.sh (curl-piped shim) + the archduino Go binary. Kept for reference.
|
|
```
|
|
|
|
Inserted as the second line (below the shebang).
|
|
|
|
- [ ] **Step 2: Update README install section**
|
|
|
|
Read the current install section:
|
|
```bash
|
|
grep -n -A 20 -i "install" README.md | head -40
|
|
```
|
|
|
|
Replace whatever install instructions currently exist with a block that contains:
|
|
|
|
```markdown
|
|
## Installation
|
|
|
|
One-line install on Arch Linux:
|
|
|
|
```bash
|
|
curl -fsSL https://gitlab.hive-brain.de/MDietermann/Archduino/raw/branch/master/bootstrap.sh | bash
|
|
```
|
|
|
|
This fetches the latest release, verifies its checksum, installs `archduino`
|
|
to `/usr/local/bin`, installs shell completions, and launches
|
|
`archduino install`.
|
|
|
|
### Development
|
|
|
|
Local build:
|
|
|
|
```bash
|
|
make build
|
|
./archduino --help
|
|
```
|
|
|
|
Releasing (maintainers only):
|
|
|
|
```bash
|
|
go install github.com/caarlos0/svu@latest
|
|
make release # prompts with computed next version, tags, pushes
|
|
```
|
|
```
|
|
|
|
Use Edit to splice this in at the appropriate location. If the README has no install section, append it after the title/intro.
|
|
|
|
- [ ] **Step 3: Commit**
|
|
|
|
```bash
|
|
git add install.sh.legacy README.md
|
|
git rm install.sh 2>/dev/null || true # git mv already handles this
|
|
git commit -m "docs: update README for bootstrap.sh install; retire install.sh"
|
|
```
|
|
|
|
---
|
|
|
|
## Task 8: End-to-end snapshot verification
|
|
|
|
**Files:** none (validation only)
|
|
|
|
- [ ] **Step 1: Clean snapshot**
|
|
|
|
```bash
|
|
make clean
|
|
make snapshot
|
|
ls dist/
|
|
```
|
|
|
|
Expected: `checksums.txt`, two `.tar.gz` archives, build dirs.
|
|
|
|
- [ ] **Step 2: Verify tarball contents match bootstrap.sh expectations**
|
|
|
|
```bash
|
|
tar -tzf dist/archduino_*_linux_amd64.tar.gz | sort
|
|
```
|
|
|
|
Expected (order may vary):
|
|
```
|
|
LICENSE
|
|
README.md
|
|
archduino
|
|
completions/_archduino
|
|
completions/archduino.bash
|
|
completions/archduino.fish
|
|
```
|
|
|
|
The paths must match what `bootstrap.sh` extracts and installs. If the archive layout differs (e.g. everything under a top-level dir), adjust `.goreleaser.yaml` `archives.wrap_in_directory: false` or update `bootstrap.sh`'s install paths.
|
|
|
|
- [ ] **Step 3: Verify checksums file format**
|
|
|
|
```bash
|
|
cat dist/checksums.txt
|
|
```
|
|
|
|
Expected: one line per archive, `<sha256> archduino_<version>_linux_<arch>.tar.gz`. This is what `sha256sum --check` consumes.
|
|
|
|
- [ ] **Step 4: Bash-syntax-check bootstrap.sh against a snapshot in a container**
|
|
|
|
```bash
|
|
docker run --rm -v "$PWD:/work:ro" archlinux bash -c '
|
|
set -e
|
|
pacman -Sy --needed --noconfirm bash curl >/dev/null
|
|
bash -n /work/bootstrap.sh
|
|
echo "syntax ok"
|
|
'
|
|
```
|
|
|
|
Expected: `syntax ok`. (Full network-dependent e2e is gated on the first real release.)
|
|
|
|
- [ ] **Step 5: Commit nothing; verification is informational**
|
|
|
|
No commit. If any step failed, go back to the relevant task and fix.
|
|
|
|
---
|
|
|
|
## Task 9: Register Gitea Actions runner and first release
|
|
|
|
**Files:** none (operational)
|
|
|
|
This task is operational, performed once. Coordinate with the user before executing.
|
|
|
|
- [ ] **Step 1: Confirm with the user**
|
|
|
|
Before running any of the steps below, confirm with the user that they want to proceed with a real release. Gitea Actions runner registration and the first tag are both irreversible-ish actions that affect the remote repo.
|
|
|
|
- [ ] **Step 2: Register a Gitea Actions runner**
|
|
|
|
Runner registration is a one-time step and is performed on a host the user controls (likely the Gitea server or a persistent machine). High-level steps (user runs these — agent does not execute on remote hosts):
|
|
|
|
1. In Gitea UI → Repo Settings → Actions → Runners → generate a registration token.
|
|
2. On the runner host, install `act_runner` (https://gitea.com/gitea/act_runner).
|
|
3. `act_runner register --instance https://gitlab.hive-brain.de --token <TOKEN>`.
|
|
4. `act_runner daemon &` (or set up a systemd unit).
|
|
|
|
Verify in the Gitea UI that the runner shows as online.
|
|
|
|
- [ ] **Step 3: Create a repo-scoped release token**
|
|
|
|
1. Gitea UI → User Settings → Applications → Generate Token.
|
|
2. Scope: `write:repository` (and `write:package` if future-proofing).
|
|
3. Copy the token.
|
|
4. Repo → Settings → Actions → Secrets → New secret named `RELEASE_TOKEN`, paste the token value.
|
|
|
|
- [ ] **Step 4: Seed the first tag**
|
|
|
|
```bash
|
|
# Confirm branch/working-tree
|
|
git checkout master
|
|
git pull
|
|
git status
|
|
# Seed v0.1.0 manually so svu has a baseline
|
|
git tag v0.1.0
|
|
git push origin v0.1.0
|
|
```
|
|
|
|
Watch the Gitea Actions tab — the `release` workflow should pick up the tag and run goreleaser. Expected outcome: a new Release page at `https://gitlab.hive-brain.de/MDietermann/Archduino/releases/tag/v0.1.0` with two tarballs and `checksums.txt` attached.
|
|
|
|
- [ ] **Step 5: Smoke-test bootstrap.sh against the real release**
|
|
|
|
In a throwaway Arch container:
|
|
|
|
```bash
|
|
docker run --rm -it archlinux bash -c '
|
|
pacman -Sy --noconfirm curl >/dev/null
|
|
curl -fsSL https://gitlab.hive-brain.de/MDietermann/Archduino/raw/branch/master/bootstrap.sh | bash -s -- --dry-run
|
|
'
|
|
```
|
|
|
|
Expected: prereqs install, release detected, tarball downloaded, checksum verified, `archduino install --dry-run` runs and exits cleanly.
|
|
|
|
- [ ] **Step 6: Merge to master**
|
|
|
|
Once the first real release is verified, merge the worktree branch back to `master`:
|
|
|
|
```bash
|
|
git checkout master
|
|
git merge --no-ff feat/plan4c-release
|
|
git push origin master
|
|
```
|
|
|
|
(Branch name may differ — use whichever worktree was created.)
|
|
|
|
---
|
|
|
|
## Notes for the executing agent
|
|
|
|
- **No Go-side tests** are created in this plan. The deliverables are configs, scripts, and a CI workflow — validation is via `goreleaser check`, `make snapshot`, `bash -n`, and `shellcheck`.
|
|
- **Conventional Commits are required from here on.** `svu` and the goreleaser changelog both depend on them. Commit messages in this plan use that format as a model.
|
|
- **Do not run `make release` during implementation** — it creates real git tags that would pollute the release history. The first real release happens in Task 9 after user confirmation.
|
|
- **If Task 3 snapshot fails** because `completion` isn't registered, revisit Task 1 Step 2 — cobra may have had its default subcommand disabled.
|
|
- **If Task 8 reveals archive layout mismatches**, the fix is in `.goreleaser.yaml` (files stanza or `wrap_in_directory`) rather than `bootstrap.sh`, because goreleaser's layout is the source of truth.
|