mirror of
https://github.com/juanfont/headscale.git
synced 2024-11-26 08:53:05 +00:00
Merge branch 'main' into magic-dns-support
This commit is contained in:
commit
040a18e6f8
9 changed files with 76 additions and 26 deletions
|
@ -19,7 +19,7 @@ builds:
|
|||
flags:
|
||||
- -mod=readonly
|
||||
ldflags:
|
||||
- -s -w -X main.version={{.Version}}
|
||||
- -s -w -X github.com/juanfont/headscale/cmd/headscale/cli.Version=v{{.Version}}
|
||||
- id: linux-armhf
|
||||
main: ./cmd/headscale/headscale.go
|
||||
mod_timestamp: '{{ .CommitTimestamp }}'
|
||||
|
@ -39,7 +39,7 @@ builds:
|
|||
flags:
|
||||
- -mod=readonly
|
||||
ldflags:
|
||||
- -s -w -X main.version={{.Version}}
|
||||
- -s -w -X github.com/juanfont/headscale/cmd/headscale/cli.Version=v{{.Version}}
|
||||
|
||||
|
||||
- id: linux-amd64
|
||||
|
@ -54,6 +54,8 @@ builds:
|
|||
- 7
|
||||
main: ./cmd/headscale/headscale.go
|
||||
mod_timestamp: '{{ .CommitTimestamp }}'
|
||||
ldflags:
|
||||
- -s -w -X github.com/juanfont/headscale/cmd/headscale/cli.Version=v{{.Version}}
|
||||
|
||||
archives:
|
||||
- id: golang-cross
|
||||
|
|
15
README.md
15
README.md
|
@ -30,6 +30,17 @@ Headscale implements this coordination server.
|
|||
- [x] Share nodes between ~~users~~ namespaces
|
||||
- [ ] 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 🤷
|
||||
|
||||
Suggestions/PRs welcomed!
|
||||
|
@ -114,7 +125,7 @@ Suggestions/PRs welcomed!
|
|||
7. Add your first machine
|
||||
|
||||
```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.
|
||||
|
@ -154,7 +165,7 @@ Alternatively, you can use Auth Keys to register your machines:
|
|||
|
||||
2. Use the authkey from your machine to register it
|
||||
```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.
|
||||
|
|
29
app.go
29
app.go
|
@ -12,6 +12,7 @@ import (
|
|||
"github.com/rs/zerolog/log"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"golang.org/x/crypto/acme"
|
||||
"golang.org/x/crypto/acme/autocert"
|
||||
"gorm.io/gorm"
|
||||
"inet.af/netaddr"
|
||||
|
@ -46,6 +47,9 @@ type Config struct {
|
|||
TLSCertPath string
|
||||
TLSKeyPath string
|
||||
|
||||
ACMEURL string
|
||||
ACMEEmail string
|
||||
|
||||
DNSConfig *tailcfg.DNSConfig
|
||||
}
|
||||
|
||||
|
@ -185,16 +189,18 @@ func (h *Headscale) Serve() error {
|
|||
r.GET("/apple/:platform", h.ApplePlatformConfig)
|
||||
var err error
|
||||
|
||||
timeout := 30 * time.Second
|
||||
|
||||
go h.watchForKVUpdates(5000)
|
||||
go h.expireEphemeralNodes(5000)
|
||||
|
||||
s := &http.Server{
|
||||
Addr: h.cfg.Addr,
|
||||
Handler: r,
|
||||
ReadTimeout: timeout,
|
||||
WriteTimeout: timeout,
|
||||
ReadTimeout: 30 * time.Second,
|
||||
// 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 != "" {
|
||||
|
@ -206,14 +212,14 @@ func (h *Headscale) Serve() error {
|
|||
Prompt: autocert.AcceptTOS,
|
||||
HostPolicy: autocert.HostWhitelist(h.cfg.TLSLetsEncryptHostname),
|
||||
Cache: autocert.DirCache(h.cfg.TLSLetsEncryptCacheDir),
|
||||
Client: &acme.Client{
|
||||
DirectoryURL: h.cfg.ACMEURL,
|
||||
},
|
||||
Email: h.cfg.ACMEEmail,
|
||||
}
|
||||
s := &http.Server{
|
||||
Addr: h.cfg.Addr,
|
||||
TLSConfig: m.TLSConfig(),
|
||||
Handler: r,
|
||||
ReadTimeout: timeout,
|
||||
WriteTimeout: timeout,
|
||||
}
|
||||
|
||||
s.TLSConfig = m.TLSConfig()
|
||||
|
||||
if h.cfg.TLSLetsEncryptChallengeType == "TLS-ALPN-01" {
|
||||
// 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
|
||||
|
@ -224,7 +230,6 @@ func (h *Headscale) Serve() error {
|
|||
// port 80 for the certificate validation in addition to the headscale
|
||||
// service, which can be configured to run on any other port.
|
||||
go func() {
|
||||
|
||||
log.Fatal().
|
||||
Err(http.ListenAndServe(h.cfg.TLSLetsEncryptListen, m.HTTPHandler(http.HandlerFunc(h.redirect)))).
|
||||
Msg("failed to set up a HTTP server")
|
||||
|
|
|
@ -186,6 +186,9 @@ func getHeadscaleApp() (*headscale.Headscale, error) {
|
|||
TLSKeyPath: absPath(viper.GetString("tls_key_path")),
|
||||
|
||||
DNSConfig: dnsConfig,
|
||||
|
||||
ACMEEmail: viper.GetString("acme_email"),
|
||||
ACMEURL: viper.GetString("acme_url"),
|
||||
}
|
||||
|
||||
h, err := headscale.NewHeadscale(cfg)
|
||||
|
|
|
@ -10,6 +10,8 @@
|
|||
"db_name": "headscale",
|
||||
"db_user": "foo",
|
||||
"db_pass": "bar",
|
||||
"acme_url": "https://acme-v02.api.letsencrypt.org/directory",
|
||||
"acme_email": "",
|
||||
"tls_letsencrypt_hostname": "",
|
||||
"tls_letsencrypt_listen": ":http",
|
||||
"tls_letsencrypt_cache_dir": ".cache",
|
||||
|
|
|
@ -6,6 +6,8 @@
|
|||
"ephemeral_node_inactivity_timeout": "30m",
|
||||
"db_type": "sqlite3",
|
||||
"db_path": "db.sqlite",
|
||||
"acme_url": "https://acme-v02.api.letsencrypt.org/directory",
|
||||
"acme_email": "",
|
||||
"tls_letsencrypt_hostname": "",
|
||||
"tls_letsencrypt_listen": ":http",
|
||||
"tls_letsencrypt_cache_dir": ".cache",
|
||||
|
|
|
@ -433,7 +433,7 @@ func (s *IntegrationTestSuite) TestPingAllPeers() {
|
|||
command := []string{
|
||||
"tailscale", "ping",
|
||||
"--timeout=1s",
|
||||
"--c=20",
|
||||
"--c=10",
|
||||
"--until-direct=true",
|
||||
ip.String(),
|
||||
}
|
||||
|
|
|
@ -317,7 +317,8 @@ func (h *Headscale) notifyChangesToPeers(m *Machine) {
|
|||
Str("func", "notifyChangesToPeers").
|
||||
Str("machine", m.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().
|
||||
Str("func", "notifyChangesToPeers").
|
||||
|
@ -388,11 +389,12 @@ func (h *Headscale) sendRequestOnUpdateChannel(m *tailcfg.Node) error {
|
|||
Msgf("Notified machine %s", m.Name)
|
||||
}
|
||||
} else {
|
||||
err := errors.New("machine does not have an open update channel")
|
||||
log.Info().
|
||||
Str("func", "requestUpdate").
|
||||
Str("machine", m.Name).
|
||||
Msgf("Machine %s does not appear to be polling", m.Name)
|
||||
return errors.New("machine does not seem to be polling")
|
||||
Msgf("Machine %s does not have an open update channel", m.Name)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
31
poll.go
31
poll.go
|
@ -230,6 +230,7 @@ func (h *Headscale) PollNetMapStream(
|
|||
Str("channel", "pollData").
|
||||
Err(err).
|
||||
Msg("Cannot write data")
|
||||
return false
|
||||
}
|
||||
log.Trace().
|
||||
Str("handler", "PollNetMapStream").
|
||||
|
@ -237,7 +238,7 @@ func (h *Headscale) PollNetMapStream(
|
|||
Str("channel", "pollData").
|
||||
Int("bytes", len(data)).
|
||||
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
|
||||
// command line, but then overwritten.
|
||||
err = h.UpdateMachine(&m)
|
||||
|
@ -258,7 +259,7 @@ func (h *Headscale) PollNetMapStream(
|
|||
Str("machine", m.Name).
|
||||
Str("channel", "pollData").
|
||||
Int("bytes", len(data)).
|
||||
Msg("Machine updated successfully after sending pollData")
|
||||
Msg("Machine entry in database updated successfully after sending pollData")
|
||||
return true
|
||||
|
||||
case data := <-keepAliveChan:
|
||||
|
@ -276,6 +277,7 @@ func (h *Headscale) PollNetMapStream(
|
|||
Str("channel", "keepAlive").
|
||||
Err(err).
|
||||
Msg("Cannot write keep alive message")
|
||||
return false
|
||||
}
|
||||
log.Trace().
|
||||
Str("handler", "PollNetMapStream").
|
||||
|
@ -283,7 +285,7 @@ func (h *Headscale) PollNetMapStream(
|
|||
Str("channel", "keepAlive").
|
||||
Int("bytes", len(data)).
|
||||
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
|
||||
// command line, but then overwritten.
|
||||
err = h.UpdateMachine(&m)
|
||||
|
@ -336,6 +338,7 @@ func (h *Headscale) PollNetMapStream(
|
|||
Str("channel", "update").
|
||||
Err(err).
|
||||
Msg("Could not write the map response")
|
||||
return false
|
||||
}
|
||||
log.Trace().
|
||||
Str("handler", "PollNetMapStream").
|
||||
|
@ -347,7 +350,7 @@ func (h *Headscale) PollNetMapStream(
|
|||
// we sometimes end in a state were the update
|
||||
// is not picked up by a client and we use this
|
||||
// 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
|
||||
// command line, but then overwritten.
|
||||
err = h.UpdateMachine(&m)
|
||||
|
@ -393,13 +396,33 @@ func (h *Headscale) PollNetMapStream(
|
|||
m.LastSeen = &now
|
||||
h.db.Save(&m)
|
||||
|
||||
log.Trace().
|
||||
Str("handler", "PollNetMapStream").
|
||||
Str("machine", m.Name).
|
||||
Str("channel", "Done").
|
||||
Msg("Cancelling keepAlive channel")
|
||||
cancelKeepAlive <- struct{}{}
|
||||
|
||||
log.Trace().
|
||||
Str("handler", "PollNetMapStream").
|
||||
Str("machine", m.Name).
|
||||
Str("channel", "Done").
|
||||
Msg("Closing update channel")
|
||||
h.closeUpdateChannel(&m)
|
||||
|
||||
close(pollDataChan)
|
||||
log.Trace().
|
||||
Str("handler", "PollNetMapStream").
|
||||
Str("machine", m.Name).
|
||||
Str("channel", "Done").
|
||||
Msg("Closing pollData channel")
|
||||
|
||||
close(keepAliveChan)
|
||||
log.Trace().
|
||||
Str("handler", "PollNetMapStream").
|
||||
Str("machine", m.Name).
|
||||
Str("channel", "Done").
|
||||
Msg("Closing keepAliveChan channel")
|
||||
|
||||
return false
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue