From f137ded5aa5700f8d81df64681fa5e08eb3e87df Mon Sep 17 00:00:00 2001 From: Daniel Bedeleanu Date: Mon, 13 Apr 2026 20:32:40 +0300 Subject: [PATCH] Build [v1.8.9] (Runtime Permission Healing for Docker Volumes) --- backend/Dockerfile | 8 ++++---- backend/entrypoint.sh | 10 +++++++--- frontend/Dockerfile | 9 ++++++--- frontend/VERSION.json | 2 +- frontend/entrypoint.sh | 19 +++++++++++++++++++ frontend/public/sw.js | 2 +- 6 files changed, 38 insertions(+), 12 deletions(-) create mode 100644 frontend/entrypoint.sh diff --git a/backend/Dockerfile b/backend/Dockerfile index 779b74d9..6ad8ec6f 100644 --- a/backend/Dockerfile +++ b/backend/Dockerfile @@ -1,10 +1,11 @@ FROM python:3.12-slim -# Install system dependencies required for python-ldap (needed by backend) +# Install system dependencies RUN apt-get update && apt-get install -y \ build-essential \ libldap2-dev \ libsasl2-dev \ + gosu \ && rm -rf /var/lib/apt/lists/* WORKDIR /app @@ -26,14 +27,13 @@ COPY scripts ./scripts ENV DATA_DIR="/app/data" ENV LOGS_DIR="/app/logs" -# Ensure the appuser can write to data and logs if we pre-create them, -# although Docker volumes will handle ownership context. +# Pre-create directories and ensure they are writable RUN mkdir -p /app/data /app/logs && chown -R appuser:appuser /app # Make initialization scripts executable RUN chmod +x /app/scripts/init_data.sh /app/backend/entrypoint.sh -USER appuser +EXPOSE 8000 EXPOSE 8000 diff --git a/backend/entrypoint.sh b/backend/entrypoint.sh index 0735d29f..be54f58c 100755 --- a/backend/entrypoint.sh +++ b/backend/entrypoint.sh @@ -23,6 +23,10 @@ export LOGS_DIR="${LOGS_DIR:-/app/logs}" echo "🐳 [Docker] Running data initialization..." bash /app/scripts/init_data.sh -# Hand off to the application server -echo "🐳 [Docker] Starting uvicorn..." -exec python -m uvicorn backend.main:app --host 0.0.0.0 --port 8000 +# Fix permissions for mounted volumes (which might be root-owned by the host) +echo "🐳 [Docker] Fixing volume permissions..." +chown -R appuser:appuser "${DATA_DIR}" "${LOGS_DIR}" + +# Hand off to the application server as non-root user +echo "🐳 [Docker] Starting uvicorn as appuser..." +exec gosu appuser python -m uvicorn backend.main:app --host 0.0.0.0 --port 8000 diff --git a/frontend/Dockerfile b/frontend/Dockerfile index 944116a2..f165010e 100644 --- a/frontend/Dockerfile +++ b/frontend/Dockerfile @@ -19,6 +19,7 @@ RUN npm run build # Step 3: Production image FROM base AS runner +RUN apk add --no-cache su-exec WORKDIR /app ENV NODE_ENV production @@ -38,11 +39,13 @@ RUN chown nextjs:nodejs .next COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./ COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static -USER nextjs +# Copy entrypoint script +COPY entrypoint.sh /app/entrypoint.sh +RUN chmod +x /app/entrypoint.sh EXPOSE 3000 ENV PORT 3000 ENV HOSTNAME "0.0.0.0" -# Note: The server.js is created by next build from the standalone output -CMD ["node", "server.js"] +ENTRYPOINT ["/app/entrypoint.sh"] + diff --git a/frontend/VERSION.json b/frontend/VERSION.json index a661face..a4c57bf9 100644 --- a/frontend/VERSION.json +++ b/frontend/VERSION.json @@ -1,5 +1,5 @@ { - "version": "1.8.7", + "version": "1.8.9", "last_build": "2026-04-13-1954", "codename": "TypeFix" } \ No newline at end of file diff --git a/frontend/entrypoint.sh b/frontend/entrypoint.sh new file mode 100644 index 00000000..241d6335 --- /dev/null +++ b/frontend/entrypoint.sh @@ -0,0 +1,19 @@ +#!/bin/sh +# ============================================================================= +# frontend/entrypoint.sh +# ============================================================================= +# Docker container entrypoint for TFM aInventory frontend. +# Fixes permissions for the logs volume and starts the Node.js server. +# ============================================================================= + +set -e + +# Fix permissions for the logs directory (useful if mounted as a volume) +if [ -d "/app/logs" ]; then + echo "🐳 [Docker] Fixing /app/logs permissions..." + chown -R nextjs:nodejs /app/logs +fi + +# Hand off to the application server as the nextjs user +echo "🐳 [Docker] Starting Next.js standalone server as nextjs user..." +exec su-exec nextjs node server.js diff --git a/frontend/public/sw.js b/frontend/public/sw.js index dc92f007..b6270bd5 100644 --- a/frontend/public/sw.js +++ b/frontend/public/sw.js @@ -1 +1 @@ -if(!self.define){let e,s={};const n=(n,a)=>(n=new URL(n+".js",a).href,s[n]||new Promise(s=>{if("document"in self){const e=document.createElement("script");e.src=n,e.onload=s,document.head.appendChild(e)}else e=n,importScripts(n),s()}).then(()=>{let e=s[n];if(!e)throw new Error(`Module ${n} didn’t register its module`);return e}));self.define=(a,c)=>{const t=e||("document"in self?document.currentScript.src:"")||location.href;if(s[t])return;let i={};const o=e=>n(e,t),r={module:{uri:t},exports:i,require:o};s[t]=Promise.all(a.map(e=>r[e]||o(e))).then(e=>(c(...e),i))}}define(["./workbox-4754cb34"],function(e){"use strict";importScripts(),self.skipWaiting(),e.clientsClaim(),e.precacheAndRoute([{url:"/_next/app-build-manifest.json",revision:"c4405793e023478e22a5140f670469e3"},{url:"/_next/static/chunks/255-4f212684648fcab9.js",revision:"4f212684648fcab9"},{url:"/_next/static/chunks/310-133da0a7e784e153.js",revision:"133da0a7e784e153"},{url:"/_next/static/chunks/374-a4ed2a0d861d9cab.js",revision:"a4ed2a0d861d9cab"},{url:"/_next/static/chunks/4bd1b696-c023c6e3521b1417.js",revision:"c023c6e3521b1417"},{url:"/_next/static/chunks/734-d2dd44012608c6c5.js",revision:"d2dd44012608c6c5"},{url:"/_next/static/chunks/7cb1fa1f-32fc9056ac653916.js",revision:"32fc9056ac653916"},{url:"/_next/static/chunks/802-838213ff73429b14.js",revision:"838213ff73429b14"},{url:"/_next/static/chunks/814-d4585cd22c8d1c30.js",revision:"d4585cd22c8d1c30"},{url:"/_next/static/chunks/app/_not-found/page-32814a613bf1bbe9.js",revision:"32814a613bf1bbe9"},{url:"/_next/static/chunks/app/admin/page-030c124b7f5b879e.js",revision:"030c124b7f5b879e"},{url:"/_next/static/chunks/app/inventory/page-847f5cffaaf40c2b.js",revision:"847f5cffaaf40c2b"},{url:"/_next/static/chunks/app/layout-451a79d582619723.js",revision:"451a79d582619723"},{url:"/_next/static/chunks/app/login/page-3bee94da08339aaf.js",revision:"3bee94da08339aaf"},{url:"/_next/static/chunks/app/logs/page-b5c1a310dbe6efde.js",revision:"b5c1a310dbe6efde"},{url:"/_next/static/chunks/app/page-cf51378d996e81f7.js",revision:"cf51378d996e81f7"},{url:"/_next/static/chunks/framework-050c1f32293f7182.js",revision:"050c1f32293f7182"},{url:"/_next/static/chunks/main-aa573100fdde0547.js",revision:"aa573100fdde0547"},{url:"/_next/static/chunks/main-app-290572149fe5c423.js",revision:"290572149fe5c423"},{url:"/_next/static/chunks/pages/_app-7d307437aca18ad4.js",revision:"7d307437aca18ad4"},{url:"/_next/static/chunks/pages/_error-cb2a52f75f2162e2.js",revision:"cb2a52f75f2162e2"},{url:"/_next/static/chunks/polyfills-42372ed130431b0a.js",revision:"846118c33b2c0e922d7b3a7676f81f6f"},{url:"/_next/static/chunks/webpack-b9fe5f5bee830aaa.js",revision:"b9fe5f5bee830aaa"},{url:"/_next/static/css/aed49b0ea0984475.css",revision:"aed49b0ea0984475"},{url:"/_next/static/css/dc95ad5bbe72bd91.css",revision:"dc95ad5bbe72bd91"},{url:"/_next/static/media/bootstrap-icons.154365ac.woff2",revision:"154365ac"},{url:"/_next/static/media/bootstrap-icons.ac1a99b3.woff",revision:"ac1a99b3"},{url:"/_next/static/y_wucd5LyR5TZClxIkxIH/_buildManifest.js",revision:"31eba5c94d685fe15b2d866310094bce"},{url:"/_next/static/y_wucd5LyR5TZClxIkxIH/_ssgManifest.js",revision:"b6652df95db52feb4daf4eca35380933"},{url:"/custom-logo.png",revision:"d41d8cd98f00b204e9800998ecf8427e"},{url:"/icons/icon-192x192.png",revision:"bde7fc04fd83ed65ce0e71259f4574b5"},{url:"/icons/icon-512x512.png",revision:"0c2346ba1775af7af6ec2e346ee7c72e"},{url:"/icons/maskable-icon.png",revision:"0c2346ba1775af7af6ec2e346ee7c72e"},{url:"/logo.png",revision:"084f394cfe19b7d85abec9878a797795"},{url:"/logo_TFM.png",revision:"084f394cfe19b7d85abec9878a797795"},{url:"/manifest.json",revision:"7b6e42d577201b657b72b8698d44d0b2"}],{ignoreURLParametersMatching:[]}),e.cleanupOutdatedCaches(),e.registerRoute("/",new e.NetworkFirst({cacheName:"start-url",plugins:[{cacheWillUpdate:async({request:e,response:s,event:n,state:a})=>s&&"opaqueredirect"===s.type?new Response(s.body,{status:200,statusText:"OK",headers:s.headers}):s}]}),"GET"),e.registerRoute(/^https:\/\/fonts\.(?:gstatic)\.com\/.*/i,new e.CacheFirst({cacheName:"google-fonts-webfonts",plugins:[new e.ExpirationPlugin({maxEntries:4,maxAgeSeconds:31536e3})]}),"GET"),e.registerRoute(/^https:\/\/fonts\.(?:googleapis)\.com\/.*/i,new e.StaleWhileRevalidate({cacheName:"google-fonts-stylesheets",plugins:[new e.ExpirationPlugin({maxEntries:4,maxAgeSeconds:604800})]}),"GET"),e.registerRoute(/\.(?:eot|otf|ttc|ttf|woff|woff2|font.css)$/i,new e.StaleWhileRevalidate({cacheName:"static-font-assets",plugins:[new e.ExpirationPlugin({maxEntries:4,maxAgeSeconds:604800})]}),"GET"),e.registerRoute(/\.(?:jpg|jpeg|gif|png|svg|ico|webp)$/i,new e.StaleWhileRevalidate({cacheName:"static-image-assets",plugins:[new e.ExpirationPlugin({maxEntries:64,maxAgeSeconds:86400})]}),"GET"),e.registerRoute(/\/_next\/image\?url=.+$/i,new e.StaleWhileRevalidate({cacheName:"next-image",plugins:[new e.ExpirationPlugin({maxEntries:64,maxAgeSeconds:86400})]}),"GET"),e.registerRoute(/\.(?:mp3|wav|ogg)$/i,new e.CacheFirst({cacheName:"static-audio-assets",plugins:[new e.RangeRequestsPlugin,new e.ExpirationPlugin({maxEntries:32,maxAgeSeconds:86400})]}),"GET"),e.registerRoute(/\.(?:mp4)$/i,new e.CacheFirst({cacheName:"static-video-assets",plugins:[new e.RangeRequestsPlugin,new e.ExpirationPlugin({maxEntries:32,maxAgeSeconds:86400})]}),"GET"),e.registerRoute(/\.(?:js)$/i,new e.StaleWhileRevalidate({cacheName:"static-js-assets",plugins:[new e.ExpirationPlugin({maxEntries:32,maxAgeSeconds:86400})]}),"GET"),e.registerRoute(/\.(?:css|less)$/i,new e.StaleWhileRevalidate({cacheName:"static-style-assets",plugins:[new e.ExpirationPlugin({maxEntries:32,maxAgeSeconds:86400})]}),"GET"),e.registerRoute(/\/_next\/data\/.+\/.+\.json$/i,new e.StaleWhileRevalidate({cacheName:"next-data",plugins:[new e.ExpirationPlugin({maxEntries:32,maxAgeSeconds:86400})]}),"GET"),e.registerRoute(/\.(?:json|xml|csv)$/i,new e.NetworkFirst({cacheName:"static-data-assets",plugins:[new e.ExpirationPlugin({maxEntries:32,maxAgeSeconds:86400})]}),"GET"),e.registerRoute(({url:e})=>{if(!(self.origin===e.origin))return!1;const s=e.pathname;return!s.startsWith("/api/auth/")&&!!s.startsWith("/api/")},new e.NetworkFirst({cacheName:"apis",networkTimeoutSeconds:10,plugins:[new e.ExpirationPlugin({maxEntries:16,maxAgeSeconds:86400})]}),"GET"),e.registerRoute(({url:e})=>{if(!(self.origin===e.origin))return!1;return!e.pathname.startsWith("/api/")},new e.NetworkFirst({cacheName:"others",networkTimeoutSeconds:10,plugins:[new e.ExpirationPlugin({maxEntries:32,maxAgeSeconds:86400})]}),"GET"),e.registerRoute(({url:e})=>!(self.origin===e.origin),new e.NetworkFirst({cacheName:"cross-origin",networkTimeoutSeconds:10,plugins:[new e.ExpirationPlugin({maxEntries:32,maxAgeSeconds:3600})]}),"GET")}); +if(!self.define){let e,s={};const a=(a,n)=>(a=new URL(a+".js",n).href,s[a]||new Promise(s=>{if("document"in self){const e=document.createElement("script");e.src=a,e.onload=s,document.head.appendChild(e)}else e=a,importScripts(a),s()}).then(()=>{let e=s[a];if(!e)throw new Error(`Module ${a} didn’t register its module`);return e}));self.define=(n,c)=>{const t=e||("document"in self?document.currentScript.src:"")||location.href;if(s[t])return;let i={};const o=e=>a(e,t),r={module:{uri:t},exports:i,require:o};s[t]=Promise.all(n.map(e=>r[e]||o(e))).then(e=>(c(...e),i))}}define(["./workbox-4754cb34"],function(e){"use strict";importScripts(),self.skipWaiting(),e.clientsClaim(),e.precacheAndRoute([{url:"/_next/app-build-manifest.json",revision:"670cb7c7d0f0aba0c1628594378c2ac9"},{url:"/_next/static/chunks/255-4f212684648fcab9.js",revision:"4f212684648fcab9"},{url:"/_next/static/chunks/310-133da0a7e784e153.js",revision:"133da0a7e784e153"},{url:"/_next/static/chunks/374-a4ed2a0d861d9cab.js",revision:"a4ed2a0d861d9cab"},{url:"/_next/static/chunks/4bd1b696-c023c6e3521b1417.js",revision:"c023c6e3521b1417"},{url:"/_next/static/chunks/734-d2dd44012608c6c5.js",revision:"d2dd44012608c6c5"},{url:"/_next/static/chunks/7cb1fa1f-32fc9056ac653916.js",revision:"32fc9056ac653916"},{url:"/_next/static/chunks/802-838213ff73429b14.js",revision:"838213ff73429b14"},{url:"/_next/static/chunks/814-d4585cd22c8d1c30.js",revision:"d4585cd22c8d1c30"},{url:"/_next/static/chunks/app/_not-found/page-32814a613bf1bbe9.js",revision:"32814a613bf1bbe9"},{url:"/_next/static/chunks/app/admin/page-030c124b7f5b879e.js",revision:"030c124b7f5b879e"},{url:"/_next/static/chunks/app/inventory/page-847f5cffaaf40c2b.js",revision:"847f5cffaaf40c2b"},{url:"/_next/static/chunks/app/layout-451a79d582619723.js",revision:"451a79d582619723"},{url:"/_next/static/chunks/app/login/page-3bee94da08339aaf.js",revision:"3bee94da08339aaf"},{url:"/_next/static/chunks/app/logs/page-b5c1a310dbe6efde.js",revision:"b5c1a310dbe6efde"},{url:"/_next/static/chunks/app/page-4b381a9697c2a2ff.js",revision:"4b381a9697c2a2ff"},{url:"/_next/static/chunks/framework-050c1f32293f7182.js",revision:"050c1f32293f7182"},{url:"/_next/static/chunks/main-aa573100fdde0547.js",revision:"aa573100fdde0547"},{url:"/_next/static/chunks/main-app-290572149fe5c423.js",revision:"290572149fe5c423"},{url:"/_next/static/chunks/pages/_app-7d307437aca18ad4.js",revision:"7d307437aca18ad4"},{url:"/_next/static/chunks/pages/_error-cb2a52f75f2162e2.js",revision:"cb2a52f75f2162e2"},{url:"/_next/static/chunks/polyfills-42372ed130431b0a.js",revision:"846118c33b2c0e922d7b3a7676f81f6f"},{url:"/_next/static/chunks/webpack-b9fe5f5bee830aaa.js",revision:"b9fe5f5bee830aaa"},{url:"/_next/static/css/aed49b0ea0984475.css",revision:"aed49b0ea0984475"},{url:"/_next/static/css/dc95ad5bbe72bd91.css",revision:"dc95ad5bbe72bd91"},{url:"/_next/static/elpNg6t1FphuQgULQNunU/_buildManifest.js",revision:"31eba5c94d685fe15b2d866310094bce"},{url:"/_next/static/elpNg6t1FphuQgULQNunU/_ssgManifest.js",revision:"b6652df95db52feb4daf4eca35380933"},{url:"/_next/static/media/bootstrap-icons.154365ac.woff2",revision:"154365ac"},{url:"/_next/static/media/bootstrap-icons.ac1a99b3.woff",revision:"ac1a99b3"},{url:"/custom-logo.png",revision:"d41d8cd98f00b204e9800998ecf8427e"},{url:"/icons/icon-192x192.png",revision:"bde7fc04fd83ed65ce0e71259f4574b5"},{url:"/icons/icon-512x512.png",revision:"0c2346ba1775af7af6ec2e346ee7c72e"},{url:"/icons/maskable-icon.png",revision:"0c2346ba1775af7af6ec2e346ee7c72e"},{url:"/logo.png",revision:"084f394cfe19b7d85abec9878a797795"},{url:"/logo_TFM.png",revision:"084f394cfe19b7d85abec9878a797795"},{url:"/manifest.json",revision:"7b6e42d577201b657b72b8698d44d0b2"}],{ignoreURLParametersMatching:[]}),e.cleanupOutdatedCaches(),e.registerRoute("/",new e.NetworkFirst({cacheName:"start-url",plugins:[{cacheWillUpdate:async({request:e,response:s,event:a,state:n})=>s&&"opaqueredirect"===s.type?new Response(s.body,{status:200,statusText:"OK",headers:s.headers}):s}]}),"GET"),e.registerRoute(/^https:\/\/fonts\.(?:gstatic)\.com\/.*/i,new e.CacheFirst({cacheName:"google-fonts-webfonts",plugins:[new e.ExpirationPlugin({maxEntries:4,maxAgeSeconds:31536e3})]}),"GET"),e.registerRoute(/^https:\/\/fonts\.(?:googleapis)\.com\/.*/i,new e.StaleWhileRevalidate({cacheName:"google-fonts-stylesheets",plugins:[new e.ExpirationPlugin({maxEntries:4,maxAgeSeconds:604800})]}),"GET"),e.registerRoute(/\.(?:eot|otf|ttc|ttf|woff|woff2|font.css)$/i,new e.StaleWhileRevalidate({cacheName:"static-font-assets",plugins:[new e.ExpirationPlugin({maxEntries:4,maxAgeSeconds:604800})]}),"GET"),e.registerRoute(/\.(?:jpg|jpeg|gif|png|svg|ico|webp)$/i,new e.StaleWhileRevalidate({cacheName:"static-image-assets",plugins:[new e.ExpirationPlugin({maxEntries:64,maxAgeSeconds:86400})]}),"GET"),e.registerRoute(/\/_next\/image\?url=.+$/i,new e.StaleWhileRevalidate({cacheName:"next-image",plugins:[new e.ExpirationPlugin({maxEntries:64,maxAgeSeconds:86400})]}),"GET"),e.registerRoute(/\.(?:mp3|wav|ogg)$/i,new e.CacheFirst({cacheName:"static-audio-assets",plugins:[new e.RangeRequestsPlugin,new e.ExpirationPlugin({maxEntries:32,maxAgeSeconds:86400})]}),"GET"),e.registerRoute(/\.(?:mp4)$/i,new e.CacheFirst({cacheName:"static-video-assets",plugins:[new e.RangeRequestsPlugin,new e.ExpirationPlugin({maxEntries:32,maxAgeSeconds:86400})]}),"GET"),e.registerRoute(/\.(?:js)$/i,new e.StaleWhileRevalidate({cacheName:"static-js-assets",plugins:[new e.ExpirationPlugin({maxEntries:32,maxAgeSeconds:86400})]}),"GET"),e.registerRoute(/\.(?:css|less)$/i,new e.StaleWhileRevalidate({cacheName:"static-style-assets",plugins:[new e.ExpirationPlugin({maxEntries:32,maxAgeSeconds:86400})]}),"GET"),e.registerRoute(/\/_next\/data\/.+\/.+\.json$/i,new e.StaleWhileRevalidate({cacheName:"next-data",plugins:[new e.ExpirationPlugin({maxEntries:32,maxAgeSeconds:86400})]}),"GET"),e.registerRoute(/\.(?:json|xml|csv)$/i,new e.NetworkFirst({cacheName:"static-data-assets",plugins:[new e.ExpirationPlugin({maxEntries:32,maxAgeSeconds:86400})]}),"GET"),e.registerRoute(({url:e})=>{if(!(self.origin===e.origin))return!1;const s=e.pathname;return!s.startsWith("/api/auth/")&&!!s.startsWith("/api/")},new e.NetworkFirst({cacheName:"apis",networkTimeoutSeconds:10,plugins:[new e.ExpirationPlugin({maxEntries:16,maxAgeSeconds:86400})]}),"GET"),e.registerRoute(({url:e})=>{if(!(self.origin===e.origin))return!1;return!e.pathname.startsWith("/api/")},new e.NetworkFirst({cacheName:"others",networkTimeoutSeconds:10,plugins:[new e.ExpirationPlugin({maxEntries:32,maxAgeSeconds:86400})]}),"GET"),e.registerRoute(({url:e})=>!(self.origin===e.origin),new e.NetworkFirst({cacheName:"cross-origin",networkTimeoutSeconds:10,plugins:[new e.ExpirationPlugin({maxEntries:32,maxAgeSeconds:3600})]}),"GET")});