support arm64 reactive resume image layout

This commit is contained in:
2026-05-20 01:15:39 +08:00
parent 944a852470
commit 2141afd3eb
10 changed files with 170 additions and 54 deletions

View File

@@ -21,16 +21,7 @@ services:
reactive-resume:
image: amruthpillai/reactive-resume:latest
restart: unless-stopped
entrypoint: ["/bin/sh", "-c"]
command:
- |
sh /opt/reactive-resume-patches/reactive-resume-runtime-patch.sh
APP_DIR="$(cat /tmp/reactive-resume-app-dir 2>/dev/null || true)"
if [ -z "$$APP_DIR" ]; then
APP_DIR="$(find /app -path '*/.output/server/index.mjs' -type f 2>/dev/null | head -n 1 | sed 's#/.output/server/index.mjs##')"
fi
cd "$${APP_DIR:-/app/apps/web}"
exec node .output/server/index.mjs
entrypoint: ["/bin/sh", "/opt/reactive-resume-patches/reactive-resume-entrypoint.sh"]
env_file:
- .env
ports:
@@ -38,6 +29,7 @@ services:
volumes:
- reactive_resume_data:/app/data
- ./patches/reactive-resume-runtime-patch.sh:/opt/reactive-resume-patches/reactive-resume-runtime-patch.sh:ro
- ./patches/reactive-resume-entrypoint.sh:/opt/reactive-resume-patches/reactive-resume-entrypoint.sh:ro
networks:
- resume_net
depends_on:

View File

@@ -0,0 +1,34 @@
#!/bin/sh
set -eu
PATCH_SCRIPT="/opt/reactive-resume-patches/reactive-resume-runtime-patch.sh"
if [ -f "$PATCH_SCRIPT" ]; then
sh "$PATCH_SCRIPT" || echo "Reactive Resume runtime patch failed, continuing with the image default startup" >&2
fi
if [ "$#" -eq 0 ]; then
if [ -f /app/apps/server/dist/index.mjs ]; then
cd /app
set -- node apps/server/dist/index.mjs
elif [ -f /app/apps/web/.output/server/index.mjs ]; then
cd /app/apps/web
set -- node .output/server/index.mjs
else
server_entry="$(cat /tmp/reactive-resume-server-entry 2>/dev/null || true)"
if [ -n "$server_entry" ] && [ -f "$server_entry" ]; then
cd "$(dirname "$server_entry")"
set -- node "$(basename "$server_entry")"
else
echo "Reactive Resume startup failed: no known server entry found" >&2
find /app -maxdepth 5 \( -name index.mjs -o -name server.js -o -name main.js \) 2>/dev/null | head -50 >&2 || true
exit 1
fi
fi
fi
if command -v docker-entrypoint.sh >/dev/null 2>&1; then
exec docker-entrypoint.sh "$@"
fi
exec "$@"

View File

@@ -2,40 +2,64 @@
set -eu
APP_DIR="${REACTIVE_RESUME_APP_DIR:-}"
SERVER_ENTRY=""
ASSETS_DIR=""
SSR_DIR=""
SERVER_INDEX_FILE=""
SSR_FILE=""
if [ -z "$APP_DIR" ]; then
for candidate in /app/apps/web /app; do
if [ -f "$candidate/.output/server/index.mjs" ]; then
APP_DIR="$candidate"
SERVER_ENTRY="$candidate/.output/server/index.mjs"
ASSETS_DIR="$candidate/.output/public/assets"
SSR_DIR="$candidate/.output/server/_ssr"
SERVER_INDEX_FILE="$SERVER_ENTRY"
break
fi
done
fi
if [ -z "$APP_DIR" ]; then
if [ -z "$SERVER_ENTRY" ]; then
index_file="$(find /app -path "*/.output/server/index.mjs" -type f 2>/dev/null | head -n 1 || true)"
if [ -n "$index_file" ]; then
APP_DIR="${index_file%/.output/server/index.mjs}"
SERVER_ENTRY="$index_file"
ASSETS_DIR="$APP_DIR/.output/public/assets"
SSR_DIR="$APP_DIR/.output/server/_ssr"
SERVER_INDEX_FILE="$SERVER_ENTRY"
fi
fi
if [ -z "$APP_DIR" ] || [ ! -f "$APP_DIR/.output/server/index.mjs" ]; then
echo "Reactive Resume runtime patch skipped: .output/server/index.mjs not found under /app" >&2
if [ -z "$SERVER_ENTRY" ] && [ -f /app/apps/server/dist/index.mjs ]; then
APP_DIR="/app"
SERVER_ENTRY="/app/apps/server/dist/index.mjs"
ASSETS_DIR="/app/apps/web/dist/assets"
SERVER_INDEX_FILE="$SERVER_ENTRY"
SSR_FILE="$SERVER_ENTRY"
fi
if [ -z "$SERVER_ENTRY" ] || [ ! -f "$SERVER_ENTRY" ]; then
echo "Reactive Resume runtime patch skipped: server entry not found under /app" >&2
exit 0
fi
printf "%s" "$APP_DIR" > /tmp/reactive-resume-app-dir
export APP_DIR
printf "%s" "$SERVER_ENTRY" > /tmp/reactive-resume-server-entry
export APP_DIR ASSETS_DIR SSR_DIR SERVER_INDEX_FILE SSR_FILE SERVER_ENTRY
node - <<'NODE'
const fs = require("fs");
const path = require("path");
const crypto = require("crypto");
const appDir = process.env.APP_DIR;
const appDir = process.env.APP_DIR || "/app";
const outputDir = path.join(appDir, ".output");
const assetsDir = path.join(outputDir, "public/assets");
const ssrDir = path.join(outputDir, "server/_ssr");
const serverIndexFile = path.join(outputDir, "server/index.mjs");
const assetsDir = process.env.ASSETS_DIR || path.join(outputDir, "public/assets");
const ssrDir = process.env.SSR_DIR || "";
const explicitSsrFile = process.env.SSR_FILE || "";
const serverIndexFile = process.env.SERVER_INDEX_FILE || path.join(outputDir, "server/index.mjs");
const filenameCacheBust = "rr-filename-title-20260520";
const pdfCacheBust = "rr-glalie-layout-20260520";
const browserBufferPolyfill = "var Buffer=globalThis.Buffer??{isBuffer:()=>false,allocUnsafe:e=>new Uint8Array(e),alloc:e=>new Uint8Array(e)};/* rr-browser-buffer-polyfill */";
@@ -117,12 +141,17 @@ function patchSsr(source) {
const filenameReplacement = `function generateFilename(prefix, extension) {\n\tlet filename = (prefix || "resume").toString().trim() || "resume";\n\tfilename = filename.replace(/[\\\\/:*?"<>|]/g, "-").replace(/\\s+/g, " ").replace(/\\.+$/, "").trim() || "resume";\n\treturn extension && filename.toLowerCase().endsWith(\`.\${extension.toLowerCase()}\`) ? filename : \`\${filename}\${extension ? \`.\${extension}\` : ""}\`;\n}`;
if (!source.includes(filenameReplacement)) {
const start = source.indexOf("function generateFilename(");
const end = source.indexOf("\nfunction downloadWithAnchor(", start);
if (start !== -1 && end !== -1) {
source = source.slice(0, start) + filenameReplacement + source.slice(end);
const slugifiedPattern = /function generateFilename\(prefix, extension\) \{\s*return `\$\{slugify\(prefix\)\}\$\{extension \? `\.\$\{extension\}` : ""\}`;\s*\}/;
if (slugifiedPattern.test(source)) {
source = source.replace(slugifiedPattern, filenameReplacement);
} else {
warn("SSR generateFilename marker not found, skipped");
const start = source.indexOf("function generateFilename(");
const end = source.indexOf("\nfunction downloadWithAnchor(", start);
if (start !== -1 && end !== -1) {
source = source.slice(0, start) + filenameReplacement + source.slice(end);
} else {
warn("SSR generateFilename marker not found, skipped");
}
}
}
@@ -155,7 +184,8 @@ function patchSsr(source) {
function patchPublicPdf(source) {
if (!source.includes("rr-browser-buffer-polyfill")) {
const insertAt = source.indexOf(";") + 1;
const importPrelude = source.match(/^(?:import[^;]+;)+/);
const insertAt = importPrelude ? importPrelude[0].length : source.indexOf(";") + 1;
if (insertAt > 0 && source.startsWith("import")) {
source = source.slice(0, insertAt) + browserBufferPolyfill + source.slice(insertAt);
} else {
@@ -193,7 +223,9 @@ for (const file of filenameFiles) {
if (patchedFilenameFiles.length === 0) warn("no filename bundle patched");
let ssrFile = "";
if (fs.existsSync(ssrDir)) {
if (explicitSsrFile && fs.existsSync(explicitSsrFile) && read(explicitSsrFile).includes("function generateFilename(")) {
ssrFile = explicitSsrFile;
} else if (ssrDir && fs.existsSync(ssrDir)) {
ssrFile = fs.readdirSync(ssrDir)
.filter((name) => name.endsWith(".mjs"))
.map((name) => path.join(ssrDir, name))