feat: add cross-platform release packaging
This commit is contained in:
parent
48c0057d11
commit
8e59c3052e
15
Makefile
15
Makefile
|
|
@ -1,6 +1,7 @@
|
|||
APP=sshkeeper
|
||||
RELEASE_CHECK_DIR ?= /tmp/sshkeeper-release-check
|
||||
|
||||
.PHONY: build run test vet fmt clean install
|
||||
.PHONY: build run test vet fmt clean install release-check
|
||||
|
||||
build:
|
||||
go build -o bin/$(APP) .
|
||||
|
|
@ -22,3 +23,15 @@ clean:
|
|||
|
||||
install:
|
||||
go build -o $(HOME)/.local/bin/$(APP) .
|
||||
|
||||
release-check:
|
||||
rm -rf $(RELEASE_CHECK_DIR)
|
||||
mkdir -p $(RELEASE_CHECK_DIR)
|
||||
go test ./...
|
||||
go vet ./...
|
||||
CGO_ENABLED=0 go build -o $(RELEASE_CHECK_DIR)/$(APP) .
|
||||
GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -o $(RELEASE_CHECK_DIR)/$(APP)-linux-amd64 .
|
||||
GOOS=linux GOARCH=arm64 CGO_ENABLED=0 go build -o $(RELEASE_CHECK_DIR)/$(APP)-linux-arm64 .
|
||||
GOOS=darwin GOARCH=amd64 CGO_ENABLED=0 go build -o $(RELEASE_CHECK_DIR)/$(APP)-darwin-amd64 .
|
||||
GOOS=darwin GOARCH=arm64 CGO_ENABLED=0 go build -o $(RELEASE_CHECK_DIR)/$(APP)-darwin-arm64 .
|
||||
GOOS=windows GOARCH=amd64 CGO_ENABLED=0 go build -o $(RELEASE_CHECK_DIR)/$(APP)-windows-amd64.exe .
|
||||
|
|
|
|||
32
README.md
32
README.md
|
|
@ -1,7 +1,8 @@
|
|||
# sshkeeper
|
||||
|
||||
`sshkeeper` is a Linux console manager for SSH profiles, secrets, and quick
|
||||
OpenSSH launches. It does not replace OpenSSH; it keeps connection metadata in a
|
||||
`sshkeeper` is a console manager for SSH profiles, secrets, and quick OpenSSH
|
||||
launches. Primary target: Linux. macOS builds are available, and Windows builds
|
||||
are experimental. It does not replace OpenSSH; it keeps connection metadata in a
|
||||
local SQLite database, keeps passwords/passphrases in an encrypted vault, and
|
||||
starts the system `ssh` client with the right options.
|
||||
|
||||
|
|
@ -42,10 +43,24 @@ Or use the build scripts:
|
|||
|
||||
```bash
|
||||
./build.sh # Build binary to bin/
|
||||
./release.sh # Build release tarballs to dist/
|
||||
./release.sh # Build release archives to dist/
|
||||
```
|
||||
|
||||
Requirements: Go 1.25+, Linux x86_64, system OpenSSH.
|
||||
Requirements: Go 1.25+ and system OpenSSH.
|
||||
|
||||
Platform status:
|
||||
|
||||
| Platform | Status | Notes |
|
||||
|----------|--------|-------|
|
||||
| Linux | Primary target | `linux/amd64` and `linux/arm64` release tarballs are available. |
|
||||
| macOS | Supported, needs verification on target machines | `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. |
|
||||
|
||||
On Windows, install OpenSSH Client via Windows Optional Features or PowerShell:
|
||||
|
||||
```powershell
|
||||
Add-WindowsCapability -Online -Name OpenSSH.Client~~~~0.0.1.0
|
||||
```
|
||||
|
||||
**Source repositories:**
|
||||
- Main public: [github.com/mirivlad/sshkeeper](https://github.com/mirivlad/sshkeeper)
|
||||
|
|
@ -55,8 +70,7 @@ Requirements: Go 1.25+, Linux x86_64, system OpenSSH.
|
|||
|
||||
```bash
|
||||
tar -xzf sshkeeper_v0.2.0_linux_amd64.tar.gz
|
||||
chmod +x sshkeeper-linux-amd64
|
||||
sudo install -m 0755 sshkeeper-linux-amd64 /usr/local/bin/sshkeeper
|
||||
sudo install -m 0755 sshkeeper_v0.2.0_linux_amd64/sshkeeper /usr/local/bin/sshkeeper
|
||||
sshkeeper
|
||||
```
|
||||
|
||||
|
|
@ -285,10 +299,13 @@ If `XDG_CONFIG_HOME` or `XDG_DATA_HOME` are set, sshkeeper stores data under
|
|||
```bash
|
||||
go test ./...
|
||||
go build -o bin/sshkeeper .
|
||||
make release-check
|
||||
```
|
||||
|
||||
`bin/` is ignored by git.
|
||||
|
||||
For release packaging details, see [docs/release.md](docs/release.md).
|
||||
|
||||
## Project Layout
|
||||
|
||||
```text
|
||||
|
|
@ -302,8 +319,9 @@ sshkeeper/
|
|||
├── internal/vault/ # Encrypted vault
|
||||
├── internal/tunnel/ # Tunnel state management
|
||||
├── docs/guide.md # User guide
|
||||
├── docs/release.md # Release packaging guide
|
||||
├── build.sh # Build binary to bin/
|
||||
├── release.sh # Build release tarballs to dist/
|
||||
├── release.sh # Build release archives to dist/
|
||||
└── main.go
|
||||
```
|
||||
|
||||
|
|
|
|||
|
|
@ -21,7 +21,8 @@ var (
|
|||
var rootCmd = &cobra.Command{
|
||||
Use: "sshkeeper",
|
||||
Short: "sshkeeper — SSH connection manager",
|
||||
Long: `sshkeeper is a console SSH connection manager for Linux.
|
||||
Long: `sshkeeper is a console SSH connection manager.
|
||||
Linux is the primary target; macOS builds are available and Windows is experimental.
|
||||
It manages server profiles, secrets, and provides a convenient way
|
||||
to launch SSH sessions using the system OpenSSH client.`,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@
|
|||
|
||||
## Что такое sshkeeper
|
||||
|
||||
sshkeeper — это консольный менеджер SSH-подключений для Linux. Он хранит профили серверов, секреты (пароли, фразы от ключей) и запускает системный `ssh` с нужными опциями.
|
||||
sshkeeper — это консольный менеджер SSH-подключений. Основная целевая платформа — Linux; сборки для macOS доступны, Windows-сборка пока экспериментальная. Он хранит профили серверов, секреты (пароли, фразы от ключей) и запускает системный `ssh` с нужными опциями.
|
||||
|
||||
**Чем sshkeeper НЕ является:**
|
||||
- Это не Ansible — он не настраивает серверы и не пушит файлы
|
||||
|
|
@ -63,14 +63,27 @@ go build -o ~/.local/bin/sshkeeper .
|
|||
./release.sh # сборка релизных архивов в dist/
|
||||
```
|
||||
|
||||
**Требования:** Go 1.25+, Linux x86_64, системный OpenSSH.
|
||||
**Требования:** Go 1.25+ и системный OpenSSH.
|
||||
|
||||
Статус платформ:
|
||||
|
||||
| Платформа | Статус | Примечание |
|
||||
|-----------|--------|------------|
|
||||
| Linux | Основная целевая платформа | Архивы `linux/amd64` и `linux/arm64`. |
|
||||
| macOS | Сборки доступны, требуется проверка на целевых машинах | Архивы `darwin/amd64` и `darwin/arm64`, нужен системный `ssh`. Homebrew formula запланирована. |
|
||||
| Windows | Experimental | Нужен OpenSSH Client как `ssh.exe` в `PATH`; password/key-passphrase PTY-сценарии на Windows пока не подтверждены. |
|
||||
|
||||
На Windows OpenSSH Client можно установить через Windows Optional Features или PowerShell:
|
||||
|
||||
```powershell
|
||||
Add-WindowsCapability -Online -Name OpenSSH.Client~~~~0.0.1.0
|
||||
```
|
||||
|
||||
### Из релиза (после публикации v0.2.0)
|
||||
|
||||
```bash
|
||||
tar -xzf sshkeeper_v0.2.0_linux_amd64.tar.gz
|
||||
chmod +x sshkeeper-linux-amd64
|
||||
sudo install -m 0755 sshkeeper-linux-amd64 /usr/local/bin/sshkeeper
|
||||
sudo install -m 0755 sshkeeper_v0.2.0_linux_amd64/sshkeeper /usr/local/bin/sshkeeper
|
||||
```
|
||||
|
||||
---
|
||||
|
|
@ -192,7 +205,8 @@ sshkeeper — Quick Help
|
|||
sshkeeper — Full Help
|
||||
|
||||
What is sshkeeper
|
||||
sshkeeper is a Linux console SSH connection manager.
|
||||
sshkeeper is a console SSH connection manager.
|
||||
Linux is the primary target; macOS builds are available and Windows is experimental.
|
||||
It stores server profiles, secrets, and launches the system ssh client.
|
||||
|
||||
Navigation
|
||||
|
|
@ -782,6 +796,9 @@ go test ./...
|
|||
# Сборка
|
||||
go build -o bin/sshkeeper .
|
||||
|
||||
# Проверка релизной кросс-сборки
|
||||
make release-check
|
||||
|
||||
# Или через скрипты
|
||||
./build.sh # сборка в bin/
|
||||
./release.sh # релизные архивы в dist/
|
||||
|
|
|
|||
|
|
@ -0,0 +1,114 @@
|
|||
# Release Packaging
|
||||
|
||||
This document describes the manual release flow for sshkeeper.
|
||||
|
||||
## Create a Tag
|
||||
|
||||
Use a semantic version tag:
|
||||
|
||||
```bash
|
||||
git status --short
|
||||
git tag -a v0.2.0 -m "sshkeeper v0.2.0"
|
||||
git push origin v0.2.0
|
||||
```
|
||||
|
||||
The release script uses `git describe --tags --always --dirty` by default. You
|
||||
can also pass the version explicitly:
|
||||
|
||||
```bash
|
||||
./release.sh v0.2.0
|
||||
```
|
||||
|
||||
or:
|
||||
|
||||
```bash
|
||||
VERSION=v0.2.0 ./release.sh
|
||||
```
|
||||
|
||||
For reproducible archives, the script uses `SOURCE_DATE_EPOCH`. By default it
|
||||
uses the timestamp of the latest git commit. To force a specific timestamp:
|
||||
|
||||
```bash
|
||||
SOURCE_DATE_EPOCH=1760000000 ./release.sh v0.2.0
|
||||
```
|
||||
|
||||
## Run Release Checks
|
||||
|
||||
Before packaging, run:
|
||||
|
||||
```bash
|
||||
make release-check
|
||||
```
|
||||
|
||||
This runs:
|
||||
|
||||
- `go test ./...`
|
||||
- `go vet ./...`
|
||||
- native `go build`
|
||||
- `linux/amd64` cross-build with `CGO_ENABLED=0`
|
||||
- `linux/arm64` cross-build with `CGO_ENABLED=0`
|
||||
- `darwin/amd64` cross-build with `CGO_ENABLED=0`
|
||||
- `darwin/arm64` cross-build with `CGO_ENABLED=0`
|
||||
- `windows/amd64` cross-build with `CGO_ENABLED=0`
|
||||
|
||||
## Build Artifacts
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
./release.sh v0.2.0
|
||||
```
|
||||
|
||||
Expected files in `dist/`:
|
||||
|
||||
```text
|
||||
sshkeeper_v0.2.0_linux_amd64.tar.gz
|
||||
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
|
||||
checksums.txt
|
||||
```
|
||||
|
||||
Each archive contains:
|
||||
|
||||
- `sshkeeper` or `sshkeeper.exe`
|
||||
- `README.md`
|
||||
- `LICENSE`
|
||||
- `docs/guide.md`
|
||||
|
||||
## Verify Checksums
|
||||
|
||||
From the `dist/` directory:
|
||||
|
||||
```bash
|
||||
sha256sum -c checksums.txt
|
||||
```
|
||||
|
||||
Expected result: every archive reports `OK`.
|
||||
|
||||
## Publish in GitHub Release
|
||||
|
||||
Upload these files to the release:
|
||||
|
||||
- all five platform archives
|
||||
- `checksums.txt`
|
||||
|
||||
Release notes should mention platform status:
|
||||
|
||||
- Linux is the primary target.
|
||||
- macOS builds are available as tar.gz for amd64 and arm64 and require the
|
||||
system `ssh` client.
|
||||
- Windows build is experimental and requires OpenSSH Client available as
|
||||
`ssh.exe` in `PATH`.
|
||||
|
||||
## Packaging TODO
|
||||
|
||||
Prepare these package channels after the first archive-based release:
|
||||
|
||||
- deb package
|
||||
- Arch PKGBUILD / AUR
|
||||
- rpm later
|
||||
- Homebrew tap
|
||||
- Scoop manifest
|
||||
- Winget later
|
||||
|
|
@ -3,6 +3,7 @@ package config
|
|||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
|
||||
"github.com/BurntSushi/toml"
|
||||
)
|
||||
|
|
@ -34,7 +35,7 @@ type UIConfig struct {
|
|||
func defaultConfig() *Config {
|
||||
return &Config{
|
||||
SSH: SSHConfig{
|
||||
Binary: "/usr/bin/ssh",
|
||||
Binary: defaultSSHBinary(),
|
||||
ConnectTimeoutSec: 10,
|
||||
TestCommand: "echo SSHKEEPER_OK",
|
||||
},
|
||||
|
|
@ -47,6 +48,13 @@ func defaultConfig() *Config {
|
|||
}
|
||||
}
|
||||
|
||||
func defaultSSHBinary() string {
|
||||
if runtime.GOOS == "windows" {
|
||||
return "ssh.exe"
|
||||
}
|
||||
return "/usr/bin/ssh"
|
||||
}
|
||||
|
||||
func Load() (*Config, error) {
|
||||
cfg := defaultConfig()
|
||||
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import (
|
|||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
"github.com/mirivlad/sshkeeper/internal/config"
|
||||
|
|
@ -12,7 +13,34 @@ import (
|
|||
|
||||
type VaultFunc func(serverAlias string, secretType string) (string, error)
|
||||
|
||||
const windowsOpenSSHInstallHint = "Install OpenSSH Client via Windows Optional Features or PowerShell:\nAdd-WindowsCapability -Online -Name OpenSSH.Client~~~~0.0.1.0"
|
||||
|
||||
func EnsureSSHBinary(binary string) error {
|
||||
return validateSSHBinaryForOS(runtime.GOOS, binary, exec.LookPath)
|
||||
}
|
||||
|
||||
func validateSSHBinaryForOS(goos string, binary string, lookPath func(string) (string, error)) error {
|
||||
if goos == "windows" {
|
||||
if _, err := lookPath("ssh.exe"); err != nil {
|
||||
return fmt.Errorf("ssh.exe not found in PATH. Windows build is experimental and requires OpenSSH Client.\n%s", windowsOpenSSHInstallHint)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
if strings.TrimSpace(binary) == "" {
|
||||
binary = "ssh"
|
||||
}
|
||||
if _, err := lookPath(binary); err != nil {
|
||||
return fmt.Errorf("ssh binary not found (%s): %w", binary, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func Connect(cfg *config.Config, server *model.Server, getVault VaultFunc) error {
|
||||
if err := EnsureSSHBinary(cfg.SSH.Binary); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
args := BuildSSHArgsSimple(server)
|
||||
if strings.TrimSpace(server.StartupCommand) != "" {
|
||||
args = append(args, server.StartupCommand)
|
||||
|
|
@ -49,6 +77,10 @@ func Connect(cfg *config.Config, server *model.Server, getVault VaultFunc) error
|
|||
}
|
||||
|
||||
func RunCommand(cfg *config.Config, server *model.Server, getVault VaultFunc, command string) error {
|
||||
if err := EnsureSSHBinary(cfg.SSH.Binary); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
args := BuildSSHArgsSimple(server)
|
||||
args = append(args, command)
|
||||
|
||||
|
|
@ -78,6 +110,10 @@ func RunCommand(cfg *config.Config, server *model.Server, getVault VaultFunc, co
|
|||
}
|
||||
|
||||
func RunCommandOutput(cfg *config.Config, server *model.Server, getVault VaultFunc, command string) (string, error) {
|
||||
if err := EnsureSSHBinary(cfg.SSH.Binary); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
args := BuildSSHArgsSimple(server)
|
||||
args = append(args, "-o", fmt.Sprintf("ConnectTimeout=%d", cfg.SSH.ConnectTimeoutSec))
|
||||
|
||||
|
|
@ -116,6 +152,10 @@ func RunCommandOutput(cfg *config.Config, server *model.Server, getVault VaultFu
|
|||
}
|
||||
|
||||
func Test(cfg *config.Config, server *model.Server, getVault VaultFunc) (bool, string) {
|
||||
if err := EnsureSSHBinary(cfg.SSH.Binary); err != nil {
|
||||
return false, err.Error()
|
||||
}
|
||||
|
||||
args := BuildSSHArgsSimple(server)
|
||||
args = append(args, "-o", fmt.Sprintf("ConnectTimeout=%d", cfg.SSH.ConnectTimeoutSec))
|
||||
|
||||
|
|
@ -179,6 +219,10 @@ func testWithPassword(cfg *config.Config, args []string, password string) (bool,
|
|||
}
|
||||
|
||||
func ConnectWithArgs(cfg *config.Config, args []string, vaultFunc VaultFunc, server *model.Server) error {
|
||||
if err := EnsureSSHBinary(cfg.SSH.Binary); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
switch server.AuthMethod {
|
||||
case model.AuthPassword:
|
||||
password, err := vaultFunc(server.Alias, "ssh_password")
|
||||
|
|
|
|||
|
|
@ -104,6 +104,40 @@ func TestConnectRunsStartupCommand(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestValidateSSHBinaryForWindowsRequiresOpenSSHClient(t *testing.T) {
|
||||
err := validateSSHBinaryForOS("windows", "ssh.exe", func(name string) (string, error) {
|
||||
if name != "ssh.exe" {
|
||||
t.Fatalf("expected lookup for ssh.exe, got %q", name)
|
||||
}
|
||||
return "", os.ErrNotExist
|
||||
})
|
||||
|
||||
if err == nil {
|
||||
t.Fatal("expected missing ssh.exe error")
|
||||
}
|
||||
if !strings.Contains(err.Error(), "OpenSSH Client") {
|
||||
t.Fatalf("expected OpenSSH Client guidance, got %q", err)
|
||||
}
|
||||
if !strings.Contains(err.Error(), "Add-WindowsCapability -Online -Name OpenSSH.Client~~~~0.0.1.0") {
|
||||
t.Fatalf("expected PowerShell install command, got %q", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateSSHBinaryForNonWindowsChecksConfiguredBinary(t *testing.T) {
|
||||
var lookedUp string
|
||||
err := validateSSHBinaryForOS("linux", "/usr/bin/ssh", func(name string) (string, error) {
|
||||
lookedUp = name
|
||||
return "/usr/bin/ssh", nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("validate ssh binary: %v", err)
|
||||
}
|
||||
if lookedUp != "/usr/bin/ssh" {
|
||||
t.Fatalf("expected lookup for configured binary, got %q", lookedUp)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildSSHArgs_Simple(t *testing.T) {
|
||||
server := &model.Server{Host: "example.org", Port: 22, User: "root"}
|
||||
args := BuildSSHArgsSimple(server)
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
//go:build !windows
|
||||
|
||||
package ssh
|
||||
|
||||
import (
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
//go:build !windows
|
||||
|
||||
package ssh
|
||||
|
||||
import (
|
||||
|
|
|
|||
|
|
@ -0,0 +1,15 @@
|
|||
//go:build windows
|
||||
|
||||
package ssh
|
||||
|
||||
import "fmt"
|
||||
|
||||
const windowsPTYUnsupportedMessage = "password and key-passphrase auth are not supported on Windows experimental builds; use key or agent auth with OpenSSH Client"
|
||||
|
||||
func ConnectWithPassword(sshBinary string, args []string, password string) error {
|
||||
return fmt.Errorf("%s", windowsPTYUnsupportedMessage)
|
||||
}
|
||||
|
||||
func connectWithPasswordAndRead(sshBinary string, args []string, password string, timeoutSec int) (bool, string) {
|
||||
return false, windowsPTYUnsupportedMessage
|
||||
}
|
||||
|
|
@ -159,7 +159,8 @@ func (m *fullHelpModel) View() string {
|
|||
rows [][2]string
|
||||
}{
|
||||
{"What is sshkeeper", [][2]string{
|
||||
{"", "sshkeeper is a Linux console SSH connection manager."},
|
||||
{"", "sshkeeper is a console SSH connection manager."},
|
||||
{"", "Linux is the primary target; macOS builds are available and Windows is experimental."},
|
||||
{"", "It stores server profiles, secrets, and launches the system ssh client."},
|
||||
{"", ""},
|
||||
}},
|
||||
|
|
|
|||
|
|
@ -93,6 +93,10 @@ func Start(cfg *config.Config, server *model.Server, forwards []*model.Forward,
|
|||
mu.Lock()
|
||||
defer mu.Unlock()
|
||||
|
||||
if err := ssh.EnsureSSHBinary(cfg.SSH.Binary); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Filter enabled forwards
|
||||
var active []*model.Forward
|
||||
for _, f := range forwards {
|
||||
|
|
|
|||
71
release.sh
71
release.sh
|
|
@ -4,26 +4,73 @@ set -euo pipefail
|
|||
cd "$(dirname "$0")"
|
||||
|
||||
APP=sshkeeper
|
||||
VERSION=$(git describe --tags --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)}
|
||||
|
||||
echo "==> Building release ${APP} ${VERSION}..."
|
||||
echo "==> SOURCE_DATE_EPOCH=${SOURCE_DATE_EPOCH}"
|
||||
|
||||
rm -rf "${DIST_DIR}"
|
||||
mkdir -p "${DIST_DIR}"
|
||||
|
||||
# Linux amd64
|
||||
echo "==> linux/amd64..."
|
||||
GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -ldflags "${LDFLAGS}" -o "${DIST_DIR}/${APP}" .
|
||||
tar -czf "${DIST_DIR}/${APP}_${VERSION}_linux_amd64.tar.gz" -C "${DIST_DIR}" "${APP}"
|
||||
rm -f "${DIST_DIR}/${APP}"
|
||||
package_docs() {
|
||||
local package_dir="$1"
|
||||
|
||||
# Linux arm64
|
||||
echo "==> linux/arm64..."
|
||||
GOOS=linux GOARCH=arm64 CGO_ENABLED=0 go build -ldflags "${LDFLAGS}" -o "${DIST_DIR}/${APP}" .
|
||||
tar -czf "${DIST_DIR}/${APP}_${VERSION}_linux_arm64.tar.gz" -C "${DIST_DIR}" "${APP}"
|
||||
rm -f "${DIST_DIR}/${APP}"
|
||||
cp README.md LICENSE "${package_dir}/"
|
||||
if [[ -f docs/guide.md ]]; then
|
||||
mkdir -p "${package_dir}/docs"
|
||||
cp docs/guide.md "${package_dir}/docs/"
|
||||
fi
|
||||
}
|
||||
|
||||
normalize_package() {
|
||||
local package_dir="$1"
|
||||
find "${package_dir}" -exec touch -h -d "@${SOURCE_DATE_EPOCH}" {} +
|
||||
}
|
||||
|
||||
build_tarball() {
|
||||
local goos="$1"
|
||||
local goarch="$2"
|
||||
local package_dir="${DIST_DIR}/${APP}_${VERSION}_${goos}_${goarch}"
|
||||
local archive="${DIST_DIR}/${APP}_${VERSION}_${goos}_${goarch}.tar.gz"
|
||||
|
||||
echo "==> ${goos}/${goarch}..."
|
||||
rm -rf "${package_dir}"
|
||||
mkdir -p "${package_dir}"
|
||||
|
||||
GOOS="${goos}" GOARCH="${goarch}" CGO_ENABLED=0 go build -trimpath -ldflags "${LDFLAGS}" -o "${package_dir}/${APP}" .
|
||||
package_docs "${package_dir}"
|
||||
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}"
|
||||
}
|
||||
|
||||
build_zip() {
|
||||
local goos="$1"
|
||||
local goarch="$2"
|
||||
local package_dir="${DIST_DIR}/${APP}_${VERSION}_${goos}_${goarch}"
|
||||
local archive="${DIST_DIR}/${APP}_${VERSION}_${goos}_${goarch}.zip"
|
||||
|
||||
echo "==> ${goos}/${goarch}..."
|
||||
rm -rf "${package_dir}"
|
||||
mkdir -p "${package_dir}"
|
||||
|
||||
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}"
|
||||
(cd "${DIST_DIR}" && find "$(basename "${package_dir}")" -print | sort | zip -X -q "$(basename "${archive}")" -@)
|
||||
rm -rf "${package_dir}"
|
||||
}
|
||||
|
||||
build_tarball linux amd64
|
||||
build_tarball linux arm64
|
||||
build_tarball darwin amd64
|
||||
build_tarball darwin arm64
|
||||
build_zip windows amd64
|
||||
|
||||
(cd "${DIST_DIR}" && sha256sum *.tar.gz *.zip > checksums.txt)
|
||||
|
||||
echo "==> Done."
|
||||
ls -lh "${DIST_DIR}/"*.tar.gz
|
||||
ls -lh "${DIST_DIR}/"*.tar.gz "${DIST_DIR}/"*.zip "${DIST_DIR}/checksums.txt"
|
||||
|
|
|
|||
Loading…
Reference in New Issue