De vez en cuando entro a las listas ‘Awesome’, sobre self-hosting, privacy o como es el caso, en la de awesome-sysadmin.

Aqui empiezo a investigar a ver si hay alguna forma mejor para montar la VPN que tengo para mis cositas, y ahi encuentro a Tailscale y Headscale. El resumen es que ambos se pueden self-hostear pero Tailscale sigue siendo una solucion demasiado completa (aunque si te fias, tienen un plan personal por 0€). Asi que tenemos la opcion DIY: Headscale

No tengo nada en contra de usar Wireguard sin aditivos, pero bueno, por probar?.

El proceso para montarlo:

En mi caso voy a usar un servidor VPS con un dominio determinado que a partir de ahora sera el imaginario “dns.com”, y sus respectivos subdominios “vpn.dns.com” y “web.dns.com”

Dentro del servidor, dentro de una carpeta para agrupar todo, como ’/home/user/Headscale/’, y dentro de la misma, todas estas:

mkdir config headplane-config headplane-data lib run

En esta misma carpeta, el docker-compose.yaml magico que se encargara de casi todo:

services:
  headscale:
    image: docker.io/headscale/headscale:0.26.1
    restart: unless-stopped
    container_name: headscale
    ports:
      - 8080
      - 9090
    volumes:
      - ./config:/etc/headscale
      - ./lib:/var/lib/headscale
      - ./run:/var/run/headscale
    command: serve
    networks:
      - "traefik_net"
    labels:
      - traefik.enable=true
      - traefik.http.middlewares.headscale-cors.headers.accessControlAllowOriginList=https://web.dns.com
      - traefik.http.middlewares.headscale-cors.headers.accessControlAllowMethods=GET,POST,PUT
      - traefik.http.middlewares.headscale-cors.headers.accessControlAllowHeaders=*
      - traefik.http.middlewares.headscale-cors.headers.accessControlMaxAge=100
      - traefik.http.middlewares.headscale-cors.headers.addVaryHeader=true
      - traefik.http.routers.headscale-http.rule=Host("vpn.dns.com")
      - traefik.http.routers.headscale-http.entrypoints=web
      - traefik.http.routers.headscale-http.middlewares=redirect-to-https
      - traefik.http.routers.headscale-secure.rule=Host("vpn.dns.com")
      - traefik.http.routers.headscale-secure.entrypoints=websecure
      - traefik.http.routers.headscale-secure.tls=true
      - traefik.http.routers.headscale-secure.tls.certresolver=myresolver
      - traefik.http.routers.headscale-secure.middlewares=headscale-cors@docker
      - traefik.http.services.headscale-service.loadbalancer.server.port=8080

  headplane:
    image: ghcr.io/tale/headplane:0.6.0
    container_name: headplane
    restart: unless-stopped
    ports:
      - 3000
    volumes:
      - ./headplane-config/config.yaml:/etc/headplane/config.yaml
      - ./config/config.yaml:/etc/headscale/config.yaml
      - ./headplane-data:/var/lib/headplane
      - /var/run/docker.sock:/var/run/docker.sock:ro
    networks:
      - "traefik_net"
    labels:
      - traefik.enable=true
      - traefik.http.routers.headplane-http.rule=Host("web.dns.com")
      - traefik.http.routers.headplane-http.entrypoints=web
      - traefik.http.routers.headplane-http.middlewares=redirect-to-https
      - traefik.http.routers.headplane-secure.rule=Host("web.dns.com")
      - traefik.http.routers.headplane-secure.entrypoints=websecure
      - traefik.http.routers.headplane-secure.tls=true
      - traefik.http.routers.headplane-secure.tls.certresolver=myresolver
      - traefik.http.services.headplane-service.loadbalancer.server.port=3000
networks:
  traefik_net:
    external: true

Los archivos de configuracion de cada cosa, parten de los ejemplo que hay en:

Como se puede ver en las 10000 labels del docker-compose, voy a usar Traefik para gestiona todo como proxy inverso, lo estoy intentando cambiar por nginx ya que creo que se adapta mejor a mi forma de hacer las cosas.

Este traefik ira en una carpeta a parte (por claridad), dentro de, por ejemplo: ’/home/user/Traefik/’, y dentro de este docker-compose.yaml:

services:
  traefik:
    image: traefik:v3.5
    container_name: traefik
    command:
      - --api.dashboard=false
      - --providers.docker=true
      - --log.level=DEBUG
      - --providers.docker.exposedbydefault=false
      - --entrypoints.web.address=:80
      - --entrypoints.websecure.address=:443
      - --certificatesresolvers.myresolver.acme.email=correo@yahooooo.com # <- Esto se cambia
      - --certificatesresolvers.myresolver.acme.storage=/acme.json
      - --certificatesresolvers.myresolver.acme.tlschallenge=true
    ports:
      - 80:80
      - 443:443
    networks:
      - "traefik_net"
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - ./acme.json:/acme.json
    labels:
      - traefik.enable=true
      - traefik.http.middlewares.redirect-to-https.redirectscheme.scheme=https
networks:
  traefik_net:
    external: true

Headscale como tal esta pensado para usarse via CLI, pero la verdad es que, pudiendo poner una interfaz grafica, mejora la cosa. A mi me ha gustado [Headplane]https://github.com/tale/headplane)

Asi todo, ejemplo de como usaria via cli:

Para acceder a la utilidad de headscale es a traves de docker asi que el comando completo queda asi

docker exec -it headscale headscale

Creo un alias para evitar el flato de escribir:

alias headscake='docker exec -it headscale headscale'

Y a funcionar: Primera ejecucion del nuevo alias. Algunos ejemplos de comandos

# Crear un apikey, este es el unico que es necesario ejecutar, para despues usar la web
headscake apikeys create

# Crear un usuario chulo
headscake user create usuario_chulo

# Comprobar que existe
headscake users list  # -> apuntar el id del usuario para el siguiente comando

# O sabiendo el nombre del usuario se puede hacer como:
# $(headscake users list | grep "usuario_chulo" | grep -o '^[0-9]\+') 

# Crear una key para que el usuario pueda aceder a la VPN:
headscake preauthkeys create --user 123 --reusable --expiration 24h

# Destruir el usuario :(
headscake user destroy --name usuario_chulo

Ahora si, con todo esto creado, se puede pasar a levantar, primero el docker compose de traefik, y despues el docker compose de headscale. Si todo esta ok, deberiamos de poder acceder a la web UI de Headplane en https://web.dns.com/admin'. Aqui hay que meter la api key de antes.

Landing page de Headplane