{"id":13455146,"url":"https://github.com/tailscale/golink","last_synced_at":"2025-05-14T06:12:11.404Z","repository":{"id":62956103,"uuid":"526276446","full_name":"tailscale/golink","owner":"tailscale","description":"A private shortlink service for tailnets","archived":false,"fork":false,"pushed_at":"2025-05-01T18:29:33.000Z","size":377,"stargazers_count":1403,"open_issues_count":23,"forks_count":95,"subscribers_count":31,"default_branch":"main","last_synced_at":"2025-05-08T00:59:26.163Z","etag":null,"topics":["golinks","tailscale","tsnet"],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"bsd-3-clause","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/tailscale.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2022-08-18T15:50:18.000Z","updated_at":"2025-05-07T06:15:19.000Z","dependencies_parsed_at":"2022-11-09T21:00:28.459Z","dependency_job_id":"cc5b9396-16a7-4e4a-9436-38a497323517","html_url":"https://github.com/tailscale/golink","commit_stats":null,"previous_names":[],"tags_count":1,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tailscale%2Fgolink","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tailscale%2Fgolink/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tailscale%2Fgolink/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tailscale%2Fgolink/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/tailscale","download_url":"https://codeload.github.com/tailscale/golink/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254080217,"owners_count":22011349,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["golinks","tailscale","tsnet"],"created_at":"2024-07-31T08:01:01.751Z","updated_at":"2025-05-14T06:12:11.355Z","avatar_url":"https://github.com/tailscale.png","language":"Go","readme":"# golink\n\n[![status: experimental](https://img.shields.io/badge/status-experimental-blue)](https://tailscale.com/kb/1167/release-stages/#experimental)\n\ngolink is a private shortlink service for your [tailnet].\nIt lets you create short, memorable links for the websites you and your team use most.\nIf you're new to golink, learn more in our [announcement blog post](https://tailscale.com/blog/golink/).\nIf you were looking for a SaaS go link service that doesn't use Tailscale,\nyou might be thinking of [golinks.io](https://golinks.io) or [trot.to](http://trot.to)\n\n[tailnet]: https://tailscale.com/kb/1136/tailnet/\n\n![Screenshot of golink home screen](screenshot.png)\n\n## Building and running\n\nTo build from source and run in dev mode:\n\n    go run ./cmd/golink -dev-listen :8080\n\ngolink will be available at http://localhost:8080/,\nstoring links in a temporary database, and will not attempt to join a tailnet.\n\nThe equivalent using the pre-built docker image:\n\n    docker run -it --rm -p 8080:8080 ghcr.io/tailscale/golink:main -dev-listen :8080\n\nIf you receive the docker error `unable to open database file: out of memory (14)`,\nuse a persistent volume as documented in [Running in production](#running-in-production).\n\n### Updating Dependencies\n\nAfter updating dependencies and making changes to `go.mod` and `go.sum`, `flake.nix` needs\nto be updated to reflect the new SHA256 of the go dependencies. This can be done by running:\n\n```bash\n./update-flake.sh\n```\n\n## Joining a tailnet\n\nCreate an [auth key] for your tailnet at \u003chttps://login.tailscale.com/admin/settings/keys\u003e.\nConfigure the auth key to your preferences, but at a minimum we generally recommend:\n\n - 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.\n - don't set \"ephemeral\" so the node isn't removed if it goes offline\n\nOnce you have a key, set it as the `TS_AUTHKEY` environment variable when starting golink.\nYou will also need to specify your sqlite database file:\n\n    TS_AUTHKEY=\"tskey-auth-\u003ckey\u003e\" go run ./cmd/golink -sqlitedb golink.db\n\ngolink stores its tailscale data files in a `tsnet-golink` directory inside [os.UserConfigDir].\nAs long as this is on a persistent volume, the auth key only needs to be provided on first run.\n\n[auth key]: https://tailscale.com/kb/1085/auth-keys/\n[tag]: https://tailscale.com/kb/1068/acl-tags/\n[os.UserConfigDir]: https://pkg.go.dev/os#UserConfigDir\n\n## MagicDNS\n\nWhen golink joins your tailnet, it will attempt to use \"go\" as its node name,\nand will be available at http://go.tailnet0000.ts.net/ (or whatever your tailnet name is).\nTo make it accessible simply as http://go/, enable [MagicDNS] for your tailnet.\nWith MagicDNS enabled, no special configuration or browser extensions are needed on client devices.\nUsers just need to have Tailscale installed and connected to the tailnet.\n\n[MagicDNS]: https://tailscale.com/kb/1081/magicdns/\n\n## Running in production\n\ngolink compiles as a single static binary (including the frontend) and can be deployed and run like any other binary.\nTwo pieces of data should be on persistent volumes:\n\n - tailscale data files in the `tsnet-golink` directory inside [os.UserConfigDir]\n - the sqlite database file where links are stored\n\nIn the docker image, both are stored in `/home/nonroot`, so you can mount a persistent volume:\n\n    docker run -v /persistent/data:/home/nonroot ghcr.io/tailscale/golink:main\n\nThe mounted directory will need to be writable by the nonroot user (uid: 65532, gid: 65532),\nfor example by calling `sudo chown 65532 /persistent/data`.\nAlternatively, you can run golink as root using `docker run -u root`.\n\nNo ports need to be exposed, whether running as a binary or in docker.\ngolink will listen on port 80 on the tailscale interface, so can be accessed at http://go/.\n\n\u003cdetails\u003e\n  \u003csummary\u003eDeploy on Fly\u003c/summary\u003e\n\n  See \u003chttps://fly.io/docs/\u003e for full instructions for deploying apps on Fly, but this should give you a good start.\n  Replace `FLY_APP_NAME` and `FLY_VOLUME_NAME` with your app and volume names.\n\n  Create a [fly.toml](https://fly.io/docs/reference/configuration/) file:\n\n  ``` toml\napp = \"FLY_APP_NAME\"\n\n[build]\nimage = \"ghcr.io/tailscale/golink:main\"\n\n[deploy]\nstrategy = \"immediate\"\n\n[mounts]\nsource=\"FLY_VOLUME_NAME\"\ndestination=\"/home/nonroot\"\n```\n\n  Then run the commands with the [flyctl CLI].\n\n  ``` sh\n  $ flyctl apps create FLY_APP_NAME\n  $ flyctl volumes create FLY_VOLUME_NAME\n  $ flyctl secrets set TS_AUTHKEY=tskey-auth-\u003ckey\u003e\n  $ flyctl deploy\n  ```\n\n[flyctl CLI]: https://fly.io/docs/hands-on/install-flyctl/\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n  \u003csummary\u003eDeploy on Modal\u003c/summary\u003e\n\n  See the [Modal docs](https://modal.com/docs/guide/managing-deployments) for full instructions on long-lived deployments.\n\n  Create a `golinks.py` file:\n\n  ```python\nimport subprocess\n\nimport modal\n\napp = modal.App(name=\"golinks\")\n\nvol = modal.Volume.from_name(\"golinks-data\", create_if_missing=True)\n\nimage = modal.Image.from_registry(\n    \"golang:1.23.0-bookworm\",\n    add_python=\"3.10\",\n).run_commands([\"go install -v github.com/tailscale/golink/cmd/golink@latest\"])\n\n@app.cls(\n    image=image,\n    secrets=[modal.Secret.from_name(\"golinks\")],\n    volumes={\"/root/.config\": vol},\n    keep_warm=1,\n    concurrency_limit=1,\n)\nclass Golinks:\n    @modal.enter()\n    def start_golinks(self):\n        subprocess.Popen(\n            [\n                \"golink\",\n                \"-verbose\",\n                \"--sqlitedb\",\n                \"/root/.config/golink.db\",\n            ]\n        )\n```\n\n  Then create your secret and deploy with the [Modal CLI](https://github.com/modal-labs/modal-client):\n\n  ```sh\n$ modal secret create golinks TS_AUTHKEY=\u003ckey\u003e\n$ modal deploy golinks.py\n  ```\n\n\u003c/details\u003e\n\n## Permissions\n\nBy default, users own the links they create and only they can update or delete those links.\nOwnership can be transferred to another user from the link edit page.\nLinks whose owner is no longer part of the tailnet can be edited by any user,\nat which point that user will become the new owner.\n\nUsers can be granted admin access to edit all links using [ACL grants] in your tailnet policy file.\nFor example, if you have your golink instance tagged with `tag:golink` and a user group named `group:golink-admins`,\nyou can grant them admin access using:\n\n```json\n{\n  \"grants\": [{\n      \"src\": [\"group:golink-admins\"],\n      \"dst\": [\"tag:golink\"],\n      \"app\": {\n        \"tailscale.com/cap/golink\": [{\n            \"admin\": true\n        }]\n      }\n  }]\n}\n```\n\nOr if you want to effectively disable the ownership model and allow everyone in your tailnet to edit all links,\nyou could assign the grant to `autogroup:member`:\n\n```json\n{\n  \"grants\": [{\n      \"src\": [\"autogroup:member\"],\n      \"dst\": [\"tag:golink\"],\n      \"app\": {\n        \"tailscale.com/cap/golink\": [{\n            \"admin\": true\n        }]\n      }\n  }]\n}\n```\n\n[ACL grants]: https://tailscale.com/kb/1324/acl-grants\n\n## Backups\n\nOnce you have golink running, you can backup all of your links in [JSON lines] format from \u003chttp://go/.export\u003e.\nAt Tailscale, we snapshot our links weekly and store them in git.\n\nTo restore links, specify the snapshot file on startup.\nOnly links that don't already exist in the database will be added.\n\n    golink -snapshot links.json\n\n[JSON lines]: https://jsonlines.org/\n\nYou can also resolve links locally using a snapshot file:\n\n    golink -resolve-from-backup links.json go/link\n\n## Firefox configuration\n\nIf you're using Firefox, you might want to configure two options to make it easy to load links:\n\n  * to prevent `go/` page loads from the address bar being treated as searches,\n    navigate to `about:config` and add a boolean setting `browser.fixup.domainwhitelist.go`\n    with a value of _true_\n\n  * if you use HTTPS-Only Mode, [add an exception](https://support.mozilla.org/en-US/kb/https-only-prefs#w_add-exceptions-for-http-websites-when-youre-in-https-only-mode)\n\n## HTTPS\n\nWhen golink joins your tailnet it will check to see if HTTPS is enabled and\nbegin serving HTTPS traffic it detects that it is. When HTTPS is enabled golink\nwill redirect all requests received by the HTTP endpoint first to their internal\nHTTPS equivalent before redirecting to the external link destination.\n\n**NB:** If you use `curl` to interact with the API of a golink instance with HTTPS\nenabled over its HTTP interface you _must_ specify the `-L` flag to follow these\nredirects or else your request will terminate early with an empty response. We\nrecommend the use of the `-L` flag in all deployments regardless of current\nHTTPS status to avoid accidental outages should it be enabled in the future.\n","funding_links":[],"categories":["Go","CSS"],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftailscale%2Fgolink","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftailscale%2Fgolink","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftailscale%2Fgolink/lists"}