improve errors for missing directories (#1765)

* improve errors for missing directories

Fixes #1761
Updates #1760

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

* update container docs

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

* update changelog with /var changes

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

---------

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>
This commit is contained in:
Kristoffer Dalby 2024-02-17 13:36:19 +01:00 committed by GitHub
parent c73e8476b9
commit b60ee9db54
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 51 additions and 2 deletions

View file

@ -36,6 +36,7 @@ after improving the test harness as part of adopting [#1460](https://github.com/
- Add a filepath entry to [`derp.server.private_key_path`](https://github.com/juanfont/headscale/blob/b35993981297e18393706b2c963d6db882bba6aa/config-example.yaml#L95) - Add a filepath entry to [`derp.server.private_key_path`](https://github.com/juanfont/headscale/blob/b35993981297e18393706b2c963d6db882bba6aa/config-example.yaml#L95)
- Docker images are now built with goreleaser (ko) [#1716](https://github.com/juanfont/headscale/pull/1716) [#1763](https://github.com/juanfont/headscale/pull/1763) - Docker images are now built with goreleaser (ko) [#1716](https://github.com/juanfont/headscale/pull/1716) [#1763](https://github.com/juanfont/headscale/pull/1763)
- Entrypoint of container image has changed from shell to headscale, require change from `headscale serve` to `serve` - Entrypoint of container image has changed from shell to headscale, require change from `headscale serve` to `serve`
- `/var/lib/headscale` and `/var/run/headscale` is no longer created automatically, see [container docs](./docs/running-headscale-container.md)
### Changes ### Changes

View file

@ -57,15 +57,22 @@ server_url: http://your-host-name:8080
# Listen to 0.0.0.0 so it's accessible outside the container # Listen to 0.0.0.0 so it's accessible outside the container
metrics_listen_addr: 0.0.0.0:9090 metrics_listen_addr: 0.0.0.0:9090
# The default /var/lib/headscale path is not writable in the container # The default /var/lib/headscale path is not writable in the container
private_key_path: /etc/headscale/private.key
# The default /var/lib/headscale path is not writable in the container
noise: noise:
private_key_path: /etc/headscale/noise_private.key 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 # The default /var/lib/headscale path is not writable in the container
database.type: sqlite3 database.type: sqlite3
database.sqlite.path: /etc/headscale/db.sqlite 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.
4. Start the headscale server while working in the host headscale directory: 4. Start the headscale server while working in the host headscale directory:
```shell ```shell

View file

@ -11,6 +11,7 @@ import (
_ "net/http/pprof" //nolint _ "net/http/pprof" //nolint
"os" "os"
"os/signal" "os/signal"
"path/filepath"
"runtime" "runtime"
"strings" "strings"
"sync" "sync"
@ -69,6 +70,7 @@ const (
AuthPrefix = "Bearer " AuthPrefix = "Bearer "
updateInterval = 5000 updateInterval = 5000
privateKeyFileMode = 0o600 privateKeyFileMode = 0o600
headscaleDirPerm = 0o700
registerCacheExpiration = time.Minute * 15 registerCacheExpiration = time.Minute * 15
registerCacheCleanup = time.Minute * 20 registerCacheCleanup = time.Minute * 20
@ -552,6 +554,12 @@ func (h *Headscale) Serve() error {
return fmt.Errorf("unable to remove old socket file: %w", err) return fmt.Errorf("unable to remove old socket file: %w", err)
} }
socketDir := filepath.Dir(h.cfg.UnixSocket)
err = util.EnsureDir(socketDir)
if err != nil {
return fmt.Errorf("setting up unix socket: %w", err)
}
socketListener, err := net.Listen("unix", h.cfg.UnixSocket) socketListener, err := net.Listen("unix", h.cfg.UnixSocket)
if err != nil { if err != nil {
return fmt.Errorf("failed to set up gRPC socket: %w", err) return fmt.Errorf("failed to set up gRPC socket: %w", err)
@ -919,6 +927,12 @@ func notFoundHandler(
} }
func readOrCreatePrivateKey(path string) (*key.MachinePrivate, error) { func readOrCreatePrivateKey(path string) (*key.MachinePrivate, error) {
dir := filepath.Dir(path)
err := util.EnsureDir(dir)
if err != nil {
return nil, fmt.Errorf("ensuring private key directory: %w", err)
}
privateKey, err := os.ReadFile(path) privateKey, err := os.ReadFile(path)
if errors.Is(err, os.ErrNotExist) { if errors.Is(err, os.ErrNotExist) {
log.Info().Str("path", path).Msg("No private key file at path, creating...") log.Info().Str("path", path).Msg("No private key file at path, creating...")

View file

@ -6,6 +6,7 @@ import (
"errors" "errors"
"fmt" "fmt"
"net/netip" "net/netip"
"path/filepath"
"strconv" "strconv"
"strings" "strings"
"time" "time"
@ -344,6 +345,12 @@ func openDB(cfg types.DatabaseConfig) (*gorm.DB, error) {
switch cfg.Type { switch cfg.Type {
case types.DatabaseSqlite: case types.DatabaseSqlite:
dir := filepath.Dir(cfg.Sqlite.Path)
err := util.EnsureDir(dir)
if err != nil {
return nil, fmt.Errorf("creating directory for sqlite: %w", err)
}
db, err := gorm.Open( db, err := gorm.Open(
sqlite.Open(cfg.Sqlite.Path+"?_synchronous=1&_journal_mode=WAL"), sqlite.Open(cfg.Sqlite.Path+"?_synchronous=1&_journal_mode=WAL"),
&gorm.Config{ &gorm.Config{

View file

@ -1,6 +1,8 @@
package util package util
import ( import (
"errors"
"fmt"
"io/fs" "io/fs"
"os" "os"
"path/filepath" "path/filepath"
@ -42,3 +44,21 @@ func GetFileMode(key string) fs.FileMode {
return fs.FileMode(mode) return fs.FileMode(mode)
} }
func EnsureDir(dir string) error {
if _, err := os.Stat(dir); os.IsNotExist(err) {
err := os.MkdirAll(dir, PermissionFallback)
if err != nil {
if errors.Is(err, os.ErrPermission) {
return fmt.Errorf(
"creating directory %s, failed with permission error, is it located somewhere Headscale can write?",
dir,
)
}
return fmt.Errorf("creating directory %s: %w", dir, err)
}
}
return nil
}