diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index 6269cf7..fd09149 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -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 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index e63a378..bb1248f 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -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 diff --git a/README.md b/README.md index 90515a7..611b9ce 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/docs/guide.md b/docs/guide.md index 6e85f01..4bce378 100644 --- a/docs/guide.md +++ b/docs/guide.md @@ -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 ``` --- diff --git a/docs/release.md b/docs/release.md index 09be57c..51af280 100644 --- a/docs/release.md +++ b/docs/release.md @@ -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 diff --git a/docs/releases/v0.4.0.md b/docs/releases/v0.4.0.md index 411c45a..a9b4053 100644 --- a/docs/releases/v0.4.0.md +++ b/docs/releases/v0.4.0.md @@ -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`. diff --git a/packaging/build-linux-packages.sh b/packaging/build-linux-packages.sh new file mode 100755 index 0000000..a841dc1 --- /dev/null +++ b/packaging/build-linux-packages.sh @@ -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 " >&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 diff --git a/packaging/nfpm.yaml b/packaging/nfpm.yaml new file mode 100644 index 0000000..f46e2a1 --- /dev/null +++ b/packaging/nfpm.yaml @@ -0,0 +1,47 @@ +name: sshkeeper +arch: ${NFPM_ARCH} +platform: linux +version: ${NFPM_VERSION} +release: ${NFPM_RELEASE} +section: utils +priority: optional +maintainer: mirivlad +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 diff --git a/release.sh b/release.sh index 312b6bb..5fe8333 100755 --- a/release.sh +++ b/release.sh @@ -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"