Giter Site home page Giter Site logo

ifargle / headscale-webui Goto Github PK

View Code? Open in Web Editor NEW
585.0 8.0 51.0 2.4 MB

A simple Headscale web UI for small-scale deployments.

License: Other

Dockerfile 1.02% Shell 0.02% Python 54.21% CSS 0.89% JavaScript 27.56% HTML 16.31%
flask headscale jquery python tailscale webui javascript docker docker-compose docker-container dockerfile

headscale-webui's Introduction

Headscale-WebUI

A simple Headscale web UI for small-scale deployments.

Screenshots | Installation | Issues


Features

  1. Enable/Disable routes and exit nodes
    • Manage failover routes as well
  2. Add, move, rename, and remove machines
  3. Add and remove users/namespaces
  4. Add and expire PreAuth keys
  5. Add and remove machine tags
  6. View machine details
    • Hostname
    • User associated with the machine
    • IP addresses in the Tailnet
    • Last seen by the control server
    • Last update with the control server
    • Creation date
    • Expiration date (will also display a badge when nearing expiration)
    • PreAuth key associated with the machine
    • Enable / disable routes and exit nodes
    • Add and delete machine tags
  7. Basic and OIDC Authentication
    • OIDC Authentication tested with Authelia and Keycloak
  8. Change your color theme! See MaterializeCSS Documentation for Colors for examples.
  9. Search your machines and users.
    • Machines have tags you can use to filter search:
      • tag:tagname Searches only for specific tags
      • machine:machine-name Searches only for specific machines
      • user:user-name Searches only for specific users

Installation

  • See SETUP.md for installation and configuration instructions.

Screenshots:

Overview Routes Machines Users Settings


Tech used:

For Python libraries, see pyproject.toml

If you use this project, please reach out! It keeps me motivated! Thank you!

headscale-webui's People

Contributors

ankitgyawali avatar atb00ker avatar ifargle avatar itbencn avatar marekpikula avatar mattcen avatar nicko170 avatar qiangyt avatar rohow avatar spomata avatar ssmidge avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

headscale-webui's Issues

Error 500 when visiting /machines endpoint

Hi, I managed to run the webui container in a rootless podman setup / reverse proxy via caddy (I can pass you the details so that they can be added to documentation).

On visiting the machines page, it crashes constantly. Pasting the output here:

[2023-03-15 12:41:53,102] ERROR in app: Exception on /machines [GET] Traceback (most recent call last): File "/app/.venv/lib/python3.11/site-packages/flask/app.py", line 2528, in wsgi_app response = self.full_dispatch_request() ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/app/.venv/lib/python3.11/site-packages/flask/app.py", line 1825, in full_dispatch_request rv = self.handle_user_exception(e) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/app/.venv/lib/python3.11/site-packages/flask/app.py", line 1823, in full_dispatch_request rv = self.dispatch_request() ^^^^^^^^^^^^^^^^^^^^^^^ File "/app/.venv/lib/python3.11/site-packages/flask/app.py", line 1799, in dispatch_request return self.ensure_sync(self.view_functions[rule.endpoint])(**view_args) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/app/server.py", line 115, in decorated return view_func(*args, **kwargs) ^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/app/server.py", line 180, in machines_page cards = renderer.render_machines_cards() ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/app/renderer.py", line 425, in render_machines_cards content = content+str(sorted_machines[index]) ~~~~~~~~~~~~~~~^^^^^^^ KeyError: 0

Basic Auth - Critical worker timeout error

[2023-03-25 12:03:28 +0700] [1] [INFO] Starting gunicorn 20.1.0
[2023-03-25 12:03:28 +0700] [1] [INFO] Listening at: http://0.0.0.0:5000 (1)
[2023-03-25 12:03:28 +0700] [1] [INFO] Using worker: sync
[2023-03-25 12:03:28 +0700] [8] [INFO] Booting worker with pid: 8
[2023-03-25 12:03:30,351] INFO in server: Headscale-WebUI Version:  v0.5.6 / main
[2023-03-25 12:03:30,351] INFO in server: LOG LEVEL SET TO DEBUG
[2023-03-25 12:03:30,351] INFO in server: DEBUG STATE:  True
[2023-03-25 12:03:30,352] INFO in server: Loading basic auth libraries and configuring app...
[2023-03-25 12:06:38 +0700] [1] [CRITICAL] WORKER TIMEOUT (pid:8)
[2023-03-25 12:06:38 +0700] [8] [INFO] Worker exiting (pid: 8)
[2023-03-25 12:06:38 +0700] [9] [INFO] Booting worker with pid: 9
[2023-03-25 12:06:39,526] INFO in server: Headscale-WebUI Version:  v0.5.6 / main
[2023-03-25 12:06:39,526] INFO in server: LOG LEVEL SET TO DEBUG
[2023-03-25 12:06:39,527] INFO in server: DEBUG STATE:  True
[2023-03-25 12:06:39,527] INFO in server: Loading basic auth libraries and configuring app...

Just tried today and keep getting bad gateway when trying to access the webui. Everytime I tried to access the URL, I always got worker exiting in the log.

Any idea what's wrong?

Originally posted by @inlophe in #60 (comment)

v0.6.0 Upgrades

  • Upgrade to the latest version of MaterializeCSS
  • Add UI for in-page search/filter to Machines/Users page
  • Add code for in-page search funcionality
  • Add a dark-mode toggle? - Not available in stable yet
  • Fix for #53
  • Fix adding tags. Currently broken.
  • #55
  • #54
  • Merge exit routes "0.0.0.0/0" and "::/0" into a single "Exit Route" button
  • Clean up route generation code
  • #63

internal server error

I can not connect to headscale-webui after docker-compose up
here is the docker log

2023-03-23 09:04:37 [chchang@cowbay headscale-webui  main ]$ docker logs -f headscale-webui 
[2023-03-23 09:04:36 +0800] [1] [INFO] Starting gunicorn 20.1.0
[2023-03-23 09:04:36 +0800] [1] [INFO] Listening at: http://0.0.0.0:5000 (1)
[2023-03-23 09:04:36 +0800] [1] [INFO] Using worker: sync
[2023-03-23 09:04:36 +0800] [8] [INFO] Booting worker with pid: 8
[2023-03-23 09:04:38,185] INFO in server: Headscale-WebUI Version:  v0.5.6 / main
[2023-03-23 09:04:38,185] INFO in server: LOG LEVEL SET TO INFO
[2023-03-23 09:04:38,185] INFO in server: DEBUG STATE:  False
[2023-03-23 09:04:48 +0800] [8] [ERROR] Error handling request /
Traceback (most recent call last):
  File "/app/.venv/lib/python3.11/site-packages/gunicorn/workers/sync.py", line 136, in handle
    self.handle_request(listener, req, client, addr)
  File "/app/.venv/lib/python3.11/site-packages/gunicorn/workers/sync.py", line 169, in handle_request
    resp, environ = wsgi.create(req, client, addr,
                    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/app/.venv/lib/python3.11/site-packages/gunicorn/http/wsgi.py", line 183, in create
    path_info = path_info.split(script_name, 1)[1]
                ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^
IndexError: list index out of range
[2023-03-23 09:04:49 +0800] [8] [ERROR] Error handling request /favicon.ico
Traceback (most recent call last):
  File "/app/.venv/lib/python3.11/site-packages/gunicorn/workers/sync.py", line 136, in handle
    self.handle_request(listener, req, client, addr)
  File "/app/.venv/lib/python3.11/site-packages/gunicorn/workers/sync.py", line 169, in handle_request
    resp, environ = wsgi.create(req, client, addr,
                    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/app/.venv/lib/python3.11/site-packages/gunicorn/http/wsgi.py", line 183, in create
    path_info = path_info.split(script_name, 1)[1]
                ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^
IndexError: list index out of range

and docker-compose.yml

version: "3"
services:
  headscale-webui:
    image: ifargle/headscale-webui:latest
    container_name: headscale-webui
    ports:
      - 127.0.0.1:8002:5000                                                                        
    environment:

any suggestions ??

always suck at Loading basic auth libraries and configuring app...

docker compose up

[+] Running 1/1
⠿ Container headscale-webui Created 0.2s
Attaching to headscale-webui
headscale-webui | [2023-03-25 11:51:46 +0800] [1] [INFO] Starting gunicorn 20.1.0
headscale-webui | [2023-03-25 11:51:46 +0800] [1] [INFO] Listening at: http://0.0.0.0:5000 (1)
headscale-webui | [2023-03-25 11:51:46 +0800] [1] [INFO] Using worker: sync
headscale-webui | [2023-03-25 11:51:46 +0800] [8] [INFO] Booting worker with pid: 8
headscale-webui | [2023-03-25 11:51:49,126] INFO in server: Headscale-WebUI Version: v0.5.6 / main
headscale-webui | [2023-03-25 11:51:49,126] INFO in server: LOG LEVEL SET TO DEBUG
headscale-webui | [2023-03-25 11:51:49,126] INFO in server: DEBUG STATE: True
headscale-webui | [2023-03-25 11:51:49,127] INFO in server: Loading basic auth libraries and configuring app...

Fix code scanning alert

Unknown Manifest error

Docker pull is resulting in following error

❯ docker pull ghcr.io/ifargle/headscale-webui:latest
v0.5.5: Pulling from ifargle/headscale-webui
manifest unknown

Tested it on different machines across different geographic locations

Unable to validate the API key

I followed the instructions shown on the website, yet it displays the following when I enter the API key that I just generated from my control server

Error
Key authentication failed. Check your key.

this is my docker-compose file (sorry for the multiple issues, thanks a bunch!)

version: "3"
services:
  headscale-webui:
    image: ghcr.io/ifargle/headscale-webui:latest
    container_name: headscale-webui
    ports:
      - 5000:5000
    environment:
      - TZ=PST
      - HS_SERVER=http://10.1.0.2:27896
      - BASE_PATH="/admin"
      - KEY="some-base64-key"
    volumes:
      - /root/docker/headscale-webui:/data
      - /root/docker/headscale/config/:/etc/headscale/:ro
    restart: unless-stopped

OpenID Connect error thrown when disabled

An error gets thrown when I try to load the main page, it happens when "oidc" is commented out in the headscale configuration file.

Docker-compose:

version: '3.5'
services:
headscale:
image: headscale/headscale:latest
container_name: headscale
volumes:
- ./config:/etc/headscale
- ./data/data:/var/lib/headscale
ports:
- 8080:8080
command: headscale serve
restart: unless-stopped
headscale-webui:
image: ghcr.io/ifargle/headscale-webui:latest
container_name: headscale-webui
depends_on:
- headscale
ports:
- 5000:5000
environment:
- TZ=America/Toronto
- HS_SERVER=[REDACTED]
- BASE_PATH="/admin"
- KEY="[REDACTED]"
volumes:
- ./volume:/data
- ./config/:/etc/headscale/

Log:

headscale-webui | 2023-02-10 21:24:24,628 - server - ERROR - Exception on /admin/ [GET]
headscale-webui | Traceback (most recent call last):
headscale-webui | File "/app/.venv/lib/python3.11/site-packages/flask/app.py", line 2525, in wsgi_app
headscale-webui | response = self.full_dispatch_request()
headscale-webui | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
headscale-webui | File "/app/.venv/lib/python3.11/site-packages/flask/app.py", line 1822, in full_dispatch_request
headscale-webui | rv = self.handle_user_exception(e)
headscale-webui | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
headscale-webui | File "/app/.venv/lib/python3.11/site-packages/flask/app.py", line 1820, in full_dispatch_request
headscale-webui | rv = self.dispatch_request()
headscale-webui | ^^^^^^^^^^^^^^^^^^^^^^^
headscale-webui | File "/app/.venv/lib/python3.11/site-packages/flask/app.py", line 1796, in dispatch_request
headscale-webui | return self.ensure_sync(self.view_functions[rule.endpoint])(**view_args)
headscale-webui | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
headscale-webui | File "/app/server.py", line 39, in overview_page
headscale-webui | render_page = renderer.render_overview(),
headscale-webui | ^^^^^^^^^^^^^^^^^^^^^^^^^^
headscale-webui | File "/app/renderer.py", line 106, in render_overview
headscale-webui | if config_yaml["oidc"]:

Server URL shown on "add machine to Headscale" is the internal URL

When I select "add machine to URL" the explanation to use Tailscale is showing the Headscale URL set at HS_SERVER enviroment variable.
The problem is that this URL is the internal connection I set between headscale and headscale-webui containers (http://192.168.2.5:8088).
May be it is better to use the URL taken from config.yaml. That is actually being shown in the overview page. The server_url variable.

Make proxying easier

Instructions aren't great and it could potentially be improved both by documentation and by updating the app itself to better handle edge cases.

redirect loop with traefik

When I try to go to the main page I get an infinite redirect. Until my browser gives up and gives me an "error: redirect error".

I guess I might have a thinking error somewhere or a bug in traefik, I'm not a traefik expert ^^

By the way, if I call directly the subpages like https://vpnadmin.example.com/settings or https://vpnadmin.example.com/overview then it works.

Because of my little experience with traefik, I had rather used an extra subdomain and not the path "/admin". I think it is actually nicer to reach the admin interface at vpn.example.com/admin.

My docker-compose:

version: '3.5'

services:
  headscale:
    image: headscale/headscale:0
    command: headscale serve
    restart: unless-stopped
    networks:
      - proxy
    volumes:
      - /opt/dockerprojekte/headscale/data/hsconfig:/etc/headscale/
      - hs-data:/var/lib/headscale
    ports:
      - 27896:8080
      - 3478:3478/udp
    environment:
      - TZ=Europe/Berlin
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.${TRAEFIKROUTERNAME}.entrypoints=http"
      - "traefik.http.routers.${TRAEFIKROUTERNAME}.rule=Host(`${DOMAIN}`)"
      - "traefik.http.middlewares.${TRAEFIKROUTERNAME}-https-redirect.redirectscheme.scheme=https"
      - "traefik.http.routers.${TRAEFIKROUTERNAME}.middlewares=${TRAEFIKROUTERNAME}-https-redirect"
      - "traefik.http.routers.${TRAEFIKROUTERNAME}-secure.entrypoints=https"
      - "traefik.http.routers.${TRAEFIKROUTERNAME}-secure.rule=Host(`${DOMAIN}`)"
      - "traefik.http.routers.${TRAEFIKROUTERNAME}-secure.tls=true"
      - "traefik.http.routers.${TRAEFIKROUTERNAME}-secure.service=${TRAEFIKROUTERNAME}"
      - "traefik.http.routers.${TRAEFIKROUTERNAME}-secure.tls.certresolver=http01"
      - "traefik.http.routers.${TRAEFIKROUTERNAME}-secure.tls.domains[0].main=${DOMAIN}"
      - "traefik.http.services.${TRAEFIKROUTERNAME}.loadbalancer.server.port=8080"
      - "traefik.docker.network=proxy"

  headscale-webui:
    image: ghcr.io/ifargle/headscale-webui:latest
    networks:
      - proxy
    environment:
      - TZ=Europe/Berlin
      - COLOR=red                              # Use the base colors (ie, no darken-3, etc) - 
      - HS_SERVER=http://headscale:8080        # Reachable endpoint for your Headscale server
      - DOMAIN_NAME=https://$DOMAINADMIN       # The base domain name for this container.
      - SCRIPT_NAME=/                          # This is your applications base path (wsgi requires the name "SCRIPT_NAME")
      - KEY                                    # Generate with "openssl rand -base64 32" - used to encrypt your key on disk.
      - AUTH_TYPE=Basic                        # AUTH_TYPE is either Basic or OIDC.  Empty for no authentication
      - LOG_LEVEL=info                         # Log level.  "DEBUG", "ERROR", "WARNING", or "INFO".  Default "INFO"
      # ENV for Basic Auth (Used only if AUTH_TYPE is "Basic").  Can be omitted if you aren't using Basic Auth
      - BASIC_AUTH_USER
      - BASIC_AUTH_PASS
      # ENV for OIDC (Used only if AUTH_TYPE is "OIDC").  Can be omitted if you aren't using OIDC
#      - OIDC_AUTH_URL=https://auth.$DOMAIN/.well-known/openid-configuration # URL for your OIDC issuer's well-known endpoint
#      - OIDC_CLIENT_ID=headscale-webui         # Your OIDC Issuer's Client ID for Headscale-WebUI
#      - OIDC_CLIENT_SECRET=YourSecretHere      # Your OIDC Issuer's Secret Key for Headscale-WebUI
    volumes:
      - hsweb-data:/data                         # Headscale-WebUI's storage.  Make sure ./volume is readable by UID 1000 (chown 1000:1000 ./volume)
      - /opt/dockerprojekte/headscale/data/hsconfig:/etc/headscale/:ro # Headscale's config storage location.  Used to read your Headscale config.
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.${TRAEFIKROUTERNAME2}.entrypoints=http"
      - "traefik.http.routers.${TRAEFIKROUTERNAME2}.rule=Host(`${DOMAINADMIN}`)"
      - "traefik.http.middlewares.${TRAEFIKROUTERNAME2}-https-redirect.redirectscheme.scheme=https"
      - "traefik.http.routers.${TRAEFIKROUTERNAME2}.middlewares=${TRAEFIKROUTERNAME2}-https-redirect"
      - "traefik.http.routers.${TRAEFIKROUTERNAME2}-secure.entrypoints=https"
      - "traefik.http.routers.${TRAEFIKROUTERNAME2}-secure.rule=Host(`${DOMAINADMIN}`)"
      - "traefik.http.routers.${TRAEFIKROUTERNAME2}-secure.tls=true"
      - "traefik.http.routers.${TRAEFIKROUTERNAME2}-secure.service=${TRAEFIKROUTERNAME2}"
      # redirect /admin to /
      # - "traefik.http.middlewares.${TRAEFIKROUTERNAME2}-stripprefix.stripprefix.forceslash=true"
      # - "traefik.http.middlewares.${TRAEFIKROUTERNAME2}-stripprefix.stripprefix.prefixes=/admin/"
      # - 'traefik.http.routers.${TRAEFIKROUTERNAME2}-secure.middlewares=${TRAEFIKROUTERNAME2}-stripprefix@docker'
      - "traefik.http.routers.${TRAEFIKROUTERNAME2}-secure.tls.certresolver=http01"
      - "traefik.http.routers.${TRAEFIKROUTERNAME2}-secure.tls.domains[0].main=${DOMAINADMIN}"
      - "traefik.http.services.${TRAEFIKROUTERNAME2}.loadbalancer.server.port=5000"
      - "traefik.docker.network=proxy"

volumes:
  hs-data:
  hsweb-data:


networks:
  proxy:
    external: true

Environment variables are set in portainer:

TRAEFIKROUTERNAME=headscale
DOMAIN=vpn.example.com
BASIC_AUTH_USER=username
BASIC_AUTH_PASS=123456
KEY=mykey
DOMAINADMIN=vpnadmin.example.com
TRAEFIKROUTERNAME2=headscalewebui

Call with curl:

curl https://vpnadmin.example.com -v -L -u "username:123456"
*   Trying 1.2.3.4:443...
* Connected to vpnadmin.example.com (1.2.3.4) port 443 (#0)
* ALPN, offering h2
* ALPN, offering http/1.1
*  CAfile: /etc/ssl/certs/ca-certificates.crt
*  CApath: /etc/ssl/certs
* TLSv1.0 (OUT), TLS header, Certificate Status (22):
* TLSv1.3 (OUT), TLS handshake, Client hello (1):
* TLSv1.2 (IN), TLS header, Certificate Status (22):
* TLSv1.3 (IN), TLS handshake, Server hello (2):
* TLSv1.2 (IN), TLS header, Finished (20):
* TLSv1.2 (IN), TLS header, Supplemental data (23):
* TLSv1.3 (IN), TLS handshake, Encrypted Extensions (8):
* TLSv1.2 (IN), TLS header, Supplemental data (23):
* TLSv1.3 (IN), TLS handshake, Certificate (11):
* TLSv1.2 (IN), TLS header, Supplemental data (23):
* TLSv1.3 (IN), TLS handshake, CERT verify (15):
* TLSv1.2 (IN), TLS header, Supplemental data (23):
* TLSv1.3 (IN), TLS handshake, Finished (20):
* TLSv1.2 (OUT), TLS header, Finished (20):
* TLSv1.3 (OUT), TLS change cipher, Change cipher spec (1):
* TLSv1.2 (OUT), TLS header, Supplemental data (23):
* TLSv1.3 (OUT), TLS handshake, Finished (20):
* SSL connection using TLSv1.3 / TLS_CHACHA20_POLY1305_SHA256
* ALPN, server accepted to use h2
* Server certificate:
*  subject: CN=vpnadmin.example.com
*  start date: Mar 28 22:22:23 2023 GMT
*  expire date: Jun 26 22:22:22 2023 GMT
*  subjectAltName: host "vpnadmin.example.com" matched cert's "vpnadmin.example.com"
*  issuer: C=US; O=Let's Encrypt; CN=R3
*  SSL certificate verify ok.
* Using HTTP2, server supports multiplexing
* Connection state changed (HTTP/2 confirmed)
* Copying HTTP/2 data in stream buffer to connection buffer after upgrade: len=0
* TLSv1.2 (OUT), TLS header, Supplemental data (23):
* TLSv1.2 (OUT), TLS header, Supplemental data (23):
* TLSv1.2 (OUT), TLS header, Supplemental data (23):
* Server auth using Basic with user 'alex'
* Using Stream ID: 1 (easy handle 0x562671e24e90)
* TLSv1.2 (OUT), TLS header, Supplemental data (23):
> GET / HTTP/2
> Host: vpnadmin.example.com
> authorization: Basic <censored>
> user-agent: curl/7.81.0
> accept: */*
> 
* TLSv1.2 (IN), TLS header, Supplemental data (23):
* TLSv1.3 (IN), TLS handshake, Newsession Ticket (4):
* TLSv1.2 (IN), TLS header, Supplemental data (23):
* Connection state changed (MAX_CONCURRENT_STREAMS == 250)!
* TLSv1.2 (OUT), TLS header, Supplemental data (23):
* TLSv1.2 (IN), TLS header, Supplemental data (23):
* TLSv1.2 (IN), TLS header, Supplemental data (23):
< HTTP/2 308 
< content-type: text/html; charset=utf-8
< date: Wed, 29 Mar 2023 07:57:16 GMT
< location: https://vpnadmin.example.com/
< server: gunicorn
< content-length: 239
< 
* TLSv1.2 (IN), TLS header, Supplemental data (23):
* Ignoring the response-body
* Connection #0 to host vpnadmin.example.com left intact
* Issue another request to this URL: 'https://vpnadmin.example.com/'
* Found bundle for host vpnadmin.example.com: 0x562671e1dff0 [can multiplex]
* Re-using existing connection! (#0) with host vpnadmin.example.com
* Connected to vpnadmin.example.com (1.2.3.4) port 443 (#0)
* Server auth using Basic with user 'alex'
* Using Stream ID: 3 (easy handle 0x562671e24e90)
* TLSv1.2 (OUT), TLS header, Supplemental data (23):
> GET / HTTP/2
> Host: vpnadmin.example.com
> authorization: Basic <censored>
> user-agent: curl/7.81.0
> accept: */*
> 
* TLSv1.2 (IN), TLS header, Supplemental data (23):
< HTTP/2 308 
< content-type: text/html; charset=utf-8
< date: Wed, 29 Mar 2023 07:57:16 GMT
< location: https://vpnadmin.example.com/
< server: gunicorn
< content-length: 239
< 
* TLSv1.2 (IN), TLS header, Supplemental data (23):
* Ignoring the response-body
* Connection #0 to host vpnadmin.example.com left intact
* Issue another request to this URL: 'https://vpnadmin.example.com/'
* Found bundle for host vpnadmin.example.com: 0x562671e1dff0 [can multiplex]
* Re-using existing connection! (#0) with host vpnadmin.example.com
* Connected to vpnadmin.example.com (1.2.3.4) port 443 (#0)
* Server auth using Basic with user 'alex'
* Using Stream ID: 5 (easy handle 0x562671e24e90)
* TLSv1.2 (OUT), TLS header, Supplemental data (23):
> GET / HTTP/2
> Host: vpnadmin.example.com
> authorization: Basic <censored>
> user-agent: curl/7.81.0
> accept: */*
> 
* TLSv1.2 (IN), TLS header, Supplemental data (23):
< HTTP/2 308 
< content-type: text/html; charset=utf-8
< date: Wed, 29 Mar 2023 07:57:16 GMT
< location: https://vpnadmin.example.com/
< server: gunicorn
< content-length: 239
< 
* TLSv1.2 (IN), TLS header, Supplemental data (23):
* Ignoring the response-body
* Connection #0 to host vpnadmin.example.com left intact
* Issue another request to this URL: 'https://vpnadmin.example.com/'
* Found bundle for host vpnadmin.example.com: 0x562671e1dff0 [can multiplex]
* Re-using existing connection! (#0) with host vpnadmin.example.com
* Connected to vpnadmin.example.com (1.2.3.4) port 443 (#0)
* Server auth using Basic with user 'alex'
* Using Stream ID: 7 (easy handle 0x562671e24e90)
* TLSv1.2 (OUT), TLS header, Supplemental data (23):
> GET / HTTP/2
> Host: vpnadmin.example.com
> authorization: Basic <censored>
> user-agent: curl/7.81.0
> accept: */*
> 
* TLSv1.2 (IN), TLS header, Supplemental data (23):
< HTTP/2 308 
< content-type: text/html; charset=utf-8
< date: Wed, 29 Mar 2023 07:57:16 GMT
< location: https://vpnadmin.example.com/
< server: gunicorn
< content-length: 239
< 
* TLSv1.2 (IN), TLS header, Supplemental data (23):
* Ignoring the response-body
* Connection #0 to host vpnadmin.example.com left intact
* Issue another request to this URL: 'https://vpnadmin.example.com/'
* Found bundle for host vpnadmin.example.com: 0x562671e1dff0 [can multiplex]
* Re-using existing connection! (#0) with host vpnadmin.example.com
* Connected to vpnadmin.example.com (1.2.3.4) port 443 (#0)
* Server auth using Basic with user 'alex'
* Using Stream ID: 9 (easy handle 0x562671e24e90)
* TLSv1.2 (OUT), TLS header, Supplemental data (23):
> GET / HTTP/2
> Host: vpnadmin.example.com
> authorization: Basic <censored>
> user-agent: curl/7.81.0
> accept: */*
> 
* TLSv1.2 (IN), TLS header, Supplemental data (23):
< HTTP/2 308 
< content-type: text/html; charset=utf-8
< date: Wed, 29 Mar 2023 07:57:16 GMT
< location: https://vpnadmin.example.com/
< server: gunicorn
< content-length: 239
< 
* TLSv1.2 (IN), TLS header, Supplemental data (23):
* Ignoring the response-body
* Connection #0 to host vpnadmin.example.com left intact
* Issue another request to this URL: 'https://vpnadmin.example.com/'
* Found bundle for host vpnadmin.example.com: 0x562671e1dff0 [can multiplex]
* Re-using existing connection! (#0) with host vpnadmin.example.com
* Connected to vpnadmin.example.com (1.2.3.4) port 443 (#0)
* Server auth using Basic with user 'alex'
* Using Stream ID: b (easy handle 0x562671e24e90)
* TLSv1.2 (OUT), TLS header, Supplemental data (23):
> GET / HTTP/2
> Host: vpnadmin.example.com
> authorization: Basic <censored>
> user-agent: curl/7.81.0
> accept: */*
> 
* TLSv1.2 (IN), TLS header, Supplemental data (23):
< HTTP/2 308 
< content-type: text/html; charset=utf-8
< date: Wed, 29 Mar 2023 07:57:16 GMT
< location: https://vpnadmin.example.com/
< server: gunicorn
< content-length: 239
< 
* TLSv1.2 (IN), TLS header, Supplemental data (23):
* Ignoring the response-body
* Connection #0 to host vpnadmin.example.com left intact
* Issue another request to this URL: 'https://vpnadmin.example.com/'
* Found bundle for host vpnadmin.example.com: 0x562671e1dff0 [can multiplex]
* Re-using existing connection! (#0) with host vpnadmin.example.com
* Connected to vpnadmin.example.com (1.2.3.4) port 443 (#0)
* Server auth using Basic with user 'alex'
* Using Stream ID: d (easy handle 0x562671e24e90)
* TLSv1.2 (OUT), TLS header, Supplemental data (23):
> GET / HTTP/2
> Host: vpnadmin.example.com
> authorization: Basic <censored>
> user-agent: curl/7.81.0
> accept: */*
> 
* TLSv1.2 (IN), TLS header, Supplemental data (23):
< HTTP/2 308 
< content-type: text/html; charset=utf-8
< date: Wed, 29 Mar 2023 07:57:16 GMT
< location: https://vpnadmin.example.com/
< server: gunicorn
< content-length: 239
< 
* TLSv1.2 (IN), TLS header, Supplemental data (23):
* Ignoring the response-body
* Connection #0 to host vpnadmin.example.com left intact
* Issue another request to this URL: 'https://vpnadmin.example.com/'
* Found bundle for host vpnadmin.example.com: 0x562671e1dff0 [can multiplex]
* Re-using existing connection! (#0) with host vpnadmin.example.com
* Connected to vpnadmin.example.com (1.2.3.4) port 443 (#0)
* Server auth using Basic with user 'alex'
* Using Stream ID: f (easy handle 0x562671e24e90)
* TLSv1.2 (OUT), TLS header, Supplemental data (23):
> GET / HTTP/2
> Host: vpnadmin.example.com
> authorization: Basic <censored>
> user-agent: curl/7.81.0
> accept: */*
> 
* TLSv1.2 (IN), TLS header, Supplemental data (23):
< HTTP/2 308 
< content-type: text/html; charset=utf-8
< date: Wed, 29 Mar 2023 07:57:16 GMT
< location: https://vpnadmin.example.com/
< server: gunicorn
< content-length: 239
< 
* TLSv1.2 (IN), TLS header, Supplemental data (23):
* Ignoring the response-body
* Connection #0 to host vpnadmin.example.com left intact
* Issue another request to this URL: 'https://vpnadmin.example.com/'
* Found bundle for host vpnadmin.example.com: 0x562671e1dff0 [can multiplex]
* Re-using existing connection! (#0) with host vpnadmin.example.com
* Connected to vpnadmin.example.com (1.2.3.4) port 443 (#0)
* Server auth using Basic with user 'alex'
* Using Stream ID: 11 (easy handle 0x562671e24e90)
* TLSv1.2 (OUT), TLS header, Supplemental data (23):
> GET / HTTP/2
> Host: vpnadmin.example.com
> authorization: Basic <censored>
> user-agent: curl/7.81.0
> accept: */*
> 
* TLSv1.2 (IN), TLS header, Supplemental data (23):
< HTTP/2 308 
< content-type: text/html; charset=utf-8
< date: Wed, 29 Mar 2023 07:57:16 GMT
< location: https://vpnadmin.example.com/
< server: gunicorn
< content-length: 239
< 
* TLSv1.2 (IN), TLS header, Supplemental data (23):
* Ignoring the response-body
* Connection #0 to host vpnadmin.example.com left intact
* Issue another request to this URL: 'https://vpnadmin.example.com/'
* Found bundle for host vpnadmin.example.com: 0x562671e1dff0 [can multiplex]
* Re-using existing connection! (#0) with host vpnadmin.example.com
* Connected to vpnadmin.example.com (1.2.3.4) port 443 (#0)
* Server auth using Basic with user 'alex'
* Using Stream ID: 13 (easy handle 0x562671e24e90)
* TLSv1.2 (OUT), TLS header, Supplemental data (23):
> GET / HTTP/2
> Host: vpnadmin.example.com
> authorization: Basic <censored>
> user-agent: curl/7.81.0
> accept: */*
> 
* TLSv1.2 (IN), TLS header, Supplemental data (23):
< HTTP/2 308 
< content-type: text/html; charset=utf-8
< date: Wed, 29 Mar 2023 07:57:16 GMT
< location: https://vpnadmin.example.com/
< server: gunicorn
< content-length: 239
< 
* TLSv1.2 (IN), TLS header, Supplemental data (23):
* Ignoring the response-body
* Connection #0 to host vpnadmin.example.com left intact
* Issue another request to this URL: 'https://vpnadmin.example.com/'
* Found bundle for host vpnadmin.example.com: 0x562671e1dff0 [can multiplex]
* Re-using existing connection! (#0) with host vpnadmin.example.com
* Connected to vpnadmin.example.com (1.2.3.4) port 443 (#0)
* Server auth using Basic with user 'alex'
* Using Stream ID: 15 (easy handle 0x562671e24e90)
* TLSv1.2 (OUT), TLS header, Supplemental data (23):
> GET / HTTP/2
> Host: vpnadmin.example.com
> authorization: Basic <censored>
> user-agent: curl/7.81.0
> accept: */*
> 
* TLSv1.2 (IN), TLS header, Supplemental data (23):
< HTTP/2 308 
< content-type: text/html; charset=utf-8
< date: Wed, 29 Mar 2023 07:57:16 GMT
< location: https://vpnadmin.example.com/
< server: gunicorn
< content-length: 239
< 
............ <truncated> ....................
* TLSv1.2 (IN), TLS header, Supplemental data (23):
* Ignoring the response-body
* Connection #0 to host vpnadmin.example.com left intact
* Issue another request to this URL: 'https://vpnadmin.example.com/'
* Found bundle for host vpnadmin.example.com: 0x562671e1dff0 [can multiplex]
* Re-using existing connection! (#0) with host vpnadmin.example.com
* Connected to vpnadmin.example.com (1.2.3.4) port 443 (#0)
* Server auth using Basic with user 'alex'
* Using Stream ID: 65 (easy handle 0x562671e24e90)
* TLSv1.2 (OUT), TLS header, Supplemental data (23):
> GET / HTTP/2
> Host: vpnadmin.example.com
> authorization: Basic <censored>
> user-agent: curl/7.81.0
> accept: */*
> 
* TLSv1.2 (IN), TLS header, Supplemental data (23):
< HTTP/2 308 
< content-type: text/html; charset=utf-8
< date: Wed, 29 Mar 2023 07:57:17 GMT
< location: https://vpnadmin.example.com/
< server: gunicorn
< content-length: 239
< 
* TLSv1.2 (IN), TLS header, Supplemental data (23):
* Ignoring the response-body
* Connection #0 to host vpnadmin.example.com left intact
* Maximum (50) redirects followed
curl: (47) Maximum (50) redirects followed

Error - /data not writable

Error - /data not writable
/data is not writable. Please ensure your permissions are correct. /data mount should be writable by UID/GID 1000:1000.

Context location

Is setting context location still needed as it says in the README? Since the docker image is published in ghcr

Code review and hardening

Hi, first of all, thank you for the great work. I consider using headscale in my company, and having a GUI is crucial for efficient operation. Since VPN is crucial in terms of security for the company, I want to thoroughly review the code of the GUI as it has direct access to all administration tasks (via API key) and is a significant attack surface both from outside and from inside. I have some questions regarding the current code style because it seems to me that some important things are implemented in a poor style, and many low-hanging fruits are left out. I have a few years of experience in Python programming, and honestly, some constructs look to me a little unsettling and make me not want to use this package for production.

  1. Do you use any autoformatter? I personally prefer black as it has a good set of sane defaults and is constrained enough to have repetitive behaviour. If you have your preference it's no problem for me, but let's use it. Formatting code by hand is tedious and error-prone.
  2. You have pylint installed, but maaaany linter warnings seem to be ignored.
  3. I can see that you don't use static type checks. For a security-focused project, it's strongly advised to enable static type checks where possible to prevent obvious bugs from getting to the code base.
  4. Would you like me to spend ~2 days reformating the code, working on static checks, fixing linter warnings and incorporating all these checks to CI?
  5. After some basic code style and lining fixes, I would like to do a security review.

Internal Server Error

After Setting up Docker Container with:

version: "3"
services:
  headscale-webui:
    image: ghcr.io/ifargle/headscale-webui:latest
    container_name: headscale-webui
    environment:
      - TZ=Europe/Berlin
      - COLOR=red                              
      - HS_SERVER=http://domain:port 
      - SCRIPT_NAME=/admin                     
      - KEY="randomkey"             
      - LOG_LEVEL=info                
    volumes:
      - ./volume:/data                         
      - ./headscale/config/:/etc/headscale/:ro 

Server Starts as expected

Trying to open the panel on http://domain:5000 gives:

Internal Server Error

In Logs Error shows:

[2023-03-09 00:07:08 +0100] [1] [INFO] Starting gunicorn 20.1.0
[2023-03-09 00:07:08 +0100] [1] [INFO] Listening at: http://0.0.0.0:5000 (1)
[2023-03-09 00:07:08 +0100] [1] [INFO] Using worker: sync
[2023-03-09 00:07:08 +0100] [7] [INFO] Booting worker with pid: 7
[2023-03-09 00:07:10 +0100] [7] [ERROR] Error handling request /
Traceback (most recent call last):
  File "/app/.venv/lib/python3.11/site-packages/gunicorn/workers/sync.py", line 136, in handle
    self.handle_request(listener, req, client, addr)
  File "/app/.venv/lib/python3.11/site-packages/gunicorn/workers/sync.py", line 169, in handle_request
    resp, environ = wsgi.create(req, client, addr,
                    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/app/.venv/lib/python3.11/site-packages/gunicorn/http/wsgi.py", line 183, in create
    path_info = path_info.split(script_name, 1)[1]
                ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^
IndexError: list index out of range
[2023-03-09 00:07:18 +0100] [7] [ERROR] Error handling request /
Traceback (most recent call last):
  File "/app/.venv/lib/python3.11/site-packages/gunicorn/workers/sync.py", line 136, in handle
    self.handle_request(listener, req, client, addr)
  File "/app/.venv/lib/python3.11/site-packages/gunicorn/workers/sync.py", line 169, in handle_request
    resp, environ = wsgi.create(req, client, addr,
                    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/app/.venv/lib/python3.11/site-packages/gunicorn/http/wsgi.py", line 183, in create
    path_info = path_info.split(script_name, 1)[1]
                ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^
IndexError: list index out of range

Cannot connect to headscale

I tried to set this webui up, but failed. I did everything according to instructions.

    To generate your API key, run the command headscale apikeys create on your control server. Once you generate your first key, this UI will automatically renew the key near expiration.
    The Headscale server is configured via the HS_SERVER environment variable in Docker. Current server: https://headscale.iluvatyr.com
    You must configure an encryption key via the KEY environment variable in Docker. One can be generated with the command openssl rand -base64 32

My encryption key is setup via the docker-compose.yml and within two double quotes "".

The headscale-webui is pointing to the correct headscale-instance. But when entering the API-Key I created within headscale, it gives errors and cannot connect.

[2023-03-31 17:51:56 +0200] [1] [INFO] Starting gunicorn 20.1.0
[2023-03-31 17:51:56 +0200] [1] [INFO] Listening at: http://0.0.0.0:5000 (1)
[2023-03-31 17:51:56 +0200] [1] [INFO] Using worker: sync
[2023-03-31 17:51:56 +0200] [8] [INFO] Booting worker with pid: 8
[2023-03-31 17:51:57,113] INFO in server: Headscale-WebUI Version:  v0.6.0 / main
[2023-03-31 17:51:57,114] INFO in server: LOG LEVEL SET TO DEBUG
[2023-03-31 17:51:57,114] INFO in server: DEBUG STATE:  True
[2023-03-31 17:51:57,114] INFO in server: Loading basic auth libraries and configuring app...
[2023-03-31 17:52:07,187] ERROR in helper: /data/key.txt EXIST: FAILED - NO ERROR
[2023-03-31 17:52:07,188] INFO in helper: All startup checks passed.
[2023-03-31 17:52:07,189] INFO in helper: Testing API key validity.
[2023-03-31 17:52:07,263] INFO in helper: Got a non-200 response from Headscale.  Test failed (Response:  500)
[2023-03-31 17:52:07,452] ERROR in helper: /data/key.txt EXIST: FAILED - NO ERROR
[2023-03-31 17:52:07,453] INFO in helper: All startup checks passed.
[2023-03-31 17:52:07,454] INFO in helper: Testing API key validity.
[2023-03-31 17:52:07,532] INFO in helper: Got a non-200 response from Headscale.  Test failed (Response:  500)

On my headscale instance, it gives me following log at the same time:
2023-03-31T17:53:53+02:00 ERR go/src/headscale/app.go:445 > failed to validate token error="Failed to parse ApiKey" client_address=172.30.0.3:35622

Maybe I am doing something wrong, but I cannot figure it out.

EDIT: I think the "test" button doesnt work correctly, as I just pressed save now after it tested with "error". and it worked.

not able to pull docker image due to "manifest unknown"

part of my docker-compose

version: "3"
services:
  headscale-webui:
    image: ghcr.io/ifargle/headscale-webui
    container_name: headscale-webui
    ports:
      - 127.0.0.1:8002:8002
    environment:
      - TZ=Asia/Taipei
      - COLOR=red                              # Use the base colors (ie, no darken-3, etc) - 
        #- HS_SERVER=https://headscale.$DOMAIN    # Reachable endpoint for your Headscale server

pull image failed

docker-compose up -d
[+] Running 0/1
 ⠧ headscale-webui Pulling                                                                                                              0.7s
manifest unknown

any suggestions ??

Container exiting on signal 7 termination

Regularly after 30-40 mins of being started the container will exit with this trace:

[2023-03-22 10:20:49,709] INFO in renderer: Finished futures [2023-03-22 11:04:43 +0100] [3] [INFO] Worker exiting (pid: 3) [2023-03-22 11:04:43 +0100] [1] [INFO] Handling signal: term [2023-03-22 11:04:43 +0100] [1] [WARNING] Worker with pid 3 was terminated due to signal 7 [2023-03-22 11:04:43 +0100] [1] [INFO] Shutting down: Master

I will try to run in docker to rule out any issue with podman but I thought I could open an issue in case this rang any bell.

Traefik has a error entryPoint "web-secure" doesn't exist

I use trafik but cann't work ok
image
my docker-compose file is

version: "3"
services:
  headscale-webui:
    image: ghcr.io/ifargle/headscale-webui:latest
    container_name: headscale-webui
    ports:
      - "5000:5000"
    environment:
      - TZ=UTC                                 # Timezone
      - HS_SERVER=http://222.110.122.115:8080                 # Set this to your Headscale server's URL.  It will need to access /api/ on Headscale.
      - BASE_PATH="/admin"                     # Default, can be anything you want.  Tailscale's Windows app expects "HS_SERVER/admin"
      - KEY="4E3jPM9UUw0tvriFjBAKUfwM9yhh7D8GcttnfFR1KlM="             # Generate with "openssl rand -base64 32"
    volumes:
      - ./volume:/data                         # Headscale-WebUI's storage
      - ./headscale/config/:/etc/headscale/:ro # Headscale's config storage location.  Used to read your Headscale config.
    labels:
      # Traefik Configs
      - "traefik.enable=true"
      - "traefik.http.routers.headscale-webui.entrypoints=web-secure"
      - "traefik.http.routers.headscale-webui.rule=Host(`headscale.opst.net`) && (PathPrefix(`/admin/`) || PathPrefix(`/admin`))"
      - "traefik.http.services.headscale-webui.loadbalancer.server.port=5000"
      - "traefik.http.routers.headscale-webui.tls.certresolver=letsencrypt"
        # redirect /admin to /
      - "traefik.http.middlewares.headscale-webui-stripprefix.stripprefix.forceslash=true"
      - "traefik.http.middlewares.headscale-webui-stripprefix.stripprefix.prefixes=/admin/

I use http://headscale.opst.net is 404

Internal Server Error on /routes

It looks like the feature to view the routes was just added which is awesome. However, when I try to view the page it just gives an "Internal Server Error". The docker logs show the following:

[2023-04-01 01:59:28,796] ERROR in app: Exception on /routes [GET]
Traceback (most recent call last):
  File "/app/.venv/lib/python3.11/site-packages/flask/app.py", line 2528, in wsgi_app
    response = self.full_dispatch_request()
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/app/.venv/lib/python3.11/site-packages/flask/app.py", line 1825, in full_dispatch_request
    rv = self.handle_user_exception(e)
         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/app/.venv/lib/python3.11/site-packages/flask/app.py", line 1823, in full_dispatch_request
    rv = self.dispatch_request()
         ^^^^^^^^^^^^^^^^^^^^^^^
  File "/app/.venv/lib/python3.11/site-packages/flask/app.py", line 1799, in dispatch_request
    return self.ensure_sync(self.view_functions[rule.endpoint])(**view_args)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/app/server.py", line 131, in decorated
    return view_func(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/app/server.py", line 183, in routes_page
    render_page       = renderer.render_routes(),
                        ^^^^^^^^^^^^^^^^^^^^^^^^
  File "/app/renderer.py", line 799, in render_routes
    if all_routes["routes"][int(route_id) - 1]["enabled"]: failover_display = failover_enabled
       ~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^
IndexError: list index out of range

I have a similar problem with the webui behind a nginx reverse proxy.

          I have a similar problem with the webui behind a nginx reverse proxy.

With SCRIPT_NAME set, I get a redirect loop. The UI should be available under https://headscale.example.com/admin. Nginx takes care for http -> https redirects but https://headscale.example.com/admin redirects me back to http, causing a loop.
Removing SCRIPT_NAME results in a 404

Originally posted by @vbrandl in #63 (comment)

docker image manifest for v0.6.0 is not working

The latest version that was pushed as v0.6.0 appears to have issues with its manifest. I'm following a process that worked yesterday with the older version and is what should happen according to the headscale-webui package page.

$ docker pull ghcr.io/ifargle/headscale-webui:latest
latest: Pulling from ifargle/headscale-webui
manifest unknown
$ docker pull ghcr.io/ifargle/headscale-webui:v0.6.0
v0.6.0: Pulling from ifargle/headscale-webui
manifest unknown

This might just mean that the image needs to be pushed again.

Tested on amd64-linux, amd64-wsl, arm-macos, arm-linux. Same responses all around. Does not appear to be a widespread ghcr.io issue as I can pull other images.

Error - /data not writable

Error - /data not writable
/data is not writable. Please ensure your permissions are correct. /data mount should be writable by UID/GID 1000:1000.
version: "3"
services:
  headscale-webui:
    image: ghcr.io/ifargle/headscale-webui:latest
    container_name: headscale-webui
    network_mode: host
    environment:
      - TZ=Asia/Shanghai
      - COLOR=red 
      - HS_SERVER=http://0.0.0.0:8080
      - DOMAIN_NAME=http://0.0.0.0:5000 
      - SCRIPT_NAME=/admin
      - KEY="PSDSzZAXi0UTtod8oQEO/13u8EK0oR+PjB+h2+XUtWI="
      - AUTH_TYPE=basic
      - LOG_LEVEL=info
      - BASIC_AUTH_USER=admin
      - BASIC_AUTH_PASS=admin
    volumes:
      - /etc/headscale/ui:/data:rw
      - /etc/headscale/config:/etc/headscale:ro

Embeded DERP is configured but information is not visible in overview

Hi,
I have the DERP server configured, but the information is not visible on the overview DERP section.
I think I found the problem on renderer.py at line 107. The "enabled" attribute is tested as a string, but is a boolean, so True must be written without double quotes.
Thank you and cheers

Nginx documentations

It would be really helpful to also include a documentation for Nginx as well 😇

Failure to redirect properly when key.txt is empty or missing

Observation

When key.txt is empty: headscale-webui serves a redirect loop on whichever page. When key.txt is missing: the console reports the lack of key; the browser is continuously redirected to /prefix/settings, even when on /prefix/settings.

Replication

  • Delete key.txt
    or
  • Empty key.txt

Tested conditions

  • With OIDC auth
  • With BASIC auth

User mitigation

Use the python console to manually encode the api key.

from cryptography.fernet import Fernet
fernet = Fernet("SECRET")
encrypted_key = fernet.encrypt("KEY".encode())

Insert result without the begining b' and end '.

Container v0.2.2 BASE_PATH issue

ENV VARS:
- HS_SERVER="http://localhost"
- BASE_PATH="/admin"

[2023-02-10 21:22:41 +0000] [8] [ERROR] Exception in worker process
Traceback (most recent call last):
  File "/app/.venv/lib/python3.11/site-packages/gunicorn/arbiter.py", line 589, in spawn_worker
    worker.init_process()
  File "/app/.venv/lib/python3.11/site-packages/gunicorn/workers/base.py", line 134, in init_process
    self.load_wsgi()
  File "/app/.venv/lib/python3.11/site-packages/gunicorn/workers/base.py", line 146, in load_wsgi
    self.wsgi = self.app.wsgi()
                ^^^^^^^^^^^^^^^
  File "/app/.venv/lib/python3.11/site-packages/gunicorn/app/base.py", line 67, in wsgi
    self.callable = self.load()
                    ^^^^^^^^^^^
  File "/app/.venv/lib/python3.11/site-packages/gunicorn/app/wsgiapp.py", line 58, in load
    return self.load_wsgiapp()
           ^^^^^^^^^^^^^^^^^^^
  File "/app/.venv/lib/python3.11/site-packages/gunicorn/app/wsgiapp.py", line 48, in load_wsgiapp
    return util.import_app(self.app_uri)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/app/.venv/lib/python3.11/site-packages/gunicorn/util.py", line 359, in import_app
    mod = importlib.import_module(module)
          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.11/importlib/__init__.py", line 126, in import_module
    return _bootstrap._gcd_import(name[level:], package, level)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "<frozen importlib._bootstrap>", line 1206, in _gcd_import
  File "<frozen importlib._bootstrap>", line 1178, in _find_and_load
  File "<frozen importlib._bootstrap>", line 1149, in _find_and_load_unlocked
  File "<frozen importlib._bootstrap>", line 690, in _load_unlocked
  File "<frozen importlib._bootstrap_external>", line 940, in exec_module
  File "<frozen importlib._bootstrap>", line 241, in _call_with_frames_removed
  File "/app/server.py", line 32, in <module>
    @app.route(BASE_PATH+'/')
     ^^^^^^^^^^^^^^^^^^^^^^^^
  File "/app/.venv/lib/python3.11/site-packages/flask/scaffold.py", line 449, in decorator
    self.add_url_rule(rule, endpoint, f, **options)
  File "/app/.venv/lib/python3.11/site-packages/flask/scaffold.py", line 50, in wrapper_func
    return f(self, *args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^
  File "/app/.venv/lib/python3.11/site-packages/flask/app.py", line 1351, in add_url_rule
    rule = self.url_rule_class(rule, methods=methods, **options)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/app/.venv/lib/python3.11/site-packages/werkzeug/routing/rules.py", line 454, in __init__
    raise ValueError("urls must start with a leading slash")

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.