Add GoReleaser configuration and version command
Some checks failed
goreleaser / goreleaser (push) Failing after 5m46s
Some checks failed
goreleaser / goreleaser (push) Failing after 5m46s
This commit is contained in:
parent
e1f7604439
commit
a806b97b9b
37
.github/workflows/go-releaser.yml
vendored
Normal file
37
.github/workflows/go-releaser.yml
vendored
Normal file
@ -0,0 +1,37 @@
|
||||
name: goreleaser
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- 'v*'
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
jobs:
|
||||
goreleaser:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: stable
|
||||
|
||||
- name: Run GoReleaser
|
||||
uses: goreleaser/goreleaser-action@v5
|
||||
with:
|
||||
distribution: goreleaser
|
||||
version: latest
|
||||
args: release --clean
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Update latest tag
|
||||
run: |
|
||||
git tag -f latest
|
||||
git push -f origin latest
|
106
.goreleaser.yml
Normal file
106
.goreleaser.yml
Normal file
@ -0,0 +1,106 @@
|
||||
# This is a GoReleaser configuration file
|
||||
# For more information about configuration options, visit:
|
||||
# https://goreleaser.com/customization/
|
||||
|
||||
version: 2
|
||||
|
||||
before:
|
||||
hooks:
|
||||
- go mod tidy
|
||||
|
||||
builds:
|
||||
- env:
|
||||
- CGO_ENABLED=0
|
||||
goos:
|
||||
- linux
|
||||
- darwin
|
||||
goarch:
|
||||
- amd64
|
||||
- arm64
|
||||
flags:
|
||||
- -trimpath
|
||||
ldflags:
|
||||
- -s -w
|
||||
- -X git.mvl.sh/vleeuwenmenno/sshtunnel/cmd.Version={{.Version}}
|
||||
- -X git.mvl.sh/vleeuwenmenno/sshtunnel/cmd.BuildDate={{.Date}}
|
||||
mod_timestamp: "{{ .CommitTimestamp }}"
|
||||
|
||||
archives:
|
||||
- format: tar.gz
|
||||
name_template: >-
|
||||
{{ .ProjectName }}_
|
||||
{{- title .Os }}_
|
||||
{{- if eq .Arch "amd64" }}x86_64
|
||||
{{- else if eq .Arch "386" }}i386
|
||||
{{- else }}{{ .Arch }}{{ end }}
|
||||
{{- if .Arm }}v{{ .Arm }}{{ end }}
|
||||
format_overrides:
|
||||
- goos: windows
|
||||
format: zip
|
||||
|
||||
checksum:
|
||||
name_template: "checksums.txt"
|
||||
|
||||
snapshot:
|
||||
name_template: "{{ incpatch .Version }}-next"
|
||||
|
||||
changelog:
|
||||
sort: asc
|
||||
filters:
|
||||
exclude:
|
||||
- "^docs:"
|
||||
- "^test:"
|
||||
|
||||
# Homebrew formula
|
||||
brews:
|
||||
- repository:
|
||||
owner: vleeuwenmenno
|
||||
name: homebrew-tap
|
||||
token: "{{ .Env.GITHUB_TOKEN }}"
|
||||
directory: Formula
|
||||
homepage: "https://git.mvl.sh/vleeuwenmenno/sshtunnel"
|
||||
description: "SSH tunnel manager CLI tool"
|
||||
license: "MIT"
|
||||
commit_author:
|
||||
name: goreleaserbot
|
||||
email: goreleaser@carlosbecker.com
|
||||
install: |
|
||||
bin.install "sshtunnel"
|
||||
|
||||
# Generate shell completions
|
||||
output = Utils.safe_popen_read("#{bin}/sshtunnel", "completion", "bash")
|
||||
(bash_completion/"sshtunnel").write output
|
||||
|
||||
output = Utils.safe_popen_read("#{bin}/sshtunnel", "completion", "zsh")
|
||||
(zsh_completion/"_sshtunnel").write output
|
||||
|
||||
output = Utils.safe_popen_read("#{bin}/sshtunnel", "completion", "fish")
|
||||
(fish_completion/"sshtunnel.fish").write output
|
||||
test: |
|
||||
system "#{bin}/sshtunnel", "version"
|
||||
|
||||
# .deb and .rpm packages
|
||||
nfpms:
|
||||
- file_name_template: "{{ .ProjectName }}_{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}"
|
||||
id: packages
|
||||
homepage: https://git.mvl.sh/vleeuwenmenno/sshtunnel
|
||||
description: |-
|
||||
SSH tunnel manager CLI tool
|
||||
maintainer: Menno van Leeuwen
|
||||
license: MIT
|
||||
vendor: vleeuwenmenno
|
||||
formats:
|
||||
- deb
|
||||
- rpm
|
||||
dependencies:
|
||||
- openssh-client
|
||||
recommends:
|
||||
- bash-completion
|
||||
scripts:
|
||||
postinstall: |
|
||||
sshtunnel completion bash > /usr/share/bash-completion/completions/sshtunnel
|
||||
sshtunnel completion zsh > /usr/share/zsh/site-functions/_sshtunnel
|
||||
sshtunnel completion fish > /usr/share/fish/vendor_completions.d/sshtunnel.fish
|
||||
chmod 644 /usr/share/bash-completion/completions/sshtunnel || true
|
||||
chmod 644 /usr/share/zsh/site-functions/_sshtunnel || true
|
||||
chmod 644 /usr/share/fish/vendor_completions.d/sshtunnel.fish || true
|
71
README.md
71
README.md
@ -13,6 +13,12 @@ A Go-based command-line tool to manage SSH tunnels. This tool allows you to:
|
||||
go install git.mvl.sh/vleeuwenmenno/sshtunnel@latest
|
||||
```
|
||||
|
||||
You can also install a specific version:
|
||||
|
||||
```
|
||||
go install git.mvl.sh/vleeuwenmenno/sshtunnel@v1.0.0
|
||||
```
|
||||
|
||||
Or clone this repository and build it yourself:
|
||||
|
||||
```
|
||||
@ -22,6 +28,8 @@ make
|
||||
sudo make install
|
||||
```
|
||||
|
||||
The build process will automatically include version information from git tags.
|
||||
|
||||
## Uninstallation
|
||||
|
||||
For installation using `go install`, run:
|
||||
@ -38,6 +46,17 @@ sudo make uninstall
|
||||
|
||||
## Usage
|
||||
|
||||
### Showing version information
|
||||
|
||||
```
|
||||
sshtunnel version
|
||||
```
|
||||
|
||||
This will display the current version of sshtunnel along with build information.
|
||||
|
||||
Options:
|
||||
- `-c, --check-updates`: Check for updates against the latest release
|
||||
|
||||
### Listing active tunnels
|
||||
|
||||
```
|
||||
@ -111,6 +130,58 @@ This command will:
|
||||
3. Validate all recorded tunnels and their current state
|
||||
4. Show active SSH tunnel processes and their status
|
||||
|
||||
### Version information
|
||||
|
||||
```
|
||||
sshtunnel version --check-updates
|
||||
```
|
||||
|
||||
This displays version information and checks for updates to the tool.
|
||||
|
||||
When a new version is available, you'll get instructions on how to update.
|
||||
|
||||
### Shell completion
|
||||
|
||||
Generate shell completion scripts:
|
||||
|
||||
```
|
||||
# Bash
|
||||
sshtunnel completion bash > ~/.bash_completion.d/sshtunnel
|
||||
source ~/.bash_completion.d/sshtunnel
|
||||
|
||||
# Zsh
|
||||
sshtunnel completion zsh > "${fpath[1]}/_sshtunnel"
|
||||
|
||||
# Fish
|
||||
sshtunnel completion fish > ~/.config/fish/completions/sshtunnel.fish
|
||||
```
|
||||
|
||||
For system-wide installation:
|
||||
```
|
||||
# Bash (Linux)
|
||||
sudo sshtunnel completion bash > /etc/bash_completion.d/sshtunnel
|
||||
|
||||
# Bash (macOS with Homebrew)
|
||||
sshtunnel completion bash > $(brew --prefix)/etc/bash_completion.d/sshtunnel
|
||||
```
|
||||
|
||||
## Development
|
||||
|
||||
### Release Process
|
||||
|
||||
The project includes a release script to help manage version tags:
|
||||
|
||||
```
|
||||
./bin/scripts/release.sh
|
||||
```
|
||||
|
||||
This script will:
|
||||
1. Find the latest version tag
|
||||
2. Suggest the next patch version (e.g., v1.0.0 → v1.0.1)
|
||||
3. Allow you to accept this suggestion or enter a custom version
|
||||
4. Create and push the new tag
|
||||
5. Update the "latest" tag to point to the new version
|
||||
|
||||
## How it works
|
||||
|
||||
The tool creates SSH tunnels using the system's SSH client and manages them by tracking their process IDs in a hidden directory (`~/.sshtunnels/`). Each tunnel is assigned a unique ID for easy management. Traffic statistics are collected and stored to help you monitor data transfer through your tunnels.
|
||||
|
@ -28,8 +28,12 @@ if [ -n "$(git status --porcelain)" ]; then
|
||||
POSTFIX=" (dirty)"
|
||||
fi
|
||||
|
||||
# Format a clean version string for the build
|
||||
VERSION=$(echo "$BRANCH" | sed 's/^v//')
|
||||
BUILD_DATE=$(date -u '+%Y-%m-%d %H:%M:%S')
|
||||
|
||||
printfe "%s\n" "cyan" "Building $BINARY_NAME binary for $BRANCH ($LATEST_COMMIT_HASH)$POSTFIX..."
|
||||
go build -o $BINARY_PATH
|
||||
go build -ldflags="-X 'git.mvl.sh/vleeuwenmenno/sshtunnel/cmd.Version=$BRANCH' -X 'git.mvl.sh/vleeuwenmenno/sshtunnel/cmd.BuildDate=$BUILD_DATE'" -o $BINARY_PATH
|
||||
|
||||
if [ $? -ne 0 ]; then
|
||||
printf "\033[0;31m"
|
||||
@ -39,9 +43,9 @@ if [ $? -ne 0 ]; then
|
||||
fi
|
||||
|
||||
# Put tag and hash in .sshtunnel_version file
|
||||
echo "$BRANCH ($LATEST_COMMIT_HASH)$POSTFIX" > $BINARY_PATH_VERSION
|
||||
echo "$BRANCH" > $BINARY_PATH_VERSION
|
||||
|
||||
printfe "%s\n" "cyan" "Generating Bash completion script..."
|
||||
printfe "%s\n" "cyan" "Generating completion scripts..."
|
||||
$BINARY_PATH completion bash > $COMPLETION_SCRIPT
|
||||
|
||||
printfe "%s\n" "green" "Bash completion script installed to $COMPLETION_SCRIPT."
|
||||
|
216
cmd/version.go
Normal file
216
cmd/version.go
Normal file
@ -0,0 +1,216 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"runtime"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var (
|
||||
// Version is set during build
|
||||
Version = "dev"
|
||||
|
||||
// BuildDate is set during build
|
||||
BuildDate = ""
|
||||
|
||||
// For go install builds, this will be the go.mod version
|
||||
versionFromMod = "$GOFLAGS: -ldflags=-X git.mvl.sh/vleeuwenmenno/sshtunnel/cmd.Version=${VERSION}"
|
||||
|
||||
checkForUpdates bool
|
||||
)
|
||||
|
||||
var versionCmd = &cobra.Command{
|
||||
Use: "version",
|
||||
Short: "Show sshtunnel version information",
|
||||
Long: `Display the current version of sshtunnel and check for updates.`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
// Display current version
|
||||
showVersion()
|
||||
|
||||
// Check for updates if requested
|
||||
if checkForUpdates {
|
||||
checkUpdate()
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
rootCmd.AddCommand(versionCmd)
|
||||
versionCmd.Flags().BoolVarP(&checkForUpdates, "check-updates", "c", false, "Check for updates")
|
||||
|
||||
// If version is still "dev", try to read from other sources
|
||||
if Version == "dev" {
|
||||
// Check if version was injected via go install's version info
|
||||
if strings.Contains(versionFromMod, "${VERSION}") {
|
||||
// Not replaced, try to read from the installed version file
|
||||
Version = readInstalledVersion()
|
||||
} else {
|
||||
// Extract version from the injected string
|
||||
re := regexp.MustCompile(`Version=(.+)$`)
|
||||
matches := re.FindStringSubmatch(versionFromMod)
|
||||
if len(matches) > 1 {
|
||||
Version = matches[1]
|
||||
} else {
|
||||
// Fall back to reading from installed file
|
||||
Version = readInstalledVersion()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func showVersion() {
|
||||
fmt.Printf("sshtunnel %s\n", Version)
|
||||
if BuildDate != "" {
|
||||
fmt.Printf("Build date: %s\n", BuildDate)
|
||||
}
|
||||
fmt.Printf("Go version: %s\n", runtime.Version())
|
||||
fmt.Printf("OS/Arch: %s/%s\n", runtime.GOOS, runtime.GOARCH)
|
||||
}
|
||||
|
||||
func readInstalledVersion() string {
|
||||
// Check system-installed version file first
|
||||
versionFile := "/usr/local/share/sshtunnel/sshtunnel.version"
|
||||
if _, err := os.Stat(versionFile); err == nil {
|
||||
content, err := os.ReadFile(versionFile)
|
||||
if err == nil {
|
||||
return strings.TrimSpace(string(content))
|
||||
}
|
||||
}
|
||||
|
||||
// Try user's home directory for go install version
|
||||
homeDir, err := os.UserHomeDir()
|
||||
if err == nil {
|
||||
goPath := filepath.Join(homeDir, "go", "pkg", "mod", "git.mvl.sh", "vleeuwenmenno", "sshtunnel@*")
|
||||
matches, err := filepath.Glob(goPath)
|
||||
if err == nil && len(matches) > 0 {
|
||||
// Sort matches to get the latest version
|
||||
// Example: .../sshtunnel@v1.0.0 -> extract v1.0.0
|
||||
latestMatch := matches[len(matches)-1]
|
||||
parts := strings.Split(filepath.Base(latestMatch), "@")
|
||||
if len(parts) > 1 {
|
||||
return parts[1]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check if executable name has version info
|
||||
execPath, err := os.Executable()
|
||||
if err == nil {
|
||||
execName := filepath.Base(execPath)
|
||||
if strings.Contains(execName, "sshtunnel-v") {
|
||||
parts := strings.Split(execName, "-v")
|
||||
if len(parts) > 1 {
|
||||
return "v" + parts[1]
|
||||
}
|
||||
}
|
||||
|
||||
// Check if it's in GOBIN with a version suffix
|
||||
re := regexp.MustCompile(`sshtunnel@(v[0-9]+\.[0-9]+\.[0-9]+)$`)
|
||||
matches := re.FindStringSubmatch(execName)
|
||||
if len(matches) > 1 {
|
||||
return matches[1]
|
||||
}
|
||||
}
|
||||
|
||||
// Default version if we couldn't find any version information
|
||||
return "dev"
|
||||
}
|
||||
|
||||
func checkUpdate() {
|
||||
fmt.Println("\nChecking for updates...")
|
||||
client := http.Client{
|
||||
Timeout: 5 * time.Second,
|
||||
}
|
||||
|
||||
resp, err := client.Get("https://git.mvl.sh/api/v1/repos/vleeuwenmenno/sshtunnel/tags")
|
||||
if err != nil {
|
||||
fmt.Printf("Error checking for updates: %s\n", err)
|
||||
return
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
fmt.Printf("Error reading response: %s\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
var tags []struct {
|
||||
Name string `json:"name"`
|
||||
}
|
||||
if err := json.Unmarshal(body, &tags); err != nil {
|
||||
fmt.Printf("Error parsing response: %s\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
// Find the latest version tag
|
||||
var latestTag string
|
||||
for _, tag := range tags {
|
||||
if strings.HasPrefix(tag.Name, "v") && (latestTag == "" || compareVersions(tag.Name, latestTag) > 0) {
|
||||
latestTag = tag.Name
|
||||
}
|
||||
}
|
||||
|
||||
if latestTag == "" {
|
||||
fmt.Println("No version tags found in the repository")
|
||||
return
|
||||
}
|
||||
|
||||
// Compare with current version
|
||||
if compareVersions(latestTag, Version) > 0 {
|
||||
fmt.Printf("A newer version is available: %s (you have %s)\n", latestTag, Version)
|
||||
fmt.Println("To update, run: go install git.mvl.sh/vleeuwenmenno/sshtunnel@latest")
|
||||
} else {
|
||||
fmt.Println("You are running the latest version")
|
||||
}
|
||||
}
|
||||
|
||||
// compareVersions compares two semantic version strings (v1.2.3)
|
||||
// returns: 1 if v1 > v2
|
||||
// -1 if v1 < v2
|
||||
// 0 if v1 == v2
|
||||
func compareVersions(v1, v2 string) int {
|
||||
// Remove 'v' prefix if any
|
||||
v1 = strings.TrimPrefix(v1, "v")
|
||||
v2 = strings.TrimPrefix(v2, "v")
|
||||
|
||||
// Split into parts
|
||||
parts1 := strings.Split(v1, ".")
|
||||
parts2 := strings.Split(v2, ".")
|
||||
|
||||
// Compare each part
|
||||
for i := 0; i < len(parts1) && i < len(parts2); i++ {
|
||||
// Skip non-numeric parts (like pre-release suffixes)
|
||||
num1 := 0
|
||||
fmt.Sscanf(parts1[i], "%d", &num1)
|
||||
|
||||
num2 := 0
|
||||
fmt.Sscanf(parts2[i], "%d", &num2)
|
||||
|
||||
if num1 > num2 {
|
||||
return 1
|
||||
}
|
||||
if num1 < num2 {
|
||||
return -1
|
||||
}
|
||||
}
|
||||
|
||||
// If we get here, the common parts are equal
|
||||
if len(parts1) > len(parts2) {
|
||||
return 1
|
||||
}
|
||||
if len(parts1) < len(parts2) {
|
||||
return -1
|
||||
}
|
||||
|
||||
return 0
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user