Add internal daemon routes and service management scripts
This commit is contained in:
parent
c9fe2bd9fe
commit
614d25c189
|
|
@ -7,6 +7,7 @@ dist/
|
||||||
.env
|
.env
|
||||||
.env.local
|
.env.local
|
||||||
.env.*.local
|
.env.*.local
|
||||||
|
daemon-dev.yml
|
||||||
|
|
||||||
# IDE
|
# IDE
|
||||||
.idea/
|
.idea/
|
||||||
|
|
@ -36,4 +37,4 @@ build/
|
||||||
|
|
||||||
# Claude
|
# Claude
|
||||||
.claude/
|
.claude/
|
||||||
plans.md
|
plans.md
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ import dbPlugin from './plugins/db.js';
|
||||||
import authPlugin from './plugins/auth.js';
|
import authPlugin from './plugins/auth.js';
|
||||||
import authRoutes from './routes/auth/index.js';
|
import authRoutes from './routes/auth/index.js';
|
||||||
import organizationRoutes from './routes/organizations/index.js';
|
import organizationRoutes from './routes/organizations/index.js';
|
||||||
|
import internalRoutes from './routes/internal/index.js';
|
||||||
import daemonNodeRoutes from './routes/nodes/daemon.js';
|
import daemonNodeRoutes from './routes/nodes/daemon.js';
|
||||||
import nodeRoutes from './routes/nodes/index.js';
|
import nodeRoutes from './routes/nodes/index.js';
|
||||||
import serverRoutes from './routes/servers/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(organizationRoutes, { prefix: '/api/organizations' });
|
||||||
await app.register(adminRoutes, { prefix: '/api/admin' });
|
await app.register(adminRoutes, { prefix: '/api/admin' });
|
||||||
await app.register(daemonNodeRoutes, { prefix: '/api/nodes' });
|
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
|
// Nested org routes: nodes and servers are scoped to an org
|
||||||
await app.register(
|
await app.register(
|
||||||
|
|
|
||||||
|
|
@ -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 };
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -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
|
||||||
|
|
@ -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
|
||||||
|
|
@ -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
|
||||||
|
|
@ -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
|
||||||
Loading…
Reference in New Issue