diff --git a/docker-compose.prod.yml b/docker-compose.prod.yml new file mode 100644 index 00000000..7a2d4aa7 --- /dev/null +++ b/docker-compose.prod.yml @@ -0,0 +1,122 @@ +services: + db: + image: postgres:12 + environment: + - POSTGRES_DB=wrolpi + - POSTGRES_PASSWORD=wrolpi + ports: + - ${DB_PORT-5432}:5432 + healthcheck: + test: ['CMD-SHELL', 'pg_isready -U postgres'] + interval: 10s + volumes: + - pg_data:/var/lib/postgresql/data + + api: + image: lrnselfreliance/wrolpi:wrolpi-api-latest + depends_on: + - db + ports: + - ${API_PORT-8081}:8081 + volumes: + - media_data:/media/wrolpi + user: '${UID-1000}:${GID-1000}' + healthcheck: + test: ['CMD-SHELL', 'curl http://api:8081/api/echo'] + interval: 2m + + archive: + image: lrnselfreliance/wrolpi:wrolpi-archive-latest + ports: + - ${ARCHIVE_PORT-8083}:8080 + healthcheck: + test: ['CMD-SHELL', 'curl http://archive:8080/'] + interval: 2m + + app: + image: lrnselfreliance/wrolpi:wrolpi-app-latest + depends_on: + - api + ports: + - ${APP_PORT-3000}:3000 + environment: + - WDS_SOCKET_PORT=0 + healthcheck: + test: ['CMD-SHELL', 'curl http://app:3000/'] + interval: 1m + user: '${UID-1000}:${GID-1000}' + command: 'serve -s build -l 3000' + + map: + image: lrnselfreliance/wrolpi:wrolpi-map-latest + volumes: + - openstreetmap_data:/var/lib/postgresql/12/main + - openstreetmap_rendered_tiles:/var/lib/mod_tile + command: 'run' + shm_size: 1g + healthcheck: + test: ['CMD-SHELL', 'curl http://map:80/'] + interval: 1m + + map_https: + image: lrnselfreliance/wrolpi:wrolpi-map_https-latest + depends_on: + - map + ports: + - ${MAP_HTTPS_PORT-8084}:443 + healthcheck: + test: ['CMD-SHELL', 'curl http://map_https:443/'] + interval: 1m + + web: + image: lrnselfreliance/wrolpi:wrolpi-web-latest + depends_on: + - app + ports: + - ${WEB_PORT-8080}:80 + - ${WEB_HTTPS_PORT-8443}:443 + volumes: + - media_data:/opt/media + healthcheck: + test: ['CMD-SHELL', 'curl http://web:80/'] + interval: 1m + + zim: + image: lrnselfreliance/wrolpi:wrolpi-zim-latest + volumes: + - media_data:/media/wrolpi + healthcheck: + test: ['CMD-SHELL', 'curl http://zim:80/'] + interval: 1m + + zim_https: + image: lrnselfreliance/wrolpi:wrolpi-zim_https-latest + depends_on: + - zim + ports: + - ${ZIM_HTTPS_PORT-8085}:443 + healthcheck: + test: ['CMD-SHELL', 'curl http://zim_https:443/'] + interval: 1m + + help: + image: lrnselfreliance/wrolpi:wrolpi-help-latest + healthcheck: + test: ['CMD-SHELL', 'curl http://help:8086/'] + interval: 1m + + help_https: + image: lrnselfreliance/wrolpi:wrolpi-help_https-latest + depends_on: + - help + ports: + - ${HELP_HTTPS_PORT-8086}:443 + healthcheck: + test: ['CMD-SHELL', 'curl http://help_https:443/'] + interval: 1m + +volumes: + pg_data: + media_data: + openstreetmap_data: + openstreetmap_rendered_tiles: \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml index fd007f91..274611c2 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,5 +1,4 @@ # Development containers, these should only be used if you are developing. -version: '3' services: db: # https://github.com/docker-library/docs/tree/master/postgres image: postgres:12 diff --git a/docker/api/Dockerfile b/docker/api/Dockerfile index 1b267f74..bbb38ba7 100644 --- a/docker/api/Dockerfile +++ b/docker/api/Dockerfile @@ -2,22 +2,26 @@ FROM python:3.11-buster ENV DOCKER=true WORKDIR /opt/wrolpi -# Update dependencies for the services install. -RUN apt update -# ffmpeg for Videos -RUN apt-get install -y ffmpeg catdoc sysstat aria2 -RUN ffmpeg -version -# yt-dlp needs cache directory +# Update dependencies and install required packages. +RUN apt-get update && \ + apt-get install -y ffmpeg catdoc sysstat aria2 && \ + ffmpeg -version && \ + apt-get clean && \ + rm -rf /var/lib/apt/lists/* + +# Create cache directory for yt-dlp. RUN mkdir /.cache && chown -R 1000:1000 /.cache -# Install dependencies. +# Install Python dependencies. COPY requirements.txt /opt/wrolpi/requirements.txt -RUN pip3 install -r /opt/wrolpi/requirements.txt +RUN pip3 install --no-cache-dir -r /opt/wrolpi/requirements.txt -# Install WROLPi. +# Copy WROLPi application files. COPY main.py /opt/wrolpi/ COPY wrolpi /opt/wrolpi/wrolpi COPY modules /opt/wrolpi/modules +COPY alembic /opt/wrolpi/alembic +COPY alembic.ini /opt/wrolpi/alembic.ini -ENTRYPOINT [ "python3", "-OO", "/opt/wrolpi/main.py"] -CMD ["-v", "api", "--host", "0.0.0.0"] +ENTRYPOINT ["python3", "-OO", "/opt/wrolpi/main.py"] +CMD ["-v", "api", "--host", "0.0.0.0"] \ No newline at end of file diff --git a/docker/app/Dockerfile b/docker/app/Dockerfile index 1fe35e55..ed99f02b 100644 --- a/docker/app/Dockerfile +++ b/docker/app/Dockerfile @@ -1,21 +1,26 @@ -# base image -FROM node:18-bullseye - +# Build stage +FROM node:18-alpine AS builder WORKDIR /app -ENV PATH /app/node_modules/.bin:$PATH +ENV PATH=/app/node_modules/.bin:$PATH -# install and cache app dependencies -USER root -RUN npm install -g serve -COPY app/public /app/public -COPY app/src /app/src -COPY app/package.json app/package-lock.json /app/ -# "node" user will run the app after build. -RUN chown -R node:node /app +# Copy package files and install production dependencies +COPY app/package.json app/package-lock.json ./ +RUN npm ci --omit=dev -# Install dependencies as "node", this is to avoid changing ownership of many files. -USER node -RUN npm install +# Copy source files and build +COPY app/public ./public +COPY app/src ./src +RUN npm run build -# Run the production build -CMD ["npm", "run", "start"] +# Runtime stage +FROM nginx:alpine +WORKDIR /usr/share/nginx/html + +# Copy static build from builder stage +COPY --from=builder /app/build ./ + +# Expose port +EXPOSE 80 + +# Run Nginx +CMD ["nginx", "-g", "daemon off;"] \ No newline at end of file diff --git a/docker/archive/Dockerfile b/docker/archive/Dockerfile index c39c0c56..1168779f 100644 --- a/docker/archive/Dockerfile +++ b/docker/archive/Dockerfile @@ -1,21 +1,29 @@ FROM browserless/chrome:1.61.1-chrome-stable - -USER root WORKDIR /app - -# Install Python for main.py ENV PYTHONUNBUFFERED=1 -RUN apt update && apt install -y python3 python3-dev python3-venv curl && ln -sf python3 /usr/bin/python -RUN python3 -m venv /app/venv -RUN . /app/venv/bin/activate -# Install a newer singlefile and Readability. +# Switch to root for package installation +USER root + +# Install Python and dependencies +RUN apt-get update && \ + apt-get install -y python3 python3-dev python3-venv curl && \ + ln -sf python3 /usr/bin/python && \ + apt-get clean && \ + rm -rf /var/lib/apt/lists/* + +# Set up virtual environment +RUN python3 -m venv /app/venv + +# Install Node.js dependencies RUN npm install -g single-file-cli@2.0.73 readability-extractor@0.0.6 -# Install the Python wrapper and it's requirements. +# Install Python dependencies COPY docker/archive/requirements.txt /app/requirements.txt -RUN /app/venv/bin/pip3 install -r /app/requirements.txt -COPY docker/archive /app +RUN /app/venv/bin/pip3 install --no-cache-dir -r /app/requirements.txt -# Launch Archive app in the virtual environment. -ENTRYPOINT [ "/app/venv/bin/python3", "/app/main.py"] +# Copy application files +COPY docker/archive/main.py /app/ + +# Launch Archive app in virtual environment +ENTRYPOINT ["/app/venv/bin/python3", "/app/main.py"] \ No newline at end of file diff --git a/docker/build.sh b/docker/build.sh new file mode 100755 index 00000000..4c37f67d --- /dev/null +++ b/docker/build.sh @@ -0,0 +1,65 @@ +#!/bin/bash +set -e + +# Determine build mode from argument +BUILD_MODE="parallel" +if [ "$1" == "--series" ]; then + BUILD_MODE="series" +elif [ "$1" != "" ] && [ "$1" != "--parallel" ]; then + echo "Usage: $0 [--parallel | --series]" + echo " --parallel: Build images in parallel (default)" + echo " --series: Build images in series" + exit 1 +fi + +# Determine project root (parent of docker/ if in docker/, or current dir if in project root) +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +if [[ "$SCRIPT_DIR" == */docker ]]; then + PROJECT_ROOT="$(dirname "$SCRIPT_DIR")" +else + PROJECT_ROOT="$SCRIPT_DIR" +fi +DOCKER_DIR="$PROJECT_ROOT/docker" + +# Verify docker directory exists +if [ ! -d "$DOCKER_DIR" ]; then + echo "Error: docker/ directory not found in $PROJECT_ROOT" + exit 1 +fi + +# Function to build a Docker image +build_image() { + local tag=$1 + local dockerfile=$2 + echo "Building $tag..." + docker build -t "$tag" -f "$dockerfile" "$PROJECT_ROOT" +} + +# List of images and their Dockerfiles +IMAGES=( + "lrnselfreliance/wrolpi:wrolpi-api-latest $DOCKER_DIR/api/Dockerfile" + "lrnselfreliance/wrolpi:wrolpi-archive-latest $DOCKER_DIR/archive/Dockerfile" + "lrnselfreliance/wrolpi:wrolpi-app-latest $DOCKER_DIR/app/Dockerfile" + "lrnselfreliance/wrolpi:wrolpi-web-latest $DOCKER_DIR/web/Dockerfile" + "lrnselfreliance/wrolpi:wrolpi-zim-latest $DOCKER_DIR/zim/Dockerfile" + "lrnselfreliance/wrolpi:wrolpi-help-latest $DOCKER_DIR/help/Dockerfile" + "lrnselfreliance/wrolpi:wrolpi-map_https-latest $DOCKER_DIR/https_proxy/Dockerfile" + "lrnselfreliance/wrolpi:wrolpi-zim_https-latest $DOCKER_DIR/https_proxy/Dockerfile" + "lrnselfreliance/wrolpi:wrolpi-help_https-latest $DOCKER_DIR/https_proxy/Dockerfile" +) + +# Build images based on mode +if [ "$BUILD_MODE" == "parallel" ]; then + for image in "${IMAGES[@]}"; do + read -r tag dockerfile <<< "$image" + build_image "$tag" "$dockerfile" & + done + wait +else + for image in "${IMAGES[@]}"; do + read -r tag dockerfile <<< "$image" + build_image "$tag" "$dockerfile" + done +fi + +echo "All images built successfully!" \ No newline at end of file diff --git a/docker/help/Dockerfile b/docker/help/Dockerfile index 631f79f6..4c20bb52 100644 --- a/docker/help/Dockerfile +++ b/docker/help/Dockerfile @@ -2,8 +2,12 @@ FROM python:3.11-bookworm ENV DOCKER=true WORKDIR /opt/wrolpi-help +# Copy application files. COPY docker/help/wrolpi-help /opt/wrolpi-help -RUN pip3 install -r /opt/wrolpi-help/requirements.txt +# Install dependencies. +RUN pip3 install --no-cache-dir -r /opt/wrolpi-help/requirements.txt + +# Serve MkDocs documentation. ENTRYPOINT ["mkdocs"] -CMD ["serve", "-a", "0.0.0.0:80"] +CMD ["serve", "-a", "0.0.0.0:8086"] \ No newline at end of file diff --git a/docker/https_proxy/Dockerfile b/docker/https_proxy/Dockerfile index 1c00a4a3..28a5b71a 100644 --- a/docker/https_proxy/Dockerfile +++ b/docker/https_proxy/Dockerfile @@ -1,12 +1,16 @@ -# This Dockerfile is used as a reverse proxy to serve a HTTPS nieve service with HTTPS. -FROM nginx:latest as https_proxy +FROM nginx:alpine +WORKDIR /etc/nginx -# Create a self-signed cert and key pair for HTTPS. -RUN openssl genrsa -out /cert.key 2048 -RUN openssl req -new -x509 -nodes -key /cert.key -out /cert.crt -days 3650 \ - -subj "/C=US/ST=State/L=City/O=Org/OU=WROLPi/CN=wrolpi.local" -RUN chmod 777 /cert.key /cert.crt +# Generate self-signed certificate and key. +RUN apk add --no-cache openssl && \ + openssl genrsa -out /cert.key 2048 && \ + openssl req -new -x509 -nodes -key /cert.key -out /cert.crt -days 3650 \ + -subj "/C=US/ST=State/L=City/O=Org/OU=WROLPi/CN=wrolpi.local" && \ + chmod 600 /cert.key /cert.crt && \ + apk del openssl -# Copy Nginx configuration +# Copy Nginx configuration. COPY docker/https_proxy/nginx.proxy.conf /etc/nginx/nginx.conf -CMD ["nginx", "-g", "daemon off;"] + +# Run Nginx in foreground. +CMD ["nginx", "-g", "daemon off;"] \ No newline at end of file diff --git a/docker/push.sh b/docker/push.sh new file mode 100755 index 00000000..439f9d95 --- /dev/null +++ b/docker/push.sh @@ -0,0 +1,38 @@ +#!/bin/bash +set -e + +# List of images to push +IMAGES=( + "lrnselfreliance/wrolpi:wrolpi-api-latest" + "lrnselfreliance/wrolpi:wrolpi-archive-latest" + "lrnselfreliance/wrolpi:wrolpi-app-latest" + "lrnselfreliance/wrolpi:wrolpi-web-latest" + "lrnselfreliance/wrolpi:wrolpi-zim-latest" + "lrnselfreliance/wrolpi:wrolpi-help-latest" + "lrnselfreliance/wrolpi:wrolpi-map_https-latest" + "lrnselfreliance/wrolpi:wrolpi-zim_https-latest" + "lrnselfreliance/wrolpi:wrolpi-help_https-latest" +) + +# Check if Docker Hub credentials are available +if ! docker system info --format '{{.IndexServerAddress}}' 2>/dev/null | grep -q "docker.io"; then + echo "Error: Not logged in to Docker Hub or credentials not accessible." + echo "Attempting to log in to Docker Hub..." + docker login -u lrnselfreliance + if [ $? -ne 0 ]; then + echo "Error: Docker login failed. Please run 'docker login -u lrnselfreliance' manually and enter your password." + exit 1 + fi + echo "Docker login successful." +fi + +# Push each image +for image in "${IMAGES[@]}"; do + echo "Pushing $image..." + docker push "$image" || { + echo "Error: Failed to push $image" + exit 1 + } +done + +echo "All images pushed successfully!" \ No newline at end of file diff --git a/docker/web/Dockerfile b/docker/web/Dockerfile index c2b55dba..57b5439c 100644 --- a/docker/web/Dockerfile +++ b/docker/web/Dockerfile @@ -1,11 +1,21 @@ -FROM nginx:latest +FROM nginx:alpine +WORKDIR /etc/nginx -# Create a self-signed cert and key pair for HTTPS. -RUN openssl genrsa -out /cert.key 2048 -RUN openssl req -new -x509 -nodes -key /cert.key -out /cert.crt -days 3650 \ - -subj "/C=US/ST=State/L=City/O=Org/OU=WROLPi/CN=wrolpi.local" -RUN chmod 777 /cert.key /cert.crt +# Generate self-signed certificate and key. +RUN apk add --no-cache openssl && \ + openssl genrsa -out /cert.key 2048 && \ + openssl req -new -x509 -nodes -key /cert.key -out /cert.crt -days 3650 \ + -subj "/C=US/ST=State/L=City/O=Org/OU=WROLPi/CN=wrolpi.local" && \ + chmod 600 /cert.key /cert.crt && \ + apk del openssl +# Remove default Nginx config. RUN rm /etc/nginx/conf.d/default.conf + +# Copy Nginx configuration and assets. COPY docker/web/nginx.dev.conf /etc/nginx/nginx.conf COPY docker/web/wrolpi.dev.conf /etc/nginx/conf.d/wrolpi.conf +COPY icon.ico icon.png /etc/nginx/ + +# Run Nginx in foreground. +CMD ["nginx", "-g", "daemon off;"] \ No newline at end of file diff --git a/docker/zim/Dockerfile b/docker/zim/Dockerfile index 22a52b9f..b218e04f 100644 --- a/docker/zim/Dockerfile +++ b/docker/zim/Dockerfile @@ -1,7 +1,15 @@ -FROM debian:12 +FROM debian:12-slim ENV DOCKER=true +WORKDIR /app -RUN apt update && apt-get install -y kiwix kiwix-tools +# Install kiwix and kiwix-tools. +RUN apt-get update && \ + apt-get install -y kiwix kiwix-tools && \ + apt-get clean && \ + rm -rf /var/lib/apt/lists/* + +# Copy entrypoint script and make it executable. COPY docker/zim/* / +RUN chmod +x /entrypoint.sh ENTRYPOINT ["/entrypoint.sh"] \ No newline at end of file diff --git a/scripts/dev_build.sh b/scripts/dev_build.sh index bafeb3af..264acdee 100755 --- a/scripts/dev_build.sh +++ b/scripts/dev_build.sh @@ -4,8 +4,8 @@ docker volume create --name=openstreetmap-data docker volume create --name=openstreetmap-rendered-tiles git submodule update --init -docker-compose pull -docker-compose build --parallel -docker-compose up -d db -docker-compose run --rm api db upgrade -docker-compose stop +docker compose pull +docker compose build --parallel +docker compose up -d db +docker compose run --rm api db upgrade +docker compose stop