agent v0.3.8: fix crash UI notifications, auto Podman machine DNS, WordPress 7.0.0 ready template

This commit is contained in:
EduBox Dev
2026-06-26 15:24:21 +00:00
parent a414f03a59
commit cf8b66340a
15 changed files with 590 additions and 35 deletions
+23 -2
View File
@@ -128,6 +128,13 @@ export async function POST(req: NextRequest) {
.replace(/{INSTANCE_ID}/g, instance.id)
.replace(/{PUBLIC_URL}/g, publicUrl || `http://localhost:${instance.port}`)
.replace(/{PUBLIC_DOMAIN}/g, "localhost"),
initScript: template.initScript
? template.initScript
.replace(/{PORT}/g, String(instance.port))
.replace(/{INSTANCE_ID}/g, instance.id)
.replace(/{PUBLIC_URL}/g, publicUrl || `http://localhost:${instance.port}`)
.replace(/{PUBLIC_DOMAIN}/g, "localhost")
: undefined,
});
if (!sent) {
@@ -161,8 +168,8 @@ export async function PATCH(req: NextRequest) {
const publicUrl = domain ? `https://${publicDomain}` : null;
if (action === "stop") {
sendToNode(instance.nodeId, { action: "delete", instanceId: instance.id });
await prisma.instance.update({ where: { id }, data: { status: "stopped" } });
const sent = sendToNode(instance.nodeId, { action: "stop", instanceId: instance.id });
if (!sent) await prisma.instance.update({ where: { id }, data: { status: "error" } });
} else if (action === "start") {
const sent = sendToNode(instance.nodeId, {
action: "start",
@@ -174,6 +181,13 @@ export async function PATCH(req: NextRequest) {
.replace(/{INSTANCE_ID}/g, instance.id)
.replace(/{PUBLIC_URL}/g, publicUrl || `http://localhost:${instance.port}`)
.replace(/{PUBLIC_DOMAIN}/g, "localhost"),
initScript: instance.template.initScript
? instance.template.initScript
.replace(/{PORT}/g, String(instance.port))
.replace(/{INSTANCE_ID}/g, instance.id)
.replace(/{PUBLIC_URL}/g, publicUrl || `http://localhost:${instance.port}`)
.replace(/{PUBLIC_DOMAIN}/g, "localhost")
: undefined,
});
if (!sent) await prisma.instance.update({ where: { id }, data: { status: "error" } });
} else if (action === "reset") {
@@ -187,6 +201,13 @@ export async function PATCH(req: NextRequest) {
.replace(/{INSTANCE_ID}/g, instance.id)
.replace(/{PUBLIC_URL}/g, publicUrl || `http://localhost:${instance.port}`)
.replace(/{PUBLIC_DOMAIN}/g, "localhost"),
initScript: instance.template.initScript
? instance.template.initScript
.replace(/{PORT}/g, String(instance.port))
.replace(/{INSTANCE_ID}/g, instance.id)
.replace(/{PUBLIC_URL}/g, publicUrl || `http://localhost:${instance.port}`)
.replace(/{PUBLIC_DOMAIN}/g, "localhost")
: undefined,
});
if (!sent) await prisma.instance.update({ where: { id }, data: { status: "error" } });
} else {
+23 -3
View File
@@ -1,6 +1,6 @@
import { Card, CardHeader, CardTitle, CardContent } from "@/components/ui/card";
const AGENT_VERSION = "0.3.0";
const AGENT_VERSION = "0.3.8";
const AGENT_BIN_NAME = "studioE5-agent";
export const dynamic = "force-dynamic";
@@ -13,13 +13,33 @@ export default function DownloadPage() {
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
<Card>
<CardHeader>
<CardTitle>Windows</CardTitle>
<CardTitle>Windows (.exe)</CardTitle>
</CardHeader>
<CardContent>
<p className="text-sm text-muted-foreground mb-4">Agent studioE5 pour Windows (64 bits)</p>
<p className="text-sm text-muted-foreground mb-4">Agent studioE5 pour Windows (64 bits). Nécessite Tailscale installé séparément ou les binaires dans <code>tailscale-bin/windows/</code>.</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>
<Card>
<CardHeader>
<CardTitle>Windows (archive)</CardTitle>
</CardHeader>
<CardContent>
<p className="text-sm text-muted-foreground mb-4">Archive complète incluant l&apos;agent, Tailscale et le README Windows.</p>
<a href={`/${AGENT_BIN_NAME}-v${AGENT_VERSION}-windows.zip`} 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 (.zip)</a>
</CardContent>
</Card>
<Card>
<CardHeader>
<CardTitle>Linux</CardTitle>
</CardHeader>
<CardContent>
<p className="text-sm text-muted-foreground mb-4">Agent studioE5 pour Linux (64 bits).</p>
<a href={`/${AGENT_BIN_NAME}-v${AGENT_VERSION}`} 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 (Linux)</a>
</CardContent>
</Card>
</div>
</div>
);
+8 -6
View File
@@ -264,35 +264,37 @@ export function initWebSocketServer(wss: WebSocketServer) {
}
if (msg.action === "instance_started" && msg.instanceId) {
await prisma.instance.update({
const { count } = await prisma.instance.updateMany({
where: { id: msg.instanceId },
data: { status: "running" },
});
if (count) console.log("[WS] Instance started:", msg.instanceId);
return;
}
if (msg.action === "instance_stopped" && msg.instanceId) {
await prisma.instance.update({
const { count } = await prisma.instance.updateMany({
where: { id: msg.instanceId },
data: { status: "stopped" },
});
console.log("[WS] Instance stopped:", msg.instanceId);
if (count) console.log("[WS] Instance stopped:", msg.instanceId);
return;
}
if (msg.action === "instance_deleted" && msg.instanceId) {
await prisma.instance.delete({
const { count } = await prisma.instance.deleteMany({
where: { id: msg.instanceId },
});
console.log("[WS] Instance deleted:", msg.instanceId);
if (count) console.log("[WS] Instance deleted:", msg.instanceId);
return;
}
if (msg.action === "instance_error" && msg.instanceId) {
await prisma.instance.update({
const { count } = await prisma.instance.updateMany({
where: { id: msg.instanceId },
data: { status: "error" },
});
if (count) console.log("[WS] Instance error:", msg.instanceId);
return;
}
} catch (err) {
@@ -0,0 +1,2 @@
-- AlterTable
ALTER TABLE "Template" ADD COLUMN "initScript" TEXT;
+1
View File
@@ -91,6 +91,7 @@ model Template {
type String
dockerImage String
composeConfig String
initScript String?
isPublic Boolean @default(true)
establishmentId String?
establishment Establishment? @relation(fields: [establishmentId], references: [id], onDelete: Cascade)
+66 -2
View File
@@ -1,5 +1,7 @@
import { PrismaClient } from "@prisma/client";
import bcrypt from "bcryptjs";
import fs from "fs";
import path from "path";
const prisma = new PrismaClient();
@@ -19,6 +21,11 @@ async function main() {
},
});
const wpReadyInitScript = fs.readFileSync(
path.join(__dirname, "../templates/wordpress-ready/wp-init.sh"),
"utf-8"
);
const templates = [
{
name: "WordPress latest vierge",
@@ -50,6 +57,30 @@ async function main() {
dbPassword: "wordpress",
dbRootPassword: "rootpassword",
},
{
name: "WordPress latest prêt à l'emploi",
type: "wordpress-ready",
dockerImage: "wordpress:latest",
dbImage: "mariadb:10.11",
dbName: "wordpress",
dbUser: "wordpress",
dbPassword: "wordpress",
dbRootPassword: "rootpassword",
ready: true,
initScript: wpReadyInitScript,
},
{
name: "WordPress 7.0.0 prêt à l'emploi",
type: "wordpress-ready",
dockerImage: "wordpress:7.0.0-php8.3",
dbImage: "mariadb:10.11",
dbName: "wordpress",
dbUser: "wordpress",
dbPassword: "wordpress",
dbRootPassword: "rootpassword",
ready: true,
initScript: wpReadyInitScript,
},
{
name: "PrestaShop 9 vierge (edubox)",
type: "prestashop",
@@ -66,6 +97,7 @@ async function main() {
const dbHost = "db";
const dbPort = "3306";
const isPrestaShop = t.type === "prestashop";
const isWordPressReady = (t as any).ready === true;
const appEnv = isPrestaShop
? ` DB_SERVER: ${dbHost}
@@ -93,6 +125,12 @@ async function main() {
WORDPRESS_DB_PREFIX: wp_
# No hardcoded WP_HOME/WP_SITEURL so WordPress auto-detects from the Host header`;
const appDNS = isWordPressReady
? ` dns:
- 8.8.8.8
- 1.1.1.1`
: "";
const appVolumes = isPrestaShop
? ` volumes:
- app_data:/var/www/html`
@@ -100,6 +138,28 @@ async function main() {
- app_data:/var/www/html
- {MU_PLUGINS_DIR}/edubox-public-url.php:/var/www/html/wp-content/mu-plugins/edubox-public-url.php:ro`;
const wpInitService = isWordPressReady
? ` wp-init:
image: wordpress:cli
user: "0:0"
environment:
WORDPRESS_DB_HOST: ${dbHost}:${dbPort}
WORDPRESS_DB_NAME: ${t.dbName}
WORDPRESS_DB_USER: ${t.dbUser}
WORDPRESS_DB_PASSWORD: ${t.dbPassword}
depends_on:
db:
condition: service_healthy
app:
condition: service_started
volumes:
- app_data:/var/www/html
- ./wp-init.sh:/wp-init.sh:ro
restart: "no"
entrypoint: ["/bin/sh", "/wp-init.sh"]
`
: "";
const composeConfig = `services:
db:
image: ${t.dbImage}
@@ -123,24 +183,28 @@ async function main() {
environment:
${appEnv}
INSTANCE_ID: {INSTANCE_ID}
${appDNS}
depends_on:
db:
condition: service_healthy
${appVolumes}
restart: unless-stopped
volumes:
${wpInitService}volumes:
db_data:
app_data:
`;
const initScript = isWordPressReady ? wpReadyInitScript : null;
await prisma.template.upsert({
where: { id: `${t.type}-${t.dockerImage.replace(/[:\/]/g, "-")}` },
update: { composeConfig },
update: { composeConfig, initScript },
create: {
id: `${t.type}-${t.dockerImage.replace(/[:\/]/g, "-")}`,
name: t.name,
type: t.type,
dockerImage: t.dockerImage,
composeConfig,
initScript,
isPublic: true,
createdBy: "system",
},
@@ -0,0 +1,47 @@
#!/bin/sh
set -e
WP_PATH=/var/www/html
FLAG=$WP_PATH/.studioe5-init-done
WP_USER=www-data
if [ -f "$FLAG" ]; then
echo "[studioE5] WordPress already initialized."
exit 0
fi
echo "[studioE5] Waiting for WordPress config and database..."
until [ -f "$WP_PATH/wp-config.php" ] && wp --path="$WP_PATH" db query "SELECT 1" --allow-root > /dev/null 2>&1; do
sleep 2
done
echo "[studioE5] Fixing permissions..."
# Ensure the web server (www-data) can write to wp-content and wp-config.php.
chmod -R 777 "$WP_PATH/wp-content"
chmod 666 "$WP_PATH/wp-config.php"
run_wp() {
su -s /bin/sh "$WP_USER" -c "wp --path=$WP_PATH $1"
}
echo "[studioE5] Installing WordPress..."
run_wp "core install --url='{PUBLIC_URL}' --title='Mon site wordpress' --admin_user='admin' --admin_password='admin' --admin_email='admin@example.com' --skip-email --allow-root"
echo "[studioE5] Setting language to French..."
run_wp "language core install fr_FR --activate --allow-root" || true
echo "[studioE5] Installing and activating theme Astra..."
run_wp "theme install astra --activate --allow-root" || true
echo "[studioE5] Installing plugins..."
run_wp "plugin install wordpress-seo --allow-root" || true
run_wp "plugin install ultimate-addons-for-gutenberg --activate --allow-root" || true
echo "[studioE5] Disabling automatic updates..."
run_wp "config set AUTOMATIC_UPDATER_DISABLED true --raw --allow-root" || true
run_wp "config set WP_AUTO_UPDATE_CORE false --raw --allow-root" || true
run_wp "config set AUTO_UPDATE_PLUGIN false --raw --allow-root" || true
run_wp "config set AUTO_UPDATE_THEME false --raw --allow-root" || true
touch "$FLAG"
echo "[studioE5] Initialization complete."