From ecb9fe6b3347b8e9e8662f4e32089f4804afda88 Mon Sep 17 00:00:00 2001 From: Zodiac Date: Mon, 29 Dec 2025 19:02:58 -0800 Subject: [PATCH] fix: Add entrypoint script to fix volume permissions at runtime - Add docker-entrypoint.sh that runs as root to fix mounted volume permissions - Creates required subdirectories (logs, users, packages) before app starts - Copies default config.js if missing - Drops to node user via su-exec before running the app - Update Dockerfile to use entrypoint and install su-exec - Update docker-compose.yml with UID/GID mapping and separate volume mounts - Wrap filesystem operations in try-catch to handle permission errors gracefully --- Dockerfile | 7 ++++--- docker-compose.yml | 5 ++++- docker-entrypoint.sh | 19 +++++++++++++++++++ server/command-line/start.ts | 35 +++++++++++++++++++++++------------ server/config.ts | 2 +- 5 files changed, 51 insertions(+), 17 deletions(-) create mode 100644 docker-entrypoint.sh diff --git a/Dockerfile b/Dockerfile index 7537170d..103a5820 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,6 +1,6 @@ FROM node:20-alpine RUN apk add --no-cache --virtual=build-dependencies build-base git python3-dev py3-setuptools && \ - apk add --no-cache yarn + apk add --no-cache yarn su-exec WORKDIR /var/opt/hardlounge-src ENV THELOUNGE_HOME /var/opt/hardlounge COPY --chown=node:node package.json yarn.lock . @@ -13,8 +13,9 @@ RUN NODE_ENV=production yarn build && \ ln -s /var/opt/hardlounge-src/index.js /var/opt/hardlounge-src/hardlounge USER root RUN apk del --purge build-dependencies && \ - mkdir -p /var/opt/hardlounge && \ + mkdir -p /var/opt/hardlounge/logs /var/opt/hardlounge/users /var/opt/hardlounge/packages && \ chown -R node:node /var/opt/hardlounge -USER node +COPY --chmod=755 docker-entrypoint.sh /docker-entrypoint.sh EXPOSE 9000 +ENTRYPOINT ["/docker-entrypoint.sh"] CMD ["/var/opt/hardlounge-src/hardlounge", "start"] \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml index 790441e3..9b793b95 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -2,7 +2,10 @@ services: hardlounge: image: git.supernets.org/supernets/hardlounge:latest build: . + user: "${UID}:${GID}" ports: - "9000:9000" volumes: - - "$PWD/config:/var/opt/hardlounge" + - ./data/logs:/var/opt/hardlounge/logs + - ./data/packages:/var/opt/hardlounge/packages + - ./config:/var/opt/hardlounge diff --git a/docker-entrypoint.sh b/docker-entrypoint.sh new file mode 100644 index 00000000..520aab3f --- /dev/null +++ b/docker-entrypoint.sh @@ -0,0 +1,19 @@ +#!/bin/sh +set -e + +# Fix permissions on mounted volume at runtime +chown -R node:node /var/opt/hardlounge 2>/dev/null || true +chmod -R 755 /var/opt/hardlounge 2>/dev/null || true + +# Create required subdirectories +mkdir -p /var/opt/hardlounge/logs /var/opt/hardlounge/users /var/opt/hardlounge/packages 2>/dev/null || true +chown -R node:node /var/opt/hardlounge 2>/dev/null || true + +# Copy default config if it doesn't exist +if [ ! -f /var/opt/hardlounge/config.js ]; then + cp /var/opt/hardlounge-src/dist/defaults/config.js /var/opt/hardlounge/config.js 2>/dev/null || true + chown node:node /var/opt/hardlounge/config.js 2>/dev/null || true +fi + +# Run as node user +exec su-exec node "$@" diff --git a/server/command-line/start.ts b/server/command-line/start.ts index a5e800cd..a9fd48b9 100644 --- a/server/command-line/start.ts +++ b/server/command-line/start.ts @@ -22,26 +22,37 @@ program function initalizeConfig() { if (!fs.existsSync(Config.getConfigPath())) { - fs.mkdirSync(Config.getHomePath(), {recursive: true}); - try { - fs.chmodSync(Config.getHomePath(), "0700"); - } catch (e) { - // Ignore chmod errors (e.g., on mounted volumes) - } + fs.mkdirSync(Config.getHomePath(), {recursive: true}); - fs.copyFileSync( - path.resolve(path.join(__dirname, "..", "..", "defaults", "config.js")), - Config.getConfigPath() - ); - log.info(`Configuration file created at ${colors.green(Config.getConfigPath())}.`); + try { + fs.chmodSync(Config.getHomePath(), "0700"); + } catch (e) { + // Ignore chmod errors (e.g., on mounted volumes) + } + + fs.copyFileSync( + path.resolve(path.join(__dirname, "..", "..", "defaults", "config.js")), + Config.getConfigPath() + ); + log.info(`Configuration file created at ${colors.green(Config.getConfigPath())}.`); + } catch (e) { + log.error( + "Unable to create config directory/file. Please ensure the config volume is writable or pre-create config.js" + ); + process.exit(1); + } } try { fs.mkdirSync(Config.getUsersPath(), {recursive: true, mode: 0o700}); } catch (e) { // Ignore permission errors on mounted volumes - fs.mkdirSync(Config.getUsersPath(), {recursive: true}); + try { + fs.mkdirSync(Config.getUsersPath(), {recursive: true}); + } catch (e2) { + // Ignore if this also fails + } } } diff --git a/server/config.ts b/server/config.ts index bad5f522..dbe9458e 100644 --- a/server/config.ts +++ b/server/config.ts @@ -276,7 +276,7 @@ class Config { try { fs.mkdirSync(userLogsPath, {recursive: true, mode: 0o750}); } catch (e: any) { - log.error("Unable to create logs directory", e); + log.warn("Unable to create logs directory, logging to disk may not work"); } } else if (logsStat && logsStat.mode & 0o001) { log.warn(