Compare commits

...

237 Commits

Author SHA1 Message Date
Kristoffer Dalby
d5037c25a6 date in changelog
Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>
2025-02-11 11:56:59 +01:00
Kristoffer Dalby
c53ff2ce00 update changelog (#2414)
Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>
2025-02-07 11:32:01 +01:00
Kristoffer Dalby
b4ac8cd9a3 hscontrol/db: add migration setting non existing pak on nodes to null (#2412)
Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>
2025-02-07 10:22:23 +01:00
Nbelles
22277d1fc7 Spell check 2025-02-05 17:29:30 +01:00
Kristoffer Dalby
9ae3570154 drop versions older than 1.62 (#2405) 2025-02-05 12:02:32 +01:00
Florian Preinstorfer
f12cb2e048 Headscale now updates the user profile 2025-02-05 11:05:03 +01:00
Florian Preinstorfer
8c09afe20c Headscale implements a single tailnet
Separate the term "tailnet" from user and be more explicit about
providing a single tailnet.

Also be more explicit about users. Refer to "headscale users" when
mentioning commandline invocations and use the term "local users" when
discussing unix accounts.

Fixes: #2335
2025-02-05 11:05:03 +01:00
Kristoffer Dalby
8b92c017ec add 1.80 to capver and update deps (#2394) 2025-02-05 07:17:51 +01:00
github-actions[bot]
9a7890d56b flake.lock: Update (#2402) 2025-02-02 07:58:12 +00:00
Kristoffer Dalby
45752db0f6 Return better web errors to the user (#2398)
* add dedicated http error to propagate to user

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>

* classify user errors in http handlers

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>

* move validation of pre auth key out of db

This move separates the logic a bit and allow us to
write specific errors for the caller, in this case the web
layer so we can present the user with the correct error
codes without bleeding web stuff into a generic validate.

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>

* update changelog

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>

---------

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>
2025-02-01 15:25:18 +01:00
Kristoffer Dalby
1c7f3bc440 no edit of oidc users, minimum hostname length (#2393)
* return an error when renaming users from OIDC
* set minimum hostname length of 2
2025-02-01 09:40:37 +00:00
Kristoffer Dalby
9bd143852f do not allow preauth keys to be deleted if assigned to node (#2396)
* do not allow preauth keys to be deleted if assigned to node

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>

* update changelog

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>

---------

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>
2025-02-01 10:31:13 +01:00
Kristoffer Dalby
d57a55c024 Rewrite authentication flow (#2374) 2025-02-01 09:16:51 +00:00
Kristoffer Dalby
e172c29360 initial capver packet tracking version (#2391)
* initial capver packet tracking version

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>

* Log the minimum version as client version, not only capver

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>

* remove old versions

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>

* use capver for integration tests

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>

* changelog

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>

* patch through m and n key

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>

---------

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>
2025-01-30 21:49:09 +00:00
Kristoffer Dalby
cd3b8e68ff clean up handler methods, common logging (#2384)
* clean up handler methods, common logging

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>

* streamline http.Error calls

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>

---------

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>
2025-01-30 22:40:29 +01:00
nblock
f44b1d37c4 Remove routes without a node_id (#2386)
The routes table has a NOT NULL constraint on node_id.

Fixes: #2376
2025-01-30 14:57:06 +01:00
Kristoffer Dalby
7ba6ad3489 simplify findUserByToken in ACL, add missing testcases (#2388)
* update users doc on unique constraints

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>

* simplify finduser func

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>

* add initial tests for findUserFromToken

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>

* add changelog

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>

---------

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>
2025-01-30 11:35:49 +01:00
Kristoffer Dalby
2c279e0a7b create and rename usernames validated by new func (#2381)
Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>
2025-01-27 22:58:10 +01:00
Kristoffer Dalby
4c8e847f47 use dedicated registration ID for auth flow (#2337) 2025-01-26 22:20:11 +01:00
github-actions[bot]
97e5d95399 flake.lock: Update (#2378) 2025-01-26 15:33:51 +00:00
Kristoffer Dalby
d1dbe4ece9 fix panic if derp update is 0 (#2368)
* fix panic if derp update is 0

Fixes #2362

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>

* update changelog

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>

---------

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>
2025-01-23 16:16:12 +01:00
Kristoffer Dalby
9e3f945eda fix postgres migration issue with 0.24 (#2367)
* fix postgres migration issue with 0.24

Fixes #2351

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>

* add postgres migration test for 2351

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>

* update changelog

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>

---------

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>
2025-01-23 14:58:42 +01:00
Kristoffer Dalby
615ee5df75 make it harder to insert invalid routes (#2371)
* make it harder to insert invalid routes

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>

* dont panic if node is not available for route

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>

* update changelog

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>

---------

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>
2025-01-23 13:40:23 +01:00
Kristoffer Dalby
c1f42cdf4b relax user validation to allow emails, add tests from various oidc providers (#2364)
* relax user validation to allow emails, add tests from various oidc providers

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>

* changelog

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>

---------

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>
2025-01-22 18:10:15 +01:00
github-actions[bot]
aa76980b43 flake.lock: Update (#2353) 2025-01-19 09:59:29 +00:00
Kristoffer Dalby
5b986ed0a7 set oidc.map_legacy_users false (#2350) 2025-01-17 14:44:04 +00:00
Kristoffer Dalby
8076c94444 Release docs 0.24 (#2349)
* correct changelog date

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>

* update docs version and copyright

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>

* fix deprecated goreleaser key and DRY

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>

---------

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>
2025-01-17 12:57:13 +00:00
Kristoffer Dalby
e88406e837 set changelog date (#2347) 2025-01-17 12:01:06 +01:00
Kristoffer Dalby
e4a3dcc3b8 use headscale server url as domain instead of base_domain (#2338) 2025-01-16 18:05:20 +01:00
Kristoffer Dalby
caad5c613d fix nil pointer deref (#2339) 2025-01-16 18:05:05 +01:00
Kristoffer Dalby
38aef77e54 allow @ and Log if OIDC username is not consider valid (#2340) 2025-01-16 18:04:54 +01:00
Dmitry Gordin
1ab7b315a2 Update apple.md for latest version of iOS (#2321)
The official iOS app now has a simpler login process for custom instances, directly within the app.
2025-01-13 12:09:53 +00:00
github-actions[bot]
610597bfb7 flake.lock: Update (#2342) 2025-01-12 18:54:59 +00:00
Stefan Majer
ede4f97a16 Fix typos 2025-01-09 10:38:25 +01:00
Kristoffer Dalby
fa641e38b8 Set CSRF cookies for OIDC (#2328)
* set state and nounce in oidc to prevent csrf

Fixes #2276

* try to fix new postgres issue

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>

---------

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>
2025-01-08 16:29:37 +01:00
github-actions[bot]
41bad2b9fd flake.lock: Update (#2324) 2025-01-05 07:35:18 +00:00
github-actions[bot]
f9bbfa5eab flake.lock: Update (#2320) 2024-12-29 11:41:52 +00:00
Rorical
b81420bef1 feat: Add PKCE Verifier for OIDC (#2314)
* feat: add PKCE verifier for OIDC

* Update CHANGELOG.md
2024-12-22 16:46:36 +00:00
github-actions[bot]
9313e5b058 flake.lock: Update (#2313) 2024-12-22 07:07:26 +00:00
Kristoffer Dalby
770f3dcb93 fix tags not resolving to username if email is present (#2309)
* ensure valid tags is populated on user gets too

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>

* ensure forced tags are added

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>

* remove unused envvar in test

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>

* debug log auth/unauth tags in policy man

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>

* defer shutdown in tags test

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>

* add tag test with groups

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>

* add email, display name, picture to create user

Updates #2166

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>

* add ability to set display and email to cli

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>

* add email to test users in integration

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>

* fix issue where tags were only assigned to email, not username

Fixes #2300
Fixes #2307

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>

* expand principles to correct login name

and if fix an issue where nodeip principles might not expand to all
relevant IPs instead of taking the first in a prefix.

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>

* fix ssh unit test

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>

* update cli and oauth tests for users with email

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>

* index by test email

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>

* fix last test

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>

---------

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>
2024-12-19 13:10:10 +01:00
Kristoffer Dalby
af4508b9dc bump deps (#2308)
* Bump go crypto

Closes #2281

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>

* upgrade tailscale

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>

* upgrade rest

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>

* nix: flake update

---------

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>
2024-12-17 15:35:42 +01:00
Florian Preinstorfer
bbc93a90a2 Set title for code listings 2024-12-17 14:08:34 +01:00
Florian Preinstorfer
0acb2b5647 Misc doc updates 2024-12-17 14:08:34 +01:00
Florian Preinstorfer
3269cfdca0 Mention reload and SIGHUP when editing the ACL policy file
Fixes: #2284
2024-12-17 14:08:34 +01:00
Florian Preinstorfer
319ce67c87 Update DNS documentation for dns.extra_records_path
* Describe both ways to add extra DNS records
* Use "extra" instead of "custom" to align with the configuration file
* Include dns.extra_records_path in the configuration file
2024-12-17 14:08:34 +01:00
Florian Preinstorfer
47b405d6c6 Changelog: support client verify for DERP
and fix some links

Ref: #2304
2024-12-17 14:08:34 +01:00
Florian Preinstorfer
65304a0ce7 Remove sealos documentation
The referenced version is outdated (0.23.0-beta1) and seems
unmaintained.
2024-12-17 14:08:34 +01:00
Dongjun Na
e270169c13 Add -race Flag to GitHub Action and Fix Data Race in CreateTailscaleNodesInUser (#2038)
* Add -race flag to Makefile and integration tests; fix data race in CreateTailscaleNodesInUser

* Fix data race in ExecuteCommand by using local buffers and mutex

Signed-off-by: Dongjun Na <kmu5544616@gmail.com>

* lint

Signed-off-by: Dongjun Na <kmu5544616@gmail.com>

---------

Signed-off-by: Dongjun Na <kmu5544616@gmail.com>
2024-12-17 14:06:57 +01:00
Shaw Drastin
7d937c6bd0 Correct macOS GUI connect guide because there's no ALT key on a mac (#2306)
* Correct macOS GUI connect guide because there's no ALT key on a mac
* also correct macOS GUI connect in hscontrol text
2024-12-17 12:11:27 +00:00
Kristoffer Dalby
ccc895b4c6 fixes to extra-record file watcher (#2298)
* Fix excess error message during writes

Fixes #2290

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>

* retry filewatcher on removed files

This should handled if files are deleted and added again, and for rename
scenarios.

Fixes #2289

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>

* test more write and remove in filewatcher

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>

---------

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>
2024-12-16 11:26:56 +01:00
Kristoffer Dalby
5345f19693 fix issue where some oidc claim bools are sent as string (#2297)
Jumpcloud send invalid json, so we need to handle it.

Fixes #2293

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>
2024-12-16 11:26:32 +01:00
Kristoffer Dalby
ec8729b772 fix sighup issue with empty acl (#2296)
Fixes #2291

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>
2024-12-16 07:48:19 +01:00
github-actions[bot]
e00b9d9a91 flake.lock: Update (#2294) 2024-12-15 06:46:14 +00:00
Kristoffer Dalby
58d089ce0a fix deletion of exit routes without nodes (#2286)
Fixes #2259

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>
2024-12-13 20:15:24 +01:00
Kristoffer Dalby
76d26a7eec update oidc part of changelog for 0.24.0 (#2285) 2024-12-13 12:35:24 +00:00
Kristoffer Dalby
380fcdba17 Add worker reading extra_records_path from file (#2271)
* consolidate scheduled tasks into one goroutine

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>

* rename Tailcfg dns struct

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>

* add dns.extra_records_path option

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>

* prettier lint

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>

* go-fmt

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>

---------

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>
2024-12-13 07:52:40 +00:00
Florian Preinstorfer
89a648c7dd Remove use_username_in_magic_dns option
Upgrade the use of dns.use_username_in_magic_dns or
dns_config.use_username_in_magic_dns to a fatal error and remove the
option from the example configuration and integration tests.

Fixes: #2219
2024-12-11 18:39:35 +01:00
Vitalij Dovhanyc
697d80d5a8 chore: configure some actions to be skipped for forks (#2005)
* chore: configure some actions to be skipped for forks

* chore: build docs only when it changes
2024-12-11 16:44:37 +01:00
Kristoffer Dalby
757defa2f2 run cross compile of headscale as part of build (#2270) 2024-12-10 16:26:53 +01:00
Kristoffer Dalby
64fd1f9483 restructure command/api to use stable IDs (#2261) 2024-12-10 16:23:55 +01:00
Kristoffer Dalby
08bd4b9bc5 fix docker network caps (#2273)
Docker releases a patch release which changed the required permissions to be able to do tun devices in containers, this caused all containers to fail in tests causing us to fail all tests. This fixes it, and adds some tools for debugging in the future.

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>
2024-12-09 16:15:38 +00:00
github-actions[bot]
26d91ae513 flake.lock: Update (#2266) 2024-12-08 09:10:42 +00:00
Florian Preinstorfer
75e74117db Add FAQ entry on which database to use
Fixes: #2257
2024-12-04 06:08:44 +01:00
Florian Preinstorfer
d2a86b1ef2 Fix broken indent 2024-12-04 06:08:44 +01:00
Florian Preinstorfer
0d3cf74098 Fix README links to point to the stable version 2024-12-04 06:08:44 +01:00
Florian Preinstorfer
44456497b0 Add versioned documentation
Setup mike to provide versioned builds of the documentation.

The goal is to have versioned docs for stable releases (0.23.0, 0.24.0)
and development docs that can progress along with the code. This allows
us to tailor docs to the next upcoming version as we no longer need to
care about diversion between rendered docs and the latest release.

Versions:
* development (alias: unstable) on each push to the main branch
* MAJOR.MINOR.PATCH (alias: stable, latest for the newest version)
  * for each "final" release tag
  * for each push to doc maintenance branches: doc/MAJOR.MINOR.PATCH

The default version should the current stable version. The doc
maintenance branches may be used to update the version specific
documentation when issues arise after a release.
2024-12-02 16:51:50 +01:00
Florian Preinstorfer
7512e236d6 Bump deprecated github actions 2024-11-27 09:52:08 +01:00
Kristoffer Dalby
f7b0cbbbea wrap policy in policy manager interface (#2255)
Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>
2024-11-26 15:16:06 +01:00
github-actions[bot]
2c1ad6d11a flake.lock: Update (#2254) 2024-11-24 09:42:22 +00:00
Kristoffer Dalby
fffd23602b Resolve user to stable unique ID in policy (#2205) 2024-11-24 00:13:27 +01:00
Kristoffer Dalby
3a2589f1a9 rename dockerfile to integration to avoid confusion (#2225)
Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>
2024-11-23 21:14:36 +00:00
Kristoffer Dalby
f6276ab9d2 fix postgres constraints, add postgres testing
This commit fixes the constraint syntax so it is both valid for
sqlite and postgres.

To validate this, I've added a new postgres testing library and a
helper that will spin up local postgres, setup a db and use it in
the constraints tests. This should also help testing db stuff in
the future.

postgres has been added to the nix dev shell and is now required
for running the unit tests.

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>
2024-11-23 21:14:55 +01:00
Kristoffer Dalby
7d9b430ec2 fix constraints
Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>
2024-11-23 21:14:55 +01:00
Kristoffer Dalby
3780c9fd69 fix nil in test
Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>
2024-11-23 21:14:55 +01:00
Kristoffer Dalby
281025bb16 fix constraints
Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>
2024-11-23 21:14:55 +01:00
Kristoffer Dalby
5e7c3153b9 nits
Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>
2024-11-23 21:14:55 +01:00
Kristoffer Dalby
7ba0c3d515 use userID instead of username everywhere
Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>
2024-11-23 21:14:55 +01:00
Kristoffer Dalby
4b58dc6eb4 make preauthkey tags test stable
Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>
2024-11-23 21:14:55 +01:00
Kristoffer Dalby
4dd12a2f97 fix oidc test, add tests for migration
Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>
2024-11-23 21:14:55 +01:00
Kristoffer Dalby
2fe65624c0 restore strip_email_domain for migration
Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>
2024-11-23 21:14:55 +01:00
Kristoffer Dalby
35b669fe59 add iss to identifier, only set email if verified
Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>
2024-11-23 21:14:55 +01:00
Kristoffer Dalby
dc07779143 add @ to end of username if not present
Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>
2024-11-23 21:14:55 +01:00
Kristoffer Dalby
d72663a4d0 remove log print
Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>
2024-11-23 21:14:55 +01:00
Kristoffer Dalby
0a82d3f17a update changelog
Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>
2024-11-23 21:14:55 +01:00
Kristoffer Dalby
78214699ad Harden OIDC migration and make optional
This commit hardens the migration part of the OIDC from
the old username based approach to the new sub based approach
and makes it possible for the operator to opt out entirely.

Fixes #1990

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>
2024-11-23 21:14:55 +01:00
Kristoffer Dalby
64bb56352f make configurable wal auto checkpoint (#2242) 2024-11-23 21:03:48 +01:00
nblock
dc17b4d378 Documentation dependencies (#2252)
* Use a trailing slash
recommended by mkdocs-material

* Update doc requirements
Let mkdocs-material resolve its imaging dependencies (cairosvg and
pillow) and fix a dependabot warning along the way.

Reference compatible versions by major.minor.
2024-11-22 16:52:36 +00:00
Kristoffer Dalby
a6b19e85db more linter fixups (#2212)
* linter fixes

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>

* conf

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>

* update nix hash

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>

---------

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>
2024-11-22 15:54:58 +00:00
ArcticLampyrid
edf9e25001 feat: support client verify for derp (add integration tests) (#2046)
* feat: support client verify for derp

* docs: fix doc for integration test

* tests: add integration test for DERP verify endpoint

* tests: use `tailcfg.DERPMap` instead of `[]byte`

* refactor: introduce func `ContainsNodeKey`

* tests(dsic): use string builder for cmd args

* ci: fix tests order

* tests: fix derper failure

* chore: cleanup

* tests(verify-client): perfer to use `CreateHeadscaleEnv`

* refactor(verify-client): simplify error handling

* tests: fix `TestDERPVerifyEndpoint`

* refactor: make `doVerify` a seperated func

---------

Co-authored-by: 117503445 <t117503445@gmail.com>
2024-11-22 13:23:05 +01:00
Motiejus Jakštys
c6336adb01 config: loosen up BaseDomain and ServerURL checks (#2248)
* config: loosen up BaseDomain and ServerURL checks

Requirements [here][1]:

> OK:
> server_url: headscale.com, base: clients.headscale.com
> server_url: headscale.com, base: headscale.net
>
> Not OK:
> server_url: server.headscale.com, base: headscale.com
>
> Essentially we have to prevent the possibility where the headscale
> server has a URL which can also be assigned to a node.
>
> So for the Not OK scenario:
>
> if the server is: server.headscale.com, and a node joins with the name
> server, it will be assigned server.headscale.com and that will break
> the connection for nodes which will now try to connect to that node
> instead of the headscale server.

Fixes #2210

[1]: https://github.com/juanfont/headscale/issues/2210#issuecomment-2488165187

* server_url and base_domain: re-word error message, fix a one-off bug and add a test case for the bug.

* lint

* lint again
2024-11-22 13:21:44 +01:00
enoperm
5fbf3f8327 Websocket derp test fixes (#2247)
* integration testing: add and validate build-time options for tailscale head

* fixup! integration testing: add and validate build-time options for tailscale head

integration testing: comply with linter

* fixup! fixup! integration testing: add and validate build-time options for tailscale head

integration testing: tsic.New must never return nil

* fixup! fixup! fixup! integration testing: add and validate build-time options for tailscale head

* minor fixes
2024-11-22 11:57:01 +01:00
Nathan Sweet
6275399327 Update tls.md to mention using the full cert chain (#2243) 2024-11-18 06:12:12 +00:00
nblock
29119bb7f4 Misc doc fixes (#2240)
* Link back to node registration docs
* adjust wording in apple docs
* Mention client specific page to check if headscale works

Ref: #2238
2024-11-18 05:46:58 +01:00
github-actions[bot]
93ba21ede5 flake.lock: Update (#2239) 2024-11-17 19:38:50 +00:00
nblock
a7874af3d0 Use discord server invite link (#2235)
Replace channel links with links to discord invite link and remove
channel list.

Fixes: #1521
2024-11-16 07:06:15 +01:00
nblock
e7245856c5 Refresh remote CLI documentation (#2216)
* Document to either use a minimal configuration file or environment
  variables to connect with a remote headscale instance.
* Document a workaround specific for headscale 0.23.0.
* Remove reference to ancient headscale version.
* Use `cli.insecure: true` or `HEADSCALE_CLI_INSECURE=1` to skip
  certificate verification.
* Style and typo fixes

Ref: #2193
2024-11-13 18:35:42 +01:00
nblock
2345c38e1e Add a page for third-party tools (#2217)
* Remove status from web-ui docs

Rename the title to indicate that there multiple web interfaces
available. Do not track the status of each web interface here as their
status is subject to change over time.

* Add page for third-party tools and scripts
2024-11-12 16:53:30 +01:00
github-actions[bot]
8cfaa6bdac flake.lock: Update (#2222) 2024-11-12 13:27:49 +00:00
docgalaxyblock
4e44d57bf7 fix: missing stable-debug tag (#2232)
Fixes #2171
2024-11-11 06:06:44 +00:00
Philip Henning
0089ceaf1d Feature tvos documentation (#2226)
* Add usage documentation for tvOS

* lint and format

* Change admonition to mkdocs flavoured style

* fix typos

* Update hscontrol/templates/apple.go

Co-authored-by: Kristoffer Dalby <kristoffer@dalby.cc>

* change outer quoting for where quoting in-text is used

---------

Co-authored-by: Kristoffer Dalby <kristoffer@dalby.cc>
2024-11-07 14:56:18 +00:00
nblock
9a46c5763c Handle /derp/latency-check (#2227)
According to 15fc6cd966
the routes `/derp/probe` and `/derp/latency-check` are the same and
different versions of the tailscale client use one or the other
endpoint.

Also handle /derp/latency-check

Fixes: #2211
2024-11-06 15:59:38 +01:00
Kristoffer Dalby
a71a933705 add nblock to doc owners (#2207)
Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>
2024-11-04 10:12:50 -06:00
github-actions[bot]
0c98d09783 Update flake.lock (#2195)
Some checks failed
Build / build (push) Has been cancelled
Build documentation / build (push) Has been cancelled
Tests / test (push) Has been cancelled
Build documentation / deploy (push) Has been cancelled
Close inactive issues / close-issues (push) Successful in 21s
Flake lock file updates:

• Updated input 'nixpkgs':
    'github:NixOS/nixpkgs/e2f08f4d8b3ecb5cf5c9fd9cb2d53bb3c71807da?narHash=sha256-CAZF2NRuHmqTtRTNAruWpHA43Gg2UvuCNEIzabP0l6M%3D' (2024-10-05)
  → 'github:NixOS/nixpkgs/41dea55321e5a999b17033296ac05fe8a8b5a257?narHash=sha256-WvLXzNNnnw%2BqpFOmgaM3JUlNEH%2BT4s22b5i2oyyCpXE%3D' (2024-10-25)

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-10-29 14:16:10 +00:00
Kristoffer Dalby
e2d5ee0927 cleanup linter warnings (#2206)
Some checks failed
Build / build (push) Has been cancelled
Build documentation / build (push) Has been cancelled
Tests / test (push) Has been cancelled
Build documentation / deploy (push) Has been cancelled
GitHub Actions Version Updater / build (push) Has been cancelled
update-flake-lock / lockfile (push) Has been cancelled
Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>
2024-10-23 10:45:59 -05:00
Kristoffer Dalby
028d9aab73 add new user fields to grpc and list command (#2202)
Updates #2166

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>
2024-10-18 14:20:03 +00:00
hopleus
b6dc6eb36c #2140 Fixed reflection of hostname change (#2199)
* #2140 Fixed updating of hostname and givenName when it is updated in HostInfo

* #2140 Added integration tests

* #2140 Fix unit tests

* Changed IsAutomaticNameMode to GivenNameHasBeenChanged. Fixed errors in files according to golangci-lint rules
2024-10-17 09:45:33 -06:00
Goran Draganić
45c9585b52 feat: derpmap field in config (#1823) 2024-10-17 05:34:20 -06:00
hopleus
cc42fc394a #2177 Added conversion of 'Hostname' to 'givenName' in a node with FQDN rules applied (#2198) 2024-10-15 18:33:03 +02:00
hopleus
52a3b54ba2 Fixed loginUrl with "WithTLS()" used. Added "WithTLS()" to scenario integration tests (#2187) 2024-10-15 14:38:43 +02:00
Pepper
0602304cea Add headplane
Add headplane to the list of UI's
2024-10-11 16:32:33 +02:00
nblock
8c7d8ee34f 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
2024-10-10 15:24:04 +02:00
hopleus
b3cda08af6 #2178 Fixed processing of fields in post request in MoveNode rpc (#2179)
* #2178 Fixed processing of fields in post request in MoveNode rpc

* #2178 Updated CHANGELOG.md
2024-10-09 09:36:47 +02:00
github-actions[bot]
101ca7f4a2 Update flake.lock (#2173)
Flake lock file updates:

• Updated input 'nixpkgs':
    'github:NixOS/nixpkgs/b5b2fecd0cadd82ef107c9583018f381ae70f222?narHash=sha256-k6YxGj08voz9NvuKExojiGXAVd69M8COtqWSKr6sQS4%3D' (2024-09-28)
  → 'github:NixOS/nixpkgs/e2f08f4d8b3ecb5cf5c9fd9cb2d53bb3c71807da?narHash=sha256-CAZF2NRuHmqTtRTNAruWpHA43Gg2UvuCNEIzabP0l6M%3D' (2024-10-05)

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-10-06 12:00:59 +00:00
Amha Mersha
24e7851a40 Changed all the html into go using go-elem (#2161)
* Changed all the HTML into go using go-elem

            Created templates package in ./hscontrol/templates.
            Moved the registerWebAPITemplate into the templates package as a function to be called.

            Replaced the apple and windows html files with go-elem.

* update flake

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>

---------

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>
Co-authored-by: Kristoffer Dalby <kristoffer@tailscale.com>
2024-10-04 11:39:24 +00:00
Kristoffer Dalby
9515040161 make reauth test compat with tailscale head (#2167)
* make reauth test compat with tailscale head

tailscale/tailscale@1eaad7d broke our reauth test as it makes the client
retry with https/443 if it reconnects within 2 minutes.

This commit fixes this by running the test as a two part,
- with https, to confirm instant reconnect works
- with http, and a 3 min wait, to check that it work without.

The change is not a general consern as headscale in prod is ran
with https.

Updates #2164

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>

* sort test for stable order

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>

---------

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>
2024-10-03 12:01:48 +02:00
Kristoffer Dalby
e16ea2ee69 set hostinfo,ipv* columns explicitly (#2165)
Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>
2024-10-02 18:12:25 +02:00
Kristoffer Dalby
218138afee Redo OIDC configuration (#2020)
expand user, add claims to user

This commit expands the user table with additional fields that
can be retrieved from OIDC providers (and other places) and
uses this data in various tailscale response objects if it is
available.

This is the beginning of implementing
https://docs.google.com/document/d/1X85PMxIaVWDF6T_UPji3OeeUqVBcGj_uHRM5CI-AwlY/edit
trying to make OIDC more coherant and maintainable in addition
to giving the user a better experience and integration with a
provider.

remove usernames in magic dns, normalisation of emails

this commit removes the option to have usernames as part of MagicDNS
domains and headscale will now align with Tailscale, where there is a
root domain, and the machine name.

In addition, the various normalisation functions for dns names has been
made lighter not caring about username and special character that wont
occur.

Email are no longer normalised as part of the policy processing.

untagle oidc and regcache, use typed cache

This commits stops reusing the registration cache for oidc
purposes and switches the cache to be types and not use any
allowing the removal of a bunch of casting.

try to make reauth/register branches clearer in oidc

Currently there was a function that did a bunch of stuff,
finding the machine key, trying to find the node, reauthing
the node, returning some status, and it was called validate
which was very confusing.

This commit tries to split this into what to do if the node
exists, if it needs to register etc.

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>
2024-10-02 14:50:17 +02:00
Kristoffer Dalby
bc9e83b52e use gorm serialiser instead of custom hooks (#2156)
* add sqlite to debug/test image

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>

* test using gorm serialiser instead of custom hooks

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>

---------

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>
2024-10-02 11:41:58 +02:00
Kristoffer Dalby
3964dec1c6 use tsaddr library and cleanups (#2150)
* resuse tsaddr code instead of handrolled

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>

* ensure we dont give out internal tailscale IPs

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>

* use prefix instead of string for routes

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>

* remove old custom compare func

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>

* trim unused util code

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>

---------

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>
2024-10-02 09:06:09 +02:00
Jacob Yundt
63035cdb5a Update headscale user creation settings in .deb (#2134)
* Update headscale user creation settings in .deb

Update the headscale user settings to:
- shell = /usr/sbin/nologin
- home-dir = /var/lib/headscale

This syncs the .deb installation behavior with the current Linux docs:
fe68f50328/docs/running-headscale-linux-manual.md (L39-L45)

Fixes juanfont/headscale#2133

* slight refactor to use existing variables.

* Fixup for HOME_DIR var
2024-09-29 11:00:52 +00:00
Kristoffer Dalby
5eda9c8d2d denormalise PreAuthKey tags (#2155)
this commit denormalises the Tags related to a Pre auth key
back onto the preauthkey table and struct as a string list.

There was not really any real normalisation here as we just added
a bunch of duplicate tags with new IDs and preauthkeyIDs, lots of
GORM cermony but no actual advantage.

This work is the start to fixup tags which currently are not working
as they should.

Updates #1369

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>
2024-09-29 13:00:27 +02:00
github-actions[bot]
49ce5734fc flake.lock: Update (#2158) 2024-09-29 08:24:08 +00:00
Hazel Atkinson
204a102389 Add ouroboros to web ui list (#2154) 2024-09-27 09:16:18 +02:00
Robert Schütz
2c974dd72d MagicDNS no longer requires nameservers (#1681)
According to https://tailscale.com/kb/1081/magicdns#accessing-devices-over-magicdns,

> MagicDNS does not require a DNS nameserver if running Tailscale v1.20 or later.
2024-09-26 19:09:19 +00:00
nblock
e367454745 Add -it to docker exec (#2148)
Some commands such as `nodes delete` require user interaction and they
fail if `-it` is no supplied to `docker exec`. Use `docker exec -it` in
documentation examples to also make them work in interactive commands.
2024-09-25 09:52:28 +02:00
Kristoffer Dalby
4f2fb65929 remove versions older than 1.56 (#2149)
* remove versions older than 1.56

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>

* remove code no longer needed for new clients

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>

* update changelog

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>

---------

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>
2024-09-24 18:34:20 +02:00
David Mell
07b596d3cc Allow nodes to use SSH agent forwarding (#2145) 2024-09-23 11:59:16 +02:00
github-actions[bot]
f3fca8302a flake.lock: Update (#2143) 2024-09-22 09:46:04 +00:00
enoperm
1e61084898 Add compatibility with only websocket-capable clients (#2132)
* handle control protocol through websocket

The necessary behaviour is already in place,
but the wasm build only issued GETs, and the handler was not invoked.

* get DERP-over-websocket working for wasm clients

* Prepare for testing builtin websocket-over-DERP

Still needs some way to assert that clients are connected through websockets,
rather than the TCP hijacking version of DERP.

* integration tests: properly differentiate between DERP transports

* do not touch unrelated code

* linter fixes

* integration testing: unexport common implementation of derp server scenario

* fixup! integration testing: unexport common implementation of derp server scenario

* dockertestutil/logs: remove unhelpful comment

* update changelog

---------

Co-authored-by: Csaba Sarkadi <sarkadicsa@tutanota.de>
2024-09-21 12:05:36 +02:00
Kristoffer Dalby
10a72e8d54 update changelog for 0.23 release (#2138)
Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>
2024-09-18 09:43:08 +01:00
Kristoffer Dalby
ed78ecda12 add shutdown that asserts if headscale had panics (#2126)
Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>
2024-09-17 11:44:55 +02:00
github-actions[bot]
6cbbcd859c flake.lock: Update (#2135) 2024-09-16 06:15:45 +00:00
nblock
e9d9c0773c Exclude irrelevant files from mkdocs rendering (#2136) 2024-09-16 06:13:45 +00:00
nblock
fe68f50328 Use headscale.example.com (#2122) 2024-09-11 16:46:06 +00:00
nblock
c3ef90a7f7 Update documentation for Apple (#2117)
* Rename docs/ios-client.md to docs/apple-client.md. Add instructions
  for macOS; those are copied from the /apple endpoint and slightly
  modified. Fix doc links in the README.
* Move infoboxes for /apple and /windows under the "Goal" section to the
  top. Those should be seen by users first as they contain *their*
  specific headscale URL.
* Swap order of macOS and iOS to move "Profiles" further down.
* Remove apple configuration profiles
* Remove Tailscale versions hints
* Mention /apple and /windows in the README along with their docs

See: #2096
2024-09-11 18:43:59 +02:00
Kristoffer Dalby
064c46f2a5 move logic for validating node names (#2127)
* move logic for validating node names

this commits moves the generation of "given names" of nodes
into the registration function, and adds validation of renames
to RenameNode using the same logic.

Fixes #2121

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>

* fix double arg

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>

---------

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>
2024-09-11 18:27:49 +02:00
Kristoffer Dalby
64319f79ff make stream shutdown if self-node has been removed (#2125)
* add shutdown that asserts if headscale had panics

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>

* add test case producing 2118 panic

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>

* make stream shutdown if self-node has been removed

Currently we will read the node from database, and since it is
deleted, the id might be set to nil. Keep the node around and
just shutdown, so it is cleanly removed from notifier.

Fixes #2118

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>

---------

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>
2024-09-11 12:00:32 +02:00
Kristoffer Dalby
4b02dc9565 make cli mode respect log.level (#2124)
Fixes #2119

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>
2024-09-11 10:43:22 +02:00
Kristoffer Dalby
7be8796d87 dont override golangci go (#2116)
Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>
2024-09-09 14:29:09 +02:00
curlwget
99f18f9cd9 chore: fix some comments (#2069) 2024-09-09 14:17:25 +02:00
github-actions[bot]
c3b260a6f7 flake.lock: Update (#2111) 2024-09-09 14:16:35 +02:00
Kristoffer Dalby
60b94b0467 Fix slow shutdown (#2113)
* rearrange shutdown

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>

* http closed is fine

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>

* update changelog

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>

* logging while shutting

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>

---------

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>
2024-09-09 14:10:22 +02:00
nblock
bac7ea67f4 Simplify windows setup instructions (#2114)
* Simplify /windows to the bare minimum. Also remove the
  /windows/tailscale.reg endpoint as its generated file is no longer
  valid for current Tailscale versions.
* Update and simplify the windows documentation accordingly.
* Add a "Unattended mode" section to the troubleshooting section
  explaining how to enable "Unattended mode" in the via the Tailscale
  tray icon.
* Add infobox about /windows to the docs

Tested on Windows 10, 22H2 with Tailscale 1.72.0

Replaces: #1995
See: #2096
2024-09-09 13:18:16 +02:00
nblock
5597edac1e Remove version and update setup instructions for Android (#2112) 2024-09-09 06:57:50 +00:00
Kristoffer Dalby
8a3a0fee3c Only load needed part of configuration (#2109) 2024-09-07 09:23:58 +02:00
Kristoffer Dalby
f368ed01ed 2068 AutoApprovers tests (#2105)
* replace old suite approved routes test with table driven

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>

* add test to reproduce issue

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>

* add integration test for 2068

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>

---------

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>
2024-09-05 16:46:20 +02:00
Kristoffer Dalby
adc084f20f add no stalebot exception (#2107)
Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>
2024-09-05 14:00:36 +02:00
Kristoffer Dalby
42d2c27853 fix goreleaser warnings (#2106)
* add version to goreleaser config

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>

* rename deprected setting

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>

---------

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>
2024-09-05 14:00:19 +02:00
nblock
1c34101e72 Remove outdated proposals (#2104)
Fixes: #2101
2024-09-05 11:50:10 +00:00
greizgh
6609f60938 actually lint file on CI (#2018)
* replace deprecated golangci-lint output format

CI was producing this kind of messages:
> [config_reader] The output format `github-actions` is deprecated, please use `colored-line-number`

* Actually lint files on CI
2024-09-05 13:37:05 +02:00
nblock
35bfe7ced0 Add support for service reload and sync service file (#2102)
* Add support for service reload and sync service file

* Copy the systemd.service file to the manual linux docs and adjust the
  path to the headscale binary to match with the previous documentation
  blocks. Unfortunately, there seems to be no easy way to include a
  file in mkdocs.
* Remove a redundant "deprecation" block. The beginning of the
  documentation already states that.
* Add `ExecReload` to the systemd.service file.

Fixes: #2016

* Its called systemd

* Fix link to systemd homepage
2024-09-05 10:08:50 +00:00
nblock
e43d6a0361 Move flags after the command (#2100)
The built-in help also shows flags to given after the command. Align
documentation examples accordingly.
2024-09-04 12:38:38 +00:00
Kristoffer Dalby
f039caf134 update godeps (#2098) 2024-09-04 07:55:16 +02:00
nblock
d66c5e144f Update documentation for 0.23 (#2096)
* docs/acl: fix path to policy file

* docs/exit-node: fixup for 0.23

* Add newlines between commands to improve readability
* Use nodes instead on name
* Remove query parameter from link to Tailscale docs

* docs/remote-cli: fix formatting

* Indent blocks below line numbers to restore numbering
* Fix minor typos

* docs/reverse-proxy: remove version information

* Websocket support is always required now
* s/see detail/see details

* docs/exit-node: add warning to manual documentation

* Replace the warning section with a warning admonition
* Fix TODO link back to the regular linux documentation

* docs/openbsd: fix typos

* the database is created on-the-fly

* docs/sealos: fix typos

* docs/container: various fixes

* Remove a stray sentence
* Remove "headscale" before serve
* Indent line continuation
* Replace hardcoded 0.22 with <VERSION>
* Fix path in debug image to /ko-app/headscale

Fixes: #1822

aa
2024-09-03 11:04:20 +00:00
Mike Poindexter
3101f895a7 Fix 764 (#2093)
* Fix KeyExpiration when a zero time value has a timezone

When a zero time value is loaded from JSON or a DB in a way that
assigns it the local timezone, it does not roudtrip in JSON as a
value for which IsZero returns true. This causes KeyExpiry to be
treated as a far past value instead of a nilish value.

See https://github.com/golang/go/issues/57040

* Fix whitespace

* Ensure that postgresql is used for all tests when env var is set

* Pass through value of HEADSCALE_INTEGRATION_POSTGRES env var

* Add option to set timezone on headscale container

* Add test for registration with auth key in alternate timezone
2024-09-03 09:22:17 +02:00
nblock
aa0f3d43cc Fix typo in example config (#2095) 2024-09-02 06:18:16 +00:00
nblock
ed71d230eb Remove references to tests/acls from the documentation (#2088) 2024-09-01 13:09:47 +00:00
github-actions[bot]
976cbfa630 flake.lock: Update (#2078) 2024-09-01 13:08:57 +00:00
nblock
a9a1a07e37 Use dns: as config key (#2092) 2024-09-01 13:08:06 +00:00
Kristoffer Dalby
1193a50e9e oldest client supported, not latest (#2086) 2024-08-30 16:59:37 +02:00
Kristoffer Dalby
cb0e2e4476 various doc updates in prep for 0.23 (#2091)
* various doc updates in prep for 0.23

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>

* add note discouraging postgresql

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>

* Update docs/faq.md

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>

* remove entry for glossary in doc

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>

* fix typo

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>

---------

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
2024-08-30 16:59:24 +02:00
Kristoffer Dalby
2b5e52b08b validate policy against nodes, error if not valid (#2089)
* validate policy against nodes, error if not valid

this commit aims to improve the feedback of "runtime" policy
errors which would only manifest when the rules are compiled to
filter rules with nodes.

this change will in;

file-based mode load the nodes from the db and try to compile the rules on
start up and return an error if they would not work as intended.

database-based mode prevent a new ACL being written to the database if
it does not compile with the current set of node.

Fixes #2073
Fixes #2044

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>

* ensure stderr can be used in err checks

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>

* test policy set validation

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>

* add new integration test to ghaction

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>

* add back defer for cli tst

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>

---------

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>
2024-08-30 16:58:29 +02:00
Roman Zabaluev
fffd9d7ee9 Update ACLs file format docs (#2066) 2024-08-30 07:20:07 +00:00
Mike Poindexter
76515d12d6 Fix self notification on expiry update via oidc relogin (#2080) 2024-08-30 08:20:29 +02:00
Mike Poindexter
34361c6f82 Fix FKs on sqlite migrations (#2083) 2024-08-30 08:08:54 +02:00
Thomas Gautvedt
f4427dd29e fix: correct a small spelling mistake (#2081) 2024-08-28 07:50:09 +00:00
Kristoffer Dalby
cf6a606d74 fix route table migration wiping routes 0.22 -> 0.23 (#2076) 2024-08-27 18:54:28 +02:00
dragon2611
827e3e83ae Issue 2045, Feature Request (#2071)
Requiring someone to write a design doc/contribute to the feature shouldn't be a requirement for raising a feature request as users may lack the skills required to do this.
2024-08-27 11:03:51 +02:00
Kristoffer Dalby
9c4c286696 fix warning errs from beta2 (#2075)
* remove default false for use usernames causing warning

Fixes #2065

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>

* Ensure DoH warnings are only emitted if err

Fixes #2064

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>

---------

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>
2024-08-23 17:17:37 +02:00
Kristoffer Dalby
a68854ac33 upgrade go (1.23) and tailscale (1.72.1) (#2077) 2024-08-23 15:28:54 +02:00
github-actions[bot]
9bed76d481 flake.lock: Update (#2059) 2024-08-19 11:05:43 +00:00
Kristoffer Dalby
84cb5d0aed make acl_policy_path fatal if policy.path is not set (#2041) 2024-08-19 13:03:01 +02:00
Kristoffer Dalby
f99497340b add coderabbit config (#2060)
Code Rabbit is one of these new fancy LLM code review tools. I am skeptical
but we can try it for free and it might provide us with some value to let
people get feedback while waiting for other people.

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>
2024-08-19 10:06:55 +00:00
nadongjun
fdc034e8ae Integrate GORM Logger with Zerolog and Add Configuration Options for Logging and Performance (#2040)
* Integrate GORM logger with zerolog and add custom GORM configuration options

* Add GormConfig struct to group GORM-related settings

* Update debug mode instruction in config-example.yaml

Co-authored-by: Kristoffer Dalby <kristoffer@dalby.cc>

---------

Co-authored-by: Kristoffer Dalby <kristoffer@dalby.cc>
2024-08-19 11:47:52 +02:00
Kristoffer Dalby
ac8491efec Redo DNS configuration (#2034)
this commit changes and streamlines the dns_config into a new
key, dns. It removes a combination of outdates and incompatible
configuration options that made it easy to confuse what headscale
could and could not do, or what to expect from ones configuration.

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>
2024-08-19 11:41:05 +02:00
Chuangbo Li
022fb24cd9 Fix command get policy works with relative policy path (#2051) 2024-08-12 12:11:59 +02:00
github-actions[bot]
fcd1183805 flake.lock: Update (#2052) 2024-08-11 05:48:15 +00:00
Kristoffer Dalby
ece907d878 test embedded derp with derp updater, check client health (#2030) 2024-08-11 07:44:59 +02:00
github-actions[bot]
948d53f934 flake.lock: Update (#2042) 2024-08-04 06:35:46 +00:00
github-actions[bot]
06f07053eb flake.lock: Update (#2035) 2024-07-28 08:42:38 +00:00
nadongjun
4ad3f3c484 Fix data race issues in EphemeralGarbageCollector tests (#2023)
* Fix data race issues in EphemeralGarbageCollector tests

* Add defer for mutex unlock in TestEphemeralGarbageCollectorOrder

* Fix mutex unlock order in closure by updating defer placement
2024-07-22 15:11:57 +00:00
Steven Honson
db7a4358e9 config-example.yaml: Remove reference to yaml for policy files (#2022) 2024-07-22 13:38:42 +00:00
github-actions[bot]
b799245f1e flake.lock: Update (#2021) 2024-07-22 08:56:21 +02:00
greizgh
8571513e3c reformat code (#2019)
* reformat code

This is mostly an automated change with `make lint`.
I had to manually please golangci-lint in routes_test because of a short
variable name.

* fix start -> strategy which was wrongly corrected by linter
2024-07-22 08:56:00 +02:00
Kristoffer Dalby
ca47d6f353 small cleanups (#2017) 2024-07-19 09:21:14 +02:00
Kristoffer Dalby
11fde62b8c remove custom contains funcs for slices.Contains (#2015)
Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>
2024-07-19 09:04:04 +02:00
Kristoffer Dalby
9e523d4687 move userprofiles into method on user struct (#2014)
Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>
2024-07-19 09:03:18 +02:00
Kristoffer Dalby
7e62031444 replace ephemeral deletion logic (#2008)
* replace ephemeral deletion logic

this commit replaces the way we remove ephemeral nodes,
currently they are deleted in a loop and we look at last seen
time. This time is now only set when a node disconnects and
there was a bug (#2006) where nodes that had never disconnected
was deleted since they did not have a last seen.

The new logic will start an expiry timer when the node disconnects
and delete the node from the database when the timer is up.

If the node reconnects within the expiry, the timer is cancelled.

Fixes #2006

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>

* use uint64 as authekyid and ptr helper in tests

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>

* add test db helper

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>

* add list ephemeral node func

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>

* schedule ephemeral nodes for removal on startup

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>

* fix gorm query for postgres

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>

* add godoc

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>

---------

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>
2024-07-18 10:01:59 +02:00
Pallab Pain
58bd38a609 feat: implements apis for managing headscale policy (#1792) 2024-07-18 07:38:25 +02:00
Rubens Peculis
00ff288f0c fix(1996): Implement register method enum converter (#2013)
Added a new function `RegisterMethodToV1Enum()` to Node, converting the internal register method string to the corresponding V1 Enum value. Included corresponding unit test in `node_test.go` to ensure correct conversion for various register methods.
2024-07-17 13:12:16 +02:00
greizgh
8823778d05 Add gofumpt to dev dependencies (#2010) 2024-07-17 13:12:02 +02:00
greizgh
74d27ee5fa Remove deprecated linters from golangci-lint (#2009)
When running lints, golangci-lint complained about removed
linters (which were already disabled).
This removes the relevant warnings.
2024-07-17 10:08:41 +02:00
github-actions[bot]
3f60ab23a6 Update flake.lock (#2011)
Flake lock file updates:

• Updated input 'nixpkgs':
    'github:NixOS/nixpkgs/4284c2b73c8bce4b46a6adf23e16d9e2ec8da4bb?narHash=sha256-i4vJL12/AdyuQuviMMd1Hk2tsGt02hDNhA0Zj1m16N8%3D' (2024-07-05)
  → 'github:NixOS/nixpkgs/8b5a3d5a1d951344d683b442c0739010b80039db?narHash=sha256-po3TZO9kcZwzvkyMJKb0WCzzDtiHWD34XeRaX1lWXp0%3D' (2024-07-12)

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-07-14 06:20:22 +00:00
github-actions[bot]
eb1591df35 flake.lock: Update (#2000) 2024-07-07 06:16:36 +00:00
github-actions[bot]
89ada557bc flake.lock: Update (#1991) 2024-07-01 05:46:33 +00:00
Kristoffer Dalby
14a3f94f0c fix search domains and remove username from magicdns (#1987) 2024-06-26 13:44:40 +02:00
Kristoffer Dalby
4a34cfc4a6 Make write-ahead-log default and configurable for sqlite (#1985)
* correctly enable WAL log for sqlite

this commit makes headscale correctly enable write-ahead-log for
sqlite and adds an option to turn it on and off.

WAL is enabled by default and should make sqlite perform a lot better,
even further eliminating the need to use postgres.

It also adds a couple of other useful defaults.

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>

* update changelog

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>

---------

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>
2024-06-23 22:06:59 +02:00
Kristoffer Dalby
8f8f469c0a Remove allocations of lists before use (#1989)
* policy: remove allocs before appends in acls

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>

* notifier: make batcher tests stable/non-flaky

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>

* {db,derp,mapper}: dont allocate until append

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>

---------

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>
2024-06-23 22:06:50 +02:00
Lars Kiesow
69c33658f6 Fix android docs (#1976)
The current Tailscale app for Android looks and behaves differently.
This patch updates the documentation for that.
2024-06-23 00:52:23 +02:00
Kyhwana Pardus
99e91a9d8a Update reverse-proxy.md (#1986)
Add blurb about how cloudflare proxy/tunnels is not supported/will not work
2024-06-23 00:47:26 +02:00
github-actions[bot]
dfc089ed6a flake.lock: Update (#1979) 2024-06-16 20:24:08 +00:00
Lars Kiesow
51676c668b Make registration screen easier to use (#1975) 2024-06-15 09:40:49 +02:00
github-actions[bot]
1f4b59566a flake.lock: Update (#1958) 2024-06-09 07:23:16 +00:00
Dongjun Na
5f9c26930c fixed typo and path (#1960) 2024-05-28 16:11:39 +00:00
Kristoffer Dalby
5a4e52b727 remove last_successful_update error check (#1959)
most of the time we dont even check this error and checking
the string for particular errors is very flake as different
databases (sqlite and psql) use different error messages, and
some users might have it in other languages.

Fixes #1956

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>
2024-05-27 11:53:37 +01:00
github-actions[bot]
51b56ba447 Update flake.lock (#1952)
Flake lock file updates:

• Updated input 'nixpkgs':
    'github:NixOS/nixpkgs/b3fcfcfabd01b947a1e4f36622bbffa3985bdac6?narHash=sha256-iJYnKMtLi5u6hZhJm94cRNSDG5Rz6ZzIkGbhPFtDRm0%3D' (2024-05-15)
  → 'github:NixOS/nixpkgs/02923630b89aa1ab36ef8e422501a6f4fd4b2016?narHash=sha256-OhysviwHQz4p2HZL4g7XGMLoUbWMjkMr/ogaR3VUYNA%3D' (2024-05-18)

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-05-24 10:30:11 +01:00
Kristoffer Dalby
c8ebbede54 Simplify map session management (#1931)
This PR removes the complicated session management introduced in https://github.com/juanfont/headscale/pull/1791 which kept track of the sessions in a map, in addition to the channel already kept track of in the notifier.

Instead of trying to close the mapsession, it will now be replaced by the new one and closed after so all new updates goes to the right place.

The map session serve function is also split into a streaming and a non-streaming version for better readability.

RemoveNode in the notifier will not remove a node if the channel is not matching the one that has been passed (e.g. it has been replaced with a new one).

A new tuning parameter has been added to added to set timeout before the notifier gives up to send an update to a node.

Add a keep alive resetter so we wait with sending keep alives if a node has just received an update.

In addition it adds a bunch of env debug flags that can be set:

- `HEADSCALE_DEBUG_HIGH_CARDINALITY_METRICS`: make certain metrics include per node.id, not recommended to use in prod. 
- `HEADSCALE_DEBUG_PROFILING_ENABLED`: activate tracing 
- `HEADSCALE_DEBUG_PROFILING_PATH`: where to store traces 
- `HEADSCALE_DEBUG_DUMP_CONFIG`: calls `spew.Dump` on the config object startup
- `HEADSCALE_DEBUG_DEADLOCK`: enable go-deadlock to dump goroutines if it looks like a deadlock has occured, enabled in integration tests.

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>
2024-05-24 10:15:34 +02:00
Stefan Majer
8185a70dc7 Fix typos (#1860)
* Fix typos

* trigger GitHub actions

* remove kdiff3 orig files

* fix unicode

* remove unnecessary function call

* remove unnecessary comment

* remove unnecessary comment

---------

Co-authored-by: ohdearaugustin <ohdearaugustin@users.noreply.github.com>
2024-05-19 23:49:27 +02:00
ohdearaugustin
2dc62e981e move debug image to distroless (#1950) 2024-05-19 11:17:37 +02:00
Kristoffer Dalby
5ad0aa44cb update tailscale go dep (#1948)
* update tailscale go dep

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>

* update gorm go dep

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>

* update grpc go dep

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>

* update golang.org go dep

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>

* update rest of go dep

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>

---------

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>
2024-05-17 08:58:33 -04:00
github-actions[bot]
723a0408a3 flake.lock: Update (#1897) 2024-05-16 14:01:34 +00:00
dependabot[bot]
30986c29cd Bump github.com/docker/docker (#1947) 2024-05-16 04:42:53 +00:00
dependabot[bot]
faa57ddc28 Bump github.com/go-jose/go-jose/v3 from 3.0.1 to 3.0.3 (#1945) 2024-05-16 03:02:12 +00:00
dependabot[bot]
fff229f4f6 Bump github.com/jackc/pgx/v5 from 5.5.3 to 5.5.4 (#1946) 2024-05-16 03:01:18 +00:00
dependabot[bot]
fd4f921281 Bump google.golang.org/protobuf from 1.32.0 to 1.33.0 (#1944) 2024-05-16 02:15:41 +00:00
dependabot[bot]
151f224a98 Bump golang.org/x/net from 0.22.0 to 0.23.0 (#1943)
Bumps [golang.org/x/net](https://github.com/golang/net) from 0.22.0 to 0.23.0.
- [Commits](https://github.com/golang/net/compare/v0.22.0...v0.23.0)

---
updated-dependencies:
- dependency-name: golang.org/x/net
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-05-16 01:22:02 +00:00
Sandro
a9763c9692 Initialize log config earlier to prevent trace messages being printed early on (#1939)
like TRC DNS configuration loaded dns_config={....}
2024-05-15 20:40:30 -04:00
MichaelKo
7fd2485000 Restore foreign keys and add constraints (#1562)
* fix #1482, restore foregin keys, add constraints

* #1562, fix tests, fix formatting

* #1562: fix tests

* #1562: fix local run of test_integration
2024-05-15 20:40:14 -04:00
Dan Pastusek
2bac80cfbf [DOCS] Make linux installation instructions more clear (#1927)
* Make linux installation instructions more clear

* Update running-headscale-linux.md
2024-05-06 20:06:30 +02:00
Michael Savage
93a915c096 Update OpenBSD installation docs for 2024 (#1915) 2024-05-06 20:03:21 +02:00
Kristoffer Dalby
622aa82da2 ensure expire routines are cleaned up (#1924)
Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>
2024-05-02 15:57:53 +00:00
Kristoffer Dalby
a9c568c801 trace log and notifier shutdown (#1922)
Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>
2024-05-02 13:39:19 +02:00
Kristoffer Dalby
1c6bfc503c fix preauth key logging in as previous user (#1920)
* add test case to reproduce #1885

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>

* fix preauth key issue logging in as wrong user

Fixes #1885

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>

* add test to gh

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>

---------

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>
2024-05-02 11:53:16 +02:00
Kristoffer Dalby
55b35f4160 fix issue preveting get node when disco is missing (#1919)
Fixed #1816

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>
2024-05-01 17:06:42 +02:00
Kristoffer Dalby
d5ed8bc074 error if no prefix is configured (#1918)
Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>
2024-04-30 11:11:29 +02:00
Kristoffer Dalby
87e2ae4d52 add autogroup:internet, fix reduce filter rules (#1917) 2024-04-30 07:23:16 +02:00
Juan Font
ff427ccb78 Apply suggestions from code review
Co-authored-by: ohdearaugustin <ohdearaugustin@users.noreply.github.com>
2024-04-29 23:04:02 +02:00
Juan Font
39277844dd Apply suggestions from code review
Co-authored-by: ohdearaugustin <ohdearaugustin@users.noreply.github.com>
2024-04-29 23:04:02 +02:00
Juan Font
50a7d15769 Update CONTRIBUTING.md
Co-authored-by: ohdearaugustin <ohdearaugustin@users.noreply.github.com>
2024-04-29 23:04:02 +02:00
Juan Font
d740ee489e Update CONTRIBUTING.md
Co-authored-by: ohdearaugustin <ohdearaugustin@users.noreply.github.com>
2024-04-29 23:04:02 +02:00
Juan Font
10e37ec28d Add contributing document 2024-04-29 23:04:02 +02:00
Kristoffer Dalby
cb0b495ea9 batch updates in notifier (#1905) 2024-04-27 10:47:39 +02:00
Juan Font
fef8261339 Do not access node ID when node is not found (#1912) 2024-04-24 07:44:35 +02:00
Kristoffer Dalby
c62d5570f2 remove multistep build, build go last, allowing cached build layers (#1903) 2024-04-24 07:44:07 +02:00
Kristoffer Dalby
318d5d2b21 replace issue templates with github issue forms
Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>
2024-04-22 23:10:34 +02:00
Kristoffer Dalby
9229d17bbe remove examples/, and kustomize (#1906)
this directory is unmaintained and not verified, if it should be restored, it should end up
under the community docs effort.

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>
2024-04-22 21:21:38 +02:00
Juan Font
aba4b36030 Clarify relation with Tailscale (#1908) 2024-04-22 20:37:59 +02:00
252 changed files with 19490 additions and 12692 deletions

15
.coderabbit.yaml Normal file
View File

@@ -0,0 +1,15 @@
# yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json
language: "en-GB"
early_access: false
reviews:
profile: "chill"
request_changes_workflow: false
high_level_summary: true
poem: true
review_status: true
collapse_walkthrough: false
auto_review:
enabled: true
drafts: true
chat:
auto_reply: true

16
.github/CODEOWNERS vendored
View File

@@ -1,10 +1,10 @@
* @juanfont @kradalby
*.md @ohdearaugustin
*.yml @ohdearaugustin
*.yaml @ohdearaugustin
Dockerfile* @ohdearaugustin
.goreleaser.yaml @ohdearaugustin
/docs/ @ohdearaugustin
/.github/workflows/ @ohdearaugustin
/.github/renovate.json @ohdearaugustin
*.md @ohdearaugustin @nblock
*.yml @ohdearaugustin @nblock
*.yaml @ohdearaugustin @nblock
Dockerfile* @ohdearaugustin @nblock
.goreleaser.yaml @ohdearaugustin @nblock
/docs/ @ohdearaugustin @nblock
/.github/workflows/ @ohdearaugustin @nblock
/.github/renovate.json @ohdearaugustin @nblock

View File

@@ -1,65 +0,0 @@
---
name: "Bug report"
about: "Create a bug report to help us improve"
title: ""
labels: ["bug"]
assignees: ""
---
<!--
Before posting a bug report, discuss the behaviour you are expecting with the Discord community
to make sure that it is truly a bug.
The issue tracker is not the place to ask for support or how to set up Headscale.
Bug reports without the sufficient information will be closed.
Headscale is a multinational community across the globe. Our language is English.
All bug reports needs to be in English.
-->
## Bug description
<!-- A clear and concise description of what the bug is. Describe the expected bahavior
and how it is currently different. If you are unsure if it is a bug, consider discussing
it on our Discord server first. -->
## Environment
<!-- Please add relevant information about your system. For example:
- Version of headscale used
- Version of tailscale client
- OS (e.g. Linux, Mac, Cygwin, WSL, etc.) and version
- Kernel version
- The relevant config parameters you used
- Log output
-->
- OS:
- Headscale version:
- Tailscale version:
<!--
We do not support running Headscale in a container nor behind a (reverse) proxy.
If either of these are true for your environment, ask the community in Discord
instead of filing a bug report.
-->
- [ ] Headscale is behind a (reverse) proxy
- [ ] Headscale runs in a container
## To Reproduce
<!-- Steps to reproduce the behavior. -->
## Logs and attachments
<!-- Please attach files with:
- Client netmap dump (see below)
- ACL configuration
- Headscale configuration
Dump the netmap of tailscale clients:
`tailscale debug netmap > DESCRIPTIVE_NAME.json`
Please provide information describing the netmap, which client, which headscale version etc.
-->

83
.github/ISSUE_TEMPLATE/bug_report.yaml vendored Normal file
View File

@@ -0,0 +1,83 @@
name: 🐞 Bug
description: File a bug/issue
title: "[Bug] <title>"
labels: ["bug", "needs triage"]
body:
- type: checkboxes
attributes:
label: Is this a support request?
description: This issue tracker is for bugs and feature requests only. If you need help, please use ask in our Discord community
options:
- label: This is not a support request
required: true
- type: checkboxes
attributes:
label: Is there an existing issue for this?
description: Please search to see if an issue already exists for the bug you encountered.
options:
- label: I have searched the existing issues
required: true
- type: textarea
attributes:
label: Current Behavior
description: A concise description of what you're experiencing.
validations:
required: true
- type: textarea
attributes:
label: Expected Behavior
description: A concise description of what you expected to happen.
validations:
required: true
- type: textarea
attributes:
label: Steps To Reproduce
description: Steps to reproduce the behavior.
placeholder: |
1. In this environment...
1. With this config...
1. Run '...'
1. See error...
validations:
required: true
- type: textarea
attributes:
label: Environment
description: |
examples:
- **OS**: Ubuntu 20.04
- **Headscale version**: 0.22.3
- **Tailscale version**: 1.64.0
value: |
- OS:
- Headscale version:
- Tailscale version:
render: markdown
validations:
required: true
- type: checkboxes
attributes:
label: Runtime environment
options:
- label: Headscale is behind a (reverse) proxy
required: false
- label: Headscale runs in a container
required: false
- type: textarea
attributes:
label: Anything else?
description: |
Links? References? Anything that will give us more context about the issue you are encountering!
- Client netmap dump (see below)
- ACL configuration
- Headscale configuration
Dump the netmap of tailscale clients:
`tailscale debug netmap > DESCRIPTIVE_NAME.json`
Please provide information describing the netmap, which client, which headscale version etc.
Tip: You can attach images or log files by clicking this area to highlight it and then dragging files in.
validations:
required: false

View File

@@ -1,26 +0,0 @@
---
name: "Feature request"
about: "Suggest an idea for headscale"
title: ""
labels: ["enhancement"]
assignees: ""
---
<!--
We typically have a clear roadmap for what we want to improve and reserve the right
to close feature requests that does not fit in the roadmap, or fit with the scope
of the project, or we actually want to implement ourselves.
Headscale is a multinational community across the globe. Our language is English.
All bug reports needs to be in English.
-->
## Why
<!-- Include the reason, why you would need the feature. E.g. what problem
does it solve? Or which workflow is currently frustrating and will be improved by
this? -->
## Description
<!-- A clear and precise description of what new or changed feature you want. -->

View File

@@ -0,0 +1,36 @@
name: 🚀 Feature Request
description: Suggest an idea for Headscale
title: "[Feature] <title>"
labels: [enhancement]
body:
- type: textarea
attributes:
label: Use case
description: Please describe the use case for this feature.
placeholder: |
<!-- Include the reason, why you would need the feature. E.g. what problem
does it solve? Or which workflow is currently frustrating and will be improved by
this? -->
validations:
required: true
- type: textarea
attributes:
label: Description
description: A clear and precise description of what new or changed feature you want.
validations:
required: true
- type: checkboxes
attributes:
label: Contribution
description: Are you willing to contribute to the implementation of this feature?
options:
- label: I can write the design doc for this feature
required: false
- label: I can contribute this feature
required: false
- type: textarea
attributes:
label: How can it be implemented?
description: Free text for your ideas on how this feature could be implemented.
validations:
required: false

View File

@@ -12,7 +12,7 @@ If you find mistakes in the documentation, please submit a fix to the documentat
<!-- Please tick if the following things apply. You… -->
- [ ] read the [CONTRIBUTING guidelines](README.md#contributing)
- [ ] have read the [CONTRIBUTING.md](./CONTRIBUTING.md) file
- [ ] raised a GitHub issue or discussed it on the projects chat beforehand
- [ ] added unit tests
- [ ] added integration tests

View File

@@ -13,7 +13,7 @@ concurrency:
cancel-in-progress: true
jobs:
build:
build-nix:
runs-on: ubuntu-latest
permissions: write-all
steps:
@@ -36,7 +36,7 @@ jobs:
- uses: DeterminateSystems/magic-nix-cache-action@main
if: steps.changed-files.outputs.files == 'true'
- name: Run build
- name: Run nix build
id: build
if: steps.changed-files.outputs.files == 'true'
run: |
@@ -69,3 +69,27 @@ jobs:
with:
name: headscale-linux
path: result/bin/headscale
build-cross:
runs-on: ubuntu-latest
strategy:
matrix:
env:
- "GOARCH=arm GOOS=linux GOARM=5"
- "GOARCH=arm GOOS=linux GOARM=6"
- "GOARCH=arm GOOS=linux GOARM=7"
- "GOARCH=arm64 GOOS=linux"
- "GOARCH=386 GOOS=linux"
- "GOARCH=amd64 GOOS=linux"
- "GOARCH=arm64 GOOS=darwin"
- "GOARCH=amd64 GOOS=darwin"
steps:
- uses: actions/checkout@v4
- uses: DeterminateSystems/nix-installer-action@main
- uses: DeterminateSystems/magic-nix-cache-action@main
- name: Run go cross compile
run: env ${{ matrix.env }} nix develop --command -- go build -o "headscale" ./cmd/headscale
- uses: actions/upload-artifact@v4
with:
name: "headscale-${{ matrix.env }}"
path: "headscale"

View File

@@ -32,7 +32,7 @@ jobs:
- name: Generate and check integration tests
if: steps.changed-files.outputs.files == 'true'
run: |
nix develop --command bash -c "cd cmd/gh-action-integration-generator/ && go generate"
nix develop --command bash -c "cd .github/workflows && go generate"
git diff --exit-code .github/workflows/test-integration.yaml
- name: Show missing tests

51
.github/workflows/docs-deploy.yml vendored Normal file
View File

@@ -0,0 +1,51 @@
name: Deploy docs
on:
push:
branches:
# Main branch for development docs
- main
# Doc maintenance branches
- doc/[0-9]+.[0-9]+.[0-9]+
tags:
# Stable release tags
- v[0-9]+.[0-9]+.[0-9]+
paths:
- "docs/**"
- "mkdocs.yml"
workflow_dispatch:
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Install python
uses: actions/setup-python@v5
with:
python-version: 3.x
- name: Setup cache
uses: actions/cache@v4
with:
key: ${{ github.ref }}
path: .cache
- name: Setup dependencies
run: pip install -r docs/requirements.txt
- name: Configure git
run: |
git config user.name github-actions
git config user.email github-actions@github.com
- name: Deploy development docs
if: github.ref == 'refs/heads/main'
run: mike deploy --push development unstable
- name: Deploy stable docs from doc branches
if: startsWith(github.ref, 'refs/heads/doc/')
run: mike deploy --push ${GITHUB_REF_NAME##*/}
- name: Deploy stable docs from tag
if: startsWith(github.ref, 'refs/tags/v')
# This assumes that only newer tags are pushed
run: mike deploy --push --update-aliases ${GITHUB_REF_NAME#v} stable latest

View File

@@ -13,11 +13,11 @@ jobs:
- name: Checkout repository
uses: actions/checkout@v4
- name: Install python
uses: actions/setup-python@v4
uses: actions/setup-python@v5
with:
python-version: 3.x
- name: Setup cache
uses: actions/cache@v2
uses: actions/cache@v4
with:
key: ${{ github.ref }}
path: .cache

View File

@@ -1,52 +0,0 @@
name: Build documentation
on:
push:
branches:
- main
workflow_dispatch:
permissions:
contents: read
pages: write
id-token: write
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Install python
uses: actions/setup-python@v4
with:
python-version: 3.x
- name: Setup cache
uses: actions/cache@v2
with:
key: ${{ github.ref }}
path: .cache
- name: Setup dependencies
run: pip install -r docs/requirements.txt
- name: Build docs
run: mkdocs build --strict
- name: Upload artifact
uses: actions/upload-pages-artifact@v3
with:
path: ./site
deploy:
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
permissions:
pages: write
id-token: write
runs-on: ubuntu-latest
needs: build
steps:
- name: Configure Pages
uses: actions/configure-pages@v4
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v4

View File

@@ -1,6 +1,6 @@
package main
//go:generate go run ./main.go
//go:generate go run ./gh-action-integration-generator.go
import (
"bytes"
@@ -42,15 +42,19 @@ func updateYAML(tests []string) {
testsForYq := fmt.Sprintf("[%s]", strings.Join(tests, ", "))
yqCommand := fmt.Sprintf(
"yq eval '.jobs.integration-test.strategy.matrix.test = %s' ../../.github/workflows/test-integration.yaml -i",
"yq eval '.jobs.integration-test.strategy.matrix.test = %s' ./test-integration.yaml -i",
testsForYq,
)
cmd := exec.Command("bash", "-c", yqCommand)
var out bytes.Buffer
cmd.Stdout = &out
var stdout bytes.Buffer
var stderr bytes.Buffer
cmd.Stdout = &stdout
cmd.Stderr = &stderr
err := cmd.Run()
if err != nil {
log.Printf("stdout: %s", stdout.String())
log.Printf("stderr: %s", stderr.String())
log.Fatalf("failed to run yq command: %s", err)
}

View File

@@ -7,6 +7,7 @@ on:
jobs:
build:
if: github.repository == 'juanfont/headscale'
runs-on: ubuntu-latest
steps:

View File

@@ -31,7 +31,7 @@ jobs:
- name: golangci-lint
if: steps.changed-files.outputs.files == 'true'
run: nix develop --command -- golangci-lint run --new-from-rev=${{github.event.pull_request.base.sha}} --out-format=github-actions .
run: nix develop --command -- golangci-lint run --new-from-rev=${{github.event.pull_request.base.sha}} --out-format=colored-line-number
prettier-lint:
runs-on: ubuntu-latest

View File

@@ -9,6 +9,7 @@ on:
jobs:
goreleaser:
if: github.repository == 'juanfont/headscale'
runs-on: ubuntu-latest
steps:
- name: Checkout

View File

@@ -6,6 +6,7 @@ on:
jobs:
close-issues:
if: github.repository == 'juanfont/headscale'
runs-on: ubuntu-latest
permissions:
issues: write
@@ -20,4 +21,5 @@ jobs:
close-issue-message: "This issue was closed because it has been inactive for 14 days since being marked as stale."
days-before-pr-stale: -1
days-before-pr-close: -1
exempt-issue-labels: "no-stale-bot"
repo-token: ${{ secrets.GITHUB_TOKEN }}

View File

@@ -1,4 +1,7 @@
name: Integration Tests
# To debug locally on a branch, and when needing secrets
# change this to include `push` so the build is ran on
# the main repository.
on: [pull_request]
concurrency:
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
@@ -18,36 +21,52 @@ jobs:
- TestACLNamedHostsCanReachBySubnet
- TestACLNamedHostsCanReach
- TestACLDevice1CanAccessDevice2
- TestPolicyUpdateWhileRunningWithCLIInDatabase
- TestAuthKeyLogoutAndReloginSameUser
- TestAuthKeyLogoutAndReloginNewUser
- TestOIDCAuthenticationPingAll
- TestOIDCExpireNodesBasedOnTokenExpiry
- TestOIDC024UserCreation
- TestOIDCAuthenticationWithPKCE
- TestOIDCReloginSameNodeNewUser
- TestAuthWebFlowAuthenticationPingAll
- TestAuthWebFlowLogoutAndRelogin
- TestUserCommand
- TestPreAuthKeyCommand
- TestPreAuthKeyCommandWithoutExpiry
- TestPreAuthKeyCommandReusableEphemeral
- TestPreAuthKeyCorrectUserLoggedInCommand
- TestApiKeyCommand
- TestNodeTagCommand
- TestNodeAdvertiseTagNoACLCommand
- TestNodeAdvertiseTagWithACLCommand
- TestNodeAdvertiseTagCommand
- TestNodeCommand
- TestNodeExpireCommand
- TestNodeRenameCommand
- TestNodeMoveCommand
- TestPolicyCommand
- TestPolicyBrokenConfigCommand
- TestDERPVerifyEndpoint
- TestResolveMagicDNS
- TestResolveMagicDNSExtraRecordsPath
- TestValidateResolvConf
- TestDERPServerScenario
- TestDERPServerWebsocketScenario
- TestPingAllByIP
- TestPingAllByIPPublicDERP
- TestAuthKeyLogoutAndRelogin
- TestEphemeral
- TestEphemeralInAlternateTimezone
- TestEphemeral2006DeletedTooQuickly
- TestPingAllByHostname
- TestTaildrop
- TestResolveMagicDNS
- TestUpdateHostnameFromClient
- TestExpireNode
- TestNodeOnlineStatus
- TestPingAllByIPManyUpDown
- Test2118DeletingOnlineNodePanics
- TestEnablingRoutes
- TestHASubnetRouterFailover
- TestEnableDisableAutoApprovedRoute
- TestAutoApprovedSubRoute2068
- TestSubnetRouteACL
- TestHeadscale
- TestCreateTailscale
@@ -58,6 +77,16 @@ jobs:
- TestSSHIsBlockedInACL
- TestSSHUserOnlyIsolation
database: [postgres, sqlite]
env:
# Github does not allow us to access secrets in pull requests,
# so this env var is used to check if we have the secret or not.
# If we have the secrets, meaning we are running on push in a fork,
# there might be secrets available for more debugging.
# If TS_OAUTH_CLIENT_ID and TS_OAUTH_SECRET is set, then the job
# will join a debug tailscale network, set up SSH and a tmux session.
# The SSH will be configured to use the SSH key of the Github user
# that triggered the build.
HAS_TAILSCALE_SECRET: ${{ secrets.TS_OAUTH_CLIENT_ID }}
steps:
- uses: actions/checkout@v4
with:
@@ -73,6 +102,16 @@ jobs:
- '**/*.go'
- 'integration_test/'
- 'config-example.yaml'
- name: Tailscale
if: ${{ env.HAS_TAILSCALE_SECRET }}
uses: tailscale/github-action@v2
with:
oauth-client-id: ${{ secrets.TS_OAUTH_CLIENT_ID }}
oauth-secret: ${{ secrets.TS_OAUTH_SECRET }}
tags: tag:gh
- name: Setup SSH server for Actor
if: ${{ env.HAS_TAILSCALE_SECRET }}
uses: alexellis/setup-sshd-actor@master
- uses: DeterminateSystems/nix-installer-action@main
if: steps.changed-files.outputs.files == 'true'
- uses: DeterminateSystems/magic-nix-cache-action@main
@@ -112,3 +151,6 @@ jobs:
with:
name: ${{ matrix.test }}-${{matrix.database}}-pprof
path: "control_logs/*.pprof.tar"
- name: Setup a blocking tmux session
if: ${{ env.HAS_TAILSCALE_SECRET }}
uses: alexellis/block-with-tmux-action@master

View File

@@ -34,4 +34,10 @@ jobs:
- name: Run tests
if: steps.changed-files.outputs.files == 'true'
run: nix develop --check
env:
# As of 2025-01-06, these env vars was not automatically
# set anymore which breaks the initdb for postgres on
# some of the database migration tests.
LC_ALL: "en_US.UTF-8"
LC_CTYPE: "en_US.UTF-8"
run: nix develop --command -- gotestsum

View File

@@ -6,6 +6,7 @@ on:
jobs:
lockfile:
if: github.repository == 'juanfont/headscale'
runs-on: ubuntu-latest
steps:
- name: Checkout repository

1
.gitignore vendored
View File

@@ -22,6 +22,7 @@ dist/
/headscale
config.json
config.yaml
config*.yaml
derp.yaml
*.hujson
*.key

View File

@@ -12,19 +12,13 @@ linters:
disable:
- depguard
- exhaustivestruct
- revive
- lll
- interfacer
- scopelint
- maligned
- golint
- gofmt
- gochecknoglobals
- gochecknoinits
- gocognit
- funlen
- exhaustivestruct
- tagliatelle
- godox
- ireturn
@@ -33,13 +27,7 @@ linters:
- nolintlint
- musttag # causes issues with imported libs
- depguard
# deprecated
- structcheck # replaced by unused
- ifshort # deprecated by the owner
- varcheck # replaced by unused
- nosnakecase # replaced by revive
- deadcode # replaced by unused
- exportloopref
# We should strive to enable these:
- wrapcheck
@@ -69,9 +57,14 @@ linters-settings:
- ok
- c
- tt
- tx
- rx
gocritic:
disabled-checks:
- appendAssign
# TODO(kradalby): Remove this
- ifElseChain
nlreturn:
block-size: 4

View File

@@ -1,4 +1,5 @@
---
version: 2
before:
hooks:
- go mod tidy -compat=1.22
@@ -81,7 +82,9 @@ nfpms:
kos:
- id: ghcr
repository: ghcr.io/juanfont/headscale
repositories:
- ghcr.io/juanfont/headscale
- headscale/headscale
# bare tells KO to only use the repository
# for tagging and naming the container.
@@ -109,33 +112,13 @@ kos:
- '{{ trimprefix .Tag "v" }}'
- "sha-{{ .ShortCommit }}"
- id: dockerhub
build: headscale
base_image: gcr.io/distroless/base-debian12
repository: headscale/headscale
bare: true
platforms:
- linux/amd64
- linux/386
- linux/arm64
- linux/arm/v7
tags:
- "{{ if not .Prerelease }}latest{{ end }}"
- "{{ if not .Prerelease }}{{ .Major }}.{{ .Minor }}.{{ .Patch }}{{ end }}"
- "{{ if not .Prerelease }}{{ .Major }}.{{ .Minor }}{{ end }}"
- "{{ if not .Prerelease }}{{ .Major }}{{ end }}"
- "{{ if not .Prerelease }}v{{ .Major }}.{{ .Minor }}.{{ .Patch }}{{ end }}"
- "{{ if not .Prerelease }}v{{ .Major }}.{{ .Minor }}{{ end }}"
- "{{ if not .Prerelease }}v{{ .Major }}{{ end }}"
- "{{ if not .Prerelease }}stable{{ else }}unstable{{ end }}"
- "{{ .Tag }}"
- '{{ trimprefix .Tag "v" }}'
- "sha-{{ .ShortCommit }}"
- id: ghcr-debug
repository: ghcr.io/juanfont/headscale
repositories:
- ghcr.io/juanfont/headscale
- headscale/headscale
bare: true
base_image: "debian:12"
base_image: gcr.io/distroless/base-debian12:debug
build: headscale
main: ./cmd/headscale
env:
@@ -153,30 +136,7 @@ kos:
- "{{ if not .Prerelease }}v{{ .Major }}.{{ .Minor }}.{{ .Patch }}-debug{{ end }}"
- "{{ if not .Prerelease }}v{{ .Major }}.{{ .Minor }}-debug{{ end }}"
- "{{ if not .Prerelease }}v{{ .Major }}-debug{{ end }}"
- "{{ if not .Prerelease }}stable{{ else }}unstable-debug{{ end }}"
- "{{ .Tag }}-debug"
- '{{ trimprefix .Tag "v" }}-debug'
- "sha-{{ .ShortCommit }}-debug"
- id: dockerhub-debug
build: headscale
base_image: "debian:12"
repository: headscale/headscale
bare: true
platforms:
- linux/amd64
- linux/386
- linux/arm64
- linux/arm/v7
tags:
- "{{ if not .Prerelease }}latest-debug{{ end }}"
- "{{ if not .Prerelease }}{{ .Major }}.{{ .Minor }}.{{ .Patch }}-debug{{ end }}"
- "{{ if not .Prerelease }}{{ .Major }}.{{ .Minor }}-debug{{ end }}"
- "{{ if not .Prerelease }}{{ .Major }}-debug{{ end }}"
- "{{ if not .Prerelease }}v{{ .Major }}.{{ .Minor }}.{{ .Patch }}-debug{{ end }}"
- "{{ if not .Prerelease }}v{{ .Major }}.{{ .Minor }}-debug{{ end }}"
- "{{ if not .Prerelease }}v{{ .Major }}-debug{{ end }}"
- "{{ if not .Prerelease }}stable{{ else }}unstable-debug{{ end }}"
- "{{ if not .Prerelease }}stable-debug{{ else }}unstable-debug{{ end }}"
- "{{ .Tag }}-debug"
- '{{ trimprefix .Tag "v" }}-debug'
- "sha-{{ .ShortCommit }}-debug"
@@ -184,7 +144,7 @@ kos:
checksum:
name_template: "checksums.txt"
snapshot:
name_template: "{{ .Tag }}-next"
version_template: "{{ .Tag }}-next"
changelog:
sort: asc
filters:

View File

@@ -1,6 +1,4 @@
.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
docs/ref/configuration.md
docs/ref/remote-cli.md

File diff suppressed because it is too large Load Diff

View File

@@ -62,7 +62,7 @@ event.
Instances of abusive, harassing, or otherwise unacceptable behavior
may be reported to the community leaders responsible for enforcement
at our Discord channel. All complaints
on our [Discord server](https://discord.gg/c84AZQhmpx). All complaints
will be reviewed and investigated promptly and fairly.
All community leaders are obligated to respect the privacy and

34
CONTRIBUTING.md Normal file
View File

@@ -0,0 +1,34 @@
# Contributing
Headscale is "Open Source, acknowledged contribution", this means that any contribution will have to be discussed with the maintainers before being added to the project.
This model has been chosen to reduce the risk of burnout by limiting the maintenance overhead of reviewing and validating third-party code.
## Why do we have this model?
Headscale has a small maintainer team that tries to balance working on the project, fixing bugs and reviewing contributions.
When we work on issues ourselves, we develop first hand knowledge of the code and it makes it possible for us to maintain and own the code as the project develops.
Code contributions are seen as a positive thing. People enjoy and engage with our project, but it also comes with some challenges; we have to understand the code, we have to understand the feature, we might have to become familiar with external libraries or services and we think about security implications. All those steps are required during the reviewing process. After the code has been merged, the feature has to be maintained. Any changes reliant on external services must be updated and expanded accordingly.
The review and day-1 maintenance adds a significant burden on the maintainers. Often we hope that the contributor will help out, but we found that most of the time, they disappear after their new feature was added.
This means that when someone contributes, we are mostly happy about it, but we do have to run it through a series of checks to establish if we actually can maintain this feature.
## What do we require?
A general description is provided here and an explicit list is provided in our pull request template.
All new features have to start out with a design document, which should be discussed on the issue tracker (not discord). It should include a use case for the feature, how it can be implemented, who will implement it and a plan for maintaining it.
All features have to be end-to-end tested (integration tests) and have good unit test coverage to ensure that they work as expected. This will also ensure that the feature continues to work as expected over time. If a change cannot be tested, a strong case for why this is not possible needs to be presented.
The contributor should help to maintain the feature over time. In case the feature is not maintained probably, the maintainers reserve themselves the right to remove features they redeem as unmaintainable. This should help to improve the quality of the software and keep it in a maintainable state.
## Bug fixes
Headscale is open to code contributions for bug fixes without discussion.
## Documentation
If you find mistakes in the documentation, please submit a fix to the documentation.

19
Dockerfile.derper Normal file
View File

@@ -0,0 +1,19 @@
# For testing purposes only
FROM golang:alpine AS build-env
WORKDIR /go/src
RUN apk add --no-cache git
ARG VERSION_BRANCH=main
RUN git clone https://github.com/tailscale/tailscale.git --branch=$VERSION_BRANCH --depth=1
WORKDIR /go/src/tailscale
ARG TARGETARCH
RUN GOARCH=$TARGETARCH go install -v ./cmd/derper
FROM alpine:3.18
RUN apk add --no-cache ca-certificates iptables iproute2 ip6tables curl
COPY --from=build-env /go/bin/* /usr/local/bin/
ENTRYPOINT [ "/usr/local/bin/derper" ]

View File

@@ -2,30 +2,23 @@
# and are in no way endorsed by Headscale's maintainers as an
# official nor supported release or distribution.
FROM docker.io/golang:1.22-bookworm AS build
FROM docker.io/golang:1.23-bookworm
ARG VERSION=dev
ENV GOPATH /go
WORKDIR /go/src/headscale
RUN apt-get update \
&& apt-get install --no-install-recommends --yes less jq sqlite3 dnsutils \
&& rm -rf /var/lib/apt/lists/* \
&& apt-get clean
RUN mkdir -p /var/run/headscale
COPY go.mod go.sum /go/src/headscale/
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go install -ldflags="-s -w -X github.com/juanfont/headscale/cmd/headscale/cli.Version=$VERSION" -a ./cmd/headscale
RUN test -e /go/bin/headscale
# Debug image
FROM docker.io/golang:1.22-bookworm
COPY --from=build /go/bin/headscale /bin/headscale
ENV TZ UTC
RUN apt-get update \
&& apt-get install --no-install-recommends --yes less jq \
&& rm -rf /var/lib/apt/lists/* \
&& apt-get clean
RUN mkdir -p /var/run/headscale
RUN CGO_ENABLED=0 GOOS=linux go install -ldflags="-s -w -X github.com/juanfont/headscale/cmd/headscale/cli.Version=$VERSION" -a ./cmd/headscale && test -e /go/bin/headscale
# Need to reset the entrypoint or everything will run as a busybox script
ENTRYPOINT []

View File

@@ -1,21 +1,45 @@
# This Dockerfile and the images produced are for testing headscale,
# and are in no way endorsed by Headscale's maintainers as an
# official nor supported release or distribution.
# Copyright (c) Tailscale Inc & AUTHORS
# SPDX-License-Identifier: BSD-3-Clause
FROM golang:latest
# This Dockerfile is more or less lifted from tailscale/tailscale
# to ensure a similar build process when testing the HEAD of tailscale.
RUN apt-get update \
&& apt-get install -y dnsutils git iptables ssh ca-certificates \
&& rm -rf /var/lib/apt/lists/*
FROM golang:1.23-alpine AS build-env
RUN useradd --shell=/bin/bash --create-home ssh-it-user
WORKDIR /go/src
RUN apk add --no-cache git
# Replace `RUN git...` with `COPY` and a local checked out version of Tailscale in `./tailscale`
# to test specific commits of the Tailscale client. This is useful when trying to find out why
# something specific broke between two versions of Tailscale with for example `git bisect`.
# COPY ./tailscale .
RUN git clone https://github.com/tailscale/tailscale.git
WORKDIR /go/tailscale
WORKDIR /go/src/tailscale
RUN git checkout main \
&& sh build_dist.sh tailscale.com/cmd/tailscale \
&& sh build_dist.sh tailscale.com/cmd/tailscaled \
&& cp tailscale /usr/local/bin/ \
&& cp tailscaled /usr/local/bin/
# see build_docker.sh
ARG VERSION_LONG=""
ENV VERSION_LONG=$VERSION_LONG
ARG VERSION_SHORT=""
ENV VERSION_SHORT=$VERSION_SHORT
ARG VERSION_GIT_HASH=""
ENV VERSION_GIT_HASH=$VERSION_GIT_HASH
ARG TARGETARCH
ARG BUILD_TAGS=""
RUN GOARCH=$TARGETARCH go install -tags="${BUILD_TAGS}" -ldflags="\
-X tailscale.com/version.longStamp=$VERSION_LONG \
-X tailscale.com/version.shortStamp=$VERSION_SHORT \
-X tailscale.com/version.gitCommitStamp=$VERSION_GIT_HASH" \
-v ./cmd/tailscale ./cmd/tailscaled ./cmd/containerboot
FROM alpine:3.18
RUN apk add --no-cache ca-certificates iptables iproute2 ip6tables curl
COPY --from=build-env /go/bin/* /usr/local/bin/
# For compat with the previous run.sh, although ideally you should be
# using build_docker.sh which sets an entrypoint for the image.
RUN mkdir /tailscale && ln -s /usr/local/bin/containerboot /tailscale/run.sh

View File

@@ -22,7 +22,7 @@ build:
dev: lint test build
test:
gotestsum -- -short -coverprofile=coverage.out ./...
gotestsum -- -short -race -coverprofile=coverage.out ./...
test_integration:
docker run \
@@ -31,16 +31,27 @@ test_integration:
--name headscale-test-suite \
-v $$PWD:$$PWD -w $$PWD/integration \
-v /var/run/docker.sock:/var/run/docker.sock \
-v $$PWD/control_logs:/tmp/control \
golang:1 \
go run gotest.tools/gotestsum@latest -- -failfast ./... -timeout 120m -parallel 8
go run gotest.tools/gotestsum@latest -- -race -failfast ./... -timeout 120m -parallel 8
lint:
golangci-lint run --fix --timeout 10m
fmt:
fmt: fmt-go fmt-prettier fmt-proto
fmt-prettier:
prettier --write '**/**.{ts,js,md,yaml,yml,sass,css,scss,html}'
golines --max-len=88 --base-formatter=gofumpt -w $(GO_SOURCES)
clang-format -style="{BasedOnStyle: Google, IndentWidth: 4, AlignConsecutiveDeclarations: true, AlignConsecutiveAssignments: true, ColumnLimit: 0}" -i $(PROTO_SOURCES)
prettier --write --print-width 80 --prose-wrap always CHANGELOG.md
fmt-go:
# TODO(kradalby): Reeval if we want to use 88 in the future.
# golines --max-len=88 --base-formatter=gofumpt -w $(GO_SOURCES)
gofumpt -l -w .
golangci-lint run --fix
fmt-proto:
clang-format -i $(PROTO_SOURCES)
proto-lint:
cd proto/ && go run github.com/bufbuild/buf/cmd/buf lint

View File

@@ -4,7 +4,7 @@
An open source, self-hosted implementation of the Tailscale control server.
Join our [Discord](https://discord.gg/c84AZQhmpx) server for a chat.
Join our [Discord server](https://discord.gg/c84AZQhmpx) for a chat.
**Note:** Always select the same GitHub tag as the released version you use
to ensure you have the correct example configuration and documentation.
@@ -32,12 +32,12 @@ organisation.
## Design goal
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.
It implements a narrow scope, a single Tailnet, suitable for a personal use, or a small
open-source organisation.
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 server they can use for
their projects and labs. It implements a narrow scope, a _single_ Tailscale
network (tailnet), suitable for a personal use, or a small open-source
organisation.
## Supporting Headscale
@@ -46,39 +46,18 @@ 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)
- Support for multiple IP ranges in the tailnet
- 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/stable/about/features/).
## Client OS support
| OS | Supports headscale |
| ------- | --------------------------------------------------------- |
| Linux | Yes |
| OpenBSD | Yes |
| FreeBSD | Yes |
| macOS | Yes (see `/apple` on your headscale for more information) |
| Windows | Yes [docs](./docs/windows-client.md) |
| Android | Yes [docs](./docs/android-client.md) |
| iOS | Yes [docs](./docs/iOS-client.md) |
Please see ["Client and operating system support" in the documentation](https://headscale.net/stable/about/clients/).
## Running headscale
**Please note that we do not support nor encourage the use of reverse proxies
and container to run Headscale.**
Please have a look at the [`documentation`](https://headscale.net/).
Please have a look at the [`documentation`](https://headscale.net/stable/).
## Talks
@@ -87,25 +66,20 @@ Please have a look at the [`documentation`](https://headscale.net/).
## Disclaimer
1. This project is not associated with Tailscale Inc.
2. The purpose of Headscale is maintaining a working, self-hosted Tailscale control panel.
This project is not associated with Tailscale Inc.
However, one of the active maintainers for Headscale [is employed by Tailscale](https://tailscale.com/blog/opensource) and he is allowed to spend work hours contributing to the project. Contributions from this maintainer are reviewed by other maintainers.
The maintainers work together on setting the direction for the project. The underlying principle is to serve the community of self-hosters, enthusiasts and hobbyists - while having a sustainable project.
## Contributing
Headscale is "Open Source, acknowledged contribution", this means that any
contribution will have to be discussed with the Maintainers before being submitted.
This model has been chosen to reduce the risk of burnout by limiting the
maintenance overhead of reviewing and validating third-party code.
Headscale is open to code contributions for bug fixes without discussion.
If you find mistakes in the documentation, please submit a fix to the documentation.
Please read the [CONTRIBUTING.md](./CONTRIBUTING.md) file.
### Requirements
To contribute to headscale you would need the lastest version of [Go](https://golang.org)
and [Buf](https://buf.build)(Protobuf generator).
To contribute to headscale you would need the latest version of [Go](https://golang.org)
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.

View File

@@ -54,7 +54,7 @@ var listAPIKeys = &cobra.Command{
Run: func(cmd *cobra.Command, args []string) {
output, _ := cmd.Flags().GetString("output")
ctx, client, conn, cancel := getHeadscaleCLIClient()
ctx, client, conn, cancel := newHeadscaleCLIWithConfig()
defer cancel()
defer conn.Close()
@@ -67,14 +67,10 @@ var listAPIKeys = &cobra.Command{
fmt.Sprintf("Error getting the list of keys: %s", err),
output,
)
return
}
if output != "" {
SuccessOutput(response.GetApiKeys(), "", output)
return
}
tableData := pterm.TableData{
@@ -102,8 +98,6 @@ var listAPIKeys = &cobra.Command{
fmt.Sprintf("Failed to render pterm table: %s", err),
output,
)
return
}
},
}
@@ -119,9 +113,6 @@ If you loose a key, create a new one and revoke (expire) the old one.`,
Run: func(cmd *cobra.Command, args []string) {
output, _ := cmd.Flags().GetString("output")
log.Trace().
Msg("Preparing to create ApiKey")
request := &v1.CreateApiKeyRequest{}
durationStr, _ := cmd.Flags().GetString("expiration")
@@ -133,19 +124,13 @@ If you loose a key, create a new one and revoke (expire) the old one.`,
fmt.Sprintf("Could not parse duration: %s\n", err),
output,
)
return
}
expiration := time.Now().UTC().Add(time.Duration(duration))
log.Trace().
Dur("expiration", time.Duration(duration)).
Msg("expiration has been set")
request.Expiration = timestamppb.New(expiration)
ctx, client, conn, cancel := getHeadscaleCLIClient()
ctx, client, conn, cancel := newHeadscaleCLIWithConfig()
defer cancel()
defer conn.Close()
@@ -156,8 +141,6 @@ If you loose a key, create a new one and revoke (expire) the old one.`,
fmt.Sprintf("Cannot create Api Key: %s\n", err),
output,
)
return
}
SuccessOutput(response.GetApiKey(), response.GetApiKey(), output)
@@ -178,11 +161,9 @@ var expireAPIKeyCmd = &cobra.Command{
fmt.Sprintf("Error getting prefix from CLI flag: %s", err),
output,
)
return
}
ctx, client, conn, cancel := getHeadscaleCLIClient()
ctx, client, conn, cancel := newHeadscaleCLIWithConfig()
defer cancel()
defer conn.Close()
@@ -197,8 +178,6 @@ var expireAPIKeyCmd = &cobra.Command{
fmt.Sprintf("Cannot expire Api Key: %s\n", err),
output,
)
return
}
SuccessOutput(response, "Key expired", output)
@@ -219,11 +198,9 @@ var deleteAPIKeyCmd = &cobra.Command{
fmt.Sprintf("Error getting prefix from CLI flag: %s", err),
output,
)
return
}
ctx, client, conn, cancel := getHeadscaleCLIClient()
ctx, client, conn, cancel := newHeadscaleCLIWithConfig()
defer cancel()
defer conn.Close()
@@ -238,8 +215,6 @@ var deleteAPIKeyCmd = &cobra.Command{
fmt.Sprintf("Cannot delete Api Key: %s\n", err),
output,
)
return
}
SuccessOutput(response, "Key deleted", output)

View File

@@ -14,7 +14,7 @@ var configTestCmd = &cobra.Command{
Short: "Test the configuration.",
Long: "Run a test of the configuration and exit.",
Run: func(cmd *cobra.Command, args []string) {
_, err := getHeadscaleApp()
_, err := newHeadscaleServerWithConfig()
if err != nil {
log.Fatal().Caller().Err(err).Msg("Error initializing")
}

View File

@@ -4,10 +4,10 @@ import (
"fmt"
v1 "github.com/juanfont/headscale/gen/go/headscale/v1"
"github.com/juanfont/headscale/hscontrol/types"
"github.com/rs/zerolog/log"
"github.com/spf13/cobra"
"google.golang.org/grpc/status"
"tailscale.com/types/key"
)
const (
@@ -64,11 +64,9 @@ var createNodeCmd = &cobra.Command{
user, err := cmd.Flags().GetString("user")
if err != nil {
ErrorOutput(err, fmt.Sprintf("Error getting user: %s", err), output)
return
}
ctx, client, conn, cancel := getHeadscaleCLIClient()
ctx, client, conn, cancel := newHeadscaleCLIWithConfig()
defer cancel()
defer conn.Close()
@@ -79,31 +77,24 @@ var createNodeCmd = &cobra.Command{
fmt.Sprintf("Error getting node from flag: %s", err),
output,
)
return
}
machineKey, err := cmd.Flags().GetString("key")
registrationID, err := cmd.Flags().GetString("key")
if err != nil {
ErrorOutput(
err,
fmt.Sprintf("Error getting key from flag: %s", err),
output,
)
return
}
var mkey key.MachinePublic
err = mkey.UnmarshalText([]byte(machineKey))
_, err = types.RegistrationIDFromString(registrationID)
if err != nil {
ErrorOutput(
err,
fmt.Sprintf("Failed to parse machine key from flag: %s", err),
output,
)
return
}
routes, err := cmd.Flags().GetStringSlice("route")
@@ -113,12 +104,10 @@ var createNodeCmd = &cobra.Command{
fmt.Sprintf("Error getting routes from flag: %s", err),
output,
)
return
}
request := &v1.DebugCreateNodeRequest{
Key: machineKey,
Key: registrationID,
Name: name,
User: user,
Routes: routes,
@@ -131,8 +120,6 @@ var createNodeCmd = &cobra.Command{
fmt.Sprintf("Cannot create node: %s", status.Convert(err).Message()),
output,
)
return
}
SuccessOutput(response.GetNode(), "Node created", output)

View File

@@ -1,8 +1,10 @@
package cli
import (
"encoding/json"
"fmt"
"net"
"net/http"
"os"
"strconv"
"time"
@@ -64,6 +66,19 @@ func mockOIDC() error {
accessTTL = newTTL
}
userStr := os.Getenv("MOCKOIDC_USERS")
if userStr == "" {
return fmt.Errorf("MOCKOIDC_USERS not defined")
}
var users []mockoidc.MockUser
err := json.Unmarshal([]byte(userStr), &users)
if err != nil {
return fmt.Errorf("unmarshalling users: %w", err)
}
log.Info().Interface("users", users).Msg("loading users from JSON")
log.Info().Msgf("Access token TTL: %s", accessTTL)
port, err := strconv.Atoi(portStr)
@@ -71,7 +86,7 @@ func mockOIDC() error {
return err
}
mock, err := getMockOIDC(clientID, clientSecret)
mock, err := getMockOIDC(clientID, clientSecret, users)
if err != nil {
return err
}
@@ -93,12 +108,18 @@ func mockOIDC() error {
return nil
}
func getMockOIDC(clientID string, clientSecret string) (*mockoidc.MockOIDC, error) {
func getMockOIDC(clientID string, clientSecret string, users []mockoidc.MockUser) (*mockoidc.MockOIDC, error) {
keypair, err := mockoidc.NewKeypair(nil)
if err != nil {
return nil, err
}
userQueue := mockoidc.UserQueue{}
for _, user := range users {
userQueue.Push(&user)
}
mock := mockoidc.MockOIDC{
ClientID: clientID,
ClientSecret: clientSecret,
@@ -107,9 +128,19 @@ func getMockOIDC(clientID string, clientSecret string) (*mockoidc.MockOIDC, erro
CodeChallengeMethodsSupported: []string{"plain", "S256"},
Keypair: keypair,
SessionStore: mockoidc.NewSessionStore(),
UserQueue: &mockoidc.UserQueue{},
UserQueue: &userQueue,
ErrorQueue: &mockoidc.ErrorQueue{},
}
mock.AddMiddleware(func(h http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
log.Info().Msgf("Request: %+v", r)
h.ServeHTTP(w, r)
if r.Response != nil {
log.Info().Msgf("Response: %+v", r.Response)
}
})
})
return &mock, nil
}

View File

@@ -4,6 +4,7 @@ import (
"fmt"
"log"
"net/netip"
"slices"
"strconv"
"strings"
"time"
@@ -38,33 +39,33 @@ func init() {
err := registerNodeCmd.MarkFlagRequired("user")
if err != nil {
log.Fatalf(err.Error())
log.Fatal(err.Error())
}
registerNodeCmd.Flags().StringP("key", "k", "", "Key")
err = registerNodeCmd.MarkFlagRequired("key")
if err != nil {
log.Fatalf(err.Error())
log.Fatal(err.Error())
}
nodeCmd.AddCommand(registerNodeCmd)
expireNodeCmd.Flags().Uint64P("identifier", "i", 0, "Node identifier (ID)")
err = expireNodeCmd.MarkFlagRequired("identifier")
if err != nil {
log.Fatalf(err.Error())
log.Fatal(err.Error())
}
nodeCmd.AddCommand(expireNodeCmd)
renameNodeCmd.Flags().Uint64P("identifier", "i", 0, "Node identifier (ID)")
err = renameNodeCmd.MarkFlagRequired("identifier")
if err != nil {
log.Fatalf(err.Error())
log.Fatal(err.Error())
}
nodeCmd.AddCommand(renameNodeCmd)
deleteNodeCmd.Flags().Uint64P("identifier", "i", 0, "Node identifier (ID)")
err = deleteNodeCmd.MarkFlagRequired("identifier")
if err != nil {
log.Fatalf(err.Error())
log.Fatal(err.Error())
}
nodeCmd.AddCommand(deleteNodeCmd)
@@ -72,7 +73,7 @@ func init() {
err = moveNodeCmd.MarkFlagRequired("identifier")
if err != nil {
log.Fatalf(err.Error())
log.Fatal(err.Error())
}
moveNodeCmd.Flags().StringP("user", "u", "", "New user")
@@ -84,7 +85,7 @@ func init() {
err = moveNodeCmd.MarkFlagRequired("user")
if err != nil {
log.Fatalf(err.Error())
log.Fatal(err.Error())
}
nodeCmd.AddCommand(moveNodeCmd)
@@ -92,7 +93,7 @@ func init() {
err = tagCmd.MarkFlagRequired("identifier")
if err != nil {
log.Fatalf(err.Error())
log.Fatal(err.Error())
}
tagCmd.Flags().
StringSliceP("tags", "t", []string{}, "List of tags to add to the node")
@@ -115,27 +116,23 @@ var registerNodeCmd = &cobra.Command{
user, err := cmd.Flags().GetString("user")
if err != nil {
ErrorOutput(err, fmt.Sprintf("Error getting user: %s", err), output)
return
}
ctx, client, conn, cancel := getHeadscaleCLIClient()
ctx, client, conn, cancel := newHeadscaleCLIWithConfig()
defer cancel()
defer conn.Close()
machineKey, err := cmd.Flags().GetString("key")
registrationID, err := cmd.Flags().GetString("key")
if err != nil {
ErrorOutput(
err,
fmt.Sprintf("Error getting node key from flag: %s", err),
output,
)
return
}
request := &v1.RegisterNodeRequest{
Key: machineKey,
Key: registrationID,
User: user,
}
@@ -149,8 +146,6 @@ var registerNodeCmd = &cobra.Command{
),
output,
)
return
}
SuccessOutput(
@@ -168,17 +163,13 @@ var listNodesCmd = &cobra.Command{
user, err := cmd.Flags().GetString("user")
if err != nil {
ErrorOutput(err, fmt.Sprintf("Error getting user: %s", err), output)
return
}
showTags, err := cmd.Flags().GetBool("tags")
if err != nil {
ErrorOutput(err, fmt.Sprintf("Error getting tags flag: %s", err), output)
return
}
ctx, client, conn, cancel := getHeadscaleCLIClient()
ctx, client, conn, cancel := newHeadscaleCLIWithConfig()
defer cancel()
defer conn.Close()
@@ -193,21 +184,15 @@ var listNodesCmd = &cobra.Command{
fmt.Sprintf("Cannot get nodes: %s", status.Convert(err).Message()),
output,
)
return
}
if output != "" {
SuccessOutput(response.GetNodes(), "", output)
return
}
tableData, err := nodesToPtables(user, showTags, response.GetNodes())
if err != nil {
ErrorOutput(err, fmt.Sprintf("Error converting to table: %s", err), output)
return
}
err = pterm.DefaultTable.WithHasHeader().WithData(tableData).Render()
@@ -217,8 +202,6 @@ var listNodesCmd = &cobra.Command{
fmt.Sprintf("Failed to render pterm table: %s", err),
output,
)
return
}
},
}
@@ -242,7 +225,7 @@ var expireNodeCmd = &cobra.Command{
return
}
ctx, client, conn, cancel := getHeadscaleCLIClient()
ctx, client, conn, cancel := newHeadscaleCLIWithConfig()
defer cancel()
defer conn.Close()
@@ -285,7 +268,7 @@ var renameNodeCmd = &cobra.Command{
return
}
ctx, client, conn, cancel := getHeadscaleCLIClient()
ctx, client, conn, cancel := newHeadscaleCLIWithConfig()
defer cancel()
defer conn.Close()
@@ -334,7 +317,7 @@ var deleteNodeCmd = &cobra.Command{
return
}
ctx, client, conn, cancel := getHeadscaleCLIClient()
ctx, client, conn, cancel := newHeadscaleCLIWithConfig()
defer cancel()
defer conn.Close()
@@ -434,7 +417,7 @@ var moveNodeCmd = &cobra.Command{
return
}
ctx, client, conn, cancel := getHeadscaleCLIClient()
ctx, client, conn, cancel := newHeadscaleCLIWithConfig()
defer cancel()
defer conn.Close()
@@ -507,7 +490,7 @@ be assigned to nodes.`,
return
}
if confirm {
ctx, client, conn, cancel := getHeadscaleCLIClient()
ctx, client, conn, cancel := newHeadscaleCLIWithConfig()
defer cancel()
defer conn.Close()
@@ -617,14 +600,14 @@ func nodesToPtables(
forcedTags = strings.TrimLeft(forcedTags, ",")
var invalidTags string
for _, tag := range node.GetInvalidTags() {
if !contains(node.GetForcedTags(), tag) {
if !slices.Contains(node.GetForcedTags(), tag) {
invalidTags += "," + pterm.LightRed(tag)
}
}
invalidTags = strings.TrimLeft(invalidTags, ",")
var validTags string
for _, tag := range node.GetValidTags() {
if !contains(node.GetForcedTags(), tag) {
if !slices.Contains(node.GetForcedTags(), tag) {
validTags += "," + pterm.LightGreen(tag)
}
}
@@ -680,7 +663,7 @@ var tagCmd = &cobra.Command{
Aliases: []string{"tags", "t"},
Run: func(cmd *cobra.Command, args []string) {
output, _ := cmd.Flags().GetString("output")
ctx, client, conn, cancel := getHeadscaleCLIClient()
ctx, client, conn, cancel := newHeadscaleCLIWithConfig()
defer cancel()
defer conn.Close()

View File

@@ -0,0 +1,87 @@
package cli
import (
"fmt"
"io"
"os"
v1 "github.com/juanfont/headscale/gen/go/headscale/v1"
"github.com/rs/zerolog/log"
"github.com/spf13/cobra"
)
func init() {
rootCmd.AddCommand(policyCmd)
policyCmd.AddCommand(getPolicy)
setPolicy.Flags().StringP("file", "f", "", "Path to a policy file in HuJSON format")
if err := setPolicy.MarkFlagRequired("file"); err != nil {
log.Fatal().Err(err).Msg("")
}
policyCmd.AddCommand(setPolicy)
}
var policyCmd = &cobra.Command{
Use: "policy",
Short: "Manage the Headscale ACL Policy",
}
var getPolicy = &cobra.Command{
Use: "get",
Short: "Print the current ACL Policy",
Aliases: []string{"show", "view", "fetch"},
Run: func(cmd *cobra.Command, args []string) {
output, _ := cmd.Flags().GetString("output")
ctx, client, conn, cancel := newHeadscaleCLIWithConfig()
defer cancel()
defer conn.Close()
request := &v1.GetPolicyRequest{}
response, err := client.GetPolicy(ctx, request)
if err != nil {
ErrorOutput(err, fmt.Sprintf("Failed loading ACL Policy: %s", err), output)
}
// TODO(pallabpain): Maybe print this better?
// This does not pass output as we dont support yaml, json or json-line
// output for this command. It is HuJSON already.
SuccessOutput("", response.GetPolicy(), "")
},
}
var setPolicy = &cobra.Command{
Use: "set",
Short: "Updates the ACL Policy",
Long: `
Updates the existing ACL Policy with the provided policy. The policy must be a valid HuJSON object.
This command only works when the acl.policy_mode is set to "db", and the policy will be stored in the database.`,
Aliases: []string{"put", "update"},
Run: func(cmd *cobra.Command, args []string) {
output, _ := cmd.Flags().GetString("output")
policyPath, _ := cmd.Flags().GetString("file")
f, err := os.Open(policyPath)
if err != nil {
ErrorOutput(err, fmt.Sprintf("Error opening the policy file: %s", err), output)
}
defer f.Close()
policyBytes, err := io.ReadAll(f)
if err != nil {
ErrorOutput(err, fmt.Sprintf("Error reading the policy file: %s", err), output)
}
request := &v1.SetPolicyRequest{Policy: string(policyBytes)}
ctx, client, conn, cancel := newHeadscaleCLIWithConfig()
defer cancel()
defer conn.Close()
if _, err := client.SetPolicy(ctx, request); err != nil {
ErrorOutput(err, fmt.Sprintf("Failed to set ACL Policy: %s", err), output)
}
SuccessOutput(nil, "Policy updated.", "")
},
}

View File

@@ -60,11 +60,9 @@ var listPreAuthKeys = &cobra.Command{
user, err := cmd.Flags().GetString("user")
if err != nil {
ErrorOutput(err, fmt.Sprintf("Error getting user: %s", err), output)
return
}
ctx, client, conn, cancel := getHeadscaleCLIClient()
ctx, client, conn, cancel := newHeadscaleCLIWithConfig()
defer cancel()
defer conn.Close()
@@ -85,8 +83,6 @@ var listPreAuthKeys = &cobra.Command{
if output != "" {
SuccessOutput(response.GetPreAuthKeys(), "", output)
return
}
tableData := pterm.TableData{
@@ -134,8 +130,6 @@ var listPreAuthKeys = &cobra.Command{
fmt.Sprintf("Failed to render pterm table: %s", err),
output,
)
return
}
},
}
@@ -150,20 +144,12 @@ var createPreAuthKeyCmd = &cobra.Command{
user, err := cmd.Flags().GetString("user")
if err != nil {
ErrorOutput(err, fmt.Sprintf("Error getting user: %s", err), output)
return
}
reusable, _ := cmd.Flags().GetBool("reusable")
ephemeral, _ := cmd.Flags().GetBool("ephemeral")
tags, _ := cmd.Flags().GetStringSlice("tags")
log.Trace().
Bool("reusable", reusable).
Bool("ephemeral", ephemeral).
Str("user", user).
Msg("Preparing to create preauthkey")
request := &v1.CreatePreAuthKeyRequest{
User: user,
Reusable: reusable,
@@ -180,8 +166,6 @@ var createPreAuthKeyCmd = &cobra.Command{
fmt.Sprintf("Could not parse duration: %s\n", err),
output,
)
return
}
expiration := time.Now().UTC().Add(time.Duration(duration))
@@ -192,7 +176,7 @@ var createPreAuthKeyCmd = &cobra.Command{
request.Expiration = timestamppb.New(expiration)
ctx, client, conn, cancel := getHeadscaleCLIClient()
ctx, client, conn, cancel := newHeadscaleCLIWithConfig()
defer cancel()
defer conn.Close()
@@ -203,8 +187,6 @@ var createPreAuthKeyCmd = &cobra.Command{
fmt.Sprintf("Cannot create Pre Auth Key: %s\n", err),
output,
)
return
}
SuccessOutput(response.GetPreAuthKey(), response.GetPreAuthKey().GetKey(), output)
@@ -227,11 +209,9 @@ var expirePreAuthKeyCmd = &cobra.Command{
user, err := cmd.Flags().GetString("user")
if err != nil {
ErrorOutput(err, fmt.Sprintf("Error getting user: %s", err), output)
return
}
ctx, client, conn, cancel := getHeadscaleCLIClient()
ctx, client, conn, cancel := newHeadscaleCLIWithConfig()
defer cancel()
defer conn.Close()
@@ -247,8 +227,6 @@ var expirePreAuthKeyCmd = &cobra.Command{
fmt.Sprintf("Cannot expire Pre Auth Key: %s\n", err),
output,
)
return
}
SuccessOutput(response, "Key expired", output)

View File

@@ -9,6 +9,7 @@ import (
"github.com/rs/zerolog"
"github.com/rs/zerolog/log"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"github.com/tcnksm/go-latest"
)
@@ -49,26 +50,21 @@ func initConfig() {
}
}
cfg, err := types.GetHeadscaleConfig()
if err != nil {
log.Fatal().Caller().Err(err).Msg("Failed to get headscale configuration")
}
machineOutput := HasMachineOutputFlag()
zerolog.SetGlobalLevel(cfg.Log.Level)
// If the user has requested a "node" readable format,
// then disable login so the output remains valid.
if machineOutput {
zerolog.SetGlobalLevel(zerolog.Disabled)
}
if cfg.Log.Format == types.JSONLogFormat {
log.Logger = log.Output(os.Stdout)
}
// logFormat := viper.GetString("log.format")
// if logFormat == types.JSONLogFormat {
// log.Logger = log.Output(os.Stdout)
// }
if !cfg.DisableUpdateCheck && !machineOutput {
disableUpdateCheck := viper.GetBool("disable_check_updates")
if !disableUpdateCheck && !machineOutput {
if (runtime.GOOS == "linux" || runtime.GOOS == "darwin") &&
Version != "dev" {
githubTag := &latest.GithubTag{

View File

@@ -7,10 +7,10 @@ import (
"strconv"
v1 "github.com/juanfont/headscale/gen/go/headscale/v1"
"github.com/juanfont/headscale/hscontrol/types"
"github.com/pterm/pterm"
"github.com/spf13/cobra"
"google.golang.org/grpc/status"
"tailscale.com/net/tsaddr"
)
const (
@@ -25,21 +25,21 @@ func init() {
enableRouteCmd.Flags().Uint64P("route", "r", 0, "Route identifier (ID)")
err := enableRouteCmd.MarkFlagRequired("route")
if err != nil {
log.Fatalf(err.Error())
log.Fatal(err.Error())
}
routesCmd.AddCommand(enableRouteCmd)
disableRouteCmd.Flags().Uint64P("route", "r", 0, "Route identifier (ID)")
err = disableRouteCmd.MarkFlagRequired("route")
if err != nil {
log.Fatalf(err.Error())
log.Fatal(err.Error())
}
routesCmd.AddCommand(disableRouteCmd)
deleteRouteCmd.Flags().Uint64P("route", "r", 0, "Route identifier (ID)")
err = deleteRouteCmd.MarkFlagRequired("route")
if err != nil {
log.Fatalf(err.Error())
log.Fatal(err.Error())
}
routesCmd.AddCommand(deleteRouteCmd)
}
@@ -64,11 +64,9 @@ var listRoutesCmd = &cobra.Command{
fmt.Sprintf("Error getting machine id from flag: %s", err),
output,
)
return
}
ctx, client, conn, cancel := getHeadscaleCLIClient()
ctx, client, conn, cancel := newHeadscaleCLIWithConfig()
defer cancel()
defer conn.Close()
@@ -82,14 +80,10 @@ var listRoutesCmd = &cobra.Command{
fmt.Sprintf("Cannot get nodes: %s", status.Convert(err).Message()),
output,
)
return
}
if output != "" {
SuccessOutput(response.GetRoutes(), "", output)
return
}
routes = response.GetRoutes()
@@ -103,14 +97,10 @@ var listRoutesCmd = &cobra.Command{
fmt.Sprintf("Cannot get routes for node %d: %s", machineID, status.Convert(err).Message()),
output,
)
return
}
if output != "" {
SuccessOutput(response.GetRoutes(), "", output)
return
}
routes = response.GetRoutes()
@@ -119,8 +109,6 @@ var listRoutesCmd = &cobra.Command{
tableData := routesToPtables(routes)
if err != nil {
ErrorOutput(err, fmt.Sprintf("Error converting to table: %s", err), output)
return
}
err = pterm.DefaultTable.WithHasHeader().WithData(tableData).Render()
@@ -130,8 +118,6 @@ var listRoutesCmd = &cobra.Command{
fmt.Sprintf("Failed to render pterm table: %s", err),
output,
)
return
}
},
}
@@ -150,11 +136,9 @@ var enableRouteCmd = &cobra.Command{
fmt.Sprintf("Error getting machine id from flag: %s", err),
output,
)
return
}
ctx, client, conn, cancel := getHeadscaleCLIClient()
ctx, client, conn, cancel := newHeadscaleCLIWithConfig()
defer cancel()
defer conn.Close()
@@ -167,14 +151,10 @@ var enableRouteCmd = &cobra.Command{
fmt.Sprintf("Cannot enable route %d: %s", routeID, status.Convert(err).Message()),
output,
)
return
}
if output != "" {
SuccessOutput(response, "", output)
return
}
},
}
@@ -193,11 +173,9 @@ var disableRouteCmd = &cobra.Command{
fmt.Sprintf("Error getting machine id from flag: %s", err),
output,
)
return
}
ctx, client, conn, cancel := getHeadscaleCLIClient()
ctx, client, conn, cancel := newHeadscaleCLIWithConfig()
defer cancel()
defer conn.Close()
@@ -210,14 +188,10 @@ var disableRouteCmd = &cobra.Command{
fmt.Sprintf("Cannot disable route %d: %s", routeID, status.Convert(err).Message()),
output,
)
return
}
if output != "" {
SuccessOutput(response, "", output)
return
}
},
}
@@ -236,11 +210,9 @@ var deleteRouteCmd = &cobra.Command{
fmt.Sprintf("Error getting machine id from flag: %s", err),
output,
)
return
}
ctx, client, conn, cancel := getHeadscaleCLIClient()
ctx, client, conn, cancel := newHeadscaleCLIWithConfig()
defer cancel()
defer conn.Close()
@@ -253,14 +225,10 @@ var deleteRouteCmd = &cobra.Command{
fmt.Sprintf("Cannot delete route %d: %s", routeID, status.Convert(err).Message()),
output,
)
return
}
if output != "" {
SuccessOutput(response, "", output)
return
}
},
}
@@ -277,16 +245,21 @@ func routesToPtables(routes []*v1.Route) pterm.TableData {
continue
}
if prefix == types.ExitRouteV4 || prefix == types.ExitRouteV6 {
if tsaddr.IsExitRoute(prefix) {
isPrimaryStr = "-"
} else {
isPrimaryStr = strconv.FormatBool(route.GetIsPrimary())
}
var nodeName string
if route.GetNode() != nil {
nodeName = route.GetNode().GetGivenName()
}
tableData = append(tableData,
[]string{
strconv.FormatUint(route.GetId(), Base10),
route.GetNode().GetGivenName(),
nodeName,
route.GetPrefix(),
strconv.FormatBool(route.GetAdvertised()),
strconv.FormatBool(route.GetEnabled()),

View File

@@ -1,6 +1,9 @@
package cli
import (
"errors"
"net/http"
"github.com/rs/zerolog/log"
"github.com/spf13/cobra"
)
@@ -16,14 +19,14 @@ var serveCmd = &cobra.Command{
return nil
},
Run: func(cmd *cobra.Command, args []string) {
app, err := getHeadscaleApp()
app, err := newHeadscaleServerWithConfig()
if err != nil {
log.Fatal().Caller().Err(err).Msg("Error initializing")
}
err = app.Serve()
if err != nil {
log.Fatal().Caller().Err(err).Msg("Error starting server")
if err != nil && !errors.Is(err, http.ErrServerClosed) {
log.Fatal().Caller().Err(err).Msg("Headscale ran into an error and had to shut down.")
}
},
}

View File

@@ -3,6 +3,7 @@ package cli
import (
"errors"
"fmt"
"net/url"
survey "github.com/AlecAivazis/survey/v2"
v1 "github.com/juanfont/headscale/gen/go/headscale/v1"
@@ -12,12 +13,46 @@ import (
"google.golang.org/grpc/status"
)
func usernameAndIDFlag(cmd *cobra.Command) {
cmd.Flags().Int64P("identifier", "i", -1, "User identifier (ID)")
cmd.Flags().StringP("name", "n", "", "Username")
}
// usernameAndIDFromFlag returns the username and ID from the flags of the command.
// If both are empty, it will exit the program with an error.
func usernameAndIDFromFlag(cmd *cobra.Command) (uint64, string) {
username, _ := cmd.Flags().GetString("name")
identifier, _ := cmd.Flags().GetInt64("identifier")
if username == "" && identifier < 0 {
err := errors.New("--name or --identifier flag is required")
ErrorOutput(
err,
fmt.Sprintf(
"Cannot rename user: %s",
status.Convert(err).Message(),
),
"",
)
}
return uint64(identifier), username
}
func init() {
rootCmd.AddCommand(userCmd)
userCmd.AddCommand(createUserCmd)
createUserCmd.Flags().StringP("display-name", "d", "", "Display name")
createUserCmd.Flags().StringP("email", "e", "", "Email")
createUserCmd.Flags().StringP("picture-url", "p", "", "Profile picture URL")
userCmd.AddCommand(listUsersCmd)
usernameAndIDFlag(listUsersCmd)
listUsersCmd.Flags().StringP("email", "e", "", "Email")
userCmd.AddCommand(destroyUserCmd)
usernameAndIDFlag(destroyUserCmd)
userCmd.AddCommand(renameUserCmd)
usernameAndIDFlag(renameUserCmd)
renameUserCmd.Flags().StringP("new-name", "r", "", "New username")
renameNodeCmd.MarkFlagRequired("new-name")
}
var errMissingParameter = errors.New("missing parameters")
@@ -44,7 +79,7 @@ var createUserCmd = &cobra.Command{
userName := args[0]
ctx, client, conn, cancel := getHeadscaleCLIClient()
ctx, client, conn, cancel := newHeadscaleCLIWithConfig()
defer cancel()
defer conn.Close()
@@ -52,6 +87,28 @@ var createUserCmd = &cobra.Command{
request := &v1.CreateUserRequest{Name: userName}
if displayName, _ := cmd.Flags().GetString("display-name"); displayName != "" {
request.DisplayName = displayName
}
if email, _ := cmd.Flags().GetString("email"); email != "" {
request.Email = email
}
if pictureURL, _ := cmd.Flags().GetString("picture-url"); pictureURL != "" {
if _, err := url.Parse(pictureURL); err != nil {
ErrorOutput(
err,
fmt.Sprintf(
"Invalid Picture URL: %s",
err,
),
output,
)
}
request.PictureUrl = pictureURL
}
log.Trace().Interface("request", request).Msg("Sending CreateUser request")
response, err := client.CreateUser(ctx, request)
if err != nil {
@@ -63,8 +120,6 @@ var createUserCmd = &cobra.Command{
),
output,
)
return
}
SuccessOutput(response.GetUser(), "User created", output)
@@ -72,47 +127,49 @@ var createUserCmd = &cobra.Command{
}
var destroyUserCmd = &cobra.Command{
Use: "destroy NAME",
Use: "destroy --identifier ID or --name NAME",
Short: "Destroys a user",
Aliases: []string{"delete"},
Args: func(cmd *cobra.Command, args []string) error {
if len(args) < 1 {
return errMissingParameter
}
return nil
},
Run: func(cmd *cobra.Command, args []string) {
output, _ := cmd.Flags().GetString("output")
userName := args[0]
request := &v1.GetUserRequest{
Name: userName,
id, username := usernameAndIDFromFlag(cmd)
request := &v1.ListUsersRequest{
Name: username,
Id: id,
}
ctx, client, conn, cancel := getHeadscaleCLIClient()
ctx, client, conn, cancel := newHeadscaleCLIWithConfig()
defer cancel()
defer conn.Close()
_, err := client.GetUser(ctx, request)
users, err := client.ListUsers(ctx, request)
if err != nil {
ErrorOutput(
err,
fmt.Sprintf("Error: %s", status.Convert(err).Message()),
output,
)
return
}
if len(users.GetUsers()) != 1 {
err := fmt.Errorf("Unable to determine user to delete, query returned multiple users, use ID")
ErrorOutput(
err,
fmt.Sprintf("Error: %s", status.Convert(err).Message()),
output,
)
}
user := users.GetUsers()[0]
confirm := false
force, _ := cmd.Flags().GetBool("force")
if !force {
prompt := &survey.Confirm{
Message: fmt.Sprintf(
"Do you want to remove the user '%s' and any associated preauthkeys?",
userName,
"Do you want to remove the user %q (%d) and any associated preauthkeys?",
user.GetName(), user.GetId(),
),
}
err := survey.AskOne(prompt, &confirm)
@@ -122,7 +179,7 @@ var destroyUserCmd = &cobra.Command{
}
if confirm || force {
request := &v1.DeleteUserRequest{Name: userName}
request := &v1.DeleteUserRequest{Id: user.GetId()}
response, err := client.DeleteUser(ctx, request)
if err != nil {
@@ -134,8 +191,6 @@ var destroyUserCmd = &cobra.Command{
),
output,
)
return
}
SuccessOutput(response, "User destroyed", output)
} else {
@@ -151,12 +206,29 @@ var listUsersCmd = &cobra.Command{
Run: func(cmd *cobra.Command, args []string) {
output, _ := cmd.Flags().GetString("output")
ctx, client, conn, cancel := getHeadscaleCLIClient()
ctx, client, conn, cancel := newHeadscaleCLIWithConfig()
defer cancel()
defer conn.Close()
request := &v1.ListUsersRequest{}
id, _ := cmd.Flags().GetInt64("identifier")
username, _ := cmd.Flags().GetString("name")
email, _ := cmd.Flags().GetString("email")
// filter by one param at most
switch {
case id > 0:
request.Id = uint64(id)
break
case username != "":
request.Name = username
break
case email != "":
request.Email = email
break
}
response, err := client.ListUsers(ctx, request)
if err != nil {
ErrorOutput(
@@ -164,23 +236,21 @@ var listUsersCmd = &cobra.Command{
fmt.Sprintf("Cannot get users: %s", status.Convert(err).Message()),
output,
)
return
}
if output != "" {
SuccessOutput(response.GetUsers(), "", output)
return
}
tableData := pterm.TableData{{"ID", "Name", "Created"}}
tableData := pterm.TableData{{"ID", "Name", "Username", "Email", "Created"}}
for _, user := range response.GetUsers() {
tableData = append(
tableData,
[]string{
user.GetId(),
fmt.Sprintf("%d", user.GetId()),
user.GetDisplayName(),
user.GetName(),
user.GetEmail(),
user.GetCreatedAt().AsTime().Format("2006-01-02 15:04:05"),
},
)
@@ -192,37 +262,53 @@ var listUsersCmd = &cobra.Command{
fmt.Sprintf("Failed to render pterm table: %s", err),
output,
)
return
}
},
}
var renameUserCmd = &cobra.Command{
Use: "rename OLD_NAME NEW_NAME",
Use: "rename",
Short: "Renames a user",
Aliases: []string{"mv"},
Args: func(cmd *cobra.Command, args []string) error {
expectedArguments := 2
if len(args) < expectedArguments {
return errMissingParameter
}
return nil
},
Run: func(cmd *cobra.Command, args []string) {
output, _ := cmd.Flags().GetString("output")
ctx, client, conn, cancel := getHeadscaleCLIClient()
ctx, client, conn, cancel := newHeadscaleCLIWithConfig()
defer cancel()
defer conn.Close()
request := &v1.RenameUserRequest{
OldName: args[0],
NewName: args[1],
id, username := usernameAndIDFromFlag(cmd)
listReq := &v1.ListUsersRequest{
Name: username,
Id: id,
}
response, err := client.RenameUser(ctx, request)
users, err := client.ListUsers(ctx, listReq)
if err != nil {
ErrorOutput(
err,
fmt.Sprintf("Error: %s", status.Convert(err).Message()),
output,
)
}
if len(users.GetUsers()) != 1 {
err := fmt.Errorf("Unable to determine user to delete, query returned multiple users, use ID")
ErrorOutput(
err,
fmt.Sprintf("Error: %s", status.Convert(err).Message()),
output,
)
}
newName, _ := cmd.Flags().GetString("new-name")
renameReq := &v1.RenameUserRequest{
OldId: id,
NewName: newName,
}
response, err := client.RenameUser(ctx, renameReq)
if err != nil {
ErrorOutput(
err,
@@ -232,8 +318,6 @@ var renameUserCmd = &cobra.Command{
),
output,
)
return
}
SuccessOutput(response.GetUser(), "User renamed", output)

View File

@@ -6,11 +6,9 @@ import (
"encoding/json"
"fmt"
"os"
"reflect"
v1 "github.com/juanfont/headscale/gen/go/headscale/v1"
"github.com/juanfont/headscale/hscontrol"
"github.com/juanfont/headscale/hscontrol/policy"
"github.com/juanfont/headscale/hscontrol/types"
"github.com/juanfont/headscale/hscontrol/util"
"github.com/rs/zerolog/log"
@@ -25,8 +23,8 @@ const (
SocketWritePermissions = 0o666
)
func getHeadscaleApp() (*hscontrol.Headscale, error) {
cfg, err := types.GetHeadscaleConfig()
func newHeadscaleServerWithConfig() (*hscontrol.Headscale, error) {
cfg, err := types.LoadServerConfig()
if err != nil {
return nil, fmt.Errorf(
"failed to load configuration while creating headscale instance: %w",
@@ -39,26 +37,11 @@ func getHeadscaleApp() (*hscontrol.Headscale, error) {
return nil, err
}
// We are doing this here, as in the future could be cool to have it also hot-reload
if cfg.ACL.PolicyPath != "" {
aclPath := util.AbsolutePathFromConfigPath(cfg.ACL.PolicyPath)
pol, err := policy.LoadACLPolicyFromPath(aclPath)
if err != nil {
log.Fatal().
Str("path", aclPath).
Err(err).
Msg("Could not load the ACL policy")
}
app.ACLPolicy = pol
}
return app, nil
}
func getHeadscaleCLIClient() (context.Context, v1.HeadscaleServiceClient, *grpc.ClientConn, context.CancelFunc) {
cfg, err := types.GetHeadscaleConfig()
func newHeadscaleCLIWithConfig() (context.Context, v1.HeadscaleServiceClient, *grpc.ClientConn, context.CancelFunc) {
cfg, err := types.LoadCLIConfig()
if err != nil {
log.Fatal().
Err(err).
@@ -89,7 +72,7 @@ func getHeadscaleCLIClient() (context.Context, v1.HeadscaleServiceClient, *grpc.
// Try to give the user better feedback if we cannot write to the headscale
// socket.
socket, err := os.OpenFile(cfg.UnixSocket, os.O_WRONLY, SocketWritePermissions) //nolint
socket, err := os.OpenFile(cfg.UnixSocket, os.O_WRONLY, SocketWritePermissions) // nolint
if err != nil {
if os.IsPermission(err) {
log.Fatal().
@@ -147,7 +130,7 @@ func getHeadscaleCLIClient() (context.Context, v1.HeadscaleServiceClient, *grpc.
return ctx, client, conn, cancel
}
func SuccessOutput(result interface{}, override string, outputFormat string) {
func output(result interface{}, override string, outputFormat string) string {
var jsonBytes []byte
var err error
switch outputFormat {
@@ -167,22 +150,27 @@ func SuccessOutput(result interface{}, override string, outputFormat string) {
log.Fatal().Err(err).Msg("failed to unmarshal output")
}
default:
//nolint
fmt.Println(override)
return
// nolint
return override
}
//nolint
fmt.Println(string(jsonBytes))
return string(jsonBytes)
}
// SuccessOutput prints the result to stdout and exits with status code 0.
func SuccessOutput(result interface{}, override string, outputFormat string) {
fmt.Println(output(result, override, outputFormat))
os.Exit(0)
}
// ErrorOutput prints an error message to stderr and exits with status code 1.
func ErrorOutput(errResult error, override string, outputFormat string) {
type errOutput struct {
Error string `json:"error"`
}
SuccessOutput(errOutput{errResult.Error()}, override, outputFormat)
fmt.Fprintf(os.Stderr, "%s\n", output(errOutput{errResult.Error()}, override, outputFormat))
os.Exit(1)
}
func HasMachineOutputFlag() bool {
@@ -212,13 +200,3 @@ func (t tokenAuth) GetRequestMetadata(
func (tokenAuth) RequireTransportSecurity() bool {
return true
}
func contains[T string](ts []T, t T) bool {
for _, v := range ts {
if reflect.DeepEqual(v, t) {
return true
}
}
return false
}

View File

@@ -4,7 +4,6 @@ import (
"io/fs"
"os"
"path/filepath"
"strings"
"testing"
"github.com/juanfont/headscale/hscontrol/types"
@@ -63,7 +62,6 @@ func (*Suite) TestConfigFileLoading(c *check.C) {
c.Assert(viper.GetString("tls_letsencrypt_hostname"), check.Equals, "")
c.Assert(viper.GetString("tls_letsencrypt_listen"), check.Equals, ":http")
c.Assert(viper.GetString("tls_letsencrypt_challenge_type"), check.Equals, "HTTP-01")
c.Assert(viper.GetStringSlice("dns_config.nameservers")[0], check.Equals, "1.1.1.1")
c.Assert(
util.GetFileMode("unix_socket_permission"),
check.Equals,
@@ -106,7 +104,6 @@ func (*Suite) TestConfigLoading(c *check.C) {
c.Assert(viper.GetString("tls_letsencrypt_hostname"), check.Equals, "")
c.Assert(viper.GetString("tls_letsencrypt_listen"), check.Equals, ":http")
c.Assert(viper.GetString("tls_letsencrypt_challenge_type"), check.Equals, "HTTP-01")
c.Assert(viper.GetStringSlice("dns_config.nameservers")[0], check.Equals, "1.1.1.1")
c.Assert(
util.GetFileMode("unix_socket_permission"),
check.Equals,
@@ -115,93 +112,3 @@ func (*Suite) TestConfigLoading(c *check.C) {
c.Assert(viper.GetBool("logtail.enabled"), check.Equals, false)
c.Assert(viper.GetBool("randomize_client_port"), check.Equals, false)
}
func (*Suite) TestDNSConfigLoading(c *check.C) {
tmpDir, err := os.MkdirTemp("", "headscale")
if err != nil {
c.Fatal(err)
}
defer os.RemoveAll(tmpDir)
path, err := os.Getwd()
if err != nil {
c.Fatal(err)
}
// Symlink the example config file
err = os.Symlink(
filepath.Clean(path+"/../../config-example.yaml"),
filepath.Join(tmpDir, "config.yaml"),
)
if err != nil {
c.Fatal(err)
}
// Load example config, it should load without validation errors
err = types.LoadConfig(tmpDir, false)
c.Assert(err, check.IsNil)
dnsConfig, baseDomain := types.GetDNSConfig()
c.Assert(dnsConfig.Nameservers[0].String(), check.Equals, "1.1.1.1")
c.Assert(dnsConfig.Resolvers[0].Addr, check.Equals, "1.1.1.1")
c.Assert(dnsConfig.Proxied, check.Equals, true)
c.Assert(baseDomain, check.Equals, "example.com")
}
func writeConfig(c *check.C, tmpDir string, configYaml []byte) {
// Populate a custom config file
configFile := filepath.Join(tmpDir, "config.yaml")
err := os.WriteFile(configFile, configYaml, 0o600)
if err != nil {
c.Fatalf("Couldn't write file %s", configFile)
}
}
func (*Suite) TestTLSConfigValidation(c *check.C) {
tmpDir, err := os.MkdirTemp("", "headscale")
if err != nil {
c.Fatal(err)
}
// defer os.RemoveAll(tmpDir)
configYaml := []byte(`---
tls_letsencrypt_hostname: example.com
tls_letsencrypt_challenge_type: ""
tls_cert_path: abc.pem
noise:
private_key_path: noise_private.key`)
writeConfig(c, tmpDir, configYaml)
// Check configuration validation errors (1)
err = types.LoadConfig(tmpDir, false)
c.Assert(err, check.NotNil)
// check.Matches can not handle multiline strings
tmp := strings.ReplaceAll(err.Error(), "\n", "***")
c.Assert(
tmp,
check.Matches,
".*Fatal config error: set either tls_letsencrypt_hostname or tls_cert_path/tls_key_path, not both.*",
)
c.Assert(
tmp,
check.Matches,
".*Fatal config error: the only supported values for tls_letsencrypt_challenge_type are.*",
)
c.Assert(
tmp,
check.Matches,
".*Fatal config error: server_url must start with https:// or http://.*",
)
// Check configuration validation errors (2)
configYaml = []byte(`---
noise:
private_key_path: noise_private.key
server_url: http://127.0.0.1:8080
tls_letsencrypt_hostname: example.com
tls_letsencrypt_challenge_type: TLS-ALPN-01
`)
writeConfig(c, tmpDir, configYaml)
err = types.LoadConfig(tmpDir, false)
c.Assert(err, check.IsNil)
}

View File

@@ -58,8 +58,8 @@ noise:
# IPv4: https://github.com/tailscale/tailscale/blob/22ebb25e833264f58d7c3f534a8b166894a89536/net/tsaddr/tsaddr.go#L33
# Any other range is NOT supported, and it will cause unexpected issues.
prefixes:
v6: fd7a:115c:a1e0::/48
v4: 100.64.0.0/10
v6: fd7a:115c:a1e0::/48
# Strategy used for allocation of IPs to nodes, available options:
# - sequential (default): assigns the next free IP from the previous given IP.
@@ -105,7 +105,7 @@ derp:
automatically_add_embedded_derp_region: true
# For better connection stability (especially when using an Exit-Node and DNS is not working),
# it is possible to optionall add the public IPv4 and IPv6 address to the Derp-Map using:
# it is possible to optionally add the public IPv4 and IPv6 address to the Derp-Map using:
ipv4: 1.2.3.4
ipv6: 2001:db8::1
@@ -138,13 +138,44 @@ disable_check_updates: false
ephemeral_node_inactivity_timeout: 30m
database:
# Database type. Available options: sqlite, postgres
# Please note that using Postgres is highly discouraged as it is only supported for legacy reasons.
# All new development, testing and optimisations are done with SQLite in mind.
type: sqlite
# Enable debug mode. This setting requires the log.level to be set to "debug" or "trace".
debug: false
# GORM configuration settings.
gorm:
# Enable prepared statements.
prepare_stmt: true
# Enable parameterized queries.
parameterized_queries: true
# Skip logging "record not found" errors.
skip_err_record_not_found: true
# Threshold for slow queries in milliseconds.
slow_threshold: 1000
# SQLite config
sqlite:
path: /var/lib/headscale/db.sqlite
# Enable WAL mode for SQLite. This is recommended for production environments.
# https://www.sqlite.org/wal.html
write_ahead_log: true
# Maximum number of WAL file frames before the WAL file is automatically checkpointed.
# https://www.sqlite.org/c3ref/wal_autocheckpoint.html
# Set to 0 to disable automatic checkpointing.
wal_autocheckpoint: 1000
# # Postgres config
# Please note that using Postgres is highly discouraged as it is only supported for legacy reasons.
# See database.type for more information.
# postgres:
# # If using a Unix socket to connect to Postgres, set the socket path in the 'host' field and leave 'port' blank.
# host: localhost
@@ -183,7 +214,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:
@@ -199,10 +230,17 @@ log:
format: text
level: info
# Path to a file containg ACL policies.
# ACLs can be defined as YAML or HUJSON.
# https://tailscale.com/kb/1018/acls/
acl_policy_path: ""
## Policy
# headscale supports Tailscale's ACL policies.
# Please have a look to their KB to better
# understand the concepts: https://tailscale.com/kb/1018/acls/
policy:
# The mode can be "file" or "database" that defines
# where the ACL policies are stored and read from.
mode: file
# If the mode is set to "file", the path to a
# HuJSON file containing ACL policies.
path: ""
## DNS
#
@@ -213,59 +251,69 @@ acl_policy_path: ""
# - https://tailscale.com/kb/1081/magicdns/
# - https://tailscale.com/blog/2021-09-private-dns-with-magicdns/
#
dns_config:
# Whether to prefer using Headscale provided DNS or use local.
override_local_dns: true
# Please note that for the DNS configuration to have any effect,
# clients must have the `--accept-dns=true` option enabled. This is the
# default for the Tailscale client. This option is enabled by default
# in the Tailscale client.
#
# Setting _any_ of the configuration and `--accept-dns=true` on the
# clients will integrate with the DNS manager on the client or
# overwrite /etc/resolv.conf.
# https://tailscale.com/kb/1235/resolv-conf
#
# If you want stop Headscale from managing the DNS configuration
# all the fields under `dns` should be set to empty values.
dns:
# Whether to use [MagicDNS](https://tailscale.com/kb/1081/magicdns/).
magic_dns: true
# Defines the base domain to create the hostnames for MagicDNS.
# This domain _must_ be different from the server_url domain.
# `base_domain` must be a FQDN, without the trailing dot.
# The FQDN of the hosts will be
# `hostname.base_domain` (e.g., _myhost.example.com_).
base_domain: example.com
# List of DNS servers to expose to clients.
nameservers:
- 1.1.1.1
global:
- 1.1.1.1
- 1.0.0.1
- 2606:4700:4700::1111
- 2606:4700:4700::1001
# NextDNS (see https://tailscale.com/kb/1218/nextdns/).
# "abc123" is example NextDNS ID, replace with yours.
#
# With metadata sharing:
# nameservers:
# - https://dns.nextdns.io/abc123
#
# Without metadata sharing:
# nameservers:
# - 2a07:a8c0::ab:c123
# - 2a07:a8c1::ab:c123
# NextDNS (see https://tailscale.com/kb/1218/nextdns/).
# "abc123" is example NextDNS ID, replace with yours.
# - https://dns.nextdns.io/abc123
# Split DNS (see https://tailscale.com/kb/1054/dns/),
# list of search domains and the DNS to query for each one.
#
# restricted_nameservers:
# foo.bar.com:
# - 1.1.1.1
# darp.headscale.net:
# - 1.1.1.1
# - 8.8.8.8
# Split DNS (see https://tailscale.com/kb/1054/dns/),
# a map of domains and which DNS server to use for each.
split:
{}
# foo.bar.com:
# - 1.1.1.1
# darp.headscale.net:
# - 1.1.1.1
# - 8.8.8.8
# Search domains to inject.
domains: []
# Set custom DNS search domains. With MagicDNS enabled,
# your tailnet base_domain is always the first search domain.
search_domains: []
# 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
# extra_records:
# so far only A and AAAA records are supported (on the tailscale side)
# See: docs/ref/dns.md
extra_records: []
# - name: "grafana.myvpn.example.com"
# type: "A"
# value: "100.64.0.3"
#
# # you can also put it in one line
# - { name: "prometheus.myvpn.example.com", type: "A", value: "100.64.0.3" }
# Whether to use [MagicDNS](https://tailscale.com/kb/1081/magicdns/).
# Only works if there is at least a nameserver defined.
magic_dns: true
# Defines the base domain to create the hostnames for MagicDNS.
# `base_domain` must be a FQDNs, without the trailing dot.
# The FQDN of the hosts will be
# `hostname.user.base_domain` (e.g., _myhost.myuser.example.com_).
base_domain: example.com
#
# Alternatively, extra DNS records can be loaded from a JSON file.
# Headscale processes this file on each change.
# extra_records_path: /var/lib/headscale/extra-records.json
# Unix socket used for the CLI to connect without authentication
# Note: for production you will want to set this to something like:
@@ -316,12 +364,30 @@ unix_socket_permission: "0770"
# allowed_users:
# - alice@example.com
#
# # If `strip_email_domain` is set to `true`, the domain part of the username email address will be removed.
# # This will transform `first-name.last-name@example.com` to the user `first-name.last-name`
# # If `strip_email_domain` is set to `false` the domain part will NOT be removed resulting to the following
# user: `first-name.last-name.example.com`
# # Optional: PKCE (Proof Key for Code Exchange) configuration
# # PKCE adds an additional layer of security to the OAuth 2.0 authorization code flow
# # by preventing authorization code interception attacks
# # See https://datatracker.ietf.org/doc/html/rfc7636
# pkce:
# # Enable or disable PKCE support (default: false)
# enabled: false
# # PKCE method to use:
# # - plain: Use plain code verifier
# # - S256: Use SHA256 hashed code verifier (default, recommended)
# method: S256
#
# strip_email_domain: true
# # Map legacy users from pre-0.24.0 versions of headscale to the new OIDC users
# # by taking the username from the legacy user and matching it with the username
# # provided by the OIDC. This is useful when migrating from legacy users to OIDC
# # to force them using the unique identifier from the OIDC and to give them a
# # proper display name and picture if available.
# # Note that this will only work if the username from the legacy user is the same
# # and there is a possibility for account takeover should a username have changed
# # with the provider.
# # When this feature is disabled, it will cause all new logins to be created as new users.
# # Note this option will be removed in the future and should be set to false
# # on all new installations, or when all users have logged in with OIDC once.
# map_legacy_users: false
# Logtail configuration
# Logtail is Tailscales logging and auditing infrastructure, it allows the control panel

16
docs/about/clients.md Normal file
View File

@@ -0,0 +1,16 @@
# 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) for more information) |
| 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) |
| tvOS | Yes (see [docs](../usage/connect/apple.md#tvos) and `/apple` on your headscale for more information) |

View File

@@ -0,0 +1,3 @@
{%
include-markdown "../../CONTRIBUTING.md"
%}

68
docs/about/faq.md Normal file
View File

@@ -0,0 +1,68 @@
# 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/) 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. It implements a narrow scope, a _single_ Tailscale
network (tailnet), suitable for a personal use, or a small open-source
organisation.
## How can I contribute?
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](contributing.md) for more information.
## Why is 'acknowledged contribution' the chosen model?
Both maintainers have full-time jobs and families, and we want to avoid burnout. We also want to avoid frustration from contributors when their PRs are not accepted.
We are more than happy to exchange emails, or to have dedicated calls before a PR is submitted.
## When/Why is Feature X going to be implemented?
We don't know. We might be working on it. If you're interested in contributing, please post a feature request about it.
Please be aware that there are a number of reasons why we might not accept specific contributions:
- It is not possible to implement the feature in a way that makes sense in a self-hosted environment.
- 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?
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, 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](../setup/install/container.md). But **please be aware that
we don't officially support deploying headscale using Docker**. On our [Discord server](https://discord.gg/c84AZQhmpx)
we have a "docker-issues" channel where you can ask for Docker-specific help to the community.
## Which database should I use?
We recommend the use of SQLite as database for headscale:
- SQLite is simple to setup and easy to use
- It scales well for all of headscale's usecases
- Development and testing happens primarily on SQLite
- PostgreSQL is still supported, but is considered to be in "maintenance mode"
The headscale project itself does not provide a tool to migrate from PostgreSQL to SQLite. Please have a look at [the
related tools documentation](../ref/integration/tools.md) for migration tooling provided by the community.
## 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](../ref/integration/reverse-proxy.md) on how to configure various reverse proxies, and a
dedicated "reverse-proxy-issues" channel on our [Discord server](https://discord.gg/c84AZQhmpx) where you can ask for
help to the community.
## Can I use headscale and tailscale on the same machine?
Running headscale on a machine that is also in the tailnet can cause problems with subnet routers, traffic relay nodes, and MagicDNS. It might work, but it is not supported.

32
docs/about/features.md Normal file
View File

@@ -0,0 +1,32 @@
# 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-extra-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
- [x] Update user profile from identity provider
- [ ] 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))

5
docs/about/help.md Normal file
View File

@@ -0,0 +1,5 @@
# Getting help
Join our [Discord server](https://discord.gg/c84AZQhmpx) for announcements and community support.
Please report bugs via [GitHub issues](https://github.com/juanfont/headscale/issues)

9
docs/about/releases.md Normal file
View File

@@ -0,0 +1,9 @@
# 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).
See the "announcements" channel on our [Discord server](https://discord.gg/c84AZQhmpx) for news about headscale.

4
docs/about/sponsor.md Normal file
View File

@@ -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!

View File

@@ -1,19 +0,0 @@
# 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`.
## Installation
Install the official Tailscale Android client from the [Google Play Store](https://play.google.com/store/apps/details?id=com.tailscale.ipn) or [F-Droid](https://f-droid.org/packages/com.tailscale.ipn/).
Ensure that the installed version is at least 1.30.0, as that is the first release to support custom URLs.
## Configuring the headscale URL
After opening the app, the kebab menu icon (three dots) on the top bar on the right must be repeatedly opened and closed until the _Change server_ option appears in the menu. This is where you can enter your headscale URL.
A screen recording of this process can be seen in the `tailscale-android` PR which implemented this functionality: <https://github.com/tailscale/tailscale-android/pull/55>
After saving and restarting the app, selecting the regular _Sign in_ option (non-SSO) should open up the headscale authentication page.

View File

@@ -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_config:
...
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.

View File

@@ -1,57 +0,0 @@
---
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/)
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.
It implements a narrow scope, a _single_ Tailnet, suitable for a personal use, or a small
open-source organisation.
## How can I contribute?
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.
## Why is 'acknowledged contribution' the chosen model?
Both maintainers have full-time jobs and families, and we want to avoid burnout. We also want to avoid frustration from contributors when their PRs are not accepted.
We are more than happy to exchange emails, or to have dedicated calls before a PR is submitted.
## When/Why is Feature X going to be implemented?
We don't know. We might be working on it. If you want to help, please send us a PR.
Please be aware that there are a number of reasons why we might not accept specific contributions:
- It is not possible to implement the feature in a way that makes sense in a self-hosted environment.
- Given that we are reverse-engineering Tailscale to satify 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?
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).
In addition to that, there are semi-official RPM packages by the Fedora infra team https://copr.fedorainfracloud.org/coprs/jonathanspw/headscale/
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.
## 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.
## Can I use headscale and tailscale on the same machine?
Running headscale on a machine that is also in the tailnet can cause problems with subnet routers, traffic relay nodes, and MagicDNS. It might work, but it is not supported.

View File

@@ -1,6 +0,0 @@
# Glossary
| Term | Description |
| --------- | ------------------------------------------------------------------------------------------------------------------------------------------- |
| Machine | A machine is a single entity connected to `headscale`, typically an installation of Tailscale. Also known as **Node** |
| Namespace | A namespace was a logical grouping of machines "owned" by the same entity, in Tailscale, this is typically a User (This is now called user) |

View File

@@ -1,30 +0,0 @@
# Connecting an iOS client
## Goal
This documentation has the goal of showing how a user can use the official iOS [Tailscale](https://tailscale.com) client with `headscale`.
## Installation
Install the official Tailscale iOS client from the [App Store](https://apps.apple.com/app/tailscale/id1470499037).
Ensure that the installed version is at least 1.38.1, as that is the first release to support alternate control servers.
## Configuring the headscale URL
!!! info "Apple devices"
An endpoint with information on how to connect your Apple devices
(currently macOS only) is available at `/apple` on your running instance.
Ensure that the tailscale app is logged out before proceeding.
Go to iOS settings, scroll down past game center and tv provider to the tailscale app and select it. The headscale URL can be entered into the _"ALTERNATE COORDINATION SERVER URL"_ box.
> **Note**
>
> If the app was previously logged into tailscale, toggle on the _Reset Keychain_ switch.
Restart the app by closing it from the iOS app switcher, open the app and select the regular _Sign in_ option (non-SSO), and it should open up to the headscale authentication page.
Enter your credentials and log in. Headscale should now be working on your iOS device.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 101 KiB

View File

@@ -4,40 +4,34 @@ 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/).
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.
Join our [Discord server](https://discord.gg/c84AZQhmpx) for a chat and community support.
## Design goal
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.
It implements a narrower scope, a single Tailnet, suitable for a personal use, or a small
open-source organisation.
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 server they can use for
their projects and labs. It implements a narrow scope, a _single_ Tailscale
network (tailnet), suitable for a personal use, or a small 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.
This model has been chosen to reduce the risk of burnout by limiting the
maintenance overhead of reviewing and validating third-party code.
Headscale is open to code contributions for bug fixes without discussion.
If you find mistakes in the documentation, please submit a fix to the documentation.
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).

View File

@@ -9,6 +9,7 @@ Type=simple
User=headscale
Group=headscale
ExecStart=/usr/bin/headscale serve
ExecReload=/usr/bin/kill -HUP $MAINPID
Restart=always
RestartSec=5

View File

@@ -6,8 +6,10 @@
HEADSCALE_EXE="/usr/bin/headscale"
BSD_HIER=""
HEADSCALE_RUN_DIR="/var/run/headscale"
HEADSCALE_HOME_DIR="/var/lib/headscale"
HEADSCALE_USER="headscale"
HEADSCALE_GROUP="headscale"
HEADSCALE_SHELL="/usr/sbin/nologin"
ensure_sudo() {
if [ "$(id -u)" = "0" ]; then
@@ -29,7 +31,7 @@ ensure_headscale_path() {
create_headscale_user() {
printf "PostInstall: Adding headscale user %s\n" "$HEADSCALE_USER"
useradd -s /bin/sh -c "headscale default user" headscale
useradd -s "$HEADSCALE_SHELL" -d "$HEADSCALE_HOME_DIR" -c "headscale default user" "$HEADSCALE_USER"
}
create_headscale_group() {

View File

@@ -1,362 +0,0 @@
# ACLs
A key component of tailscale is the notion of Tailnet. This notion is hidden
but the implications that it have on how to use tailscale are not.
For tailscale an [tailnet](https://tailscale.com/kb/1136/tailnet/) is the
following:
> For personal users, you are a tailnet of many devices and one person. Each
> device gets a private Tailscale IP address in the CGNAT range and every
> device can talk directly to every other device, wherever they are on the
> internet.
>
> For businesses and organizations, a tailnet is many devices and many users.
> It can be based on your Microsoft Active Directory, your Google Workspace, a
> GitHub organization, Okta tenancy, or other identity provider namespace. All
> of the devices and users in your tailnet can be seen by the tailnet
> administrators in the Tailscale admin console. There you can apply
> tailnet-wide configuration, such as ACLs that affect visibility of devices
> inside your tailnet, DNS settings, and more.
## Current implementation and issues
Currently in headscale, the namespaces are used both as tailnet and users. The
issue is that if we want to use the ACL's we can't use both at the same time.
Tailnet's cannot communicate with each others. So we can't have an ACL that
authorize tailnet (namespace) A to talk to tailnet (namespace) B.
We also can't write ACLs based on the users (namespaces in headscale) since all
devices belong to the same user.
With the current implementation the only ACL that we can user is to associate
each headscale IP to a host manually then write the ACLs according to this
manual mapping.
```json
{
"hosts": {
"host1": "100.64.0.1",
"server": "100.64.0.2"
},
"acls": [
{ "action": "accept", "users": ["host1"], "ports": ["host2:80,443"] }
]
}
```
While this works, it requires a lot of manual editing on the configuration and
to keep track of all devices IP address.
## Proposition for a next implementation
In order to ease the use of ACL's we need to split the tailnet and users
notion.
A solution could be to consider a headscale server (in it's entirety) as a
tailnet.
For personal users the default behavior could either allow all communications
between all namespaces (like tailscale) or dissallow all communications between
namespaces (current behavior).
For businesses and organisations, viewing a headscale instance a single tailnet
would allow users (namespace) to talk to each other with the ACLs. As described
in tailscale's documentation [[1]], a server should be tagged and personnal
devices should be tied to a user. Translated in headscale's terms each user can
have multiple devices and all those devices should be in the same namespace.
The servers should be tagged and used as such.
This implementation would render useless the sharing feature that is currently
implemented since an ACL could do the same. Simplifying to only one user
interface to do one thing is easier and less confusing for the users.
To better suit the ACLs in this proposition, it's advised to consider that each
namespaces belong to one person. This person can have multiple devices, they
will all be considered as the same user in the ACLs. OIDC feature wouldn't need
to map people to namespace, just create a namespace if the person isn't
registered yet.
As a sidenote, users would like to write ACLs as YAML. We should offer users
the ability to rules in either format (HuJSON or YAML).
[1]: https://tailscale.com/kb/1068/acl-tags/
## Example
Let's build an example use case for a small business (It may be the place where
ACL's are the most useful).
We have a small company with a boss, an admin, two developper and an intern.
The boss should have access to all servers but not to the users hosts. Admin
should also have access to all hosts except that their permissions should be
limited to maintaining the hosts (for example purposes). The developers can do
anything they want on dev hosts, but only watch on productions hosts. Intern
can only interact with the development servers.
Each user have at least a device connected to the network and we have some
servers.
- database.prod
- database.dev
- app-server1.prod
- app-server1.dev
- billing.internal
### Current headscale implementation
Let's create some namespaces
```bash
headscale namespaces create prod
headscale namespaces create dev
headscale namespaces create internal
headscale namespaces create users
headscale nodes register -n users boss-computer
headscale nodes register -n users admin1-computer
headscale nodes register -n users dev1-computer
headscale nodes register -n users dev1-phone
headscale nodes register -n users dev2-computer
headscale nodes register -n users intern1-computer
headscale nodes register -n prod database
headscale nodes register -n prod app-server1
headscale nodes register -n dev database
headscale nodes register -n dev app-server1
headscale nodes register -n internal billing
headscale nodes list
ID | Name | Namespace | IP address
1 | boss-computer | users | 100.64.0.1
2 | admin1-computer | users | 100.64.0.2
3 | dev1-computer | users | 100.64.0.3
4 | dev1-phone | users | 100.64.0.4
5 | dev2-computer | users | 100.64.0.5
6 | intern1-computer | users | 100.64.0.6
7 | database | prod | 100.64.0.7
8 | app-server1 | prod | 100.64.0.8
9 | database | dev | 100.64.0.9
10 | app-server1 | dev | 100.64.0.10
11 | internal | internal | 100.64.0.11
```
In order to only allow the communications related to our description above we
need to add the following ACLs
```json
{
"hosts": {
"boss-computer": "100.64.0.1",
"admin1-computer": "100.64.0.2",
"dev1-computer": "100.64.0.3",
"dev1-phone": "100.64.0.4",
"dev2-computer": "100.64.0.5",
"intern1-computer": "100.64.0.6",
"prod-app-server1": "100.64.0.8"
},
"groups": {
"group:dev": ["dev1-computer", "dev1-phone", "dev2-computer"],
"group:admin": ["admin1-computer"],
"group:boss": ["boss-computer"],
"group:intern": ["intern1-computer"]
},
"acls": [
// boss have access to all servers but no users hosts
{
"action": "accept",
"users": ["group:boss"],
"ports": ["prod:*", "dev:*", "internal:*"]
},
// admin have access to adminstration port (lets only consider port 22 here)
{
"action": "accept",
"users": ["group:admin"],
"ports": ["prod:22", "dev:22", "internal:22"]
},
// dev can do anything on dev servers and check access on prod servers
{
"action": "accept",
"users": ["group:dev"],
"ports": ["dev:*", "prod-app-server1:80,443"]
},
// interns only have access to port 80 and 443 on dev servers (lame internship)
{ "action": "accept", "users": ["group:intern"], "ports": ["dev:80,443"] },
// users can access their own devices
{
"action": "accept",
"users": ["dev1-computer"],
"ports": ["dev1-phone:*"]
},
{
"action": "accept",
"users": ["dev1-phone"],
"ports": ["dev1-computer:*"]
},
// internal namespace communications should still be allowed within the namespace
{ "action": "accept", "users": ["dev"], "ports": ["dev:*"] },
{ "action": "accept", "users": ["prod"], "ports": ["prod:*"] },
{ "action": "accept", "users": ["internal"], "ports": ["internal:*"] }
]
}
```
Since communications between namespace isn't possible we also have to share the
devices between the namespaces.
```bash
// add boss host to prod, dev and internal network
headscale nodes share -i 1 -n prod
headscale nodes share -i 1 -n dev
headscale nodes share -i 1 -n internal
// add admin computer to prod, dev and internal network
headscale nodes share -i 2 -n prod
headscale nodes share -i 2 -n dev
headscale nodes share -i 2 -n internal
// add all dev to prod and dev network
headscale nodes share -i 3 -n dev
headscale nodes share -i 4 -n dev
headscale nodes share -i 3 -n prod
headscale nodes share -i 4 -n prod
headscale nodes share -i 5 -n dev
headscale nodes share -i 5 -n prod
headscale nodes share -i 6 -n dev
```
This fake network have not been tested but it should work. Operating it could
be quite tedious if the company grows. Each time a new user join we have to add
it to a group, and share it to the correct namespaces. If the user want
multiple devices we have to allow communication to each of them one by one. If
business conduct a change in the organisations we may have to rewrite all acls
and reorganise all namespaces.
If we add servers in production we should also update the ACLs to allow dev
access to certain category of them (only app servers for example).
### example based on the proposition in this document
Let's create the namespaces
```bash
headscale namespaces create boss
headscale namespaces create admin1
headscale namespaces create dev1
headscale namespaces create dev2
headscale namespaces create intern1
```
We don't need to create namespaces for the servers because the servers will be
tagged. When registering the servers we will need to add the flag
`--advertised-tags=tag:<tag1>,tag:<tag2>`, and the user (namespace) that is
registering the server should be allowed to do it. Since anyone can add tags to
a server they can register, the check of the tags is done on headscale server
and only valid tags are applied. A tag is valid if the namespace that is
registering it is allowed to do it.
Here are the ACL's to implement the same permissions as above:
```json
{
// groups are simpler and only list the namespaces name
"groups": {
"group:boss": ["boss"],
"group:dev": ["dev1", "dev2"],
"group:admin": ["admin1"],
"group:intern": ["intern1"]
},
"tagOwners": {
// the administrators can add servers in production
"tag:prod-databases": ["group:admin"],
"tag:prod-app-servers": ["group:admin"],
// the boss can tag any server as internal
"tag:internal": ["group:boss"],
// dev can add servers for dev purposes as well as admins
"tag:dev-databases": ["group:admin", "group:dev"],
"tag:dev-app-servers": ["group:admin", "group:dev"]
// interns cannot add servers
},
"acls": [
// boss have access to all servers
{
"action": "accept",
"users": ["group:boss"],
"ports": [
"tag:prod-databases:*",
"tag:prod-app-servers:*",
"tag:internal:*",
"tag:dev-databases:*",
"tag:dev-app-servers:*"
]
},
// admin have only access to administrative ports of the servers
{
"action": "accept",
"users": ["group:admin"],
"ports": [
"tag:prod-databases:22",
"tag:prod-app-servers:22",
"tag:internal:22",
"tag:dev-databases:22",
"tag:dev-app-servers:22"
]
},
{
"action": "accept",
"users": ["group:dev"],
"ports": [
"tag:dev-databases:*",
"tag:dev-app-servers:*",
"tag:prod-app-servers:80,443"
]
},
// servers should be able to talk to database. Database should not be able to initiate connections to server
{
"action": "accept",
"users": ["tag:dev-app-servers"],
"ports": ["tag:dev-databases:5432"]
},
{
"action": "accept",
"users": ["tag:prod-app-servers"],
"ports": ["tag:prod-databases:5432"]
},
// interns have access to dev-app-servers only in reading mode
{
"action": "accept",
"users": ["group:intern"],
"ports": ["tag:dev-app-servers:80,443"]
},
// we still have to allow internal namespaces communications since nothing guarantees that each user have their own namespaces. This could be talked over.
{ "action": "accept", "users": ["boss"], "ports": ["boss:*"] },
{ "action": "accept", "users": ["dev1"], "ports": ["dev1:*"] },
{ "action": "accept", "users": ["dev2"], "ports": ["dev2:*"] },
{ "action": "accept", "users": ["admin1"], "ports": ["admin1:*"] },
{ "action": "accept", "users": ["intern1"], "ports": ["intern1:*"] }
]
}
```
With this implementation, the sharing step is not necessary. Maintenance cost
of the ACL file is lower and less tedious (no need to map hostname and IP's
into it).

View File

@@ -1,48 +0,0 @@
# Better route management
As of today, route management in Headscale is very basic and does not allow for much flexibility, including implementing subnet HA, 4via6 or more advanced features. We also have a number of bugs (e.g., routes exposed by ephemeral nodes)
This proposal aims to improve the route management.
## Current situation
Routes advertised by the nodes are read from the Hostinfo struct. If approved from the the CLI or via autoApprovers, the route is added to the EnabledRoutes field in `Machine`.
This means that the advertised routes are not persisted in the database, as Hostinfo is always replaced. In the same way, EnabledRoutes can get out of sync with the actual routes in the node.
In case of colliding routes (i.e., subnets that are exposed from multiple nodes), we are currently just sending all of them in `PrimaryRoutes`... and hope for the best. (`PrimaryRoutes` is the field in `Node` used for subnet failover).
## Proposal
The core part is to create a new `Route` struct (and DB table), with the following fields:
```go
type Route struct {
ID uint64 `gorm:"primary_key"`
Machine *Machine
Prefix IPPrefix
Advertised bool
Enabled bool
IsPrimary bool
CreatedAt *time.Time
UpdatedAt *time.Time
DeletedAt *time.Time
}
```
- The `Advertised` field is set to true if the route is being advertised by the node. It is set to false if the route is removed. This way we can indicate if a later enabled route has stopped being advertised. A similar behaviour happens in the Tailscale.com control panel.
- The `Enabled` field is set to true if the route is enabled - via CLI or autoApprovers.
- `IsPrimary` indicates if Headscale has selected this route as the primary route for that particular subnet. This allows us to implement subnet failover. This would be fully automatic if there is more than subnet routers advertising the same network - which is the behaviour of Tailscale.com.
## Stuff to bear in mind
- We need to make sure to migrate the current `EnabledRoutes` of `Machine` into the new table.
- When a node stops sharing a subnet, I reckon we should mark it both as not `Advertised` and not `Enabled`. Users should re-enable it if the node advertises it again.
- If only one subnet router is advertising a subnet, we should mark it as primary.
- Regarding subnet failover, the current behaviour of Tailscale.com is to perform the failover after 15 seconds from the node disconnecting from their control panel. I reckon we cannot do the same currently. Our maximum granularity is the keep alive period.

View File

@@ -3,7 +3,7 @@ Headscale implements the same policy ACLs as Tailscale.com, adapted to the self-
For instance, instead of referring to users when defining groups you must
use users (which are the equivalent to user/logins in Tailscale.com).
Please check https://tailscale.com/kb/1018/acls/, and `./tests/acls/` in this repo for working examples.
Please check https://tailscale.com/kb/1018/acls/ for further information.
When using ACL's the User borders are no longer applied. All machines
whichever the User have the ability to communicate with other hosts as
@@ -36,28 +36,30 @@ 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.
ACLs have to be written in [huJSON](https://github.com/tailscale/hujson).
ACLs could be written either on [huJSON](https://github.com/tailscale/hujson)
or YAML. Check the [test ACLs](../tests/acls) for further information.
When registering the servers we will need to add the flag
`--advertise-tags=tag:<tag1>,tag:<tag2>`, and the user that is
registering the server should be allowed to do it. Since anyone can add tags to
a server they can register, the check of the tags is done on headscale server
and only valid tags are applied. A tag is valid if the user that is
When [registering the servers](../usage/getting-started.md#register-a-node) we
will need to add the flag `--advertise-tags=tag:<tag1>,tag:<tag2>`, and the user
that is registering the server should be allowed to do it. Since anyone can add
tags to a server they can register, the check of the tags is done on headscale
server and only valid tags are applied. A tag is valid if the user that is
registering it is allowed to do it.
To use ACLs in headscale, you must edit your config.yaml file. In there you will find a `acl_policy_path: ""` parameter. This will need to point to your ACL file. More info on how these policies are written can be found [here](https://tailscale.com/kb/1018/acls/).
To use ACLs in headscale, you must edit your `config.yaml` file. In there you will find a `policy.path` parameter. This
will need to point to your ACL file. More info on how these policies are written can be found
[here](https://tailscale.com/kb/1018/acls/).
Please reload or restart Headscale after updating the ACL file. Headscale may be reloaded either via its systemd service
(`sudo systemctl reload headscale`) or by sending a SIGHUP signal (`sudo kill -HUP $(pidof headscale)`) to the main
process. Headscale logs the result of ACL policy processing after each reload.
Here are the ACL's to implement the same permissions as above:
```json
```json title="acl.json"
{
// groups are collections of users having a common scope. A user can be in multiple groups
// groups cannot be composed of groups
@@ -88,7 +90,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"
},

39
docs/ref/configuration.md Normal file
View File

@@ -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: <https://github.com/juanfont/headscale/blob/main/config-example.yaml>
* Version {{ headscale.version }}: <https://github.com/juanfont/headscale/blob/v{{ headscale.version }}/config-example.yaml>
=== "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
```

112
docs/ref/dns.md Normal file
View File

@@ -0,0 +1,112 @@
# DNS
Headscale supports [most DNS features](../about/features.md) from Tailscale. DNS related settings can be configured
within `dns` section of the [configuration file](./configuration.md).
## Setting extra DNS records
Headscale allows to set extra DNS records which are made available via
[MagicDNS](https://tailscale.com/kb/1081/magicdns). Extra DNS records can be configured either via static entries in the
[configuration file](./configuration.md) or from a JSON file that Headscale continuously watches for changes:
* Use the `dns.extra_records` option in the [configuration file](./configuration.md) for entries that are static and
don't change while Headscale is running. Those entries are processed when Headscale is starting up and changes to the
configuration require a restart of Headscale.
* For dynamic DNS records that may be added, updated or removed while Headscale is running or DNS records that are
generated by scripts the option `dns.extra_records_path` in the [configuration file](./configuration.md) is useful.
Set it to the absolute path of the JSON file containing DNS records and Headscale processes this file as it detects
changes.
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"
Currently, [only A and AAAA records are processed by Tailscale](https://github.com/tailscale/tailscale/blob/v1.78.3/ipn/ipnlocal/local.go#L4461-L4479).
1. Configure extra DNS records using one of the available configuration options:
=== "Static entries, via `dns.extra_records`"
```yaml title="config.yaml"
dns:
...
extra_records:
- name: "grafana.myvpn.example.com"
type: "A"
value: "100.64.0.3"
- name: "prometheus.myvpn.example.com"
type: "A"
value: "100.64.0.3"
...
```
Restart your headscale instance.
=== "Dynamic entries, via `dns.extra_records_path`"
```json title="extra-records.json"
[
{
"name": "grafana.myvpn.example.com",
"type": "A",
"value": "100.64.0.3"
},
{
"name": "prometheus.myvpn.example.com",
"type": "A",
"value": "100.64.0.3"
}
]
```
Headscale picks up changes to the above JSON file automatically.
!!! tip "Good to know"
* The `dns.extra_records_path` option in the [configuration file](./configuration.md) needs to reference the
JSON file containing extra DNS records.
* Be sure to "sort keys" and produce a stable output in case you generate the JSON file with a script.
Headscale uses a checksum to detect changes to the file and a stable output avoids unnecessary processing.
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:
```nginx title="nginx.conf"
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;
}
}
```

View File

@@ -5,7 +5,7 @@
Register the node and make it advertise itself as an exit node:
```console
$ sudo tailscale up --login-server https://my-server.com --advertise-exit-node
$ sudo tailscale up --login-server https://headscale.example.com --advertise-exit-node
```
If the node is already registered, it can advertise exit capabilities like this:
@@ -14,28 +14,30 @@ If the node is already registered, it can advertise exit capabilities like this:
$ sudo tailscale set --advertise-exit-node
```
To use a node as an exit node, IP forwarding must be enabled on the node. Check the official [Tailscale documentation](https://tailscale.com/kb/1019/subnets/?tab=linux#enable-ip-forwarding) for how to enable IP fowarding.
To use a node as an exit node, IP forwarding must be enabled on the node. Check the official [Tailscale documentation](https://tailscale.com/kb/1019/subnets/?tab=linux#enable-ip-forwarding) for how to enable IP forwarding.
## On the control server
```console
$ # list nodes
$ headscale routes list
ID | Machine | Prefix | Advertised | Enabled | Primary
1 | | 0.0.0.0/0 | false | false | -
2 | | ::/0 | false | false | -
3 | phobos | 0.0.0.0/0 | true | false | -
4 | phobos | ::/0 | true | false | -
ID | Node | Prefix | Advertised | Enabled | Primary
1 | | 0.0.0.0/0 | false | false | -
2 | | ::/0 | false | false | -
3 | phobos | 0.0.0.0/0 | true | false | -
4 | phobos | ::/0 | true | false | -
$ # enable routes for phobos
$ headscale routes enable -r 3
$ headscale routes enable -r 4
$ # Check node list again. The routes are now enabled.
$ headscale routes list
ID | Machine | Prefix | Advertised | Enabled | Primary
1 | | 0.0.0.0/0 | false | false | -
2 | | ::/0 | false | false | -
3 | phobos | 0.0.0.0/0 | true | true | -
4 | phobos | ::/0 | true | true | -
ID | Node | Prefix | Advertised | Enabled | Primary
1 | | 0.0.0.0/0 | false | false | -
2 | | ::/0 | false | false | -
3 | phobos | 0.0.0.0/0 | true | true | -
4 | phobos | ::/0 | true | true | -
```
## On the client
@@ -46,4 +48,4 @@ The exit node can now be used with:
$ sudo tailscale set --exit-node phobos
```
Check the official [Tailscale documentation](https://tailscale.com/kb/1103/exit-nodes/?q=exit#step-3-use-the-exit-node) for how to do it on your device.
Check the official [Tailscale documentation](https://tailscale.com/kb/1103/exit-nodes#use-the-exit-node) for how to do it on your device.

View File

@@ -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**.
@@ -11,15 +11,19 @@ Running headscale behind a reverse proxy is useful when running multiple applica
### WebSockets
The reverse proxy MUST be configured to support WebSockets, as it is needed for clients running Tailscale v1.30+.
The reverse proxy MUST be configured to support WebSockets to communicate with Tailscale clients.
WebSockets support is required when using the headscale embedded DERP server. In this case, you will also need to expose the UDP port used for STUN (by default, udp/3478). Please check our [config-example.yaml](https://github.com/juanfont/headscale/blob/main/config-example.yaml).
WebSockets support is also required when using the headscale embedded DERP server. In this case, you will also need to expose the UDP port used for STUN (by default, udp/3478). Please check our [config-example.yaml](https://github.com/juanfont/headscale/blob/main/config-example.yaml).
### Cloudflare
Running headscale behind a cloudflare proxy or cloudflare tunnel is not supported and will not work as Cloudflare does not support WebSocket POSTs as required by the Tailscale protocol. See [this issue](https://github.com/juanfont/headscale/issues/1468)
### TLS
Headscale can be configured not to use TLS, leaving it to the reverse proxy to handle. Add the following configuration values to your headscale config file.
```yaml
```yaml title="config.yaml"
server_url: https://<YOUR_SERVER_NAME> # This should be the FQDN at which headscale will be served
listen_addr: 0.0.0.0:8080
metrics_listen_addr: 0.0.0.0:9090
@@ -31,7 +35,7 @@ tls_key_path: ""
The following example configuration can be used in your nginx setup, substituting values as necessary. `<IP:PORT>` should be the IP address and port where headscale is running. In most cases, this will be `http://localhost:8080`.
```Nginx
```nginx title="nginx.conf"
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
@@ -76,7 +80,7 @@ Sending local reply with details upgrade_failed
### Envoy
You need add a new upgrade_type named `tailscale-control-protocol`. [see detail](https://www.envoyproxy.io/docs/envoy/latest/api-v3/extensions/filters/network/http_connection_manager/v3/http_connection_manager.proto#extensions-filters-network-http-connection-manager-v3-httpconnectionmanager-upgradeconfig)
You need to add a new upgrade_type named `tailscale-control-protocol`. [see details](https://www.envoyproxy.io/docs/envoy/latest/api-v3/extensions/filters/network/http_connection_manager/v3/http_connection_manager.proto#extensions-filters-network-http-connection-manager-v3-httpconnectionmanager-upgradeconfig)
### Istio
@@ -109,21 +113,21 @@ spec:
The following Caddyfile is all that is necessary to use Caddy as a reverse proxy for headscale, in combination with the `config.yaml` specifications above to disable headscale's built in TLS. Replace values as necessary - `<YOUR_SERVER_NAME>` should be the FQDN at which headscale will be served, and `<IP:PORT>` should be the IP address and port where headscale is running. In most cases, this will be `localhost:8080`.
```
```none title="Caddyfile"
<YOUR_SERVER_NAME> {
reverse_proxy <IP:PORT>
}
```
Caddy v2 will [automatically](https://caddyserver.com/docs/automatic-https) provision a certficate for your domain/subdomain, force HTTPS, and proxy websockets - no further configuration is necessary.
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 `<IP:PORT>`. 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 `<IP:PORT>`. 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.
```
```apache title="apache.conf"
<VirtualHost *:443>
ServerName <YOUR_SERVER_NAME>

View File

@@ -0,0 +1,13 @@
# Tools related to headscale
!!! 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.
This page collects third-party tools and scripts related to headscale.
| Name | Repository Link | Description |
| --------------------- | --------------------------------------------------------------- | ------------------------------------------------- |
| tailscale-manager | [Github](https://github.com/singlestore-labs/tailscale-manager) | Dynamically manage Tailscale route advertisements |
| headscalebacktosqlite | [Github](https://github.com/bigbozza/headscalebacktosqlite) | Migrate headscale from PostgreSQL back to SQLite |

View File

@@ -0,0 +1,19 @@
# Web interfaces for headscale
!!! 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.
Headscale doesn't provide a built-in web interface but users may pick one from the available options.
| Name | Repository Link | Description |
| --------------- | ------------------------------------------------------- | ----------------------------------------------------------------------------------- |
| headscale-webui | [Github](https://github.com/ifargle/headscale-webui) | A simple headscale web UI for small-scale deployments. |
| headscale-ui | [Github](https://github.com/gurucomputing/headscale-ui) | A web frontend for the headscale Tailscale-compatible coordination server |
| HeadscaleUi | [GitHub](https://github.com/simcu/headscale-ui) | A static headscale admin ui, no backend environment required |
| Headplane | [GitHub](https://github.com/tale/headplane) | An advanced Tailscale inspired frontend for headscale |
| headscale-admin | [Github](https://github.com/GoodiesHQ/headscale-admin) | Headscale-Admin is meant to be a simple, modern web interface for headscale |
| ouroboros | [Github](https://github.com/yellowsink/ouroboros) | Ouroboros is designed for users to manage their own devices, rather than for admins |
You can ask for support on our [Discord server](https://discord.gg/c84AZQhmpx) in the "web-interfaces" channel.

View File

@@ -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.
@@ -11,7 +11,7 @@ Known limitations:
In your `config.yaml`, customize this to your liking:
```yaml
```yaml title="config.yaml"
oidc:
# Block further startup until the OIDC provider is healthy and available
only_start_if_oidc_is_available: true
@@ -45,6 +45,18 @@ oidc:
allowed_users:
- alice@example.com
# Optional: PKCE (Proof Key for Code Exchange) configuration
# PKCE adds an additional layer of security to the OAuth 2.0 authorization code flow
# by preventing authorization code interception attacks
# See https://datatracker.ietf.org/doc/html/rfc7636
pkce:
# Enable or disable PKCE support (default: false)
enabled: false
# PKCE method to use:
# - plain: Use plain code verifier
# - S256: Use SHA256 hashed code verifier (default, recommended)
method: S256
# If `strip_email_domain` is set to `true`, the domain part of the username email address will be removed.
# This will transform `first-name.last-name@example.com` to the user `first-name.last-name`
# If `strip_email_domain` is set to `false` the domain part will NOT be removed resulting to the following
@@ -54,9 +66,9 @@ 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
```hcl title="terraform.hcl"
resource "azuread_application" "headscale" {
display_name = "Headscale"
@@ -84,7 +96,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,9 +137,9 @@ output "headscale_client_secret" {
}
```
And in your Headscale `config.yaml`:
And in your headscale `config.yaml`:
```yaml
```yaml title="config.yaml"
oidc:
issuer: "https://login.microsoftonline.com/<tenant-UUID>/v2.0"
client_id: "<client-id-from-terraform>"
@@ -144,7 +156,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 +170,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 title="config.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.

105
docs/ref/remote-cli.md Normal file
View File

@@ -0,0 +1,105 @@
# Controlling headscale with remote CLI
This documentation has the goal of showing a user how-to control a headscale instance
from a remote machine with the `headscale` command line binary.
## Prerequisite
- A workstation to run `headscale` (any supported platform, e.g. Linux).
- A headscale server with gRPC enabled.
- Connections to the gRPC port (default: `50443`) are allowed.
- Remote access requires an encrypted connection via TLS.
- An API key to authenticate with the headscale server.
## Create an API key
We need to create an API key to authenticate with the remote headscale server when using it from our workstation.
To create an 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 associated with the server:
```shell
headscale apikeys list
```
and to expire a key:
```shell
headscale apikeys expire --prefix "<PREFIX>"
```
## Download and configure headscale
1. Download the [`headscale` binary from GitHub's release page](https://github.com/juanfont/headscale/releases). Make
sure to use the same version as on the server.
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. Provide the connection parameters for the remote headscale server either via a minimal YAML configuration file or via
environment variables:
=== "Minimal YAML configuration file"
```yaml title="config.yaml"
cli:
address: <HEADSCALE_ADDRESS>:<PORT>
api_key: <API_KEY_FROM_PREVIOUS_STEP>
```
=== "Environment variables"
```shell
export HEADSCALE_CLI_ADDRESS="<HEADSCALE_ADDRESS>:<PORT>"
export HEADSCALE_CLI_API_KEY="<API_KEY_FROM_PREVIOUS_STEP>"
```
!!! bug
Headscale currently requires at least an empty configuration file when environment variables are used to
specify connection details. See [issue 2193](https://github.com/juanfont/headscale/issues/2193) for more
information.
This instructs the `headscale` binary to connect to a remote instance at `<HEADSCALE_ADDRESS>:<PORT>`, instead of
connecting to the local instance.
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
- Make sure you have the _same_ headscale version on your server and workstation.
- Ensure that connections to the gRPC port are allowed.
- Verify that your TLS certificate is valid and trusted.
- If you don't have access to a trusted certificate (e.g. from Let's Encrypt), either:
- Add your self-signed certificate to the trust store of your OS _or_
- Disable certificate verification by either setting `cli.insecure: true` in the configuration file or by setting
`HEADSCALE_CLI_INSECURE=1` via an environment variable. We do **not** recommend to disable certificate validation.

View File

@@ -4,16 +4,18 @@
Headscale can be configured to expose its web service via TLS. To configure the certificate and key file manually, set the `tls_cert_path` and `tls_cert_path` configuration parameters. If the path is relative, it will be interpreted as relative to the directory the configuration file was read from.
```yaml
```yaml title="config.yaml"
tls_cert_path: ""
tls_key_path: ""
```
The certificate should contain the full chain, else some clients, like the Tailscale Android client, will reject it.
## Let's Encrypt / ACME
To get a certificate automatically via [Let's Encrypt](https://letsencrypt.org/), set `tls_letsencrypt_hostname` to the desired certificate hostname. This name must resolve to the IP address(es) headscale is reachable on (i.e., it must correspond to the `server_url` configuration parameter). The certificate and Let's Encrypt account credentials will be stored in the directory configured in `tls_letsencrypt_cache_dir`. If the path is relative, it will be interpreted as relative to the directory the configuration file was read from.
```yaml
```yaml title="config.yaml"
tls_letsencrypt_hostname: ""
tls_letsencrypt_listen: ":http"
tls_letsencrypt_cache_dir: ".cache"
@@ -47,7 +49,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

View File

@@ -1,100 +0,0 @@
# Controlling `headscale` with remote CLI
## Prerequisit
- 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 overriden 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 "<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="<HEADSCALE ADDRESS>:<PORT>"
export HEADSCALE_CLI_API_KEY="<API KEY FROM PREVIOUS STAGE>"
```
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 your 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 environement

View File

@@ -1,4 +1,6 @@
cairosvg~=2.7.1
mkdocs-material~=9.5.18
mkdocs-minify-plugin~=0.7.1
pillow~=10.1.0
mike~=2.1
mkdocs-include-markdown-plugin~=7.1
mkdocs-macros-plugin~=1.3
mkdocs-material[imaging]~=9.5
mkdocs-minify-plugin~=0.7
mkdocs-redirects~=1.2

View File

@@ -1,195 +0,0 @@
# Running headscale in a container
!!! 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 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`
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. Create an empty SQlite datebase in the headscale directory:
```shell
touch ./config/db.sqlite
```
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
```
- **(Advanced)** If you would like to hand craft a config file **instead** of downloading the example config file, create a blank `headscale` configuration in the headscale directory to edit:
```shell
touch ./config/config.yaml
```
Modify the config file to your preferences before launching Docker container.
Here are some settings that you likely want:
```yaml
# Change to your hostname or host IP
server_url: http://your-host-name:8080
# Listen to 0.0.0.0 so it's accessible outside the container
metrics_listen_addr: 0.0.0.0:9090
# The default /var/lib/headscale path is not writable in the container
noise:
private_key_path: /etc/headscale/noise_private.key
# The default /var/lib/headscale path is not writable in the container
derp:
private_key_path: /etc/headscale/private.key
# The default /var/run/headscale path is not writable in the container
unix_socket: /etc/headscale/headscale.sock
# The default /var/lib/headscale path is not writable in the container
database.type: sqlite3
database.sqlite.path: /etc/headscale/db.sqlite
```
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:
```shell
docker run \
--name headscale \
--detach \
--volume $(pwd)/config:/etc/headscale/ \
--publish 127.0.0.1:8080:8080 \
--publish 127.0.0.1:9090:9090 \
headscale/headscale:<VERSION> \
headscale serve
```
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.
Example `docker-compose.yaml`
```yaml
version: "3.7"
services:
headscale:
image: headscale/headscale:0.22.3
restart: unless-stopped
container_name: headscale
ports:
- "127.0.0.1:8080:8080"
- "127.0.0.1:9090:9090"
volumes:
# pls change [config_path] to the fullpath of the config folder just created
- [config_path]:/etc/headscale
command: headscale serve
```
1. Verify `headscale` is running:
Follow the container logs:
```shell
docker logs --follow headscale
```
Verify running containers:
```shell
docker ps
```
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
docker exec headscale \
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
```
To register a machine when running `headscale` in a container, take the headscale command and pass it to the container:
```shell
docker exec headscale \
headscale --user myfirstuser nodes register --key <YOU_+MACHINE_KEY>
```
### Register machine using a pre authenticated key
Generate a key using the command line:
```shell
docker exec headscale \
headscale --user myfirstuser preauthkeys create --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 <YOUR_HEADSCALE_URL> --authkey <YOUR_AUTH_KEY>
```
## Debugging headscale running in Docker
The `headscale/headscale` Docker container is based on a "distroless" image that does not contain a shell or any other debug tools. If you need to debug your application running in the Docker container, you can use the `-debug` variant, for example `headscale/headscale:x.x.x-debug`.
### Running the debug Docker container
To run the debug Docker container, use the exact same commands as above, but replace `headscale/headscale:x.x.x` with `headscale/headscale:x.x.x-debug` (`x.x.x` is the version of headscale). The two containers are compatible with each other, so you can alternate between them.
### Executing commands in the debug container
The default command in the debug container is to run `headscale`, which is located at `/bin/headscale` inside the container.
Additionally, the debug container includes a minimalist Busybox shell.
To launch a shell in the container, use:
```
docker run -it headscale/headscale:x.x.x-debug sh
```
You can also execute commands directly, such as `ls /bin` in this example:
```
docker run headscale/headscale:x.x.x-debug ls /bin
```
Using `docker exec` allows you to run commands in an existing container.

View File

@@ -1,197 +0,0 @@
# Running headscale on Linux
## Note: 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 (TODO LINK)[].
## 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 VERSION>/headscale_<HEADSCALE VERSION>_linux_<ARCH>
```
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 an empty SQLite database:
```shell
touch /var/lib/headscale/db.sqlite
```
1. Create a `headscale` configuration:
```shell
touch /etc/headscale/config.yaml
```
**(Strongly Recommended)** Download a copy of the [example configuration][config-example.yaml](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 --user myfirstuser nodes register --key <YOUR_MACHINE_KEY>
```
### Register machine using a pre authenticated key
Generate a key using the command line:
```shell
headscale --user myfirstuser preauthkeys create --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 <YOUR_HEADSCALE_URL> --authkey <YOUR_AUTH_KEY>
```
## Running `headscale` in the background with SystemD
:warning: **Deprecated**: This part is very outdated and you should use the [pre-packaged Headscale for this](./running-headscale-linux.md)
This section demonstrates how to run `headscale` as a service in the background with [SystemD](https://www.freedesktop.org/wiki/Software/systemd/).
This should work on most modern Linux distributions.
1. Create a SystemD service configuration at `/etc/systemd/system/headscale.service` containing:
```systemd
[Unit]
Description=headscale controller
After=syslog.target
After=network.target
[Service]
Type=simple
User=headscale
Group=headscale
ExecStart=/usr/local/bin/headscale serve
Restart=always
RestartSec=5
# Optional security enhancements
NoNewPrivileges=yes
PrivateTmp=yes
ProtectSystem=strict
ProtectHome=yes
WorkingDirectory=/var/lib/headscale
ReadWritePaths=/var/lib/headscale /var/run/headscale
AmbientCapabilities=CAP_NET_BIND_SERVICE
RuntimeDirectory=headscale
[Install]
WantedBy=multi-user.target
```
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.

View File

@@ -1,95 +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 for your platform (`.deb` for Ubuntu and Debian) from [Headscale's releases page](https://github.com/juanfont/headscale/releases):
```shell
wget --output-document=headscale.deb \
https://github.com/juanfont/headscale/releases/download/v<HEADSCALE VERSION>/headscale_<HEADSCALE VERSION>_linux_<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 <YOUR_HEADSCALE_URL>
```
Register the machine:
```shell
headscale --user myfirstuser nodes register --key <YOUR_MACHINE_KEY>
```
### Register machine using a pre authenticated key
Generate a key using the command line:
```shell
headscale --user myfirstuser preauthkeys create --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 <YOUR_HEADSCALE_URL> --authkey <YOUR_AUTH_KEY>
```

View File

@@ -1,210 +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 7.1.
In additional 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 (not recommended)
!!! info
As of OpenBSD 7.2, there's a headscale in ports collection, however, it's severely outdated(v0.12.4). You can install it via `pkg_add headscale`.
1. Install from source on OpenBSD 7.2
```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 offical relase 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 offical relase 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 an empty SQLite database:
```shell
touch /var/lib/headscale/db.sqlite
```
1. Create a `headscale` configuration:
```shell
touch /etc/headscale/config.yaml
```
**(Strongly Recommended)** Download a copy of the [example configuration][config-example.yaml](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 --user myfirstuser nodes register --key <YOU_+MACHINE_KEY>
```
### Register machine using a pre authenticated key
Generate a key using the command line:
```shell
headscale --user myfirstuser preauthkeys create --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 <YOUR_HEADSCALE_URL> --authkey <YOUR_AUTH_KEY>
```
## 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.

View File

@@ -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(version [0.23.0-alpha2](https://github.com/juanfont/headscale/releases/tag/v0.23.0-alpha2)):
[![](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 <YOUR_HEADSCALE_URL> 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 --user myfirstuser nodes register --key <YOU_+MACHINE_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 --user myfirstuser preauthkeys create --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 <YOUR_HEADSCALE_URL> --authkey <YOUR_AUTH_KEY>
```
## 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 "<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="<HEADSCALE ADDRESS>:443"
export HEADSCALE_CLI_API_KEY="<API KEY FROM PREVIOUS STAGE>"
```
In the headscale application's detail page, The Public Address corresponding to port 50443 corresponds to the value of <HEADSCALE ADDRESS>.
![](./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/)

View File

@@ -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 local 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 third-party repository for various RPM based distributions is available at:
<https://copr.fedorainfracloud.org/coprs/jonathanspw/headscale/>. 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
```

View File

@@ -0,0 +1,155 @@
# Running headscale in a container
!!! 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**.
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
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. 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
```
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:
```shell
docker run \
--name headscale \
--detach \
--volume $(pwd)/config:/etc/headscale/ \
--publish 127.0.0.1:8080:8080 \
--publish 127.0.0.1:9090:9090 \
headscale/headscale:<VERSION> \
serve
```
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.
Example `docker-compose.yaml`
```yaml
version: "3.7"
services:
headscale:
image: headscale/headscale:<VERSION>
restart: unless-stopped
container_name: headscale
ports:
- "127.0.0.1:8080:8080"
- "127.0.0.1:9090:9090"
volumes:
# Please change <CONFIG_PATH> to the fullpath of the config folder just created
- <CONFIG_PATH>:/etc/headscale
command: serve
```
1. Verify headscale is running:
Follow the container logs:
```shell
docker logs --follow headscale
```
Verify running containers:
```shell
docker ps
```
Verify headscale is available:
```shell
curl http://127.0.0.1:9090/metrics
```
1. Create a headscale user:
```shell
docker exec -it headscale \
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
```
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 \
headscale nodes register --user myfirstuser --key <YOUR_MACHINE_KEY>
```
### Register machine using a pre authenticated key
Generate a key using the command line:
```shell
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:
```shell
tailscale up --login-server <YOUR_HEADSCALE_URL> --authkey <YOUR_AUTH_KEY>
```
## Debugging headscale running in Docker
The `headscale/headscale` Docker container is based on a "distroless" image that does not contain a shell or any other debug tools. If you need to debug your application running in the Docker container, you can use the `-debug` variant, for example `headscale/headscale:x.x.x-debug`.
### Running the debug Docker container
To run the debug Docker container, use the exact same commands as above, but replace `headscale/headscale:x.x.x` with `headscale/headscale:x.x.x-debug` (`x.x.x` is the version of headscale). The two containers are compatible with each other, so you can alternate between them.
### Executing commands in the debug container
The default command in the debug container is to run `headscale`, which is located at `/ko-app/headscale` inside the container.
Additionally, the debug container includes a minimalist Busybox shell.
To launch a shell in the container, use:
```
docker run -it headscale/headscale:x.x.x-debug sh
```
You can also execute commands directly, such as `ls /ko-app` in this example:
```
docker run headscale/headscale:x.x.x-debug ls /ko-app
```
Using `docker exec -it` allows you to run commands in an existing container.

View File

@@ -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
local 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 local 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 local 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 VERSION>/headscale_<HEADSCALE VERSION>_linux_<ARCH>
```
1. Make `headscale` executable:
```shell
sudo chmod +x /usr/local/bin/headscale
```
1. Add a dedicated local 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 title="config.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
```

View File

@@ -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 prerequisites
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 prerequisites
# 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
```

View File

@@ -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 local 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 local 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 `<VALUE_TO_CHANGE>` 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.

10
docs/setup/upgrade.md Normal file
View File

@@ -0,0 +1,10 @@
# Upgrade an existing installation
Update an existing headscale installation 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.

View File

@@ -0,0 +1,14 @@
# Connecting an Android client
This documentation has the goal of showing how a user can use the official Android [Tailscale](https://tailscale.com) client with headscale.
## Installation
Install the official Tailscale Android client from the [Google Play Store](https://play.google.com/store/apps/details?id=com.tailscale.ipn) or [F-Droid](https://f-droid.org/packages/com.tailscale.ipn/).
## Configuring the headscale URL
- Open the app and select the settings menu in the upper-right corner
- Tap on `Accounts`
- In the kebab menu icon (three dots) in the upper-right corner select `Use an alternate server`
- Enter your server URL (e.g `https://headscale.example.com`) and follow the instructions

View File

@@ -0,0 +1,65 @@
# Connecting an Apple client
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"
An endpoint with information on how to connect your Apple device
is also available at `/apple` on your running instance.
## iOS
### Installation
Install the official Tailscale iOS client from the [App Store](https://apps.apple.com/app/tailscale/id1470499037).
### Configuring the headscale URL
- Open the Tailscale app
- Click the account icon in the top-right corner and select `Log in…`.
- Tap the top-right options menu button and select `Use custom coordination server`.
- Enter your instance url (e.g `https://headscale.example.com`)
- Enter your credentials and log in. Headscale should now be working on your iOS device.
## macOS
### Installation
Choose one of the available [Tailscale clients for macOS](https://tailscale.com/kb/1065/macos-variants) and install it.
### Configuring the headscale URL
#### Command line
Use Tailscale's login command to connect with your headscale instance (e.g `https://headscale.example.com`):
```
tailscale login --login-server <YOUR_HEADSCALE_URL>
```
#### GUI
- Option + Click the Tailscale icon in the menu and hover over the Debug menu
- Under `Custom Login Server`, select `Add Account...`
- Enter the URL of your headscale instance (e.g `https://headscale.example.com`) and press `Add Account`
- Follow the login procedure in the browser
## tvOS
### Installation
Install the official Tailscale tvOS client from the [App Store](https://apps.apple.com/app/tailscale/id1470499037).
!!! danger
**Don't** open the Tailscale App after installation!
### Configuring the headscale URL
- Open Settings (the Apple tvOS settings) > Apps > Tailscale
- Under `ALTERNATE COORDINATION SERVER URL`, select `URL`
- Enter the URL of your headscale instance (e.g `https://headscale.example.com`) and press `OK`
- Return to the tvOS Home screen
- Open Tailscale
- Click the button `Install VPN configuration` and confirm the appearing popup by clicking the `Allow` button
- Scan the QR code and follow the login procedure

View File

@@ -0,0 +1,59 @@
# Connecting a Windows client
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"
An endpoint with information on how to connect your Windows device
is also available at `/windows` on your running instance.
## Installation
Download the [Official Windows Client](https://tailscale.com/download/windows) and install it.
## Configuring the headscale URL
Open a Command Prompt or Powershell and use Tailscale's login command to connect with your headscale instance (e.g
`https://headscale.example.com`):
```
tailscale login --login-server <YOUR_HEADSCALE_URL>
```
Follow the instructions in the opened browser window to finish the configuration.
## Troubleshooting
### Unattended mode
By default, Tailscale's Windows client is only running when the user is logged in. If you want to keep Tailscale running
all the time, please enable "Unattended mode":
- Click on the Tailscale tray icon and select `Preferences`
- Enable `Run unattended`
- Confirm the "Unattended mode" message
See also [Keep Tailscale running when I'm not logged in to my computer](https://tailscale.com/kb/1088/run-unattended)
### Failing node registration
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:
```
2022-02-11T00:59:29Z DBG Machine registration has expired. Sending a authurl to register machine=redacted
```
This typically means that the registry keys above was not set appropriately.
To reset and try again, it is important to do the following:
1. Shut down the Tailscale service (or the client running in the tray)
2. Delete Tailscale Application data folder, located at `C:\Users\<USERNAME>\AppData\Local\Tailscale` and try to connect again.
3. Ensure the Windows node is deleted from headscale (to ensure fresh setup)
4. Start Tailscale on the Windows machine and retry the login.

View File

@@ -0,0 +1,135 @@
# 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.
* Headscale is reachable from the Internet. Verify this by opening client specific setup instructions in your
browser, e.g. https://headscale.example.com/windows
* 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 <COMMAND> --help
```
=== "Container"
```shell
# Show help
docker exec -it headscale \
headscale help
# Show help for a specific command
docker exec -it headscale \
headscale <COMMAND> --help
```
## Manage headscale users
In headscale, a node (also known as machine or device) is always assigned to a
headscale user. Such a headscale user may have many nodes assigned to them and
can be managed with the `headscale users` command. Invoke the built-in help for
more information: `headscale users --help`.
### Create a headscale user
=== "Native"
```shell
headscale users create <USER>
```
=== "Container"
```shell
docker exec -it headscale \
headscale users create <USER>
```
### List existing headscale 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 <YOUR_HEADSCALE_URL>
```
Usually, a browser window with further instructions is opened and contains the value for `<YOUR_MACHINE_KEY>`. Approve
and register the node on your headscale server:
=== "Native"
```shell
headscale nodes register --user <USER> --key <YOUR_MACHINE_KEY>
```
=== "Container"
```shell
docker exec -it headscale \
headscale nodes register --user <USER> --key <YOUR_MACHINE_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 <USER>
```
=== "Container"
```shell
docker exec -it headscale \
headscale preauthkeys create --user <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 <YOUR_HEADSCALE_URL> --authkey <YOUR_AUTH_KEY>
```

View File

@@ -1,15 +0,0 @@
# Headscale web interface
!!! 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.
| 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-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 |
You can ask for support on our dedicated [Discord channel](https://discord.com/channels/896711691637780480/1105842846386356294).

View File

@@ -1,60 +0,0 @@
# 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`.
## Add registry keys
To make the Windows client behave as expected and to run well with `headscale`, two registry keys **must** be set:
- `HKLM:\SOFTWARE\Tailscale IPN\UnattendedMode` must be set to `always` as a `string` type, to allow Tailscale to run properly in the background
- `HKLM:\SOFTWARE\Tailscale IPN\LoginURL` must be set to `<YOUR HEADSCALE URL>` as a `string` type, to ensure Tailscale contacts the correct control server.
You can set these using the Windows Registry Editor:
![windows-registry](./images/windows-registry.png)
Or via the following Powershell commands (right click Powershell icon and select "Run as administrator"):
```
New-Item -Path "HKLM:\SOFTWARE\Tailscale IPN"
New-ItemProperty -Path 'HKLM:\Software\Tailscale IPN' -Name UnattendedMode -PropertyType String -Value always
New-ItemProperty -Path 'HKLM:\Software\Tailscale IPN' -Name LoginURL -PropertyType String -Value https://YOUR-HEADSCALE-URL
```
The Tailscale Windows client has been observed to reset its configuration on logout/reboot and these two keys [resolves that issue](https://github.com/tailscale/tailscale/issues/2798).
For a guide on how to edit registry keys, [check out Computer Hope](https://www.computerhope.com/issues/ch001348.htm).
## Installation
Download the [Official Windows Client](https://tailscale.com/download/windows) and install it.
When the installation has finished, start Tailscale and log in (you might have to click the icon in the system tray).
The log in should open a browser Window and direct you to your `headscale` instance.
## Troubleshooting
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:
```
2022-02-11T00:59:29Z DBG Machine registration has expired. Sending a authurl to register machine=redacted
```
This typically means that the registry keys above was not set appropriately.
To reset and try again, it is important to do the following:
1. Ensure the registry keys from the previous guide is correctly set.
2. Shut down the Tailscale service (or the client running in the tray)
3. Delete Tailscale Application data folder, located at `C:\Users\<USERNAME>\AppData\Local\Tailscale` and try to connect again.
4. Ensure the Windows node is deleted from headscale (to ensure fresh setup)
5. Start Tailscale on the windows machine and retry the login.

View File

@@ -1,5 +0,0 @@
# Examples
This directory contains examples on how to run `headscale` on different platforms.
All examples are provided by the community and they are not verified by the `headscale` authors.

View File

@@ -1,2 +0,0 @@
/**/site
/**/secrets

View File

@@ -1,100 +0,0 @@
# Deploying headscale on Kubernetes
**Note:** This is contributed by the community and not verified by the headscale authors.
This directory contains [Kustomize](https://kustomize.io) templates that deploy
headscale in various configurations.
These templates currently support Rancher k3s. Other clusters may require
adaptation, especially around volume claims and ingress.
Commands below assume this directory is your current working directory.
# Generate secrets and site configuration
Run `./init.bash` to generate keys, passwords, and site configuration files.
Edit `base/site/public.env`, changing `public-hostname` to the public DNS name
that will be used for your headscale deployment.
Set `public-proto` to "https" if you're planning to use TLS & Let's Encrypt.
Configure DERP servers by editing `base/site/derp.yaml` if needed.
# Add the image to the registry
You'll somehow need to get `headscale:latest` into your cluster image registry.
An easy way to do this with k3s:
- Reconfigure k3s to use docker instead of containerd (`k3s server --docker`)
- `docker build -t headscale:latest ..` from here
# Create the namespace
If it doesn't already exist, `kubectl create ns headscale`.
# Deploy headscale
## sqlite
`kubectl -n headscale apply -k ./sqlite`
## postgres
`kubectl -n headscale apply -k ./postgres`
# TLS & Let's Encrypt
Test a staging certificate with your configured DNS name and Let's Encrypt.
`kubectl -n headscale apply -k ./staging-tls`
Replace with a production certificate.
`kubectl -n headscale apply -k ./production-tls`
## Static / custom TLS certificates
Only Let's Encrypt is supported. If you need other TLS settings, modify or patch the ingress.
# Administration
Use the wrapper script to remotely operate headscale to perform administrative
tasks like creating namespaces, authkeys, etc.
```
[c@nix-slate:~/Projects/headscale/k8s]$ ./headscale.bash
headscale is an open source implementation of the Tailscale control server
https://github.com/juanfont/headscale
Usage:
headscale [command]
Available Commands:
help Help about any command
namespace Manage the namespaces of headscale
node Manage the nodes of headscale
preauthkey Handle the preauthkeys in headscale
routes Manage the routes of headscale
serve Launches the headscale server
version Print the version.
Flags:
-h, --help help for headscale
-o, --output string Output format. Empty for human-readable, 'json' or 'json-line'
Use "headscale [command] --help" for more information about a command.
```
# TODO / Ideas
- Interpolate `email:` option to the ClusterIssuer from site configuration.
This probably needs to be done with a transformer, kustomize vars don't seem to work.
- Add kustomize examples for cloud-native ingress, load balancer
- CockroachDB for the backend
- DERP server deployment
- Tor hidden service

View File

@@ -1,9 +0,0 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: headscale-config
data:
server_url: $(PUBLIC_PROTO)://$(PUBLIC_HOSTNAME)
listen_addr: "0.0.0.0:8080"
metrics_listen_addr: "127.0.0.1:9090"
ephemeral_node_inactivity_timeout: "30m"

View File

@@ -1,18 +0,0 @@
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: headscale
annotations:
kubernetes.io/ingress.class: traefik
spec:
rules:
- host: $(PUBLIC_HOSTNAME)
http:
paths:
- backend:
service:
name: headscale
port:
number: 8080
path: /
pathType: Prefix

Some files were not shown because too many files have changed in this diff Show More