agent v0.3.8: fix crash UI notifications, auto Podman machine DNS, WordPress 7.0.0 ready template
This commit is contained in:
@@ -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 {
|
||||
|
||||
@@ -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'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>
|
||||
);
|
||||
|
||||
@@ -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;
|
||||
@@ -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
@@ -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."
|
||||
Reference in New Issue
Block a user