Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
104 commits
Select commit Hold shift + click to select a range
7dfcbef
feat: initial webssh tooling
tale May 30, 2025
55eacb5
feat: expand frame type to support stdout/stderr chan
tale May 31, 2025
cb32637
feat: add xterm frontend ui
tale May 31, 2025
a0a8085
fix: connect to stdout fd after starting an ssh shell
tale May 31, 2025
ccde351
feat: support resizing and other xterm.js addons
tale May 31, 2025
46f1576
chore: update nix go hash
tale May 31, 2025
f9751f5
chore: add tooling for go wasm
tale Jun 6, 2025
7a6ad8d
feat: add horrible barely-working go ipn wasm code
tale Jun 6, 2025
0f9bf73
chore: reorganize go code
tale Jun 8, 2025
f4af5b9
feat: cleanup and streamline webssh capability
tale Jun 9, 2025
7214398
style: format go code on precommit
tale Jun 9, 2025
6a0e097
chore: fix tiny nits
tale Jun 9, 2025
7f376c3
fix: better ssh resilience and customizable timeout for future backoff
tale Jun 16, 2025
6d15c41
chore: use mise for ci
tale Jun 16, 2025
ab7587e
chore: cache go build deps
tale Jun 16, 2025
4961e83
fix: cleanup agent and add to ci
tale Jun 16, 2025
799d7b5
chore: update go deps nix hash
tale Jun 16, 2025
b34a3f0
chore: sigh update pnpm deps hash
tale Jun 16, 2025
1e86b0e
feat: add ssh buttons in the ui using hostinfo checks
tale Jun 18, 2025
bf1d75a
fix: resize cols and rows, not the other way around *sigh*
tale Jun 18, 2025
b18147f
feat: cleanup removal of old ssh plexer and logic
tale Jun 20, 2025
c0af2aa
chore: update changelog
tale Jun 20, 2025
71f130e
feat: switch to libsql since its esm friendly
tale Jun 20, 2025
73f0a0d
fix: use distroless and fix build
tale Jun 20, 2025
cf55621
docs: warn about permission change
tale Jun 20, 2025
dd287c0
fix: menu should work even with ssh target
tale Jun 20, 2025
1150d16
fix: support user input when not specified for ssh
tale Jun 21, 2025
8819af2
fix: properly handle removing the last split dns record
tale Jun 21, 2025
87b8d64
feat: reintroduce missing local dns override (fixes #236)
tale Jun 21, 2025
2bf088b
feat: speed up docker build
tale Jun 22, 2025
144a8b8
feat: cache docker build cache using gha
tale Jun 22, 2025
9a81b76
feat: add provenance attestations
tale Jun 22, 2025
8f1b577
fix: allow id-token write in gh actions
tale Jun 22, 2025
f28bfd5
fix: add attestation perms to gha
tale Jun 22, 2025
f990670
Merge remote-tracking branch 'origin/main' into next
tale Jun 22, 2025
84c820e
chore: merge remote-tracking branch 'origin' into next
tale Jun 23, 2025
2cc4339
chore: update changelog
tale Jun 23, 2025
5a254b7
docs: enforce node lts 22.16 which has sqlite support unflagged
tale Jun 24, 2025
eed12d3
feat: build a debug-shell and distroless container
tale Jun 24, 2025
ecc5476
fix: pass in github token for mise ratelimits
tale Jun 24, 2025
fc7bedc
feat: append -next to pre-release versions in pnpm build
tale Jun 24, 2025
eacde3d
chore: attestation is sha256 based, we dont need tags
tale Jun 24, 2025
d809eea
feat: finalize split debug and distroless container (closes #255)
tale Jun 24, 2025
6d99aca
feat: add matrix build for release
tale Jun 24, 2025
ce4617d
chore: walk back on nonroot, its too much of a breaking change
tale Jun 24, 2025
7691f74
fix: basename should always be prefix???
tale Jul 10, 2025
5cfd9e4
Implement path loading
StealthBadger747 Jul 12, 2025
5ab9686
Update the configuration README
StealthBadger747 Jul 30, 2025
51a85e1
fix: nix update hash
igor-ramazanov Aug 2, 2025
7a34a01
Got the build working
StealthBadger747 Jul 15, 2025
8b3d0e9
Maybe actually fix builds
StealthBadger747 Jul 15, 2025
c311250
Copy drizzle as well
StealthBadger747 Jul 15, 2025
7f952bc
Nix changes
StealthBadger747 May 19, 2025
943e5d2
Remove inlined secrets options
igor-ramazanov May 20, 2025
e0fde7d
Turn `integration.proc.enabled` by default
igor-ramazanov May 20, 2025
441e316
Cleanup doc and set sane defaults
StealthBadger747 May 20, 2025
d257b22
chore: nix: `nix fmt`
igor-ramazanov Jul 27, 2025
89e38e1
feat: nix: rename `hp_ssh_wasm` to `headplane-ssh-wasm` for consistency
igor-ramazanov Jul 27, 2025
c59632a
feat: nix: match NixOS module to the config schema
igor-ramazanov Jul 27, 2025
ea004df
feat: nix: add `debug` logging flag
igor-ramazanov Jul 27, 2025
344901d
fix: `debug` log level when searching for a Headscale process
igor-ramazanov Jul 27, 2025
8a92358
fix: agent inherits UID/GID from the parent process
igor-ramazanov Jul 27, 2025
1eaf3ab
feat: nix: use updated `pnpm` fetcher version
igor-ramazanov Jul 28, 2025
1ce0dc3
fix: remove `direnv` and `envrc` from the repo
igor-ramazanov Jul 29, 2025
ebd219b
chore: update pnpm nix deps hash
tale Aug 5, 2025
1355e15
chore: update changelog
tale Aug 5, 2025
cdcb38f
fix: nix: hash
igor-ramazanov Aug 9, 2025
fdbfe58
feat: nix: add a new `mise` task and nix flake output to generate Nix…
igor-ramazanov Jul 29, 2025
9192013
feat: documentation website using Vitepress
igor-ramazanov Aug 9, 2025
c98dc5e
feat: nix: docs: improve NixOS docs generations
igor-ramazanov Aug 9, 2025
bc3fc5e
fix: turns out we need isSsrBuild in vite.config.ts still
tale Aug 18, 2025
361bc49
chore: pin vitepress to 2.0.0 and add sponsor link
tale Aug 18, 2025
616b63b
chore: add wrangler.toml
tale Aug 18, 2025
17712cb
feat: upgrade react-router + hono-server
tale Aug 18, 2025
a4a037e
feat: update to rolldown-vite and typescript-go
tale Aug 18, 2025
8cb91cd
feat: overhaul hp_agent lifecycle handling
tale Aug 19, 2025
d2c4f5e
feat: completely overhaul the auth model
tale Aug 19, 2025
ae2bd35
chore: update biome to v2
tale Aug 19, 2025
797ed56
chore: remove github pages workflow
tale Aug 19, 2025
bcd8745
chore: update go mods
tale Aug 20, 2025
8fc657f
feat: handle logging from the agent
tale Aug 20, 2025
82f6294
Merge branch 'main' into next
tale Aug 20, 2025
356abab
feat: do insane type validation for the config
tale Aug 21, 2025
ff3bdc1
feat: remove octicons icon pack
tale Aug 21, 2025
6c56563
chore: update and prune deps
tale Aug 21, 2025
cd4e8f8
chore: handle empty (already migrated) user oidc file
tale Aug 21, 2025
9183f80
chore: type fixes
tale Aug 21, 2025
4351e1f
feat: support gravatar profile pictures for oidc
tale Aug 21, 2025
9bc8483
chore: shutup the dotenvx shameless promo
tale Aug 21, 2025
d7b1e19
chore: add deprecation notice for oidc user file
tale Aug 21, 2025
a9d5e10
fix: redirect admin to admin/ (regression)
tale Aug 26, 2025
bda9ded
fix: if there are 0 admins, promote next admin on oidc login
tale Aug 28, 2025
eb46694
feat: overhaul oidc work
tale Aug 29, 2025
5b138c1
Fix auto populating an empty oidc
StealthBadger747 Sep 19, 2025
4df15fd
Route Vite prefix properly
StealthBadger747 Sep 26, 2025
1dfb2fb
fix: upgrade to new tailscale go version to fix ssh
tale Sep 27, 2025
4803b2a
Fix routing for vite prefix on dev vs build
StealthBadger747 Sep 27, 2025
65f5684
Merge branch 'next' into erikp/fix-vite-dev-path
StealthBadger747 Sep 27, 2025
cee28fb
Update nix stuff
StealthBadger747 Sep 27, 2025
341a0e8
Merge pull request #319 from StealthBadger747/erikp/fix-vite-dev-path
tale Sep 27, 2025
9b53711
feat: redo xterm terminal logic
tale Oct 2, 2025
0eef9ae
Bump nixpkgs, remove unnecessary `go1251Overlay`
igor-ramazanov Oct 8, 2025
43cbc17
Merge pull request #325 from igor-ramazanov/next
tale Oct 9, 2025
4fd8062
Merge branch 'main' into next
tale Oct 12, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion .envrc

This file was deleted.

3 changes: 3 additions & 0 deletions .github/CODEOWNERS
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
* @tale

/nix @tale @StealthBadger747
28 changes: 14 additions & 14 deletions .github/workflows/build.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -28,20 +28,15 @@ jobs:
- name: Check out the repo
uses: actions/checkout@v4

- name: Install node.js
uses: actions/setup-node@v4
with:
node-version: 22

- uses: pnpm/action-setup@v4
name: Install pnpm
with:
run_install: false
- name: Setup Mise
uses: jdx/mise-action@v2

- name: Get pnpm store directory
- name: Set caching paths
shell: bash
run: |
echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV
echo "GO_CACHE=$(go env GOCACHE)" >> $GITHUB_ENV
echo "GO_MODCACHE=$(go env GOMODCACHE)" >> $GITHUB_ENV

- uses: actions/cache@v4
name: Setup pnpm cache
Expand All @@ -51,11 +46,16 @@ jobs:
restore-keys: |
${{ runner.os }}-pnpm-store-

- name: Install dependencies
run: pnpm install
- name: Setup Go cache
uses: actions/cache@v4
with:
path: |
${{ env.GO_CACHE }}
${{ env.GO_MODCACHE }}
key: ${{ runner.os }}-go-${{ hashFiles('**/go.mod', '**/go.sum') }}

- name: Build
run: pnpm build
- name: CI pipeline
run: mise run ci

nix:
name: nix
Expand Down
32 changes: 29 additions & 3 deletions .github/workflows/next.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,23 @@ permissions:
actions: write # Allow canceling in-progress runs
contents: read # Read access to the repository
packages: write # Write access to the container registry
id-token: write # For the attest action to push
attestations: write # For the attest action to push

jobs:
publish:
# Ensure the action only runs if manually dispatched or a PR on the `next` branch in the *main* repository is opened or synchronized.
if: ${{ github.event_name == 'workflow_dispatch' || (github.event.pull_request && github.event.pull_request.head.repo.full_name == github.repository && github.event.pull_request.head.ref == 'next') }}
name: Docker Pre-release
runs-on: ubuntu-latest
strategy:
matrix:
include:
- target: final
tag: 'next'
- target: debug-shell
tag: 'next-shell'

steps:
- name: Check out the repo
uses: actions/checkout@v4
Expand All @@ -29,7 +39,7 @@ jobs:
with:
images: ghcr.io/${{ github.repository }}
tags: |
type=raw,value=next
type=raw,value=${{ matrix.tag }}

- name: Set up QEMU
uses: docker/setup-qemu-action@v3
Expand All @@ -44,12 +54,28 @@ jobs:
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Build and push Docker image
uses: docker/build-push-action@v5
- name: Build and publish ghcr.io/${{ github.repository }}:${{ matrix.tag }}
uses: docker/build-push-action@v6
id: push
with:
context: .
file: ./Dockerfile
target: ${{ matrix.target }}
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
platforms: linux/amd64, linux/arm64
cache-from: type=gha
cache-to: type=gha,mode=max
build-args: |
IMAGE_TAG=ghcr.io/${{ github.repository }}:${{ matrix.tag }}
secrets: |
gh_token=${{ secrets.GITHUB_TOKEN }}

- name: Attestation Provenance for ghcr.io/${{ github.repository }}:${{ matrix.tag }}
uses: actions/attest-build-provenance@v2
id: attest
with:
subject-name: ghcr.io/${{ github.repository }}
subject-digest: ${{ steps.push.outputs.digest }}
push-to-registry: true
30 changes: 28 additions & 2 deletions .github/workflows/release.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,21 @@ permissions:
actions: write # Allow canceling in-progress runs
contents: read # Read access to the repository
packages: write # Write access to the container registry
id-token: write # For the attest action to push
attestations: write # For the attest action to push

jobs:
docker:
name: Docker Release
runs-on: ubuntu-latest
strategy:
matrix:
include:
- target: final
tag_suffix: ''
- target: debug-shell
tag_suffix: '-shell'

steps:
- name: Check out the repo
uses: actions/checkout@v4
Expand All @@ -28,6 +38,9 @@ jobs:
images: ghcr.io/${{ github.repository }}
tags: |
type=semver,pattern={{version}}
flavor: |
latest=auto
suffix=${{ matrix.tag_suffix }},onlatest=true

- name: Set up QEMU
uses: docker/setup-qemu-action@v3
Expand All @@ -42,12 +55,25 @@ jobs:
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Build and push Docker image
uses: docker/build-push-action@v5
- name: Build and push ${{ fromJSON(steps.meta.outputs.json).tags[0] }}
uses: docker/build-push-action@v6
id: push
with:
context: .
file: ./Dockerfile
target: ${{ matrix.target }}
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
platforms: linux/amd64, linux/arm64
cache-from: type=gha
cache-to: type=gha,mode=max
build-args: |
IMAGE_TAG=ghcr.io/${{ github.repository }}:${{ fromJSON(steps.meta.outputs.json).labels['org.opencontainers.image.version'] }}
- name: Attestation Provenance for ${{ fromJSON(steps.meta.outputs.json).tags[0] }}
uses: actions/attest-build-provenance@v2
id: attest
with:
subject-name: ghcr.io/${{ github.repository }}
subject-digest: ${{ steps.push.outputs.digest }}
push-to-registry: true
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,9 @@ node_modules
/build
/test
.env
app/hp_ssh.wasm
app/wasm_exec.js
/docs/.vitepress/dist/
/docs/.vitepress/cache/

/.direnv
4 changes: 3 additions & 1 deletion .tool-versions
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
# REMEMBER TO UPDATE mise.toml TOO
pnpm 10.4.0
node 22
node 22.16
go 1.25.1
10 changes: 9 additions & 1 deletion .zed/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,20 @@
},
"code_actions_on_format": {
"source.fixAll.biome": true,
"source.organizeImports.biome": true
"source.organizeImports.biome": true,
"source.organizeImports": true
},
"languages": {
"YAML": {
"tab_size": 2,
"hard_tabs": false
},
"Go": {
"formatter": {
"language_server": {
"name": "gopls"
}
}
}
}
}
29 changes: 29 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,32 @@
### 0.6.1 (Next)
- **Headplane now supports connecting to machines via SSH in the web browser.**
- This is an experimental feature and requires the `integration.agent` section to be set up in the config file.
- This is built on top of a Go binary that runs in WebAssembly, using Xterm.js for the terminal interface.
- Begin using a new SQLite database file in `/var/lib/headplane/hp_persist.db`.
- The database is created automatically if it does not exist.
- It currently stores SSH connection details and HostInfo for the agent.
- User information is automatically migrated from the previous database.
- The docker container now runs in a distroless image (closes [#255](https://github.com/tale/headplane/issues/255)).
- A debug version of the container that runs as root and has a shell is available as `ghcr.io/tale/headplane:<version>-shell`.
- Removing a Split DNS record will no longer make the split domain unresolvable by clients (closes [#231](https://github.com/tale/headplane/issues/231)).
- Reintroduce the toggle for overriding local DNS settings in the Headscale config (closes [#236](https://github.com/tale/headplane/issues/236)).
- Prefer cross-compiling in the Dockerfile to speed up builds while still supporting multiple architectures.
- Add a build attestation to validate SLSA provenance for the Docker image.
- Implement more accurate guessing on the PID with the `/proc` integration (via [#219](https://github.com/tale/headplane/pull/219)).
- Usernames will now correctly fall back to emails if not provided (via [#257](https://github.com/tale/headplane/pull/257)).
- Configuration loading via paths is now supported for sensitive values (via [#283](https://github.com/tale/headplane/pulls/283))
- Options like `server.cookie_secret_path` can override `server.cookie_secret`
- Environment variables are interpolatable into these paths
- See the full reference in the [docs](https://github.com/tale/headplane/blob/main/docs/Configuration.md#sensitive-values)
- The nix overlay build is fixed for the SSH module (via [#282](https://github.com/tale/headplane/pull/282))
- Switch our build processes to use TypeScript Go and Rolldown Vite for better build and type-check performance.
- Cookies are now encrypted JWTs, preserving API key secrets (*GHSA-wrqq-v7qw-r5w7*)
- OIDC profile pictures are now available from Gravatar by setting `oidc.profile_picture_source` to `gravatar` (closes [#232](https://github.com/tale/headplane/issues/232)).
- OIDC now allows passing many custom parameters:
- `oidc.authorization_endpoint`, `oidc.token_endpoint`, and `oidc.userinfo_endpoint` can be overridden to support non-standard providers or scenarios without discovery (closes [#117](https://github.com/tale/headplane/issues/117)).
- `oidc.scope` can be set to specify custom scopes (defaults to `openid email profile`).
- `oidc.extra_params` can be set to pass arbitrary query parameters to the authorization endpoint (closes [#197](https://github.com/tale/headplane/issues/197)).

### 0.6.0 (May 25, 2025)
- Headplane 0.6.0 now requires **Headscale 0.26.0** or newer.
- Breaking API changes with routes and pre auth keys are now supported (closes [#204](https://github.com/tale/headplane/issues/204)).
Expand Down
10 changes: 10 additions & 0 deletions Caddyfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
https://localhost {
reverse_proxy headscale:8080
tls /certs/localhost.pem /certs/localhost-key.pem

header {
Access-Control-Allow-Origin *
Access-Control-Allow-Headers *
Access-Control-Allow-Methods GET, POST, OPTIONS
}
}
70 changes: 49 additions & 21 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,35 +1,63 @@
FROM golang:1.24 AS agent-build
WORKDIR /app
FROM --platform=$BUILDPLATFORM jdxcode/mise:latest AS mise-context
COPY mise.toml .tool-versions ./
RUN --mount=type=secret,id=gh_token,env=MISE_GITHUB_TOKEN mise install

FROM --platform=$BUILDPLATFORM mise-context AS go-build
WORKDIR /build/

COPY go.mod go.sum ./
RUN go mod download

COPY agent/ ./agent
RUN CGO_ENABLED=0 GOOS=linux go build \
-trimpath \
-ldflags "-s -w" \
-o /app/hp_agent ./agent/cmd/hp_agent
COPY cmd/ ./cmd/
COPY internal/ ./internal/

FROM node:22-alpine AS build
WORKDIR /app
ARG TARGETOS
ARG TARGETARCH
ARG IMAGE_TAG
RUN mkdir -p /build/app/ && \
GOOS=$TARGETOS GOARCH=$TARGETARCH CGO_ENABLED=0 IMAGE_TAG=$IMAGE_TAG \
mise run wasm ::: agent ::: fake-shell

RUN npm install -g pnpm@10
RUN apk add --no-cache git
COPY package.json pnpm-lock.yaml ./
RUN chmod +x /build/build/hp_agent
RUN chmod +x /build/build/sh

FROM --platform=$BUILDPLATFORM mise-context AS js-build
WORKDIR /build
COPY patches ./patches
COPY package.json pnpm-lock.yaml ./
RUN pnpm install --frozen-lockfile

COPY . .
RUN pnpm run build
RUN mise trust
COPY --from=go-build /build/app/hp_ssh.wasm /build/app/hp_ssh.wasm
COPY --from=go-build /build/app/wasm_exec.js /build/app/wasm_exec.js

FROM node:22-alpine
RUN apk add --no-cache ca-certificates
RUN mkdir -p /var/lib/headplane
RUN mkdir -p /usr/libexec/headplane
ARG IMAGE_TAG
RUN IMAGE_TAG=$IMAGE_TAG pnpm run build
RUN mkdir -p /var/lib/headplane/agent

FROM gcr.io/distroless/nodejs22-debian12:latest AS final
COPY --from=js-build /build/build/ /app/build/
COPY --from=js-build /build/drizzle /app/drizzle/
COPY --from=js-build /var/lib/headplane /var/lib/headplane
COPY --from=js-build /build/node_modules/ /app/node_modules/
COPY --from=go-build /build/build/hp_agent /usr/libexec/headplane/agent

# Fake shell to inform the user that they should use the debug image
COPY --from=go-build /build/build/sh /bin/sh
COPY --from=go-build /build/build/sh /bin/bash

WORKDIR /app
CMD [ "/app/build/server/index.js" ]

FROM node:22-alpine AS debug-shell
RUN apk add --no-cache bash curl git

COPY --from=js-build /build/build/ /app/build/
COPY --from=js-build /build/drizzle /app/drizzle/
COPY --from=js-build /var/lib/headplane /var/lib/headplane
COPY --from=js-build /build/node_modules/ /app/node_modules/
COPY --from=go-build /build/build/hp_agent /usr/libexec/headplane/agent

WORKDIR /app
COPY --from=build /app/build /app/build
COPY --from=agent-build /app/hp_agent /usr/libexec/headplane/agent
RUN chmod +x /usr/libexec/headplane/agent
CMD [ "node", "./build/server/index.js" ]
CMD [ "node", "/app/build/server/index.js" ]
Loading
Loading