build: add deb and rpm release packages

This commit is contained in:
mirivlad 2026-09-05 22:46:45 +08:00
parent 0151076ca9
commit b481bb9f3e
9 changed files with 226 additions and 16 deletions

View File

@ -28,6 +28,14 @@ jobs:
go-version-file: go.mod
cache: true
- name: install nfpm
env:
NFPM_VERSION: v2.47.0
run: |
mkdir -p "$RUNNER_TEMP/bin"
GOBIN="$RUNNER_TEMP/bin" go install github.com/goreleaser/nfpm/v2/cmd/nfpm@"$NFPM_VERSION"
echo "$RUNNER_TEMP/bin" >> "$GITHUB_PATH"
# Cheaper than the full release-check, but still refuses to publish a
# broken build.
- name: test
@ -81,6 +89,7 @@ jobs:
Verify downloads against \`checksums.txt\`.
EOF
PKG_VERSION="${VERSION#v}"
gh release delete nightly --yes || echo "no previous nightly release"
gh release create nightly \
--prerelease \
@ -91,4 +100,8 @@ jobs:
"dist/sshkeeper_${VERSION}_darwin_amd64.tar.gz" \
"dist/sshkeeper_${VERSION}_darwin_arm64.tar.gz" \
"dist/sshkeeper_${VERSION}_windows_amd64.zip" \
"dist/sshkeeper_${PKG_VERSION}-1_amd64.deb" \
"dist/sshkeeper_${PKG_VERSION}-1_arm64.deb" \
"dist/sshkeeper-${PKG_VERSION}-1.x86_64.rpm" \
"dist/sshkeeper-${PKG_VERSION}-1.aarch64.rpm" \
dist/checksums.txt

View File

@ -23,6 +23,14 @@ jobs:
go-version-file: go.mod
cache: true
- name: install nfpm
env:
NFPM_VERSION: v2.47.0
run: |
mkdir -p "$RUNNER_TEMP/bin"
GOBIN="$RUNNER_TEMP/bin" go install github.com/goreleaser/nfpm/v2/cmd/nfpm@"$NFPM_VERSION"
echo "$RUNNER_TEMP/bin" >> "$GITHUB_PATH"
# Gate the release on the same checks used locally. A red suite must not
# be able to publish.
- name: release checks
@ -53,6 +61,7 @@ jobs:
set -- --generate-notes
fi
PKG_VERSION="${VERSION#v}"
gh release create "$VERSION" \
--title "sshkeeper $VERSION" \
--verify-tag \
@ -62,4 +71,8 @@ jobs:
"dist/sshkeeper_${VERSION}_darwin_amd64.tar.gz" \
"dist/sshkeeper_${VERSION}_darwin_arm64.tar.gz" \
"dist/sshkeeper_${VERSION}_windows_amd64.zip" \
"dist/sshkeeper_${PKG_VERSION}-1_amd64.deb" \
"dist/sshkeeper_${PKG_VERSION}-1_arm64.deb" \
"dist/sshkeeper-${PKG_VERSION}-1.x86_64.rpm" \
"dist/sshkeeper-${PKG_VERSION}-1.aarch64.rpm" \
dist/checksums.txt

View File

@ -52,7 +52,7 @@ Platform status:
| Platform | Status | Notes |
|----------|--------|-------|
| Linux | Primary release target | `linux/amd64` and `linux/arm64` release tarballs are available. |
| Linux | Primary release target | `amd64`/`arm64` tarballs plus native `.deb` and `.rpm` packages. |
| macOS | Primary release target | `darwin/amd64` and `darwin/arm64` release tarballs are available. Requires system `ssh` client. Homebrew formula planned. |
| Windows | Experimental | Requires OpenSSH Client available as `ssh.exe` in `PATH`. Password/key-passphrase PTY flows are not validated on Windows. |
@ -68,6 +68,21 @@ Add-WindowsCapability -Online -Name OpenSSH.Client~~~~0.0.1.0
### Install from release
Debian/Ubuntu (amd64):
```bash
sudo apt install ./sshkeeper_0.4.0-1_amd64.deb
```
Fedora/RHEL-family (x86_64):
```bash
sudo dnf install ./sshkeeper-0.4.0-1.x86_64.rpm
```
`arm64`/`aarch64` packages are published alongside the x86_64 builds. The
traditional tar.gz archive remains available too:
```bash
tar -xzf sshkeeper_v0.4.0_linux_amd64.tar.gz
sudo install -m 0755 sshkeeper_v0.4.0_linux_amd64/sshkeeper /usr/local/bin/sshkeeper

View File

@ -69,7 +69,7 @@ go build -o ~/.local/bin/sshkeeper .
| Платформа | Статус | Примечание |
|-----------|--------|------------|
| Linux | Основная релизная платформа | Архивы `linux/amd64` и `linux/arm64`. |
| Linux | Основная релизная платформа | Архивы `amd64`/`arm64`, а также `.deb` и `.rpm`. |
| macOS | Основная релизная платформа | Архивы `darwin/amd64` и `darwin/arm64`, нужен системный `ssh`. Homebrew formula запланирована. |
| Windows | Experimental | Нужен OpenSSH Client как `ssh.exe` в `PATH`; password/key-passphrase PTY-сценарии на Windows пока не подтверждены. |
@ -79,11 +79,26 @@ go build -o ~/.local/bin/sshkeeper .
Add-WindowsCapability -Online -Name OpenSSH.Client~~~~0.0.1.0
```
### Из релиза (после публикации v0.2.0)
### Из релиза
Для Debian/Ubuntu (amd64):
```bash
tar -xzf sshkeeper_v0.2.0_linux_amd64.tar.gz
sudo install -m 0755 sshkeeper_v0.2.0_linux_amd64/sshkeeper /usr/local/bin/sshkeeper
sudo apt install ./sshkeeper_0.4.0-1_amd64.deb
```
Для Fedora/RHEL-подобных систем (x86_64):
```bash
sudo dnf install ./sshkeeper-0.4.0-1.x86_64.rpm
```
Для ARM64 публикуются `sshkeeper_0.4.0-1_arm64.deb` и
`sshkeeper-0.4.0-1.aarch64.rpm`. Архивный вариант остаётся доступен:
```bash
tar -xzf sshkeeper_v0.4.0_linux_amd64.tar.gz
sudo install -m 0755 sshkeeper_v0.4.0_linux_amd64/sshkeeper /usr/local/bin/sshkeeper
```
---

View File

@ -13,8 +13,9 @@ runs, and how to reproduce it by hand when needed.
| `nightly.yml` | push to `main` | rebuilds the tip of `main` and replaces the `nightly` prerelease |
`release.yml` builds through `release.sh` rather than reimplementing packaging,
so CI and a local run stay in step. See [Reproducibility](#reproducibility) for
what that guarantees.
so CI and a local run stay in step. Linux `.deb` and `.rpm` packages are built
with nFPM v2.47.0 from the exact Linux tarball binaries. See
[Reproducibility](#reproducibility) for what that guarantees.
### Release notes
@ -90,7 +91,14 @@ This runs:
## Build Artifacts
Run:
Linux package generation requires nFPM v2.47.0. GitHub Actions installs this
exact version; for a local release build install the same tool first:
```bash
go install github.com/goreleaser/nfpm/v2/cmd/nfpm@v2.47.0
```
Then run:
```bash
./release.sh v0.2.0
@ -104,6 +112,10 @@ sshkeeper_v0.2.0_linux_arm64.tar.gz
sshkeeper_v0.2.0_darwin_amd64.tar.gz
sshkeeper_v0.2.0_darwin_arm64.tar.gz
sshkeeper_v0.2.0_windows_amd64.zip
sshkeeper_0.2.0-1_amd64.deb
sshkeeper_0.2.0-1_arm64.deb
sshkeeper-0.2.0-1.x86_64.rpm
sshkeeper-0.2.0-1.aarch64.rpm
checksums.txt
```
@ -114,6 +126,11 @@ Each archive contains:
- `LICENSE`
- `docs/guide.md`
Linux packages install the same release binary as `/usr/bin/sshkeeper` and add
README, LICENSE, and the user guide under `/usr/share/doc/sshkeeper/`. Debian
packages depend on `openssh-client`; RPM packages depend on `openssh-clients`.
The package revision starts at `1` and is reset when the upstream version changes.
## Verify Checksums
From the `dist/` directory:
@ -138,7 +155,10 @@ byte for byte. `release.sh` pins everything that would otherwise vary:
everything else, so the builder's umask cannot leak into the archive.
- the Windows zip is packaged under `LC_ALL=C` and `TZ=UTC`, because `sort`
orders entries by locale and zip stores DOS local time with no zone.
orders entries by locale and zip stores DOS local time with no zone;
- nFPM receives the same `SOURCE_DATE_EPOCH` and packages files extracted from
the already-built Linux tarballs, so `.deb`/`.rpm` contain the identical Linux
binary rather than triggering a second compile.
With those in place the archives themselves reproduce across hosts: a build on
`ubuntu-latest` (umask 022, C locale, UTC) and one on a workstation (umask 002,
@ -172,11 +192,10 @@ Release notes should mention platform status:
## Packaging TODO
Prepare these package channels after the first archive-based release:
Native `.deb` and `.rpm` packages are part of the release pipeline. Remaining
package channels:
- deb package
- Arch PKGBUILD / AUR
- rpm later
- Homebrew tap
- Scoop manifest
- Winget later

View File

@ -185,11 +185,23 @@ the data matters to you.
## Install
Debian/Ubuntu (amd64):
```bash
tar -xzf sshkeeper_v0.4.0_linux_amd64.tar.gz
sudo install -m 0755 sshkeeper_v0.4.0_linux_amd64/sshkeeper /usr/local/bin/sshkeeper
sudo apt install ./sshkeeper_0.4.0-1_amd64.deb
```
Fedora/RHEL-family (x86_64):
```bash
sudo dnf install ./sshkeeper-0.4.0-1.x86_64.rpm
```
ARM64 packages (`arm64.deb` / `aarch64.rpm`) and the original tar.gz archives
are published alongside them. Package dependencies pull in the distro OpenSSH
client; user config, database and vault files are not owned or modified by the
package.
Verify downloads against `checksums.txt`. Linux and macOS are the primary
release targets. Windows remains experimental and requires OpenSSH Client
(`ssh.exe`) in `PATH`.

View File

@ -0,0 +1,73 @@
#!/usr/bin/env bash
set -euo pipefail
cd "$(dirname "$0")/.."
APP=sshkeeper
VERSION=${1:-${VERSION:-}}
NFPM_BIN=${NFPM_BIN:-nfpm}
NFPM_RELEASE=${NFPM_RELEASE:-1}
if [[ -z "$VERSION" ]]; then
echo "usage: $0 <version>" >&2
exit 2
fi
if ! command -v "$NFPM_BIN" >/dev/null 2>&1; then
echo "nfpm is required to build .deb/.rpm packages" >&2
exit 1
fi
PKG_VERSION=${VERSION#v}
if [[ -z "${SOURCE_DATE_EPOCH:-}" ]]; then
if git rev-parse --verify -q "${VERSION}^{commit}" >/dev/null; then
SOURCE_DATE_EPOCH=$(git log -1 --format=%ct "$VERSION")
else
SOURCE_DATE_EPOCH=$(git log -1 --format=%ct 2>/dev/null || date +%s)
fi
fi
export SOURCE_DATE_EPOCH
TMP_DIR=$(mktemp -d)
trap 'rm -rf "$TMP_DIR"' EXIT
build_one() {
local goarch="$1"
local rpmarch
local tarball="dist/${APP}_${VERSION}_linux_${goarch}.tar.gz"
local package_root="${TMP_DIR}/${APP}_${VERSION}_linux_${goarch}"
local extracted="${package_root}/${APP}"
case "$goarch" in
amd64) rpmarch=x86_64 ;;
arm64) rpmarch=aarch64 ;;
*) echo "unsupported package arch: $goarch" >&2; return 1 ;;
esac
if [[ ! -f "$tarball" ]]; then
echo "missing Linux release archive: $tarball" >&2
return 1
fi
tar -xzf "$tarball" -C "$TMP_DIR"
if [[ ! -x "$extracted" ]]; then
echo "missing binary in $tarball" >&2
return 1
fi
export NFPM_ARCH="$goarch"
export NFPM_VERSION="$PKG_VERSION"
export NFPM_RELEASE
export NFPM_BINARY="$extracted"
export NFPM_README="${package_root}/README.md"
export NFPM_LICENSE="${package_root}/LICENSE"
export NFPM_GUIDE="${package_root}/docs/guide.md"
"$NFPM_BIN" package --config packaging/nfpm.yaml --packager deb \
--target "dist/${APP}_${PKG_VERSION}-${NFPM_RELEASE}_${goarch}.deb"
"$NFPM_BIN" package --config packaging/nfpm.yaml --packager rpm \
--target "dist/${APP}-${PKG_VERSION}-${NFPM_RELEASE}.${rpmarch}.rpm"
}
build_one amd64
build_one arm64
echo "==> Linux packages:"
ls -lh dist/*.deb dist/*.rpm

47
packaging/nfpm.yaml Normal file
View File

@ -0,0 +1,47 @@
name: sshkeeper
arch: ${NFPM_ARCH}
platform: linux
version: ${NFPM_VERSION}
release: ${NFPM_RELEASE}
section: utils
priority: optional
maintainer: mirivlad <mirvtop@yandex.ru>
description: |
Console manager for SSH profiles, bastion routes, port forwards,
background tunnels, and encrypted SSH secrets.
vendor: sshkeeper
homepage: https://github.com/mirivlad/sshkeeper
license: MIT
contents:
- src: ${NFPM_BINARY}
dst: /usr/bin/sshkeeper
expand: true
file_info:
mode: 0755
- src: ${NFPM_README}
dst: /usr/share/doc/sshkeeper/README.md
expand: true
file_info:
mode: 0644
- src: ${NFPM_GUIDE}
dst: /usr/share/doc/sshkeeper/guide.md
expand: true
file_info:
mode: 0644
- src: ${NFPM_LICENSE}
dst: /usr/share/doc/sshkeeper/LICENSE
expand: true
file_info:
mode: 0644
overrides:
deb:
depends:
- openssh-client
rpm:
depends:
- openssh-clients
rpm:
compression: gzip

View File

@ -9,6 +9,7 @@ VERSION=${VERSION:-${1:-$(git describe --tags --match 'v*' --always --dirty 2>/d
LDFLAGS="-s -w -X main.version=${VERSION}"
DIST_DIR="dist"
SOURCE_DATE_EPOCH=${SOURCE_DATE_EPOCH:-$(git log -1 --format=%ct 2>/dev/null || date +%s)}
export SOURCE_DATE_EPOCH
echo "==> Building release ${APP} ${VERSION}..."
echo "==> SOURCE_DATE_EPOCH=${SOURCE_DATE_EPOCH}"
@ -88,7 +89,9 @@ build_tarball darwin amd64
build_tarball darwin arm64
build_zip windows amd64
(cd "${DIST_DIR}" && sha256sum *.tar.gz *.zip > checksums.txt)
./packaging/build-linux-packages.sh "${VERSION}"
(cd "${DIST_DIR}" && sha256sum *.tar.gz *.zip *.deb *.rpm > checksums.txt)
echo "==> Done."
ls -lh "${DIST_DIR}/"*.tar.gz "${DIST_DIR}/"*.zip "${DIST_DIR}/checksums.txt"
ls -lh "${DIST_DIR}/"*.tar.gz "${DIST_DIR}/"*.zip "${DIST_DIR}/"*.deb "${DIST_DIR}/"*.rpm "${DIST_DIR}/checksums.txt"