The workflow was missing the pull-requests write permission, so its PR
settings never had any effect. Grant it and close PRs two weeks after
the author goes silent, matching the contribution policy.
The image build succeeds but exporting to the GHA cache fails with
'failed to reserve cache' under concurrent preview builds against the
10 GiB budget, and buildx treats that as fatal. #3053 (mode=max->min)
did not fix it. Remove cache-from/cache-to type=gha entirely so the
Preview build is decoupled from the cache budget.
vikunja's nfpm.yaml packs ./config.yml.sample as /etc/vikunja/config.yml.
The release-binaries action already regenerates it for the zip bundles,
but release-os-package runs on a fresh runner without that file, so
nfpm aborted with "matching ./config.yml.sample: file does not exist"
on every vikunja os-package matrix shard (the veans shards skip this
step entirely).
Add a vikunja-only step to regenerate it before nfpm runs.
The s3-action expands the upload glob into paths without a leading
"./", but strip-path-prefix was set to "./dist/os-packages/" (or
"./veans/dist/os-packages/"). The prefix never matched, so packages
landed at /<project>/<version>/<project>/dist/os-packages/<file>
instead of /<project>/<version>/<file>.
Drop the "./" prefix to match the working DIST_PREFIX pattern in
release-binaries.
The release-binaries and release-os-package composite actions were
comparing the raw release-version input against the literal "main" to
decide whether to use "unstable" for filenames and the S3 directory.
Callers always pass `steps.ghd.outputs.describe`, which is a value
like `v2.3.0-408-ge053d317` on non-tag builds — so the check never
fired and unstable artifacts landed under `/<project>/<describe>/`
with `<project>-<describe>-...` filenames.
Drive the switch from `github.ref_type == 'tag'` instead, matching the
pattern the desktop and config-yaml jobs in release.yml already use.
The raw describe value still flows into RELEASE_VERSION so the binary
and package metadata keep the precise commit reference.
GitHub's action manifest parser evaluates `${{ ... }}` expressions inside
`description:` block scalars, and `secrets` isn't a valid context in a
composite action — so the literal example text in the docstring caused
manifest validation to fail before any step ran.
build_mage_bin is only consumed by publish-repos in release.yml, so it
doesn't belong in the test workflow. Move it to release.yml as a
prep job and add it to publish-repos's needs list.
Reviewer asked us to stop over-configuring the release-binaries and
release-os-package composite actions — they're called only with
vikunja or veans, so per-project paths, artifact names, cache keys, S3
target, and version-or-unstable can all be derived inside the action
from the project name. The xgo-out-name input goes away too.
Vikunja-specific pre-build (downloading frontend_dist, generating
config.yml.sample) now happens inside the action, gated on the project
input. Callers no longer need those preamble steps.
Secrets stay as inputs — composite actions can't read \`\${{ secrets.* }}\`
directly; passing them through is the simplest workaround.
Each callsite shrinks to ~13 lines of mostly-secret pass-through plus
2-4 lines of real parameters.
Replace the inline bodies of binaries, veans-binaries, os-package, and
veans-os-package jobs with calls to the new release-binaries and
release-os-package composite actions. Each call site is now ~25 lines
of inputs instead of ~75 lines of duplicated mage+upx+gpg+s3 plumbing.
publish-repos switches from the parent's ./mage-static to the
prebuilt build_mage_bin artifact so it can drive build/'s repo metadata
targets inside the publish-repos containers.
publish-repos runs inside ubuntu/fedora/archlinux containers that don't
ship a Go toolchain. Compile build/magefile.go into a static binary in
the test workflow (mirroring the existing mage_bin job for the parent
magefile) and upload it as the build_mage_bin artifact so publish-repos
can chmod+x and run it without setup-go.
Two reusable composite actions wrap the CI side of the release pipeline:
- release-binaries: setup-go, install mage + upx, cache xgo, invoke
`mage release:build <project>` from build/, GPG-sign the zip bundles,
upload to S3, store binaries and zips as workflow artifacts.
- release-os-package: download a binaries artifact, install mage,
`mage release:prepare-nfpm-config <project> <arch>`, stage the binary,
nfpm pack (with rpm signing inline and archlinux signing after), upload
to S3, store the package as an artifact.
Both actions are parameterized on project name, output paths, artifact
names, S3 target, and GPG/S3 secrets — adding a third Go binary to the
monorepo just means defining its project in build/magefile.go and adding
a four-line call site in release.yml.
Cross-compile veans for the same OS/arch matrix as the main vikunja
binary, wrap each into a signed zip, build deb/rpm/apk/archlinux
packages via nfpm, and merge those into the existing dl.vikunja.io
package repos so `apt install veans` works from the same source.
- veans/magefile.go: Release namespace (xgo cross-compile, upx, sha256,
per-target zip bundle, nfpm.yaml templating).
- veans/nfpm.yaml: minimal — binary at /usr/local/bin/veans, no service
or postinstall.
- .github/workflows/release.yml: veans-binaries + veans-os-package
jobs, veans artifacts merged into publish-repos and create-release.
S3 layout mirrors vikunja under /veans/<version>/.
Three helpers I added recently have no e2e coverage because the
suite always passes --bot-username with a valid name and
--yes-buckets to skip prompts.
Nine tests in a new bootstrap_test.go:
- TestValidateBotUsername — table-driven, 18 rows: valid shapes
(bot-foo, bot-foo-bar, bot-foo123, bot-foo_bar, bot-foo.bar,
bot-a), invalid prefix (foo, Bot-foo, ""), invalid chars
(spaces, commas, uppercase, !, embedded space), the reserved
link-share-N pattern, and the bare "bot-" edge.
- TestConfirmOverwriteExistingConfig — file-missing path, the
OverwriteExistingConfig=true short-circuit, every interesting
prompt answer (y, yes, Y, Yes, " yes " → proceed; n, "",
garbage → CodeConflict with path in message; prompter error
→ CodeUnknown wrapping the original via errors.Is).
- TestBootstrapBuckets_{AllPresent,AutoApprove,PromptDeclined,
PromptAborted,PromptUnknownCap,PromptAccepted} — drive the
function against a stub httptest server (bucketServer helper)
that records ListBuckets responses and CreateBucket payloads,
with a scripted queuePrompter for the prompt-driven cases.
Covers the alias-match short circuit, the auto-approve path,
the new declined/aborted/retry-cap paths, and the y-accepted
path.
Local helpers (queuePrompter for scripted answers with injectable
error; bucketServer for the stubbed bucket endpoints) stay in the
test file — no production code changes.
A keyring transient failure on Set silently falls through to the file
backend today, which leaves a stale keyring entry from any prior
successful write shadowing the new file-backend token. Fixing the
shadow itself is deferred (would need a Set-and-Delete coordination,
or a stricter contract).
What we can do cheaply: surface the fallback so an operator hitting
the shadow has a breadcrumb. On Chain.Set fallthrough past a writable
backend that errored, print:
veans: credential store: keyring rejected write (X); falling back to file
The warning goes to stderr (not the structured envelope — Set still
returns nil because the write landed somewhere). Env-backend's
read-only skip is unchanged and silent.
ChainStderr is exposed as a package var so tests can capture/assert
the warning when we backfill credential-store coverage.
The project picker used to require at least one pre-existing project
and would otherwise hard-error: "no projects visible to this user —
create one in the Vikunja UI first". Now it always offers an extra
numbered entry "Create a new project" and, when the user picks it,
prompts for a title (required) + identifier (optional). Empty-list
case routes straight to creation.
Backed by a new client.CreateProject(ctx, *Project) method (`PUT
/projects`); the e2e harness now uses that instead of the raw c.Do
call it did before.
Also fixed a latent bufio bug in StdPrompter.ReadLine that this work
surfaced: every call created a fresh bufio.Reader, which read-ahead a
buffer and threw it away on return. Second+ prompts read empty. Reuse
one buffered reader on the StdPrompter instance.