Skip to content

ginsys/goshlink

Repository files navigation

goshlink

A command-line interface (CLI) for managing Shlink short URLs via the REST API v3.

Features

  • Create short URLs with custom slugs/paths
  • List all short URLs with automatic pagination
  • Support for multiple domains with easy alias configuration
  • Three-tier configuration: config file, environment variables, and CLI flags
  • Smart output: plain table for terminals, JSON for pipes
  • XDG Base Directory compliant

Installation

From Source

go install github.com/ginsys/goshlink/cmd/goshlink@latest

Build Locally

git clone https://github.com/ginsys/goshlink.git
cd goshlink
go build ./cmd/goshlink

Configuration

goshlink supports three configuration sources with the following precedence:

  1. CLI flags (highest priority)
  2. Environment variables
  3. Config file (lowest priority)

Required Settings

  • base_url - Shlink instance URL (scheme + host + optional subpath)
  • api_key - Shlink API key
  • default_domain - Default domain for short URLs

Optional Settings

  • list_sort - Sorting for plain output: "domain,path" (default) or "path,domain"

Config File

Config file location (XDG compliant):

  • Primary: $XDG_CONFIG_HOME/goshlink/config.toml
  • Fallback: ~/.config/goshlink/config.toml

Example config.toml:

base_url = "https://s.example.com"
api_key = "your-api-key-here"
default_domain = "s.example.com"
list_sort = "domain,path"

[domains]
prod = "s.example.com"
work = "go.example.net"
lab  = "s.home.arpa"

Note: The base_url should NOT include /rest/v3. The CLI automatically appends this.

Examples:

  • https://s.example.com → API base: https://s.example.com/rest/v3
  • https://s.example.com/shlink/ → API base: https://s.example.com/shlink/rest/v3

Environment Variables

export GOSHLINK_BASE_URL="https://s.example.com"
export GOSHLINK_API_KEY="your-api-key-here"
export GOSHLINK_DEFAULT_DOMAIN="s.example.com"
export GOSHLINK_LIST_SORT="domain,path"  # optional

CLI Flags

Global flags available for all commands:

  • --base-url - Override base URL
  • --api-key - Override API key
  • --default-domain - Override default domain
  • --output - Output mode: plain or json

Domain Aliases

Domain aliases provide a convenient shorthand for frequently used domains. Define them in the [domains] section of your config file:

[domains]
prod = "s.example.com"
work = "go.example.net"
lab  = "s.home.arpa"

When using the --domain flag:

  • If the value contains a . (dot), it's treated as a literal domain
  • Otherwise, it's looked up as an alias

Examples:

# Using alias
goshlink create --domain prod mylink https://example.com

# Using literal domain
goshlink create --domain s.example.com mylink https://example.com

Command Aliases

All commands have short aliases for convenience:

Command Aliases Usage
create c goshlink c <path> <url>
list l, ls goshlink l
config cf goshlink cf

Examples:

goshlink c mylink https://example.com    # create
goshlink l                                # list
goshlink cf                               # config wizard

Usage

Configure

goshlink config

Interactively create or update the configuration file. This command prompts for:

  • Shlink base URL
  • API key
  • Default domain
  • Domain aliases (optional)

Configuration is saved to ~/.config/goshlink/config.toml.

Example:

$ goshlink config
Configure goshlink
==================

Shlink base URL: https://s.example.com
API key: your-api-key-here
Default domain: s.example.com

Add/modify domain aliases? [Y/n]: y

Alias name (empty to finish): prod
Domain for 'prod': s.example.com
Add another? [y/N]: n

Configuration saved to ~/.config/goshlink/config.toml

Create a Short URL

goshlink create <path> <long-url>

Arguments:

  • <path> - Custom slug/path for the short URL (must not start with /)
  • <long-url> - The destination URL

Flags:

  • --domain <domain-or-alias> - Domain or alias (default: default_domain from config)

Examples:

# Basic usage (uses default_domain)
goshlink create mylink https://example.com/very/long/url

# With domain alias
goshlink create --domain work meeting https://zoom.us/j/123456789

# With literal domain
goshlink create --domain s.example.com docs https://docs.example.com

# Path with slashes (allowed)
goshlink create reports/2024/q1 https://example.com/reports/2024-q1.pdf

# Force JSON output
goshlink create --output json mylink https://example.com

List Short URLs

goshlink list

Flags:

  • --domain <domain-or-alias> - Filter by domain or alias
  • --output <mode> - Output mode: plain or json

Examples:

# List all short URLs
goshlink list

# Filter by domain alias
goshlink list --domain prod

# Filter by literal domain
goshlink list --domain s.example.com

# Force JSON output
goshlink list --output json

# Pipe to jq for processing
goshlink list | jq '.[] | select(.domain == "s.example.com")'

Output Modes

goshlink supports two output modes:

Plain Text (Table)

Default when output is a terminal (TTY). Shows a 4-column table:

DOMAIN               PATH                 SHORT_URL                                LONG_URL
s.example.com        mylink               https://s.example.com/mylink             https://example.com

Sorting is configurable via list_sort config option. Default: domain (primary) and path (secondary).

JSON

Default when output is piped or redirected. Key API response fields:

Create:

{
  "shortCode": "mylink",
  "shortUrl": "https://s.example.com/mylink",
  "longUrl": "https://example.com",
  "dateCreated": "2024-01-12T10:30:00Z",
  "domain": "s.example.com",
  ...
}

List:

[
  {
    "shortCode": "mylink",
    "shortUrl": "https://s.example.com/mylink",
    "longUrl": "https://example.com",
    ...
  },
  ...
]

Auto-Detection

The output mode is automatically detected:

  • TTY (terminal): plain text table
  • Pipe/redirect: JSON

Override with --output:

# Force plain even when piping
goshlink list --output plain | less

# Force JSON in terminal
goshlink list --output json

Examples

Initial Setup

Option 1: Interactive Configuration (Recommended)

Use the built-in config wizard:

goshlink config

This will interactively prompt for:

  • Shlink base URL
  • API key
  • Default domain
  • Optional domain aliases

The configuration is automatically saved to ~/.config/goshlink/config.toml.

Option 2: Manual Configuration

  1. Create config directory:
mkdir -p ~/.config/goshlink
  1. Create config file:
cat > ~/.config/goshlink/config.toml <<EOF
base_url = "https://s.example.com"
api_key = "your-api-key-here"
default_domain = "s.example.com"

[domains]
prod = "s.example.com"
work = "go.example.net"
EOF
  1. Test configuration:
goshlink list

Common Workflows

Create and verify:

goshlink create docs https://docs.example.com
goshlink list --domain prod

Export all URLs to JSON:

goshlink list > urls.json

Find URLs matching pattern:

goshlink list | jq '.[] | select(.longUrl | contains("github"))'

Create URL on specific domain:

goshlink create --domain work standup https://meet.google.com/abc-defg-hij

Error Handling

goshlink provides clear error messages:

Missing configuration:

Error: missing required configuration: base_url, api_key

Invalid path:

Error: path must not start with '/'

API errors:

Error: creating short URL: HTTP 400: INVALID_SLUG - The custom slug is already in use

Unknown domain alias:

Error: unknown domain alias: unknown

Development

Makefile Targets

The project includes a Makefile for common tasks:

make help          # Show available targets (default)
make test          # Run all tests
make build         # Build the goshlink binary with version
make clean         # Remove build artifacts
make release       # Trigger release workflow (requires gh CLI)
make prerelease    # Trigger prerelease workflow with auto-increment (requires gh CLI)
make release-local # Test release build locally (requires goreleaser)

Release targets:

  • make release - Triggers final release workflow for current VERSION
  • make prerelease - Auto-increments RC number and triggers prerelease
    • First run: creates v0.1.0-rc1
    • Subsequent runs: v0.1.0-rc2, v0.1.0-rc3, etc.
  • make release-local - Test GoReleaser locally without publishing

Version Management

The project version is managed via a VERSION file in the repository root. This file contains the semantic version number (e.g., 0.1.0).

View current version:

cat VERSION

Local builds:

make build
./goshlink --version  # Shows: 0.1.0-dev-<gitsha>

Local dev builds append -dev-<gitsha> to distinguish them from release builds.

Managing versions:

The VERSION file is the single source of truth for versioning. All releases are triggered from this file.

CI/CD Workflow

The project uses GitHub Actions for continuous integration and releases:

CI Workflow

  • Trigger: Push to main, pull requests, or tags
  • Actions:
    • Runs all tests
    • Builds cross-platform binaries (linux/amd64, linux/arm64, darwin/amd64, darwin/arm64)
    • Version format: <version>-<datetime>-<gitsha> (e.g., 0.1.0-20240112T153045-abc1234)
    • Uploads binaries as workflow artifacts

Download artifacts from the Actions tab on GitHub to test builds.

Release Workflow (Manual Dispatch)

Releases are created via manual workflow dispatch in GitHub Actions.

Creating a release:

Option A: Using Make (Recommended)

# Update VERSION if needed
echo "0.2.0" > VERSION
git commit -am "Bump to 0.2.0" && git push

# Trigger release
make release        # Final release
make prerelease     # Prerelease with auto-increment (rc1, rc2, etc.)

The make prerelease command automatically:

  • Queries existing releases from GitHub
  • Finds next RC number (e.g., rc1rc2rc3)
  • Triggers workflow with correct suffix

Option B: Manual via GitHub UI

  1. Update VERSION file (if needed):

    echo "0.2.0" > VERSION
    git commit -am "Bump to 0.2.0"
    git push origin main
  2. Trigger release via GitHub Actions:

    • Go to GitHub → Actions → Release workflow
    • Click "Run workflow" button
    • Optional: Enter prerelease suffix (e.g., -rc1, -beta)
    • Click "Run workflow"

Workflow automatically:

  • Reads VERSION file
  • Creates git tag (e.g., v0.2.0 or v0.2.0-rc1)
  • Builds binaries for all platforms
  • Creates checksums
  • Generates changelog
  • Publishes GitHub release with binaries

Examples:

Action Command Creates
First prerelease make prerelease v0.1.0-rc1
Next prerelease make prerelease v0.1.0-rc2
Final release make release v0.1.0

Local release testing:

# Test release build without publishing
make release-local

Run Tests

go test ./...

Project Structure

goshlink/
├── cmd/goshlink/           # CLI entry point
│   └── main.go
├── internal/
│   ├── config/             # Configuration management
│   │   ├── config.go
│   │   └── config_test.go
│   ├── shlink/             # Shlink API client
│   │   ├── client.go
│   │   ├── client_test.go
│   │   └── types.go
│   └── output/             # Output formatting
│       └── output.go
├── go.mod
├── go.sum
└── README.md

API Compatibility

This tool uses Shlink REST API v3. The API path /rest/v3 is automatically appended to your configured base_url.

For more information about Shlink, visit shlink.io.

Contributing

Contributions welcome! Please open an issue or pull request.

License

goshlink is licensed under the GNU General Public License version 3 (GPL-3.0-only).

  • See LICENSE for the full license text.
  • Third-party dependencies are licensed under their respective terms; see THIRD_PARTY_NOTICES.md and the LICENSES/ directory (generated via make licenses).

About

A command-line interface (CLI) for managing [Shlink](https://shlink.io/) short URLs via the REST API v3.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published