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
57 changes: 57 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
name: release

on:
push:
tags:
- '[0-9]+.[0-9]+.[0-9]+'

jobs:
build:
runs-on: ubuntu-latest
container:
image: debian:bookworm-slim
steps:
- name: Prepare build dependencies
run: |
apt-get update -y
DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
ca-certificates make perl jq git gawk sed gzip tar
rm -rf /var/lib/apt/lists/*
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Verify tag/version consistency
id: ver
run: |
TAG_NAME="${GITHUB_REF##*/}"
FILE_VER=$(cat VERSION)
if [ "$TAG_NAME" != "$FILE_VER" ]; then
echo "Tag $TAG_NAME does not match VERSION file $FILE_VER" >&2
exit 1
fi
echo "version=$TAG_NAME" >> $GITHUB_OUTPUT
- name: Build
run: |
make clean
make keychain-$(cat VERSION).tar.gz
- name: Extract changelog section
run: |
ver=$(cat VERSION)
awk -v ver="$ver" '/^## keychain '"$ver"' /{f=1;print;next} /^## keychain / && f && $0 !~ ver {exit} f' ChangeLog.md > .release-notes.md
if [ ! -s .release-notes.md ]; then
echo "Failed to extract changelog for $ver" >&2
exit 1
fi
- name: Upload artifacts (build only; manual publish step remains maintainer-driven)
uses: actions/upload-artifact@v4
with:
name: keychain-${{ steps.ver.outputs.version }}-artifacts
path: |
keychain-${{ steps.ver.outputs.version }}.tar.gz
keychain
keychain.1
.release-notes.md
- name: Summary
run: |
echo 'Artifacts prepared. Use make release or release-refresh locally to publish via API if desired.' >> $GITHUB_STEP_SUMMARY
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,6 @@ keychain
keychain.1
keychain.txt
keychain.spec
.specstory/
.ci-artifacts*/
/keychain-*.tar.gz
31 changes: 29 additions & 2 deletions ChangeLog.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,32 @@
# ChangeLog for Keychain
https://www.funtoo.org/Funtoo:Keychain
# ChangeLog for Keychain - https://github.com/danielrobbins/keychain

## keychain 2.9.6 (06 Sep 2025)

Documentation/branding release (no functional code changes):

* Updated references in wiki to reflect the new official home of Keychain at
https://github.com/danielrobbins/keychain.
* Consolidate historical references; retain only intentional archival note(s).

Additional release engineering improvements:

* Add release automation helpers: Makefile `release` (create) and
`release-refresh` (asset replace), plus scripts under `scripts/` and
GitHub Actions workflow to build artifacts on tag push (staging only).
* Add `docs/release-steps.md` to formalize release process (numeric tags only,
assets: tarball, wrapper script, man page).
* Orchestrated release flow (`make release` / `make release-refresh`) now enforces:
- Mandatory CI (Debian container) artifact fetch for the tag.
- Normalized comparisons:
* `keychain` – raw sha256.
* `keychain.1` – raw sha256; on mismatch, re-compare with Pod::Man first line stripped.
* Tarball – internal file list + per-file sha256 (man page internally normalized) ignoring tar/gzip metadata.
- If (and only if) all artifacts match (raw or normalized) CI artifacts are used DIRECTLY for publication; local artifacts are never overwritten (kept for audit).
- Any real content mismatch aborts unless `KEYCHAIN_FORCE_LOCAL=1` is explicitly set (single override; `KEYCHAIN_ADOPT_CI` removed).
- Copy/paste diff command hints emitted on mismatch for rapid investigation.
- Asset path indirection via exported variables prevents local file mutation, improving auditability.
* Release notes body automatically extended with a Build Provenance table (sha256 for `keychain` and `keychain.1`) plus the tag commit SHA1.
* Workflow continues to only stage artifacts; publication requires explicit maintainer action (no auto-release on tag push).

## keychain 2.9.5 (16 May 2025)

Expand Down
23 changes: 22 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ keychain.spec: keychain.spec.in keychain.sh

keychain.1: keychain.pod keychain.sh
pod2man --name=keychain --release=$V \
--center='https://www.funtoo.org' \
--center='https://github.com/danielrobbins/keychain' \
keychain.pod keychain.1
sed -i.orig -e "s/^'br/.br/" keychain.1

Expand Down Expand Up @@ -64,3 +64,24 @@ keychain-$V.tar.gz: $(TARBALL_CONTENTS)
/bin/tar czvf keychain-$V.tar.gz keychain-$V
rm -rf keychain-$V
ls -l keychain-$V.tar.gz

# --- Release Automation Helpers ---
.PHONY: release release-refresh

RELEASE_ASSETS=keychain-$V.tar.gz keychain keychain.1

# "release" will orchestrate a tagged release with CI artifact validation & confirmation.
release: $(RELEASE_ASSETS)
@echo "Orchestrating release $(V)"; \
if [ -z "$$GITHUB_TOKEN" ]; then \
echo "GITHUB_TOKEN not set; export a repo-scoped token to proceed." >&2; exit 1; \
fi; \
./scripts/release-orchestrate.sh create $(V)

# "release-refresh" updates assets of an existing GitHub release (e.g. fixups) with CI validation.
release-refresh: $(RELEASE_ASSETS)
@echo "Orchestrating release-refresh $(V)"; \
if [ -z "$$GITHUB_TOKEN" ]; then \
echo "GITHUB_TOKEN not set; export a repo-scoped token to proceed." >&2; exit 1; \
fi; \
./scripts/release-orchestrate.sh refresh $(V)
4 changes: 0 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,6 @@ allowing your scripts to take advantage of key-based logins.
`Keychain` also integrates with `gpg-agent`, so that GPG keys can be cached
at the same time as SSH keys.

**Additional documentation for Keychain can be found on [the Keychain
wiki page](https://www.funtoo.org/Funtoo:Keychain).**


IMPORTANT - GitHub Contributors
===============================

Expand Down
2 changes: 1 addition & 1 deletion VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
2.9.5
2.9.6
141 changes: 141 additions & 0 deletions docs/release-steps.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
# Keychain Release Steps

This document defines the standard release process. Releases use **numeric tags only** (no leading `v`). Example: `2.9.6`.

## 1. When to Bump
- Patch (X.Y.Z -> X.Y.Z+1): Documentation, branding, hardening w/o behavior change.
- Minor (X.Y -> X.Y+1): User-visible new features, option additions.
- Major (X -> X+1): Backward-incompatible changes, removed options.

## 2. Pre-Flight Checklist
1. Working tree clean (`git status`).
2. Update `ChangeLog.md`: add new section at top: `## keychain <version> (<DD Mon YYYY>)`.
3. Update `VERSION` file to match new version.
4. Ensure only intentional `funtoo.org` references (historical note in docs only).
5. Decide if any last-minute man page edits are required.

## 3. Build Artifacts
Manual build (optional; `make release` now auto-rebuilds prerequisites):
```
make clean && make keychain-$(cat VERSION).tar.gz
```
`make release` or `make release-refresh` will ensure these artifacts exist automatically.

Artifacts:
- `keychain` (executable wrapper, not committed)
- `keychain.1` (man page)
- `keychain.spec`
- `keychain.txt`
- `keychain-<version>.tar.gz`

## 4. Local Sanity Tests
```
./keychain --version
./keychain --help | head -20
grep -R "github.com/funtoo/keychain" . && echo "(should be zero results)"
```
Check man page header `.TH` line for correct date/version and updated center URL (GitHub canonical).

## 5. Tagging
Signed (preferred):
```
git tag -s $(cat VERSION) -m "$(cat VERSION)"
```
Unsigned:
```
git tag $(cat VERSION)
```
Push:
```
git push
git push --tags
```

## 6. Orchestrated Release Path (Preferred)
Run:
```
make release # for first publication
```
You will see:
1. Local build presence check (or build via prerequisites).
2. CI artifact fetch (MANDATORY). Failure to retrieve artifacts aborts; you must wait for the workflow to finish.
3. Normalized comparison phase (LOCAL vs CI build):
* `keychain` – raw sha256 digest compare.
* `keychain.1` – raw hash first; if different, re-compare with the Pod::Man auto-generated first line stripped. A normalized match counts as a match (header differences ignored).
* `keychain-<version>.tar.gz` – unpack both tarballs; compare sorted file list and per-file sha256 (man page internally also normalized on first line). Blob-level tar/gzip metadata differences (mtime, uid, compression variance) are ignored if internal contents match.
Outcome:
- If all artifacts match (raw or normalized) -> Release uses the CI artifact files directly (local artifacts remain untouched for auditing).
- If any real content mismatch exists -> Abort.
- Override (discouraged) to force publish local artifacts despite mismatch: `KEYCHAIN_FORCE_LOCAL=1 make release`
(Use corresponding `... make release-refresh` for refresh mode.)
4. Display of generated release notes (ChangeLog excerpt + provenance table preview).
5. Y/N confirmation prompt.
6. Release creation (or refresh) + asset upload + release notes (re)generation with provenance table via GitHub API.

## 7. Automated Path (Tag-Driven Workflow)
Pushing a tag matching `X.Y.Z` triggers `.github/workflows/release.yml` which:
- Validates `VERSION` matches tag.
- Builds artifacts inside a Debian container.
- Extracts ChangeLog section into `.release-notes.md`.
- Uploads a private workflow artifact bundle (NOT a published GitHub Release).

Publication only occurs when you run `make release` (or refresh) locally; CI never auto-publishes.

## 8. Fast-Fail vs Refresh
Targets:
- `make release` – Orchestrated create (fails if release exists) with digest validation & confirmation.
- `make release-refresh` – Same flow but updates existing release assets AND regenerates release notes (including provenance table).

Both require `GITHUB_TOKEN` (repo scope) exported in the environment.

## 9. Refresh Scenario Workflow
If you forgot something (docs only, same version):
```
# Edit ChangeLog.md (if you need to adjust text; refresh will regenerate release notes from current ChangeLog plus provenance.)
# Rebuild if needed (optional): make keychain-$(cat VERSION).tar.gz
make release-refresh
```
You will again get CI fetch attempt, comparisons, preview, and prompt.
If functional change needed after publishing: bump version, amend ChangeLog, retag.

## 10. Rollback
If a bad tag was pushed:
```
git push origin :refs/tags/<version>
# Optionally delete the GitHub release in the UI.
# Fix issues, retag and push again.
```

## 11. Future Hardening (Planned)
- ShellCheck + POSIX lint gating before release.
- GPG signing of tarball & man page.
- Audit target (`make audit-brand`) to fail on unexpected deprecated domains.
- Security hardening sweep (tracked separately).

## 12. Changelog Extraction (Reference)
Pseudo-command used by workflow:
```
version=$(cat VERSION)
awk -v ver="$version" '/^## keychain 'ver' /{f=1;print;next} /^## keychain /&&f && $0 !~ ver {exit} f' ChangeLog.md
```

## 13. Verification Matrix
| Item | Location | Must Match |
|------|----------|-----------|
| Version tag | git tag | `VERSION` file |
| Wrapper script | `keychain` | contains version string |
| Man page header | `keychain.1` | version/date/center URL |
| Tarball name | `keychain-<version>.tar.gz` | version |

## 14. Minimal Quick Release Recap
```
$EDITOR ChangeLog.md VERSION
make clean && make keychain-$(cat VERSION).tar.gz
./keychain --version
git tag -s $(cat VERSION) -m "$(cat VERSION)"
git push && git push --tags
# Create GitHub release, upload assets
```

---
Maintained as of 06 Sep 2025 (CI artifacts canonical: local artifacts are never overwritten; only source path selection differs).
13 changes: 7 additions & 6 deletions keychain.pod
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,7 @@ these credentials are cached in memory and available for use.
Keychain supports most UNIX-like operating systems. It supports integration
with Bourne-compatible, csh-compatible and fish shells.

Additional documentation for Keychain can be found on
L<the Keychain wiki page|https://www.funtoo.org/Funtoo:Keychain>.
Official project home: L<https://github.com/danielrobbins/keychain>.

=head1 LIFECYCLE

Expand Down Expand Up @@ -643,7 +642,9 @@ L<ssh-agent(1)>, L<gpg-agent(1)>, L<ssh-add(1)>, L<ssh(1)>

=head1 NOTES

Keychain was created and is currently maintained by Daniel Robbins. If you need
to report a bug or request an enhancement, please report it to the GitHub
project page at L<https://github.com/funtoo/keychain>. For more information
about keychain, please visit L<https://www.funtoo.org/Funtoo:Keychain>.
Keychain was created and is currently maintained by Daniel Robbins. To report a
bug or request an enhancement, use the issue tracker at
L<https://github.com/danielrobbins/keychain>.

The former Funtoo Linux wiki page is preserved only as an historical reference:
L<https://www.funtoo.org/Funtoo:Keychain>.
6 changes: 3 additions & 3 deletions keychain.sh
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

versinfo() {
qprint
qprint " Copyright ${CYANN}2009-##CUR_YEAR##${OFF} Daniel Robbins, Funtoo Solutions, Inc;"
qprint " Copyright ${CYANN}2009-##CUR_YEAR##${OFF} Daniel Robbins, BreezyOps;"
qprint " lockfile() Copyright ${CYANN}2009${OFF} Parallels, Inc."
qprint " Copyright ${CYANN}2007${OFF} Aron Griffis;"
qprint " Copyright ${CYANN}2002-2006${OFF} Gentoo Foundation;"
Expand Down Expand Up @@ -270,7 +270,7 @@ findpids() {

# If none worked, we're stuck
error "Unable to use \"ps\" to scan for $fp_prog-agent processes"
error "Please report to https://github.com/funtoo/keychain/issues."
error "Please report to https://github.com/danielrobbins/keychain/issues."
return 1
}

Expand Down Expand Up @@ -927,7 +927,7 @@ $color || unset BLUE CYAN CYANN GREEN PURP OFF RED
[ "$myaction" = list-fp ] && eval "$(catpidf_shell sh)" && exec ssh-add -L

qprint #initial newline
mesg "${PURP}keychain ${OFF}${CYANN}${version}${OFF} ~ ${GREEN}https://www.funtoo.org/Funtoo:Keychain${OFF}"
mesg "${PURP}keychain ${OFF}${CYANN}${version}${OFF} ~ ${GREEN}https://github.com/danielrobbins/keychain${OFF}"

[ "$myaction" = version ] && { versinfo; exit 0; }
[ "$myaction" = help ] && { versinfo; helpinfo; exit 0; }
Expand Down
2 changes: 1 addition & 1 deletion keychain.spec.in
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ Version: KEYCHAIN_VERSION
Release: 1
Summary: agent manager for OpenSSH, ssh.com, Sun SSH, and GnuPG
Packager: Daniel Robbins <drobbins@funtoo.org>
URL: https://github.com/funtoo/keychain
URL: https://github.com/danielrobbins/keychain
Source0: %{name}-%{version}.tar.bz2
License: GPL v2
Group: Applications/Internet
Expand Down
26 changes: 26 additions & 0 deletions scripts/fetch-ci-artifacts.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
#!/bin/sh
# Fetch latest workflow artifacts for the given version tag using the GitHub API.
# Usage: GITHUB_TOKEN=... GITHUB_REPOSITORY=owner/repo ./scripts/fetch-ci-artifacts.sh <version> <destdir>
set -eu
VER=${1:?usage: fetch-ci-artifacts.sh <version> <destdir>}
DEST=${2:?usage: fetch-ci-artifacts.sh <version> <destdir>}
REPO=${GITHUB_REPOSITORY:?GITHUB_REPOSITORY not set}
[ -n "${GITHUB_TOKEN:-}" ] || { echo "GITHUB_TOKEN not set" >&2; exit 1; }

# Find workflow run for this tag (latest by created_at)
# We assume workflow file name 'release.yml'.
RUNS_JSON=$(curl -fsSL -H "Authorization: Bearer $GITHUB_TOKEN" "https://api.github.com/repos/$REPO/actions/runs?per_page=50")
RUN_ID=$(printf '%s' "$RUNS_JSON" | jq -r --arg ver "$VER" '.workflow_runs | map(select(.head_branch == $ver or .display_title == $ver or .head_sha != null)) | map(select(.name=="release")) | map(select(.head_branch==$ver)) | sort_by(.created_at) | last.id')
[ "$RUN_ID" != "null" ] || { echo "No workflow run found for tag $VER" >&2; exit 1; }

ARTIFACTS=$(curl -fsSL -H "Authorization: Bearer $GITHUB_TOKEN" "https://api.github.com/repos/$REPO/actions/runs/$RUN_ID/artifacts")
ART_ID=$(printf '%s' "$ARTIFACTS" | jq -r '.artifacts[] | select(.name | test("keychain-")) | .id' | tail -1)
[ -n "$ART_ID" ] || { echo "No artifacts found for run $RUN_ID" >&2; exit 1; }

TMPZIP=$(mktemp)
curl -fsSL -H "Authorization: Bearer $GITHUB_TOKEN" -L "https://api.github.com/repos/$REPO/actions/artifacts/$ART_ID/zip" -o "$TMPZIP"
mkdir -p "$DEST"
unzip -qo "$TMPZIP" -d "$DEST"
rm -f "$TMPZIP"

echo "Fetched CI artifacts for $VER into $DEST" >&2
Loading