feat(vpn): VPN on-demand Tailscale + agent studioE5 standalone

- Agent studioE5 standalone en Go (console + systray)
- VPN on-demand via tailscaled + tailscale up (authkey Headscale)
- Resolver/serveur dans le tailnet studioe5
- Caddy on-demand TLS pour les instances
- Nouveaux endpoints serveur /api/internal/send-to-node
- Suppression des anciens binaires edubox-agent
- Suivi dans SUIVI_VPN_ONDEMAND.md
This commit is contained in:
EduBox Dev
2026-06-23 09:48:00 +00:00
parent dd49993157
commit 124543d658
40 changed files with 1303 additions and 485 deletions
+1 -1
View File
@@ -6,7 +6,7 @@ export default function LoginPage() {
return (
<div className="flex min-h-screen items-center justify-center bg-gray-50">
<div className="w-full max-w-md p-8 space-y-6 bg-white rounded-lg shadow-md">
<h1 className="text-2xl font-bold text-center text-gray-900">EduBox V2</h1>
<h1 className="text-2xl font-bold text-center text-gray-900">studioE5</h1>
<p className="text-center text-muted-foreground">Connexion à la plateforme</p>
<LoginForm />
</div>
+4 -1
View File
@@ -11,7 +11,10 @@ export async function GET(req: NextRequest) {
return NextResponse.json({ ok: false }, { status: 400 });
}
if (domain === MAIN_DOMAIN || domain === `headscale.${MAIN_DOMAIN}`) {
if (
domain === MAIN_DOMAIN ||
domain === `headscale.${MAIN_DOMAIN}`
) {
return NextResponse.json({ ok: true });
}
+4 -3
View File
@@ -1,12 +1,13 @@
import { NextResponse } from "next/server";
const AGENT_VERSION = "0.3.0";
const AGENT_BIN_NAME = "studioE5-agent";
export async function GET() {
return NextResponse.json({
version: AGENT_VERSION,
windows: `/edubox-agent-v${AGENT_VERSION}.exe`,
linux: `/edubox-agent-v${AGENT_VERSION}`,
mac: `/edubox-agent-v${AGENT_VERSION}-mac`,
windows: `/${AGENT_BIN_NAME}-v${AGENT_VERSION}.exe`,
linux: `/${AGENT_BIN_NAME}-v${AGENT_VERSION}`,
mac: `/${AGENT_BIN_NAME}-v${AGENT_VERSION}-mac`,
});
}
@@ -0,0 +1,12 @@
import { NextRequest, NextResponse } from "next/server";
import { sendToNode } from "@/lib/websocket";
export async function POST(req: NextRequest) {
const body = await req.json();
const { nodeId, message } = body;
if (!nodeId || !message) {
return NextResponse.json({ error: "Missing nodeId or message" }, { status: 400 });
}
const sent = sendToNode(nodeId, message);
return NextResponse.json({ sent });
}
+1 -1
View File
@@ -26,7 +26,7 @@ export default function DashboardNav({ role }: { role: string }) {
return (
<nav className="w-64 bg-white border-r flex flex-col">
<div className="p-6 border-b">
<h2 className="text-xl font-bold text-primary">EduBox</h2>
<h2 className="text-xl font-bold text-primary">studioE5</h2>
</div>
<div className="flex-1 p-4 space-y-1">
{links.map((link) => (
+3 -2
View File
@@ -1,6 +1,7 @@
import { Card, CardHeader, CardTitle, CardContent } from "@/components/ui/card";
const AGENT_VERSION = "0.3.0";
const AGENT_BIN_NAME = "studioE5-agent";
export const dynamic = "force-dynamic";
@@ -15,8 +16,8 @@ export default function DownloadPage() {
<CardTitle>Windows</CardTitle>
</CardHeader>
<CardContent>
<p className="text-sm text-muted-foreground mb-4">Agent EduBox pour Windows (64 bits)</p>
<a href={`/edubox-agent-v${AGENT_VERSION}.exe`} download className="inline-flex items-center justify-center rounded-md text-sm font-medium bg-primary text-primary-foreground hover:bg-primary/90 h-10 px-4 py-2 w-full">Télécharger (.exe)</a>
<p className="text-sm text-muted-foreground mb-4">Agent studioE5 pour Windows (64 bits)</p>
<a href={`/${AGENT_BIN_NAME}-v${AGENT_VERSION}.exe`} download className="inline-flex items-center justify-center rounded-md text-sm font-medium bg-primary text-primary-foreground hover:bg-primary/90 h-10 px-4 py-2 w-full">Télécharger (.exe)</a>
</CardContent>
</Card>
</div>
+1 -1
View File
@@ -5,7 +5,7 @@ import "./globals.css";
const inter = Inter({ subsets: ["latin"] });
export const metadata: Metadata = {
title: "EduBox V2",
title: "studioE5",
description: "Plateforme de gestion d'instances pour l'enseignement BTS",
};
+35 -5
View File
@@ -19,9 +19,6 @@ async function main() {
},
});
// Remove obsolete PrestaShop templates from previous seeds
await prisma.template.deleteMany({ where: { type: "prestashop" } });
const templates = [
{
name: "WordPress latest vierge",
@@ -53,20 +50,53 @@ async function main() {
dbPassword: "wordpress",
dbRootPassword: "rootpassword",
},
{
name: "PrestaShop 9 vierge (edubox)",
type: "prestashop",
dockerImage: "151.80.60.98:3001/yacine/edubox/edubox-prestashop:9-edubox-8",
dbImage: "mariadb:10.11",
dbName: "prestashop",
dbUser: "prestashop",
dbPassword: "prestashop",
dbRootPassword: "rootpassword",
},
];
for (const t of templates) {
const dbHost = "db";
const dbPort = "3306";
const isPrestaShop = t.type === "prestashop";
const appEnv = ` WORDPRESS_DB_HOST: ${dbHost}:${dbPort}
const appEnv = isPrestaShop
? ` DB_SERVER: ${dbHost}
DB_PORT: ${dbPort}
DB_NAME: ${t.dbName}
DB_USER: ${t.dbUser}
DB_PASSWD: ${t.dbPassword}
DB_PREFIX: ps_
PS_DOMAIN: {PUBLIC_DOMAIN}
PS_SHOP_NAME: ${t.name}
PS_INSTALL_AUTO: "1"
PS_INSTALL_DB: "0"
PS_ENABLE_SSL: "0"
PS_LANGUAGE: fr
PS_COUNTRY: fr
ADMIN_MAIL: admin@edubox.local
ADMIN_PASSWD: EduboxPrestashop2024!
PS_FOLDER_ADMIN: admin-edubox
PS_FOLDER_INSTALL: install
PS_DEV_MODE: "1"`
: ` WORDPRESS_DB_HOST: ${dbHost}:${dbPort}
WORDPRESS_DB_NAME: ${t.dbName}
WORDPRESS_DB_USER: ${t.dbUser}
WORDPRESS_DB_PASSWORD: ${t.dbPassword}
WORDPRESS_DB_PREFIX: wp_
# No hardcoded WP_HOME/WP_SITEURL so WordPress auto-detects from the Host header`;
const appVolumes = ` volumes:
const appVolumes = isPrestaShop
? ` volumes:
- app_data:/var/www/html`
: ` volumes:
- app_data:/var/www/html
- {MU_PLUGINS_DIR}/edubox-public-url.php:/var/www/html/wp-content/mu-plugins/edubox-public-url.php:ro`;
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.