golink

package module
v0.0.0-...-43d714d Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Nov 21, 2024 License: BSD-3-Clause Imports: 33 Imported by: 1

README

golink is a private shortlink service for your tailnet. It lets you create short, memorable links for the websites you and your team use most. If you're new to golink, learn more in our announcement blog post. If you were looking for a SaaS go link service that doesn't use Tailscale, you might be thinking of golinks.io or trot.to

Screenshot of golink home screen

Building and running

To build from source and run in dev mode:

go run ./cmd/golink -dev-listen :8080

golink will be available at http://localhost:8080/, storing links in a temporary database, and will not attempt to join a tailnet.

The equivalent using the pre-built docker image:

docker run -it --rm -p 8080:8080 ghcr.io/tailscale/golink:main -dev-listen :8080

If you receive the docker error unable to open database file: out of memory (14), use a persistent volume as documented in Running in production.

Updating Dependencies

After updating dependencies and making changes to go.mod and go.sum, flake.nix needs to be updated to reflect the new SHA256 of the go dependencies. This can be done by running:

./update-flake.sh

Joining a tailnet

Create an auth key for your tailnet at https://login.tailscale.com/admin/settings/keys. Configure the auth key to your preferences, but at a minimum we generally recommend:

  • add a tag (maybe something like tag:golink) to make it easier to set ACLs for controlling access and to ensure the node doesn't expires.
  • don't set "ephemeral" so the node isn't removed if it goes offline

Once you have a key, set it as the TS_AUTHKEY environment variable when starting golink. You will also need to specify your sqlite database file:

TS_AUTHKEY="tskey-auth-<key>" go run ./cmd/golink -sqlitedb golink.db

golink stores its tailscale data files in a tsnet-golink directory inside os.UserConfigDir. As long as this is on a persistent volume, the auth key only needs to be provided on first run.

MagicDNS

When golink joins your tailnet, it will attempt to use "go" as its node name, and will be available at http://go.tailnet0000.ts.net/ (or whatever your tailnet name is). To make it accessible simply as http://go/, enable MagicDNS for your tailnet. With MagicDNS enabled, no special configuration or browser extensions are needed on client devices. Users just need to have Tailscale installed and connected to the tailnet.

Running in production

golink compiles as a single static binary (including the frontend) and can be deployed and run like any other binary. Two pieces of data should be on persistent volumes:

  • tailscale data files in the tsnet-golink directory inside os.UserConfigDir
  • the sqlite database file where links are stored

In the docker image, both are stored in /home/nonroot, so you can mount a persistent volume:

docker run -v /persistent/data:/home/nonroot ghcr.io/tailscale/golink:main

The mounted directory will need to be writable by the nonroot user (uid: 65532, gid: 65532), for example by calling sudo chown 65532 /persistent/data. Alternatively, you can run golink as root using docker run -u root.

No ports need to be exposed, whether running as a binary or in docker. golink will listen on port 80 on the tailscale interface, so can be accessed at http://go/.

Deploy on Fly

See https://fly.io/docs/ for full instructions for deploying apps on Fly, but this should give you a good start. Replace FLY_APP_NAME and FLY_VOLUME_NAME with your app and volume names.

Create a fly.toml file:

app = "FLY_APP_NAME"

[build]
image = "ghcr.io/tailscale/golink:main"

[deploy]
strategy = "immediate"

[mounts]
source="FLY_VOLUME_NAME"
destination="/home/nonroot"

Then run the commands with the flyctl CLI.

$ flyctl apps create FLY_APP_NAME
$ flyctl volumes create FLY_VOLUME_NAME
$ flyctl secrets set TS_AUTHKEY=tskey-auth-<key>
$ flyctl deploy
Deploy on Modal

See the Modal docs for full instructions on long-lived deployments.

Create a golinks.py file:

import subprocess

import modal

app = modal.App(name="golinks")

vol = modal.Volume.from_name("golinks-data", create_if_missing=True)

image = modal.Image.from_registry(
  "golang:1.23.0-bookworm",
  add_python="3.10",
).run_commands(["go install -v github.com/tailscale/golink/cmd/golink@latest"])

@app.cls(
  image=image,
  secrets=[modal.Secret.from_name("golinks")],
  volumes={"/root/.config": vol},
  keep_warm=1,
  concurrency_limit=1,
)
class Golinks:
  @modal.enter()
  def start_golinks(self):
      subprocess.Popen(
          [
              "golink",
              "-verbose",
              "--sqlitedb",
              "/root/.config/golink.db",
          ]
      )

Then create your secret and deploy with the Modal CLI:

$ modal secret create golinks TS_AUTHKEY=<key>
$ modal deploy golinks.py

Permissions

By default, users own the links they create and only they can update or delete those links. Ownership can be transferred to another user from the link edit page. Links whose owner is no longer part of the tailnet can be edited by any user, at which point that user will become the new owner.

Users can be granted admin access to edit all links using ACL grants in your tailnet policy file. For example, if you have your golink instance tagged with tag:golink and a user group named group:golink-admins, you can grant them admin access using:

{
  "grants": [{
      "src": ["group:golink-admins"],
      "dst": ["tag:golink"],
      "app": {
        "tailscale.com/cap/golink": [{
            "admin": true
        }]
      }
  }]
}

Or if you want to effectively disable the ownership model and allow everyone in your tailnet to edit all links, you could assign the grant to autogroup:member:

{
  "grants": [{
      "src": ["autogroup:member"],
      "dst": ["tag:golink"],
      "app": {
        "tailscale.com/cap/golink": [{
            "admin": true
        }]
      }
  }]
}

Backups

Once you have golink running, you can backup all of your links in JSON lines format from http://go/.export. At Tailscale, we snapshot our links weekly and store them in git.

To restore links, specify the snapshot file on startup. Only links that don't already exist in the database will be added.

golink -snapshot links.json

You can also resolve links locally using a snapshot file:

golink -resolve-from-backup links.json go/link

Firefox configuration

If you're using Firefox, you might want to configure two options to make it easy to load links:

  • to prevent go/ page loads from the address bar being treated as searches, navigate to about:config and add a boolean setting browser.fixup.domainwhitelist.go with a value of true

  • if you use HTTPS-Only Mode, add an exception

HTTPS

When golink joins your tailnet it will check to see if HTTPS is enabled and begin serving HTTPS traffic it detects that it is. When HTTPS is enabled golink will redirect all requests received by the HTTP endpoint first to their internal HTTPS equivalent before redirecting to the external link destination.

NB: If you use curl to interact with the API of a golink instance with HTTPS enabled over its HTTP interface you must specify the -L flag to follow these redirects or else your request will terminate early with an empty response. We recommend the use of the -L flag in all deployments regardless of current HTTPS status to avoid accidental outages should it be enabled in the future.

Documentation

Overview

The golink server runs http://go/, a private shortlink service for tailnets.

Index

Constants

This section is empty.

Variables

View Source
var LastSnapshot []byte

LastSnapshot is the data snapshot (as returned by the /.export handler) that will be loaded on startup.

Functions

func HSTS

func HSTS(h http.Handler) http.Handler

HSTS wraps the provided handler and sets Strict-Transport-Security header on responses. It inspects the Host header to ensure we do not specify HSTS response on non fully qualified domain name origins.

func Run

func Run() error

Types

type ClickStats

type ClickStats map[string]int

ClickStats is the number of clicks a set of links have received in a given time period. It is keyed by link short name, with values of total clicks.

type Link struct {
	Short    string // the "foo" part of http://go/foo
	Long     string // the target URL or text/template pattern to run
	Created  time.Time
	LastEdit time.Time // when the link was last edited
	Owner    string    // user@domain
}

Link is the structure stored for each go short link.

type SQLiteDB

type SQLiteDB struct {
	// contains filtered or unexported fields
}

SQLiteDB stores Links in a SQLite database.

func NewSQLiteDB

func NewSQLiteDB(f string) (*SQLiteDB, error)

NewSQLiteDB returns a new SQLiteDB that stores links in a SQLite database stored at f.

func (*SQLiteDB) Delete

func (s *SQLiteDB) Delete(short string) error

Delete removes a Link using its short name.

func (*SQLiteDB) DeleteStats

func (s *SQLiteDB) DeleteStats(short string) error

DeleteStats deletes click stats for a link.

func (*SQLiteDB) Load

func (s *SQLiteDB) Load(short string) (*Link, error)

Load returns a Link by its short name.

It returns fs.ErrNotExist if the link does not exist.

The caller owns the returned value.

func (*SQLiteDB) LoadAll

func (s *SQLiteDB) LoadAll() ([]*Link, error)

LoadAll returns all stored Links.

The caller owns the returned values.

func (*SQLiteDB) LoadStats

func (s *SQLiteDB) LoadStats() (ClickStats, error)

LoadStats returns click stats for links.

func (*SQLiteDB) Save

func (s *SQLiteDB) Save(link *Link) error

Save saves a Link.

func (*SQLiteDB) SaveStats

func (s *SQLiteDB) SaveStats(stats ClickStats) error

SaveStats records click stats for links. The provided map includes incremental clicks that have occurred since the last time SaveStats was called.

Directories

Path Synopsis
cmd
golink
The golink server runs http://go/, a private shortlink service for tailnets.
The golink server runs http://go/, a private shortlink service for tailnets.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL