diff --git a/.dockerignore b/.dockerignore index 3fee6d0..e62bef4 100644 --- a/.dockerignore +++ b/.dockerignore @@ -7,3 +7,7 @@ node_modules/ .env .venv/ docker-compose-example/ +docker-compose*.yml +dev.sh +index.html +site.webmanifest diff --git a/.env.example b/.env.example index c749c8f..bfb2e46 100644 --- a/.env.example +++ b/.env.example @@ -1,8 +1,40 @@ -# Agencja AI — Environment Configuration (Local Development) +# Agencja AI — Environment Configuration # Copy to .env and fill in your values. # Never commit .env with real credentials to version control. +# +# Po zmianie wartości uruchom: ./dev.sh config (lub ./dev.sh restart) -# --- Frontend Server --- +# --- Frontend Server (TYLKO lokalny dev — nie używaj w Dokploy) --- # Port na hoście (localhost). W kontenerze nginx zawsze nasłuchuje na 80. -# Mapowanie w docker-compose.yml: ${FRONTEND_PORT}:80 FRONTEND_PORT=8081 + +# --- Strona (meta, SEO, PWA) --- +SITE_TITLE="Agencja AI" +SITE_SHORT_NAME="Agencja AI" +SITE_BASE_URL=https://agencja-ai.com +SITE_DESCRIPTION="Lokalne modele LLM, własny serwer AI (Proxmox, TrueNAS, Ubuntu), RAG, Ollama, Open WebUI, bezpieczny self-hosting i wdrożenia biznesowe lokalnej AI." +# Krótki opis pod og:description (Facebook, LinkedIn) — bez HTML; puste = SITE_DESCRIPTION +FACEBOOK_DESCRIPTION="Lokalne modele LLM (Llama 3, Mistral, Command-R, Phi-3, DeepSeek i inne). Budowa własnego serwera AI na bazie Proxmox, TrueNAS SCALE oraz Ubuntu. Optymalizacja GPU w środowisku Linux (Ubuntu, OpenSUSE, Proxmox). Technologia RAG – jak podpiąć Twoją firmową wiedzę do lokalnego AI. Prywatne instancje Ollama, Open WebUI, AnythingLLM, PrivateGPT. Bezpieczny self-hosting – od konfiguracji Proxmox VE, przez TrueNAS, po produkcyjne wdrożenia. Porównania sprzętu, zużycie prądu, chłodzenie i realne koszty posiadania własnego AI. Automatyzacje, integracje i zaawansowane zastosowania biznesowe lokalnej AI." +TWITTER_DESCRIPTION="Lokalne modele LLM (Llama 3, Mistral, Command-R, Phi-3, DeepSeek i inne). Budowa własnego serwera AI na bazie Proxmox, TrueNAS SCALE oraz Ubuntu. Optymalizacja GPU w środowisku Linux (Ubuntu, OpenSUSE, Proxmox). Technologia RAG – jak podpiąć Twoją firmową wiedzę do lokalnego AI. Prywatne instancje Ollama, Open WebUI, AnythingLLM, PrivateGPT. Bezpieczny self-hosting – od konfiguracji Proxmox VE, przez TrueNAS, po produkcyjne wdrożenia. Porównania sprzętu, zużycie prądu, chłodzenie i realne koszty posiadania własnego AI. Automatyzacje, integracje i zaawansowane zastosowania biznesowe lokalnej AI." +SITE_OG_IMAGE_ALT="Agencja AI — lokalna sztuczna inteligencja i self-hosting" +THEME_COLOR="#000000" + +# --- Ikony (ścieżki względem roota strony) --- +FAVICON_48=/assets/imgs/favico/48x48.png +FAVICON_180=/assets/imgs/favico/180x180.png +FAVICON_512=/assets/imgs/favico/512x512.png + +# --- YouTube --- +# Short FHD 16:9 (landscape / szeroki viewport) — ID z adresu youtube.com/watch?v=... +VIDEO_ID_FHD=kujXXtoHUbc +POST_VIDEO_REDIRECT_URL_FHD= +# Short vertical 9:16 (portrait / wąski viewport) — puste = fallback do FHD +VIDEO_ID_VERTICAL= +POST_VIDEO_REDIRECT_URL_VERTICAL= +# Zachowanie po zakończeniu intro (domyślnie dla obu formatów): +# true = przekieruj na POST_VIDEO_REDIRECT_URL_* (YouTube / inny link) +# false = zostaw miniaturkę YouTube jako tło strony (bez przekierowania) +POST_VIDEO_REDIRECT_ENABLED=false +# Opcjonalnie osobno dla każdego formatu (puste = użyj POST_VIDEO_REDIRECT_ENABLED): +# POST_VIDEO_REDIRECT_ENABLED_FHD=false +# POST_VIDEO_REDIRECT_ENABLED_VERTICAL=false diff --git a/.gitignore b/.gitignore index 958ef40..f9292ee 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,10 @@ .env *.env.local +# Generated from .env at dev/build time +index.html +site.webmanifest + # Python __pycache__/ .venv/ diff --git a/Dockerfile b/Dockerfile index 442925d..ff0c8c5 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,19 +1,25 @@ FROM nginx:1.27-alpine AS production +RUN apk add --no-cache gettext + # Remove default nginx config RUN rm /etc/nginx/conf.d/default.conf -# Copy nginx config from project root +# Copy nginx configs from project root +COPY nginx.conf /etc/nginx/nginx.conf COPY default.conf /etc/nginx/conf.d/default.conf -# Copy web files (index.html + assets) -COPY index.html /usr/share/nginx/html/index.html -COPY site.webmanifest /usr/share/nginx/html/site.webmanifest +# Copy static assets COPY assets/ /usr/share/nginx/html/assets/ +# Templates + generator (rendered at container start) +COPY index.html.template site.webmanifest.template generate-from-env.sh /opt/agencja-ai/ +RUN chmod +x /opt/agencja-ai/generate-from-env.sh + +COPY docker-entrypoint.sh /docker-entrypoint.d/99-generate-from-env.sh +RUN chmod +x /docker-entrypoint.d/99-generate-from-env.sh + EXPOSE 80 HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \ CMD wget -qO- http://localhost:80/ || exit 1 - -CMD ["nginx", "-g", "daemon off;"] diff --git a/default.conf b/default.conf index e8cb034..d9a754a 100644 --- a/default.conf +++ b/default.conf @@ -10,9 +10,24 @@ server { gzip_types text/plain text/css application/json application/javascript text/xml application/xml image/svg+xml; gzip_min_length 1024; + # Block hidden files and sensitive extensions + location ~ /\. { + deny all; + return 404; + } + + location ~* \.(sh|template|env|yml|yaml|md|git|bak|sql|log)$ { + deny all; + return 404; + } + # Main page - location / { - try_files $uri $uri/ /index.html; + location = / { + try_files /index.html =404; + } + + location = /index.html { + add_header Cache-Control "no-cache"; } # Web app manifest @@ -29,7 +44,15 @@ server { try_files $uri =404; } + # Everything else → 404 (no fallback to index.html) + location / { + return 404; + } + # Security headers add_header X-Frame-Options SAMEORIGIN always; add_header X-Content-Type-Options nosniff always; + add_header Referrer-Policy "strict-origin-when-cross-origin" always; + add_header Permissions-Policy "camera=(), microphone=(), geolocation=(), payment=()" always; + add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' https://www.youtube.com https://www.gstatic.com; style-src 'self' 'unsafe-inline'; img-src 'self' https://img.youtube.com data:; frame-src https://www.youtube.com; connect-src 'self' https://www.youtube.com; base-uri 'self'; form-action 'none'; object-src 'none'; upgrade-insecure-requests;" always; } diff --git a/dev.sh b/dev.sh index 10e7be1..d23df28 100755 --- a/dev.sh +++ b/dev.sh @@ -10,12 +10,22 @@ if [[ ! -f .env ]]; then echo "Utworzono .env z .env.example" fi +generate_from_env() { + set -a + # shellcheck disable=SC1091 + source .env + set +a + chmod +x generate-from-env.sh + ./generate-from-env.sh . +} + case "${1:-up}" in up) + generate_from_env $COMPOSE up -d echo "" echo "Dev server: http://localhost:${FRONTEND_PORT:-8081}" - echo "Edytuj pliki → F5 w przeglądarce (bez rebuildu)." + echo "Edytuj .env → ./dev.sh config → F5 w przeglądarce." ;; down) $COMPOSE down @@ -24,14 +34,19 @@ case "${1:-up}" in $COMPOSE logs -f ;; restart) + generate_from_env $COMPOSE restart ;; + config) + generate_from_env + echo "Wygenerowano index.html i site.webmanifest z .env." + ;; reload-nginx) docker exec agencja-ai-com-frontend nginx -s reload echo "Nginx przeładowany (po zmianie default.conf)." ;; *) - echo "Użycie: $0 [up|down|logs|restart|reload-nginx]" + echo "Użycie: $0 [up|down|logs|restart|reload-nginx|config]" exit 1 ;; esac diff --git a/docker-compose-example/.env.example b/docker-compose-example/.env.example index 2274ea6..c280926 100644 --- a/docker-compose-example/.env.example +++ b/docker-compose-example/.env.example @@ -1,9 +1,27 @@ # Agencja AI — Environment Configuration (Dokploy) -# Copy to .env before deploy if needed. -# Never commit .env with real credentials to version control. +# Po zmianie wartości uruchom redeploy w Dokploy. # --- Frontend Server --- -# Port publikowany na hoście. W kontenerze nginx zawsze nasłuchuje na 80. -# Mapowanie w docker-compose.yml: ${FRONTEND_PORT}:80 -# Dokploy/Traefik dodatkowo routuje po FQDN do wewnętrznego portu 80 kontenera. FRONTEND_PORT=8081 + +# --- Strona (meta, SEO, PWA) --- +SITE_TITLE="Agencja AI" +SITE_SHORT_NAME="Agencja AI" +SITE_BASE_URL=https://agencja-ai.com +SITE_DESCRIPTION="Lokalne modele LLM, własny serwer AI (Proxmox, TrueNAS, Ubuntu), RAG, Ollama, Open WebUI, bezpieczny self-hosting i wdrożenia biznesowe lokalnej AI." +FACEBOOK_DESCRIPTION="Lokalne modele LLM (Llama 3, Mistral, Command-R, Phi-3, DeepSeek i inne). Budowa własnego serwera AI na bazie Proxmox, TrueNAS SCALE oraz Ubuntu. Optymalizacja GPU w środowisku Linux (Ubuntu, OpenSUSE, Proxmox). Technologia RAG – jak podpiąć Twoją firmową wiedzę do lokalnego AI. Prywatne instancje Ollama, Open WebUI, AnythingLLM, PrivateGPT. Bezpieczny self-hosting – od konfiguracji Proxmox VE, przez TrueNAS, po produkcyjne wdrożenia. Porównania sprzętu, zużycie prądu, chłodzenie i realne koszty posiadania własnego AI. Automatyzacje, integracje i zaawansowane zastosowania biznesowe lokalnej AI." +TWITTER_DESCRIPTION="Lokalne modele LLM (Llama 3, Mistral, Command-R, Phi-3, DeepSeek i inne). Budowa własnego serwera AI na bazie Proxmox, TrueNAS SCALE oraz Ubuntu. Optymalizacja GPU w środowisku Linux (Ubuntu, OpenSUSE, Proxmox). Technologia RAG – jak podpiąć Twoją firmową wiedzę do lokalnego AI. Prywatne instancje Ollama, Open WebUI, AnythingLLM, PrivateGPT. Bezpieczny self-hosting – od konfiguracji Proxmox VE, przez TrueNAS, po produkcyjne wdrożenia. Porównania sprzętu, zużycie prądu, chłodzenie i realne koszty posiadania własnego AI. Automatyzacje, integracje i zaawansowane zastosowania biznesowe lokalnej AI." +SITE_OG_IMAGE_ALT="Agencja AI — lokalna sztuczna inteligencja i self-hosting" +THEME_COLOR=#000000 + +# --- Ikony (ścieżki względem roota strony) --- +FAVICON_48=/assets/imgs/favico/48x48.png +FAVICON_180=/assets/imgs/favico/180x180.png +FAVICON_512=/assets/imgs/favico/512x512.png + +# --- YouTube --- +VIDEO_ID_FHD=kujXXtoHUbc +POST_VIDEO_REDIRECT_URL_FHD= +VIDEO_ID_VERTICAL= +POST_VIDEO_REDIRECT_URL_VERTICAL= +POST_VIDEO_REDIRECT_ENABLED=false diff --git a/docker-compose-example/README.md b/docker-compose-example/README.md index f2204ce..5c1f966 100644 --- a/docker-compose-example/README.md +++ b/docker-compose-example/README.md @@ -1,59 +1,139 @@ # Agencja AI - Frontend (Dokploy Deployment) -Strona internetowa ze statycznym obrazem tła od pierwszej klatki i krótkim intro wideo streamowanym z YouTube (bez widocznych ramek playera) po załadowaniu. +Statyczna strona z intro YouTube (short FHD 16:9 lub vertical 9:16 — wybór po formacie viewportu). Tło przed filmem to miniaturka z YouTube. Konfiguracja w `.env`, HTML generowany przy starcie kontenera. ## Struktura projektu ``` site-agencja-ai-com/ -├── docker-compose.yml # Lokalny dev (build: context .) -├── Dockerfile # Kontener nginx 1.27 Alpine -├── default.conf # Konfiguracja nginx -├── index.html # Strona ze streamem YouTube -├── assets/imgs/www-bg-agencja-ai-com-001.jpg # Obraz tła (367 KB) -├── assets/imgs/favico/ # Favicony (48, 180, 512 px) -├── site.webmanifest # Manifest PWA / Android -├── .dockerignore # Ignorowane pliki przy budowie -├── .gitignore # Git ignore -└── docker-compose-example/ # Pliki deploymentowe Dokploy - ├── docker-compose.yml # Dokploy: image + git clone - ├── .env # Wymagany przez Dokploy (pusty) - └── README.md # Ten plik +├── docker-compose.yml # Lokalny dev (build z Dockerfile) +├── Dockerfile +├── default.conf +├── nginx.conf +├── index.html.template # Szablon strony +├── generate-from-env.sh # Generator HTML z .env +├── assets/imgs/favico/ # Favicony +├── site.webmanifest.template +└── docker-compose-example/ # Pliki deploymentowe Dokploy + ├── docker-compose.yml + ├── .env.example + └── README.md ``` -## Dwa podejścia do deployu +## Dokploy deployment -### 1. Lokalny development (docker-compose.yml w root) - -Używa `build:` z Dockerfile — buduje obraz lokalnie. Port hosta ustawiasz w `.env`: - -```bash -cp .env.example .env # opcjonalnie zmień FRONTEND_PORT -cd /home/tomasz-syn-grzegorza/Pulpit/cursor/site-agencja-ai-com/ -docker compose up -d --build -# Strona na http://localhost:${FRONTEND_PORT} (domyślnie 8081) -``` - -### 2. Dokploy deployment (docker-compose-example/) - -Używa `image:` + runtime git clone — nie wymaga lokalnego build contextu, bo Dokploy kopiuje tylko `docker-compose.yml` i `.env` na serwer. +Dokploy kopiuje na serwer **tylko** `docker-compose.yml` + `.env` — nie ma tam `Dockerfile` ani pełnego repo. Kontener przy starcie robi `git clone` publicznego repo, generuje HTML z `.env` i uruchamia nginx. **Kroki w Dokploy UI:** -1. Nowa aplikacja → wklej zawartość `docker-compose-example/docker-compose.yml` -2. Wklej `.env` z tego samego katalogu (ustaw `FRONTEND_PORT` jeśli potrzebny) -3. Ustaw FQDN w UI (np. `agencja-ai.com`) -4. Deploy +1. Nowa aplikacja (Compose) → wklej `docker-compose-example/docker-compose.yml` +2. Wklej `.env` (na podstawie `.env.example`) — ustaw `FRONTEND_PORT`, `VIDEO_ID_*`, opisy SEO +3. **Domains** → dodaj **oba** hosty (Container Port = `80`, HTTPS = Let's Encrypt): + - `agencja-ai.com` + - `www.agencja-ai.com` ← **wymagane** — bez tego www dostaje domyślny cert Traefika (CN=*, self-signed) i telefony pokazują „serwer nie potrafi udowodnić tożsamości” +4. (Opcjonalnie) **Advanced → Redirects** → preset **www → non-www** +5. Deploy -**Porty:** `FRONTEND_PORT` z `.env` mapuje port hosta na nginx w kontenerze (`${FRONTEND_PORT}:80`). Wewnątrz kontenera nginx zawsze nasłuchuje na porcie 80 (`default.conf`). Dokploy/Traefik routuje po FQDN do tego samego wewnętrznego portu 80. +**Porty:** compose mapuje `${FRONTEND_PORT}:80`. Traefik/Dokploy dodatkowo routuje po FQDN do wewnętrznego portu 80 kontenera. + +**Repo musi być publiczne** w Gitea — clone bez tokenu. Po `git push` zrób redeploy, żeby kontener pobrał nowy kod. ## Jak to działa -1. **Obraz tła od startu** - Lokalny JPG widoczny od pierwszej klatki; loader znika po załadowaniu obrazka -2. **Intro wideo YouTube** - Standardowy embed YouTube (`kujXXtoHUbc`) jako nakładka po załadowaniu playera -3. **Po zakończeniu filmu** - Warstwa wideo znika, obraz JPG zostaje -4. **Safety timeout 15s** - Jeśli YouTube nie załaduje się w czasie → strona zostaje na obrazku tła +1. **Start kontenera** — `git clone` publicznego repo do `/app` +2. **`generate-from-env.sh`** — buduje `index.html` i `site.webmanifest` ze zmiennych `.env` +3. **Kopiowanie** — `assets/`, `default.conf`, `nginx.conf` → nginx +4. **Cleanup** — `rm -rf /app` (brak pełnego repo na dysku kontenera) +5. **Intro wideo** — YouTube IFrame API, autoplay; po filmie redirect lub tło -## Modyfikacja +## Konfiguracja (.env) -- **Inny film:** zmień `VIDEO_ID` w `index.html` -- **Inny port hosta:** zmień `FRONTEND_PORT` w `.env` (nginx w kontenerze zostaje na porcie 80) +| Zmienna | Opis | +|---------|------| +| `FRONTEND_PORT` | Port na hoście (np. `8081`) | +| `SITE_TITLE`, `SITE_BASE_URL`, `SITE_DESCRIPTION` | Meta i SEO | +| `FACEBOOK_DESCRIPTION`, `TWITTER_DESCRIPTION` | Opisy social media | +| `VIDEO_ID_FHD` / `VIDEO_ID_VERTICAL` | ID shortów YouTube | +| `POST_VIDEO_REDIRECT_URL_*` | URL po zakończeniu filmu | +| `POST_VIDEO_REDIRECT_ENABLED` | `true` = redirect, `false` = zostaje tło | + +## Lokalny development + +```bash +cp .env.example .env +./dev.sh up +# Strona na http://localhost:${FRONTEND_PORT} (domyślnie 8081) +# Edytuj .env → ./dev.sh config → F5 w przeglądarce +``` + +Produkcyjny build lokalnie: + +```bash +docker compose up -d --build +``` + +## Hardening bezpieczeństwa (wbudowany) + +- Repo publiczne — brak tokena GITEA w kontenerze +- Po starcie `/app` usuwane — brak compose/README/szablonów na dysku +- nginx: whitelist ścieżek, blokada `.env`/`.sh`/`.template`, nagłówki CSP/Referrer-Policy +- CSP z `'unsafe-inline'` dla skryptu YouTube (inline JS w `index.html`) +- `server_tokens off` — ukryta wersja nginx + +## Weryfikacja po deployu + +```bash +# Strona działa +curl -sI https://agencja-ai.com/ | head -5 + +# Wrażliwe pliki niedostępne (404) +curl -sI https://agencja-ai.com/.env | grep HTTP +curl -sI https://agencja-ai.com/generate-from-env.sh | grep HTTP +curl -sI https://agencja-ai.com/random-path | grep HTTP + +# Nagłówki bezpieczeństwa +curl -sI https://agencja-ai.com/ | grep -iE 'content-security-policy|referrer-policy|permissions-policy' + +# W kontenerze (na serwerze) +docker logs agencja-ai-com-frontend --tail 30 +docker exec agencja-ai-com-frontend ls /app 2>&1 # powinno: No such file +docker exec agencja-ai-com-frontend head -3 /usr/share/nginx/html/index.html +``` + +## Rozwiązywanie problemów + +### Kontener `Restarting` / unhealthy + +```bash +docker logs agencja-ai-com-frontend --tail 50 +``` + +Typowe przyczyny: +- Repo **prywatne** → ustaw jako Public w Gitea +- Brak lub błędny `.env` w Dokploy +- Błąd w `generate-from-env.sh` (sprawdź logi) + +### Strona czarna / loader bez końca + +- Sprawdź konsolę przeglądarki (CSP / YouTube) +- Upewnij się, że `VIDEO_ID_*` są poprawne w `.env` +- Po zmianie `default.conf` lub szablonu: `git push` + redeploy + +### Zmiana konfiguracji po deployu + +1. Edytuj `.env` w Dokploy +2. Redeploy / restart kontenera (przy starcie ponownie klonuje repo i generuje HTML) + +### Błąd certyfikatu SSL na telefonie („serwer nie potrafi udowodnić…”) + +**Diagnoza (stan typowy):** +- `https://agencja-ai.com` — certyfikat Let's Encrypt OK +- `https://www.agencja-ai.com` — **self-signed** (Traefik default, CN=`*`) → błąd na Androidzie / iPhone / WebView Facebooka + +**Naprawa w Dokploy (zalecane):** +1. **Domains** → **Add Domain** +2. Host: `www.agencja-ai.com`, Port: `80`, HTTPS: włączone, Certificate: **letsencrypt** +3. Deploy / poczekaj ~30 s na wystawienie certyfikatu +4. Sprawdź: `curl -I https://www.agencja-ai.com` (powinno być HTTP 200 lub 301, bez błędu SSL) +5. (Opcjonalnie) **Advanced → Redirects** → **www to non-www** + +**Facebook / in-app browser:** linki w postach powinny wskazywać `https://agencja-ai.com` (bez www). diff --git a/docker-compose-example/docker-compose.yml b/docker-compose-example/docker-compose.yml index 1e993ea..0bea192 100644 --- a/docker-compose-example/docker-compose.yml +++ b/docker-compose-example/docker-compose.yml @@ -1,12 +1,14 @@ # ============================================================ # Agencja AI - Docker Compose (Dokploy / manual) # ============================================================ -# Dokploy-compatible pattern: uses pre-built image + runtime git clone. +# Dokploy-compatible pattern: pre-built nginx image + runtime git clone. # Dokploy only copies docker-compose.yml + .env to the server, so local -# build context with a Dockerfile does not work. This approach matches -# other working apps on our servers (e.g. wdj-hermes-frontend-matrix). +# build context with a Dockerfile does not work. # -# Port hosta: FRONTEND_PORT z .env (domyślnie 8080) → nginx w kontenerze na porcie 80. +# Repo publiczne — clone bez tokenu. HTML generowany z .env przy starcie. +# Po setupie /app jest usuwane (brak pełnego repo na dysku kontenera). +# +# Port hosta: FRONTEND_PORT z .env → nginx w kontenerze na porcie 80. # Dokploy/Traefik routuje po FQDN do wewnętrznego portu 80 kontenera. # ============================================================ @@ -15,19 +17,31 @@ services: image: nginx:1.27-alpine container_name: agencja-ai-com-frontend restart: unless-stopped + ports: - "${FRONTEND_PORT:-8080}:80" + env_file: + - .env + command: > - sh -c " - apk add --no-cache git && + sh -ce " + echo '[agencja-ai] Installing git + gettext...' && + apk add --no-cache git gettext && + echo '[agencja-ai] Cloning repo...' && rm -rf /app && - git clone https://gitea.expert-it.agency-ai.dev/tomasz-syn-grzegorza/site-agencja-ai-com.git /app && - cp /app/index.html /usr/share/nginx/html/index.html && - cp /app/site.webmanifest /usr/share/nginx/html/site.webmanifest && + git clone --depth 1 https://gitea.expert-it.agency-ai.dev/tomasz-syn-grzegorza/site-agencja-ai-com.git /app && + echo '[agencja-ai] Generating index.html from env...' && + chmod +x /app/generate-from-env.sh && + TEMPLATE_DIR=/app /app/generate-from-env.sh /usr/share/nginx/html && + echo '[agencja-ai] Copying assets and nginx config...' && cp -r /app/assets /usr/share/nginx/html/assets && - rm /etc/nginx/conf.d/default.conf && + rm -f /etc/nginx/conf.d/default.conf && cp /app/default.conf /etc/nginx/conf.d/default.conf && + cp /app/nginx.conf /etc/nginx/nginx.conf && + echo '[agencja-ai] Removing cloned repo from container...' && + rm -rf /app && + echo '[agencja-ai] Starting nginx...' && nginx -g 'daemon off;' " @@ -36,7 +50,7 @@ services: interval: 30s timeout: 10s retries: 3 - start_period: 45s + start_period: 60s deploy: resources: diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml index 5ce27e1..ad9779e 100644 --- a/docker-compose.dev.yml +++ b/docker-compose.dev.yml @@ -5,6 +5,8 @@ services: frontend: image: nginx:1.27-alpine build: !reset null + env_file: + - .env volumes: - ./index.html:/usr/share/nginx/html/index.html:ro - ./site.webmanifest:/usr/share/nginx/html/site.webmanifest:ro diff --git a/docker-compose.yml b/docker-compose.yml index b659572..7fdb238 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -8,5 +8,7 @@ services: dockerfile: Dockerfile container_name: agencja-ai-com-frontend restart: unless-stopped + env_file: + - .env ports: - "${FRONTEND_PORT:-8080}:80" diff --git a/docker-entrypoint.sh b/docker-entrypoint.sh new file mode 100755 index 0000000..465d79c --- /dev/null +++ b/docker-entrypoint.sh @@ -0,0 +1,4 @@ +#!/bin/sh +TEMPLATE_DIR=/opt/agencja-ai /opt/agencja-ai/generate-from-env.sh /usr/share/nginx/html +rm -rf /opt/agencja-ai +unset GITEA_REPO_URL FRONTEND_PORT 2>/dev/null || true diff --git a/generate-from-env.sh b/generate-from-env.sh new file mode 100755 index 0000000..4b1b235 --- /dev/null +++ b/generate-from-env.sh @@ -0,0 +1,68 @@ +#!/bin/sh +# Generuje index.html i site.webmanifest z zmiennych środowiskowych (.env). +set -eu + +SCRIPT_DIR="$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)" +TEMPLATE_DIR="${TEMPLATE_DIR:-$SCRIPT_DIR}" +cd "$SCRIPT_DIR" + +if [ -n "${1:-}" ]; then + OUTPUT_DIR="$1" +elif [ -d /usr/share/nginx/html ]; then + OUTPUT_DIR="/usr/share/nginx/html" +else + OUTPUT_DIR="." +fi + +# --- Domyślne wartości szablonu --- +SITE_TITLE="${SITE_TITLE:-Agencja AI}" +SITE_SHORT_NAME="${SITE_SHORT_NAME:-Agencja AI}" +SITE_BASE_URL="${SITE_BASE_URL:-https://agencja-ai.com}" +SITE_DESCRIPTION="${SITE_DESCRIPTION:-Lokalne modele LLM, własny serwer AI (Proxmox, TrueNAS, Ubuntu), RAG, Ollama, Open WebUI, bezpieczny self-hosting i wdrożenia biznesowe lokalnej AI.}" +FACEBOOK_DESCRIPTION="${FACEBOOK_DESCRIPTION:-Lokalne modele LLM (Llama 3, Mistral, Command-R, Phi-3, DeepSeek i inne). Budowa własnego serwera AI na bazie Proxmox, TrueNAS SCALE oraz Ubuntu. Optymalizacja GPU w środowisku Linux (Ubuntu, OpenSUSE, Proxmox). Technologia RAG – jak podpiąć Twoją firmową wiedzę do lokalnego AI. Prywatne instancje Ollama, Open WebUI, AnythingLLM, PrivateGPT. Bezpieczny self-hosting – od konfiguracji Proxmox VE, przez TrueNAS, po produkcyjne wdrożenia. Porównania sprzętu, zużycie prądu, chłodzenie i realne koszty posiadania własnego AI. Automatyzacje, integracje i zaawansowane zastosowania biznesowe lokalnej AI.}" +TWITTER_DESCRIPTION="${TWITTER_DESCRIPTION:-$FACEBOOK_DESCRIPTION}" +SITE_OG_IMAGE_ALT="${SITE_OG_IMAGE_ALT:-Agencja AI — lokalna sztuczna inteligencja i self-hosting}" +THEME_COLOR="${THEME_COLOR:-#000000}" + +FAVICON_48="${FAVICON_48:-/assets/imgs/favico/48x48.png}" +FAVICON_180="${FAVICON_180:-/assets/imgs/favico/180x180.png}" +FAVICON_512="${FAVICON_512:-/assets/imgs/favico/512x512.png}" + +VIDEO_ID_FHD="${VIDEO_ID_FHD:-${VIDEO_ID:-kujXXtoHUbc}}" +VIDEO_ID_VERTICAL="${VIDEO_ID_VERTICAL:-}" +POST_VIDEO_REDIRECT_URL_FHD="${POST_VIDEO_REDIRECT_URL_FHD:-${POST_VIDEO_REDIRECT_URL:-}}" +POST_VIDEO_REDIRECT_URL_VERTICAL="${POST_VIDEO_REDIRECT_URL_VERTICAL:-}" +POST_VIDEO_REDIRECT_ENABLED="${POST_VIDEO_REDIRECT_ENABLED:-false}" +POST_VIDEO_REDIRECT_ENABLED_FHD="${POST_VIDEO_REDIRECT_ENABLED_FHD:-$POST_VIDEO_REDIRECT_ENABLED}" +POST_VIDEO_REDIRECT_ENABLED_VERTICAL="${POST_VIDEO_REDIRECT_ENABLED_VERTICAL:-$POST_VIDEO_REDIRECT_ENABLED}" + +OG_IMAGE_URL="https://img.youtube.com/vi/${VIDEO_ID_FHD}/maxresdefault.jpg" + +escape_js() { + printf '%s' "$1" | sed "s/'/\\\\'/g" +} + +parse_bool_js() { + case "$(printf '%s' "$1" | tr '[:upper:]' '[:lower:]')" in + true|1|yes|on) printf 'true' ;; + *) printf 'false' ;; + esac +} + +VIDEO_ID_FHD_JS="$(escape_js "$VIDEO_ID_FHD")" +VIDEO_ID_VERTICAL_JS="$(escape_js "$VIDEO_ID_VERTICAL")" +POST_VIDEO_REDIRECT_URL_FHD_JS="$(escape_js "$POST_VIDEO_REDIRECT_URL_FHD")" +POST_VIDEO_REDIRECT_URL_VERTICAL_JS="$(escape_js "$POST_VIDEO_REDIRECT_URL_VERTICAL")" +POST_VIDEO_REDIRECT_ENABLED_FHD_JS="$(parse_bool_js "$POST_VIDEO_REDIRECT_ENABLED_FHD")" +POST_VIDEO_REDIRECT_ENABLED_VERTICAL_JS="$(parse_bool_js "$POST_VIDEO_REDIRECT_ENABLED_VERTICAL")" + +export SITE_TITLE SITE_SHORT_NAME SITE_BASE_URL SITE_DESCRIPTION FACEBOOK_DESCRIPTION TWITTER_DESCRIPTION SITE_OG_IMAGE_ALT +export THEME_COLOR FAVICON_48 FAVICON_180 FAVICON_512 OG_IMAGE_URL +export VIDEO_ID_FHD_JS VIDEO_ID_VERTICAL_JS POST_VIDEO_REDIRECT_URL_FHD_JS POST_VIDEO_REDIRECT_URL_VERTICAL_JS POST_VIDEO_REDIRECT_ENABLED_FHD_JS POST_VIDEO_REDIRECT_ENABLED_VERTICAL_JS + +TEMPLATE_VARS='${SITE_TITLE} ${SITE_SHORT_NAME} ${SITE_BASE_URL} ${SITE_DESCRIPTION} ${FACEBOOK_DESCRIPTION} ${TWITTER_DESCRIPTION} ${SITE_OG_IMAGE_ALT} ${THEME_COLOR} ${FAVICON_48} ${FAVICON_180} ${FAVICON_512} ${OG_IMAGE_URL} ${VIDEO_ID_FHD_JS} ${VIDEO_ID_VERTICAL_JS} ${POST_VIDEO_REDIRECT_URL_FHD_JS} ${POST_VIDEO_REDIRECT_URL_VERTICAL_JS} ${POST_VIDEO_REDIRECT_ENABLED_FHD_JS} ${POST_VIDEO_REDIRECT_ENABLED_VERTICAL_JS}' + +mkdir -p "$OUTPUT_DIR" + +envsubst "$TEMPLATE_VARS" < "$TEMPLATE_DIR/index.html.template" > "$OUTPUT_DIR/index.html" +envsubst "$TEMPLATE_VARS" < "$TEMPLATE_DIR/site.webmanifest.template" > "$OUTPUT_DIR/site.webmanifest" diff --git a/index.html b/index.html.template similarity index 55% rename from index.html rename to index.html.template index 40afa19..ec19caa 100644 --- a/index.html +++ b/index.html.template @@ -3,29 +3,28 @@ - Agencja AI - - - + ${SITE_TITLE} + + - + - - - + + + - - + + - - - - - - + + + + + + - - + +