Compare commits

...

21 Commits

Author SHA1 Message Date
Aarnav Tale 5171120df8 Revert "Implement path-based secret loading"
This reverts commit 8ea8c7195f.
2025-05-23 10:42:25 -04:00
Aarnav Tale 71bdca6b8b Revert "Rework the documentation to reflect the whitelist approach"
This reverts commit 91fcf5233e.
2025-05-23 10:42:25 -04:00
Aarnav Tale 13a711f505 Revert "Cleanup"
This reverts commit 62863ad649.
2025-05-23 10:42:25 -04:00
Erik Parawell 62863ad649 Cleanup 2025-05-19 16:38:35 -04:00
Erik Parawell 91fcf5233e Rework the documentation to reflect the whitelist approach 2025-05-19 16:38:35 -04:00
Erik Parawell 8ea8c7195f Implement path-based secret loading 2025-05-19 16:38:35 -04:00
github-actions[bot] c3a50ea4f4 chore: update flake.lock (#206)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2025-05-14 00:10:00 -04:00
github-actions[bot] 6716e5f0b0 chore: update flake.lock (#202)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2025-05-04 15:25:02 -04:00
github-actions[bot] 346b44ec69 chore: update flake.lock (#196)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2025-05-03 23:46:31 -04:00
George Ntoutsos faa61b0f1d feat: add filtering by container label for Docker integration (#194) 2025-04-24 19:03:33 -04:00
github-actions[bot] 6b63fe209f chore: update flake.lock (#190)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2025-04-20 15:38:03 -04:00
github-actions[bot] c8507cff7c chore: update flake.lock (#180)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2025-04-15 11:00:11 -04:00
Federico Cerutti b86f4461c0 feat: restart with headscale, update server.js path in systemd example (#184) 2025-04-10 09:08:43 -04:00
Aarnav Tale 77b510c927 docs: oops 2025-04-05 13:14:00 -04:00
Aarnav Tale 5adcb8c582 ci: skip ci build on certain paths 2025-04-05 12:07:31 -04:00
Aarnav Tale 524c5eb639 docs: docker version tags had an extra char 2025-04-05 11:58:32 -04:00
Aarnav Tale 2894c664d3 docs: fix tag typo 2025-04-05 11:56:59 -04:00
Aarnav Tale 5c2d08decd chore: update dep tooling 2025-04-04 16:09:10 -04:00
Aarnav Tale f2e8c6ae4c chore: update dep tooling 2025-04-04 16:06:22 -04:00
Aarnav Tale c3e727842a docs: mention versioning policy 2025-04-04 15:44:16 -04:00
Aarnav Tale 96345ab0a6 chore: switch from nightly to pr based incubration 2025-04-04 10:19:37 -04:00
15 changed files with 172 additions and 50 deletions
+6 -1
View File
@@ -1,6 +1,12 @@
name: Build
on:
push:
paths-ignore:
- ".zed/**"
- "assets/**"
- "docs/**"
- "CHANGELOG.md"
- "README.md"
branches:
- "main"
- "next"
@@ -30,7 +36,6 @@ jobs:
- uses: pnpm/action-setup@v4
name: Install pnpm
with:
version: 10
run_install: false
- name: Get pnpm store directory
@@ -1,11 +1,11 @@
name: Nightly
name: Pre-release (next)
on:
schedule:
- cron: "0 8 * * *"
workflow_dispatch:
pull_request:
types: [opened, synchronize, reopened]
concurrency:
group: publish-nightly-${{ github.ref }}
group: pre-release-${{ github.ref }}
cancel-in-progress: true
permissions:
@@ -15,12 +15,22 @@ permissions:
jobs:
publish:
name: docker-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
steps:
- name: Check out the repo
uses: actions/checkout@v4
- name: Docker Metadata
id: meta
uses: docker/metadata-action@v5
with:
images: ghcr.io/${{ github.repository }}
tags: |
type=raw,value=next
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
@@ -34,14 +44,6 @@ jobs:
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Docker Metadata
id: meta
uses: docker/metadata-action@v5
with:
images: ghcr.io/${{ github.repository }}
tags: |
type=edge,branch=main
- name: Build and push Docker image
uses: docker/build-push-action@v5
with:
+11 -13
View File
@@ -5,7 +5,7 @@ on:
- "*"
concurrency:
group: publish-${{ github.ref }}
group: release-${{ github.ref }}
cancel-in-progress: true
permissions:
@@ -14,13 +14,21 @@ permissions:
packages: write # Write access to the container registry
jobs:
publish:
name: docker-publish
docker:
name: Docker Release
runs-on: ubuntu-latest
steps:
- name: Check out the repo
uses: actions/checkout@v4
- name: Docker Metadata
id: meta
uses: docker/metadata-action@v5
with:
images: ghcr.io/${{ github.repository }}
tags: |
type=semver,pattern={{version}}
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
@@ -34,16 +42,6 @@ jobs:
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Docker Metadata
id: meta
uses: docker/metadata-action@v5
with:
images: ghcr.io/${{ github.repository }}
tags: |
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=raw,value=latest,enable=false
- name: Build and push Docker image
uses: docker/build-push-action@v5
with:
+2
View File
@@ -0,0 +1,2 @@
pnpm 10.4.0
node 22
+8 -1
View File
@@ -44,11 +44,18 @@ There are 2 ways to deploy Headplane:
Simple mode does not include the automatic management of DNS and Headplane
settings, requiring manual editing and reloading when making changes.
## Contributing
### Versioning
Headplane uses [semantic versioning](https://semver.org/) for its releases (since v0.6.0).
Pre-release builds are available under the `next` tag and get updated when a new release
PR is opened and actively in testing.
### Contributing
Headplane is an open-source project and contributions are welcome! If you have
any suggestions, bug reports, or feature requests, please open an issue. Also
refer to the [contributor guidelines](./docs/CONTRIBUTING.md) for more info.
---
<picture>
<source
media="(prefers-color-scheme: dark)"
+90 -3
View File
@@ -6,6 +6,11 @@ import log from '~/utils/log';
import type { HeadplaneConfig } from '../schema';
import { Integration } from './abstract';
interface DockerContainer {
Id: string;
Names: string[];
}
type T = NonNullable<HeadplaneConfig['integration']>['docker'];
export default class DockerIntegration extends Integration<T> {
private maxAttempts = 10;
@@ -15,13 +20,63 @@ export default class DockerIntegration extends Integration<T> {
return 'Docker';
}
async getContainerName(label: string, value: string): Promise<string> {
if (!this.client) {
throw new Error('Docker client is not initialized');
}
const filters = encodeURIComponent(
JSON.stringify({
label: [`${label}=${value}`],
}),
);
const { body } = await this.client.request({
method: 'GET',
path: `/containers/json?filters=${filters}`,
});
const containers: DockerContainer[] =
(await body.json()) as DockerContainer[];
if (containers.length > 1) {
throw new Error(
`Found multiple Docker containers matching label ${label}=${value}. Please specify a container name.`,
);
}
if (containers.length === 0) {
throw new Error(
`No Docker containers found matching label: ${label}=${value}`,
);
}
log.info(
'config',
'Found Docker container matching label: %s=%s',
label,
value,
);
return containers[0].Id;
}
async isAvailable() {
if (this.context.container_name.length === 0) {
log.error('config', 'Docker container name is empty');
// Perform a basic check to see if any of the required properties are set
if (
this.context.container_name.length === 0 &&
!this.context.container_label
) {
log.error('config', 'Docker container name and label are both empty');
return false;
}
log.info('config', 'Using container: %s', this.context.container_name);
if (
this.context.container_name.length > 0 &&
!this.context.container_label
) {
log.error(
'config',
'Docker container name and label are mutually exclusive',
);
return false;
}
// Verify that Docker socket is reachable
let url: URL | undefined;
try {
url = new URL(this.context.socket);
@@ -72,6 +127,38 @@ export default class DockerIntegration extends Integration<T> {
socketPath: url.pathname,
});
}
if (this.client === undefined) {
log.error('config', 'Failed to create Docker client');
return false;
}
if (this.context.container_name.length === 0) {
try {
if (this.context.container_label === undefined) {
log.error('config', 'Docker container label is not defined');
return false;
}
const containerName = await this.getContainerName(
this.context.container_label.name,
this.context.container_label.value,
);
if (containerName.length === 0) {
log.error(
'config',
'No Docker containers found matching label: %s=%s',
this.context.container_label.name,
this.context.container_label.value,
);
return false;
}
this.context.container_name = containerName;
} catch (error) {
log.error('config', 'Failed to get Docker container name: %s', error);
return false;
}
}
log.info('config', 'Using container: %s', this.context.container_name);
return this.client !== undefined;
}
+6
View File
@@ -41,10 +41,16 @@ const headscaleConfig = type({
config_strict: stringToBool,
}).onDeepUndeclaredKey('reject');
const containerLabel = type({
name: 'string',
value: 'string',
}).optional();
const dockerConfig = type({
enabled: stringToBool,
container_name: 'string',
socket: 'string = "unix:///var/run/docker.sock"',
container_label: containerLabel,
});
const kubernetesConfig = type({
+1 -1
View File
@@ -3,7 +3,7 @@
"vcs": {
"enabled": false,
"clientKind": "git",
"useIgnoreFile": false
"useIgnoreFile": true
},
"files": {
"ignoreUnknown": false,
+2
View File
@@ -10,6 +10,8 @@ services:
headscale:
image: "headscale/headscale:0.25.1"
container_name: "headscale"
labels:
- com.headplane.selector=headscale
restart: "unless-stopped"
command: "serve"
networks:
+8 -2
View File
@@ -47,8 +47,14 @@ headscale:
integration:
docker:
enabled: false
# The name (or ID) of the container running Headscale
container_name: "headscale"
# Preferred method: use container_label to dynamically discover the Headscale container.
container_label:
name: "com.headplane.selector"
value: "headscale"
# Optional fallback: directly specify the container name (or ID)
# of the container running Headscale
# container_name: "headscale"
# The path to the Docker socket (do not change this if you are unsure)
# Docker socket paths must start with unix:// or tcp:// and at the moment
# https connections are not supported.
+4 -3
View File
@@ -30,7 +30,7 @@ Clone the Headplane repository, install dependencies, and build the project:
```sh
git clone https://github.com/tale/headplane
cd headplane
git checkout v0.5.5 # Or whatever tag you want to use
git checkout 0.5.10 # Or whatever tag you want to use
pnpm install
pnpm build
```
@@ -68,14 +68,15 @@ Headplane service:
```ini
[Unit]
Description=Headplane
After=network.target
# Decomment the following line if running on bare metal with integrated mode (/proc integration)
# PartOf=headscale.service
[Service]
Type=simple
User=headplane
Group=headplane
WorkingDirectory=/path/to/headplane
ExecStart=/usr/bin/node /path/to/headplane/build/headplane/server.js
ExecStart=/usr/bin/node /path/to/headplane/build/server/index.js
Restart=always
[Install]
+10 -7
View File
@@ -34,7 +34,7 @@ Here is what a sample Docker Compose deployment would look like:
services:
headplane:
# I recommend you pin the version to a specific release
image: ghcr.io/tale/headplane0.5.10:
image: ghcr.io/tale/headplane:0.5.10
container_name: headplane
restart: unless-stopped
ports:
@@ -70,11 +70,14 @@ you build the container yourself or run Headplane in Bare-Metal mode.
> setting up your `config.yaml` file to the appropriate values.
## Docker Integration
The Docker integration is the easiest to setup, as it only requires the Docker socket
to be mounted into the container along with some configuration. As long as Headplane
has access to the Docker socket and the name of the Headscale container, it will
automatically propagate config and DNS changes to Headscale without any additional
configuration.
The Docker integration is the easiest to set up, as it only requires mounting the
Docker socket into the container along with some basic configuration. Headplane
uses Docker labels to discover the Headscale container. As long as Headplane has
access to the Docker socket and can identify the Headscale container—either by
label or name—it will automatically propagate configuration and DNS changes to
Headscale without any additional setup. Alternatively, instead of using a label
to dynamically determine the container name, it is possible to directly specify
the container name.
## Native Linux (/proc) Integration
The `proc` integration is used when you are running Headscale and Headplane on
@@ -151,7 +154,7 @@ spec:
serviceAccountName: default
containers:
- name: headplane
image: ghcr.io/tale/headplane0.5.10:
image: ghcr.io/tale/headplane:0.5.10
env:
# Set these if the pod name for Headscale is not static
# We will use the downward API to get the pod name instead
+1 -1
View File
@@ -19,7 +19,7 @@ Here is what a sample Docker Compose deployment would look like:
services:
headplane:
# I recommend you pin the version to a specific release
image: ghcr.io/tale/headplane0.5.10:
image: ghcr.io/tale/headplane:0.5.10
container_name: headplane
restart: unless-stopped
ports:
Generated
+3 -3
View File
@@ -40,11 +40,11 @@
},
"nixpkgs": {
"locked": {
"lastModified": 1741646908,
"narHash": "sha256-55a1x5k+oFY2QCFjj7Mn5nPa8Do0shVl0m280mOAW/Q=",
"lastModified": 1746576598,
"narHash": "sha256-FshoQvr6Aor5SnORVvh/ZdJ1Sa2U4ZrIMwKBX5k2wu0=",
"owner": "nixos",
"repo": "nixpkgs",
"rev": "ab0c5b18dab5e4b5d06ed679f8fd7cdc9970c4be",
"rev": "b3582c75c7f21ce0b429898980eddbbf05c68e55",
"type": "github"
},
"original": {
+5 -2
View File
@@ -5,6 +5,7 @@
"version": "0.5.10",
"type": "module",
"scripts": {
"preinstall": "npx only-allow pnpm",
"build": "react-router build",
"dev": "HEADPLANE_LOAD_ENV_OVERRIDES=true HEADPLANE_CONFIG_PATH=./config.example.yaml react-router dev",
"start": "node build/server/index.js",
@@ -73,14 +74,16 @@
"vite-plugin-babel": "^1.3.0",
"vite-tsconfig-paths": "^5.1.4"
},
"packageManager": "pnpm@10.4.0",
"engines": {
"node": ">=22",
"pnpm": ">=10 <11"
"pnpm": ">=10.4 <11"
},
"pnpm": {
"patchedDependencies": {
"@shopify/lang-jsonc@1.0.0": "patches/@shopify__lang-jsonc@1.0.0.patch",
"react-router-hono-server": "patches/react-router-hono-server.patch"
}
},
"onlyBuiltDependencies": ["@biomejs/biome", "esbuild", "lefthook"]
}
}