From 8c7d8ee34f3f0cdca5f36983eea38f68cdd0abc0 Mon Sep 17 00:00:00 2001 From: nblock Date: Thu, 10 Oct 2024 15:24:04 +0200 Subject: [PATCH] Restructure headscale documentation (#2163) * Setup mkdocs-redirects * Restructure existing documentation * Move client OS support into the documentation * Move existing Client OS support table into its own documentation page * Link from README.md to the rendered documentation * Document minimum Tailscale client version * Reuse CONTRIBUTING.md" in the documentation * Include "CONTRIBUTING.md" from the repository root * Update FAQ and index page and link to the contributing docs * Add configuration reference * Add a getting started page and explain the first steps with headscale * Use the existing "Using headscale" sections and combine them into a single getting started guide with a little bit more explanation. * Explain how to get help from the command line client. * Remove duplicated sections from existing installation guides * Document requirements and assumptions * Document packages provided by the community * Move deb install guide to official releases * Move manual install guide to official releases * Move container documentation to setup section * Move sealos documentation to cloud install page * Move OpenBSD docs to build from source * Simplify DNS documentation * Add sponsor page * Add releases page * Add features page * Add help page * Add upgrading page * Adjust mkdocs nav * Update wording Use the term headscale for the project, Headscale on the beginning of a sentence and `headscale` when refering to the CLI. * Welcome to headscale * Link to existing documentation in the FAQ * Remove the goal header and use the text as opener * Indent code block in OIDC * Make a few pages linter compatible Also update ignored files for prettier * Recommend HTTPS on port 443 Fixes: #2164 * Use hosts in acl documentation thx @efficacy38 for noticing this Ref: #1863 * Use mkdocs-macros to set headscale version once --- .prettierignore | 6 +- README.md | 26 +-- config-example.yaml | 4 +- docs/about/clients.md | 15 ++ docs/about/contributing.md | 3 + docs/{ => about}/faq.md | 31 ++- docs/about/features.md | 31 +++ docs/about/help.md | 11 + docs/about/releases.md | 10 + docs/about/sponsor.md | 4 + docs/dns-records.md | 92 -------- docs/images/headscale-sealos-grpc-url.png | Bin 35911 -> 0 bytes docs/images/headscale-sealos-url.png | Bin 36024 -> 0 bytes docs/index.md | 13 +- docs/{ => ref}/acls.md | 6 +- docs/ref/configuration.md | 39 ++++ docs/ref/dns.md | 80 +++++++ docs/{ => ref}/exit-node.md | 0 docs/{ => ref/integration}/reverse-proxy.md | 6 +- docs/{ => ref/integration}/web-ui.md | 6 +- docs/{ => ref}/oidc.md | 27 ++- docs/ref/remote-cli.md | 98 +++++++++ docs/{ => ref}/tls.md | 2 +- docs/remote-cli.md | 100 --------- docs/requirements.txt | 3 + docs/running-headscale-linux-manual.md | 163 -------------- docs/running-headscale-linux.md | 97 --------- docs/running-headscale-openbsd.md | 202 ------------------ docs/running-headscale-sealos.md | 136 ------------ docs/setup/install/cloud.md | 25 +++ docs/setup/install/community.md | 55 +++++ .../install/container.md} | 81 ++++--- docs/setup/install/official.md | 117 ++++++++++ docs/setup/install/source.md | 63 ++++++ docs/setup/requirements.md | 28 +++ docs/setup/upgrade.md | 10 + .../connect/android.md} | 4 +- .../connect/apple.md} | 4 +- .../connect/windows.md} | 6 +- docs/usage/getting-started.md | 132 ++++++++++++ mkdocs.yml | 73 +++++-- 41 files changed, 867 insertions(+), 942 deletions(-) create mode 100644 docs/about/clients.md create mode 100644 docs/about/contributing.md rename docs/{ => about}/faq.md (51%) create mode 100644 docs/about/features.md create mode 100644 docs/about/help.md create mode 100644 docs/about/releases.md create mode 100644 docs/about/sponsor.md delete mode 100644 docs/dns-records.md delete mode 100644 docs/images/headscale-sealos-grpc-url.png delete mode 100644 docs/images/headscale-sealos-url.png rename docs/{ => ref}/acls.md (98%) create mode 100644 docs/ref/configuration.md create mode 100644 docs/ref/dns.md rename docs/{ => ref}/exit-node.md (100%) rename docs/{ => ref/integration}/reverse-proxy.md (96%) rename docs/{ => ref/integration}/web-ui.md (88%) rename docs/{ => ref}/oidc.md (92%) create mode 100644 docs/ref/remote-cli.md rename docs/{ => ref}/tls.md (98%) delete mode 100644 docs/remote-cli.md delete mode 100644 docs/running-headscale-linux-manual.md delete mode 100644 docs/running-headscale-linux.md delete mode 100644 docs/running-headscale-openbsd.md delete mode 100644 docs/running-headscale-sealos.md create mode 100644 docs/setup/install/cloud.md create mode 100644 docs/setup/install/community.md rename docs/{running-headscale-container.md => setup/install/container.md} (64%) create mode 100644 docs/setup/install/official.md create mode 100644 docs/setup/install/source.md create mode 100644 docs/setup/requirements.md create mode 100644 docs/setup/upgrade.md rename docs/{android-client.md => usage/connect/android.md} (96%) rename docs/{apple-client.md => usage/connect/apple.md} (98%) rename docs/{windows-client.md => usage/connect/windows.md} (95%) create mode 100644 docs/usage/getting-started.md diff --git a/.prettierignore b/.prettierignore index d455d02cc1..4b873f4915 100644 --- a/.prettierignore +++ b/.prettierignore @@ -1,6 +1,2 @@ .github/workflows/test-integration-v2* -docs/dns-records.md -docs/running-headscale-container.md -docs/running-headscale-linux-manual.md -docs/running-headscale-linux.md -docs/running-headscale-openbsd.md +docs/about/features.md diff --git a/README.md b/README.md index ff44e8e466..2994bd2dd3 100644 --- a/README.md +++ b/README.md @@ -46,31 +46,11 @@ buttons available in the repo. ## Features -- Full "base" support of Tailscale's features -- Configurable DNS - - [Split DNS](https://tailscale.com/kb/1054/dns/#using-dns-settings-in-the-admin-console) -- Node registration - - Single-Sign-On (via Open ID Connect) - - Pre authenticated key -- Taildrop (File Sharing) -- [Access control lists](https://tailscale.com/kb/1018/acls/) -- [MagicDNS](https://tailscale.com/kb/1081/magicdns) -- Dual stack (IPv4 and IPv6) -- Routing advertising (including exit nodes) -- Ephemeral nodes -- Embedded [DERP server](https://tailscale.com/blog/how-tailscale-works/#encrypted-tcp-relays-derp) +Please see ["Features" in the documentation](https://headscale.net/about/features/). ## Client OS support -| OS | Supports headscale | -| ------- | -------------------------------------------------------------------------------------------------- | -| Linux | Yes | -| OpenBSD | Yes | -| FreeBSD | Yes | -| Windows | Yes (see [docs](./docs/windows-client.md) and `/windows` on your headscale for more information) | -| Android | Yes (see [docs](./docs/android-client.md)) | -| macOS | Yes (see [docs](./docs/apple-client.md#macos) and `/apple` on your headscale for more information) | -| iOS | Yes (see [docs](./docs/apple-client.md#ios) and `/apple` on your headscale for more information) | +Please see ["Client and operating system support" in the documentation](https://headscale.net/about/clients/). ## Running headscale @@ -99,7 +79,7 @@ Please read the [CONTRIBUTING.md](./CONTRIBUTING.md) file. ### Requirements To contribute to headscale you would need the latest version of [Go](https://golang.org) -and [Buf](https://buf.build)(Protobuf generator). +and [Buf](https://buf.build) (Protobuf generator). We recommend using [Nix](https://nixos.org/) to setup a development environment. This can be done with `nix develop`, which will install the tools and give you a shell. diff --git a/config-example.yaml b/config-example.yaml index 5b757bc959..2632555d2b 100644 --- a/config-example.yaml +++ b/config-example.yaml @@ -209,7 +209,7 @@ tls_letsencrypt_cache_dir: /var/lib/headscale/cache # Type of ACME challenge to use, currently supported types: # HTTP-01 or TLS-ALPN-01 -# See [docs/tls.md](docs/tls.md) for more information +# See: docs/ref/tls.md for more information tls_letsencrypt_challenge_type: HTTP-01 # When HTTP-01 challenge is chosen, letsencrypt must set up a # verification endpoint, and it will be listening on: @@ -297,7 +297,7 @@ dns: # Extra DNS records # so far only A-records are supported (on the tailscale side) - # See https://github.com/juanfont/headscale/blob/main/docs/dns-records.md#Limitations + # See: docs/ref/dns.md extra_records: [] # - name: "grafana.myvpn.example.com" # type: "A" diff --git a/docs/about/clients.md b/docs/about/clients.md new file mode 100644 index 0000000000..eafb2946be --- /dev/null +++ b/docs/about/clients.md @@ -0,0 +1,15 @@ +# Client and operating system support + +We aim to support the [**last 10 releases** of the Tailscale client](https://tailscale.com/changelog#client) on all +provided operating systems and platforms. Some platforms might require additional configuration to connect with +headscale. + +| OS | Supports headscale | +| ------- | ----------------------------------------------------------------------------------------------------- | +| Linux | Yes | +| OpenBSD | Yes | +| FreeBSD | Yes | +| Windows | Yes (see [docs](../usage/connect/windows.md) and `/windows` on your headscale for more information) | +| Android | Yes (see [docs](../usage/connect/android.md)) | +| macOS | Yes (see [docs](../usage/connect/apple.md#macos) and `/apple` on your headscale for more information) | +| iOS | Yes (see [docs](../usage/connect/apple.md#ios) and `/apple` on your headscale for more information) | diff --git a/docs/about/contributing.md b/docs/about/contributing.md new file mode 100644 index 0000000000..4eeeef13ce --- /dev/null +++ b/docs/about/contributing.md @@ -0,0 +1,3 @@ +{% + include-markdown "../../CONTRIBUTING.md" +%} diff --git a/docs/faq.md b/docs/about/faq.md similarity index 51% rename from docs/faq.md rename to docs/about/faq.md index 2a45996737..139e0117ff 100644 --- a/docs/faq.md +++ b/docs/about/faq.md @@ -1,15 +1,10 @@ ---- -hide: - - navigation ---- - # Frequently Asked Questions ## What is the design goal of headscale? -`headscale` aims to implement a self-hosted, open source alternative to the [Tailscale](https://tailscale.com/) +Headscale aims to implement a self-hosted, open source alternative to the [Tailscale](https://tailscale.com/) control server. -`headscale`'s goal is to provide self-hosters and hobbyists with an open-source +Headscale's goal is to provide self-hosters and hobbyists with an open-source server they can use for their projects and labs. It implements a narrow scope, a _single_ Tailnet, suitable for a personal use, or a small open-source organisation. @@ -19,9 +14,7 @@ open-source organisation. Headscale is "Open Source, acknowledged contribution", this means that any contribution will have to be discussed with the Maintainers before being submitted. -Headscale is open to code contributions for bug fixes without discussion. - -If you find mistakes in the documentation, please also submit a fix to the documentation. +Please see [Contributing](contributing.md) for more information. ## Why is 'acknowledged contribution' the chosen model? @@ -39,18 +32,22 @@ Please be aware that there are a number of reasons why we might not accept speci - Given that we are reverse-engineering Tailscale to satisfy our own curiosity, we might be interested in implementing the feature ourselves. - You are not sending unit and integration tests with it. -## Do you support Y method of deploying Headscale? +## Do you support Y method of deploying headscale? -We currently support deploying `headscale` using our binaries and the DEB packages. Both can be found in the -[GitHub releases page](https://github.com/juanfont/headscale/releases). +We currently support deploying headscale using our binaries and the DEB packages. Visit our [installation guide using +official releases](../setup/install/official.md) for more information. -In addition to that, there are semi-official RPM packages by the Fedora infra team https://copr.fedorainfracloud.org/coprs/jonathanspw/headscale/ +In addition to that, you may use packages provided by the community or from distributions. Learn more in the +[installation guide using community packages](../setup/install/community.md). -For convenience, we also build Docker images with `headscale`. But **please be aware that we don't officially support deploying `headscale` using Docker**. We have a [Discord channel](https://discord.com/channels/896711691637780480/1070619770942148618) where you can ask for Docker-specific help to the community. +For convenience, we also [build Docker images with headscale](../setup/install/container.md). But **please be aware that +we don't officially support deploying headscale using Docker**. We have a [Discord +channel](https://discord.com/channels/896711691637780480/1070619770942148618) where you can ask for Docker-specific help +to the community. -## Why is my reverse proxy not working with Headscale? +## Why is my reverse proxy not working with headscale? -We don't know. We don't use reverse proxies with `headscale` ourselves, so we don't have any experience with them. We have [community documentation](https://headscale.net/reverse-proxy/) on how to configure various reverse proxies, and a dedicated [Discord channel](https://discord.com/channels/896711691637780480/1070619818346164324) where you can ask for help to the community. +We don't know. We don't use reverse proxies with headscale ourselves, so we don't have any experience with them. We have [community documentation](../ref/integration/reverse-proxy.md) on how to configure various reverse proxies, and a dedicated [Discord channel](https://discord.com/channels/896711691637780480/1070619818346164324) where you can ask for help to the community. ## Can I use headscale and tailscale on the same machine? diff --git a/docs/about/features.md b/docs/about/features.md new file mode 100644 index 0000000000..80e94874c1 --- /dev/null +++ b/docs/about/features.md @@ -0,0 +1,31 @@ +# Features + +Headscale aims to implement a self-hosted, open source alternative to the Tailscale control server. Headscale's goal is +to provide self-hosters and hobbyists with an open-source server they can use for their projects and labs. This page +provides on overview of headscale's feature and compatibility with the Tailscale control server: + +- [x] Full "base" support of Tailscale's features +- [x] Node registration + - [x] Interactive + - [x] Pre authenticated key +- [x] [DNS](https://tailscale.com/kb/1054/dns) + - [x] [MagicDNS](https://tailscale.com/kb/1081/magicdns) + - [x] [Global and restricted nameservers (split DNS)](https://tailscale.com/kb/1054/dns#nameservers) + - [x] [search domains](https://tailscale.com/kb/1054/dns#search-domains) + - [x] [Extra DNS records (headscale only)](../ref/dns.md#setting-custom-dns-records) +- [x] [Taildrop (File Sharing)](https://tailscale.com/kb/1106/taildrop) +- [x] Routing advertising (including exit nodes) +- [x] Dual stack (IPv4 and IPv6) +- [x] Ephemeral nodes +- [x] Embedded [DERP server](https://tailscale.com/kb/1232/derp-servers) +- [x] Access control lists ([GitHub label "policy"](https://github.com/juanfont/headscale/labels/policy%20%F0%9F%93%9D)) + - [x] ACL management via API + - [x] `autogroup:internet` + - [ ] `autogroup:self` + - [ ] `autogroup:member` +* [ ] Node registration using Single-Sign-On (OpenID Connect) ([GitHub label "OIDC"](https://github.com/juanfont/headscale/labels/OIDC)) + - [x] Basic registration + - [ ] Dynamic ACL support + - [ ] OIDC groups cannot be used in ACLs +- [ ] [Funnel](https://tailscale.com/kb/1223/funnel) ([#1040](https://github.com/juanfont/headscale/issues/1040)) +- [ ] [Serve](https://tailscale.com/kb/1312/serve) ([#1234](https://github.com/juanfont/headscale/issues/1921)) diff --git a/docs/about/help.md b/docs/about/help.md new file mode 100644 index 0000000000..71f47071ee --- /dev/null +++ b/docs/about/help.md @@ -0,0 +1,11 @@ +# Getting help + +Join our Discord server for announcements and community support: + +- [announcements](https://discord.com/channels/896711691637780480/896711692120129538) +- [general](https://discord.com/channels/896711691637780480/896711692120129540) +- [docker-issues](https://discord.com/channels/896711691637780480/1070619770942148618) +- [reverse-proxy-issues](https://discord.com/channels/896711691637780480/1070619818346164324) +- [web-interfaces](https://discord.com/channels/896711691637780480/1105842846386356294) + +Please report bugs via [GitHub issues](https://github.com/juanfont/headscale/issues) diff --git a/docs/about/releases.md b/docs/about/releases.md new file mode 100644 index 0000000000..718c0f5398 --- /dev/null +++ b/docs/about/releases.md @@ -0,0 +1,10 @@ +# Releases + +All headscale releases are available on the [GitHub release page](https://github.com/juanfont/headscale/releases). Those +releases are available as binaries for various platforms and architectures, packages for Debian based systems and source +code archives. Container images are available on [Docker Hub](https://hub.docker.com/r/headscale/headscale). + +An Atom/RSS feed of headscale releases is available [here](https://github.com/juanfont/headscale/releases.atom). + +Join the ["announcements" channel on Discord](https://discord.com/channels/896711691637780480/896711692120129538) for +news about headscale. diff --git a/docs/about/sponsor.md b/docs/about/sponsor.md new file mode 100644 index 0000000000..3fdb8e4b59 --- /dev/null +++ b/docs/about/sponsor.md @@ -0,0 +1,4 @@ +# Sponsor + +If you like to support the development of headscale, please consider a donation via +[ko-fi.com/headscale](https://ko-fi.com/headscale). Thank you! diff --git a/docs/dns-records.md b/docs/dns-records.md deleted file mode 100644 index 6c8fc42a9b..0000000000 --- a/docs/dns-records.md +++ /dev/null @@ -1,92 +0,0 @@ -# Setting custom DNS records - -!!! warning "Community documentation" - - This page is not actively maintained by the headscale authors and is - written by community members. It is _not_ verified by `headscale` developers. - - **It might be outdated and it might miss necessary steps**. - -## Goal - -This documentation has the goal of showing how a user can set custom DNS records with `headscale`s magic dns. -An example use case is to serve apps on the same host via a reverse proxy like NGINX, in this case a Prometheus monitoring stack. This allows to nicely access the service with "http://grafana.myvpn.example.com" instead of the hostname and portnum combination "http://hostname-in-magic-dns.myvpn.example.com:3000". - -## Setup - -### 1. Change the configuration - -1. Change the `config.yaml` to contain the desired records like so: - - ```yaml - dns: - ... - extra_records: - - name: "prometheus.myvpn.example.com" - type: "A" - value: "100.64.0.3" - - - name: "grafana.myvpn.example.com" - type: "A" - value: "100.64.0.3" - ... - ``` - -1. Restart your headscale instance. - - !!! warning - - Beware of the limitations listed later on! - -### 2. Verify that the records are set - -You can use a DNS querying tool of your choice on one of your hosts to verify that your newly set records are actually available in MagicDNS, here we used [`dig`](https://man.archlinux.org/man/dig.1.en): - -``` -$ dig grafana.myvpn.example.com - -; <<>> DiG 9.18.10 <<>> grafana.myvpn.example.com -;; global options: +cmd -;; Got answer: -;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 44054 -;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1 - -;; OPT PSEUDOSECTION: -; EDNS: version: 0, flags:; udp: 65494 -;; QUESTION SECTION: -;grafana.myvpn.example.com. IN A - -;; ANSWER SECTION: -grafana.myvpn.example.com. 593 IN A 100.64.0.3 - -;; Query time: 0 msec -;; SERVER: 127.0.0.53#53(127.0.0.53) (UDP) -;; WHEN: Sat Dec 31 11:46:55 CET 2022 -;; MSG SIZE rcvd: 66 -``` - -### 3. Optional: Setup the reverse proxy - -The motivating example here was to be able to access internal monitoring services on the same host without specifying a port: - -``` -server { - listen 80; - listen [::]:80; - - server_name grafana.myvpn.example.com; - - location / { - proxy_pass http://localhost:3000; - proxy_set_header Host $http_host; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; - } - -} -``` - -## Limitations - -[Not all types of records are supported](https://github.com/tailscale/tailscale/blob/6edf357b96b28ee1be659a70232c0135b2ffedfd/ipn/ipnlocal/local.go#L2989-L3007), especially no CNAME records. diff --git a/docs/images/headscale-sealos-grpc-url.png b/docs/images/headscale-sealos-grpc-url.png deleted file mode 100644 index 1b0df4f3fd2cfd085830c56c90bb70de02fdc648..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 35911 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

iut0)R?9c`ieZP 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 RNGVZJ-peSzt&jo*{VyECz<2-v diff --git a/docs/images/headscale-sealos-url.png b/docs/images/headscale-sealos-url.png deleted file mode 100644 index 66233698b73cffd0458a40c1c66a98656fb97e34..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 36024 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 U3^H2&avIA^t4LM9Hx2oJ0A&Ins{jB1 diff --git a/docs/index.md b/docs/index.md index f1b6e1b1af..36c87a003d 100644 --- a/docs/index.md +++ b/docs/index.md @@ -4,11 +4,11 @@ hide: - toc --- -# headscale +# Welcome to headscale -`headscale` is an open source, self-hosted implementation of the Tailscale control server. +Headscale is an open source, self-hosted implementation of the Tailscale control server. -This page contains the documentation for the latest version of headscale. Please also check our [FAQ](faq.md). +This page contains the documentation for the latest version of headscale. Please also check our [FAQ](./about/faq.md). Join our [Discord](https://discord.gg/c84AZQhmpx) server for a chat and community support. @@ -23,16 +23,15 @@ open-source organisation. ## Supporting headscale -If you like `headscale` and find it useful, there is a sponsorship and donation -buttons available in the repo. +Please see [Sponsor](about/sponsor.md) for more information. ## Contributing Headscale is "Open Source, acknowledged contribution", this means that any contribution will have to be discussed with the Maintainers before being submitted. -Please see [CONTRIBUTING.md](https://github.com/juanfont/headscale/blob/main/CONTRIBUTING.md) for more information. +Please see [Contributing](about/contributing.md) for more information. ## About -`headscale` is maintained by [Kristoffer Dalby](https://kradalby.no/) and [Juan Font](https://font.eu). +Headscale is maintained by [Kristoffer Dalby](https://kradalby.no/) and [Juan Font](https://font.eu). diff --git a/docs/acls.md b/docs/ref/acls.md similarity index 98% rename from docs/acls.md rename to docs/ref/acls.md index 4ab8fb469d..a621da5dd9 100644 --- a/docs/acls.md +++ b/docs/ref/acls.md @@ -36,12 +36,12 @@ servers. - billing.internal - router.internal -![ACL implementation example](images/headscale-acl-network.png) +![ACL implementation example](../images/headscale-acl-network.png) ## ACL setup Note: Users will be created automatically when users authenticate with the -Headscale server. +headscale server. ACLs have to be written in [huJSON](https://github.com/tailscale/hujson). @@ -87,7 +87,7 @@ Here are the ACL's to implement the same permissions as above: // to define a single host, use a /32 mask. You cannot use DNS entries here, // as they're prone to be hijacked by replacing their IP addresses. // see https://github.com/tailscale/tailscale/issues/3800 for more information. - "Hosts": { + "hosts": { "postgresql.internal": "10.20.0.2/32", "webservers.internal": "10.20.10.1/29" }, diff --git a/docs/ref/configuration.md b/docs/ref/configuration.md new file mode 100644 index 0000000000..239d9cb603 --- /dev/null +++ b/docs/ref/configuration.md @@ -0,0 +1,39 @@ +# Configuration + +- Headscale loads its configuration from a YAML file +- It searches for `config.yaml` in the following paths: + - `/etc/headscale` + - `$HOME/.headscale` + - the current working directory +- Use the command line flag `-c`, `--config` to load the configuration from a different path +- Validate the configuration file with: `headscale configtest` + +!!! example "Get the [example configuration from the GitHub repository](https://github.com/juanfont/headscale/blob/main/config-example.yaml)" + + Always select the [same GitHub tag](https://github.com/juanfont/headscale/tags) as the released version you use to + ensure you have the correct example configuration. The `main` branch might contain unreleased changes. + + === "View on GitHub" + + * Development version: + * Version {{ headscale.version }}: + + === "Download with `wget`" + + ```shell + # Development version + wget -O config.yaml https://raw.githubusercontent.com/juanfont/headscale/main/config-example.yaml + + # Version {{ headscale.version }} + wget -O config.yaml https://raw.githubusercontent.com/juanfont/headscale/v{{ headscale.version }}/config-example.yaml + ``` + + === "Download with `curl`" + + ```shell + # Development version + curl -o config.yaml https://raw.githubusercontent.com/juanfont/headscale/main/config-example.yaml + + # Version {{ headscale.version }} + curl -o config.yaml https://raw.githubusercontent.com/juanfont/headscale/v{{ headscale.version }}/config-example.yaml + ``` diff --git a/docs/ref/dns.md b/docs/ref/dns.md new file mode 100644 index 0000000000..1e3ad8977a --- /dev/null +++ b/docs/ref/dns.md @@ -0,0 +1,80 @@ +# DNS + +Headscale supports [most DNS features](../about/features.md) from Tailscale and DNS releated settings can be configured +in the [configuration file](./configuration.md) within the `dns` section. + +## Setting custom DNS records + +!!! warning "Community documentation" + + This page is not actively maintained by the headscale authors and is + written by community members. It is _not_ verified by headscale developers. + + **It might be outdated and it might miss necessary steps**. + +Headscale allows to set custom DNS records which are made available via +[MagicDNS](https://tailscale.com/kb/1081/magicdns). An example use case is to serve multiple apps on the same host via a +reverse proxy like NGINX, in this case a Prometheus monitoring stack. This allows to nicely access the service with +"http://grafana.myvpn.example.com" instead of the hostname and port combination +"http://hostname-in-magic-dns.myvpn.example.com:3000". + +!!! warning "Limitations" + + [Not all types of records are supported](https://github.com/tailscale/tailscale/blob/6edf357b96b28ee1be659a70232c0135b2ffedfd/ipn/ipnlocal/local.go#L2989-L3007), especially no CNAME records. + +1. Update the [configuration file](./configuration.md) to contain the desired records like so: + + ```yaml + dns: + ... + extra_records: + - name: "prometheus.myvpn.example.com" + type: "A" + value: "100.64.0.3" + + - name: "grafana.myvpn.example.com" + type: "A" + value: "100.64.0.3" + ... + ``` + +1. Restart your headscale instance. + +1. Verify that DNS records are properly set using the DNS querying tool of your choice: + + === "Query with dig" + + ```shell + dig +short grafana.myvpn.example.com + 100.64.0.3 + ``` + + === "Query with drill" + + ```shell + drill -Q grafana.myvpn.example.com + 100.64.0.3 + ``` + +1. Optional: Setup the reverse proxy + + The motivating example here was to be able to access internal monitoring services on the same host without + specifying a port, depicted as NGINX configuration snippet: + + ``` + server { + listen 80; + listen [::]:80; + + server_name grafana.myvpn.example.com; + + location / { + proxy_pass http://localhost:3000; + proxy_set_header Host $http_host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } + + } + ``` diff --git a/docs/exit-node.md b/docs/ref/exit-node.md similarity index 100% rename from docs/exit-node.md rename to docs/ref/exit-node.md diff --git a/docs/reverse-proxy.md b/docs/ref/integration/reverse-proxy.md similarity index 96% rename from docs/reverse-proxy.md rename to docs/ref/integration/reverse-proxy.md index b042b348da..a50e894a10 100644 --- a/docs/reverse-proxy.md +++ b/docs/ref/integration/reverse-proxy.md @@ -3,7 +3,7 @@ !!! warning "Community documentation" This page is not actively maintained by the headscale authors and is - written by community members. It is _not_ verified by `headscale` developers. + written by community members. It is _not_ verified by headscale developers. **It might be outdated and it might miss necessary steps**. @@ -121,11 +121,11 @@ The following Caddyfile is all that is necessary to use Caddy as a reverse proxy Caddy v2 will [automatically](https://caddyserver.com/docs/automatic-https) provision a certificate for your domain/subdomain, force HTTPS, and proxy websockets - no further configuration is necessary. -For a slightly more complex configuration which utilizes Docker containers to manage Caddy, Headscale, and Headscale-UI, [Guru Computing's guide](https://blog.gurucomputing.com.au/smart-vpns-with-headscale/) is an excellent reference. +For a slightly more complex configuration which utilizes Docker containers to manage Caddy, headscale, and Headscale-UI, [Guru Computing's guide](https://blog.gurucomputing.com.au/smart-vpns-with-headscale/) is an excellent reference. ## Apache -The following minimal Apache config will proxy traffic to the Headscale instance on ``. Note that `upgrade=any` is required as a parameter for `ProxyPass` so that WebSockets traffic whose `Upgrade` header value is not equal to `WebSocket` (i. e. Tailscale Control Protocol) is forwarded correctly. See the [Apache docs](https://httpd.apache.org/docs/2.4/mod/mod_proxy_wstunnel.html) for more information on this. +The following minimal Apache config will proxy traffic to the headscale instance on ``. Note that `upgrade=any` is required as a parameter for `ProxyPass` so that WebSockets traffic whose `Upgrade` header value is not equal to `WebSocket` (i. e. Tailscale Control Protocol) is forwarded correctly. See the [Apache docs](https://httpd.apache.org/docs/2.4/mod/mod_proxy_wstunnel.html) for more information on this. ``` diff --git a/docs/web-ui.md b/docs/ref/integration/web-ui.md similarity index 88% rename from docs/web-ui.md rename to docs/ref/integration/web-ui.md index 57631845c2..2425d8b777 100644 --- a/docs/web-ui.md +++ b/docs/ref/integration/web-ui.md @@ -3,14 +3,14 @@ !!! warning "Community contributions" This page contains community contributions. The projects listed here are not - maintained by the Headscale authors and are written by community members. + maintained by the headscale authors and are written by community members. | Name | Repository Link | Description | Status | | --------------- | ------------------------------------------------------- | ----------------------------------------------------------------------------------- | ------ | -| headscale-webui | [Github](https://github.com/ifargle/headscale-webui) | A simple Headscale web UI for small-scale deployments. | Alpha | +| headscale-webui | [Github](https://github.com/ifargle/headscale-webui) | A simple headscale web UI for small-scale deployments. | Alpha | | headscale-ui | [Github](https://github.com/gurucomputing/headscale-ui) | A web frontend for the headscale Tailscale-compatible coordination server | Alpha | | HeadscaleUi | [GitHub](https://github.com/simcu/headscale-ui) | A static headscale admin ui, no backend enviroment required | Alpha | -| headscale-admin | [Github](https://github.com/GoodiesHQ/headscale-admin) | Headscale-Admin is meant to be a simple, modern web interface for Headscale | Beta | +| headscale-admin | [Github](https://github.com/GoodiesHQ/headscale-admin) | Headscale-Admin is meant to be a simple, modern web interface for headscale | Beta | | ouroboros | [Github](https://github.com/yellowsink/ouroboros) | Ouroboros is designed for users to manage their own devices, rather than for admins | Stable | You can ask for support on our dedicated [Discord channel](https://discord.com/channels/896711691637780480/1105842846386356294). diff --git a/docs/oidc.md b/docs/ref/oidc.md similarity index 92% rename from docs/oidc.md rename to docs/ref/oidc.md index c8746bbc6b..734184df83 100644 --- a/docs/oidc.md +++ b/docs/ref/oidc.md @@ -1,4 +1,4 @@ -# Configuring Headscale to use OIDC authentication +# Configuring headscale to use OIDC authentication In order to authenticate users through a centralized solution one must enable the OIDC integration. @@ -54,7 +54,7 @@ oidc: ## Azure AD example -In order to integrate Headscale with Azure Active Directory, we'll need to provision an App Registration with the correct scopes and redirect URI. Here with Terraform: +In order to integrate headscale with Azure Active Directory, we'll need to provision an App Registration with the correct scopes and redirect URI. Here with Terraform: ```hcl resource "azuread_application" "headscale" { @@ -84,7 +84,7 @@ resource "azuread_application" "headscale" { } } web { - # Points at your running Headscale instance + # Points at your running headscale instance redirect_uris = ["https://headscale.example.com/oidc/callback"] implicit_grant { @@ -125,7 +125,7 @@ output "headscale_client_secret" { } ``` -And in your Headscale `config.yaml`: +And in your headscale `config.yaml`: ```yaml oidc: @@ -144,7 +144,7 @@ oidc: ## Google OAuth Example -In order to integrate Headscale with Google, you'll need to have a [Google Cloud Console](https://console.cloud.google.com) account. +In order to integrate headscale with Google, you'll need to have a [Google Cloud Console](https://console.cloud.google.com) account. Google OAuth has a [verification process](https://support.google.com/cloud/answer/9110914?hl=en) if you need to have users authenticate who are outside of your domain. If you only need to authenticate users from your domain name (ie `@example.com`), you don't need to go through the verification process. @@ -158,17 +158,16 @@ However if you don't have a domain, or need to add users outside of your domain, 4. Click `Create Credentials` -> `OAuth client ID` 5. Under `Application Type`, choose `Web Application` 6. For `Name`, enter whatever you like -7. Under `Authorised redirect URIs`, use `https://example.com/oidc/callback`, replacing example.com with your Headscale URL. +7. Under `Authorised redirect URIs`, use `https://example.com/oidc/callback`, replacing example.com with your headscale URL. 8. Click `Save` at the bottom of the form 9. Take note of the `Client ID` and `Client secret`, you can also download it for reference if you need it. 10. Edit your headscale config, under `oidc`, filling in your `client_id` and `client_secret`: - -```yaml -oidc: - issuer: "https://accounts.google.com" - client_id: "" - client_secret: "" - scope: ["openid", "profile", "email"] -``` + ```yaml + oidc: + issuer: "https://accounts.google.com" + client_id: "" + client_secret: "" + scope: ["openid", "profile", "email"] + ``` You can also use `allowed_domains` and `allowed_users` to restrict the users who can authenticate. diff --git a/docs/ref/remote-cli.md b/docs/ref/remote-cli.md new file mode 100644 index 0000000000..041d46c419 --- /dev/null +++ b/docs/ref/remote-cli.md @@ -0,0 +1,98 @@ +# Controlling headscale with remote CLI + +This documentation has the goal of showing a user how-to set control a headscale instance +from a remote machine with the `headscale` command line binary. + +## Prerequisite + +- A workstation to run headscale (could be Linux, macOS, other supported platforms) +- A headscale server (version `0.13.0` or newer) +- Access to create API keys (local access to the headscale server) +- headscale _must_ be served over TLS/HTTPS + - Remote access does _not_ support unencrypted traffic. +- Port `50443` must be open in the firewall (or port overridden by `grpc_listen_addr` option) + +## Create an API key + +We need to create an API key to authenticate our remote headscale when using it from our workstation. + +To create a API key, log into your headscale server and generate a key: + +```shell +headscale apikeys create --expiration 90d +``` + +Copy the output of the command and save it for later. Please note that you can not retrieve a key again, +if the key is lost, expire the old one, and create a new key. + +To list the keys currently assosicated with the server: + +```shell +headscale apikeys list +``` + +and to expire a key: + +```shell +headscale apikeys expire --prefix "" +``` + +## Download and configure headscale + +1. Download the latest [`headscale` binary from GitHub's release page](https://github.com/juanfont/headscale/releases): + +1. Put the binary somewhere in your `PATH`, e.g. `/usr/local/bin/headscale` + +1. Make `headscale` executable: + + ```shell + chmod +x /usr/local/bin/headscale + ``` + +1. Configure the CLI through environment variables + + ```shell + export HEADSCALE_CLI_ADDRESS=":" + export HEADSCALE_CLI_API_KEY="" + ``` + + for example: + + ```shell + export HEADSCALE_CLI_ADDRESS="headscale.example.com:50443" + export HEADSCALE_CLI_API_KEY="abcde12345" + ``` + + This will tell the `headscale` binary to connect to a remote instance, instead of looking + for a local instance (which is what it does on the server). + + The API key is needed to make sure that you are allowed to access the server. The key is _not_ + needed when running directly on the server, as the connection is local. + +1. Test the connection + + Let us run the headscale command to verify that we can connect by listing our nodes: + + ```shell + headscale nodes list + ``` + + You should now be able to see a list of your nodes from your workstation, and you can + now control the headscale server from your workstation. + +## Behind a proxy + +It is possible to run the gRPC remote endpoint behind a reverse proxy, like Nginx, and have it run on the _same_ port as headscale. + +While this is _not a supported_ feature, an example on how this can be set up on +[NixOS is shown here](https://github.com/kradalby/dotfiles/blob/4489cdbb19cddfbfae82cd70448a38fde5a76711/machines/headscale.oracldn/headscale.nix#L61-L91). + +## Troubleshooting + +Checklist: + +- Make sure you have the _same_ headscale version on your server and workstation +- Make sure you use version `0.13.0` or newer. +- Verify that your TLS certificate is valid and trusted + - If you do not have access to a trusted certificate (e.g. from Let's Encrypt), add your self signed certificate to the trust store of your OS or + - Set `HEADSCALE_CLI_INSECURE` to 0 in your environment diff --git a/docs/tls.md b/docs/ref/tls.md similarity index 98% rename from docs/tls.md rename to docs/ref/tls.md index ba87f4e641..173399e47c 100644 --- a/docs/tls.md +++ b/docs/ref/tls.md @@ -47,7 +47,7 @@ Headscale uses [autocert](https://pkg.go.dev/golang.org/x/crypto/acme/autocert), If you want to validate that certificate renewal completed successfully, this can be done either manually, or through external monitoring software. Two examples of doing this manually: -1. Open the URL for your Headscale server in your browser of choice, and manually inspecting the expiry date of the certificate you receive. +1. Open the URL for your headscale server in your browser of choice, and manually inspecting the expiry date of the certificate you receive. 2. Or, check remotely from CLI using `openssl`: ```bash diff --git a/docs/remote-cli.md b/docs/remote-cli.md deleted file mode 100644 index c641b78990..0000000000 --- a/docs/remote-cli.md +++ /dev/null @@ -1,100 +0,0 @@ -# Controlling `headscale` with remote CLI - -## Prerequisite - -- A workstation to run `headscale` (could be Linux, macOS, other supported platforms) -- A `headscale` server (version `0.13.0` or newer) -- Access to create API keys (local access to the `headscale` server) -- `headscale` _must_ be served over TLS/HTTPS - - Remote access does _not_ support unencrypted traffic. -- Port `50443` must be open in the firewall (or port overridden by `grpc_listen_addr` option) - -## Goal - -This documentation has the goal of showing a user how-to set control a `headscale` instance -from a remote machine with the `headscale` command line binary. - -## Create an API key - -We need to create an API key to authenticate our remote `headscale` when using it from our workstation. - -To create a API key, log into your `headscale` server and generate a key: - -```shell -headscale apikeys create --expiration 90d -``` - -Copy the output of the command and save it for later. Please note that you can not retrieve a key again, -if the key is lost, expire the old one, and create a new key. - -To list the keys currently assosicated with the server: - -```shell -headscale apikeys list -``` - -and to expire a key: - -```shell -headscale apikeys expire --prefix "" -``` - -## Download and configure `headscale` - -1. Download the latest [`headscale` binary from GitHub's release page](https://github.com/juanfont/headscale/releases): - -2. Put the binary somewhere in your `PATH`, e.g. `/usr/local/bin/headscale` - -3. Make `headscale` executable: - - ```shell - chmod +x /usr/local/bin/headscale - ``` - -4. Configure the CLI through environment variables - - ```shell - export HEADSCALE_CLI_ADDRESS=":" - export HEADSCALE_CLI_API_KEY="" - ``` - - for example: - - ```shell - export HEADSCALE_CLI_ADDRESS="headscale.example.com:50443" - export HEADSCALE_CLI_API_KEY="abcde12345" - ``` - - This will tell the `headscale` binary to connect to a remote instance, instead of looking - for a local instance (which is what it does on the server). - - The API key is needed to make sure that you are allowed to access the server. The key is _not_ - needed when running directly on the server, as the connection is local. - -5. Test the connection - - Let us run the headscale command to verify that we can connect by listing our nodes: - - ```shell - headscale nodes list - ``` - - You should now be able to see a list of your nodes from your workstation, and you can - now control the `headscale` server from your workstation. - -## Behind a proxy - -It is possible to run the gRPC remote endpoint behind a reverse proxy, like Nginx, and have it run on the _same_ port as `headscale`. - -While this is _not a supported_ feature, an example on how this can be set up on -[NixOS is shown here](https://github.com/kradalby/dotfiles/blob/4489cdbb19cddfbfae82cd70448a38fde5a76711/machines/headscale.oracldn/headscale.nix#L61-L91). - -## Troubleshooting - -Checklist: - -- Make sure you have the _same_ `headscale` version on your server and workstation -- Make sure you use version `0.13.0` or newer. -- Verify that your TLS certificate is valid and trusted - - If you do not have access to a trusted certificate (e.g. from Let's Encrypt), add your self signed certificate to the trust store of your OS or - - Set `HEADSCALE_CLI_INSECURE` to 0 in your environment diff --git a/docs/requirements.txt b/docs/requirements.txt index bcbf7c0ef8..0c70d5fbf8 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -1,4 +1,7 @@ cairosvg~=2.7.1 +mkdocs-include-markdown-plugin~=6.2.2 +mkdocs-macros-plugin~=1.2.0 mkdocs-material~=9.5.18 mkdocs-minify-plugin~=0.7.1 +mkdocs-redirects~=1.2.1 pillow~=10.1.0 diff --git a/docs/running-headscale-linux-manual.md b/docs/running-headscale-linux-manual.md deleted file mode 100644 index 3a0d91e0b4..0000000000 --- a/docs/running-headscale-linux-manual.md +++ /dev/null @@ -1,163 +0,0 @@ -# Running headscale on Linux - -!!! warning "Outdated and advanced" - - This documentation is considered the "legacy"/advanced/manual version of the documentation, you most likely do not - want to use this documentation and rather look at the [distro specific documentation](./running-headscale-linux.md). - -## Goal - -This documentation has the goal of showing a user how-to set up and run `headscale` on Linux. -In additional to the "get up and running section", there is an optional [systemd section](#running-headscale-in-the-background-with-systemd) -describing how to make `headscale` run properly in a server environment. - -## Configure and run `headscale` - -1. Download the latest [`headscale` binary from GitHub's release page](https://github.com/juanfont/headscale/releases): - - ```shell - wget --output-document=/usr/local/bin/headscale \ - https://github.com/juanfont/headscale/releases/download/v/headscale__linux_ - ``` - -1. Make `headscale` executable: - - ```shell - chmod +x /usr/local/bin/headscale - ``` - -1. Prepare a directory to hold `headscale` configuration and the [SQLite](https://www.sqlite.org/) database: - - ```shell - # Directory for configuration - - mkdir -p /etc/headscale - - # Directory for Database, and other variable data (like certificates) - mkdir -p /var/lib/headscale - # or if you create a headscale user: - useradd \ - --create-home \ - --home-dir /var/lib/headscale/ \ - --system \ - --user-group \ - --shell /usr/sbin/nologin \ - headscale - ``` - -1. Create a `headscale` configuration: - - ```shell - touch /etc/headscale/config.yaml - ``` - - **(Strongly Recommended)** Download a copy of the [example configuration](https://github.com/juanfont/headscale/blob/main/config-example.yaml) from the headscale repository. - -1. Start the headscale server: - - ```shell - headscale serve - ``` - - This command will start `headscale` in the current terminal session. - - --- - - To continue the tutorial, open a new terminal and let it run in the background. - Alternatively use terminal emulators like [tmux](https://github.com/tmux/tmux) or [screen](https://www.gnu.org/software/screen/). - - To run `headscale` in the background, please follow the steps in the [systemd section](#running-headscale-in-the-background-with-systemd) before continuing. - -1. Verify `headscale` is running: - Verify `headscale` is available: - - ```shell - curl http://127.0.0.1:9090/metrics - ``` - -1. Create a user ([tailnet](https://tailscale.com/kb/1136/tailnet/)): - - ```shell - headscale users create myfirstuser - ``` - -### Register a machine (normal login) - -On a client machine, execute the `tailscale` login command: - -```shell -tailscale up --login-server YOUR_HEADSCALE_URL -``` - -Register the machine: - -```shell -headscale nodes register --user myfirstuser --key -``` - -### Register machine using a pre authenticated key - -Generate a key using the command line: - -```shell -headscale preauthkeys create --user myfirstuser --reusable --expiration 24h -``` - -This will return a pre-authenticated key that can be used to connect a node to `headscale` during the `tailscale` command: - -```shell -tailscale up --login-server --authkey -``` - -## Running `headscale` in the background with systemd - -This section demonstrates how to run `headscale` as a service in the background with [systemd](https://systemd.io/). -This should work on most modern Linux distributions. - -1. Copy [headscale's systemd service file](./packaging/headscale.systemd.service) to - `/etc/systemd/system/headscale.service` and adjust it to suit your local setup. The following parameters likely need - to be modified: `ExecStart`, `WorkingDirectory`, `ReadWritePaths`. - - Note that when running as the headscale user ensure that, either you add your current user to the headscale group: - - ```shell - usermod -a -G headscale current_user - ``` - - or run all headscale commands as the headscale user: - - ```shell - su - headscale - ``` - -1. In `/etc/headscale/config.yaml`, override the default `headscale` unix socket with path that is writable by the `headscale` user or group: - - ```yaml - unix_socket: /var/run/headscale/headscale.sock - ``` - -1. Reload systemd to load the new configuration file: - - ```shell - systemctl daemon-reload - ``` - -1. Enable and start the new `headscale` service: - - ```shell - systemctl enable --now headscale - ``` - -1. Verify the headscale service: - - ```shell - systemctl status headscale - ``` - - Verify `headscale` is available: - - ```shell - curl http://127.0.0.1:9090/metrics - ``` - -`headscale` will now run in the background and start at boot. diff --git a/docs/running-headscale-linux.md b/docs/running-headscale-linux.md deleted file mode 100644 index ffa510a6a9..0000000000 --- a/docs/running-headscale-linux.md +++ /dev/null @@ -1,97 +0,0 @@ -# Running headscale on Linux - -## Requirements - -- Ubuntu 20.04 or newer, Debian 11 or newer. - -## Goal - -Get Headscale up and running. - -This includes running Headscale with systemd. - -## Migrating from manual install - -If you are migrating from the old manual install, the best thing would be to remove -the files installed by following [the guide in reverse](./running-headscale-linux-manual.md). - -You should _not_ delete the database (`/var/lib/headscale/db.sqlite`) and the -configuration (`/etc/headscale/config.yaml`). - -## Installation - -1. Download the [latest Headscale package](https://github.com/juanfont/headscale/releases/latest) for your platform (`.deb` for Ubuntu and Debian). - - ```shell - HEADSCALE_VERSION="" # See above URL for latest version, e.g. "X.Y.Z" (NOTE: do not add the "v" prefix!) - HEADSCALE_ARCH="" # Your system architecture, e.g. "amd64" - wget --output-document=headscale.deb \ - "https://github.com/juanfont/headscale/releases/download/v${HEADSCALE_VERSION}/headscale_${HEADSCALE_VERSION}_linux_${HEADSCALE_ARCH}.deb" - ``` - -1. Install Headscale: - - ```shell - sudo apt install ./headscale.deb - ``` - -1. Enable Headscale service, this will start Headscale at boot: - - ```shell - sudo systemctl enable headscale - ``` - -1. Configure Headscale by editing the configuration file: - - ```shell - nano /etc/headscale/config.yaml - ``` - -1. Start Headscale: - - ```shell - sudo systemctl start headscale - ``` - -1. Check that Headscale is running as intended: - - ```shell - systemctl status headscale - ``` - -## Using Headscale - -### Create a user - -```shell -headscale users create myfirstuser -``` - -### Register a machine (normal login) - -On a client machine, run the `tailscale` login command: - -```shell -tailscale up --login-server -``` - -Register the machine: - -```shell -headscale nodes register --user myfirstuser --key -``` - -### Register machine using a pre authenticated key - -Generate a key using the command line: - -```shell -headscale preauthkeys create --user myfirstuser --reusable --expiration 24h -``` - -This will return a pre-authenticated key that is used to -connect a node to `headscale` during the `tailscale` command: - -```shell -tailscale up --login-server --authkey -``` diff --git a/docs/running-headscale-openbsd.md b/docs/running-headscale-openbsd.md deleted file mode 100644 index 449034ba97..0000000000 --- a/docs/running-headscale-openbsd.md +++ /dev/null @@ -1,202 +0,0 @@ -# Running headscale on OpenBSD - -!!! warning "Community documentation" - - This page is not actively maintained by the headscale authors and is - written by community members. It is _not_ verified by `headscale` developers. - - **It might be outdated and it might miss necessary steps**. - -## Goal - -This documentation has the goal of showing a user how-to install and run `headscale` on OpenBSD. -In addition to the "get up and running section", there is an optional [rc.d section](#running-headscale-in-the-background-with-rcd) -describing how to make `headscale` run properly in a server environment. - -## Install `headscale` - -1. Install from ports - - You can install headscale from ports by running `pkg_add headscale`. - -1. Install from source - - ```shell - # Install prerequistes - pkg_add go - - git clone https://github.com/juanfont/headscale.git - - cd headscale - - # optionally checkout a release - # option a. you can find official release at https://github.com/juanfont/headscale/releases/latest - # option b. get latest tag, this may be a beta release - latestTag=$(git describe --tags `git rev-list --tags --max-count=1`) - - git checkout $latestTag - - go build -ldflags="-s -w -X github.com/juanfont/headscale/cmd/headscale/cli.Version=$latestTag" github.com/juanfont/headscale - - # make it executable - chmod a+x headscale - - # copy it to /usr/local/sbin - cp headscale /usr/local/sbin - ``` - -1. Install from source via cross compile - - ```shell - # Install prerequistes - # 1. go v1.20+: headscale newer than 0.21 needs go 1.20+ to compile - # 2. gmake: Makefile in the headscale repo is written in GNU make syntax - - git clone https://github.com/juanfont/headscale.git - - cd headscale - - # optionally checkout a release - # option a. you can find official release at https://github.com/juanfont/headscale/releases/latest - # option b. get latest tag, this may be a beta release - latestTag=$(git describe --tags `git rev-list --tags --max-count=1`) - - git checkout $latestTag - - make build GOOS=openbsd - - # copy headscale to openbsd machine and put it in /usr/local/sbin - ``` - -## Configure and run `headscale` - -1. Prepare a directory to hold `headscale` configuration and the [SQLite](https://www.sqlite.org/) database: - - ```shell - # Directory for configuration - - mkdir -p /etc/headscale - - # Directory for database, and other variable data (like certificates) - mkdir -p /var/lib/headscale - ``` - -1. Create a `headscale` configuration: - - ```shell - touch /etc/headscale/config.yaml - ``` - -**(Strongly Recommended)** Download a copy of the [example configuration](https://github.com/juanfont/headscale/blob/main/config-example.yaml) from the headscale repository. - -1. Start the headscale server: - - ```shell - headscale serve - ``` - - This command will start `headscale` in the current terminal session. - - *** - - To continue the tutorial, open a new terminal and let it run in the background. - Alternatively use terminal emulators like [tmux](https://github.com/tmux/tmux). - - To run `headscale` in the background, please follow the steps in the [rc.d section](#running-headscale-in-the-background-with-rcd) before continuing. - -1. Verify `headscale` is running: - - Verify `headscale` is available: - - ```shell - curl http://127.0.0.1:9090/metrics - ``` - -1. Create a user ([tailnet](https://tailscale.com/kb/1136/tailnet/)): - - ```shell - headscale users create myfirstuser - ``` - -### Register a machine (normal login) - -On a client machine, execute the `tailscale` login command: - -```shell -tailscale up --login-server YOUR_HEADSCALE_URL -``` - -Register the machine: - -```shell -headscale nodes register --user myfirstuser --key -``` - -### Register machine using a pre authenticated key - -Generate a key using the command line: - -```shell -headscale preauthkeys create --user myfirstuser --reusable --expiration 24h -``` - -This will return a pre-authenticated key that can be used to connect a node to `headscale` during the `tailscale` command: - -```shell -tailscale up --login-server --authkey -``` - -## Running `headscale` in the background with rc.d - -This section demonstrates how to run `headscale` as a service in the background with [rc.d](https://man.openbsd.org/rc.d). - -1. Create a rc.d service at `/etc/rc.d/headscale` containing: - - ```shell - #!/bin/ksh - - daemon="/usr/local/sbin/headscale" - daemon_logger="daemon.info" - daemon_user="root" - daemon_flags="serve" - daemon_timeout=60 - - . /etc/rc.d/rc.subr - - rc_bg=YES - rc_reload=NO - - rc_cmd $1 - ``` - -1. `/etc/rc.d/headscale` needs execute permission: - - ```shell - chmod a+x /etc/rc.d/headscale - ``` - -1. Start `headscale` service: - - ```shell - rcctl start headscale - ``` - -1. Make `headscale` service start at boot: - - ```shell - rcctl enable headscale - ``` - -1. Verify the headscale service: - - ```shell - rcctl check headscale - ``` - - Verify `headscale` is available: - - ```shell - curl http://127.0.0.1:9090/metrics - ``` - - `headscale` will now run in the background and start at boot. diff --git a/docs/running-headscale-sealos.md b/docs/running-headscale-sealos.md deleted file mode 100644 index 52f5c7ecce..0000000000 --- a/docs/running-headscale-sealos.md +++ /dev/null @@ -1,136 +0,0 @@ -# Running headscale on Sealos - -!!! warning "Community documentation" - - This page is not actively maintained by the headscale authors and is - written by community members. It is _not_ verified by `headscale` developers. - - **It might be outdated and it might miss necessary steps**. - -## Goal - -This documentation has the goal of showing a user how-to run `headscale` on Sealos. - -## Running headscale server - -1. Click the following prebuilt template: - - [![](https://cdn.jsdelivr.net/gh/labring-actions/templates@main/Deploy-on-Sealos.svg)](https://cloud.sealos.io/?openapp=system-template%3FtemplateName%3Dheadscale) - -2. Click "Deploy Application" on the template page to start deployment. Upon completion, two applications appear: Headscale, and its [visual interface](https://github.com/GoodiesHQ/headscale-admin). -3. Once deployment concludes, click 'Details' on the Headscale application page to navigate to the application's details. -4. Wait for the application's status to switch to running. For accessing the headscale server, the Public Address associated with port 8080 is the address of the headscale server. To access the Headscale console, simply append `/admin/` to the Headscale public URL. - - ![](./images/headscale-sealos-url.png) - -5. Click on 'Terminal' button on the right side of the details to access the Terminal of the headscale application. then create a user ([tailnet](https://tailscale.com/kb/1136/tailnet/)): - - ```bash - headscale users create myfirstuser - ``` - -### Register a machine (normal login) - -On a client machine, execute the `tailscale` login command: - -```bash -# replace with the public domain provided by Sealos -tailscale up --login-server YOUR_HEADSCALE_URL -``` - -To register a machine when running headscale in [Sealos](https://sealos.io), click on 'Terminal' button on the right side of the headscale application's detail page to access the Terminal of the headscale application, then take the headscale command: - -```bash -headscale nodes register --user myfirstuser --key -``` - -### Register machine using a pre authenticated key - -click on 'Terminal' button on the right side of the headscale application's detail page to access the Terminal of the headscale application, then generate a key using the command line: - -```bash -headscale preauthkeys create --user myfirstuser --reusable --expiration 24h -``` - -This will return a pre-authenticated key that can be used to connect a node to `headscale` during the `tailscale` command: - -```bash -tailscale up --login-server --authkey -``` - -## Controlling headscale with remote CLI - -This documentation has the goal of showing a user how-to set control a headscale instance from a remote machine with the headscale command line binary. - -### Create an API key - -We need to create an API key to authenticate our remote headscale when using it from our workstation. - -To create a API key, click on 'Terminal' button on the right side of the headscale application's detail page to access the Terminal of the headscale application, then generate a key: - -```bash -headscale apikeys create --expiration 90d -``` - -Copy the output of the command and save it for later. Please note that you can not retrieve a key again, if the key is lost, expire the old one, and create a new key. - -To list the keys currently assosicated with the server: - -```bash -headscale apikeys list -``` - -and to expire a key: - -```bash -headscale apikeys expire --prefix "" -``` - -### Download and configure `headscale` client - -1. Download the latest [`headscale` binary from GitHub's release page](https://github.com/juanfont/headscale/releases): - -2. Put the binary somewhere in your `PATH`, e.g. `/usr/local/bin/headscale` - -3. Make `headscale` executable: - -```shell -chmod +x /usr/local/bin/headscale -``` - -4. Configure the CLI through Environment Variables - -```shell -export HEADSCALE_CLI_ADDRESS=":443" -export HEADSCALE_CLI_API_KEY="" -``` - -In the headscale application's detail page, The Public Address corresponding to port 50443 corresponds to the value of . - -![](./images/headscale-sealos-grpc-url.png) - -for example: - -```shell -export HEADSCALE_CLI_ADDRESS="pwnjnnly.cloud.sealos.io:443" -export HEADSCALE_CLI_API_KEY="abcde12345" -``` - -This will tell the `headscale` binary to connect to a remote instance, instead of looking -for a local instance. - -The API key is needed to make sure that your are allowed to access the server. The key is _not_ -needed when running directly on the server, as the connection is local. - -1. Test the connection - -Let us run the headscale command to verify that we can connect by listing our nodes: - -```shell -headscale nodes list -``` - -You should now be able to see a list of your nodes from your workstation, and you can -now control the `headscale` server from your workstation. - -> Reference: [Headscale Deployment and Usage Guide: Mastering Tailscale's Self-Hosting Basics](https://icloudnative.io/en/posts/how-to-set-up-or-migrate-headscale/) diff --git a/docs/setup/install/cloud.md b/docs/setup/install/cloud.md new file mode 100644 index 0000000000..99e6c74bfc --- /dev/null +++ b/docs/setup/install/cloud.md @@ -0,0 +1,25 @@ +# Running headscale in a cloud + +!!! warning "Community documentation" + + This page is not actively maintained by the headscale authors and is + written by community members. It is _not_ verified by headscale developers. + + **It might be outdated and it might miss necessary steps**. + +## Sealos + +[Deploy headscale as service on Sealos.](https://icloudnative.io/en/posts/how-to-set-up-or-migrate-headscale/) + +1. Click the following prebuilt template: + + [![](https://cdn.jsdelivr.net/gh/labring-actions/templates@main/Deploy-on-Sealos.svg)](https://cloud.sealos.io/?openapp=system-template%3FtemplateName%3Dheadscale) + +2. Click "Deploy Application" on the template page to start deployment. Upon completion, two applications appear: headscale, and one of its [web interfaces](../../ref/integration/web-ui.md). +3. Once deployment concludes, click 'Details' on the headscale application page to navigate to the application's details. +4. Wait for the application's status to switch to running. For accessing the headscale server, the Public Address associated with port 8080 is the address of the headscale server. To access the headscale console, simply append `/admin/` to the headscale public URL. + +!!! tip "Remote CLI" + + Headscale can be managed remotely via its remote CLI support. See our [Controlling headscale with remote + CLI](../../ref/remote-cli.md) documentation for details. diff --git a/docs/setup/install/community.md b/docs/setup/install/community.md new file mode 100644 index 0000000000..f9d7cc187e --- /dev/null +++ b/docs/setup/install/community.md @@ -0,0 +1,55 @@ +# Community packages + +Several Linux distributions and community members provide packages for headscale. Those packages may be used instead of +the [official releases](./official.md) provided by the headscale maintainers. Such packages offer improved integration +for their targeted operating system and usually: + +- setup a dedicated user account to run headscale +- provide a default configuration +- install headscale as system service + +!!! warning "Community packages might be outdated" + + The packages mentioned on this page might be outdated or unmaintained. Use the [official releases](./official.md) to + get the current stable version or to test pre-releases. + + [![Packaging status](https://repology.org/badge/vertical-allrepos/headscale.svg)](https://repology.org/project/headscale/versions) + +## Arch Linux + +Arch Linux offers a package for headscale, install via: + +```shell +pacman -S headscale +``` + +The [AUR package `headscale-git`](https://aur.archlinux.org/packages/headscale-git) can be used to build the current +development version. + +## Fedora, RHEL, CentOS + +A 3rd-party repository for various RPM based distributions is available at: +. The site provides detailed setup and installation +instructions. + +## Nix, NixOS + +A Nix package is available as: `headscale`. See the [NixOS package site for installation +details](https://search.nixos.org/packages?show=headscale). + +## Gentoo + +```shell +emerge --ask net-vpn/headscale +``` + +Gentoo specific documentation is available [here](https://wiki.gentoo.org/wiki/User:Maffblaster/Drafts/Headscale). + +## OpenBSD + +Headscale is available in ports. The port installs headscale as system service with `rc.d` and provides usage +instructions upon installation. + +```shell +pkg_add headscale +``` diff --git a/docs/running-headscale-container.md b/docs/setup/install/container.md similarity index 64% rename from docs/running-headscale-container.md rename to docs/setup/install/container.md index 4357ab556e..81e7f7b750 100644 --- a/docs/running-headscale-container.md +++ b/docs/setup/install/container.md @@ -3,46 +3,36 @@ !!! warning "Community documentation" This page is not actively maintained by the headscale authors and is - written by community members. It is _not_ verified by `headscale` developers. + written by community members. It is _not_ verified by headscale developers. **It might be outdated and it might miss necessary steps**. -## Goal - -This documentation has the goal of showing a user how-to set up and run `headscale` in a container. +This documentation has the goal of showing a user how-to set up and run headscale in a container. [Docker](https://www.docker.com) is used as the reference container implementation, but there is no reason that it should not work with alternatives like [Podman](https://podman.io). The Docker image can be found on Docker Hub [here](https://hub.docker.com/r/headscale/headscale). -## Configure and run `headscale` +## Configure and run headscale -1. Prepare a directory on the host Docker node in your directory of choice, used to hold `headscale` configuration and the [SQLite](https://www.sqlite.org/) database: +1. Prepare a directory on the host Docker node in your directory of choice, used to hold headscale configuration and the [SQLite](https://www.sqlite.org/) database: ```shell mkdir -p ./headscale/config cd ./headscale ``` -1. **(Strongly Recommended)** Download a copy of the [example configuration](https://github.com/juanfont/headscale/blob/main/config-example.yaml) from the headscale repository. - - - Using `wget`: - - ```shell - wget -O ./config/config.yaml https://raw.githubusercontent.com/juanfont/headscale/main/config-example.yaml - ``` - - - Using `curl`: - - ```shell - curl https://raw.githubusercontent.com/juanfont/headscale/main/config-example.yaml -o ./config/config.yaml - ``` +1. Download the example configuration for your chosen version and save it as: `/etc/headscale/config.yaml`. Adjust the + configuration to suit your local environment. See [Configuration](../../ref/configuration.md) for details. - Modify the config file to your preferences before launching Docker container. + ```shell + sudo mkdir -p /etc/headscale + sudo nano /etc/headscale/config.yaml + ``` Alternatively, you can mount `/var/lib` and `/var/run` from your host system by adding `--volume $(pwd)/lib:/var/lib/headscale` and `--volume $(pwd)/run:/var/run/headscale` in the next step. -1. Start the headscale server while working in the host headscale directory: +1. Start the headscale server while working in the host headscale directory: ```shell docker run \ @@ -58,29 +48,30 @@ not work with alternatives like [Podman](https://podman.io). The Docker image ca Note: use `0.0.0.0:8080:8080` instead of `127.0.0.1:8080:8080` if you want to expose the container externally. This command will mount `config/` under `/etc/headscale`, forward port 8080 out of the container so the - `headscale` instance becomes available and then detach so headscale runs in the background. + headscale instance becomes available and then detach so headscale runs in the background. Example `docker-compose.yaml` - ```yaml - version: "3.7" - - services: - headscale: - image: headscale/headscale: - restart: unless-stopped - container_name: headscale - ports: - - "127.0.0.1:8080:8080" - - "127.0.0.1:9090:9090" - volumes: - # Please change to the fullpath of the config folder just created - - :/etc/headscale - command: serve - ``` - -1. Verify `headscale` is running: - Follow the container logs: + ```yaml + version: "3.7" + + services: + headscale: + image: headscale/headscale: + restart: unless-stopped + container_name: headscale + ports: + - "127.0.0.1:8080:8080" + - "127.0.0.1:9090:9090" + volumes: + # Please change to the fullpath of the config folder just created + - :/etc/headscale + command: serve + ``` + +1. Verify headscale is running: + + Follow the container logs: ```shell docker logs --follow headscale @@ -92,13 +83,13 @@ not work with alternatives like [Podman](https://podman.io). The Docker image ca docker ps ``` - Verify `headscale` is available: + Verify headscale is available: ```shell curl http://127.0.0.1:9090/metrics ``` -1. Create a user ([tailnet](https://tailscale.com/kb/1136/tailnet/)): +1. Create a user ([tailnet](https://tailscale.com/kb/1136/tailnet/)): ```shell docker exec -it headscale \ @@ -113,7 +104,7 @@ On a client machine, execute the `tailscale` login command: tailscale up --login-server YOUR_HEADSCALE_URL ``` -To register a machine when running `headscale` in a container, take the headscale command and pass it to the container: +To register a machine when running headscale in a container, take the headscale command and pass it to the container: ```shell docker exec -it headscale \ @@ -129,7 +120,7 @@ docker exec -it headscale \ headscale preauthkeys create --user myfirstuser --reusable --expiration 24h ``` -This will return a pre-authenticated key that can be used to connect a node to `headscale` during the `tailscale` command: +This will return a pre-authenticated key that can be used to connect a node to headscale during the `tailscale` command: ```shell tailscale up --login-server --authkey diff --git a/docs/setup/install/official.md b/docs/setup/install/official.md new file mode 100644 index 0000000000..d3f307f53a --- /dev/null +++ b/docs/setup/install/official.md @@ -0,0 +1,117 @@ +# Official releases + +Official releases for headscale are available as binaries for various platforms and DEB packages for Debian and Ubuntu. +Both are available on the [GitHub releases page](https://github.com/juanfont/headscale/releases). + +## Using packages for Debian/Ubuntu (recommended) + +It is recommended to use our DEB packages to install headscale on a Debian based system as those packages configure a +user to run headscale, provide a default configuration and ship with a systemd service file. Supported distributions are +Ubuntu 20.04 or newer, Debian 11 or newer. + +1. Download the [latest headscale package](https://github.com/juanfont/headscale/releases/latest) for your platform (`.deb` for Ubuntu and Debian). + + ```shell + HEADSCALE_VERSION="" # See above URL for latest version, e.g. "X.Y.Z" (NOTE: do not add the "v" prefix!) + HEADSCALE_ARCH="" # Your system architecture, e.g. "amd64" + wget --output-document=headscale.deb \ + "https://github.com/juanfont/headscale/releases/download/v${HEADSCALE_VERSION}/headscale_${HEADSCALE_VERSION}_linux_${HEADSCALE_ARCH}.deb" + ``` + +1. Install headscale: + + ```shell + sudo apt install ./headscale.deb + ``` + +1. [Configure headscale by editing the configuration file](../../ref/configuration.md): + + ```shell + sudo nano /etc/headscale/config.yaml + ``` + +1. Enable and start the headscale service: + + ```shell + sudo systemctl enable --now headscale + ``` + +1. Verify that headscale is running as intended: + + ```shell + sudo systemctl status headscale + ``` + +## Using standalone binaries (advanced) + +!!! warning "Advanced" + + This installation method is considered advanced as one needs to take care of the headscale user and the systemd + service themselves. If possible, use the [DEB packages](#using-packages-for-debianubuntu-recommended) or a + [community package](./community.md) instead. + +This section describes the installation of headscale according to the [Requirements and +assumptions](../requirements.md#assumptions). Headscale is run by a dedicated user and the service itself is managed by +systemd. + +1. Download the latest [`headscale` binary from GitHub's release page](https://github.com/juanfont/headscale/releases): + + ```shell + sudo wget --output-document=/usr/local/bin/headscale \ + https://github.com/juanfont/headscale/releases/download/v/headscale__linux_ + ``` + +1. Make `headscale` executable: + + ```shell + sudo chmod +x /usr/local/bin/headscale + ``` + +1. Add a dedicated user to run headscale: + + ```shell + sudo useradd \ + --create-home \ + --home-dir /var/lib/headscale/ \ + --system \ + --user-group \ + --shell /usr/sbin/nologin \ + headscale + ``` + +1. Download the example configuration for your chosen version and save it as: `/etc/headscale/config.yaml`. Adjust the + configuration to suit your local environment. See [Configuration](../../ref/configuration.md) for details. + + ```shell + sudo mkdir -p /etc/headscale + sudo nano /etc/headscale/config.yaml + ``` + +1. Copy [headscale's systemd service file](../../packaging/headscale.systemd.service) to + `/etc/systemd/system/headscale.service` and adjust it to suit your local setup. The following parameters likely need + to be modified: `ExecStart`, `WorkingDirectory`, `ReadWritePaths`. + +1. In `/etc/headscale/config.yaml`, override the default `headscale` unix socket with a path that is writable by the + `headscale` user or group: + + ```yaml + unix_socket: /var/run/headscale/headscale.sock + ``` + +1. Reload systemd to load the new configuration file: + + ```shell + systemctl daemon-reload + ``` + +1. Enable and start the new headscale service: + + ```shell + systemctl enable --now headscale + ``` + +1. Verify that headscale is running as intended: + + ```shell + systemctl status headscale + ``` diff --git a/docs/setup/install/source.md b/docs/setup/install/source.md new file mode 100644 index 0000000000..327430b4e1 --- /dev/null +++ b/docs/setup/install/source.md @@ -0,0 +1,63 @@ +# Build from source + +!!! warning "Community documentation" + + This page is not actively maintained by the headscale authors and is + written by community members. It is _not_ verified by headscale developers. + + **It might be outdated and it might miss necessary steps**. + +Headscale can be built from source using the latest version of [Go](https://golang.org) and [Buf](https://buf.build) +(Protobuf generator). See the [Contributing section in the GitHub +README](https://github.com/juanfont/headscale#contributing) for more information. + +## OpenBSD + +### Install from source + +```shell +# Install prerequistes +pkg_add go + +git clone https://github.com/juanfont/headscale.git + +cd headscale + +# optionally checkout a release +# option a. you can find official release at https://github.com/juanfont/headscale/releases/latest +# option b. get latest tag, this may be a beta release +latestTag=$(git describe --tags `git rev-list --tags --max-count=1`) + +git checkout $latestTag + +go build -ldflags="-s -w -X github.com/juanfont/headscale/cmd/headscale/cli.Version=$latestTag" github.com/juanfont/headscale + +# make it executable +chmod a+x headscale + +# copy it to /usr/local/sbin +cp headscale /usr/local/sbin +``` + +### Install from source via cross compile + +```shell +# Install prerequistes +# 1. go v1.20+: headscale newer than 0.21 needs go 1.20+ to compile +# 2. gmake: Makefile in the headscale repo is written in GNU make syntax + +git clone https://github.com/juanfont/headscale.git + +cd headscale + +# optionally checkout a release +# option a. you can find official release at https://github.com/juanfont/headscale/releases/latest +# option b. get latest tag, this may be a beta release +latestTag=$(git describe --tags `git rev-list --tags --max-count=1`) + +git checkout $latestTag + +make build GOOS=openbsd + +# copy headscale to openbsd machine and put it in /usr/local/sbin +``` diff --git a/docs/setup/requirements.md b/docs/setup/requirements.md new file mode 100644 index 0000000000..a9ef2ca334 --- /dev/null +++ b/docs/setup/requirements.md @@ -0,0 +1,28 @@ +# Requirements + +Headscale should just work as long as the following requirements are met: + +- A server with a public IP address for headscale. A dual-stack setup with a public IPv4 and a public IPv6 address is + recommended. +- Headscale is served via HTTPS on port 443[^1]. +- A reasonably modern Linux or BSD based operating system. +- A dedicated user account to run headscale. +- A little bit of command line knowledge to configure and operate headscale. + +## Assumptions + +The headscale documentation and the provided examples are written with a few assumptions in mind: + +- Headscale is running as system service via a dedicated user `headscale`. +- The [configuration](../ref/configuration.md) is loaded from `/etc/headscale/config.yaml`. +- SQLite is used as database. +- The data directory for headscale (used for private keys, ACLs, SQLite database, …) is located in `/var/lib/headscale`. +- URLs and values that need to be replaced by the user are either denoted as `` or use placeholder + values such as `headscale.example.com`. + +Please adjust to your local environment accordingly. + +[^1]: + The Tailscale client assumes HTTPS on port 443 in certain situations. Serving headscale either via HTTP or via HTTPS + on a port other than 443 is possible but sticking with HTTPS on port 443 is strongly recommended for production + setups. See [issue 2164](https://github.com/juanfont/headscale/issues/2164) for more information. diff --git a/docs/setup/upgrade.md b/docs/setup/upgrade.md new file mode 100644 index 0000000000..e518a7b50e --- /dev/null +++ b/docs/setup/upgrade.md @@ -0,0 +1,10 @@ +# Upgrade an existing installation + +An existing headscale installation can be updated to a new version: + +- Read the announcement on the [GitHub releases](https://github.com/juanfont/headscale/releases) page for the new + version. It lists the changes of the release along with possible breaking changes. +- **Create a backup of your database.** +- Update headscale to the new version, preferably by following the same installation method. +- Compare and update the [configuration](../ref/configuration.md) file. +- Restart headscale. diff --git a/docs/android-client.md b/docs/usage/connect/android.md similarity index 96% rename from docs/android-client.md rename to docs/usage/connect/android.md index 044b9fcf40..98305bd702 100644 --- a/docs/android-client.md +++ b/docs/usage/connect/android.md @@ -1,8 +1,6 @@ # Connecting an Android client -## Goal - -This documentation has the goal of showing how a user can use the official Android [Tailscale](https://tailscale.com) client with `headscale`. +This documentation has the goal of showing how a user can use the official Android [Tailscale](https://tailscale.com) client with headscale. ## Installation diff --git a/docs/apple-client.md b/docs/usage/connect/apple.md similarity index 98% rename from docs/apple-client.md rename to docs/usage/connect/apple.md index 29ad4b4547..7597c7178e 100644 --- a/docs/apple-client.md +++ b/docs/usage/connect/apple.md @@ -1,8 +1,6 @@ # Connecting an Apple client -## Goal - -This documentation has the goal of showing how a user can use the official iOS and macOS [Tailscale](https://tailscale.com) clients with `headscale`. +This documentation has the goal of showing how a user can use the official iOS and macOS [Tailscale](https://tailscale.com) clients with headscale. !!! info "Instructions on your headscale instance" diff --git a/docs/windows-client.md b/docs/usage/connect/windows.md similarity index 95% rename from docs/windows-client.md rename to docs/usage/connect/windows.md index 66c47279d1..2d073981c7 100644 --- a/docs/windows-client.md +++ b/docs/usage/connect/windows.md @@ -1,8 +1,6 @@ # Connecting a Windows client -## Goal - -This documentation has the goal of showing how a user can use the official Windows [Tailscale](https://tailscale.com) client with `headscale`. +This documentation has the goal of showing how a user can use the official Windows [Tailscale](https://tailscale.com) client with headscale. !!! info "Instructions on your headscale instance" @@ -45,7 +43,7 @@ If you are seeing repeated messages like: [GIN] 2022/02/10 - 16:39:34 | 200 | 1.105306ms | 127.0.0.1 | POST "/machine/redacted" ``` -in your `headscale` output, turn on `DEBUG` logging and look for: +in your headscale output, turn on `DEBUG` logging and look for: ``` 2022-02-11T00:59:29Z DBG Machine registration has expired. Sending a authurl to register machine=redacted diff --git a/docs/usage/getting-started.md b/docs/usage/getting-started.md new file mode 100644 index 0000000000..d344156b97 --- /dev/null +++ b/docs/usage/getting-started.md @@ -0,0 +1,132 @@ +# Getting started + +This page helps you get started with headscale and provides a few usage examples for the headscale command line tool +`headscale`. + +!!! note "Prerequisites" + + * Headscale is installed and running as system service. Read the [setup section](../setup/requirements.md) for + installation instructions. + * The configuration file exists and is adjusted to suit your environment, see + [Configuration](../ref/configuration.md) for details. + * The Tailscale client is installed, see [Client and operating system support](../about/clients.md) for more + information. + +## Getting help + +The `headscale` command line tool provides built-in help. To show available commands along with their arguments and +options, run: + +=== "Native" + + ```shell + # Show help + headscale help + + # Show help for a specific command + headscale --help + ``` + +=== "Container" + + ```shell + # Show help + docker exec -it headscale \ + headscale help + + # Show help for a specific command + docker exec -it headscale \ + headscale --help + ``` + +## Manage users + +In headscale, a node (also known as machine or device) is always assigned to a specific user, a +[tailnet](https://tailscale.com/kb/1136/tailnet/). Such users can be managed with the `headscale users` command. Invoke +the built-in help for more information: `headscale users --help`. + +### Create a user + +=== "Native" + + ```shell + headscale users create + ``` + +=== "Container" + + ```shell + docker exec -it headscale \ + headscale users create + ``` + +### List existing users + +=== "Native" + + ```shell + headscale users list + ``` + +=== "Container" + + ```shell + docker exec -it headscale \ + headscale users list + ``` + +## Register a node + +One has to register a node first to use headscale as coordination with Tailscale. The following examples work for the +Tailscale client on Linux/BSD operating systems. Alternatively, follow the instructions to connect +[Android](connect/android.md), [Apple](connect/apple.md) or [Windows](connect/windows.md) devices. + +### Normal, interactive login + +On a client machine, run the `tailscale up` command and provide the FQDN of your headscale instance as argument: + +```shell +tailscale up --login-server +``` + +Usually, a browser window with further instructions is opened and contains the value for ``. Approve +and register the node on your headscale server: + +=== "Native" + + ```shell + headscale nodes register --user --key + ``` + +=== "Container" + + ```shell + docker exec -it headscale \ + headscale nodes register --user --key + ``` + +### Using a preauthkey + +It is also possible to generate a preauthkey and register a node non-interactively. First, generate a preauthkey on the +headscale instance. By default, the key is valid for one hour and can only be used once (see `headscale preauthkeys +--help` for other options): + +=== "Native" + + ```shell + headscale preauthkeys create --user + ``` + +=== "Container" + + ```shell + docker exec -it headscale \ + headscale preauthkeys create --user + ``` + +The command returns the preauthkey on success which is used to connect a node to the headscale instance via the +`tailscale up` command: + +```shell +tailscale up --login-server --authkey +``` diff --git a/mkdocs.yml b/mkdocs.yml index a8e38cdd8f..d01c94cc3d 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -66,9 +66,26 @@ exclude_docs: | plugins: - search: separator: '[\s\-,:!=\[\]()"`/]+|\.(?!\d)|&[lg]t;|(?!\b)(?=[A-Z][a-z])' + - macros: + - include-markdown: - minify: minify_html: true - social: {} + - redirects: + redirect_maps: + acls.md: ref/acls.md + android-client.md: usage/connect/android.md + apple-client.md: usage/connect/apple.md + dns-records.md: ref/dns.md + exit-node.md: ref/exit-node.md + faq.md: about/faq.md + iOS-client.md: usage/connect/apple.md#ios + oidc.md: ref/oidc.md + remote-cli.md: ref/remote-cli.md + reverse-proxy.md: ref/integration/reverse-proxy.md + tls.md: ref/tls.md + web-ui.md: ref/integration/web-ui.md + windows-client.md: usage/connect/windows.md # Customization extra: @@ -83,6 +100,8 @@ extra: link: https://github.com/juanfont/headscale/pkgs/container/headscale - icon: fontawesome/brands/discord link: https://discord.gg/c84AZQhmpx + headscale: + version: 0.23.0 # Extensions markdown_extensions: @@ -128,23 +147,39 @@ markdown_extensions: # Page tree nav: - - Home: index.md - - FAQ: faq.md - - Getting started: + - Welcome: index.md + - About: + - FAQ: about/faq.md + - Features: about/features.md + - Clients: about/clients.md + - Getting help: about/help.md + - Releases: about/releases.md + - Contributing: about/contributing.md + - Sponsor: about/sponsor.md + + - Setup: + - Requirements and Assumptions: setup/requirements.md - Installation: - - Linux: running-headscale-linux.md - - OpenBSD: running-headscale-openbsd.md - - Container: running-headscale-container.md - - Configuration: - - Web UI: web-ui.md - - OIDC authentication: oidc.md - - Exit node: exit-node.md - - Reverse proxy: reverse-proxy.md - - TLS: tls.md - - ACLs: acls.md - - Custom DNS records: dns-records.md - - Remote CLI: remote-cli.md - - Usage: - - Android: android-client.md - - Apple: apple-client.md - - Windows: windows-client.md + - Official releases: setup/install/official.md + - Community packages: setup/install/community.md + - Container: setup/install/container.md + - Cloud: setup/install/cloud.md + - Build from source: setup/install/source.md + - Upgrade: setup/upgrade.md + - Usage: + - Getting started: usage/getting-started.md + - Connect a node: + - Android: usage/connect/android.md + - Apple: usage/connect/apple.md + - Windows: usage/connect/windows.md + - Reference: + - Configuration: ref/configuration.md + - OIDC authentication: ref/oidc.md + - Exit node: ref/exit-node.md + - TLS: ref/tls.md + - ACLs: ref/acls.md + - DNS: ref/dns.md + - Remote CLI: ref/remote-cli.md + - Integration: + - Reverse proxy: ref/integration/reverse-proxy.md + - Web UI: ref/integration/web-ui.md