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

4
dist/SHA256SUMS vendored
View File

@@ -1,3 +1,3 @@
fa3b2b64a9afd7af60f57cfda8431af4e171cc1cdba4a6a2b89d50000a574f54 reactive-resume-clean-install-20260520.zip
d76b4920388fac71edcc6d84e1db609f62c4c2b7a3aa34f5fe3bad33813d9cba reactive-resume-personal-direct-20260520.zip
c949c68c6c3343c3d6db6ee13deb49c0a575ee9c758fac75ff647c6ff93e8da3 reactive-resume-personal-qnap-nas-20260520.zip
6724400586b14de9a954dcb22e1b7150d8339b3ebf07bb5b0685a5aa60009bc9 reactive-resume-personal-direct-20260520.zip
21e588dc25e90e358f823175cfab7ef4b4880fed959bab8d9d7c65200848b6d0 reactive-resume-personal-qnap-nas-20260520.zip

View File

@@ -1,3 +1,3 @@
fa3b2b64a9afd7af60f57cfda8431af4e171cc1cdba4a6a2b89d50000a574f54 reactive-resume-clean-install-20260520.zip
d76b4920388fac71edcc6d84e1db609f62c4c2b7a3aa34f5fe3bad33813d9cba reactive-resume-personal-direct-20260520.zip
c949c68c6c3343c3d6db6ee13deb49c0a575ee9c758fac75ff647c6ff93e8da3 reactive-resume-personal-qnap-nas-20260520.zip
6724400586b14de9a954dcb22e1b7150d8339b3ebf07bb5b0685a5aa60009bc9 reactive-resume-personal-direct-20260520.zip
21e588dc25e90e358f823175cfab7ef4b4880fed959bab8d9d7c65200848b6d0 reactive-resume-personal-qnap-nas-20260520.zip

Binary file not shown.

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,6 +141,10 @@ 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 slugifiedPattern = /function generateFilename\(prefix, extension\) \{\s*return `\$\{slugify\(prefix\)\}\$\{extension \? `\.\$\{extension\}` : ""\}`;\s*\}/;
if (slugifiedPattern.test(source)) {
source = source.replace(slugifiedPattern, filenameReplacement);
} else {
const start = source.indexOf("function generateFilename(");
const end = source.indexOf("\nfunction downloadWithAnchor(", start);
if (start !== -1 && end !== -1) {
@@ -125,6 +153,7 @@ function patchSsr(source) {
warn("SSR generateFilename marker not found, skipped");
}
}
}
source = source
.replace(/const sideMargin = bodyLineHeight \* \.(?:2|08);/, "const sideMargin = bodyLineHeight * .08;")
@@ -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))

View File

@@ -44,16 +44,7 @@ services:
reactive_resume_app:
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"]
depends_on:
reactive_resume_permissions:
condition: service_completed_successfully
@@ -64,6 +55,7 @@ services:
volumes:
- /share/Container/reactive_resume/data/uploads:/app/data
- /share/Container/reactive_resume/patches/reactive-resume-runtime-patch.sh:/opt/reactive-resume-patches/reactive-resume-runtime-patch.sh:ro
- /share/Container/reactive_resume/patches/reactive-resume-entrypoint.sh:/opt/reactive-resume-patches/reactive-resume-entrypoint.sh:ro
environment:
HTTP_PROXY: http://192.168.3.12:7893
HTTPS_PROXY: http://192.168.3.12:7893

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,6 +141,10 @@ 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 slugifiedPattern = /function generateFilename\(prefix, extension\) \{\s*return `\$\{slugify\(prefix\)\}\$\{extension \? `\.\$\{extension\}` : ""\}`;\s*\}/;
if (slugifiedPattern.test(source)) {
source = source.replace(slugifiedPattern, filenameReplacement);
} else {
const start = source.indexOf("function generateFilename(");
const end = source.indexOf("\nfunction downloadWithAnchor(", start);
if (start !== -1 && end !== -1) {
@@ -125,6 +153,7 @@ function patchSsr(source) {
warn("SSR generateFilename marker not found, skipped");
}
}
}
source = source
.replace(/const sideMargin = bodyLineHeight \* \.(?:2|08);/, "const sideMargin = bodyLineHeight * .08;")
@@ -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))