Add internal daemon routes and service management scripts

This commit is contained in:
hibna 2026-02-22 10:16:42 +00:00
parent c9fe2bd9fe
commit 614d25c189
7 changed files with 173 additions and 1 deletions

3
.gitignore vendored
View File

@ -7,6 +7,7 @@ dist/
.env
.env.local
.env.*.local
daemon-dev.yml
# IDE
.idea/
@ -36,4 +37,4 @@ build/
# Claude
.claude/
plans.md
plans.md

View File

@ -7,6 +7,7 @@ import dbPlugin from './plugins/db.js';
import authPlugin from './plugins/auth.js';
import authRoutes from './routes/auth/index.js';
import organizationRoutes from './routes/organizations/index.js';
import internalRoutes from './routes/internal/index.js';
import daemonNodeRoutes from './routes/nodes/daemon.js';
import nodeRoutes from './routes/nodes/index.js';
import serverRoutes from './routes/servers/index.js';
@ -85,6 +86,7 @@ await app.register(authRoutes, { prefix: '/api/auth' });
await app.register(organizationRoutes, { prefix: '/api/organizations' });
await app.register(adminRoutes, { prefix: '/api/admin' });
await app.register(daemonNodeRoutes, { prefix: '/api/nodes' });
await app.register(internalRoutes, { prefix: '/api/internal' });
// Nested org routes: nodes and servers are scoped to an org
await app.register(

View File

@ -0,0 +1,72 @@
import { Type } from '@sinclair/typebox';
import type { FastifyInstance, FastifyRequest } from 'fastify';
import { eq } from 'drizzle-orm';
import { nodes } from '@source/database';
import { AppError } from '../../lib/errors.js';
function extractBearerToken(authHeader?: string): string | null {
if (!authHeader) return null;
const [scheme, token] = authHeader.split(' ');
if (!scheme || !token || scheme.toLowerCase() !== 'bearer') return null;
return token;
}
async function requireDaemonToken(app: FastifyInstance, request: FastifyRequest): Promise<void> {
const token = extractBearerToken(
typeof request.headers.authorization === 'string'
? request.headers.authorization
: undefined,
);
if (!token) {
throw AppError.unauthorized('Missing daemon bearer token', 'DAEMON_AUTH_MISSING');
}
const node = await app.db.query.nodes.findFirst({
where: eq(nodes.daemonToken, token),
columns: { id: true },
});
if (!node) {
throw AppError.unauthorized('Invalid daemon token', 'DAEMON_AUTH_INVALID');
}
}
export default async function internalRoutes(app: FastifyInstance) {
app.get('/schedules/due', async (request) => {
await requireDaemonToken(app, request);
return { tasks: [] };
});
app.post(
'/schedules/:taskId/ack',
{
schema: {
params: Type.Object({
taskId: Type.String(),
}),
},
},
async (request) => {
await requireDaemonToken(app, request);
const { taskId } = request.params as { taskId: string };
return { success: true, taskId };
},
);
app.post(
'/servers/:serverUuid/backup',
{
schema: {
params: Type.Object({
serverUuid: Type.String(),
}),
},
},
async (request) => {
await requireDaemonToken(app, request);
const { serverUuid } = request.params as { serverUuid: string };
return { success: true, serverUuid };
},
);
}

81
scripts/panelctl.sh Executable file
View File

@ -0,0 +1,81 @@
#!/usr/bin/env bash
set -euo pipefail
ROOT="/root/codex/source-gamepanel"
PNPM="/root/.nvm/versions/node/v24.13.1/bin/pnpm"
SERVICES=(
"source-gamepanel-api.service"
"source-gamepanel-web.service"
"source-gamepanel-daemon.service"
)
usage() {
cat <<'EOF'
Usage: panelctl <command>
Commands:
start Start all services
stop Stop all services
restart Restart all services
status Show status of all services
logs Tail logs of all services
rebuild Rebuild API/Web/Daemon binaries
update Pull latest code, rebuild, migrate DB, restart services
deploy Rebuild current code, migrate DB, restart services
EOF
}
rebuild_all() {
cd "$ROOT"
"$PNPM" install --frozen-lockfile
"$PNPM" --filter @source/api build
"$PNPM" --filter @source/web build
source "$HOME/.cargo/env"
cd "$ROOT/apps/daemon"
cargo build --release
}
migrate_db() {
cd "$ROOT"
"$PNPM" db:migrate
}
cmd="${1:-}"
case "$cmd" in
start)
systemctl start "${SERVICES[@]}"
;;
stop)
systemctl stop "${SERVICES[@]}"
;;
restart)
systemctl restart "${SERVICES[@]}"
;;
status)
systemctl --no-pager --full status "${SERVICES[@]}"
;;
logs)
journalctl -u "${SERVICES[0]}" -u "${SERVICES[1]}" -u "${SERVICES[2]}" -f
;;
rebuild)
rebuild_all
;;
update)
cd "$ROOT"
git pull --ff-only
rebuild_all
migrate_db
systemctl restart "${SERVICES[@]}"
systemctl --no-pager --full status "${SERVICES[@]}"
;;
deploy)
rebuild_all
migrate_db
systemctl restart "${SERVICES[@]}"
systemctl --no-pager --full status "${SERVICES[@]}"
;;
*)
usage
exit 1
;;
esac

5
scripts/run-api.sh Executable file
View File

@ -0,0 +1,5 @@
#!/usr/bin/env bash
set -euo pipefail
cd /root/codex/source-gamepanel/apps/api
exec /root/.nvm/versions/node/v24.13.1/bin/pnpm dev

6
scripts/run-daemon.sh Executable file
View File

@ -0,0 +1,6 @@
#!/usr/bin/env bash
set -euo pipefail
cd /root/codex/source-gamepanel/apps/daemon
export DAEMON_CONFIG=/root/codex/source-gamepanel/daemon-dev.yml
exec /root/codex/source-gamepanel/apps/daemon/target/release/gamepanel-daemon

5
scripts/run-web.sh Executable file
View File

@ -0,0 +1,5 @@
#!/usr/bin/env bash
set -euo pipefail
cd /root/codex/source-gamepanel/apps/web
exec /root/.nvm/versions/node/v24.13.1/bin/pnpm dev --host 0.0.0.0 --port 5173