From: Radek Czajka Date: Tue, 3 Nov 2026 12:04:53 +0000 (+0100) Subject: Docker stuff X-Git-Url: https://git.mdrn.pl/wolnelektury.git/commitdiff_plain/b21c4511ab979116455f41e844790264e678d235?ds=sidebyside Docker stuff --- diff --git a/Dockerfile b/Dockerfile index b23530b9c..2fc76debf 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,7 +1,9 @@ +# Base system and requirements. + FROM python:3.9-trixie AS base -ARG UID=1000 -ARG GID=1000 +ARG UID=8100 +ARG GID=8100 RUN apt-get update && apt-get install -y \ git \ @@ -39,15 +41,18 @@ RUN mkdir /app/.ipython +# dev +# Assumes code will be mounted. FROM base AS dev #RUN pip install --no-cache-dir coverage +# prod-base +# Installs code and production requirements. - -FROM base AS prod +FROM base AS prod-base USER root @@ -58,16 +63,66 @@ USER app COPY src /app/src +# static-builder +# Static files are collected here. Statics are then copied into an Nginx image, +# and staticfiles.json manifest file is copied back into production image. - -FROM prod AS static-builder +FROM prod-base AS static-builder RUN python manage.py collectstatic --noinput +# nginx +# Contains static files and nginx config. FROM nginx:alpine AS nginx +ARG UID=8100 +ARG GID=8100 + # COPY nginx.conf conf.d/... +RUN addgroup -g $GID app && addgroup nginx app COPY --from=static-builder /app/var/static /app/var/static + + + + +# Postgres with Polish hunspell dictionary withoud diacritics +# to allow full text search without diacritics. + +# --- Stage 1: prepare dictionary files --- +FROM python:3.13-slim-trixie AS polish-dict + +RUN apt-get update \ + && apt-get install -y --no-install-recommends hunspell-pl \ + && rm -rf /var/lib/apt/lists/* + +COPY scripts/fold_hunspell.py /usr/local/bin/fold_hunspell.py + +RUN set -eu; \ + mkdir /out; cd /tmp; \ + enc="$(sed -n 's/^SET[[:space:]]*//p' /usr/share/hunspell/pl_PL.aff | tr -d '\r')"; \ + iconv -f "${enc:-UTF-8}" -t UTF-8 /usr/share/hunspell/pl_PL.dic > pl.dict; \ + iconv -f "${enc:-UTF-8}" -t UTF-8 /usr/share/hunspell/pl_PL.aff > pl.affix; \ + python3 /usr/local/bin/fold_hunspell.py pl.dict pl.affix \ + /out/polish.dict /out/polish.affix; \ + dpkg-query -W -f='hunspell-pl ${Version}\n' hunspell-pl > /out/polish.version + +# --- Stage 2: actual Postgres image with the dictionary --- +FROM postgres:18 AS postgres + +COPY --from=polish-dict /out/ /tmp/polish-dict/ +RUN mv /tmp/polish-dict/* "/usr/share/postgresql/$PG_MAJOR/tsearch_data/" \ + && rmdir /tmp/polish-dict + +COPY scripts/postgres-polish.sql /docker-entrypoint-initdb.d/10-postgres-polish.sql + + + +# prod +# That's the production image with staticfiles.json manifest file added. + +FROM prod-base AS prod + +COPY --from=static-builder /app/var/static/staticfiles.json /app/var/static/staticfiles.json diff --git a/docker-compose.yml b/docker-compose.yml index bda15bb40..b01e97071 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -26,8 +26,6 @@ x-app-base: &app-base services: web: <<: *app-base - ports: - - "8000:8000" command: ./manage.py runserver 0.0.0.0:8000 celery: @@ -35,7 +33,9 @@ services: command: celery -A wolnelektury worker -l info db: - image: postgres:18 + build: + context: . + target: postgres env_file: - .env volumes: @@ -47,6 +47,21 @@ services: memcached: image: memcached:1.6-alpine + nginx: + build: + context: . + target: nginx + args: + UID: ${UID:-1000} + GID: ${GID:-1000} + ports: + - "127.0.0.1:8101:80" + volumes: + - ./var/media:/app/var/media:ro + - ./scripts/nginx.conf:/etc/nginx/conf.d/default.conf:ro + depends_on: + - web + volumes: - ipython: \ No newline at end of file + ipython: diff --git a/scripts/fold_hunspell.py b/scripts/fold_hunspell.py new file mode 100644 index 000000000..bc63e7873 --- /dev/null +++ b/scripts/fold_hunspell.py @@ -0,0 +1,66 @@ +#!/usr/bin/env python3 +""" +Tworzy wersję słownika hunspell bez polskich znaków dla PostgreSQL (szablon ispell). + +Użycie: + fold_hunspell.py polish.dict polish.affix polish_ascii.dict polish_ascii.affix + +Pliki wejściowe muszą być w UTF-8 (po iconv z ISO-8859-2). + +Samo usunięcie znaków psuje warunki reguł z klasami negowanymi: np. +`SFX H ć ło [^ąeśź]ć` (wszystko poza ą, e, ś, ź przed ć) po zamianie +stałoby się `[^aesz]c` i przestało pasować do czasowników na -ać. +Dlatego litera zostaje w klasie negowanej tylko wtedy, gdy oryginał +wykluczał wszystkie litery, które się na nią składają; w przeciwnym +razie jest usuwana, a pusta klasa zamienia się w `.`. Reguły stają się +przez to nieco zbyt liberalne (nadmiarowe formy), ale żadna forma nie ginie. +""" +import re +import sys +import unicodedata + + +def fold(text): + text = text.replace('ł', 'l').replace('Ł', 'L') + return ''.join( + c for c in unicodedata.normalize('NFKD', text) + if not unicodedata.combining(c) + ) + + +LETTERS = 'aąbcćdeęfghijklłmnńoóprsśtuvwxyzźż' +GROUPS = {} +for letter in LETTERS: + GROUPS.setdefault(fold(letter), set()).add(letter) + + +def fold_negated_class(match): + excluded = set(match.group(1)) + keep = [] + for c in match.group(1): + folded = fold(c) + if GROUPS.get(folded, {c}) <= excluded and folded not in keep: + keep.append(folded) + return '[^' + ''.join(keep) + ']' if keep else '.' + + +def fold_affix_line(line): + parts = line.split() + if len(parts) >= 5 and parts[0] in ('SFX', 'PFX') and '[^' in parts[4]: + condition = re.sub(r'\[\^([^\]]*)\]', fold_negated_class, parts[4]) + line = line.replace(parts[4], condition, 1) + if line.startswith('SET '): + return 'SET UTF-8\n' + return fold(line) + + +def main(dict_in, affix_in, dict_out, affix_out): + with open(dict_in, encoding='utf-8') as src, open(dict_out, 'w', encoding='utf-8') as dst: + dst.write(fold(src.read())) + with open(affix_in, encoding='utf-8') as src, open(affix_out, 'w', encoding='utf-8') as dst: + for line in src: + dst.write(fold_affix_line(line)) + + +if __name__ == '__main__': + main(*sys.argv[1:5]) diff --git a/scripts/nginx.conf b/scripts/nginx.conf new file mode 100644 index 000000000..872f1a273 --- /dev/null +++ b/scripts/nginx.conf @@ -0,0 +1,60 @@ +server { + listen 80; + + access_log off; + error_log /dev/stderr warn; + + client_max_body_size 4G; + send_timeout 300s; + proxy_buffers 16 16k; + proxy_buffer_size 32k; + + location /static/ { + root /app/var; + expires 30d; + } + + location /media/ { + root /app/var; + expires 30d; + } + location ~ ^/media/.*\.(mp3|ogg|daisy\.zip)$ { + add_header Content-Disposition "attachment"; + } + location ~ ^/media/.*\.txt$ { + add_header Content-Type "text/plain; charset=utf-8"; + } + location ~ ^/media/book/(pdf|epub|mobi|fb2|cover|xml)/ { + # kill cache + add_header Last-Modified $date_gmt; + add_header Cache-Control 'no-store, no-cache, must-revalidate, proxy-revalidate, max-age=0'; + if_modified_since off; + expires off; + etag off; + } + + location /api/ { + add_header 'Access-Control-Allow-Origin' '*' always; + add_header 'Access-Control-Allow-Methods' 'GE, POST, PUT, DELETE' always; + add_header 'Access-Control-Allow-Headers' 'Authorization, Content-Type' always; + add_header 'Access-Control-Expose-Headers' 'Link' always; + + if ($request_method = OPTIONS) { + return 204; + } + + proxy_set_header Accept-Language ""; + proxy_set_header Host $host; + proxy_pass http://web:8000; + } + location / { + root /app/var/static/root; + try_files $uri @backend; + } + + location @backend { + proxy_set_header Accept-Language ""; + proxy_set_header Host $host; + proxy_pass http://web:8000; + } +} diff --git a/scripts/postgres-polish.sql b/scripts/postgres-polish.sql new file mode 100644 index 000000000..100764abd --- /dev/null +++ b/scripts/postgres-polish.sql @@ -0,0 +1,27 @@ +CREATE EXTENSION IF NOT EXISTS unaccent; + +DO $$ + BEGIN + IF NOT EXISTS (SELECT 1 FROM pg_ts_dict + WHERE dictname = 'polish_hunspell' + AND dictnamespace = 'public'::regnamespace) THEN + CREATE TEXT SEARCH DICTIONARY public.polish_hunspell ( + TEMPLATE = ispell, DictFile = polish, AffFile = polish + ); + END IF; + IF NOT EXISTS (SELECT 1 FROM pg_ts_config + WHERE cfgname = 'polish' + AND cfgnamespace = 'public'::regnamespace) THEN + CREATE TEXT SEARCH CONFIGURATION public.polish (COPY = pg_catalog.simple); + END IF; +END $$; + +ALTER TEXT SEARCH DICTIONARY public.polish_hunspell ( + DictFile = polish, AffFile = polish, StopWords +); + +ALTER TEXT SEARCH CONFIGURATION public.polish + ALTER MAPPING FOR asciiword, asciihword, hword_asciipart, word, hword, hword_part + WITH unaccent, polish_hunspell, simple; + +