clean: suppression complète PrestaShop
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import { NextResponse } from "next/server";
|
||||
|
||||
const AGENT_VERSION = "0.2.7";
|
||||
const AGENT_VERSION = "0.3.0";
|
||||
|
||||
export async function GET() {
|
||||
return NextResponse.json({
|
||||
|
||||
@@ -42,9 +42,13 @@ export async function GET(req: NextRequest) {
|
||||
const publicUrl = domain
|
||||
? `https://${inst.id}.${domain}`
|
||||
: null;
|
||||
const localUrl = inst.node.tailscaleIp
|
||||
? `http://${inst.node.tailscaleIp}:${inst.port}`
|
||||
: null;
|
||||
return {
|
||||
...inst,
|
||||
publicUrl,
|
||||
localUrl,
|
||||
};
|
||||
});
|
||||
|
||||
@@ -70,7 +74,6 @@ export async function POST(req: NextRequest) {
|
||||
const domain = node?.student?.class.establishment?.domain;
|
||||
const publicDomain = domain ? `${instance.id}.${domain}` : "localhost";
|
||||
const publicUrl = domain ? `https://${publicDomain}` : null;
|
||||
|
||||
const sent = sendToNode(nodeId, {
|
||||
action: "start",
|
||||
instanceId: instance.id,
|
||||
@@ -80,7 +83,7 @@ export async function POST(req: NextRequest) {
|
||||
.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, publicDomain),
|
||||
.replace(/{PUBLIC_DOMAIN}/g, "localhost"),
|
||||
});
|
||||
|
||||
if (!sent) {
|
||||
@@ -99,7 +102,6 @@ export async function PATCH(req: NextRequest) {
|
||||
const domain = instance.node.student?.class.establishment?.domain;
|
||||
const publicDomain = domain ? `${instance.id}.${domain}` : "localhost";
|
||||
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" } });
|
||||
@@ -113,7 +115,7 @@ export async function PATCH(req: NextRequest) {
|
||||
.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, publicDomain),
|
||||
.replace(/{PUBLIC_DOMAIN}/g, "localhost"),
|
||||
});
|
||||
if (!sent) await prisma.instance.update({ where: { id }, data: { status: "error" } });
|
||||
} else if (action === "reset") {
|
||||
@@ -126,7 +128,7 @@ export async function PATCH(req: NextRequest) {
|
||||
.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, publicDomain),
|
||||
.replace(/{PUBLIC_DOMAIN}/g, "localhost"),
|
||||
});
|
||||
if (!sent) await prisma.instance.update({ where: { id }, data: { status: "error" } });
|
||||
}
|
||||
|
||||
@@ -1,149 +0,0 @@
|
||||
import { NextRequest, NextResponse } from "next/server";
|
||||
import { prisma } from "@/lib/prisma";
|
||||
|
||||
const MAIN_DOMAIN = process.env.MAIN_DOMAIN || "alfrednobel.edudeploy.com";
|
||||
|
||||
async function proxyRequest(req: NextRequest) {
|
||||
const host = req.headers.get("host") || "";
|
||||
const cleanHost = host.split(":")[0];
|
||||
|
||||
if (!cleanHost.endsWith(`.${MAIN_DOMAIN}`)) {
|
||||
return NextResponse.json({ error: "Invalid host" }, { status: 404 });
|
||||
}
|
||||
|
||||
const subdomain = cleanHost.replace(`.${MAIN_DOMAIN}`, "");
|
||||
|
||||
const instance = await prisma.instance.findUnique({
|
||||
where: { id: subdomain },
|
||||
include: { node: true },
|
||||
});
|
||||
|
||||
if (!instance || !instance.node?.tailscaleIp) {
|
||||
return NextResponse.json(
|
||||
{ error: "Instance not found or not connected" },
|
||||
{ status: 404 }
|
||||
);
|
||||
}
|
||||
|
||||
const publicUrl = `https://${cleanHost}`;
|
||||
const targetUrl = new URL(req.url);
|
||||
// The middleware rewrites /foo to /api/proxy/foo; strip the prefix before forwarding
|
||||
let pathname = targetUrl.pathname;
|
||||
if (pathname.startsWith("/api/proxy")) {
|
||||
pathname = pathname.slice("/api/proxy".length) || "/";
|
||||
}
|
||||
const upstream = `http://${instance.node.tailscaleIp}:${instance.port}${pathname}${targetUrl.search}`;
|
||||
|
||||
const headers = new Headers(req.headers);
|
||||
headers.delete("host");
|
||||
headers.set("host", cleanHost);
|
||||
headers.set("x-forwarded-host", cleanHost);
|
||||
headers.set("x-forwarded-proto", "https");
|
||||
headers.set("x-forwarded-port", "443");
|
||||
headers.set("x-forwarded-for", req.headers.get("x-forwarded-for") || "unknown");
|
||||
|
||||
const needsBody = req.method !== "GET" && req.method !== "HEAD";
|
||||
const upstreamRes = await fetch(upstream, {
|
||||
method: req.method,
|
||||
headers,
|
||||
body: needsBody ? (req.body as BodyInit) : undefined,
|
||||
// Node.js fetch requires duplex when forwarding a request body stream
|
||||
...(needsBody ? { duplex: "half" } : {}),
|
||||
redirect: "manual",
|
||||
});
|
||||
|
||||
const responseHeaders = new Headers(upstreamRes.headers);
|
||||
// Remove content-encoding because Next.js/fetch handles decompression automatically
|
||||
responseHeaders.delete("content-encoding");
|
||||
responseHeaders.delete("content-length");
|
||||
|
||||
// Rewrite any header values that point to localhost or the internal Tailscale address
|
||||
const localPatterns = [
|
||||
`http://${instance.node.tailscaleIp}:${instance.port}`,
|
||||
`https://${instance.node.tailscaleIp}:${instance.port}`,
|
||||
`http://localhost:${instance.port}`,
|
||||
`https://localhost:${instance.port}`,
|
||||
`http://localhost`,
|
||||
`https://localhost`,
|
||||
];
|
||||
responseHeaders.forEach((value, key) => {
|
||||
let newValue = value;
|
||||
for (const pattern of localPatterns) {
|
||||
newValue = newValue.replaceAll(pattern, publicUrl);
|
||||
}
|
||||
if (newValue !== value) {
|
||||
responseHeaders.set(key, newValue);
|
||||
}
|
||||
});
|
||||
|
||||
// Sanitize Set-Cookie headers so sessions work through the public domain.
|
||||
// WordPress may issue cookies for "localhost" or without Secure flag; fix it.
|
||||
const setCookies = responseHeaders.getSetCookie();
|
||||
if (setCookies.length > 0) {
|
||||
responseHeaders.delete("set-cookie");
|
||||
for (let cookie of setCookies) {
|
||||
// Drop any Domain=... set for localhost/internal domains
|
||||
cookie = cookie.replace(/;\s*Domain=[^;]+/gi, "");
|
||||
// Ensure Secure is present for HTTPS public URLs
|
||||
if (!/;\s*Secure\b/i.test(cookie)) {
|
||||
cookie += "; Secure";
|
||||
}
|
||||
// Make sure cookies are sent on sub-domain navigations
|
||||
if (!/;\s*SameSite\b/i.test(cookie)) {
|
||||
cookie += "; SameSite=Lax";
|
||||
}
|
||||
responseHeaders.append("set-cookie", cookie);
|
||||
}
|
||||
}
|
||||
|
||||
const contentType = responseHeaders.get("content-type") || "";
|
||||
const shouldRewriteBody =
|
||||
contentType.includes("text/html") ||
|
||||
contentType.includes("text/css") ||
|
||||
contentType.includes("application/javascript") ||
|
||||
contentType.includes("application/json");
|
||||
|
||||
if (!shouldRewriteBody) {
|
||||
return new Response(upstreamRes.body, {
|
||||
status: upstreamRes.status,
|
||||
statusText: upstreamRes.statusText,
|
||||
headers: responseHeaders,
|
||||
});
|
||||
}
|
||||
|
||||
// For text responses, rewrite localhost/internal URLs to the public URL.
|
||||
// Also handle protocol-relative URLs that some WordPress plugins/themes use.
|
||||
let body = await upstreamRes.text();
|
||||
const localBase = `http://${instance.node.tailscaleIp}:${instance.port}`;
|
||||
const localBaseHttps = `https://${instance.node.tailscaleIp}:${instance.port}`;
|
||||
const localLocalhostHttp = `http://localhost:${instance.port}`;
|
||||
const localLocalhostHttps = `https://localhost:${instance.port}`;
|
||||
const localLocalhostPlainHttp = `http://localhost`;
|
||||
const localLocalhostPlainHttps = `https://localhost`;
|
||||
const localLocalhostProtocolRelative = `//localhost`;
|
||||
const localTailscaleProtocolRelative = `//${instance.node.tailscaleIp}:${instance.port}`;
|
||||
|
||||
body = body
|
||||
.replaceAll(localBase, publicUrl)
|
||||
.replaceAll(localBaseHttps, publicUrl)
|
||||
.replaceAll(localLocalhostHttp, publicUrl)
|
||||
.replaceAll(localLocalhostHttps, publicUrl)
|
||||
.replaceAll(localLocalhostPlainHttp, publicUrl)
|
||||
.replaceAll(localLocalhostPlainHttps, publicUrl)
|
||||
.replaceAll(localTailscaleProtocolRelative, publicUrl.replace(/^https?:/, ""))
|
||||
.replaceAll(localLocalhostProtocolRelative, publicUrl.replace(/^https?:/, ""));
|
||||
|
||||
return new Response(body, {
|
||||
status: upstreamRes.status,
|
||||
statusText: upstreamRes.statusText,
|
||||
headers: responseHeaders,
|
||||
});
|
||||
}
|
||||
|
||||
export const GET = proxyRequest;
|
||||
export const POST = proxyRequest;
|
||||
export const PUT = proxyRequest;
|
||||
export const PATCH = proxyRequest;
|
||||
export const DELETE = proxyRequest;
|
||||
export const HEAD = proxyRequest;
|
||||
export const OPTIONS = proxyRequest;
|
||||
@@ -0,0 +1,28 @@
|
||||
import { NextRequest, NextResponse } from "next/server";
|
||||
import { prisma } from "@/lib/prisma";
|
||||
|
||||
export async function GET(req: NextRequest) {
|
||||
const { searchParams } = new URL(req.url);
|
||||
const subdomain = searchParams.get("subdomain");
|
||||
|
||||
if (!subdomain) {
|
||||
return NextResponse.json({ error: "subdomain required" }, { status: 400 });
|
||||
}
|
||||
|
||||
const instance = await prisma.instance.findUnique({
|
||||
where: { id: subdomain },
|
||||
include: { node: true },
|
||||
});
|
||||
|
||||
if (!instance || !instance.node) {
|
||||
return NextResponse.json({ error: "instance not found" }, { status: 404 });
|
||||
}
|
||||
|
||||
if (instance.node.status !== "online" || !instance.node.tailscaleIp) {
|
||||
return NextResponse.json({ error: "node offline" }, { status: 503 });
|
||||
}
|
||||
|
||||
return NextResponse.json({
|
||||
upstream: `${instance.node.tailscaleIp}:${instance.port}`,
|
||||
});
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Card, CardHeader, CardTitle, CardContent } from "@/components/ui/card";
|
||||
|
||||
const AGENT_VERSION = "0.2.7";
|
||||
const AGENT_VERSION = "0.3.0";
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
@@ -19,24 +19,6 @@ export default function DownloadPage() {
|
||||
<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>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Linux</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<p className="text-sm text-muted-foreground mb-4">Agent EduBox pour Linux (64 bits)</p>
|
||||
<a href={`/edubox-agent-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</a>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>macOS</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<p className="text-sm text-muted-foreground mb-4">Agent EduBox pour macOS (Intel & Apple Silicon)</p>
|
||||
<a href={`/edubox-agent-v${AGENT_VERSION}-mac`} 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</a>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -30,6 +30,7 @@ export default async function InstancesPage() {
|
||||
return {
|
||||
...inst,
|
||||
publicUrl: domain ? `https://${inst.id}.${domain}` : null,
|
||||
localUrl: inst.node.tailscaleIp ? `http://${inst.node.tailscaleIp}:${inst.port}` : null,
|
||||
};
|
||||
});
|
||||
|
||||
@@ -53,6 +54,7 @@ export default async function InstancesPage() {
|
||||
<TableHead>Port</TableHead>
|
||||
<TableHead>Statut</TableHead>
|
||||
<TableHead>URL publique</TableHead>
|
||||
<TableHead>Accès prof</TableHead>
|
||||
<TableHead>Actions</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
@@ -70,6 +72,9 @@ export default async function InstancesPage() {
|
||||
<TableCell>
|
||||
{inst.publicUrl ? <a href={inst.publicUrl} target="_blank" rel="noopener" className="text-blue-600 hover:underline">{inst.publicUrl}</a> : "-"}
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
{inst.localUrl ? <a href={inst.localUrl} target="_blank" rel="noopener" className="text-blue-600 hover:underline">{inst.localUrl}</a> : "-"}
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<InstanceActions instanceId={inst.id} status={inst.status} />
|
||||
</TableCell>
|
||||
@@ -77,7 +82,7 @@ export default async function InstancesPage() {
|
||||
))}
|
||||
{instances.length === 0 && (
|
||||
<TableRow>
|
||||
<TableCell colSpan={8} className="text-center text-muted-foreground">Aucune instance</TableCell>
|
||||
<TableCell colSpan={9} className="text-center text-muted-foreground">Aucune instance</TableCell>
|
||||
</TableRow>
|
||||
)}
|
||||
</TableBody>
|
||||
|
||||
+3
-21
@@ -1,28 +1,10 @@
|
||||
import { NextResponse } from "next/server";
|
||||
import type { NextRequest } from "next/server";
|
||||
|
||||
const MAIN_DOMAIN = process.env.MAIN_DOMAIN || "alfrednobel.edudeploy.com";
|
||||
|
||||
export function middleware(req: NextRequest) {
|
||||
const host = req.headers.get("host") || "";
|
||||
const cleanHost = host.split(":")[0];
|
||||
|
||||
if (cleanHost === MAIN_DOMAIN || cleanHost === `www.${MAIN_DOMAIN}`) {
|
||||
return NextResponse.next();
|
||||
}
|
||||
|
||||
if (!cleanHost.endsWith(`.${MAIN_DOMAIN}`)) {
|
||||
return NextResponse.next();
|
||||
}
|
||||
|
||||
const pathname = req.nextUrl.pathname;
|
||||
const search = req.nextUrl.search;
|
||||
|
||||
// Rewrite to the internal proxy API while preserving the original host header
|
||||
const rewriteUrl = new URL(`/api/proxy${pathname}${search}`, req.url);
|
||||
return NextResponse.rewrite(rewriteUrl);
|
||||
export function middleware(_req: NextRequest) {
|
||||
return NextResponse.next();
|
||||
}
|
||||
|
||||
export const config = {
|
||||
matcher: ["/((?!api/proxy|api/check-domain|_next|static|favicon.ico).*)",],
|
||||
matcher: ["/((?!_next|static|favicon.ico).*)"],
|
||||
};
|
||||
|
||||
+6
-46
@@ -19,6 +19,9 @@ async function main() {
|
||||
},
|
||||
});
|
||||
|
||||
// Remove obsolete PrestaShop templates from previous seeds
|
||||
await prisma.template.deleteMany({ where: { type: "prestashop" } });
|
||||
|
||||
const templates = [
|
||||
{
|
||||
name: "WordPress latest vierge",
|
||||
@@ -50,63 +53,20 @@ async function main() {
|
||||
dbPassword: "wordpress",
|
||||
dbRootPassword: "rootpassword",
|
||||
},
|
||||
{
|
||||
name: "PrestaShop latest vierge",
|
||||
type: "prestashop",
|
||||
dockerImage: "prestashop/prestashop:latest",
|
||||
dbImage: "mariadb:10.11",
|
||||
dbName: "prestashop",
|
||||
dbUser: "prestashop",
|
||||
dbPassword: "prestashop",
|
||||
dbRootPassword: "rootpassword",
|
||||
},
|
||||
{
|
||||
name: "PrestaShop 8.1 vierge",
|
||||
type: "prestashop",
|
||||
dockerImage: "prestashop/prestashop:8.1",
|
||||
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 = 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: "1"
|
||||
PS_LANGUAGE: fr
|
||||
PS_COUNTRY: fr
|
||||
ADMIN_MAIL: admin@edubox.local
|
||||
ADMIN_PASSWD: EduboxPrestashop2024!
|
||||
PS_FOLDER_ADMIN: admin
|
||||
PS_FOLDER_INSTALL: install
|
||||
PS_DEV_MODE: "1"`
|
||||
: ` WORDPRESS_DB_HOST: ${dbHost}:${dbPort}
|
||||
const appEnv = ` 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 = isPrestaShop
|
||||
? ` volumes:
|
||||
- app_data:/var/www/html`
|
||||
: ` volumes:
|
||||
const appVolumes = ` 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`;
|
||||
|
||||
@@ -129,7 +89,7 @@ async function main() {
|
||||
app:
|
||||
image: ${t.dockerImage}
|
||||
ports:
|
||||
- "127.0.0.1:{PORT}:80"
|
||||
- "{PORT}:80"
|
||||
environment:
|
||||
${appEnv}
|
||||
INSTANCE_ID: {INSTANCE_ID}
|
||||
|
||||
Executable
BIN
Binary file not shown.
Executable
BIN
Binary file not shown.
Executable
BIN
Binary file not shown.
Executable
BIN
Binary file not shown.
Reference in New Issue
Block a user