mirror of
				https://github.com/DumbWareio/DumbDrop.git
				synced 2025-11-04 05:53:18 +00:00 
			
		
		
		
	Compare commits
	
		
			7 Commits
		
	
	
		
			main
			...
			80b5426c52
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					80b5426c52 | ||
| 
						 | 
					cb7e49b0e1 | ||
| 
						 | 
					5666569580 | ||
| 
						 | 
					6f1b93ed39 | ||
| 
						 | 
					6cf39e4639 | ||
| 
						 | 
					65d10a6576 | ||
| 
						 | 
					105d2a7412 | 
							
								
								
									
										23
									
								
								.env.example
									
									
									
									
									
								
							
							
						
						
									
										23
									
								
								.env.example
									
									
									
									
									
								
							@@ -65,4 +65,25 @@ AUTO_UPLOAD=false
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
# Comma-separated list of origins allowed to embed the app in an iframe (optional)
 | 
					# Comma-separated list of origins allowed to embed the app in an iframe (optional)
 | 
				
			||||||
# ALLOWED_IFRAME_ORIGINS=https://example.com,https://another.com
 | 
					# ALLOWED_IFRAME_ORIGINS=https://example.com,https://another.com
 | 
				
			||||||
ALLOWED_IFRAME_ORIGINS=
 | 
					ALLOWED_IFRAME_ORIGINS=
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# --- Docker Specific Settings ---
 | 
				
			||||||
 | 
					# User and Group IDs for file permissions
 | 
				
			||||||
 | 
					# Sets the user/group the application runs as inside the container.
 | 
				
			||||||
 | 
					# Files created in the mapped volume (e.g., ./local_uploads) will have this ownership.
 | 
				
			||||||
 | 
					# Set these to match your host user's ID/GID to avoid permission issues.
 | 
				
			||||||
 | 
					# Find your IDs with `id -u` and `id -g` on Linux/macOS.
 | 
				
			||||||
 | 
					# PUID=1000
 | 
				
			||||||
 | 
					# PGID=1000
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# File Mode Creation Mask (Umask)
 | 
				
			||||||
 | 
					# Controls the default permissions for newly created files.
 | 
				
			||||||
 | 
					# 022 (default): Files 644 (rw-r--r--), Dirs 755 (rwxr-xr-x)
 | 
				
			||||||
 | 
					# 002: Files 664 (rw-rw-r--), Dirs 775 (rwxrwxr-x) - Good for group sharing
 | 
				
			||||||
 | 
					# 007: Files 660 (rw-rw----), Dirs 770 (rwxrwx---) - More restrictive
 | 
				
			||||||
 | 
					# 077: Files 600 (rw-------), Dirs 700 (rwx------) - Most restrictive
 | 
				
			||||||
 | 
					# UMASK=022
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Custom footer links (comma-separated, format: "Link Text @ URL")
 | 
				
			||||||
 | 
					# Example: FOOTER_LINKS=My Site @ https://example.com, Another Link @ https://another.org
 | 
				
			||||||
 | 
					FOOTER_LINKS=
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										3
									
								
								.github/workflows/docker-publish.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										3
									
								
								.github/workflows/docker-publish.yml
									
									
									
									
										vendored
									
									
								
							@@ -4,6 +4,7 @@ on:
 | 
				
			|||||||
  push:
 | 
					  push:
 | 
				
			||||||
    branches:
 | 
					    branches:
 | 
				
			||||||
      - main # Trigger the workflow on pushes to the main branch
 | 
					      - main # Trigger the workflow on pushes to the main branch
 | 
				
			||||||
 | 
					      - dev  # Trigger the workflow on pushes to the dev branch
 | 
				
			||||||
 | 
					
 | 
				
			||||||
jobs:
 | 
					jobs:
 | 
				
			||||||
  build-and-push:
 | 
					  build-and-push:
 | 
				
			||||||
@@ -39,6 +40,8 @@ jobs:
 | 
				
			|||||||
          images: |
 | 
					          images: |
 | 
				
			||||||
            name=dumbwareio/dumbdrop
 | 
					            name=dumbwareio/dumbdrop
 | 
				
			||||||
          tags: |
 | 
					          tags: |
 | 
				
			||||||
 | 
					            # Add :dev tag for pushes to the dev branch
 | 
				
			||||||
 | 
					            type=raw,value=dev,enable=${{ github.ref == 'refs/heads/dev' }}
 | 
				
			||||||
            # the semantic versioning tags add "latest" when a version tag is present
 | 
					            # the semantic versioning tags add "latest" when a version tag is present
 | 
				
			||||||
            # but since version tags aren't being used (yet?) let's add "latest" anyway
 | 
					            # but since version tags aren't being used (yet?) let's add "latest" anyway
 | 
				
			||||||
            type=raw,value=latest
 | 
					            type=raw,value=latest
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										82
									
								
								Dockerfile
									
									
									
									
									
								
							
							
						
						
									
										82
									
								
								Dockerfile
									
									
									
									
									
								
							@@ -1,8 +1,17 @@
 | 
				
			|||||||
# Base stage for shared configurations
 | 
					# Base stage for shared configurations
 | 
				
			||||||
FROM node:20-alpine as base
 | 
					FROM node:20-alpine as base
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Install python and create virtual environment with minimal dependencies
 | 
					# Add user and group IDs as arguments with defaults
 | 
				
			||||||
RUN apk add --no-cache python3 py3-pip && \
 | 
					ARG PUID=1000
 | 
				
			||||||
 | 
					ARG PGID=1000
 | 
				
			||||||
 | 
					# Default umask (complement of 022 is 755 for dirs, 644 for files)
 | 
				
			||||||
 | 
					ARG UMASK=022
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Install necessary packages:
 | 
				
			||||||
 | 
					# - shadow: for user/group management (usermod, groupmod)
 | 
				
			||||||
 | 
					# - su-exec: lightweight sudo alternative
 | 
				
			||||||
 | 
					# - python3, pip: for apprise dependency
 | 
				
			||||||
 | 
					RUN apk add --no-cache shadow su-exec python3 py3-pip && \
 | 
				
			||||||
    python3 -m venv /opt/venv && \
 | 
					    python3 -m venv /opt/venv && \
 | 
				
			||||||
    rm -rf /var/cache/apk/*
 | 
					    rm -rf /var/cache/apk/*
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -14,51 +23,90 @@ RUN . /opt/venv/bin/activate && \
 | 
				
			|||||||
# Add virtual environment to PATH
 | 
					# Add virtual environment to PATH
 | 
				
			||||||
ENV PATH="/opt/venv/bin:$PATH"
 | 
					ENV PATH="/opt/venv/bin:$PATH"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Create group and user with fallback to prevent build failures
 | 
				
			||||||
 | 
					# We use the ARG values here, but with a fallback mechanism to avoid build failures
 | 
				
			||||||
 | 
					RUN addgroup -g ${PGID} nodeuser 2>/dev/null || \
 | 
				
			||||||
 | 
					    (echo "Group with GID ${PGID} already exists, creating with alternate GID" && addgroup nodeuser) && \
 | 
				
			||||||
 | 
					    adduser -u ${PUID} -G nodeuser -s /bin/sh -D nodeuser 2>/dev/null || \
 | 
				
			||||||
 | 
					    (echo "User with UID ${PUID} already exists, creating with alternate UID" && adduser -G nodeuser -s /bin/sh -D nodeuser)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
WORKDIR /usr/src/app
 | 
					WORKDIR /usr/src/app
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Set UMASK - this applies to processes run by the user created in this stage
 | 
				
			||||||
 | 
					# The entrypoint will also set it based on the ENV var at runtime.
 | 
				
			||||||
 | 
					RUN umask ${UMASK}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Dependencies stage
 | 
					# Dependencies stage
 | 
				
			||||||
FROM base as deps
 | 
					FROM base as deps
 | 
				
			||||||
 | 
					
 | 
				
			||||||
COPY package*.json ./
 | 
					# Change ownership early so npm cache is owned correctly
 | 
				
			||||||
 | 
					RUN chown nodeuser:nodeuser /usr/src/app
 | 
				
			||||||
 | 
					USER nodeuser
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					COPY --chown=nodeuser:nodeuser package*.json ./
 | 
				
			||||||
RUN npm ci --only=production && \
 | 
					RUN npm ci --only=production && \
 | 
				
			||||||
    # Remove npm cache
 | 
					    # Remove npm cache
 | 
				
			||||||
    npm cache clean --force
 | 
					    npm cache clean --force
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Switch back to root temporarily for steps requiring root privileges if any
 | 
				
			||||||
 | 
					# USER root
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Development stage
 | 
					# Development stage
 | 
				
			||||||
 | 
					# Note: Running dev stage as non-root might require adjustments
 | 
				
			||||||
 | 
					# depending on tooling (e.g., nodemon needing specific permissions)
 | 
				
			||||||
 | 
					# For now, let's keep it simpler and potentially run dev as root or figure out permissions later if needed.
 | 
				
			||||||
FROM deps as development
 | 
					FROM deps as development
 | 
				
			||||||
 | 
					USER root # Switch back to root for installing dev deps and copying files owned by host
 | 
				
			||||||
ENV NODE_ENV=development
 | 
					ENV NODE_ENV=development
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Install dev dependencies
 | 
					# Install dev dependencies
 | 
				
			||||||
 | 
					COPY --chown=nodeuser:nodeuser package*.json ./
 | 
				
			||||||
RUN npm install && \
 | 
					RUN npm install && \
 | 
				
			||||||
    npm cache clean --force
 | 
					    npm cache clean --force
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Create upload directory
 | 
					# Create and own upload/data directories
 | 
				
			||||||
RUN mkdir -p uploads
 | 
					# Using local_uploads based on project structure, also create standard uploads
 | 
				
			||||||
 | 
					RUN mkdir -p /usr/src/app/local_uploads /usr/src/app/uploads && \
 | 
				
			||||||
 | 
					    chown -R nodeuser:nodeuser /usr/src/app/local_uploads /usr/src/app/uploads
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Copy source with specific paths to avoid unnecessary files
 | 
					# Copy source code - ensure ownership is correct if needed later
 | 
				
			||||||
COPY src/ ./src/
 | 
					COPY --chown=nodeuser:nodeuser src/ ./src/
 | 
				
			||||||
COPY public/ ./public/
 | 
					COPY --chown=nodeuser:nodeuser public/ ./public/
 | 
				
			||||||
COPY __tests__/ ./__tests__/
 | 
					COPY --chown=nodeuser:nodeuser __tests__/ ./__tests__/
 | 
				
			||||||
COPY dev/ ./dev/
 | 
					COPY --chown=nodeuser:nodeuser dev/ ./dev/
 | 
				
			||||||
COPY .eslintrc.json .eslintignore ./
 | 
					COPY --chown=nodeuser:nodeuser .eslintrc.json .eslintignore .prettierrc nodemon.json ./
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Expose port
 | 
					# Expose port
 | 
				
			||||||
EXPOSE 3000
 | 
					EXPOSE 3000
 | 
				
			||||||
 | 
					
 | 
				
			||||||
CMD ["npm", "run", "dev"]
 | 
					# We won't switch user yet for dev, might cause issues with host mounts/debugging
 | 
				
			||||||
 | 
					# USER nodeuser
 | 
				
			||||||
 | 
					# CMD ["npm", "run", "dev"] # Default CMD, likely overridden by compose
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Production stage
 | 
					# Production stage
 | 
				
			||||||
FROM deps as production
 | 
					FROM deps as production
 | 
				
			||||||
 | 
					USER root # Switch back to root for creating dirs and copying files
 | 
				
			||||||
ENV NODE_ENV=production
 | 
					ENV NODE_ENV=production
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Create upload directory
 | 
					# Create and own upload/data directories
 | 
				
			||||||
RUN mkdir -p uploads
 | 
					RUN mkdir -p /usr/src/app/local_uploads /usr/src/app/uploads && \
 | 
				
			||||||
 | 
					    chown -R nodeuser:nodeuser /usr/src/app /usr/src/app/local_uploads /usr/src/app/uploads
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Copy only necessary source files
 | 
					# Copy only necessary source files and ensure ownership
 | 
				
			||||||
COPY src/ ./src/
 | 
					COPY --chown=nodeuser:nodeuser src/ ./src/
 | 
				
			||||||
COPY public/ ./public/
 | 
					COPY --chown=nodeuser:nodeuser public/ ./public/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Copy the entrypoint script and make it executable
 | 
				
			||||||
 | 
					COPY --chown=nodeuser:nodeuser src/scripts/entrypoint.sh /usr/local/bin/entrypoint.sh
 | 
				
			||||||
 | 
					RUN chmod +x /usr/local/bin/entrypoint.sh
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Expose port
 | 
					# Expose port
 | 
				
			||||||
EXPOSE 3000
 | 
					EXPOSE 3000
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Set the entrypoint
 | 
				
			||||||
 | 
					ENTRYPOINT ["/usr/local/bin/entrypoint.sh"]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Default command to run (passed to entrypoint)
 | 
				
			||||||
CMD ["npm", "start"]
 | 
					CMD ["npm", "start"]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# USER nodeuser # User switch happens inside entrypoint script using su-exec
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -95,7 +95,7 @@ For local development setup, troubleshooting, and advanced usage, see the dedica
 | 
				
			|||||||
| BASE_URL               | Base URL for the application                                     | http://localhost:PORT                   | No       |
 | 
					| BASE_URL               | Base URL for the application                                     | http://localhost:PORT                   | No       |
 | 
				
			||||||
| MAX_FILE_SIZE          | Maximum file size in MB                                          | 1024                                    | No       |
 | 
					| MAX_FILE_SIZE          | Maximum file size in MB                                          | 1024                                    | No       |
 | 
				
			||||||
| DUMBDROP_PIN           | PIN protection (4-10 digits)                                     | None                                    | No       |
 | 
					| DUMBDROP_PIN           | PIN protection (4-10 digits)                                     | None                                    | No       |
 | 
				
			||||||
| DUMBDROP_TITLE         | Site title displayed in header                                   | DumbDrop                                | No       |
 | 
					| DUMBDROP_TITLE         | Title displayed in the browser tab                       | DumbDrop                                | No       |
 | 
				
			||||||
| APPRISE_URL            | Apprise URL for notifications                                    | None                                    | No       |
 | 
					| APPRISE_URL            | Apprise URL for notifications                                    | None                                    | No       |
 | 
				
			||||||
| APPRISE_MESSAGE        | Notification message template                                    | New file uploaded {filename} ({size}), Storage used {storage} | No |
 | 
					| APPRISE_MESSAGE        | Notification message template                                    | New file uploaded {filename} ({size}), Storage used {storage} | No |
 | 
				
			||||||
| APPRISE_SIZE_UNIT      | Size unit for notifications (B, KB, MB, GB, TB, or Auto)         | Auto                                    | No       |
 | 
					| APPRISE_SIZE_UNIT      | Size unit for notifications (B, KB, MB, GB, TB, or Auto)         | Auto                                    | No       |
 | 
				
			||||||
@@ -104,6 +104,7 @@ For local development setup, troubleshooting, and advanced usage, see the dedica
 | 
				
			|||||||
| ALLOWED_IFRAME_ORIGINS | Comma-separated list of origins allowed to embed the app in an iframe | None                              | No       |
 | 
					| ALLOWED_IFRAME_ORIGINS | Comma-separated list of origins allowed to embed the app in an iframe | None                              | No       |
 | 
				
			||||||
| UPLOAD_DIR             | Directory for uploads (Docker/production; should be `/app/uploads` in container) | None (see LOCAL_UPLOAD_DIR fallback)    | No       |
 | 
					| UPLOAD_DIR             | Directory for uploads (Docker/production; should be `/app/uploads` in container) | None (see LOCAL_UPLOAD_DIR fallback)    | No       |
 | 
				
			||||||
| LOCAL_UPLOAD_DIR       | Directory for uploads (local dev, fallback: './local_uploads')   | ./local_uploads                         | No       |
 | 
					| LOCAL_UPLOAD_DIR       | Directory for uploads (local dev, fallback: './local_uploads')   | ./local_uploads                         | No       |
 | 
				
			||||||
 | 
					| FOOTER_LINKS           | Comma-separated custom footer links (Format: "Text @ URL")       | None                                    | No       |
 | 
				
			||||||
 | 
					
 | 
				
			||||||
- **UPLOAD_DIR** is used in Docker/production. If not set, LOCAL_UPLOAD_DIR is used for local development. If neither is set, the default is `./local_uploads`.
 | 
					- **UPLOAD_DIR** is used in Docker/production. If not set, LOCAL_UPLOAD_DIR is used for local development. If neither is set, the default is `./local_uploads`.
 | 
				
			||||||
- **Docker Note:** The Dockerfile now only creates the `uploads` directory inside the container. The host's `./local_uploads` is mounted to `/app/uploads` and should be managed on the host system.
 | 
					- **Docker Note:** The Dockerfile now only creates the `uploads` directory inside the container. The host's `./local_uploads` is mounted to `/app/uploads` and should be managed on the host system.
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -16,10 +16,22 @@ services:
 | 
				
			|||||||
            BASE_URL: http://localhost:3000  # The base URL for the application
 | 
					            BASE_URL: http://localhost:3000  # The base URL for the application
 | 
				
			||||||
            
 | 
					            
 | 
				
			||||||
            # Additional available environment variables (commented out with defaults)
 | 
					            # Additional available environment variables (commented out with defaults)
 | 
				
			||||||
 | 
					            # FOOTER_LINKS: "My Site @ https://example.com,Docs @ https://docs.example.com" # Custom footer links
 | 
				
			||||||
            # PORT: 3000  # Server port (default: 3000)
 | 
					            # PORT: 3000  # Server port (default: 3000)
 | 
				
			||||||
            # NODE_ENV: production  # Node environment (development/production)
 | 
					            # NODE_ENV: production  # Node environment (development/production)
 | 
				
			||||||
            # DEBUG: false  # Debug mode for verbose logging (default: false in production, true in development)
 | 
					            # DEBUG: false  # Debug mode for verbose logging (default: false in production, true in development)
 | 
				
			||||||
            # APPRISE_URL: ""  # Apprise notification URL for upload notifications (default: none)
 | 
					            # APPRISE_URL: ""  # Apprise notification URL for upload notifications (default: none)
 | 
				
			||||||
            # APPRISE_MESSAGE: "New file uploaded - {filename} ({size}), Storage used {storage}"  # Notification message template with placeholders: {filename}, {size}, {storage}
 | 
					            # APPRISE_MESSAGE: "New file uploaded - {filename} ({size}), Storage used {storage}"  # Notification message template with placeholders: {filename}, {size}, {storage}
 | 
				
			||||||
            # APPRISE_SIZE_UNIT: "Auto"  # Size unit for notifications (B, KB, MB, GB, TB, or Auto)
 | 
					            # APPRISE_SIZE_UNIT: "Auto"  # Size unit for notifications (B, KB, MB, GB, TB, or Auto)
 | 
				
			||||||
            # ALLOWED_EXTENSIONS: ".jpg,.jpeg,.png,.pdf,.doc,.docx,.txt"  # Comma-separated list of allowed file extensions (default: all allowed)
 | 
					            # ALLOWED_EXTENSIONS: ".jpg,.jpeg,.png,.pdf,.doc,.docx,.txt"  # Comma-separated list of allowed file extensions (default: all allowed)
 | 
				
			||||||
 | 
					            # PUID: 1000  # User ID for file ownership (default: 1000)
 | 
				
			||||||
 | 
					            # PGID: 1000  # Group ID for file ownership (default: 1000)
 | 
				
			||||||
 | 
					            # UMASK: "000"  # File permissions mask (default: 000)
 | 
				
			||||||
 | 
					        restart: unless-stopped
 | 
				
			||||||
 | 
					        # user: "${PUID}:${PGID}" # Don't set user here, entrypoint handles it
 | 
				
			||||||
 | 
					        # Consider adding healthcheck
 | 
				
			||||||
 | 
					        # healthcheck:
 | 
				
			||||||
 | 
					        #   test: ["CMD", "curl", "--fail", "http://localhost:3000/health"] # Assuming a /health endpoint exists
 | 
				
			||||||
 | 
					        #   interval: 30s
 | 
				
			||||||
 | 
					        #   timeout: 10s
 | 
				
			||||||
 | 
					        #   retries: 3
 | 
				
			||||||
@@ -49,6 +49,9 @@
 | 
				
			|||||||
        <div id="uploadProgress"></div>
 | 
					        <div id="uploadProgress"></div>
 | 
				
			||||||
        <div id="fileList" class="file-list"></div>
 | 
					        <div id="fileList" class="file-list"></div>
 | 
				
			||||||
        <button id="uploadButton" class="upload-button" style="display: none;">Upload Files</button>
 | 
					        <button id="uploadButton" class="upload-button" style="display: none;">Upload Files</button>
 | 
				
			||||||
 | 
					        <footer>
 | 
				
			||||||
 | 
					            {{FOOTER_CONTENT}}
 | 
				
			||||||
 | 
					        </footer>
 | 
				
			||||||
    </div>
 | 
					    </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    <script defer>
 | 
					    <script defer>
 | 
				
			||||||
@@ -962,9 +965,8 @@
 | 
				
			|||||||
            setTheme(next);
 | 
					            setTheme(next);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // Initialize theme
 | 
					        // Apply theme on initial load
 | 
				
			||||||
        const savedTheme = localStorage.getItem('theme') || 
 | 
					        const savedTheme = localStorage.getItem('theme') || 'system';
 | 
				
			||||||
            (window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light');
 | 
					 | 
				
			||||||
        setTheme(savedTheme);
 | 
					        setTheme(savedTheme);
 | 
				
			||||||
    </script>
 | 
					    </script>
 | 
				
			||||||
</body>
 | 
					</body>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -39,6 +39,7 @@ body {
 | 
				
			|||||||
    display: flex;
 | 
					    display: flex;
 | 
				
			||||||
    justify-content: center;
 | 
					    justify-content: center;
 | 
				
			||||||
    padding-top: 2rem;
 | 
					    padding-top: 2rem;
 | 
				
			||||||
 | 
					    padding-bottom: 80px;
 | 
				
			||||||
    color: var(--text-color);
 | 
					    color: var(--text-color);
 | 
				
			||||||
    transition: background-color 0.3s ease, color 0.3s ease;
 | 
					    transition: background-color 0.3s ease, color 0.3s ease;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -359,3 +360,47 @@ button:disabled {
 | 
				
			|||||||
        font-size: 1.125rem;
 | 
					        font-size: 1.125rem;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* Footer Styles */
 | 
				
			||||||
 | 
					footer {
 | 
				
			||||||
 | 
					    position: fixed;
 | 
				
			||||||
 | 
					    bottom: 10px;
 | 
				
			||||||
 | 
					    left: 0;
 | 
				
			||||||
 | 
					    right: 0;
 | 
				
			||||||
 | 
					    width: 100%;
 | 
				
			||||||
 | 
					    max-width: 600px;
 | 
				
			||||||
 | 
					    margin-left: auto;
 | 
				
			||||||
 | 
					    margin-right: auto;
 | 
				
			||||||
 | 
					    padding: 15px;
 | 
				
			||||||
 | 
					    text-align: center;
 | 
				
			||||||
 | 
					    font-size: 0.85rem;
 | 
				
			||||||
 | 
					    color: var(--text-color);
 | 
				
			||||||
 | 
					    opacity: 0.7;
 | 
				
			||||||
 | 
					    border-top: 1px solid var(--border-color);
 | 
				
			||||||
 | 
					    transition: background-color 0.3s ease, color 0.3s ease;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					footer a {
 | 
				
			||||||
 | 
					    color: var(--text-color);
 | 
				
			||||||
 | 
					    text-decoration: none;
 | 
				
			||||||
 | 
					    transition: opacity 0.2s ease;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					footer a:hover {
 | 
				
			||||||
 | 
					    opacity: 1;
 | 
				
			||||||
 | 
					    text-decoration: underline;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.footer-separator {
 | 
				
			||||||
 | 
					    margin: 0 0.5em;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@media (max-width: 480px) {
 | 
				
			||||||
 | 
					    footer {
 | 
				
			||||||
 | 
					        font-size: 0.75rem;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    .footer-separator {
 | 
				
			||||||
 | 
					        margin: 0 0.3em;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										58
									
								
								src/app.js
									
									
									
									
									
								
							
							
						
						
									
										58
									
								
								src/app.js
									
									
									
									
									
								
							@@ -46,20 +46,48 @@ app.use('/api/files', requirePin(config.pin), downloadLimiter, fileRoutes);
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
// Root route
 | 
					// Root route
 | 
				
			||||||
app.get('/', (req, res) => {
 | 
					app.get('/', (req, res) => {
 | 
				
			||||||
  // Check if the PIN is configured and the cookie exists
 | 
					  try {
 | 
				
			||||||
  if (config.pin && (!req.cookies?.DUMBDROP_PIN || !safeCompare(req.cookies.DUMBDROP_PIN, config.pin))) {
 | 
					    // Check if the PIN is configured and the cookie exists
 | 
				
			||||||
    return res.redirect('/login.html');
 | 
					    if (config.pin && (!req.cookies?.DUMBDROP_PIN || !safeCompare(req.cookies.DUMBDROP_PIN, config.pin))) {
 | 
				
			||||||
 | 
					      return res.redirect('/login.html');
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    let html = fs.readFileSync(path.join(__dirname, '../public', 'index.html'), 'utf8');
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    // Standard replacements
 | 
				
			||||||
 | 
					    html = html.replace(/{{SITE_TITLE}}/g, config.siteTitle);
 | 
				
			||||||
 | 
					    html = html.replace('{{AUTO_UPLOAD}}', config.autoUpload.toString());
 | 
				
			||||||
 | 
					    html = html.replace('{{MAX_RETRIES}}', config.clientMaxRetries.toString());
 | 
				
			||||||
 | 
					    // Ensure baseUrl has a trailing slash for correct asset linking
 | 
				
			||||||
 | 
					    const baseUrlWithSlash = config.baseUrl.endsWith('/') ? config.baseUrl : config.baseUrl + '/';
 | 
				
			||||||
 | 
					    html = html.replace(/{{BASE_URL}}/g, baseUrlWithSlash);
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    // Generate Footer Content
 | 
				
			||||||
 | 
					    let footerHtml = ''; // Initialize empty
 | 
				
			||||||
 | 
					    if (config.footerLinks && config.footerLinks.length > 0) {
 | 
				
			||||||
 | 
					        // If custom links exist, use only them
 | 
				
			||||||
 | 
					        footerHtml = config.footerLinks.map(link => 
 | 
				
			||||||
 | 
					            `<a href="${link.url}" target="_blank" rel="noopener noreferrer">${link.text}</a>`
 | 
				
			||||||
 | 
					        ).join('<span class="footer-separator"> | </span>');
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					        // Otherwise, use only the default static link
 | 
				
			||||||
 | 
					        footerHtml = `<span class="footer-static">Built by <a href="https://www.dumbware.io/" target="_blank" rel="noopener noreferrer">Dumbwareio</a></span>`;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    html = html.replace('{{FOOTER_CONTENT}}', footerHtml);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Inject demo banner if applicable
 | 
				
			||||||
 | 
					    html = injectDemoBanner(html);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Send the final processed HTML
 | 
				
			||||||
 | 
					    res.send(html);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  } catch (err) {
 | 
				
			||||||
 | 
					    logger.error(`Error processing index.html for / route: ${err.message}`);
 | 
				
			||||||
 | 
					    // Check if headers have already been sent before trying to send an error response
 | 
				
			||||||
 | 
					    if (!res.headersSent) {
 | 
				
			||||||
 | 
					      res.status(500).send('Error loading page');
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  
 | 
					 | 
				
			||||||
  let html = fs.readFileSync(path.join(__dirname, '../public', 'index.html'), 'utf8');
 | 
					 | 
				
			||||||
  html = html.replace(/{{SITE_TITLE}}/g, config.siteTitle);
 | 
					 | 
				
			||||||
  html = html.replace('{{AUTO_UPLOAD}}', config.autoUpload.toString());
 | 
					 | 
				
			||||||
  html = html.replace('{{MAX_RETRIES}}', config.clientMaxRetries.toString());
 | 
					 | 
				
			||||||
  // Ensure baseUrl has a trailing slash for correct asset linking
 | 
					 | 
				
			||||||
  const baseUrlWithSlash = config.baseUrl.endsWith('/') ? config.baseUrl : config.baseUrl + '/';
 | 
					 | 
				
			||||||
  html = html.replace(/{{BASE_URL}}/g, baseUrlWithSlash);
 | 
					 | 
				
			||||||
  html = injectDemoBanner(html);
 | 
					 | 
				
			||||||
  res.send(html);
 | 
					 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Login route
 | 
					// Login route
 | 
				
			||||||
@@ -108,6 +136,10 @@ app.use(express.static('public'));
 | 
				
			|||||||
// Error handling middleware
 | 
					// Error handling middleware
 | 
				
			||||||
app.use((err, req, res, next) => { // eslint-disable-line no-unused-vars
 | 
					app.use((err, req, res, next) => { // eslint-disable-line no-unused-vars
 | 
				
			||||||
  logger.error(`Unhandled error: ${err.message}`);
 | 
					  logger.error(`Unhandled error: ${err.message}`);
 | 
				
			||||||
 | 
					  // Check if headers have already been sent before trying to send an error response
 | 
				
			||||||
 | 
					  if (res.headersSent) {
 | 
				
			||||||
 | 
					    return next(err); // Pass error to default handler if headers sent
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
  res.status(500).json({ 
 | 
					  res.status(500).json({ 
 | 
				
			||||||
    message: 'Internal server error', 
 | 
					    message: 'Internal server error', 
 | 
				
			||||||
    error: process.env.NODE_ENV === 'development' ? err.message : undefined 
 | 
					    error: process.env.NODE_ENV === 'development' ? err.message : undefined 
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -12,7 +12,7 @@ console.log('Loaded ENV:', {
 | 
				
			|||||||
  NODE_ENV: process.env.NODE_ENV
 | 
					  NODE_ENV: process.env.NODE_ENV
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
const { validatePin } = require('../utils/security');
 | 
					const { validatePin } = require('../utils/security');
 | 
				
			||||||
const logger = require('../utils/logger');
 | 
					const logger = require('../utils/logger'); // Use the default logger instance
 | 
				
			||||||
const fs = require('fs');
 | 
					const fs = require('fs');
 | 
				
			||||||
const path = require('path');
 | 
					const path = require('path');
 | 
				
			||||||
const { version } = require('../../package.json'); // Get version from package.json
 | 
					const { version } = require('../../package.json'); // Get version from package.json
 | 
				
			||||||
@@ -66,15 +66,15 @@ function determineUploadDirectory() {
 | 
				
			|||||||
  let uploadDir;
 | 
					  let uploadDir;
 | 
				
			||||||
  if (process.env.UPLOAD_DIR) {
 | 
					  if (process.env.UPLOAD_DIR) {
 | 
				
			||||||
    uploadDir = process.env.UPLOAD_DIR;
 | 
					    uploadDir = process.env.UPLOAD_DIR;
 | 
				
			||||||
    logConfig(`Upload directory set from UPLOAD_DIR: ${uploadDir}`);
 | 
					    logger.info(`Upload directory set from UPLOAD_DIR: ${uploadDir}`);
 | 
				
			||||||
  } else if (process.env.LOCAL_UPLOAD_DIR) {
 | 
					  } else if (process.env.LOCAL_UPLOAD_DIR) {
 | 
				
			||||||
    uploadDir = process.env.LOCAL_UPLOAD_DIR;
 | 
					    uploadDir = process.env.LOCAL_UPLOAD_DIR;
 | 
				
			||||||
    logConfig(`Upload directory using LOCAL_UPLOAD_DIR fallback: ${uploadDir}`, 'warning');
 | 
					    logger.warn(`Upload directory using LOCAL_UPLOAD_DIR fallback: ${uploadDir}`);
 | 
				
			||||||
  } else {
 | 
					  } else {
 | 
				
			||||||
    uploadDir = './local_uploads';
 | 
					    uploadDir = './local_uploads';
 | 
				
			||||||
    logConfig(`Upload directory using default fallback: ${uploadDir}`, 'warning');
 | 
					    logger.warn(`Upload directory using default fallback: ${uploadDir}`);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  logConfig(`Final upload directory path: ${require('path').resolve(uploadDir)}`);
 | 
					  logger.info(`Final upload directory path: ${require('path').resolve(uploadDir)}`);
 | 
				
			||||||
  return uploadDir;
 | 
					  return uploadDir;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -95,12 +95,12 @@ function ensureLocalUploadDirExists(uploadDir) {
 | 
				
			|||||||
  try {
 | 
					  try {
 | 
				
			||||||
    if (!fs.existsSync(uploadDir)) {
 | 
					    if (!fs.existsSync(uploadDir)) {
 | 
				
			||||||
      fs.mkdirSync(uploadDir, { recursive: true });
 | 
					      fs.mkdirSync(uploadDir, { recursive: true });
 | 
				
			||||||
      logConfig(`Created local upload directory: ${uploadDir}`);
 | 
					      logger.info(`Created local upload directory: ${uploadDir}`);
 | 
				
			||||||
    } else {
 | 
					    } else {
 | 
				
			||||||
      logConfig(`Local upload directory exists: ${uploadDir}`);
 | 
					      logger.info(`Local upload directory exists: ${uploadDir}`);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  } catch (err) {
 | 
					  } catch (err) {
 | 
				
			||||||
    logConfig(`Failed to create local upload directory: ${uploadDir}. Error: ${err.message}`, 'warning');
 | 
					    logger.warn(`Failed to create local upload directory: ${uploadDir}. Error: ${err.message}`);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -108,6 +108,34 @@ function ensureLocalUploadDirExists(uploadDir) {
 | 
				
			|||||||
const resolvedUploadDir = determineUploadDirectory();
 | 
					const resolvedUploadDir = determineUploadDirectory();
 | 
				
			||||||
ensureLocalUploadDirExists(resolvedUploadDir);
 | 
					ensureLocalUploadDirExists(resolvedUploadDir);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Function to parse the FOOTER_LINKS environment variable
 | 
				
			||||||
 | 
					 * @param {string} linksString - The input string containing links
 | 
				
			||||||
 | 
					 * @returns {Array} - An array of objects containing text and URL
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					const parseFooterLinks = (linksString) => {
 | 
				
			||||||
 | 
					  if (!linksString) {
 | 
				
			||||||
 | 
					    return [];
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  return linksString.split(',')
 | 
				
			||||||
 | 
					    .map(linkPair => {
 | 
				
			||||||
 | 
					      const parts = linkPair.split('@').map(part => part.trim());
 | 
				
			||||||
 | 
					      if (parts.length === 2 && parts[0] && parts[1]) {
 | 
				
			||||||
 | 
					        // Basic URL validation (starts with http/https)
 | 
				
			||||||
 | 
					        if (parts[1].startsWith('http://') || parts[1].startsWith('https://')) {
 | 
				
			||||||
 | 
					           return { text: parts[0], url: parts[1] };
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					           logger.warn(`Invalid URL format in FOOTER_LINKS for "${parts[0]}": ${parts[1]}. Skipping.`);
 | 
				
			||||||
 | 
					           return null;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      } else {
 | 
				
			||||||
 | 
					        logger.warn(`Invalid format in FOOTER_LINKS: "${linkPair}". Expected "Text @ URL". Skipping.`);
 | 
				
			||||||
 | 
					        return null;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					    .filter(link => link !== null); // Remove null entries from invalid formats
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * Application configuration
 | 
					 * Application configuration
 | 
				
			||||||
 * Loads and validates environment variables
 | 
					 * Loads and validates environment variables
 | 
				
			||||||
@@ -178,7 +206,18 @@ const config = {
 | 
				
			|||||||
   * Set via DUMBDROP_TITLE in .env
 | 
					   * Set via DUMBDROP_TITLE in .env
 | 
				
			||||||
   */
 | 
					   */
 | 
				
			||||||
  siteTitle: process.env.DUMBDROP_TITLE || DEFAULT_SITE_TITLE,
 | 
					  siteTitle: process.env.DUMBDROP_TITLE || DEFAULT_SITE_TITLE,
 | 
				
			||||||
 | 
					  /**
 | 
				
			||||||
 | 
					   * Parsed custom footer links
 | 
				
			||||||
 | 
					   * Set via FOOTER_LINKS in .env (e.g., "Link 1 @ URL1, Link 2 @ URL2")
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
 | 
					  _footerLinksRaw: (() => {
 | 
				
			||||||
 | 
					    const rawValue = process.env.FOOTER_LINKS;
 | 
				
			||||||
 | 
					    console.log(`[CONFIG] Raw FOOTER_LINKS from process.env: '${rawValue}'`); 
 | 
				
			||||||
 | 
					    return rawValue; // Keep the original flow, just log
 | 
				
			||||||
 | 
					  })(),
 | 
				
			||||||
 | 
					  footerLinks: parseFooterLinks(process.env.FOOTER_LINKS),
 | 
				
			||||||
  
 | 
					  
 | 
				
			||||||
 | 
					  // =====================
 | 
				
			||||||
  // =====================
 | 
					  // =====================
 | 
				
			||||||
  // =====================
 | 
					  // =====================
 | 
				
			||||||
  // Notification settings
 | 
					  // Notification settings
 | 
				
			||||||
@@ -227,9 +266,8 @@ const config = {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
    const retries = parseInt(envValue, 10);
 | 
					    const retries = parseInt(envValue, 10);
 | 
				
			||||||
    if (isNaN(retries) || retries < 0) {
 | 
					    if (isNaN(retries) || retries < 0) {
 | 
				
			||||||
      logConfig(
 | 
					      logger.warn(
 | 
				
			||||||
        `Invalid CLIENT_MAX_RETRIES value: "${envValue}". Using default: ${defaultValue}`,
 | 
					        `Invalid CLIENT_MAX_RETRIES value: "${envValue}". Using default: ${defaultValue}`,
 | 
				
			||||||
        'warning',
 | 
					 | 
				
			||||||
      );
 | 
					      );
 | 
				
			||||||
      return logAndReturn('CLIENT_MAX_RETRIES', defaultValue, true);
 | 
					      return logAndReturn('CLIENT_MAX_RETRIES', defaultValue, true);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										110
									
								
								src/scripts/entrypoint.sh
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										110
									
								
								src/scripts/entrypoint.sh
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,110 @@
 | 
				
			|||||||
 | 
					#!/bin/sh
 | 
				
			||||||
 | 
					# Simple entrypoint script to manage user permissions and execute CMD
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Exit immediately if a command exits with a non-zero status.
 | 
				
			||||||
 | 
					set -e
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Function to log messages
 | 
				
			||||||
 | 
					log_info() {
 | 
				
			||||||
 | 
					    echo "[INFO] Entrypoint: $1"
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					log_warning() {
 | 
				
			||||||
 | 
					    echo "[WARN] Entrypoint: $1"
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					log_error() {
 | 
				
			||||||
 | 
					    echo "[ERROR] Entrypoint: $1" >&2
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					log_info "Starting entrypoint script..."
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Default user/group/umask values
 | 
				
			||||||
 | 
					DEFAULT_UID=1000
 | 
				
			||||||
 | 
					DEFAULT_GID=1000
 | 
				
			||||||
 | 
					DEFAULT_UMASK=022
 | 
				
			||||||
 | 
					# Default upload directory if not set by user (should align with Dockerfile/compose)
 | 
				
			||||||
 | 
					DEFAULT_UPLOAD_DIR="/usr/src/app/local_uploads"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Check if PUID or PGID environment variables are set by the user
 | 
				
			||||||
 | 
					if [ -z "${PUID}" ] && [ -z "${PGID}" ]; then
 | 
				
			||||||
 | 
					    # --- Run as Root --- 
 | 
				
			||||||
 | 
					    log_info "PUID/PGID not set, running as root."
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    # Set umask (use UMASK env var if provided, otherwise default)
 | 
				
			||||||
 | 
					    CURRENT_UMASK=${UMASK:-$DEFAULT_UMASK}
 | 
				
			||||||
 | 
					    log_info "Setting umask to ${CURRENT_UMASK}"
 | 
				
			||||||
 | 
					    umask "${CURRENT_UMASK}"
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    # Execute the command passed to the entrypoint as root
 | 
				
			||||||
 | 
					    log_info "Executing command as root: $@"
 | 
				
			||||||
 | 
					    exec "$@"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					else
 | 
				
			||||||
 | 
					    # --- Run as Custom User (nodeuser with adjusted UID/GID) ---
 | 
				
			||||||
 | 
					    log_info "PUID/PGID set, configuring user 'nodeuser'..."
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    # Use provided UID/GID or default if only one is set
 | 
				
			||||||
 | 
					    CURRENT_UID=${PUID:-$DEFAULT_UID} 
 | 
				
			||||||
 | 
					    CURRENT_GID=${PGID:-$DEFAULT_GID}
 | 
				
			||||||
 | 
					    CURRENT_UMASK=${UMASK:-$DEFAULT_UMASK}
 | 
				
			||||||
 | 
					    # Read the upload directory from ENV var or use default
 | 
				
			||||||
 | 
					    TARGET_UPLOAD_DIR=${UPLOAD_DIR:-$DEFAULT_UPLOAD_DIR}
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    log_info "Target UID: ${CURRENT_UID}, GID: ${CURRENT_GID}, UMASK: ${CURRENT_UMASK}"
 | 
				
			||||||
 | 
					    log_info "Target Upload Dir: ${TARGET_UPLOAD_DIR}"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Check if user/group exists (should exist from Dockerfile)
 | 
				
			||||||
 | 
					    if ! getent group nodeuser > /dev/null 2>&1; then
 | 
				
			||||||
 | 
					        log_warning "Group 'nodeuser' not found, creating with GID ${CURRENT_GID}..."
 | 
				
			||||||
 | 
					        addgroup -g "${CURRENT_GID}" nodeuser
 | 
				
			||||||
 | 
					    else
 | 
				
			||||||
 | 
					        EXISTING_GID=$(getent group nodeuser | cut -d: -f3)
 | 
				
			||||||
 | 
					        if [ "${EXISTING_GID}" != "${CURRENT_GID}" ]; then
 | 
				
			||||||
 | 
					            log_info "Updating 'nodeuser' group GID from ${EXISTING_GID} to ${CURRENT_GID}..."
 | 
				
			||||||
 | 
					            groupmod -o -g "${CURRENT_GID}" nodeuser
 | 
				
			||||||
 | 
					        fi
 | 
				
			||||||
 | 
					    fi
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if ! getent passwd nodeuser > /dev/null 2>&1; then
 | 
				
			||||||
 | 
					        log_warning "User 'nodeuser' not found, creating with UID ${CURRENT_UID}..."
 | 
				
			||||||
 | 
					        adduser -u "${CURRENT_UID}" -G nodeuser -s /bin/sh -D nodeuser
 | 
				
			||||||
 | 
					    else
 | 
				
			||||||
 | 
					        EXISTING_UID=$(getent passwd nodeuser | cut -d: -f3)
 | 
				
			||||||
 | 
					        if [ "${EXISTING_UID}" != "${CURRENT_UID}" ]; then
 | 
				
			||||||
 | 
					            log_info "Updating 'nodeuser' user UID from ${EXISTING_UID} to ${CURRENT_UID}..."
 | 
				
			||||||
 | 
					            usermod -o -u "${CURRENT_UID}" nodeuser
 | 
				
			||||||
 | 
					        fi
 | 
				
			||||||
 | 
					    fi
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    # Ensure the base application directory ownership is correct
 | 
				
			||||||
 | 
					    log_info "Ensuring ownership of /usr/src/app..."
 | 
				
			||||||
 | 
					    chown -R nodeuser:nodeuser /usr/src/app || log_warning "Could not chown /usr/src/app"
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    # Ensure the target upload directory exists and has correct ownership
 | 
				
			||||||
 | 
					    if [ -n "${TARGET_UPLOAD_DIR}" ]; then
 | 
				
			||||||
 | 
					        if [ ! -d "${TARGET_UPLOAD_DIR}" ]; then
 | 
				
			||||||
 | 
					            log_info "Creating directory: ${TARGET_UPLOAD_DIR}"
 | 
				
			||||||
 | 
					            # Use -p to create parent directories as needed
 | 
				
			||||||
 | 
					            mkdir -p "${TARGET_UPLOAD_DIR}"
 | 
				
			||||||
 | 
					            # Chown after creation
 | 
				
			||||||
 | 
					            chown nodeuser:nodeuser "${TARGET_UPLOAD_DIR}" || log_warning "Could not chown ${TARGET_UPLOAD_DIR}"
 | 
				
			||||||
 | 
					        else
 | 
				
			||||||
 | 
					            # Directory exists, ensure ownership
 | 
				
			||||||
 | 
					            log_info "Ensuring ownership of ${TARGET_UPLOAD_DIR}..."
 | 
				
			||||||
 | 
					            chown -R nodeuser:nodeuser "${TARGET_UPLOAD_DIR}" || log_warning "Could not chown ${TARGET_UPLOAD_DIR}"
 | 
				
			||||||
 | 
					        fi
 | 
				
			||||||
 | 
					    else
 | 
				
			||||||
 | 
					         log_warning "UPLOAD_DIR variable is not set or is empty, skipping ownership check for upload directory."
 | 
				
			||||||
 | 
					    fi
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    # Set the umask
 | 
				
			||||||
 | 
					    log_info "Setting umask to ${CURRENT_UMASK}"
 | 
				
			||||||
 | 
					    umask "${CURRENT_UMASK}"
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    # Execute the command passed to the entrypoint using su-exec to drop privileges
 | 
				
			||||||
 | 
					    log_info "Executing command as nodeuser (${CURRENT_UID}:${CURRENT_GID}): $@"
 | 
				
			||||||
 | 
					    exec su-exec nodeuser "$@"
 | 
				
			||||||
 | 
					fi
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					log_info "Entrypoint script finished (should not reach here if exec worked)." 
 | 
				
			||||||
		Reference in New Issue
	
	Block a user