Facilitate custom UID/GID at runtime in Docker. (#161)
* Cleanup /scripts dir * Facilitate custom UID/GID at Docker runtime. Changes file permissions at runtime depending on UID/GID. * Disable non-functional frontend tests for now. --------- Co-authored-by: antanst <>
This commit is contained in:
parent
ee37441ad0
commit
dad0bd45ff
11 changed files with 97 additions and 4551 deletions
28
Dockerfile
28
Dockerfile
|
|
@ -24,21 +24,20 @@ RUN NODE_ENV=production npm run frontend:build
|
|||
|
||||
# Run backend tests
|
||||
RUN DOCKER_BUILD=1 npm run backend:test
|
||||
|
||||
# Cleanup
|
||||
RUN npm cache clean --force && \
|
||||
rm -rf ~/.npm /tmp/* && \
|
||||
apk del .build-deps
|
||||
|
||||
|
||||
####################
|
||||
# Production stage #
|
||||
####################
|
||||
FROM node:20-alpine AS production
|
||||
|
||||
# Set build-time and runtime UID/GID (default 1001)
|
||||
ARG APP_UID=1001
|
||||
ARG APP_GID=1001
|
||||
ENV APP_UID=${APP_UID}
|
||||
ENV APP_GID=${APP_GID}
|
||||
ENV APP_UID=1001
|
||||
ENV APP_GID=1001
|
||||
|
||||
RUN addgroup -g ${APP_GID} -S app && \
|
||||
adduser -S app -u ${APP_UID} -G app
|
||||
|
|
@ -47,7 +46,9 @@ RUN apk add --no-cache --virtual .runtime-deps \
|
|||
sqlite \
|
||||
openssl \
|
||||
curl \
|
||||
dumb-init && \
|
||||
procps-ng \
|
||||
dumb-init \
|
||||
su-exec && \
|
||||
rm -rf /var/cache/apk/* /tmp/* && \
|
||||
rm -rf /usr/share/man /usr/share/doc /usr/share/info
|
||||
|
||||
|
|
@ -58,13 +59,15 @@ WORKDIR /app
|
|||
COPY ./backend/ /app/backend/
|
||||
RUN chmod +x /app/backend/cmd/start.sh
|
||||
|
||||
COPY ./scripts/docker-entrypoint.sh /app/scripts/docker-entrypoint.sh
|
||||
RUN chmod +x /app/scripts/docker-entrypoint.sh
|
||||
|
||||
# Copy frontend
|
||||
RUN rm -rf /app/backend/dist
|
||||
COPY --from=builder --chown=app:app /app/dist ./backend/dist
|
||||
COPY --from=builder --chown=app:app /app/public/locales ./backend/dist/locales
|
||||
|
||||
# Copy all dependencies (now in root)
|
||||
COPY --from=builder --chown=app:app /app/node_modules ./node_modules
|
||||
COPY --from=builder --chown=app:app /app/package.json /app/
|
||||
|
||||
# Create necessary directories
|
||||
RUN mkdir -p /app/backend/db /app/backend/certs && \
|
||||
|
|
@ -72,14 +75,12 @@ RUN mkdir -p /app/backend/db /app/backend/certs && \
|
|||
|
||||
# Cleanup
|
||||
RUN apk del --no-cache .runtime-deps sqlite openssl curl && \
|
||||
apk add --no-cache sqlite-libs openssl curl dumb-init && \
|
||||
apk add --no-cache sqlite-libs openssl curl dumb-init su-exec && \
|
||||
rm -rf /usr/local/lib/node_modules/npm/docs /usr/local/lib/node_modules/npm/man && \
|
||||
rm -rf /root/.npm /tmp/* /var/tmp/* /var/cache/apk/*
|
||||
|
||||
VOLUME ["/app/backend/db"]
|
||||
|
||||
USER app
|
||||
|
||||
EXPOSE 3002
|
||||
|
||||
ENV NODE_ENV=production \
|
||||
|
|
@ -92,11 +93,8 @@ ENV NODE_ENV=production \
|
|||
DISABLE_TELEGRAM=false \
|
||||
DISABLE_SCHEDULER=false
|
||||
|
||||
# Docker healthcheck
|
||||
HEALTHCHECK --interval=60s --timeout=3s --start-period=10s --retries=2 \
|
||||
CMD curl -sf http://localhost:3002/api/health || exit 1
|
||||
|
||||
# Use dumb-init for proper signal handling
|
||||
ENTRYPOINT ["dumb-init", "--"]
|
||||
WORKDIR /app/backend
|
||||
CMD ["/app/backend/cmd/start.sh"]
|
||||
ENTRYPOINT ["/app/scripts/docker-entrypoint.sh"]
|
||||
|
|
|
|||
18
README.md
18
README.md
|
|
@ -5,11 +5,7 @@ smart recurring tasks, and seamless Telegram integration. Get focused, stay prod
|
|||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
More screenshots [here](#screenshots).
|
||||
|
||||
## 🚀 How It Works
|
||||
|
||||
|
|
@ -104,6 +100,7 @@ The following environment variables are used to configure tududi:
|
|||
- `TUDUDI_SESSION_SECRET` - Session encryption key (generate with `openssl rand -hex 64`)
|
||||
|
||||
#### Optional Variables:
|
||||
- `PUID`, `GUID` - Run with specified user and group ID (instead of defaults 1001/1001)
|
||||
- `TUDUDI_INTERNAL_SSL_ENABLED` - Set to 'true' if using HTTPS internally (default: false)
|
||||
- `TUDUDI_ALLOWED_ORIGINS` - Controls CORS access for different deployment scenarios:
|
||||
- Not set: Only allows localhost origins
|
||||
|
|
@ -139,6 +136,8 @@ docker run \
|
|||
-e TUDUDI_SESSION_SECRET=$(openssl rand -hex 64) \
|
||||
-e TUDUDI_INTERNAL_SSL_ENABLED=false \
|
||||
-e TUDUDI_ALLOWED_ORIGINS=https://tududi,http://tududi:3002 \
|
||||
-e PUID=1001 \
|
||||
-e GUID=1001 \
|
||||
-v ~/tududi_db:/app/backend/db \
|
||||
-v ~/tududi_uploads:/app/backend/uploads \
|
||||
-p 3002:3002 \
|
||||
|
|
@ -443,6 +442,15 @@ Join the tududi community:
|
|||
- **[BreachHarbor](https://breachharbor.com)** - Cybersecurity suite for digital asset protection
|
||||
- **[Hevetra](https://hevetra.com)** - Digital tracking for child health milestones
|
||||
|
||||
# Screenshots
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
---
|
||||
|
||||
README created by [Chris Veleris](https://github.com/chrisvel) for `tududi`.
|
||||
|
|
|
|||
|
|
@ -1,47 +1,24 @@
|
|||
#!/bin/sh
|
||||
set -eu
|
||||
|
||||
# Check and create directories with proper permissions
|
||||
if [ ! -d "db" ]; then
|
||||
mkdir -p db
|
||||
fi
|
||||
|
||||
if [ ! -w "db" ]; then
|
||||
if [ "$(id -u)" = "0" ]; then
|
||||
echo "⚠️ Attempting to fix permissions for /app/backend/db as root..."
|
||||
chown -R "$APP_UID":"$APP_GID" db || true
|
||||
chmod -R 770 db || true
|
||||
if [ ! -w "db" ]; then
|
||||
echo "❌ ERROR: Database directory /app/backend/db is not writable by user $APP_UID:$APP_GID after chown/chmod"
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
echo "❌ ERROR: Database directory /app/backend/db is not writable by user $(id -u):$(id -g)"
|
||||
echo "ℹ️ If using Docker volumes, ensure the host directory has proper ownership or run the container as root for automatic fix."
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
mkdir -p certs
|
||||
|
||||
DB_FILE="db/production.sqlite3"
|
||||
[ "$NODE_ENV" = "development" ] && DB_FILE="db/development.sqlite3"
|
||||
|
||||
# Check if database exists and create/authenticate
|
||||
if [ ! -f "$DB_FILE" ]; then
|
||||
echo "🔧 Creating new database..."
|
||||
echo "Creating new database..."
|
||||
node -e "require(\"./models\").sequelize.sync({force:true}).then(()=>{console.log(\"✅ DB ready\");process.exit(0)}).catch(e=>{console.error(\"❌\",e.message);process.exit(1)})"
|
||||
else
|
||||
echo "🔍 Checking database connection..."
|
||||
echo "Checking database connection..."
|
||||
node -e "require(\"./models\").sequelize.authenticate().then(()=>{console.log(\"✅ DB OK\");process.exit(0)}).catch(e=>{console.error(\"❌\",e.message);process.exit(1)})"
|
||||
fi
|
||||
|
||||
# Run database migrations automatically
|
||||
echo "🔄 Running database migrations..."
|
||||
echo "Running database migrations..."
|
||||
if npx sequelize-cli db:migrate --config config/database.js; then
|
||||
echo "✅ Migrations completed successfully"
|
||||
echo "Migrations completed successfully"
|
||||
else
|
||||
echo "⚠️ Migration failed, but continuing startup (may be expected for new installations)"
|
||||
echo "Migration failed, but continuing startup (may be expected for new installations)"
|
||||
fi
|
||||
|
||||
if [ -n "${TUDUDI_USER_EMAIL:-}" ] && [ -n "${TUDUDI_USER_PASSWORD:-}" ]; then
|
||||
|
|
|
|||
|
|
@ -8,13 +8,11 @@ services:
|
|||
- TUDUDI_SESSION_SECRET=changeme-please-use-openssl
|
||||
- TUDUDI_INTERNAL_SSL_ENABLED=false
|
||||
- TUDUDI_ALLOWED_ORIGINS=http://localhost:3002
|
||||
# Optionally set APP_UID and APP_GID to match your host user/group
|
||||
# - APP_UID=1001
|
||||
# - APP_GID=1001
|
||||
# Runtime UID/GID configuration - set these to match your host user/group
|
||||
- PUID=1001
|
||||
- PGID=1001
|
||||
volumes:
|
||||
- ./tududi_db:/app/backend/db
|
||||
ports:
|
||||
- "3002:3002"
|
||||
restart: unless-stopped
|
||||
# Uncomment to run as root for automatic permission fix (not recommended for production)
|
||||
# user: "0:0"
|
||||
|
|
|
|||
14
package.json
14
package.json
|
|
@ -10,10 +10,10 @@
|
|||
"start": "npm run backend:start",
|
||||
"dev": "npm run frontend:dev",
|
||||
"build": "npm run frontend:build",
|
||||
"test": "npm run frontend:test && npm run backend:test",
|
||||
"test": "npm run backend:test",
|
||||
"test:watch": "npm run frontend:test:watch",
|
||||
"test:coverage": "npm run frontend:test:coverage && npm run backend:test:coverage",
|
||||
|
||||
|
||||
"frontend:dev": "webpack serve --config webpack.config.js --hot",
|
||||
"frontend:start": "tsc --noEmit && webpack serve --config webpack.config.js",
|
||||
"frontend:build": "npm run clean && tsc --noEmit && webpack --config webpack.config.js",
|
||||
|
|
@ -23,7 +23,7 @@
|
|||
"frontend:lint": "eslint 'frontend/**/*.{js,jsx,ts,tsx}'",
|
||||
"frontend:lint-fix": "eslint --fix 'frontend/**/*.{js,jsx,ts,tsx}'",
|
||||
"frontend:format": "prettier --write 'frontend/**/*.{js,jsx,ts,tsx}'",
|
||||
|
||||
|
||||
"backend:start": "cd backend && ./cmd/start.sh",
|
||||
"backend:dev": "cd backend && nodemon app.js",
|
||||
"backend:test": "cd backend && cross-env NODE_ENV=test jest",
|
||||
|
|
@ -34,27 +34,27 @@
|
|||
"backend:lint": "cd backend && eslint .",
|
||||
"backend:lint-fix": "cd backend && eslint . --fix",
|
||||
"backend:format": "cd backend && prettier --write .",
|
||||
|
||||
|
||||
"db:init": "cd backend && node scripts/db-init.js",
|
||||
"db:sync": "cd backend && node scripts/db-sync.js",
|
||||
"db:migrate": "cd backend && node scripts/db-migrate.js",
|
||||
"db:reset": "cd backend && node scripts/db-reset.js",
|
||||
"db:status": "cd backend && node scripts/db-status.js",
|
||||
"db:seed": "cd backend && node scripts/seed-dev-data.js",
|
||||
|
||||
|
||||
"user:create": "cd backend && node scripts/user-create.js",
|
||||
"migration:create": "cd backend && node scripts/migration-create.js",
|
||||
"migration:run": "cd backend && npx sequelize-cli db:migrate",
|
||||
"migration:undo": "cd backend && npx sequelize-cli db:migrate:undo",
|
||||
"migration:undo:all": "cd backend && npx sequelize-cli db:migrate:undo:all",
|
||||
"migration:status": "cd backend && npx sequelize-cli db:migrate:status",
|
||||
|
||||
|
||||
"translations:sync": "cd scripts && ./sync-translations.js",
|
||||
"translations:sync-all": "cd scripts && ./sync-translations.js --all",
|
||||
"translations:dry-run": "cd scripts && ./sync-translations.js --all --dry-run",
|
||||
"translations:check": "cd scripts && ./sync-translations.js --all --dry-run --verbose",
|
||||
"translations:export": "cd scripts && ./sync-translations.js --all --dry-run --output missing-translations.json",
|
||||
|
||||
|
||||
"clean": "rimraf dist",
|
||||
"lint": "npm run frontend:lint && npm run backend:lint",
|
||||
"lint-fix": "npm run frontend:lint-fix && npm run backend:lint-fix",
|
||||
|
|
|
|||
56
scripts/docker-entrypoint.sh
Normal file
56
scripts/docker-entrypoint.sh
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
#!/bin/sh
|
||||
set -eu
|
||||
|
||||
# Runtime UID/GID Configuration
|
||||
# This script allows setting the user ID and group ID at runtime using PUID and PGID environment variables
|
||||
# This solves the issue where hardcoded UID/GID (1001:1001) conflicts with host system users
|
||||
|
||||
# Get runtime UID/GID from environment variables, fallback to build-time defaults
|
||||
PUID=${PUID:-${APP_UID:-1001}}
|
||||
PGID=${PGID:-${APP_GID:-1001}}
|
||||
|
||||
# Get current app user/group info
|
||||
CURRENT_UID=$(id -u app)
|
||||
CURRENT_GID=$(id -g app)
|
||||
|
||||
echo "Runtime UID/GID Configuration"
|
||||
echo "Current: $CURRENT_UID:$CURRENT_GID"
|
||||
echo "Target: $PUID:$PGID"
|
||||
|
||||
# Only modify user/group if different from current
|
||||
if [ "$CURRENT_UID" != "$PUID" ] || [ "$CURRENT_GID" != "$PGID" ]; then
|
||||
echo "Configuring user permissions..."
|
||||
|
||||
deluser app 2>/dev/null || true
|
||||
delgroup app 2>/dev/null || true
|
||||
|
||||
if getent group "$PGID" >/dev/null 2>&1; then
|
||||
TARGET_GROUP=$(getent group "$PGID" | cut -d: -f1)
|
||||
echo "Using existing group: $TARGET_GROUP ($PGID)"
|
||||
else
|
||||
addgroup -g "$PGID" -S app
|
||||
TARGET_GROUP="app"
|
||||
echo "Created app group with GID: $PGID"
|
||||
fi
|
||||
|
||||
if getent passwd "$PUID" >/dev/null 2>&1; then
|
||||
echo "Using existing user with UID $PUID"
|
||||
else
|
||||
adduser -S app -u "$PUID" -G "$TARGET_GROUP"
|
||||
echo "Created user with UID: $PUID, GID: $PGID"
|
||||
fi
|
||||
|
||||
echo "Fixing ownership of application directories..."
|
||||
chown -R app:$TARGET_GROUP /app
|
||||
mkdir -p /app/backend/db /app/backend/certs
|
||||
chown -R app:$TARGET_GROUP /app/backend/db /app/backend/certs
|
||||
chmod 770 /app/backend/db /app/backend/certs
|
||||
|
||||
echo "User configuration completed"
|
||||
else
|
||||
echo "No user configuration needed"
|
||||
fi
|
||||
|
||||
# Drop privileges and execute the original start script
|
||||
echo "🚀 Starting application as user $(id -u app):$(id -g app)"
|
||||
exec su-exec app dumb-init -- /app/backend/cmd/start.sh
|
||||
|
|
@ -1,55 +0,0 @@
|
|||
#!/bin/bash
|
||||
|
||||
echo "🌍 Translation Sync Script Examples"
|
||||
echo "=================================="
|
||||
echo ""
|
||||
|
||||
echo "📋 Prerequisites:"
|
||||
echo "1. Set your OpenAI API key: export OPENAI_API_KEY=\"your-key-here\""
|
||||
echo "2. Install dependencies: cd scripts && npm install"
|
||||
echo ""
|
||||
|
||||
echo "📖 Basic Usage Examples:"
|
||||
echo ""
|
||||
|
||||
echo "# Check what would be updated (dry run):"
|
||||
echo "npm run translations:dry-run"
|
||||
echo ""
|
||||
|
||||
echo "# Update all languages:"
|
||||
echo "npm run translations:sync-all"
|
||||
echo ""
|
||||
|
||||
echo "# Update specific languages:"
|
||||
echo "npm run translations:sync -- --lang=jp,de"
|
||||
echo ""
|
||||
|
||||
echo "# Check specific language:"
|
||||
echo "npm run translations:sync -- --lang=es --dry-run"
|
||||
echo ""
|
||||
|
||||
echo "🔧 Direct Script Usage:"
|
||||
echo ""
|
||||
|
||||
echo "# From scripts directory:"
|
||||
echo "cd scripts"
|
||||
echo "./sync-translations.js --all"
|
||||
echo "./sync-translations.js --lang=jp,el,de"
|
||||
echo "./sync-translations.js --lang=it --dry-run"
|
||||
echo ""
|
||||
|
||||
echo "📊 Current Status:"
|
||||
if [ -z "$OPENAI_API_KEY" ]; then
|
||||
echo "❌ OPENAI_API_KEY not set"
|
||||
else
|
||||
echo "✅ OPENAI_API_KEY is configured"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "🗂️ Available Languages:"
|
||||
echo "- de (German)"
|
||||
echo "- es (Spanish)"
|
||||
echo "- el (Greek)"
|
||||
echo "- it (Italian)"
|
||||
echo "- jp (Japanese)"
|
||||
echo "- ua (Ukrainian)"
|
||||
File diff suppressed because it is too large
Load diff
28
scripts/package-lock.json
generated
28
scripts/package-lock.json
generated
|
|
@ -1,28 +0,0 @@
|
|||
{
|
||||
"name": "tududi-translation-scripts",
|
||||
"version": "1.0.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "tududi-translation-scripts",
|
||||
"version": "1.0.0",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"commander": "^11.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/commander": {
|
||||
"version": "11.1.0",
|
||||
"resolved": "https://registry.npmjs.org/commander/-/commander-11.1.0.tgz",
|
||||
"integrity": "sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=16"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,17 +0,0 @@
|
|||
{
|
||||
"name": "tududi-translation-scripts",
|
||||
"version": "1.0.0",
|
||||
"description": "Translation management scripts for tududi",
|
||||
"main": "sync-translations.js",
|
||||
"scripts": {
|
||||
"sync": "node sync-translations.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"commander": "^11.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.0.0"
|
||||
},
|
||||
"author": "tududi team",
|
||||
"license": "MIT"
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue