Adds headscale app.
This commit is contained in:
5
headscale/app.yaml
Normal file
5
headscale/app.yaml
Normal file
@@ -0,0 +1,5 @@
|
||||
name: headscale
|
||||
is: headscale
|
||||
description: Headscale is a self-hosted, open-source implementation of the Tailscale control server, enabling you to manage your own private WireGuard-based mesh VPN network.
|
||||
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/headscale.svg
|
||||
latest: "1"
|
||||
65
headscale/versions/1/README.md
Normal file
65
headscale/versions/1/README.md
Normal file
@@ -0,0 +1,65 @@
|
||||
# Headscale
|
||||
|
||||
Headscale is an open-source, self-hosted implementation of the Tailscale control plane. It lets you run your own private WireGuard-based mesh VPN using the Tailscale client.
|
||||
|
||||
## Configuration
|
||||
|
||||
Key settings in `config.yaml`:
|
||||
|
||||
- **domain** - Where the Headscale control plane API is accessible
|
||||
- **vpnBaseDomain** - Base domain for MagicDNS hostnames assigned to nodes (default: `vpn.{your-cloud-domain}`)
|
||||
- **storage** - Persistent volume size (default: `2Gi`)
|
||||
|
||||
## First-Time Setup
|
||||
|
||||
1. Add and deploy the app:
|
||||
```bash
|
||||
wild app add headscale
|
||||
wild app deploy headscale
|
||||
```
|
||||
|
||||
2. Create a user (namespace) in Headscale:
|
||||
```bash
|
||||
kubectl exec -n headscale deploy/headscale -- headscale users create <username>
|
||||
```
|
||||
|
||||
3. Generate a pre-auth key for that user:
|
||||
```bash
|
||||
kubectl exec -n headscale deploy/headscale -- headscale preauthkeys create --user <user-id> --reusable --expiration 24h
|
||||
```
|
||||
Note: `--user` requires the numeric user ID, not the username. Run `headscale users list` to get the ID.
|
||||
|
||||
4. On each device you want to join the VPN, install the Tailscale client and connect it to your Headscale instance:
|
||||
```bash
|
||||
tailscale up --login-server https://<your-headscale-domain> --authkey <preauthkey>
|
||||
```
|
||||
|
||||
## Registering Nodes via Browser Auth
|
||||
|
||||
If a Tailscale client connects without a pre-auth key, it shows an auth URL in its logs. Register that node with:
|
||||
```bash
|
||||
kubectl exec -n headscale deploy/headscale -- headscale nodes register --auth-id <hskey-authreq-XXXX> --user <username>
|
||||
```
|
||||
Note: `--user` here accepts the username string (not the numeric ID).
|
||||
|
||||
## Useful Commands
|
||||
|
||||
```bash
|
||||
# List users
|
||||
kubectl exec -n headscale deploy/headscale -- headscale users list
|
||||
|
||||
# List connected nodes
|
||||
kubectl exec -n headscale deploy/headscale -- headscale nodes list
|
||||
|
||||
# Create a pre-auth key (requires numeric user ID)
|
||||
kubectl exec -n headscale deploy/headscale -- headscale preauthkeys create --user <id> --reusable
|
||||
|
||||
# Delete a node
|
||||
kubectl exec -n headscale deploy/headscale -- headscale nodes delete --identifier <node-id>
|
||||
```
|
||||
|
||||
## Notes
|
||||
|
||||
- Headscale uses SQLite by default — no external database dependency
|
||||
- Configuration is managed via a ConfigMap (`headscale-config`) — changes require restarting the pod
|
||||
- In Headscale v0.29+, different commands accept different user identifier types: `preauthkeys` and `users destroy` require the numeric ID, while `nodes register` accepts the username string
|
||||
88
headscale/versions/1/configmap.yaml
Normal file
88
headscale/versions/1/configmap.yaml
Normal file
@@ -0,0 +1,88 @@
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: headscale-config
|
||||
namespace: {{ .namespace }}
|
||||
data:
|
||||
config.yaml: |
|
||||
server_url: "https://{{ .domain }}"
|
||||
listen_addr: "0.0.0.0:8080"
|
||||
grpc_listen_addr: "0.0.0.0:50443"
|
||||
grpc_allow_insecure: false
|
||||
metrics_listen_addr: "127.0.0.1:9090"
|
||||
|
||||
# Trust forwarded IPs from Traefik ingress
|
||||
trusted_proxies:
|
||||
- "10.0.0.0/8"
|
||||
- "172.16.0.0/12"
|
||||
- "192.168.0.0/16"
|
||||
|
||||
log:
|
||||
level: info
|
||||
format: text
|
||||
|
||||
database:
|
||||
type: sqlite
|
||||
sqlite:
|
||||
path: /var/lib/headscale/db.sqlite
|
||||
write_ahead_log: true
|
||||
wal_autocheckpoint: 1000
|
||||
|
||||
policy:
|
||||
mode: file
|
||||
path: /etc/headscale/acls.hujson
|
||||
|
||||
tls_letsencrypt_hostname: ""
|
||||
tls_cert_path: ""
|
||||
tls_key_path: ""
|
||||
|
||||
noise:
|
||||
private_key_path: /var/lib/headscale/noise_private.key
|
||||
|
||||
prefixes:
|
||||
v6: fd7a:115c:a1e0::/48
|
||||
v4: 100.64.0.0/10
|
||||
allocation: sequential
|
||||
|
||||
derp:
|
||||
server:
|
||||
enabled: false
|
||||
region_id: 999
|
||||
region_code: headscale
|
||||
region_name: Headscale Embedded DERP
|
||||
stun_listen_addr: "0.0.0.0:3478"
|
||||
private_key_path: /var/lib/headscale/derp_server_private.key
|
||||
automatically_add_embedded_derp_region: true
|
||||
urls:
|
||||
- https://controlplane.tailscale.com/derpmap/default
|
||||
paths: []
|
||||
auto_update_enabled: true
|
||||
update_frequency: 24h
|
||||
|
||||
dns:
|
||||
magic_dns: true
|
||||
base_domain: {{ .vpnBaseDomain }}
|
||||
override_local_dns: true
|
||||
nameservers:
|
||||
global:
|
||||
- 1.1.1.1
|
||||
- 1.0.0.1
|
||||
search_domains: []
|
||||
|
||||
node:
|
||||
ephemeral:
|
||||
inactivity_timeout: 30m
|
||||
|
||||
unix_socket: /var/lib/headscale/headscale.sock
|
||||
unix_socket_permission: "0770"
|
||||
|
||||
acls.hujson: |
|
||||
{
|
||||
"acls": [
|
||||
{
|
||||
"action": "accept",
|
||||
"src": ["*"],
|
||||
"dst": ["*:*"]
|
||||
}
|
||||
]
|
||||
}
|
||||
73
headscale/versions/1/deployment.yaml
Normal file
73
headscale/versions/1/deployment.yaml
Normal file
@@ -0,0 +1,73 @@
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: headscale
|
||||
namespace: {{ .namespace }}
|
||||
spec:
|
||||
replicas: 1
|
||||
strategy:
|
||||
type: Recreate
|
||||
selector:
|
||||
matchLabels:
|
||||
component: server
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
component: server
|
||||
spec:
|
||||
securityContext:
|
||||
runAsNonRoot: false
|
||||
runAsUser: 0
|
||||
seccompProfile:
|
||||
type: RuntimeDefault
|
||||
containers:
|
||||
- name: headscale
|
||||
image: headscale/headscale:v0.29.1
|
||||
args: ["serve"]
|
||||
ports:
|
||||
- name: http
|
||||
containerPort: 8080
|
||||
protocol: TCP
|
||||
- name: grpc
|
||||
containerPort: 50443
|
||||
protocol: TCP
|
||||
resources:
|
||||
limits:
|
||||
cpu: 500m
|
||||
memory: 256Mi
|
||||
requests:
|
||||
cpu: 50m
|
||||
memory: 128Mi
|
||||
volumeMounts:
|
||||
- name: headscale-config
|
||||
mountPath: /etc/headscale
|
||||
- name: headscale-data
|
||||
mountPath: /var/lib/headscale
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /health
|
||||
port: 8080
|
||||
initialDelaySeconds: 30
|
||||
periodSeconds: 30
|
||||
failureThreshold: 3
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: /health
|
||||
port: 8080
|
||||
initialDelaySeconds: 10
|
||||
periodSeconds: 10
|
||||
failureThreshold: 3
|
||||
securityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
capabilities:
|
||||
drop:
|
||||
- ALL
|
||||
readOnlyRootFilesystem: false
|
||||
volumes:
|
||||
- name: headscale-config
|
||||
configMap:
|
||||
name: headscale-config
|
||||
- name: headscale-data
|
||||
persistentVolumeClaim:
|
||||
claimName: headscale-data
|
||||
restartPolicy: Always
|
||||
26
headscale/versions/1/ingress.yaml
Normal file
26
headscale/versions/1/ingress.yaml
Normal file
@@ -0,0 +1,26 @@
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: headscale
|
||||
namespace: headscale
|
||||
annotations:
|
||||
external-dns.alpha.kubernetes.io/target: {{ .externalDnsDomain }}
|
||||
external-dns.alpha.kubernetes.io/cloudflare-proxied: "false"
|
||||
external-dns.alpha.kubernetes.io/ttl: "60"
|
||||
spec:
|
||||
ingressClassName: traefik
|
||||
rules:
|
||||
- host: {{ .domain }}
|
||||
http:
|
||||
paths:
|
||||
- path: /
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: headscale
|
||||
port:
|
||||
number: 80
|
||||
tls:
|
||||
- hosts:
|
||||
- {{ .domain }}
|
||||
secretName: {{ .tlsSecretName }}
|
||||
16
headscale/versions/1/kustomization.yaml
Normal file
16
headscale/versions/1/kustomization.yaml
Normal file
@@ -0,0 +1,16 @@
|
||||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||
kind: Kustomization
|
||||
namespace: headscale
|
||||
labels:
|
||||
- includeSelectors: true
|
||||
pairs:
|
||||
app: headscale
|
||||
managedBy: kustomize
|
||||
partOf: wild-cloud
|
||||
resources:
|
||||
- namespace.yaml
|
||||
- configmap.yaml
|
||||
- pvc.yaml
|
||||
- deployment.yaml
|
||||
- service.yaml
|
||||
- ingress.yaml
|
||||
19
headscale/versions/1/manifest.yaml
Normal file
19
headscale/versions/1/manifest.yaml
Normal file
@@ -0,0 +1,19 @@
|
||||
version: 0.29.1-1
|
||||
scripts:
|
||||
- name: register-node
|
||||
description: Register a Tailscale node using the auth ID shown in the Tailscale app.
|
||||
path: scripts/register-node.sh
|
||||
params:
|
||||
- name: AUTH_ID
|
||||
description: The auth ID shown by the Tailscale app (e.g. hskey-authreq-...)
|
||||
required: true
|
||||
- name: USER
|
||||
description: Headscale username to register the node under
|
||||
required: true
|
||||
defaultConfig:
|
||||
namespace: headscale
|
||||
domain: headscale.{{ .cloud.domain }}
|
||||
externalDnsDomain: "{{ .cloud.domain }}"
|
||||
tlsSecretName: wildcard-wild-cloud-tls
|
||||
storage: 2Gi
|
||||
vpnBaseDomain: vpn.{{ .cloud.domain }}
|
||||
4
headscale/versions/1/namespace.yaml
Normal file
4
headscale/versions/1/namespace.yaml
Normal file
@@ -0,0 +1,4 @@
|
||||
apiVersion: v1
|
||||
kind: Namespace
|
||||
metadata:
|
||||
name: "{{ .namespace }}"
|
||||
11
headscale/versions/1/pvc.yaml
Normal file
11
headscale/versions/1/pvc.yaml
Normal file
@@ -0,0 +1,11 @@
|
||||
apiVersion: v1
|
||||
kind: PersistentVolumeClaim
|
||||
metadata:
|
||||
name: headscale-data
|
||||
namespace: headscale
|
||||
spec:
|
||||
accessModes:
|
||||
- ReadWriteOnce
|
||||
resources:
|
||||
requests:
|
||||
storage: {{ .storage }}
|
||||
18
headscale/versions/1/scripts/register-node.sh
Executable file
18
headscale/versions/1/scripts/register-node.sh
Executable file
@@ -0,0 +1,18 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
if [ -z "$AUTH_ID" ]; then
|
||||
echo "Error: AUTH_ID is required"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ -z "$USER" ]; then
|
||||
echo "Error: USER is required"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
NAMESPACE=$(cat manifest.yaml | grep 'namespace:' | head -1 | awk '{print $2}')
|
||||
|
||||
echo "Registering node for user '$USER' with auth ID: $AUTH_ID"
|
||||
kubectl exec -n "$NAMESPACE" deploy/headscale -- \
|
||||
headscale auth register --auth-id "$AUTH_ID" --user "$USER"
|
||||
18
headscale/versions/1/service.yaml
Normal file
18
headscale/versions/1/service.yaml
Normal file
@@ -0,0 +1,18 @@
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: headscale
|
||||
namespace: headscale
|
||||
spec:
|
||||
type: ClusterIP
|
||||
ports:
|
||||
- name: http
|
||||
port: 80
|
||||
protocol: TCP
|
||||
targetPort: 8080
|
||||
- name: grpc
|
||||
port: 50443
|
||||
protocol: TCP
|
||||
targetPort: 50443
|
||||
selector:
|
||||
component: server
|
||||
Reference in New Issue
Block a user