Compare commits
No commits in common. "main" and "v0.3.1" have entirely different histories.
|
|
@ -1,71 +0,0 @@
|
|||
name: CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
pull_request:
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
group: ci-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
test:
|
||||
name: test (${{ matrix.os }})
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [ubuntu-latest, macos-latest]
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version-file: go.mod
|
||||
cache: true
|
||||
|
||||
# Formatting is platform independent, so check it once rather than twice.
|
||||
- name: gofmt
|
||||
if: matrix.os == 'ubuntu-latest'
|
||||
run: |
|
||||
unformatted="$(gofmt -l .)"
|
||||
if [ -n "$unformatted" ]; then
|
||||
echo "These files are not gofmt-clean:" >&2
|
||||
echo "$unformatted" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: go vet
|
||||
run: go vet ./...
|
||||
|
||||
- name: go test
|
||||
run: go test ./... -count=1
|
||||
|
||||
cross-build:
|
||||
name: cross-build
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version-file: go.mod
|
||||
cache: true
|
||||
|
||||
# Mirrors the release targets, so a platform-specific break surfaces on
|
||||
# the pull request rather than at tag time.
|
||||
- name: build all release targets
|
||||
run: |
|
||||
set -euo pipefail
|
||||
for target in linux/amd64 linux/arm64 darwin/amd64 darwin/arm64 windows/amd64; do
|
||||
goos="${target%/*}"
|
||||
goarch="${target#*/}"
|
||||
echo "==> ${goos}/${goarch}"
|
||||
GOOS="$goos" GOARCH="$goarch" CGO_ENABLED=0 \
|
||||
go build -trimpath -o /tmp/sshkeeper-ci-build .
|
||||
done
|
||||
|
|
@ -1,94 +0,0 @@
|
|||
name: Nightly
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
# Two pushes in quick succession must not race for the rolling tag. Let the
|
||||
# newer commit win rather than publishing a nightly built from older code.
|
||||
concurrency:
|
||||
group: nightly
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
nightly:
|
||||
name: publish nightly
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version-file: go.mod
|
||||
cache: true
|
||||
|
||||
# Cheaper than the full release-check, but still refuses to publish a
|
||||
# broken build.
|
||||
- name: test
|
||||
run: |
|
||||
go vet ./...
|
||||
go test ./... -count=1
|
||||
|
||||
# Version discovery is pinned to v* tags (see build.sh), so the rolling
|
||||
# nightly tag below cannot hijack this value.
|
||||
- name: resolve version
|
||||
id: version
|
||||
run: echo "value=$(git describe --tags --match 'v*' --always)" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: build artifacts
|
||||
env:
|
||||
VERSION: ${{ steps.version.outputs.value }}
|
||||
run: ./release.sh "$VERSION"
|
||||
|
||||
# Move the rolling tag before touching the release: a GitHub release must
|
||||
# point at a tag, and this one always tracks the tip of main.
|
||||
- name: move nightly tag
|
||||
run: |
|
||||
set -euo pipefail
|
||||
git tag -f nightly
|
||||
git push -f origin nightly
|
||||
|
||||
# Replace rather than update: assets are immutable once uploaded, so the
|
||||
# old release has to go before the new archives can take its name.
|
||||
- name: replace nightly release
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
VERSION: ${{ steps.version.outputs.value }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
# Heredoc, not an inline string: the notes are markdown and must not
|
||||
# inherit this file's YAML indentation.
|
||||
cat > /tmp/nightly-notes.md <<EOF
|
||||
Automated build from the tip of \`main\`, rebuilt on every push.
|
||||
|
||||
**This is not a stable release.** It is untagged, unannounced and may be
|
||||
broken. The \`Latest\` badge stays on the newest \`v*\` release, which is
|
||||
what you want for normal use.
|
||||
|
||||
| | |
|
||||
|---|---|
|
||||
| Version | \`${VERSION}\` |
|
||||
| Commit | ${GITHUB_SHA} |
|
||||
| Built | $(date -u '+%Y-%m-%d %H:%M UTC') |
|
||||
|
||||
Verify downloads against \`checksums.txt\`.
|
||||
EOF
|
||||
|
||||
gh release delete nightly --yes || echo "no previous nightly release"
|
||||
gh release create nightly \
|
||||
--prerelease \
|
||||
--title "sshkeeper nightly (${VERSION})" \
|
||||
--notes-file /tmp/nightly-notes.md \
|
||||
"dist/sshkeeper_${VERSION}_linux_amd64.tar.gz" \
|
||||
"dist/sshkeeper_${VERSION}_linux_arm64.tar.gz" \
|
||||
"dist/sshkeeper_${VERSION}_darwin_amd64.tar.gz" \
|
||||
"dist/sshkeeper_${VERSION}_darwin_arm64.tar.gz" \
|
||||
"dist/sshkeeper_${VERSION}_windows_amd64.zip" \
|
||||
dist/checksums.txt
|
||||
|
|
@ -1,65 +0,0 @@
|
|||
name: Release
|
||||
|
||||
on:
|
||||
push:
|
||||
tags: ['v*']
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
jobs:
|
||||
release:
|
||||
name: publish ${{ github.ref_name }}
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
# Full history and tags: release.sh derives SOURCE_DATE_EPOCH from the
|
||||
# tagged commit, and version discovery needs the v* tags to be present.
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version-file: go.mod
|
||||
cache: true
|
||||
|
||||
# Gate the release on the same checks used locally. A red suite must not
|
||||
# be able to publish.
|
||||
- name: release checks
|
||||
run: make release-check
|
||||
|
||||
# Build through release.sh rather than reimplementing packaging here, so
|
||||
# CI and a local ./release.sh produce byte-identical archives.
|
||||
- name: build artifacts
|
||||
env:
|
||||
VERSION: ${{ github.ref_name }}
|
||||
run: ./release.sh "$VERSION"
|
||||
|
||||
- name: publish
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
VERSION: ${{ github.ref_name }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
# A hand-written docs/releases/<tag>.md wins; otherwise fall back to
|
||||
# GitHub's generated changelog.
|
||||
notes="docs/releases/${VERSION}.md"
|
||||
if [ -f "$notes" ]; then
|
||||
echo "Using hand-written notes from $notes"
|
||||
set -- --notes-file "$notes"
|
||||
else
|
||||
echo "No $notes, generating notes from commit history"
|
||||
set -- --generate-notes
|
||||
fi
|
||||
|
||||
gh release create "$VERSION" \
|
||||
--title "sshkeeper $VERSION" \
|
||||
--verify-tag \
|
||||
"$@" \
|
||||
"dist/sshkeeper_${VERSION}_linux_amd64.tar.gz" \
|
||||
"dist/sshkeeper_${VERSION}_linux_arm64.tar.gz" \
|
||||
"dist/sshkeeper_${VERSION}_darwin_amd64.tar.gz" \
|
||||
"dist/sshkeeper_${VERSION}_darwin_arm64.tar.gz" \
|
||||
"dist/sshkeeper_${VERSION}_windows_amd64.zip" \
|
||||
dist/checksums.txt
|
||||
5
build.sh
5
build.sh
|
|
@ -4,10 +4,7 @@ set -euo pipefail
|
|||
cd "$(dirname "$0")"
|
||||
|
||||
APP=sshkeeper
|
||||
# --match 'v*' keeps the rolling `nightly` tag from hijacking the version: a
|
||||
# plain `git describe --tags` picks whichever tag is nearest, so a nightly build
|
||||
# would otherwise stamp binaries "nightly" instead of v<last release>-N-g<sha>.
|
||||
VERSION=$(git describe --tags --match 'v*' --always --dirty 2>/dev/null || echo "dev")
|
||||
VERSION=$(git describe --tags --always --dirty 2>/dev/null || echo "dev")
|
||||
LDFLAGS="-s -w -X main.version=${VERSION}"
|
||||
|
||||
echo "==> Building ${APP} ${VERSION}..."
|
||||
|
|
|
|||
|
|
@ -1,41 +1,10 @@
|
|||
# Release Packaging
|
||||
|
||||
Releases are published by GitHub Actions. Pushing a `v*` tag is the whole
|
||||
release procedure; the rest of this document describes what that automation
|
||||
runs, and how to reproduce it by hand when needed.
|
||||
|
||||
## Workflows
|
||||
|
||||
| Workflow | Trigger | Result |
|
||||
|----------|---------|--------|
|
||||
| `ci.yml` | push to `main`, every pull request | `gofmt`, `go vet`, `go test` on Linux and macOS, plus a cross-build of all five release targets |
|
||||
| `release.yml` | push of a `v*` tag | runs `make release-check`, then `release.sh`, then publishes the GitHub release |
|
||||
| `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.
|
||||
|
||||
### Release notes
|
||||
|
||||
`release.yml` looks for `docs/releases/<tag>.md`. If that file exists it becomes
|
||||
the release body; otherwise GitHub generates notes from commit history. Write
|
||||
the file before pushing the tag when a release deserves a real description.
|
||||
|
||||
### The nightly prerelease
|
||||
|
||||
`nightly.yml` force-moves a rolling `nightly` tag to the tip of `main` and
|
||||
republishes a prerelease from it. It is marked prerelease deliberately, so
|
||||
GitHub's `Latest` badge stays on the newest `v*` release.
|
||||
|
||||
Because that tag moves, version discovery in `build.sh` and `release.sh` is
|
||||
pinned with `--match 'v*'`. Without the filter `git describe` would select
|
||||
`nightly` and stamp binaries with it instead of `v<last release>-<n>-g<sha>`.
|
||||
Keep the filter if you touch those scripts.
|
||||
This document describes the manual release flow for sshkeeper.
|
||||
|
||||
## Create a Tag
|
||||
|
||||
Use a semantic version tag. Pushing it is what triggers `release.yml`:
|
||||
Use a semantic version tag:
|
||||
|
||||
```bash
|
||||
git status --short
|
||||
|
|
@ -43,14 +12,8 @@ git tag -a v0.2.0 -m "sshkeeper v0.2.0"
|
|||
git push origin v0.2.0
|
||||
```
|
||||
|
||||
The remaining sections describe the manual equivalent, which is still the way
|
||||
to test packaging locally or to recover if Actions is unavailable.
|
||||
|
||||
The release script uses `git describe --tags --match 'v*' --always --dirty` by
|
||||
default. The `--match 'v*'` filter matters: nightly builds move a `nightly` tag
|
||||
across `main`, and without the filter `git describe` would pick that tag and
|
||||
stamp binaries `nightly` instead of `v<last release>-<n>-g<sha>`. You can also
|
||||
pass the version explicitly:
|
||||
The release script uses `git describe --tags --always --dirty` by default. You
|
||||
can also pass the version explicitly:
|
||||
|
||||
```bash
|
||||
./release.sh v0.2.0
|
||||
|
|
@ -124,40 +87,9 @@ sha256sum -c checksums.txt
|
|||
|
||||
Expected result: every archive reports `OK`.
|
||||
|
||||
## Reproducibility
|
||||
|
||||
Rebuilding the same commit with the same Go version reproduces the **binaries**
|
||||
byte for byte. `release.sh` pins everything that would otherwise vary:
|
||||
|
||||
- `-trimpath` and `CGO_ENABLED=0` keep build paths and the host toolchain out
|
||||
of the binary;
|
||||
- `SOURCE_DATE_EPOCH` (the commit timestamp) sets every archive mtime;
|
||||
- `tar --sort=name --owner=0 --group=0 --numeric-owner` fixes entry order and
|
||||
ownership, and `gzip -n` drops the compression timestamp;
|
||||
- `normalize_package` forces 755 on directories and the program and 644 on
|
||||
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.
|
||||
|
||||
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,
|
||||
`ru_RU.UTF-8`, UTC+08) produce identical checksums for all five archives.
|
||||
|
||||
The strongest check is still the binary, since it does not depend on the host's
|
||||
`tar` and `gzip` at all:
|
||||
|
||||
```bash
|
||||
tar -xzf sshkeeper_<version>_linux_amd64.tar.gz
|
||||
sha256sum sshkeeper_<version>_linux_amd64/sshkeeper
|
||||
```
|
||||
|
||||
Comparing whole-archive hashes works too, as long as both builds used the same
|
||||
Go version.
|
||||
|
||||
## Publish in GitHub Release
|
||||
|
||||
`release.yml` does this automatically on tag push. To publish by hand, upload:
|
||||
Upload these files to the release:
|
||||
|
||||
- all five platform archives
|
||||
- `checksums.txt`
|
||||
|
|
|
|||
|
|
@ -1,140 +0,0 @@
|
|||
Release automation and reproducible packaging. sshkeeper itself behaves exactly
|
||||
as in v0.3.1 — no functional changes to the TUI or the CLI.
|
||||
|
||||
This is also the first release published by GitHub Actions rather than by hand.
|
||||
|
||||
## In this release
|
||||
|
||||
**Archives are now reproducible.** Rebuilding a tag on a different machine used
|
||||
to produce different checksums even when every packaged file was byte-identical,
|
||||
because three host properties leaked into the archives:
|
||||
|
||||
| Leak | Effect |
|
||||
|------|--------|
|
||||
| File modes followed the builder's umask | umask 002 packaged `664`/`775`, umask 022 packaged `644`/`755` |
|
||||
| `sort` orders entries by locale | a `ru_RU.UTF-8` host emitted `docs/` before `LICENSE`, a C locale the reverse |
|
||||
| zip stores DOS local time with no zone | the same commit embedded `19:06` at UTC+08 and `11:06` at UTC |
|
||||
|
||||
All three are pinned now. A build on `ubuntu-latest` and one on a workstation
|
||||
with a different umask, locale and timezone produce identical checksums for all
|
||||
five archives. The binaries were always reproducible; only the packaging varied.
|
||||
|
||||
**CI.** The repository previously had no automation. It now runs `gofmt`,
|
||||
`go vet` and `go test` on Linux *and* macOS for every push and pull request,
|
||||
plus a cross-build of all five release targets. macOS is a stated release
|
||||
target that until now was only ever cross-compiled, never tested.
|
||||
|
||||
**Releases are automated.** Pushing a `v*` tag runs the release checks, builds
|
||||
through the same `release.sh` used locally, and publishes. Nightly builds from
|
||||
`main` are published as a separate `nightly` prerelease, so the `Latest` badge
|
||||
always points at a real release.
|
||||
|
||||
---
|
||||
|
||||
# Everything since v0.2.0
|
||||
|
||||
## Breaking change: full help moved off F1
|
||||
|
||||
**`Ctrl+H` opens full help. `F1` no longer has any binding.** `?` still opens
|
||||
contextual quick help outside text editors. This landed in v0.3.0.
|
||||
|
||||
`Ctrl+H` is the BS control character (0x08). xterm and most modern emulators
|
||||
send DEL (0x7F) for Backspace, so help and text editing do not collide. A
|
||||
terminal configured to send BS for Backspace cannot tell them apart; switch it
|
||||
to DEL (in xterm, `backarrowKey: false`).
|
||||
|
||||
Nothing else requires action when upgrading. Vaults, server profiles and stored
|
||||
port forwards are unchanged, and no migration runs.
|
||||
|
||||
## The TUI was rebuilt around one shell (v0.3.0)
|
||||
|
||||
In v0.2.0 only the server dashboard had a real layout. Other screens rendered
|
||||
free-form strings or the default Bubbles list frame, so they had no shared
|
||||
width budget, no borders, and footers that floated wherever the content ended.
|
||||
|
||||
Every full-screen state now shares one contract: a header with breadcrumb and
|
||||
truthful vault status, a separator, framed content panels, and a contextual
|
||||
footer anchored to the last terminal row.
|
||||
|
||||
- Actions, search, tag input, confirmations and both help screens render inside
|
||||
the shell.
|
||||
- The port forward manager and editor use framed, width-budgeted layouts. Column
|
||||
widths derive from the panel's inner width, so no row consumes the terminal's
|
||||
last column.
|
||||
- Tag, command template, template picker/mode/results and tunnel managers use
|
||||
framed lists with a `>` selection marker, so selection never depends on colour
|
||||
alone.
|
||||
- Server and template editors use a framed form panel with the title moved into
|
||||
the breadcrumb. Required markers, validation and dirty-state confirmation are
|
||||
unchanged.
|
||||
|
||||
**Responsive layouts.** The supported floor is `60x16`. Wide (100+ columns)
|
||||
shows two panels, medium (70–99) stacks them, narrow (60–69) keeps a single
|
||||
compact panel. Below the floor only the minimum-size message renders. Long
|
||||
ASCII, Cyrillic, CJK, combining and emoji content truncates by display cells
|
||||
rather than byte count.
|
||||
|
||||
**Safety.** Destructive actions confirm with Cancel selected first and name the
|
||||
exact target and its consequence. Status and help context stay truthful to
|
||||
actual vault and connection state. Form validation prevents silent loss of
|
||||
edits.
|
||||
|
||||
## `forward add` was completely broken (fixed in v0.3.1)
|
||||
|
||||
`sshkeeper forward add` could never succeed in v0.2.0 or v0.3.0. The command
|
||||
read `--local-port` and marked it required, but the flag was never registered,
|
||||
so cobra rejected it as unknown and the value fell back to 0:
|
||||
|
||||
```
|
||||
$ sshkeeper forward add web --type local --local-port 15432 \
|
||||
--remote-addr 127.0.0.1 --remote-port 5432
|
||||
unknown flag: --local-port
|
||||
```
|
||||
|
||||
No combination of arguments worked. Both documented forms work now:
|
||||
|
||||
```
|
||||
$ sshkeeper forward add web --name "Local PostgreSQL" --type local \
|
||||
--local-port 15432 --remote-addr db01.internal.example.com --remote-port 5432
|
||||
✓ Forward added [1]
|
||||
|
||||
$ sshkeeper forward add web --name "SOCKS proxy" --type dynamic --local-port 1080
|
||||
✓ Forward added [2]
|
||||
```
|
||||
|
||||
Omitting the flag now reports `required flag(s) "local-port" not set` instead of
|
||||
a misleading port-range error. The TUI (`Ctrl+W`) was never affected.
|
||||
|
||||
The command's tests had constructed their own throwaway cobra command and
|
||||
registered the flags by hand, so the real command's registration was never
|
||||
exercised and the suite passed against a broken command. Coverage now parses
|
||||
argv into the actual command. An audit of every other command found no further
|
||||
flag that is read but never registered.
|
||||
|
||||
## Also fixed since v0.2.0
|
||||
|
||||
- Port forward fields accept digits correctly (`10bcc07`).
|
||||
- Platform and repository status are stated accurately in the docs: Linux and
|
||||
macOS are primary release targets, Windows is experimental.
|
||||
|
||||
## Release-by-release
|
||||
|
||||
| Version | Contents |
|
||||
|---------|----------|
|
||||
| v0.3.0 | Unified TUI shell, responsive layouts, `F1` → `Ctrl+H` |
|
||||
| v0.3.1 | `forward add` fix |
|
||||
| v0.3.2 | Reproducible packaging, CI, automated releases |
|
||||
|
||||
## Install
|
||||
|
||||
```bash
|
||||
tar -xzf sshkeeper_v0.3.2_linux_amd64.tar.gz
|
||||
sudo install -m 0755 sshkeeper_v0.3.2_linux_amd64/sshkeeper /usr/local/bin/sshkeeper
|
||||
```
|
||||
|
||||
Verify downloads against `checksums.txt`. Linux and macOS are the primary
|
||||
release targets; the Windows build is experimental and needs OpenSSH Client
|
||||
available as `ssh.exe` on `PATH`.
|
||||
|
||||
To verify a build yourself, check out the tag and run `./release.sh v0.3.2` —
|
||||
the checksums should match this release exactly, given the same Go version.
|
||||
26
release.sh
26
release.sh
|
|
@ -4,8 +4,7 @@ set -euo pipefail
|
|||
cd "$(dirname "$0")"
|
||||
|
||||
APP=sshkeeper
|
||||
# --match 'v*' ignores the rolling `nightly` tag; see build.sh for the details.
|
||||
VERSION=${VERSION:-${1:-$(git describe --tags --match 'v*' --always --dirty 2>/dev/null || echo "dev")}}
|
||||
VERSION=${VERSION:-${1:-$(git describe --tags --always --dirty 2>/dev/null || echo "dev")}}
|
||||
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)}
|
||||
|
|
@ -28,16 +27,6 @@ package_docs() {
|
|||
|
||||
normalize_package() {
|
||||
local package_dir="$1"
|
||||
local binary="$2"
|
||||
|
||||
# Permissions must not depend on the builder's umask. Without this, a host
|
||||
# with umask 002 packages 664/775 while one with umask 022 packages
|
||||
# 644/755, and the archives differ even though every file inside is
|
||||
# byte-identical.
|
||||
find "${package_dir}" -type d -exec chmod 755 {} +
|
||||
find "${package_dir}" -type f -exec chmod 644 {} +
|
||||
chmod 755 "${package_dir}/${binary}"
|
||||
|
||||
find "${package_dir}" -exec touch -h -d "@${SOURCE_DATE_EPOCH}" {} +
|
||||
}
|
||||
|
||||
|
|
@ -53,7 +42,7 @@ build_tarball() {
|
|||
|
||||
GOOS="${goos}" GOARCH="${goarch}" CGO_ENABLED=0 go build -trimpath -ldflags "${LDFLAGS}" -o "${package_dir}/${APP}" .
|
||||
package_docs "${package_dir}"
|
||||
normalize_package "${package_dir}" "${APP}"
|
||||
normalize_package "${package_dir}"
|
||||
tar --sort=name --owner=0 --group=0 --numeric-owner --mtime="@${SOURCE_DATE_EPOCH}" -cf - -C "${DIST_DIR}" "$(basename "${package_dir}")" | gzip -n > "${archive}"
|
||||
rm -rf "${package_dir}"
|
||||
}
|
||||
|
|
@ -70,15 +59,8 @@ build_zip() {
|
|||
|
||||
GOOS="${goos}" GOARCH="${goarch}" CGO_ENABLED=0 go build -trimpath -ldflags "${LDFLAGS}" -o "${package_dir}/${APP}.exe" .
|
||||
package_docs "${package_dir}"
|
||||
normalize_package "${package_dir}" "${APP}.exe"
|
||||
# LC_ALL=C: `sort` is locale-sensitive, and it decides the entry order here.
|
||||
# A ru_RU.UTF-8 host orders docs/ before LICENSE where a C locale does the
|
||||
# reverse, producing a different archive from identical files.
|
||||
#
|
||||
# TZ=UTC: zip records DOS local time with no zone, so the same build in
|
||||
# +08:00 and in UTC would embed different timestamps. tar needs neither —
|
||||
# it sorts internally and stores Unix epochs.
|
||||
(cd "${DIST_DIR}" && export LC_ALL=C TZ=UTC && find "$(basename "${package_dir}")" -print | sort | zip -X -q "$(basename "${archive}")" -@)
|
||||
normalize_package "${package_dir}"
|
||||
(cd "${DIST_DIR}" && find "$(basename "${package_dir}")" -print | sort | zip -X -q "$(basename "${archive}")" -@)
|
||||
rm -rf "${package_dir}"
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue