mirror of
https://github.com/tale/headplane.git
synced 2026-07-26 07:48:14 +00:00
Merge pull request #335 from tale/next
This commit is contained in:
+8
-4
@@ -1,7 +1,11 @@
|
||||
node_modules
|
||||
.dockerignore
|
||||
Dockerfile
|
||||
|
||||
/.cache
|
||||
/.github
|
||||
/.react-router
|
||||
/node_modules
|
||||
/build
|
||||
/docs
|
||||
/nix
|
||||
|
||||
.dockerignore
|
||||
.env
|
||||
Dockerfile
|
||||
|
||||
@@ -55,7 +55,7 @@ jobs:
|
||||
key: ${{ runner.os }}-go-${{ hashFiles('**/go.mod', '**/go.sum') }}
|
||||
|
||||
- name: CI pipeline
|
||||
run: mise run ci
|
||||
run: ./build.sh --skip-pnpm-prune
|
||||
|
||||
nix:
|
||||
name: nix
|
||||
|
||||
+4
-1
@@ -6,7 +6,10 @@ node_modules
|
||||
.env
|
||||
/app/hp_ssh.wasm
|
||||
/app/wasm_exec.js
|
||||
|
||||
/public/hp_ssh.wasm
|
||||
/public/wasm_exec.js
|
||||
|
||||
/docs/.vitepress/dist/
|
||||
/docs/.vitepress/cache/
|
||||
|
||||
/.direnv
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
{
|
||||
"formatter": {
|
||||
"language_server": {
|
||||
"name": "biome"
|
||||
}
|
||||
},
|
||||
"code_actions_on_format": {
|
||||
"source.fixAll.biome": true,
|
||||
"source.organizeImports.biome": true,
|
||||
"source.organizeImports": true
|
||||
},
|
||||
"languages": {
|
||||
"YAML": {
|
||||
"tab_size": 2,
|
||||
"hard_tabs": false
|
||||
},
|
||||
"Go": {
|
||||
"formatter": {
|
||||
"language_server": {
|
||||
"name": "gopls"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,10 @@
|
||||
# 0.6.2 (Next)
|
||||
- Bundle all `node_modules` aside from native ones to reduce bundle and container size (closes [#331](https://github.com/tale/headplane/issues/331)).
|
||||
- Allow conditionally compiling the SSH WASM integration when building (closes [#337](https://github.com/tale/headplane/issues/337)).
|
||||
- Implemented the ability to customize the build with a custom script (see `./build.sh --help` for more information).
|
||||
|
||||
---
|
||||
|
||||
# 0.6.1 (October 12, 2025)
|
||||
- **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.
|
||||
|
||||
+41
-38
@@ -1,11 +1,7 @@
|
||||
FROM --platform=$BUILDPLATFORM jdxcode/mise:latest AS mise-context
|
||||
COPY mise.toml ./
|
||||
RUN --mount=type=secret,id=gh_token,env=MISE_GITHUB_TOKEN mise install
|
||||
FROM --platform=$BUILDPLATFORM golang:1.25.1 AS go-base
|
||||
WORKDIR /run
|
||||
|
||||
FROM --platform=$BUILDPLATFORM mise-context AS go-build
|
||||
WORKDIR /build/
|
||||
|
||||
COPY go.mod go.sum ./
|
||||
COPY go.mod go.sum build.sh ./
|
||||
RUN go mod download
|
||||
|
||||
COPY cmd/ ./cmd/
|
||||
@@ -14,50 +10,57 @@ COPY internal/ ./internal/
|
||||
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 GOOS=$TARGETOS GOARCH=$TARGETARCH CGO_ENABLED=0 IMAGE_TAG=$IMAGE_TAG \
|
||||
./build.sh --wasm --agent --fake-shell \
|
||||
--wasm-output /bin/hp_ssh.wasm \
|
||||
--agent-output /bin/hp_agent \
|
||||
--fake-shell-output /bin/fake-sh
|
||||
|
||||
RUN chmod +x /build/build/hp_agent
|
||||
RUN chmod +x /build/build/sh
|
||||
RUN chmod +x /bin/hp_ssh.wasm
|
||||
RUN chmod +x /bin/hp_agent
|
||||
RUN chmod +x /bin/fake-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 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
|
||||
|
||||
ARG IMAGE_TAG
|
||||
RUN IMAGE_TAG=$IMAGE_TAG pnpm run build
|
||||
# Folder needs to exist for later stages
|
||||
RUN mkdir -p /var/lib/headplane/agent
|
||||
|
||||
FROM --platform=$BUILDPLATFORM node:22.16-slim AS js-base
|
||||
WORKDIR /run
|
||||
|
||||
RUN corepack enable
|
||||
COPY patches ./patches
|
||||
COPY package.json pnpm-lock.yaml build.sh ./
|
||||
|
||||
COPY --from=go-base /bin/hp_ssh.wasm /run/app/hp_ssh.wasm
|
||||
COPY --from=go-base /bin/wasm_exec.js /run/app/wasm_exec.js
|
||||
RUN ./build.sh --app --app-install-only
|
||||
|
||||
COPY . .
|
||||
RUN ./build.sh --app
|
||||
|
||||
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
|
||||
COPY --from=js-base /run/build /app/build
|
||||
COPY --from=js-base /run/drizzle /app/drizzle
|
||||
COPY --from=js-base /run/node_modules /app/node_modules
|
||||
|
||||
COPY --from=go-base /bin/hp_agent /usr/libexec/headplane/agent
|
||||
COPY --from=go-base /var/lib/headplane /var/lib/headplane
|
||||
|
||||
# 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
|
||||
COPY --from=go-base /bin/fake-sh /bin/sh
|
||||
COPY --from=go-base /bin/fake-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
|
||||
RUN apk add --no-cache bash curl
|
||||
|
||||
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
|
||||
COPY --from=js-base /run/build /app/build
|
||||
COPY --from=js-base /run/drizzle /app/drizzle
|
||||
COPY --from=js-base /run/node_modules /app/node_modules
|
||||
|
||||
COPY --from=go-base /bin/hp_agent /usr/libexec/headplane/agent
|
||||
COPY --from=go-base /var/lib/headplane /var/lib/headplane
|
||||
|
||||
WORKDIR /app
|
||||
CMD [ "node", "/app/build/server/index.js" ]
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
FROM golang:1.23 AS builder
|
||||
WORKDIR /app
|
||||
|
||||
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
|
||||
|
||||
FROM scratch
|
||||
COPY --from=builder /app/hp_agent /hp_agent
|
||||
ENTRYPOINT ["/hp_agent"]
|
||||
+4
-2
@@ -8,6 +8,7 @@ import {
|
||||
useNavigation,
|
||||
} from 'react-router';
|
||||
import '@fontsource-variable/inter';
|
||||
import { ExternalScripts } from 'remix-utils/external-scripts';
|
||||
import { ErrorPopup } from '~/components/Error';
|
||||
import ProgressBar from '~/components/ProgressBar';
|
||||
import ToastProvider from '~/components/ToastProvider';
|
||||
@@ -38,16 +39,17 @@ export function Layout({ children }: { readonly children: React.ReactNode }) {
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charSet="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta content="width=device-width, initial-scale=1" name="viewport" />
|
||||
<Meta />
|
||||
<Links />
|
||||
<link rel="icon" href="favicon.ico" />
|
||||
<link href="favicon.ico" rel="icon" />
|
||||
</head>
|
||||
<body className="overscroll-none dark:bg-headplane-900 dark:text-headplane-50">
|
||||
{children}
|
||||
<ToastProvider queue={toastQueue} />
|
||||
<ScrollRestoration />
|
||||
<Scripts />
|
||||
<ExternalScripts />
|
||||
</body>
|
||||
</html>
|
||||
</LiveDataProvider>
|
||||
|
||||
+76
-41
@@ -1,4 +1,5 @@
|
||||
/** biome-ignore-all lint/correctness/noNestedComponentDefinitions: Wtf? */
|
||||
|
||||
import { faker } from '@faker-js/faker';
|
||||
import { eq } from 'drizzle-orm';
|
||||
import { Loader2 } from 'lucide-react';
|
||||
@@ -6,17 +7,17 @@ import { useEffect, useState } from 'react';
|
||||
import {
|
||||
ActionFunctionArgs,
|
||||
data,
|
||||
LinksFunction,
|
||||
LoaderFunctionArgs,
|
||||
ShouldRevalidateFunction,
|
||||
useLoaderData,
|
||||
useSubmit,
|
||||
} from 'react-router';
|
||||
import wasm from '~/hp_ssh.wasm?url';
|
||||
import { ExternalScriptsHandle } from 'remix-utils/external-scripts';
|
||||
import { LoadContext } from '~/server';
|
||||
import { EphemeralNodeInsert, ephemeralNodes } from '~/server/db/schema';
|
||||
import { Machine, PreAuthKey, User } from '~/types';
|
||||
import { useLiveData } from '~/utils/live-data';
|
||||
import '~/wasm_exec';
|
||||
import UserPrompt from './user-prompt';
|
||||
import XTerm from './xterm.client';
|
||||
|
||||
@@ -28,6 +29,19 @@ export async function loader({
|
||||
request,
|
||||
context,
|
||||
}: LoaderFunctionArgs<LoadContext>) {
|
||||
const origin = new URL(request.url).origin;
|
||||
const assets = ['/wasm_exec.js', '/hp_ssh.wasm'];
|
||||
const missing: string[] = [];
|
||||
|
||||
for (const file of assets) {
|
||||
const res = await fetch(`${origin}${file}`, { method: 'HEAD' });
|
||||
if (!res.ok) missing.push(file);
|
||||
}
|
||||
|
||||
if (missing.length > 0) {
|
||||
throw data('WebSSH is not configured in this build.', 405);
|
||||
}
|
||||
|
||||
if (!context.agents?.agentID()) {
|
||||
throw data(
|
||||
'WebSSH is only available with the Headplane agent integration',
|
||||
@@ -200,6 +214,26 @@ export async function action({
|
||||
.where(eq(ephemeralNodes.auth_key, authKey));
|
||||
}
|
||||
|
||||
export const links: LinksFunction = () => [
|
||||
{
|
||||
rel: 'preload',
|
||||
href: '/hp_ssh.wasm',
|
||||
as: 'fetch',
|
||||
type: 'application/wasm',
|
||||
crossOrigin: 'anonymous',
|
||||
},
|
||||
];
|
||||
|
||||
export const handle: ExternalScriptsHandle = {
|
||||
scripts: [
|
||||
{
|
||||
src: '/wasm_exec.js',
|
||||
crossOrigin: 'anonymous',
|
||||
preload: true,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export default function Page() {
|
||||
const submit = useSubmit();
|
||||
const { pause } = useLiveData();
|
||||
@@ -215,46 +249,47 @@ export default function Page() {
|
||||
|
||||
pause();
|
||||
const go = new Go(); // Go is defined by wasm_exec.js
|
||||
WebAssembly.instantiateStreaming(fetch(wasm), go.importObject).then(
|
||||
(value) => {
|
||||
go.run(value.instance);
|
||||
const handle = TsWasmNet(ipnDetails, {
|
||||
NotifyState: (state) => {
|
||||
console.log('State changed:', state);
|
||||
if (state === 'Running') {
|
||||
setIpn(handle);
|
||||
}
|
||||
},
|
||||
NotifyNetMap: (netmap) => {
|
||||
// Only set NodeKey if it is not already set and then
|
||||
// also dispatch that to the backend to track the
|
||||
// ephemeral node.
|
||||
//
|
||||
// We open an SSE connection to the backend
|
||||
// so that when the connection is closed,
|
||||
// the backend can delete the ephemeral node.
|
||||
if (nodeKey === null) {
|
||||
setNodeKey(netmap.NodeKey);
|
||||
submit(
|
||||
{
|
||||
node_key: netmap.NodeKey,
|
||||
auth_key: ipnDetails.PreAuthKey,
|
||||
},
|
||||
{ method: 'POST' },
|
||||
);
|
||||
}
|
||||
},
|
||||
NotifyBrowseToURL: (url) => {
|
||||
console.log('Browse to URL:', url);
|
||||
},
|
||||
NotifyPanicRecover: (message) => {
|
||||
console.error('Panic recover:', message);
|
||||
},
|
||||
});
|
||||
WebAssembly.instantiateStreaming(
|
||||
fetch('/hp_ssh.wasm'),
|
||||
go.importObject,
|
||||
).then((value) => {
|
||||
go.run(value.instance);
|
||||
const handle = TsWasmNet(ipnDetails, {
|
||||
NotifyState: (state) => {
|
||||
console.log('State changed:', state);
|
||||
if (state === 'Running') {
|
||||
setIpn(handle);
|
||||
}
|
||||
},
|
||||
NotifyNetMap: (netmap) => {
|
||||
// Only set NodeKey if it is not already set and then
|
||||
// also dispatch that to the backend to track the
|
||||
// ephemeral node.
|
||||
//
|
||||
// We open an SSE connection to the backend
|
||||
// so that when the connection is closed,
|
||||
// the backend can delete the ephemeral node.
|
||||
if (nodeKey === null) {
|
||||
setNodeKey(netmap.NodeKey);
|
||||
submit(
|
||||
{
|
||||
node_key: netmap.NodeKey,
|
||||
auth_key: ipnDetails.PreAuthKey,
|
||||
},
|
||||
{ method: 'POST' },
|
||||
);
|
||||
}
|
||||
},
|
||||
NotifyBrowseToURL: (url) => {
|
||||
console.log('Browse to URL:', url);
|
||||
},
|
||||
NotifyPanicRecover: (message) => {
|
||||
console.error('Panic recover:', message);
|
||||
},
|
||||
});
|
||||
|
||||
handle.Start();
|
||||
},
|
||||
);
|
||||
handle.Start();
|
||||
});
|
||||
}, []);
|
||||
|
||||
if (!sshDetails.username) {
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
import { Icon } from '@iconify/react';
|
||||
import { ArrowRight } from 'lucide-react';
|
||||
import { useEffect } from 'react';
|
||||
import { GrApple } from 'react-icons/gr';
|
||||
import { ImFinder } from 'react-icons/im';
|
||||
import { MdAndroid } from 'react-icons/md';
|
||||
import { PiTerminalFill, PiWindowsLogoFill } from 'react-icons/pi';
|
||||
import { LoaderFunctionArgs, NavLink, useLoaderData } from 'react-router';
|
||||
import Button from '~/components/Button';
|
||||
import Card from '~/components/Card';
|
||||
@@ -132,7 +129,7 @@ export default function Page() {
|
||||
key="linux"
|
||||
title={
|
||||
<div className="flex items-center gap-1">
|
||||
<PiTerminalFill className="ml-1 w-4" />
|
||||
<Icon className="ml-1 w-4" icon="ion:terminal" />
|
||||
<span>Linux</span>
|
||||
</div>
|
||||
}
|
||||
@@ -163,7 +160,7 @@ export default function Page() {
|
||||
key="windows"
|
||||
title={
|
||||
<div className="flex items-center gap-1">
|
||||
<PiWindowsLogoFill className="ml-1 w-4" />
|
||||
<Icon className="ml-1 w-4" icon="mdi:microsoft" />
|
||||
<span>Windows</span>
|
||||
</div>
|
||||
}
|
||||
@@ -186,7 +183,10 @@ export default function Page() {
|
||||
key="macos"
|
||||
title={
|
||||
<div className="flex items-center gap-1">
|
||||
<ImFinder className="ml-1 w-4" />
|
||||
<Icon
|
||||
className="ml-1 w-4"
|
||||
icon="streamline-logos:mac-finder-logo-solid"
|
||||
/>
|
||||
<span>macOS</span>
|
||||
</div>
|
||||
}
|
||||
@@ -218,7 +218,7 @@ export default function Page() {
|
||||
key="ios"
|
||||
title={
|
||||
<div className="flex items-center gap-1">
|
||||
<GrApple className="ml-1 w-4" />
|
||||
<Icon className="ml-1 w-4" icon="grommet-icons:apple" />
|
||||
<span>iOS</span>
|
||||
</div>
|
||||
}
|
||||
@@ -241,7 +241,7 @@ export default function Page() {
|
||||
key="android"
|
||||
title={
|
||||
<div className="flex items-center gap-1">
|
||||
<MdAndroid className="ml-1 w-4" />
|
||||
<Icon className="ml-1 w-4" icon="material-symbols:android" />
|
||||
<span>Android</span>
|
||||
</div>
|
||||
}
|
||||
|
||||
@@ -106,3 +106,13 @@ export default createHonoServer({
|
||||
log.info('server', 'Running on %s:%s', info.address, info.port);
|
||||
},
|
||||
});
|
||||
|
||||
process.on('SIGINT', () => {
|
||||
log.info('server', 'Received SIGINT, shutting down...');
|
||||
process.exit(0);
|
||||
});
|
||||
|
||||
process.on('SIGTERM', () => {
|
||||
log.info('server', 'Received SIGTERM, shutting down...');
|
||||
process.exit(0);
|
||||
});
|
||||
|
||||
@@ -0,0 +1,166 @@
|
||||
#!/bin/sh
|
||||
# This is a general purpose build script for Headplane to be used across many
|
||||
# different environments such as CI, Docker, Nix, and locally. For specific
|
||||
# usage instructions, run `./build.sh --help`.
|
||||
|
||||
set -eu
|
||||
|
||||
ROOT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
cd "$ROOT_DIR" || exit 1
|
||||
|
||||
APP_DIR="$ROOT_DIR/app"
|
||||
BUILD_DIR="$ROOT_DIR/build"
|
||||
PUBLIC_DIR="$ROOT_DIR/public"
|
||||
|
||||
BUILD_WASM=0
|
||||
BUILD_APP=0
|
||||
BUILD_AGENT=0
|
||||
BUILD_FAKE_SHELL=0
|
||||
SKIP_PATH_CHECKS=0
|
||||
SKIP_PNPM_PRUNE=0
|
||||
APP_INSTALL_ONLY=0
|
||||
|
||||
WASM_OUTPUT="$PUBLIC_DIR/hp_ssh.wasm"
|
||||
AGENT_OUTPUT="$BUILD_DIR/hp_agent"
|
||||
FAKE_SHELL_OUTPUT="$BUILD_DIR/hp_fake_sh"
|
||||
|
||||
die() { echo "error: $*" >&2; exit 1; }
|
||||
run() { echo ">> $*"; "$@"; }
|
||||
|
||||
while [ $# -gt 0 ]; do
|
||||
case "$1" in
|
||||
--wasm) BUILD_WASM=1 ;;
|
||||
--app) BUILD_APP=1 ;;
|
||||
--agent) BUILD_AGENT=1 ;;
|
||||
--fake-shell) BUILD_FAKE_SHELL=1 ;;
|
||||
--skip-path-checks) SKIP_PATH_CHECKS=1 ;;
|
||||
--skip-pnpm-prune) SKIP_PNPM_PRUNE=1 ;;
|
||||
--app-install-only) APP_INSTALL_ONLY=1 ;;
|
||||
|
||||
--wasm-output)
|
||||
shift
|
||||
[ $# -gt 0 ] || die "--wasm-output requires a path"
|
||||
WASM_OUTPUT=$1
|
||||
;;
|
||||
|
||||
--agent-output)
|
||||
shift
|
||||
[ $# -gt 0 ] || die "--agent-output requires a path"
|
||||
AGENT_OUTPUT=$1
|
||||
;;
|
||||
|
||||
--fake-shell-output)
|
||||
shift
|
||||
[ $# -gt 0 ] || die "--fake-shell-output requires a path"
|
||||
FAKE_SHELL_OUTPUT=$1
|
||||
;;
|
||||
|
||||
--help)
|
||||
cat <<EOF
|
||||
Usage: $0 [flags]
|
||||
--wasm build wasm module
|
||||
--app build react-router app
|
||||
--agent build tailscale agent
|
||||
--fake-shell build fake shell binary (for Docker)
|
||||
--skip-path-checks skip safety checks (ie. checking PATH)
|
||||
--skip-pnpm-prune skip pruning devDependencies from node_modules
|
||||
--app-install-only only install app dependencies, skip build
|
||||
--wasm-output <path> override wasm output path
|
||||
--agent-output <path> override agent output path
|
||||
--fake-shell-output <path> override fake shell output path
|
||||
EOF
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
die "unknown flag: $1"
|
||||
;;
|
||||
esac
|
||||
shift
|
||||
done
|
||||
|
||||
# By default build everything except for the fake shell
|
||||
if [ "$BUILD_WASM" -eq 0 ] && [ "$BUILD_APP" -eq 0 ] && \
|
||||
[ "$BUILD_AGENT" -eq 0 ] && [ "$BUILD_FAKE_SHELL" -eq 0 ]; then
|
||||
BUILD_WASM=1
|
||||
BUILD_APP=1
|
||||
BUILD_AGENT=1
|
||||
fi
|
||||
|
||||
if [ "$SKIP_PATH_CHECKS" -eq 0 ]; then
|
||||
[ -d "$ROOT_DIR" ] || die "missing project root"
|
||||
|
||||
need_go=0
|
||||
need_pnpm=0
|
||||
|
||||
[ "$BUILD_WASM" -eq 1 ] && need_go=1
|
||||
[ "$BUILD_APP" -eq 1 ] && need_pnpm=1
|
||||
[ "$BUILD_AGENT" -eq 1 ] && need_go=1
|
||||
[ "$BUILD_FAKE_SHELL" -eq 1 ] && need_go=1
|
||||
|
||||
if [ $need_go -eq 1 ]; then
|
||||
echo "==> Checking for Go toolchain"
|
||||
command -v go >/dev/null 2>&1 || die "go not installed"
|
||||
go version >/dev/null 2>&1 || die "go not working"
|
||||
fi
|
||||
|
||||
if [ $need_pnpm -eq 1 ]; then
|
||||
echo "==> Checking for pnpm"
|
||||
command -v pnpm >/dev/null 2>&1 || die "pnpm not installed"
|
||||
pnpm --version >/dev/null 2>&1 || die "pnpm not working"
|
||||
fi
|
||||
fi
|
||||
|
||||
build_wasm() {
|
||||
echo "==> Building SSH WASM module → $WASM_OUTPUT"
|
||||
mkdir -p "$(dirname "$WASM_OUTPUT")"
|
||||
echo "// $(go version)" > "$(dirname "$WASM_OUTPUT")/wasm_exec.js"
|
||||
|
||||
# This depends on Go 1.23+ since the path is different in earlier versions
|
||||
cat "$(go env GOROOT)/lib/wasm/wasm_exec.js" >> \
|
||||
"$(dirname "$WASM_OUTPUT")/wasm_exec.js"
|
||||
|
||||
GOOS=js GOARCH=wasm go build -o "$WASM_OUTPUT" ./cmd/hp_ssh
|
||||
}
|
||||
|
||||
build_app() {
|
||||
echo "==> Building React Router app → $BUILD_DIR"
|
||||
[ -f "$WASM_OUTPUT" ] || echo "warning: Building without SSH WASM module"
|
||||
pnpm install --frozen-lockfile
|
||||
|
||||
if [ "$APP_INSTALL_ONLY" -eq 1 ]; then
|
||||
echo "==> Skipping app build (install only)"
|
||||
return
|
||||
fi
|
||||
|
||||
pnpm run build
|
||||
|
||||
if [ "$SKIP_PNPM_PRUNE" -eq 0 ]; then
|
||||
echo "==> Pruning devDependencies from node_modules"
|
||||
pnpm prune --prod
|
||||
fi
|
||||
}
|
||||
|
||||
build_agent() {
|
||||
echo "==> Building Tailscale agent → $AGENT_OUTPUT"
|
||||
mkdir -p "$(dirname "$AGENT_OUTPUT")"
|
||||
go build -o "$AGENT_OUTPUT" ./cmd/hp_agent
|
||||
}
|
||||
|
||||
build_fake_shell() {
|
||||
[ -n "${IMAGE_TAG:-}" ] || die \
|
||||
"\$IMAGE_TAG is required to build fake shell binary"
|
||||
|
||||
echo "==> Building fake shell binary → $FAKE_SHELL_OUTPUT"
|
||||
mkdir -p "$(dirname "$FAKE_SHELL_OUTPUT")"
|
||||
go build -ldflags="-s -w -X main.imageTag=${IMAGE_TAG}" \
|
||||
-o "$FAKE_SHELL_OUTPUT" ./cmd/fake_sh
|
||||
}
|
||||
|
||||
[ "$BUILD_FAKE_SHELL" = 1 ] && build_fake_shell
|
||||
|
||||
[ "$BUILD_WASM" = 1 ] && build_wasm
|
||||
[ "$BUILD_APP" = 1 ] && build_app
|
||||
[ "$BUILD_AGENT" = 1 ] && build_agent
|
||||
[ "$BUILD_FAKE_SHELL" = 1 ] && build_fake_shell
|
||||
|
||||
echo "✅ Build complete."
|
||||
@@ -3,37 +3,6 @@ go = "1.25.1"
|
||||
pnpm = "10.4.0"
|
||||
node = "22.16"
|
||||
|
||||
[tasks.copy-wasm-shim]
|
||||
alias = ["gojs"]
|
||||
description = "Copies Go's wasm_exec.js to the public directory"
|
||||
run = [
|
||||
"echo // $(go version) > app/wasm_exec.js",
|
||||
"cat $(go env GOROOT)/lib/wasm/wasm_exec.js >> app/wasm_exec.js",
|
||||
]
|
||||
|
||||
[tasks.build-go-wasm]
|
||||
alias = ["wasm"]
|
||||
depends = ["copy-wasm-shim"]
|
||||
description = "Builds the Go WebAssembly module for Tailscale SSH"
|
||||
env = { GOOS = "js", GOARCH = "wasm" }
|
||||
run = "go build -o app/hp_ssh.wasm ./cmd/hp_ssh"
|
||||
|
||||
[tasks.build-go-agent]
|
||||
alias = ["agent"]
|
||||
description = "Builds the Go agent for HostInfo"
|
||||
run = "go build -o build/hp_agent ./cmd/hp_agent"
|
||||
# env = { CGO_ENABLED = "1" }
|
||||
# run = "go build -o build/hp_agent.so -buildmode=c-shared ./cmd/hp_agent"
|
||||
|
||||
|
||||
[tasks.build-fake-shell]
|
||||
alias = ["fake-shell"]
|
||||
description = "Builds the fake shell for Distroless docker images"
|
||||
run = [
|
||||
'test -n "$IMAGE_TAG" || (echo "IMAGE_TAG is not set" && exit 1)',
|
||||
'go build -ldflags="-s -w -X main.imageTag=$IMAGE_TAG" -o build/sh ./cmd/fake_sh'
|
||||
]
|
||||
|
||||
[tasks.build-nixos-docs]
|
||||
alias = ["nixos-docs"]
|
||||
description = "Builds NixOS module documentation"
|
||||
|
||||
+1
-1
@@ -28,7 +28,7 @@ in
|
||||
|
||||
pnpmDeps = pnpm_10.fetchDeps {
|
||||
inherit (finalAttrs) pname version src;
|
||||
hash = "sha256-KyUcaR2Lvu5kT8arr4ZO8rCa5HWXTqmk8C7P8WoYK+c=";
|
||||
hash = "sha256-KniPa9RMQAy5TY7H/TmETQNjqnuJv/kdFuRQdIoE/gQ=";
|
||||
fetcherVersion = 1;
|
||||
};
|
||||
|
||||
|
||||
+28
-30
@@ -6,9 +6,7 @@
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"preinstall": "npx only-allow pnpm",
|
||||
"prebuild": "npx @jdxcode/mise run build-go-wasm",
|
||||
"build": "react-router build",
|
||||
"postbuild": "npx @jdxcode/mise run build-go-agent",
|
||||
"dev": "HEADPLANE_LOAD_ENV_OVERRIDES=true HEADPLANE_CONFIG_PATH=./config.example.yaml react-router dev",
|
||||
"start": "node build/server/index.js",
|
||||
"typecheck": "react-router typegen && tsgo",
|
||||
@@ -18,20 +16,28 @@
|
||||
"docs:preview": "vitepress preview docs"
|
||||
},
|
||||
"dependencies": {
|
||||
"@libsql/client": "0.15.12"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@biomejs/biome": "^2.2.0",
|
||||
"@dnd-kit/core": "^6.3.1",
|
||||
"@dnd-kit/modifiers": "^7.0.0",
|
||||
"@dnd-kit/sortable": "^8.0.0",
|
||||
"@dnd-kit/utilities": "^3.2.2",
|
||||
"@faker-js/faker": "9.9.0",
|
||||
"@fontsource-variable/inter": "^5.2.6",
|
||||
"@fontsource-variable/inter": "^5.2.8",
|
||||
"@iconify/react": "^6.0.2",
|
||||
"@kubernetes/client-node": "^1.3.0",
|
||||
"@libsql/client": "0.15.12",
|
||||
"@react-aria/toast": "3.0.6",
|
||||
"@react-aria/toast": "3.0.8",
|
||||
"@react-router/dev": "^7.8.1",
|
||||
"@react-router/node": "^7.8.1",
|
||||
"@react-stately/toast": "3.1.2",
|
||||
"@shopify/lang-jsonc": "^1.0.1",
|
||||
"@types/react": "^19.1.10",
|
||||
"@types/react-dom": "^19.1.7",
|
||||
"@tailwindcss/vite": "^4.1.12",
|
||||
"@types/node": "^24.3.0",
|
||||
"@types/react": "^19.2.2",
|
||||
"@types/react-dom": "^19.2.2",
|
||||
"@typescript/native-preview": "7.0.0-dev.20250821.1",
|
||||
"@uiw/codemirror-theme-github": "4.25.1",
|
||||
"@uiw/codemirror-theme-xcode": "4.25.1",
|
||||
"@uiw/react-codemirror": "4.25.1",
|
||||
@@ -43,48 +49,40 @@
|
||||
"arktype": "^2.1.20",
|
||||
"clsx": "^2.1.1",
|
||||
"dotenv": "17.2.1",
|
||||
"drizzle-kit": "^0.31.4",
|
||||
"drizzle-orm": "0.44.4",
|
||||
"isbot": "5.1.30",
|
||||
"jose": "6.1.0",
|
||||
"js-yaml": "^4.1.0",
|
||||
"lefthook": "^1.12.3",
|
||||
"lucide-react": "^0.540.0",
|
||||
"mime": "^4.0.7",
|
||||
"openid-client": "6.7.0",
|
||||
"react": "19.1.1",
|
||||
"react-aria": "3.42.0",
|
||||
"postcss": "^8.5.6",
|
||||
"react": "19.2.0",
|
||||
"react-aria": "3.44.0",
|
||||
"react-codemirror-merge": "4.25.1",
|
||||
"react-dom": "19.1.1",
|
||||
"react-dom": "19.2.0",
|
||||
"react-error-boundary": "^6.0.0",
|
||||
"react-icons": "^5.5.0",
|
||||
"react-router": "^7.8.1",
|
||||
"react-router-dom": "^7.8.1",
|
||||
"react-router-hono-server": "2.21.0",
|
||||
"react-stately": "3.40.0",
|
||||
"react-scan": "^0.4.3",
|
||||
"react-stately": "3.42.0",
|
||||
"remix-utils": "^8.8.0",
|
||||
"tailwind-merge": "3.3.1",
|
||||
"ulidx": "2.4.1",
|
||||
"undici": "7.14.0",
|
||||
"usehooks-ts": "^3.1.1",
|
||||
"yaml": "2.8.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@biomejs/biome": "^2.2.0",
|
||||
"@react-router/dev": "^7.8.1",
|
||||
"@tailwindcss/vite": "^4.1.12",
|
||||
"@types/node": "^24.3.0",
|
||||
"@typescript/native-preview": "7.0.0-dev.20250821.1",
|
||||
"drizzle-kit": "^0.31.4",
|
||||
"js-yaml": "^4.1.0",
|
||||
"lefthook": "^1.12.3",
|
||||
"postcss": "^8.5.6",
|
||||
"react-router-dom": "^7.8.1",
|
||||
"react-scan": "^0.4.3",
|
||||
"tailwindcss": "^4.1.12",
|
||||
"tailwindcss-animate": "^1.0.7",
|
||||
"tailwindcss-react-aria-components": "^2.0.0",
|
||||
"typescript": "^5.9.2",
|
||||
"ulidx": "2.4.1",
|
||||
"undici": "7.14.0",
|
||||
"usehooks-ts": "^3.1.1",
|
||||
"vite": "npm:rolldown-vite@7.1.4",
|
||||
"vite-tsconfig-paths": "^5.1.4",
|
||||
"vitepress": "next",
|
||||
"vitest": "^3.1.3"
|
||||
"vitest": "^3.1.3",
|
||||
"yaml": "2.8.1"
|
||||
},
|
||||
"packageManager": "pnpm@10.4.0",
|
||||
"engines": {
|
||||
|
||||
Generated
+1158
-1310
File diff suppressed because it is too large
Load Diff
+14
-1
@@ -37,10 +37,23 @@ export default defineConfig(({ command, isSsrBuild }) => ({
|
||||
},
|
||||
build: {
|
||||
target: 'esnext',
|
||||
sourcemap: true,
|
||||
rolldownOptions:
|
||||
command === 'build'
|
||||
? {
|
||||
// Exclude libsql from the server side since it's a native module
|
||||
// Exclude WASM from the client since it fetches from the server
|
||||
external: isSsrBuild ? [/^@libsql\//] : [/\.wasm(\?url)?$/],
|
||||
output: {
|
||||
manualChunks: undefined,
|
||||
inlineDynamicImports: isSsrBuild,
|
||||
},
|
||||
}
|
||||
: undefined,
|
||||
},
|
||||
ssr: {
|
||||
target: 'node',
|
||||
noExternal: isSsrBuild ? ['@libsql/client'] : undefined,
|
||||
noExternal: command === 'build' ? true : undefined,
|
||||
},
|
||||
optimizeDeps: {
|
||||
include: ['@libsql/client'],
|
||||
|
||||
Reference in New Issue
Block a user