diff --git a/src/client/components/DashboardLanding.tsx b/src/client/components/DashboardLanding.tsx index a771f83..6899adc 100644 --- a/src/client/components/DashboardLanding.tsx +++ b/src/client/components/DashboardLanding.tsx @@ -5,6 +5,7 @@ import { AppSelectorChevron, DashboardContainer, DashboardHeader, + DashboardInstallCmd, DashboardTitle, StatusDot, StatusDotLink, @@ -35,7 +36,9 @@ export function DashboardLanding({ render }: { render: () => void }) { )} - {/*Your personal web appliance*/} + + curl -fsSL {location.origin}/install | bash + diff --git a/src/client/styles/index.ts b/src/client/styles/index.ts index f3e933d..3275071 100644 --- a/src/client/styles/index.ts +++ b/src/client/styles/index.ts @@ -27,6 +27,7 @@ export { ClickableAppName, DashboardContainer, DashboardHeader, + DashboardInstallCmd, DashboardSubtitle, DashboardTitle, HamburgerButton, diff --git a/src/client/styles/layout.ts b/src/client/styles/layout.ts index 6492cde..a223607 100644 --- a/src/client/styles/layout.ts +++ b/src/client/styles/layout.ts @@ -229,6 +229,14 @@ export const DashboardTitle = define('DashboardTitle', { }, }) +export const DashboardInstallCmd = define('DashboardInstallCmd', { + base: 'code', + fontSize: 13, + opacity: 0.6, + userSelect: 'all', + cursor: 'text', +}) + export const DashboardSubtitle = define('DashboardSubtitle', { fontSize: 16, color: theme('colors-textMuted'), diff --git a/src/server/index.tsx b/src/server/index.tsx index 072a2ea..f8f40a2 100644 --- a/src/server/index.tsx +++ b/src/server/index.tsx @@ -6,9 +6,9 @@ import syncRouter from './api/sync' import systemRouter from './api/system' import { Hype } from '@because/hype' import { cleanupStalePublishers } from './mdns' +import { extractSubdomain, proxySubdomain, proxyWebSocket, websocket } from './proxy' import type { Server } from 'bun' import type { WsData } from './proxy' -import { extractSubdomain, proxySubdomain, proxyWebSocket, websocket } from './proxy' const app = new Hype({ layout: false, logging: !!process.env.DEBUG }) @@ -59,6 +59,31 @@ app.all('/api/tools/:tool/:path{.+}', async c => { }) }) +const DIST_DIR = import.meta.dir + '/../../dist' +const INSTALL_SCRIPT = await Bun.file(import.meta.dir + '/install.sh').text() + +// Install script: curl -fsSL http://toes.local/install | bash +app.get('/install', c => { + if (!TOES_URL) return c.text('TOES_URL is not configured', 500) + const script = INSTALL_SCRIPT.replace('__TOES_URL__', TOES_URL) + return c.text(script, 200, { 'content-type': 'text/plain' }) +}) + +// Serve built CLI binaries from dist/ +app.get('/dist/:file', async c => { + const file = c.req.param('file') + if (!file || file.includes('/') || file.includes('..')) { + return c.text('Not found', 404) + } + const bunFile = Bun.file(`${DIST_DIR}/${file}`) + if (!(await bunFile.exists())) { + return c.text(`Binary "${file}" not found — run cli:build:all on the server`, 404) + } + return new Response(bunFile, { + headers: { 'content-type': 'application/octet-stream' }, + }) +}) + cleanupStalePublishers() await initApps() diff --git a/src/server/install.sh b/src/server/install.sh new file mode 100644 index 0000000..eb10d76 --- /dev/null +++ b/src/server/install.sh @@ -0,0 +1,38 @@ +#!/usr/bin/env bash +set -e + +OS=$(uname -s | tr '[:upper:]' '[:lower:]') +ARCH=$(uname -m) + +case "$OS" in + darwin) OS=macos ;; + linux) OS=linux ;; + *) echo "Unsupported OS: $OS" >&2; exit 1 ;; +esac + +case "$ARCH" in + x86_64) ARCH=x64 ;; + arm64|aarch64) ARCH=arm64 ;; + *) echo "Unsupported arch: $ARCH" >&2; exit 1 ;; +esac + +BINARY="toes-${OS}-${ARCH}" +URL="__TOES_URL__/dist/${BINARY}" + +if [ -n "$TOES_INSTALL_DIR" ]; then + INSTALL_DIR="$TOES_INSTALL_DIR" +elif [ -d /usr/local/bin ] && [ -w /usr/local/bin ]; then + INSTALL_DIR=/usr/local/bin +else + INSTALL_DIR="$HOME/.local/bin" +fi +mkdir -p "$INSTALL_DIR" + +echo "Downloading toes CLI (${OS}/${ARCH})..." +curl -fsSL "$URL" -o "$INSTALL_DIR/toes" +chmod +x "$INSTALL_DIR/toes" +echo "Installed toes to $INSTALL_DIR/toes" + +if ! echo "$PATH" | tr ':' '\n' | grep -qx "$INSTALL_DIR"; then + echo "Add $INSTALL_DIR to your PATH to use toes globally" +fi