Migrate to env-driven templates and slavic-pulse feature set.
Replace hardcoded HTML with .env generation, YouTube thumbnail backgrounds, responsive FHD/vertical intro, hardened nginx, and updated Dokploy deploy flow. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -7,3 +7,7 @@ node_modules/
|
|||||||
.env
|
.env
|
||||||
.venv/
|
.venv/
|
||||||
docker-compose-example/
|
docker-compose-example/
|
||||||
|
docker-compose*.yml
|
||||||
|
dev.sh
|
||||||
|
index.html
|
||||||
|
site.webmanifest
|
||||||
|
|||||||
+35
-3
@@ -1,8 +1,40 @@
|
|||||||
# Agencja AI — Environment Configuration (Local Development)
|
# Agencja AI — Environment Configuration
|
||||||
# Copy to .env and fill in your values.
|
# Copy to .env and fill in your values.
|
||||||
# Never commit .env with real credentials to version control.
|
# 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.
|
# Port na hoście (localhost). W kontenerze nginx zawsze nasłuchuje na 80.
|
||||||
# Mapowanie w docker-compose.yml: ${FRONTEND_PORT}:80
|
|
||||||
FRONTEND_PORT=8081
|
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
|
||||||
|
|||||||
@@ -2,6 +2,10 @@
|
|||||||
.env
|
.env
|
||||||
*.env.local
|
*.env.local
|
||||||
|
|
||||||
|
# Generated from .env at dev/build time
|
||||||
|
index.html
|
||||||
|
site.webmanifest
|
||||||
|
|
||||||
# Python
|
# Python
|
||||||
__pycache__/
|
__pycache__/
|
||||||
.venv/
|
.venv/
|
||||||
|
|||||||
+12
-6
@@ -1,19 +1,25 @@
|
|||||||
FROM nginx:1.27-alpine AS production
|
FROM nginx:1.27-alpine AS production
|
||||||
|
|
||||||
|
RUN apk add --no-cache gettext
|
||||||
|
|
||||||
# Remove default nginx config
|
# Remove default nginx config
|
||||||
RUN rm /etc/nginx/conf.d/default.conf
|
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 default.conf /etc/nginx/conf.d/default.conf
|
||||||
|
|
||||||
# Copy web files (index.html + assets)
|
# Copy static assets
|
||||||
COPY index.html /usr/share/nginx/html/index.html
|
|
||||||
COPY site.webmanifest /usr/share/nginx/html/site.webmanifest
|
|
||||||
COPY assets/ /usr/share/nginx/html/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
|
EXPOSE 80
|
||||||
|
|
||||||
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
|
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
|
||||||
CMD wget -qO- http://localhost:80/ || exit 1
|
CMD wget -qO- http://localhost:80/ || exit 1
|
||||||
|
|
||||||
CMD ["nginx", "-g", "daemon off;"]
|
|
||||||
|
|||||||
+25
-2
@@ -10,9 +10,24 @@ server {
|
|||||||
gzip_types text/plain text/css application/json application/javascript text/xml application/xml image/svg+xml;
|
gzip_types text/plain text/css application/json application/javascript text/xml application/xml image/svg+xml;
|
||||||
gzip_min_length 1024;
|
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
|
# Main page
|
||||||
location / {
|
location = / {
|
||||||
try_files $uri $uri/ /index.html;
|
try_files /index.html =404;
|
||||||
|
}
|
||||||
|
|
||||||
|
location = /index.html {
|
||||||
|
add_header Cache-Control "no-cache";
|
||||||
}
|
}
|
||||||
|
|
||||||
# Web app manifest
|
# Web app manifest
|
||||||
@@ -29,7 +44,15 @@ server {
|
|||||||
try_files $uri =404;
|
try_files $uri =404;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Everything else → 404 (no fallback to index.html)
|
||||||
|
location / {
|
||||||
|
return 404;
|
||||||
|
}
|
||||||
|
|
||||||
# Security headers
|
# Security headers
|
||||||
add_header X-Frame-Options SAMEORIGIN always;
|
add_header X-Frame-Options SAMEORIGIN always;
|
||||||
add_header X-Content-Type-Options nosniff 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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,12 +10,22 @@ if [[ ! -f .env ]]; then
|
|||||||
echo "Utworzono .env z .env.example"
|
echo "Utworzono .env z .env.example"
|
||||||
fi
|
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
|
case "${1:-up}" in
|
||||||
up)
|
up)
|
||||||
|
generate_from_env
|
||||||
$COMPOSE up -d
|
$COMPOSE up -d
|
||||||
echo ""
|
echo ""
|
||||||
echo "Dev server: http://localhost:${FRONTEND_PORT:-8081}"
|
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)
|
down)
|
||||||
$COMPOSE down
|
$COMPOSE down
|
||||||
@@ -24,14 +34,19 @@ case "${1:-up}" in
|
|||||||
$COMPOSE logs -f
|
$COMPOSE logs -f
|
||||||
;;
|
;;
|
||||||
restart)
|
restart)
|
||||||
|
generate_from_env
|
||||||
$COMPOSE restart
|
$COMPOSE restart
|
||||||
;;
|
;;
|
||||||
|
config)
|
||||||
|
generate_from_env
|
||||||
|
echo "Wygenerowano index.html i site.webmanifest z .env."
|
||||||
|
;;
|
||||||
reload-nginx)
|
reload-nginx)
|
||||||
docker exec agencja-ai-com-frontend nginx -s reload
|
docker exec agencja-ai-com-frontend nginx -s reload
|
||||||
echo "Nginx przeładowany (po zmianie default.conf)."
|
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
|
exit 1
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
|
|||||||
@@ -1,9 +1,27 @@
|
|||||||
# Agencja AI — Environment Configuration (Dokploy)
|
# Agencja AI — Environment Configuration (Dokploy)
|
||||||
# Copy to .env before deploy if needed.
|
# Po zmianie wartości uruchom redeploy w Dokploy.
|
||||||
# Never commit .env with real credentials to version control.
|
|
||||||
|
|
||||||
# --- Frontend Server ---
|
# --- 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
|
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
|
||||||
|
|||||||
@@ -1,59 +1,139 @@
|
|||||||
# Agencja AI - Frontend (Dokploy Deployment)
|
# 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
|
## Struktura projektu
|
||||||
|
|
||||||
```
|
```
|
||||||
site-agencja-ai-com/
|
site-agencja-ai-com/
|
||||||
├── docker-compose.yml # Lokalny dev (build: context .)
|
├── docker-compose.yml # Lokalny dev (build z Dockerfile)
|
||||||
├── Dockerfile # Kontener nginx 1.27 Alpine
|
├── Dockerfile
|
||||||
├── default.conf # Konfiguracja nginx
|
├── default.conf
|
||||||
├── index.html # Strona ze streamem YouTube
|
├── nginx.conf
|
||||||
├── assets/imgs/www-bg-agencja-ai-com-001.jpg # Obraz tła (367 KB)
|
├── index.html.template # Szablon strony
|
||||||
├── assets/imgs/favico/ # Favicony (48, 180, 512 px)
|
├── generate-from-env.sh # Generator HTML z .env
|
||||||
├── site.webmanifest # Manifest PWA / Android
|
├── assets/imgs/favico/ # Favicony
|
||||||
├── .dockerignore # Ignorowane pliki przy budowie
|
├── site.webmanifest.template
|
||||||
├── .gitignore # Git ignore
|
└── docker-compose-example/ # Pliki deploymentowe Dokploy
|
||||||
└── docker-compose-example/ # Pliki deploymentowe Dokploy
|
├── docker-compose.yml
|
||||||
├── docker-compose.yml # Dokploy: image + git clone
|
├── .env.example
|
||||||
├── .env # Wymagany przez Dokploy (pusty)
|
└── README.md
|
||||||
└── README.md # Ten plik
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## Dwa podejścia do deployu
|
## Dokploy deployment
|
||||||
|
|
||||||
### 1. Lokalny development (docker-compose.yml w root)
|
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.
|
||||||
|
|
||||||
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.
|
|
||||||
|
|
||||||
**Kroki w Dokploy UI:**
|
**Kroki w Dokploy UI:**
|
||||||
1. Nowa aplikacja → wklej zawartość `docker-compose-example/docker-compose.yml`
|
1. Nowa aplikacja (Compose) → wklej `docker-compose-example/docker-compose.yml`
|
||||||
2. Wklej `.env` z tego samego katalogu (ustaw `FRONTEND_PORT` jeśli potrzebny)
|
2. Wklej `.env` (na podstawie `.env.example`) — ustaw `FRONTEND_PORT`, `VIDEO_ID_*`, opisy SEO
|
||||||
3. Ustaw FQDN w UI (np. `agencja-ai.com`)
|
3. **Domains** → dodaj **oba** hosty (Container Port = `80`, HTTPS = Let's Encrypt):
|
||||||
4. Deploy
|
- `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
|
## Jak to działa
|
||||||
|
|
||||||
1. **Obraz tła od startu** - Lokalny JPG widoczny od pierwszej klatki; loader znika po załadowaniu obrazka
|
1. **Start kontenera** — `git clone` publicznego repo do `/app`
|
||||||
2. **Intro wideo YouTube** - Standardowy embed YouTube (`kujXXtoHUbc`) jako nakładka po załadowaniu playera
|
2. **`generate-from-env.sh`** — buduje `index.html` i `site.webmanifest` ze zmiennych `.env`
|
||||||
3. **Po zakończeniu filmu** - Warstwa wideo znika, obraz JPG zostaje
|
3. **Kopiowanie** — `assets/`, `default.conf`, `nginx.conf` → nginx
|
||||||
4. **Safety timeout 15s** - Jeśli YouTube nie załaduje się w czasie → strona zostaje na obrazku tła
|
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`
|
| Zmienna | Opis |
|
||||||
- **Inny port hosta:** zmień `FRONTEND_PORT` w `.env` (nginx w kontenerze zostaje na porcie 80)
|
|---------|------|
|
||||||
|
| `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).
|
||||||
|
|||||||
@@ -1,12 +1,14 @@
|
|||||||
# ============================================================
|
# ============================================================
|
||||||
# Agencja AI - Docker Compose (Dokploy / manual)
|
# 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
|
# Dokploy only copies docker-compose.yml + .env to the server, so local
|
||||||
# build context with a Dockerfile does not work. This approach matches
|
# build context with a Dockerfile does not work.
|
||||||
# other working apps on our servers (e.g. wdj-hermes-frontend-matrix).
|
|
||||||
#
|
#
|
||||||
# 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.
|
# Dokploy/Traefik routuje po FQDN do wewnętrznego portu 80 kontenera.
|
||||||
# ============================================================
|
# ============================================================
|
||||||
|
|
||||||
@@ -15,19 +17,31 @@ services:
|
|||||||
image: nginx:1.27-alpine
|
image: nginx:1.27-alpine
|
||||||
container_name: agencja-ai-com-frontend
|
container_name: agencja-ai-com-frontend
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
|
|
||||||
ports:
|
ports:
|
||||||
- "${FRONTEND_PORT:-8080}:80"
|
- "${FRONTEND_PORT:-8080}:80"
|
||||||
|
|
||||||
|
env_file:
|
||||||
|
- .env
|
||||||
|
|
||||||
command: >
|
command: >
|
||||||
sh -c "
|
sh -ce "
|
||||||
apk add --no-cache git &&
|
echo '[agencja-ai] Installing git + gettext...' &&
|
||||||
|
apk add --no-cache git gettext &&
|
||||||
|
echo '[agencja-ai] Cloning repo...' &&
|
||||||
rm -rf /app &&
|
rm -rf /app &&
|
||||||
git clone https://gitea.expert-it.agency-ai.dev/tomasz-syn-grzegorza/site-agencja-ai-com.git /app &&
|
git clone --depth 1 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 &&
|
echo '[agencja-ai] Generating index.html from env...' &&
|
||||||
cp /app/site.webmanifest /usr/share/nginx/html/site.webmanifest &&
|
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 &&
|
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/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;'
|
nginx -g 'daemon off;'
|
||||||
"
|
"
|
||||||
|
|
||||||
@@ -36,7 +50,7 @@ services:
|
|||||||
interval: 30s
|
interval: 30s
|
||||||
timeout: 10s
|
timeout: 10s
|
||||||
retries: 3
|
retries: 3
|
||||||
start_period: 45s
|
start_period: 60s
|
||||||
|
|
||||||
deploy:
|
deploy:
|
||||||
resources:
|
resources:
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ services:
|
|||||||
frontend:
|
frontend:
|
||||||
image: nginx:1.27-alpine
|
image: nginx:1.27-alpine
|
||||||
build: !reset null
|
build: !reset null
|
||||||
|
env_file:
|
||||||
|
- .env
|
||||||
volumes:
|
volumes:
|
||||||
- ./index.html:/usr/share/nginx/html/index.html:ro
|
- ./index.html:/usr/share/nginx/html/index.html:ro
|
||||||
- ./site.webmanifest:/usr/share/nginx/html/site.webmanifest:ro
|
- ./site.webmanifest:/usr/share/nginx/html/site.webmanifest:ro
|
||||||
|
|||||||
@@ -8,5 +8,7 @@ services:
|
|||||||
dockerfile: Dockerfile
|
dockerfile: Dockerfile
|
||||||
container_name: agencja-ai-com-frontend
|
container_name: agencja-ai-com-frontend
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
|
env_file:
|
||||||
|
- .env
|
||||||
ports:
|
ports:
|
||||||
- "${FRONTEND_PORT:-8080}:80"
|
- "${FRONTEND_PORT:-8080}:80"
|
||||||
|
|||||||
Executable
+4
@@ -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
|
||||||
Executable
+68
@@ -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"
|
||||||
@@ -3,29 +3,28 @@
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>Agencja AI</title>
|
<title>${SITE_TITLE}</title>
|
||||||
<!-- SITE_BASE_URL: https://agencja-ai.com -->
|
<meta name="description" content="${SITE_DESCRIPTION}">
|
||||||
<meta name="description" content="Lokalne modele LLM, własny serwer AI (Proxmox, TrueNAS, Ubuntu), RAG, Ollama, Open WebUI, bezpieczny self-hosting i wdrożenia biznesowe lokalnej AI.">
|
<link rel="canonical" href="${SITE_BASE_URL}/">
|
||||||
<link rel="canonical" href="https://agencja-ai.com/">
|
|
||||||
<meta property="og:type" content="website">
|
<meta property="og:type" content="website">
|
||||||
<meta property="og:site_name" content="Agencja AI">
|
<meta property="og:site_name" content="${SITE_TITLE}">
|
||||||
<meta property="og:locale" content="pl_PL">
|
<meta property="og:locale" content="pl_PL">
|
||||||
<meta property="og:title" content="Agencja AI">
|
<meta property="og:title" content="${SITE_TITLE}">
|
||||||
<meta property="og:url" content="https://agencja-ai.com/">
|
<meta property="og:url" content="${SITE_BASE_URL}/">
|
||||||
<meta property="og:image" content="https://agencja-ai.com/assets/imgs/www-bg-agencja-ai-com-001.jpg">
|
<meta property="og:image" content="${OG_IMAGE_URL}">
|
||||||
<meta property="og:image:type" content="image/jpeg">
|
<meta property="og:image:type" content="image/jpeg">
|
||||||
<meta property="og:image:alt" content="Agencja AI — lokalna sztuczna inteligencja i self-hosting">
|
<meta property="og:image:alt" content="${SITE_OG_IMAGE_ALT}">
|
||||||
<meta property="og:description" content="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.">
|
<meta property="og:description" content="${FACEBOOK_DESCRIPTION}">
|
||||||
<meta name="twitter:card" content="summary_large_image">
|
<meta name="twitter:card" content="summary_large_image">
|
||||||
<meta name="twitter:title" content="Agencja AI">
|
<meta name="twitter:title" content="${SITE_TITLE}">
|
||||||
<meta name="twitter:image" content="https://agencja-ai.com/assets/imgs/www-bg-agencja-ai-com-001.jpg">
|
<meta name="twitter:image" content="${OG_IMAGE_URL}">
|
||||||
<meta name="twitter:description" content="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.">
|
<meta name="twitter:description" content="${TWITTER_DESCRIPTION}">
|
||||||
<link rel="icon" type="image/png" sizes="48x48" href="/assets/imgs/favico/48x48.png">
|
<link rel="icon" type="image/png" sizes="48x48" href="${FAVICON_48}">
|
||||||
<link rel="icon" type="image/png" sizes="512x512" href="/assets/imgs/favico/512x512.png">
|
<link rel="icon" type="image/png" sizes="512x512" href="${FAVICON_512}">
|
||||||
<link rel="apple-touch-icon" sizes="180x180" href="/assets/imgs/favico/180x180.png">
|
<link rel="apple-touch-icon" sizes="180x180" href="${FAVICON_180}">
|
||||||
<link rel="manifest" href="/site.webmanifest">
|
<link rel="manifest" href="/site.webmanifest">
|
||||||
<meta name="theme-color" content="#000000">
|
<meta name="theme-color" content="${THEME_COLOR}">
|
||||||
<link rel="preload" as="image" href="/assets/imgs/www-bg-agencja-ai-com-001.jpg">
|
<link rel="preconnect" href="https://img.youtube.com">
|
||||||
<link rel="preconnect" href="https://www.youtube.com">
|
<link rel="preconnect" href="https://www.youtube.com">
|
||||||
<link rel="preconnect" href="https://www.google.com">
|
<link rel="preconnect" href="https://www.google.com">
|
||||||
<style>
|
<style>
|
||||||
@@ -146,7 +145,7 @@
|
|||||||
<div id="stage-root">
|
<div id="stage-root">
|
||||||
<div id="stage">
|
<div id="stage">
|
||||||
<div id="stage-image" class="stage-layer">
|
<div id="stage-image" class="stage-layer">
|
||||||
<img src="/assets/imgs/www-bg-agencja-ai-com-001.jpg" alt="">
|
<img id="stage-bg" src="" alt="${SITE_OG_IMAGE_ALT}">
|
||||||
</div>
|
</div>
|
||||||
<div id="stage-video" class="stage-layer">
|
<div id="stage-video" class="stage-layer">
|
||||||
<div id="player"></div>
|
<div id="player"></div>
|
||||||
@@ -155,29 +154,84 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
const VIDEO_ID = 'kujXXtoHUbc';
|
const VIDEO_CONFIGS = {
|
||||||
const BG_IMAGE = '/assets/imgs/www-bg-agencja-ai-com-001.jpg';
|
fhd: {
|
||||||
|
id: '${VIDEO_ID_FHD_JS}',
|
||||||
|
ratio: 16 / 9,
|
||||||
|
redirectUrl: '${POST_VIDEO_REDIRECT_URL_FHD_JS}',
|
||||||
|
redirectEnabled: ${POST_VIDEO_REDIRECT_ENABLED_FHD_JS}
|
||||||
|
},
|
||||||
|
vertical: {
|
||||||
|
id: '${VIDEO_ID_VERTICAL_JS}',
|
||||||
|
ratio: 9 / 16,
|
||||||
|
redirectUrl: '${POST_VIDEO_REDIRECT_URL_VERTICAL_JS}',
|
||||||
|
redirectEnabled: ${POST_VIDEO_REDIRECT_ENABLED_VERTICAL_JS}
|
||||||
|
}
|
||||||
|
};
|
||||||
let player;
|
let player;
|
||||||
|
let activeFormat;
|
||||||
|
let activeConfig;
|
||||||
|
|
||||||
const loader = document.getElementById('loader');
|
const loader = document.getElementById('loader');
|
||||||
const stageVideo = document.getElementById('stage-video');
|
const stageVideo = document.getElementById('stage-video');
|
||||||
const stage = document.getElementById('stage');
|
const stage = document.getElementById('stage');
|
||||||
const VIDEO_RATIO = 16 / 9;
|
|
||||||
|
function isInAppBrowser() {
|
||||||
|
const ua = navigator.userAgent || '';
|
||||||
|
return /FBAN|FBAV|Instagram|Line\/|Twitter|LinkedInApp|wv\)/i.test(ua);
|
||||||
|
}
|
||||||
|
|
||||||
|
function hideLoader() {
|
||||||
|
loader.classList.add('hidden');
|
||||||
|
}
|
||||||
|
|
||||||
|
function youtubeThumbnailUrl(videoId, quality) {
|
||||||
|
return 'https://img.youtube.com/vi/' + videoId + '/' + (quality || 'maxresdefault') + '.jpg';
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateStageBackground() {
|
||||||
|
const img = document.getElementById('stage-bg');
|
||||||
|
if (!img || !activeConfig || !activeConfig.id) return;
|
||||||
|
delete img.dataset.fallback;
|
||||||
|
img.onerror = function () {
|
||||||
|
if (!img.dataset.fallback) {
|
||||||
|
img.dataset.fallback = '1';
|
||||||
|
img.src = youtubeThumbnailUrl(activeConfig.id, 'hqdefault');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
img.src = youtubeThumbnailUrl(activeConfig.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getVideoFormat() {
|
||||||
|
const w = stage.clientWidth || window.innerWidth;
|
||||||
|
const h = stage.clientHeight || window.innerHeight;
|
||||||
|
return w >= h ? 'fhd' : 'vertical';
|
||||||
|
}
|
||||||
|
|
||||||
|
function resolveConfig(format) {
|
||||||
|
const config = VIDEO_CONFIGS[format];
|
||||||
|
if (config.id) return config;
|
||||||
|
const fallbackKey = format === 'fhd' ? 'vertical' : 'fhd';
|
||||||
|
const fallback = VIDEO_CONFIGS[fallbackKey];
|
||||||
|
if (fallback.id) return fallback;
|
||||||
|
return config;
|
||||||
|
}
|
||||||
|
|
||||||
function sizeVideoCover() {
|
function sizeVideoCover() {
|
||||||
const iframe = document.querySelector('#stage-video iframe');
|
const iframe = document.querySelector('#stage-video iframe');
|
||||||
if (!stage || !iframe) return;
|
if (!stage || !iframe || !activeConfig) return;
|
||||||
|
|
||||||
|
const ratio = activeConfig.ratio;
|
||||||
const w = stage.clientWidth;
|
const w = stage.clientWidth;
|
||||||
const h = stage.clientHeight;
|
const h = stage.clientHeight;
|
||||||
let iw, ih;
|
let iw, ih;
|
||||||
|
|
||||||
if (w / h > VIDEO_RATIO) {
|
if (w / h > ratio) {
|
||||||
iw = w;
|
iw = w;
|
||||||
ih = w / VIDEO_RATIO;
|
ih = w / ratio;
|
||||||
} else {
|
} else {
|
||||||
ih = h;
|
ih = h;
|
||||||
iw = h * VIDEO_RATIO;
|
iw = h * ratio;
|
||||||
}
|
}
|
||||||
|
|
||||||
iframe.style.width = iw + 'px';
|
iframe.style.width = iw + 'px';
|
||||||
@@ -189,14 +243,27 @@
|
|||||||
iframe.style.margin = '0';
|
iframe.style.margin = '0';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
activeFormat = getVideoFormat();
|
||||||
|
activeConfig = resolveConfig(activeFormat);
|
||||||
|
updateStageBackground();
|
||||||
|
|
||||||
|
const stageBg = document.getElementById('stage-bg');
|
||||||
const bgPreload = new Image();
|
const bgPreload = new Image();
|
||||||
bgPreload.onload = () => loader.classList.add('hidden');
|
bgPreload.onload = hideLoader;
|
||||||
bgPreload.src = BG_IMAGE;
|
bgPreload.onerror = hideLoader;
|
||||||
|
if (stageBg && stageBg.src) {
|
||||||
|
bgPreload.src = stageBg.src;
|
||||||
|
} else {
|
||||||
|
hideLoader();
|
||||||
|
}
|
||||||
|
|
||||||
const tag = document.createElement('script');
|
const tag = document.createElement('script');
|
||||||
tag.src = 'https://www.youtube.com/iframe_api';
|
tag.src = 'https://www.youtube.com/iframe_api';
|
||||||
|
tag.onerror = hideLoader;
|
||||||
document.head.appendChild(tag);
|
document.head.appendChild(tag);
|
||||||
|
|
||||||
|
setTimeout(hideLoader, isInAppBrowser() ? 4000 : 15000);
|
||||||
|
|
||||||
function enableVideoSound() {
|
function enableVideoSound() {
|
||||||
if (!player || typeof player.unMute !== 'function') return;
|
if (!player || typeof player.unMute !== 'function') return;
|
||||||
player.unMute();
|
player.unMute();
|
||||||
@@ -204,13 +271,15 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
function onYouTubeIframeAPIReady() {
|
function onYouTubeIframeAPIReady() {
|
||||||
|
if (!activeConfig || !activeConfig.id) return;
|
||||||
|
|
||||||
player = new YT.Player('player', {
|
player = new YT.Player('player', {
|
||||||
height: '100%',
|
height: '100%',
|
||||||
width: '100%',
|
width: '100%',
|
||||||
videoId: VIDEO_ID,
|
videoId: activeConfig.id,
|
||||||
playerVars: {
|
playerVars: {
|
||||||
autoplay: 1,
|
autoplay: 1,
|
||||||
mute: 0,
|
mute: 1,
|
||||||
controls: 0,
|
controls: 0,
|
||||||
showinfo: 0,
|
showinfo: 0,
|
||||||
modestbranding: 1,
|
modestbranding: 1,
|
||||||
@@ -231,24 +300,38 @@
|
|||||||
|
|
||||||
function onPlayerReady(event) {
|
function onPlayerReady(event) {
|
||||||
sizeVideoCover();
|
sizeVideoCover();
|
||||||
enableVideoSound();
|
|
||||||
stageVideo.classList.add('active');
|
stageVideo.classList.add('active');
|
||||||
event.target.playVideo();
|
event.target.playVideo();
|
||||||
|
if (!isInAppBrowser()) {
|
||||||
|
enableVideoSound();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function onPlayerStateChange(event) {
|
function onPlayerStateChange(event) {
|
||||||
if (event.data === YT.PlayerState.ENDED) {
|
if (event.data === YT.PlayerState.ENDED) {
|
||||||
stageVideo.classList.remove('active');
|
stageVideo.classList.remove('active');
|
||||||
|
if (activeConfig.redirectEnabled && activeConfig.redirectUrl) {
|
||||||
|
window.location.href = activeConfig.redirectUrl;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
window.addEventListener('resize', sizeVideoCover);
|
function handleResize() {
|
||||||
|
const newFormat = getVideoFormat();
|
||||||
|
if (newFormat !== activeFormat) {
|
||||||
|
activeFormat = newFormat;
|
||||||
|
activeConfig = resolveConfig(activeFormat);
|
||||||
|
updateStageBackground();
|
||||||
|
if (player && activeConfig.id) {
|
||||||
|
player.loadVideoById(activeConfig.id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sizeVideoCover();
|
||||||
|
}
|
||||||
|
|
||||||
|
window.addEventListener('resize', handleResize);
|
||||||
|
|
||||||
document.getElementById('stage-root').addEventListener('pointerdown', enableVideoSound, { once: true });
|
document.getElementById('stage-root').addEventListener('pointerdown', enableVideoSound, { once: true });
|
||||||
|
|
||||||
setTimeout(() => {
|
|
||||||
loader.classList.add('hidden');
|
|
||||||
}, 15000);
|
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
+30
@@ -0,0 +1,30 @@
|
|||||||
|
user nginx;
|
||||||
|
worker_processes auto;
|
||||||
|
error_log /var/log/nginx/error.log warn;
|
||||||
|
pid /var/run/nginx.pid;
|
||||||
|
|
||||||
|
events {
|
||||||
|
worker_connections 1024;
|
||||||
|
}
|
||||||
|
|
||||||
|
http {
|
||||||
|
include /etc/nginx/mime.types;
|
||||||
|
default_type application/octet-stream;
|
||||||
|
|
||||||
|
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
|
||||||
|
'$status $body_bytes_sent "$http_referer" '
|
||||||
|
'"$http_user_agent" "$http_x_forwarded_for"';
|
||||||
|
|
||||||
|
access_log /var/log/nginx/access.log main;
|
||||||
|
|
||||||
|
sendfile on;
|
||||||
|
tcp_nopush on;
|
||||||
|
keepalive_timeout 15;
|
||||||
|
client_body_timeout 10;
|
||||||
|
client_header_timeout 10;
|
||||||
|
send_timeout 10;
|
||||||
|
|
||||||
|
server_tokens off;
|
||||||
|
|
||||||
|
include /etc/nginx/conf.d/*.conf;
|
||||||
|
}
|
||||||
@@ -1,24 +1,24 @@
|
|||||||
{
|
{
|
||||||
"name": "Agencja AI",
|
"name": "${SITE_TITLE}",
|
||||||
"short_name": "Agencja AI",
|
"short_name": "${SITE_SHORT_NAME}",
|
||||||
"icons": [
|
"icons": [
|
||||||
{
|
{
|
||||||
"src": "/assets/imgs/favico/48x48.png",
|
"src": "${FAVICON_48}",
|
||||||
"sizes": "48x48",
|
"sizes": "48x48",
|
||||||
"type": "image/png"
|
"type": "image/png"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"src": "/assets/imgs/favico/180x180.png",
|
"src": "${FAVICON_180}",
|
||||||
"sizes": "180x180",
|
"sizes": "180x180",
|
||||||
"type": "image/png"
|
"type": "image/png"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"src": "/assets/imgs/favico/512x512.png",
|
"src": "${FAVICON_512}",
|
||||||
"sizes": "512x512",
|
"sizes": "512x512",
|
||||||
"type": "image/png"
|
"type": "image/png"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"display": "standalone",
|
"display": "standalone",
|
||||||
"theme_color": "#000000",
|
"theme_color": "${THEME_COLOR}",
|
||||||
"background_color": "#000000"
|
"background_color": "${THEME_COLOR}"
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user