headplane icon indicating copy to clipboard operation
headplane copied to clipboard

Deep integration without the need for Docker

Open tale opened this issue 1 year ago • 7 comments

I realize that using Docker is a non-starter for a lot of people, so the goal is to support the configuration and ACL changes when Headscale is running outside of Docker. For now this thread will mostly serve as a list of potential solutions and the work needed. This will also be what holds back a 1.0 release for now.

Work that'll need to be done:

  • [x] Supporting multiple different "control methods"
  • [x] Better error handling on misconfigurations
  • [x] Automatic detection where we can
  • [x] Robust process signaling with Node's process.kill

Docker API:

  • [x] Better handling of access to /var/run/docker.sock
  • [x] Support different socket URLs (not just socket files)
  • [x] Is it possible to detect the container automatically?
  • [x] ~~Support Docker API versions below v1.30~~

systemd Driver:

  • [ ] Use systemctl show --property MainPID --value headscale
  • [ ] Handle errors when invalid units or values or provided
  • [ ] Validate the responses from systemctl for pids

pidof/pgrep Method:

  • [x] ~~Ensure that pidof/pgrep is actually available on all systems~~
  • [x] ~~Not resorting to regex to parse the pid out of the output~~
  • [x] ~~Apparently it's actually unreliable?~~

DIY Method:

  • [ ] Support setting up raw bash scripts to run on trigger
  • [ ] Sanity checks, is the script executable, do we have permission?

tale avatar Apr 02 '24 00:04 tale

As mentioned on discord.. the headscale pid can be located within the /proc filesystem, this is probably the same or similar to what pidof does under the covers (I have not looked at the pidof source, just guessing).

Under '/proc` is a numeric directory for every pid. Iterate over those until you find one where /proc/n/cmdline contains /usr/bin/headscale, null, serve, null. That's the process id, send SIGHUP, done!

On my system right now:

root@headscale1:/proc/8641# hexdump -C cmdline
00000000  2f 75 73 72 2f 62 69 6e  2f 68 65 61 64 73 63 61  |/usr/bin/headsca|
00000010  6c 65 00 73 65 72 76 65  00                       |le.serve.|
00000019

That would not require executing anything at all, or being root. It just requires that headplane is running as the same uid or euid as headscale. A requirement that is easily met if both are running behind a reverse proxy.

benmehlman avatar Apr 02 '24 16:04 benmehlman

I'm very much a docker "fanboy" so can only really speak to that method,

Regarding better handling of /var/run/docker.sock I have a docker socket proxy installed for this purpose, so you can define what permissions or elements of the API are accessible. tecnativa/docker-socket-proxy is probably the most widely used, but there are more recent forks such as the one from ZoeyVid, so an option to use tcp connections rather than volume map /var/run/docker.sock would be most welcome from myself.

In terms of supporting docker api versions < v1.30, I'm wondering if that's necessary? As far as I can tell v1.30 was released with Docker Engine 17.06 which is now nearly 7 years old with the docker API v1.24 being the last supported version which was released about 12 months prior in June 2016.

One idea to cut down on support burden for docker container based installs would be to publish an entire stack including

  • headscale
  • headplane
  • docker-socket-proxy (if you wanted to go this route)

Projects such as Immich do, so using my own install as an (incomplete) example it means your README will be specifying a container name in the headscale container, making it a non-technical solution to the issue of grepping whatever random container name people might be using if you don't specify the full stack.

version: '3.9'

services:
   headscale:
       image: headscale/headscale:v0.23.0-alpha5
       container_name: headscale
       command: serve
       networks:
           - traefik
       # ports:
       #     - 8080:8080
       volumes:
           - ${CONFIG}/headscale/config.yml:/etc/headscale/config.yml
           - ${CONFIG}/headscale/acl.json:/etc/headscale/acl.json
           - ${CONFIG}/headscale/headscale:/var/lib/headscale    
       restart: unless-stopped

   headplane:
       image: ghcr.io/tale/headplane:latest
       container_name: headplane
       networks:
           - traefik
       ports:
           - 3000:3000
       environment:
           - COOKIE_SECRET=${HEADPLANE_COOKIE_SECRET}
           - API_KEY=${HEADPLANE_API_KEY}
           - HEADSCALE_CONTAINER=${HEADPLANE_HEADSCALE_CONTAINER}
           - DISABLE_API_KEY_LOGIN=${HEADPLANE_DISABLE_API_KEY_LOGIN}
           - HOST=${HEADPLANE_HOST}
           - PORT=${HEADPLANE_PORT}
           - HEADSCALE_URL=${HEADPLANE_HEADSCALE_URL}
           - CONFIG_FILE=${HEADPLANE_CONFIG_FILE}
           - ACL_FILE=${HEADPLANE_ACL_FILE}
           - OIDC_CLIENT_ID=${HEADPLANE_OIDC_CLIENT_ID}
           - OIDC_ISSUER=${HEADPLANE_OIDC_ISSUER}
           - OIDC_CLIENT_SECRET=${HEADPLANE_OIDC_CLIENT_SECRET}
       volumes:
           - ${CONFIG}/headscale/headscale:/var/lib/headscale 
           - ${CONFIG}/headscale:/etc/headscale
           - /var/run/docker.sock:/var/run/docker.sock:ro
       restart: unless-stopped

Just some random thoughts I had that might or might not help.

Loving the progress so far though!

arcoast avatar Apr 02 '24 18:04 arcoast

I mean, is the proxy 1:1? I was proposing just making a DOCKER_SOCK variable that let's you specify what to use (what ultimately gets passed to the fetch calls). Would that not be enough?

tale avatar Apr 02 '24 20:04 tale

I mean, is the proxy 1:1? I was proposing just making a DOCKER_SOCK variable that let's you specify what to use (what ultimately gets passed to the fetch calls). Would that not be enough?

Yeah, I think that'd be perfect, I was just throwing out ideas. As long as I can send requests to tcp://docker-socket-proxy:2375 I'm golden.

arcoast avatar Apr 02 '24 21:04 arcoast

@arcoast What you suggested is possible in the latest update 0.1.6

tale avatar May 23 '24 23:05 tale

#12 solves half of this issue by implementing the /proc method.

tale avatar May 23 '24 23:05 tale

Now that everything else is implemented, I want to look at a systemd integration and a DIY one before I can call this finished.

tale avatar Aug 03 '24 03:08 tale