mirror of
				https://github.com/juanfont/headscale.git
				synced 2025-10-28 10:51:44 +01:00 
			
		
		
		
	Merge branch 'main' into magic-dns-support
This commit is contained in:
		
						commit
						040a18e6f8
					
				| @ -19,7 +19,7 @@ builds: | |||||||
|     flags: |     flags: | ||||||
|       - -mod=readonly |       - -mod=readonly | ||||||
|     ldflags: |     ldflags: | ||||||
|       - -s -w -X main.version={{.Version}} |       - -s -w -X github.com/juanfont/headscale/cmd/headscale/cli.Version=v{{.Version}} | ||||||
|   - id: linux-armhf |   - id: linux-armhf | ||||||
|     main: ./cmd/headscale/headscale.go |     main: ./cmd/headscale/headscale.go | ||||||
|     mod_timestamp: '{{ .CommitTimestamp }}' |     mod_timestamp: '{{ .CommitTimestamp }}' | ||||||
| @ -39,7 +39,7 @@ builds: | |||||||
|     flags: |     flags: | ||||||
|       - -mod=readonly |       - -mod=readonly | ||||||
|     ldflags: |     ldflags: | ||||||
|       - -s -w -X main.version={{.Version}} |       - -s -w -X github.com/juanfont/headscale/cmd/headscale/cli.Version=v{{.Version}} | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|   - id: linux-amd64 |   - id: linux-amd64 | ||||||
| @ -54,6 +54,8 @@ builds: | |||||||
|       - 7 |       - 7 | ||||||
|     main: ./cmd/headscale/headscale.go |     main: ./cmd/headscale/headscale.go | ||||||
|     mod_timestamp: '{{ .CommitTimestamp }}' |     mod_timestamp: '{{ .CommitTimestamp }}' | ||||||
|  |     ldflags: | ||||||
|  |       - -s -w -X github.com/juanfont/headscale/cmd/headscale/cli.Version=v{{.Version}} | ||||||
| 
 | 
 | ||||||
| archives: | archives: | ||||||
|   - id: golang-cross |   - id: golang-cross | ||||||
|  | |||||||
							
								
								
									
										15
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										15
									
								
								README.md
									
									
									
									
									
								
							| @ -30,6 +30,17 @@ Headscale implements this coordination server. | |||||||
| - [x] Share nodes between ~~users~~ namespaces | - [x] Share nodes between ~~users~~ namespaces | ||||||
| - [ ] MagicDNS / Smart DNS | - [ ] MagicDNS / Smart DNS | ||||||
| 
 | 
 | ||||||
|  | ## Client OS support | ||||||
|  | 
 | ||||||
|  | | OS | Supports headscale | | ||||||
|  | | --- | --- | | ||||||
|  | | Linux | Yes | | ||||||
|  | | OpenBSD | Yes | | ||||||
|  | | macOS | Yes (see `/apple` on your headscale for more information) | | ||||||
|  | | Windows | Yes | | ||||||
|  | | Android | [You need to compile the client yourself](https://github.com/juanfont/headscale/issues/58#issuecomment-885255270) | | ||||||
|  | | iOS | Not yet | | ||||||
|  | 
 | ||||||
| ## Roadmap 🤷 | ## Roadmap 🤷 | ||||||
| 
 | 
 | ||||||
| Suggestions/PRs welcomed! | Suggestions/PRs welcomed! | ||||||
| @ -114,7 +125,7 @@ Suggestions/PRs welcomed! | |||||||
| 7. Add your first machine | 7. Add your first machine | ||||||
| 
 | 
 | ||||||
|    ```shell |    ```shell | ||||||
|    tailscale up -login-server YOUR_HEADSCALE_URL |    tailscale up --login-server YOUR_HEADSCALE_URL | ||||||
|    ``` |    ``` | ||||||
| 
 | 
 | ||||||
| 8. Navigate to the URL you will get with `tailscale up`, where you'll find your machine key. | 8. Navigate to the URL you will get with `tailscale up`, where you'll find your machine key. | ||||||
| @ -154,7 +165,7 @@ Alternatively, you can use Auth Keys to register your machines: | |||||||
| 
 | 
 | ||||||
| 2. Use the authkey from your machine to register it | 2. Use the authkey from your machine to register it | ||||||
|    ```shell |    ```shell | ||||||
|    tailscale up -login-server YOUR_HEADSCALE_URL --authkey YOURAUTHKEY |    tailscale up --login-server YOUR_HEADSCALE_URL --authkey YOURAUTHKEY | ||||||
|    ``` |    ``` | ||||||
| 
 | 
 | ||||||
| If you create an authkey with the `--ephemeral` flag, that key will create ephemeral nodes. This implies that `--reusable` is true. | If you create an authkey with the `--ephemeral` flag, that key will create ephemeral nodes. This implies that `--reusable` is true. | ||||||
|  | |||||||
							
								
								
									
										29
									
								
								app.go
									
									
									
									
									
								
							
							
						
						
									
										29
									
								
								app.go
									
									
									
									
									
								
							| @ -12,6 +12,7 @@ import ( | |||||||
| 	"github.com/rs/zerolog/log" | 	"github.com/rs/zerolog/log" | ||||||
| 
 | 
 | ||||||
| 	"github.com/gin-gonic/gin" | 	"github.com/gin-gonic/gin" | ||||||
|  | 	"golang.org/x/crypto/acme" | ||||||
| 	"golang.org/x/crypto/acme/autocert" | 	"golang.org/x/crypto/acme/autocert" | ||||||
| 	"gorm.io/gorm" | 	"gorm.io/gorm" | ||||||
| 	"inet.af/netaddr" | 	"inet.af/netaddr" | ||||||
| @ -46,6 +47,9 @@ type Config struct { | |||||||
| 	TLSCertPath string | 	TLSCertPath string | ||||||
| 	TLSKeyPath  string | 	TLSKeyPath  string | ||||||
| 
 | 
 | ||||||
|  | 	ACMEURL   string | ||||||
|  | 	ACMEEmail string | ||||||
|  | 
 | ||||||
| 	DNSConfig *tailcfg.DNSConfig | 	DNSConfig *tailcfg.DNSConfig | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| @ -185,16 +189,18 @@ func (h *Headscale) Serve() error { | |||||||
| 	r.GET("/apple/:platform", h.ApplePlatformConfig) | 	r.GET("/apple/:platform", h.ApplePlatformConfig) | ||||||
| 	var err error | 	var err error | ||||||
| 
 | 
 | ||||||
| 	timeout := 30 * time.Second |  | ||||||
| 
 |  | ||||||
| 	go h.watchForKVUpdates(5000) | 	go h.watchForKVUpdates(5000) | ||||||
| 	go h.expireEphemeralNodes(5000) | 	go h.expireEphemeralNodes(5000) | ||||||
| 
 | 
 | ||||||
| 	s := &http.Server{ | 	s := &http.Server{ | ||||||
| 		Addr:        h.cfg.Addr, | 		Addr:        h.cfg.Addr, | ||||||
| 		Handler:     r, | 		Handler:     r, | ||||||
| 		ReadTimeout:  timeout, | 		ReadTimeout: 30 * time.Second, | ||||||
| 		WriteTimeout: timeout, | 		// Go does not handle timeouts in HTTP very well, and there is
 | ||||||
|  | 		// no good way to handle streaming timeouts, therefore we need to
 | ||||||
|  | 		// keep this at unlimited and be careful to clean up connections
 | ||||||
|  | 		// https://blog.cloudflare.com/the-complete-guide-to-golang-net-http-timeouts/#aboutstreaming
 | ||||||
|  | 		WriteTimeout: 0, | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	if h.cfg.TLSLetsEncryptHostname != "" { | 	if h.cfg.TLSLetsEncryptHostname != "" { | ||||||
| @ -206,14 +212,14 @@ func (h *Headscale) Serve() error { | |||||||
| 			Prompt:     autocert.AcceptTOS, | 			Prompt:     autocert.AcceptTOS, | ||||||
| 			HostPolicy: autocert.HostWhitelist(h.cfg.TLSLetsEncryptHostname), | 			HostPolicy: autocert.HostWhitelist(h.cfg.TLSLetsEncryptHostname), | ||||||
| 			Cache:      autocert.DirCache(h.cfg.TLSLetsEncryptCacheDir), | 			Cache:      autocert.DirCache(h.cfg.TLSLetsEncryptCacheDir), | ||||||
|  | 			Client: &acme.Client{ | ||||||
|  | 				DirectoryURL: h.cfg.ACMEURL, | ||||||
|  | 			}, | ||||||
|  | 			Email: h.cfg.ACMEEmail, | ||||||
| 		} | 		} | ||||||
| 		s := &http.Server{ | 
 | ||||||
| 			Addr:         h.cfg.Addr, | 		s.TLSConfig = m.TLSConfig() | ||||||
| 			TLSConfig:    m.TLSConfig(), | 
 | ||||||
| 			Handler:      r, |  | ||||||
| 			ReadTimeout:  timeout, |  | ||||||
| 			WriteTimeout: timeout, |  | ||||||
| 		} |  | ||||||
| 		if h.cfg.TLSLetsEncryptChallengeType == "TLS-ALPN-01" { | 		if h.cfg.TLSLetsEncryptChallengeType == "TLS-ALPN-01" { | ||||||
| 			// Configuration via autocert with TLS-ALPN-01 (https://tools.ietf.org/html/rfc8737)
 | 			// Configuration via autocert with TLS-ALPN-01 (https://tools.ietf.org/html/rfc8737)
 | ||||||
| 			// The RFC requires that the validation is done on port 443; in other words, headscale
 | 			// The RFC requires that the validation is done on port 443; in other words, headscale
 | ||||||
| @ -224,7 +230,6 @@ func (h *Headscale) Serve() error { | |||||||
| 			// port 80 for the certificate validation in addition to the headscale
 | 			// port 80 for the certificate validation in addition to the headscale
 | ||||||
| 			// service, which can be configured to run on any other port.
 | 			// service, which can be configured to run on any other port.
 | ||||||
| 			go func() { | 			go func() { | ||||||
| 
 |  | ||||||
| 				log.Fatal(). | 				log.Fatal(). | ||||||
| 					Err(http.ListenAndServe(h.cfg.TLSLetsEncryptListen, m.HTTPHandler(http.HandlerFunc(h.redirect)))). | 					Err(http.ListenAndServe(h.cfg.TLSLetsEncryptListen, m.HTTPHandler(http.HandlerFunc(h.redirect)))). | ||||||
| 					Msg("failed to set up a HTTP server") | 					Msg("failed to set up a HTTP server") | ||||||
|  | |||||||
| @ -186,6 +186,9 @@ func getHeadscaleApp() (*headscale.Headscale, error) { | |||||||
| 		TLSKeyPath:  absPath(viper.GetString("tls_key_path")), | 		TLSKeyPath:  absPath(viper.GetString("tls_key_path")), | ||||||
| 
 | 
 | ||||||
| 		DNSConfig: dnsConfig, | 		DNSConfig: dnsConfig, | ||||||
|  |      | ||||||
|  | 		ACMEEmail: viper.GetString("acme_email"), | ||||||
|  | 		ACMEURL:   viper.GetString("acme_url"), | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	h, err := headscale.NewHeadscale(cfg) | 	h, err := headscale.NewHeadscale(cfg) | ||||||
|  | |||||||
| @ -10,6 +10,8 @@ | |||||||
|     "db_name": "headscale", |     "db_name": "headscale", | ||||||
|     "db_user": "foo", |     "db_user": "foo", | ||||||
|     "db_pass": "bar", |     "db_pass": "bar", | ||||||
|  |     "acme_url": "https://acme-v02.api.letsencrypt.org/directory", | ||||||
|  |     "acme_email": "", | ||||||
|     "tls_letsencrypt_hostname": "", |     "tls_letsencrypt_hostname": "", | ||||||
|     "tls_letsencrypt_listen": ":http", |     "tls_letsencrypt_listen": ":http", | ||||||
|     "tls_letsencrypt_cache_dir": ".cache", |     "tls_letsencrypt_cache_dir": ".cache", | ||||||
|  | |||||||
| @ -6,6 +6,8 @@ | |||||||
|     "ephemeral_node_inactivity_timeout": "30m", |     "ephemeral_node_inactivity_timeout": "30m", | ||||||
|     "db_type": "sqlite3", |     "db_type": "sqlite3", | ||||||
|     "db_path": "db.sqlite", |     "db_path": "db.sqlite", | ||||||
|  |     "acme_url": "https://acme-v02.api.letsencrypt.org/directory", | ||||||
|  |     "acme_email": "", | ||||||
|     "tls_letsencrypt_hostname": "", |     "tls_letsencrypt_hostname": "", | ||||||
|     "tls_letsencrypt_listen": ":http", |     "tls_letsencrypt_listen": ":http", | ||||||
|     "tls_letsencrypt_cache_dir": ".cache", |     "tls_letsencrypt_cache_dir": ".cache", | ||||||
|  | |||||||
| @ -433,7 +433,7 @@ func (s *IntegrationTestSuite) TestPingAllPeers() { | |||||||
| 						command := []string{ | 						command := []string{ | ||||||
| 							"tailscale", "ping", | 							"tailscale", "ping", | ||||||
| 							"--timeout=1s", | 							"--timeout=1s", | ||||||
| 							"--c=20", | 							"--c=10", | ||||||
| 							"--until-direct=true", | 							"--until-direct=true", | ||||||
| 							ip.String(), | 							ip.String(), | ||||||
| 						} | 						} | ||||||
|  | |||||||
| @ -317,7 +317,8 @@ func (h *Headscale) notifyChangesToPeers(m *Machine) { | |||||||
| 				Str("func", "notifyChangesToPeers"). | 				Str("func", "notifyChangesToPeers"). | ||||||
| 				Str("machine", m.Name). | 				Str("machine", m.Name). | ||||||
| 				Str("peer", p.Name). | 				Str("peer", p.Name). | ||||||
| 				Msgf("Peer %s does not appear to be polling", p.Name) | 				Msgf("Peer %s does not have an open update client, skipping.", p.Name) | ||||||
|  | 			continue | ||||||
| 		} | 		} | ||||||
| 		log.Trace(). | 		log.Trace(). | ||||||
| 			Str("func", "notifyChangesToPeers"). | 			Str("func", "notifyChangesToPeers"). | ||||||
| @ -388,11 +389,12 @@ func (h *Headscale) sendRequestOnUpdateChannel(m *tailcfg.Node) error { | |||||||
| 				Msgf("Notified machine %s", m.Name) | 				Msgf("Notified machine %s", m.Name) | ||||||
| 		} | 		} | ||||||
| 	} else { | 	} else { | ||||||
|  | 		err := errors.New("machine does not have an open update channel") | ||||||
| 		log.Info(). | 		log.Info(). | ||||||
| 			Str("func", "requestUpdate"). | 			Str("func", "requestUpdate"). | ||||||
| 			Str("machine", m.Name). | 			Str("machine", m.Name). | ||||||
| 			Msgf("Machine %s does not appear to be polling", m.Name) | 			Msgf("Machine %s does not have an open update channel", m.Name) | ||||||
| 		return errors.New("machine does not seem to be polling") | 		return err | ||||||
| 	} | 	} | ||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
|  | |||||||
							
								
								
									
										31
									
								
								poll.go
									
									
									
									
									
								
							
							
						
						
									
										31
									
								
								poll.go
									
									
									
									
									
								
							| @ -230,6 +230,7 @@ func (h *Headscale) PollNetMapStream( | |||||||
| 					Str("channel", "pollData"). | 					Str("channel", "pollData"). | ||||||
| 					Err(err). | 					Err(err). | ||||||
| 					Msg("Cannot write data") | 					Msg("Cannot write data") | ||||||
|  | 				return false | ||||||
| 			} | 			} | ||||||
| 			log.Trace(). | 			log.Trace(). | ||||||
| 				Str("handler", "PollNetMapStream"). | 				Str("handler", "PollNetMapStream"). | ||||||
| @ -237,7 +238,7 @@ func (h *Headscale) PollNetMapStream( | |||||||
| 				Str("channel", "pollData"). | 				Str("channel", "pollData"). | ||||||
| 				Int("bytes", len(data)). | 				Int("bytes", len(data)). | ||||||
| 				Msg("Data from pollData channel written successfully") | 				Msg("Data from pollData channel written successfully") | ||||||
| 				// TODO: Abstract away all the database calls, this can cause race conditions
 | 				// TODO(kradalby): Abstract away all the database calls, this can cause race conditions
 | ||||||
| 				// when an outdated machine object is kept alive, e.g. db is update from
 | 				// when an outdated machine object is kept alive, e.g. db is update from
 | ||||||
| 				// command line, but then overwritten.
 | 				// command line, but then overwritten.
 | ||||||
| 			err = h.UpdateMachine(&m) | 			err = h.UpdateMachine(&m) | ||||||
| @ -258,7 +259,7 @@ func (h *Headscale) PollNetMapStream( | |||||||
| 				Str("machine", m.Name). | 				Str("machine", m.Name). | ||||||
| 				Str("channel", "pollData"). | 				Str("channel", "pollData"). | ||||||
| 				Int("bytes", len(data)). | 				Int("bytes", len(data)). | ||||||
| 				Msg("Machine updated successfully after sending pollData") | 				Msg("Machine entry in database updated successfully after sending pollData") | ||||||
| 			return true | 			return true | ||||||
| 
 | 
 | ||||||
| 		case data := <-keepAliveChan: | 		case data := <-keepAliveChan: | ||||||
| @ -276,6 +277,7 @@ func (h *Headscale) PollNetMapStream( | |||||||
| 					Str("channel", "keepAlive"). | 					Str("channel", "keepAlive"). | ||||||
| 					Err(err). | 					Err(err). | ||||||
| 					Msg("Cannot write keep alive message") | 					Msg("Cannot write keep alive message") | ||||||
|  | 				return false | ||||||
| 			} | 			} | ||||||
| 			log.Trace(). | 			log.Trace(). | ||||||
| 				Str("handler", "PollNetMapStream"). | 				Str("handler", "PollNetMapStream"). | ||||||
| @ -283,7 +285,7 @@ func (h *Headscale) PollNetMapStream( | |||||||
| 				Str("channel", "keepAlive"). | 				Str("channel", "keepAlive"). | ||||||
| 				Int("bytes", len(data)). | 				Int("bytes", len(data)). | ||||||
| 				Msg("Keep alive sent successfully") | 				Msg("Keep alive sent successfully") | ||||||
| 				// TODO: Abstract away all the database calls, this can cause race conditions
 | 				// TODO(kradalby): Abstract away all the database calls, this can cause race conditions
 | ||||||
| 				// when an outdated machine object is kept alive, e.g. db is update from
 | 				// when an outdated machine object is kept alive, e.g. db is update from
 | ||||||
| 				// command line, but then overwritten.
 | 				// command line, but then overwritten.
 | ||||||
| 			err = h.UpdateMachine(&m) | 			err = h.UpdateMachine(&m) | ||||||
| @ -336,6 +338,7 @@ func (h *Headscale) PollNetMapStream( | |||||||
| 						Str("channel", "update"). | 						Str("channel", "update"). | ||||||
| 						Err(err). | 						Err(err). | ||||||
| 						Msg("Could not write the map response") | 						Msg("Could not write the map response") | ||||||
|  | 					return false | ||||||
| 				} | 				} | ||||||
| 				log.Trace(). | 				log.Trace(). | ||||||
| 					Str("handler", "PollNetMapStream"). | 					Str("handler", "PollNetMapStream"). | ||||||
| @ -347,7 +350,7 @@ func (h *Headscale) PollNetMapStream( | |||||||
| 					// we sometimes end in a state were the update
 | 					// we sometimes end in a state were the update
 | ||||||
| 					// is not picked up by a client and we use this
 | 					// is not picked up by a client and we use this
 | ||||||
| 					// to determine if we should "force" an update.
 | 					// to determine if we should "force" an update.
 | ||||||
| 					// TODO: Abstract away all the database calls, this can cause race conditions
 | 					// TODO(kradalby): Abstract away all the database calls, this can cause race conditions
 | ||||||
| 					// when an outdated machine object is kept alive, e.g. db is update from
 | 					// when an outdated machine object is kept alive, e.g. db is update from
 | ||||||
| 					// command line, but then overwritten.
 | 					// command line, but then overwritten.
 | ||||||
| 				err = h.UpdateMachine(&m) | 				err = h.UpdateMachine(&m) | ||||||
| @ -393,13 +396,33 @@ func (h *Headscale) PollNetMapStream( | |||||||
| 			m.LastSeen = &now | 			m.LastSeen = &now | ||||||
| 			h.db.Save(&m) | 			h.db.Save(&m) | ||||||
| 
 | 
 | ||||||
|  | 			log.Trace(). | ||||||
|  | 				Str("handler", "PollNetMapStream"). | ||||||
|  | 				Str("machine", m.Name). | ||||||
|  | 				Str("channel", "Done"). | ||||||
|  | 				Msg("Cancelling keepAlive channel") | ||||||
| 			cancelKeepAlive <- struct{}{} | 			cancelKeepAlive <- struct{}{} | ||||||
| 
 | 
 | ||||||
|  | 			log.Trace(). | ||||||
|  | 				Str("handler", "PollNetMapStream"). | ||||||
|  | 				Str("machine", m.Name). | ||||||
|  | 				Str("channel", "Done"). | ||||||
|  | 				Msg("Closing update channel") | ||||||
| 			h.closeUpdateChannel(&m) | 			h.closeUpdateChannel(&m) | ||||||
| 
 | 
 | ||||||
| 			close(pollDataChan) | 			close(pollDataChan) | ||||||
|  | 			log.Trace(). | ||||||
|  | 				Str("handler", "PollNetMapStream"). | ||||||
|  | 				Str("machine", m.Name). | ||||||
|  | 				Str("channel", "Done"). | ||||||
|  | 				Msg("Closing pollData channel") | ||||||
| 
 | 
 | ||||||
| 			close(keepAliveChan) | 			close(keepAliveChan) | ||||||
|  | 			log.Trace(). | ||||||
|  | 				Str("handler", "PollNetMapStream"). | ||||||
|  | 				Str("machine", m.Name). | ||||||
|  | 				Str("channel", "Done"). | ||||||
|  | 				Msg("Closing keepAliveChan channel") | ||||||
| 
 | 
 | ||||||
| 			return false | 			return false | ||||||
| 		} | 		} | ||||||
|  | |||||||
		Loading…
	
		Reference in New Issue
	
	Block a user