diff --git a/.dockerignore b/.dockerignore index f201e9225..c7e4f9719 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,6 +1,7 @@ -assets _build .cargo deps .elixir_ls -priv +priv/static +native/philomena/target +node_modules diff --git a/.github/workflows/production.yml b/.github/workflows/production.yml new file mode 100644 index 000000000..3f062f8c4 --- /dev/null +++ b/.github/workflows/production.yml @@ -0,0 +1,123 @@ +name: Production Images + +on: + push: + branches: + - master + tags: + - '*' + +jobs: + build-and-push-philomena: + name: 'Build and Publish Philomena' + runs-on: ubuntu-latest + permissions: + contents: read + packages: write + attestations: write + id-token: write + steps: + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Checkout repository + uses: actions/checkout@v5 + + - name: Log in to GitHub Container Registry + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Extract metadata for Docker + id: meta + uses: docker/metadata-action@v5 + with: + images: ghcr.io/philomena-dev/philomena + + - name: Build and push Philomena image + uses: docker/build-push-action@v5 + with: + context: . + file: ./docker/production/Dockerfile + platforms: 'linux/amd64' + push: true + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + + build-and-push-web: + name: 'Build and Publish Web' + runs-on: ubuntu-latest + permissions: + contents: read + packages: write + attestations: write + id-token: write + steps: + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Checkout repository + uses: actions/checkout@v5 + + - name: Log in to GitHub Container Registry + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Extract metadata for Docker + id: meta + uses: docker/metadata-action@v5 + with: + images: ghcr.io/philomena-dev/philomena-web + + - name: Build and push Philomena Web Server image + uses: docker/build-push-action@v5 + with: + context: . + file: ./docker/production/web/Dockerfile + platforms: 'linux/amd64' + push: true + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + + build-and-push-fiberglass: + name: 'Build and Publish Fiberglass Server' + runs-on: ubuntu-latest + permissions: + contents: read + packages: write + attestations: write + id-token: write + steps: + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Checkout repository + uses: actions/checkout@v5 + + - name: Log in to GitHub Container Registry + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Extract metadata for Docker + id: meta + uses: docker/metadata-action@v5 + with: + images: ghcr.io/philomena-dev/fiberglass-server + + - name: Build and push Fiberglass Media Processing image + uses: docker/build-push-action@v5 + with: + context: . + file: ./docker/production/fiberglass/Dockerfile + platforms: 'linux/amd64' + push: true + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} diff --git a/docker-compose.yml b/docker-compose.yml index 9e2cedb35..5aee7dadd 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -68,7 +68,7 @@ services: - '5173:5173' postgres: - image: postgres:17.6-alpine + image: postgres:17.7-alpine environment: - POSTGRES_PASSWORD=postgres volumes: diff --git a/docker/production/Dockerfile b/docker/production/Dockerfile new file mode 100644 index 000000000..e397668e7 --- /dev/null +++ b/docker/production/Dockerfile @@ -0,0 +1,101 @@ +# We need to grab "psql" from the Postgres image for database setup +FROM postgres:17.7-alpine AS pg + +# +# Step 1: Build Philomena release +# + +FROM elixir:1.18.4-alpine AS builder + +WORKDIR /tmp/philomena + +ENV MIX_ENV=prod +ENV NODE_ENV=production +ENV PATH=$PATH:/root/.cargo/bin + +ADD https://api.github.com/repos/philomena-dev/fiberglass-wrapper/git/refs/heads/master /tmp/fiberglass_wrapper_version.json + +# Install dependencies and build tools +RUN apk update --allow-untrusted \ + && apk add build-base git npm nodejs wget rust cargo --allow-untrusted \ + && mix local.hex --force \ + && mix local.rebar --force + +# Build fiberglass-wrapper from source +RUN git clone --depth 1 https://github.com/philomena-dev/fiberglass-wrapper /tmp/fiberglass_wrapper \ + && cd /tmp/fiberglass_wrapper \ + && cargo build --release + +# Copy repo files into the image +COPY . /tmp/philomena + +# Install and compile assets +RUN cd /tmp/philomena/assets \ + && NODE_ENV=development npm install \ + && npm run deploy + +# Build the application and create symlinks for easier access +RUN cd /tmp/philomena \ + && mix deps.get \ + && mix release --overwrite + +# Digest and copy static assets +RUN mix phx.digest -o /tmp/philomena/_build/prod/rel/philomena/lib/philomena-*/priv/static + +# +# Step 2: Copy only the final release into a minimal image +# Also copy psql from the Postgres image +# + +FROM alpine:3.23 + +LABEL org.opencontainers.image.authors="liamwhite , Nighty , and contributors" +LABEL org.opencontainers.image.description="The official Philomena Docker image intended for production use." +LABEL org.opencontainers.image.source="https://github.com/philomena-dev/philomena" +LABEL org.opencontainers.image.licenses="AGPL-3.0-only" + +# Install runtime dependencies +# Required by Erlang/Elixir: libncursesw libstdc++ libgcc +# Required by psql: libedit krb5-libs libldap +RUN apk update \ + && apk add --no-cache libncursesw libstdc++ libgcc libedit krb5-libs libldap + +# Set up a non-root user to run the application +RUN addgroup -g 1000 -S philomena \ + && adduser -u 1000 -S philomena -G philomena + +# Copy the finished release and the config (because of .json files) from the builder stage... +COPY --from=builder --chown=1000:1000 /tmp/philomena/_build/prod/rel/philomena /srv/philomena +COPY --from=builder --chown=1000:1000 /tmp/philomena/config /srv/philomena/config + +# Copy fiberglass-wrapper executable too +COPY --from=builder /tmp/fiberglass_wrapper/target/release/fiberglass-wrapper /usr/local/bin/fiberglass-wrapper + +# ...and the production scripts from the repository +COPY --chown=1000:1000 docker/production/purge-cache /usr/local/bin/purge-cache +COPY --chown=1000:1000 docker/production/run-cron /usr/local/bin/run-cron +COPY --chown=1000:1000 docker/production/run-cron-daily /usr/local/bin/run-cron-daily +COPY --chown=1000:1000 docker/production/run-production /usr/local/bin/run-production +COPY --chown=1000:1000 docker/production/setup-production /usr/local/bin/setup-production +COPY --chown=1000:1000 docker/production/programs/* /usr/local/bin/ + +# Copy postgres client. +COPY --from=pg /usr/local/bin/psql /usr/local/bin/psql +COPY --from=pg /usr/local/bin/pg_isready /usr/local/bin/pg_isready +COPY --from=pg /usr/local/lib/libpq.so /usr/local/lib/libpq.so +COPY --from=pg /usr/local/lib/libpq.so.5 /usr/local/lib/libpq.so.5 +COPY --from=pg /usr/local/lib/libpq.so.5.17 /usr/local/lib/libpq.so.5.17 + +# A "philomena" symlink for easier access +RUN ln -sf /srv/philomena/bin/philomena /usr/local/bin/philomena + +USER philomena + +# Create the static assets symlink as the philomena user +RUN ln -sf /srv/philomena/lib/philomena-*/priv /srv/philomena/priv + +WORKDIR /srv/philomena + +EXPOSE 4000-4002 + +CMD ["/usr/local/bin/run-production"] diff --git a/docker/production/fiberglass/Dockerfile b/docker/production/fiberglass/Dockerfile new file mode 100644 index 000000000..2afe24276 --- /dev/null +++ b/docker/production/fiberglass/Dockerfile @@ -0,0 +1,22 @@ +FROM ghcr.io/philomena-dev/fiberglass:2025-12-10 + +LABEL org.opencontainers.image.authors="liamwhite , Nighty , and contributors" +LABEL org.opencontainers.image.description="Legacy Philomena image processing tools, used by Philomena 1.2.x and older. Deprecated in favor of Mediaproc." +LABEL org.opencontainers.image.source="https://github.com/philomena-dev/philomena" +LABEL org.opencontainers.image.licenses="AGPL-3.0-only" + +WORKDIR /tmp/fiberglass-server + +COPY docker/production/fiberglass/fiberglass-server.ru /tmp/fiberglass-server/fiberglass-server.ru + +USER root + +RUN apk add ruby ruby-dev build-base rsvg-convert \ + && gem install rack puma rackup base64 \ + && apk del build-base \ + && rm -f /sbin/apk \ + && rm -rf /etc/apk /lib/apk /usr/share/apk /var/lib/apk + +USER fiberglass + +CMD puma -b tcp://0.0.0.0:8080 -w 8 -t 1:1 /tmp/fiberglass-server/fiberglass-server.ru diff --git a/docker/production/fiberglass/fiberglass-server.ru b/docker/production/fiberglass/fiberglass-server.ru new file mode 100644 index 000000000..cdd137f69 --- /dev/null +++ b/docker/production/fiberglass/fiberglass-server.ru @@ -0,0 +1,83 @@ +require 'open3' +require 'base64' +require 'fileutils' +require 'securerandom' + +$PERMITTED_COMMANDS = %w[convert ffmpeg ffprobe file gifsicle identify image-intensities jpegtran magick mediastat mediathumb optipng safe-rsvg-convert svgstat] + +class Application + def call(env) + req = Rack::Request.new(env) + + if req.post? + run(req.body.read) + else + [405, {}, []] + end + end + + private + + def run(input) + # Container-side script. Can be more lax here. + + cwd = "/tmp/#{SecureRandom.uuid}" + FileUtils.mkdir_p(cwd) + FileUtils.cd(cwd) + + progname = nil + args = [] + files = [] + + # Parse input + input.each_line.with_index do |line, index| + line.chomp! + + if index == 0 + progname = Base64.strict_decode64(line) + + unless $PERMITTED_COMMANDS.include?(progname.chomp) + return [400, {}, []] + end + + next + end + + if index == 1 + args = line.split(",").map { |a| Base64.strict_decode64(a) } + next + end + + name, contents = line.split(":") + files << name + File.write(name, Base64.strict_decode64(contents.to_s)) + end + + # Run command + stdout, stderr, status = Open3.capture3(progname, *args) + + # Generate output + output = [] + + output.push status.exitstatus.to_s + output.push "\n" + + output.push Base64.strict_encode64(stdout) + output.push "\n" + + output.push Base64.strict_encode64(stderr) + output.push "\n" + + files.each do |file| + output.push Base64.strict_encode64(File.read(file)) + output.push "\n" + end + + FileUtils.cd("/") + FileUtils.rm_rf(cwd) + + [200, {}, output] + end +end + +run Application.new diff --git a/docker/production/programs/convert b/docker/production/programs/convert new file mode 100755 index 000000000..84c8a7374 --- /dev/null +++ b/docker/production/programs/convert @@ -0,0 +1,2 @@ +#!/bin/sh +safe-wrapper convert "$@" diff --git a/docker/production/programs/ffmpeg b/docker/production/programs/ffmpeg new file mode 100755 index 000000000..d6cbe9354 --- /dev/null +++ b/docker/production/programs/ffmpeg @@ -0,0 +1,2 @@ +#!/bin/sh +safe-wrapper ffmpeg "$@" diff --git a/docker/production/programs/ffprobe b/docker/production/programs/ffprobe new file mode 100755 index 000000000..b0970ec1a --- /dev/null +++ b/docker/production/programs/ffprobe @@ -0,0 +1,2 @@ +#!/bin/sh +safe-wrapper ffprobe "$@" diff --git a/docker/production/programs/file b/docker/production/programs/file new file mode 100755 index 000000000..ce3920675 --- /dev/null +++ b/docker/production/programs/file @@ -0,0 +1,2 @@ +#!/bin/sh +safe-wrapper file "$@" diff --git a/docker/production/programs/gifsicle b/docker/production/programs/gifsicle new file mode 100755 index 000000000..db73f423d --- /dev/null +++ b/docker/production/programs/gifsicle @@ -0,0 +1,2 @@ +#!/bin/sh +safe-wrapper gifsicle "$@" diff --git a/docker/production/programs/identify b/docker/production/programs/identify new file mode 100755 index 000000000..d4c766bed --- /dev/null +++ b/docker/production/programs/identify @@ -0,0 +1,2 @@ +#!/bin/sh +safe-wrapper identify "$@" diff --git a/docker/production/programs/image-intensities b/docker/production/programs/image-intensities new file mode 100755 index 000000000..fc5d001b1 --- /dev/null +++ b/docker/production/programs/image-intensities @@ -0,0 +1,2 @@ +#!/bin/sh +safe-wrapper image-intensities "$@" diff --git a/docker/production/programs/jpegtran b/docker/production/programs/jpegtran new file mode 100755 index 000000000..66799c443 --- /dev/null +++ b/docker/production/programs/jpegtran @@ -0,0 +1,2 @@ +#!/bin/sh +safe-wrapper jpegtran "$@" diff --git a/docker/production/programs/magick b/docker/production/programs/magick new file mode 100755 index 000000000..fd1b4ab7b --- /dev/null +++ b/docker/production/programs/magick @@ -0,0 +1,2 @@ +#!/bin/sh +safe-wrapper magick "$@" diff --git a/docker/production/programs/mediastat b/docker/production/programs/mediastat new file mode 100755 index 000000000..5a80cb0f5 --- /dev/null +++ b/docker/production/programs/mediastat @@ -0,0 +1,2 @@ +#!/bin/sh +safe-wrapper mediastat "$@" diff --git a/docker/production/programs/mediathumb b/docker/production/programs/mediathumb new file mode 100755 index 000000000..f20f3ca62 --- /dev/null +++ b/docker/production/programs/mediathumb @@ -0,0 +1,2 @@ +#!/bin/sh +safe-wrapper mediathumb "$@" diff --git a/docker/production/programs/optipng b/docker/production/programs/optipng new file mode 100755 index 000000000..af5b8aedc --- /dev/null +++ b/docker/production/programs/optipng @@ -0,0 +1,2 @@ +#!/bin/sh +safe-wrapper optipng "$@" diff --git a/docker/production/programs/safe-rsvg-convert b/docker/production/programs/safe-rsvg-convert new file mode 100755 index 000000000..df37e518a --- /dev/null +++ b/docker/production/programs/safe-rsvg-convert @@ -0,0 +1,2 @@ +#!/bin/sh +safe-wrapper safe-rsvg-convert "$@" diff --git a/docker/production/programs/safe-wrapper b/docker/production/programs/safe-wrapper new file mode 100755 index 000000000..52351e568 --- /dev/null +++ b/docker/production/programs/safe-wrapper @@ -0,0 +1,6 @@ +#!/bin/sh +if [[ -z "${FIBERGLASS_URL}" ]]; then + export FIBERGLASS_URL=http://fiberglass:8080 +fi + +fiberglass-wrapper "$@" diff --git a/docker/production/programs/svgstat b/docker/production/programs/svgstat new file mode 100755 index 000000000..c64511993 --- /dev/null +++ b/docker/production/programs/svgstat @@ -0,0 +1,2 @@ +#!/bin/sh +safe-wrapper svgstat "$@" diff --git a/docker/production/purge-cache b/docker/production/purge-cache new file mode 100755 index 000000000..2dcbb11e6 --- /dev/null +++ b/docker/production/purge-cache @@ -0,0 +1,19 @@ +#!/bin/sh +set -e + +# Run your custom purge command here. +# +# The script receives the list of URLs to be purged +# as JSON on the first argument. +# +# {"files":[]} + +body="$1" +api_token= +zone_id= + +# curl -XPOST \ +# -H "Content-Type: application/json" \ +# -H "Authorization: Bearer ${api_token}" \ +# "https://api.cloudflare.com/client/v4/zones/${zone_id}/purge_cache" \ +# -d "${body}" diff --git a/docker/production/run-cron b/docker/production/run-cron new file mode 100755 index 000000000..0b1e2fda6 --- /dev/null +++ b/docker/production/run-cron @@ -0,0 +1,8 @@ +#!/bin/sh +set -e + +export RELEASE_NODE=philomena_runner_0 + +philomena eval 'Philomena.Release.update_channels()' +philomena eval 'Philomena.Release.verify_artist_links()' +philomena eval 'Philomena.Release.update_stats()' diff --git a/docker/production/run-cron-daily b/docker/production/run-cron-daily new file mode 100755 index 000000000..4ce81cd84 --- /dev/null +++ b/docker/production/run-cron-daily @@ -0,0 +1,7 @@ +#!/bin/sh +set -e + +export RELEASE_NODE=philomena_daily + +philomena eval 'Philomena.Release.generate_autocomplete()' +philomena eval 'Philomena.Release.clean_moderation_logs()' diff --git a/docker/production/run-production b/docker/production/run-production new file mode 100755 index 000000000..562e50969 --- /dev/null +++ b/docker/production/run-production @@ -0,0 +1,14 @@ +#!/bin/sh +set -e + +if [[ -z "${START_WORKER}" ]]; then + ulimit -n 16384 + + export START_ENDPOINT=true + export RELEASE_NODE=philomena_app_0 +else + export START_WORKER=true + export RELEASE_NODE=philomena_worker_0 +fi + +philomena start diff --git a/docker/production/setup-production b/docker/production/setup-production new file mode 100755 index 000000000..cc57e7c6b --- /dev/null +++ b/docker/production/setup-production @@ -0,0 +1,25 @@ +#!/bin/sh +set -e + +echo "Waiting for database to start..." +until pg_isready; do + sleep 2 +done + +echo "Waiting for OpenSearch..." +until wget --no-check-certificate -qO - $OPENSEARCH_URL; do + sleep 2 +done + +# Check if the schema_migrations table exists +TABLE_EXISTS=$(psql -tAc "SELECT EXISTS (SELECT FROM information_schema.tables WHERE table_schema = 'public' AND table_name = 'schema_migrations');") + +if [ "$TABLE_EXISTS" = "f" ]; then + echo "database appears to be uninitialized (table public.schema_migrations is missing), setting up..." + psql < priv/repo/structure.sql + philomena eval 'Philomena.Release.seed()' +else + echo "database appears to be initialized, skipping setup" +fi + +philomena eval 'Philomena.Release.migrate()' diff --git a/docker/production/web/Caddyfile b/docker/production/web/Caddyfile new file mode 100644 index 000000000..7ee7f21c8 --- /dev/null +++ b/docker/production/web/Caddyfile @@ -0,0 +1,201 @@ +{ + auto_https disable_certs + + filesystem philomena s3 { + bucket {$S3_BUCKET} + endpoint {$S3_ENDPOINT} + region auto + use_path_style + } + + servers { + client_ip_headers CF-Connecting-IP X-Forwarded-For + } +} + +(cors) { + @get_and_options method GET OPTIONS + @only_options method OPTIONS + + header @get_and_options { + Access-Control-Allow-Origin "*" + Access-Control-Allow-Methods "GET, OPTIONS" + Access-Control-Allow-Headers "DNT,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type" + } + + header @only_options { + Access-Control-Max-Age 1728000 + Content-Type 'text/plain charset=UTF-8' + Content-Length 0 + } +} + +(s3path) { + handle {args[0]} { + rewrite {args[0]} {args[1]} + file_server { + fs philomena + } + } +} + +(filepath) { + handle {args[0]} { + file_server { + root /var/www + } + + header Cache-Control "public, max-age=2147483647" + } +} + +{$APP_URL} { + @json path_regexp /.+\.json$ + @static path_regexp ^/.+\.(ico|js|css|eot|woff|svg|ttf|otf|png|jpg|swf|gif|woff2|mp3)$ + @downtime expression {$DOWNTIME} == true + + rate_limit { + zone dynamic { + match { + host {$SITE_DOMAIN} + } + key {client_ip} + events 30 + window 5s + } + + log_key + } + + handle @downtime { + error 503 + } + + handle_errors 500 502 504 429 { + log_skip + rewrite * /error_50x.html + root /var/www + file_server + } + + handle @static { + encode zstd gzip + header Cache-Control "public, max-age=2147483647" + file_server { + root /var/www + } + } + + handle @json { + log_skip + respond 400 + } + + header { + Strict-Transport-Security "max-age=31556925" + } + + request_body { + max_size 130MiB + } + + route { + # Try to serve static files first... + file_server { + # Root must be set here, as setting it globally breaks other filesystems. + root /var/www + pass_thru # Instead of returning 404, simply move on + } + + import cors + + # Delegate to app if there's no such file + reverse_proxy http://{$APP_ADDRESS} { + header_up X-Real-Ip {client_ip} + header_up X-Forwarded-For {client_ip} + header_up X-Forwarded-Ssl on + + header_down X-Frame-Options SAMEORIGIN + header_down Cache-Control 'no-store, no-cache' + } + } + + log { + format filter { + request>headers delete + request>remote_port delete + resp_headers delete + wrap json + } + + output file /var/log/caddy/access.log { + roll_size 2GiB + roll_keep 20 + } + + level INFO + } + + log_append user_agent {http.request.header.User-Agent} +} + +{$CDN_URL} { + # Handle images and static assets + @allimages path /img/* /avatars/* /spns/* /badge-img/* /tag-img/* + @imgdownload path_regexp ^/img/download/(.+)/([0-9]+).*\.([A-Za-z0-9]+)$ + @imgview path_regexp ^/img/view/(.+)/([0-9]+).*\.([A-Za-z0-9]+)$ + @img path_regexp ^/img/(.+)$ + @spns path_regexp ^/spns/(.+)$ + @avatars path_regexp ^/avatars/(.+)$ + @badges path_regexp ^/badges/(.+)$ + @tags path_regexp ^/tags/(.+)$ + @html path_regexp ^/(.+\.html)$ + @static path_regexp ^/(.+\.html|challenge\.css|favicon\.svg)$ + + import s3path @imgdownload /images/{re.imgdownload.1}/{re.imgdownload.2}/full.{re.imgdownload.3} + import s3path @imgview /images/{re.imgview.1}/{re.imgview.2}/full.{re.imgview.3} + import s3path @img /images/{re.img.1} + import s3path @avatars /avatars/{re.avatars.1} + import s3path @spns /adverts/{re.spns.1} + import s3path @badges /badges/{re.badges.1} + import s3path @tags /tags/{re.tags.1} + + import filepath @html + + # This block and the next block after it (the header one) are required + # because it is possible for there to be files in the bucket which have + # an incorrect content type (e.g. "application/octet-stream"). + # The Content-Type header is force-set here based on the file extension. + map {path} {custom_content_type} { + ~.*\.png$ "image/png" + ~.*\.jpe?g$ "image/jpeg" + ~.*\.gif$ "image/gif" + ~.*\.svg$ "image/svg+xml" + ~.*\.mp4$ "video/mp4" + ~.*\.webm$ "video/webm" + default "text/html" + } + + header @allimages { + Content-Type {custom_content_type} + } + + # Download path should download the image, not view it. + header @imgdownload { + Content-Disposition "attachment" + } + + handle @static { + import cors + encode zstd gzip + header Cache-Control "public, max-age=2147483647" + file_server { + root /var/www + } + } + + handle { + import cors + respond 404 + } +} diff --git a/docker/production/web/Dockerfile b/docker/production/web/Dockerfile new file mode 100644 index 000000000..dbc289c43 --- /dev/null +++ b/docker/production/web/Dockerfile @@ -0,0 +1,63 @@ +ARG CADDY_VERSION="2.10.2" +ARG CADDY_FS_S3_VERSION=v0.11.0 + +# +# Step 1: Build and digest Philomena static assets +# + +FROM elixir:1.18.4-alpine AS philomena + +WORKDIR /tmp/philomena + +ENV MIX_ENV=prod +ENV NODE_ENV=production + +# Install dependencies and build tools +RUN apk update --allow-untrusted \ + && apk add build-base git npm nodejs --allow-untrusted \ + && mix local.hex --force \ + && mix local.rebar --force + +# Copy repo files into the image +COPY . /tmp/philomena + +# Install and compile assets +RUN cd /tmp/philomena/assets \ + && NODE_ENV=development npm install \ + && npm run deploy + +# Get the dependencies and digest the assets, no need to compile the whole app, +# as we only need the static files for the web server. +RUN cd /tmp/philomena \ + && mix deps.get \ + && mix phx.digest --no-compile -o /tmp/philomena/priv/static + +# +# Step 2: Build Caddy with required plugins (e.g. S3 file system) +# + +FROM caddy:$CADDY_VERSION-builder-alpine AS builder + +RUN xcaddy build \ + --with github.com/sagikazarmark/caddy-fs-s3@${CADDY_FS_S3_VERSION} \ + --with github.com/mholt/caddy-ratelimit + +# +# Step 3: Assemble the final image +# + +FROM caddy:$CADDY_VERSION-alpine + +LABEL org.opencontainers.image.authors="liamwhite , Nighty , and contributors" +LABEL org.opencontainers.image.description="The official Philomena Web Server image intended for production use." +LABEL org.opencontainers.image.source="https://github.com/philomena-dev/philomena" +LABEL org.opencontainers.image.licenses="AGPL-3.0-only" + +COPY --from=philomena /tmp/philomena/priv/static /var/www +COPY --from=builder /usr/bin/caddy /usr/bin/caddy +COPY docker/production/web/Caddyfile /etc/caddy/Caddyfile +COPY docker/production/web/downtime.html /var/www/downtime.html +COPY docker/production/web/error_50x.html /var/www/error_50x.html + +EXPOSE 80 +EXPOSE 443 diff --git a/docker/production/web/downtime.html b/docker/production/web/downtime.html new file mode 100644 index 000000000..d79ed1b3a --- /dev/null +++ b/docker/production/web/downtime.html @@ -0,0 +1,117 @@ + + + + + + Philomena - Downtime + + + + +
+

OFFLINE Maintenance in Progress

+

Current status:

+
+
+

+ Maintenance work is being performed on the site. We expect to be back online shortly. Thank you for your + patience. Please check back soon. +

+
+
+

In the meantime, please accept our apologies for the inconvenience.

+
+ + diff --git a/docker/production/web/error_50x.html b/docker/production/web/error_50x.html new file mode 100644 index 000000000..d72a542c2 --- /dev/null +++ b/docker/production/web/error_50x.html @@ -0,0 +1,117 @@ + + + + + + Philomena - Downtime + + + + +
+

5XXServer Error

+
+
+

An unexpected error occurred while processing your request.

+

+ If this is your first time seeing this error, please try again later. If the problem persists, please + contact the site administrator. +

+
+
+

In the meantime, please accept our apologies for the inconvenience.

+
+ + diff --git a/lib/philomena/release.ex b/lib/philomena/release.ex index 32e24263d..5637bd164 100644 --- a/lib/philomena/release.ex +++ b/lib/philomena/release.ex @@ -14,6 +14,11 @@ defmodule Philomena.Release do {:ok, _, _} = Ecto.Migrator.with_repo(repo, &Ecto.Migrator.run(&1, :down, to: version)) end + def seed do + start_app() + Code.require_file("priv/repo/seeds.exs") + end + def update_channels do start_app() Philomena.Channels.update_tracked_channels!()