diff --git a/VPS_SETUP_FROM_SCRATCH.md b/VPS_SETUP_FROM_SCRATCH.md index 0fdb521..0657d84 100644 --- a/VPS_SETUP_FROM_SCRATCH.md +++ b/VPS_SETUP_FROM_SCRATCH.md @@ -81,7 +81,7 @@ On the VPS: ```bash sudo mkdir -p /run/secrets -sudo cp /opt/app/backend/honey-config.properties.template /run/secrets/honey-config.properties +sudo cp /opt/app/backend/honey-be/honey-config.properties.template /run/secrets/honey-config.properties sudo chmod 640 /run/secrets/honey-config.properties sudo chown root:docker /run/secrets/honey-config.properties # if your user is in docker group, or root:$USER ``` @@ -114,6 +114,9 @@ Create 2 files `admin_api_url` and `admin_base_path` with URL and secret path in **When you need to source it:** Only for one-off manual `docker compose` runs (e.g. first-time start in §2.6, or starting phpMyAdmin in §4.1). You do **not** need to source it for deployment: `scripts/rolling-update.sh` loads the password from the secret file automatically when `DB_ROOT_PASSWORD` is not set. +**IMPORTANT** +`Change Java memory in docker-compose.prod.yml when you know the PROD VPS characteristics.` + ### 2.4 Logging (logback) and config dir Backend uses an external **logback** config so you can change log level without rebuilding. Create the config dir and put `logback-spring.xml` there: @@ -135,7 +138,7 @@ cp src/main/resources/logback-spring.xml /opt/app/backend/config/ Optional: run the existing setup script (it may still reference lottery paths; adjust or run the copy above): ```bash -cd /opt/app/backend +cd /opt/app/backend/honey-be ./scripts/setup-logging.sh ``` @@ -161,13 +164,15 @@ Note: `on Staged VPS it has 4G RAM, so don't forget to change it for PROD accord ### 2.6 First start (backend + DB only) ```bash -cd /opt/app/backend +cd /opt/app/backend/honey-be source scripts/load-db-password.sh docker compose -f docker-compose.prod.yml up -d db # wait for DB healthy docker compose -f docker-compose.prod.yml up -d backend ``` +Note: `for Staged use a separate docker compose.` + Check: ```bash @@ -183,97 +188,20 @@ Backend should listen only on `127.0.0.1:8080` (Nginx will proxy to it). Do **no ### 3.1 Split config (like your lottery VPS) -- **Main config:** `/etc/nginx/nginx.conf` (includes sites, worker settings, etc.) -- **Site config:** `/etc/nginx/sites-enabled/your-domain` (or `your-domain.conf`) for the Honey server block. +- `Take 2 files from already working VPS: nginx.conf and sites-enabled/ and put to new VPS.` +- `Remove or comment lines reg certificates and change 2 listen lines`: -So you have two files as on lottery: one global, one site. - -### 3.2 Site config (HTTPS, API, frontend, admin, avatars) - -Create a site config (e.g. `honey.yourdomain.com` or same domain as lottery). Example path: `/etc/nginx/sites-available/honey.conf` and symlink in `sites-enabled`. - -- **Frontend:** root `/opt/app/frontend/dist` (SPA; `try_files` to `index.html`). -- **Admin panel:** root `/opt/app/admin-panel` (or a location like `/admin` pointing there). -- **API:** `location /api/` proxy to `http://127.0.0.1:8080`. -- **WebSocket:** `location /ws` proxy to `http://127.0.0.1:8080` with upgrade headers. -- **Avatars:** `location /avatars/` alias `/opt/app/data/avatars/`. -- **phpMyAdmin:** e.g. `location /pma/` or a secret path proxy to `http://127.0.0.1:8081` (see below). - -Use the repo’s **`nginx.conf.template`** as reference; adapt server_name, SSL paths, and add an admin location. Example skeleton: - -```nginx -# Upstream for backend (same as template) -upstream backend { - server 127.0.0.1:8080; -} - -server { - listen 80; - server_name your-domain.com; - location /.well-known/acme-challenge/ { root /var/www/certbot; } - location / { return 301 https://$host$request_uri; } -} - -server { - listen 443 ssl http2; - server_name your-domain.com; - - ssl_certificate /etc/letsencrypt/live/your-domain.com/fullchain.pem; - ssl_certificate_key /etc/letsencrypt/live/your-domain.com/privkey.pem; - - root /opt/app/frontend/dist; - index index.html; - - location /api/ { - proxy_pass http://backend; - proxy_http_version 1.1; - proxy_set_header Host $host; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; - proxy_set_header Upgrade $http_upgrade; - proxy_set_header Connection "upgrade"; - proxy_cache_bypass $http_upgrade; - } - - location /ws { - proxy_pass http://backend; - proxy_http_version 1.1; - proxy_set_header Upgrade $http_upgrade; - proxy_set_header Connection "upgrade"; - proxy_set_header Host $host; - proxy_read_timeout 7d; - proxy_send_timeout 7d; - } - - location /avatars/ { - alias /opt/app/data/avatars/; - expires 1h; - } - - # Admin panel (e.g. secret path) - location /your-secret-admin-path/ { - alias /opt/app/admin-panel/; - try_files $uri $uri/ /your-secret-admin-path/index.html; - } - - # phpMyAdmin (secret path, optional) - location /your-secret-pma-path/ { - proxy_pass http://127.0.0.1:8081/; - proxy_set_header Host $host; - proxy_set_header X-Forwarded-Proto $scheme; - } - - location / { - try_files $uri $uri/ /index.html; - } -} +```bash + # SSL Certificates + ssl_certificate /etc/letsencrypt/live/testforapp.website/fullchain.pem; + ssl_certificate_key /etc/letsencrypt/live/testforapp.website/privkey.pem; + Change 'listen 443 ssl http2;' to 'listen 443;` + Change `listen [::]:443 ssl http2;` to `listen [::]:443;` ``` - Enable and test: ```bash -sudo ln -s /etc/nginx/sites-available/honey.conf /etc/nginx/sites-enabled/ +sudo ln -s /etc/nginx/sites-available/testforapp.website /etc/nginx/sites-enabled/ sudo nginx -t sudo systemctl reload nginx ``` @@ -281,10 +209,11 @@ sudo systemctl reload nginx ### 3.3 SSL (Let’s Encrypt) ```bash -sudo certbot --nginx -d your-domain.com +sudo certbot --nginx -d testforapp.website ``` Certbot will adjust the server block for certificates. Reload Nginx if needed. +And remove redundant ssl/listen lines from server block to not override certbot's configs. --- @@ -295,7 +224,7 @@ Certbot will adjust the server block for certificates. Reload Nginx if needed. `docker-compose.prod.yml` already defines a **phpmyadmin** service (port 8081, same network as `db`). Start it: ```bash -cd /opt/app/backend +cd /opt/app/backend/honey-be source scripts/load-db-password.sh docker compose -f docker-compose.prod.yml up -d phpmyadmin ``` @@ -406,7 +335,7 @@ The script auto-detects Nginx config from paths like `/etc/nginx/sites-enabled/w From the backend directory, run (no need to source `load-db-password.sh` — the script does it): ```bash -cd /opt/app/backend +cd /opt/app/backend/honey-be chmod +x scripts/rolling-update.sh sudo ./scripts/rolling-update.sh ``` @@ -469,7 +398,7 @@ sudo crontab -e Add: ```cron -0 2 * * * /opt/app/backend/scripts/backup-database.sh >> /opt/app/logs/backup.log 2>&1 +0 2 * * * /opt/app/backend/honey-be/scripts/backup-database.sh >> /opt/app/logs/backup.log 2>&1 ``` Use the Honey-adapted backup script path and ensure `backup-database.sh` uses `honey_db` and `honey-mysql`. @@ -478,25 +407,25 @@ Use the Honey-adapted backup script path and ensure `backup-database.sh` uses `h ## 10. Quick reference -| Item | Honey | -|------|--------| -| **App root** | `/opt/app` | -| **Backend code** | `/opt/app/backend` (honey-be) | -| **Frontend static** | `/opt/app/frontend/dist` (honey-fe build) | -| **Admin static** | `/opt/app/admin-panel` (honey-admin build) | -| **Secret file** | `/run/secrets/honey-config.properties` | +| Item | Honey | +|------|-----------------------------------------------------------------| +| **App root** | `/opt/app` | +| **Backend code** | `/opt/app/backend/honey-be` (honey-be) | +| **Frontend static** | `/opt/app/frontend/dist` (honey-fe build) | +| **Admin static** | `/opt/app/admin-panel` (honey-admin build) | +| **Secret file** | `/run/secrets/honey-config.properties` | | **Logs** | `/opt/app/logs` (+ logback config in `/opt/app/backend/config`) | -| **Avatars** | `/opt/app/data/avatars` | +| **Avatars** | `/opt/app/data/avatars` | | **Nginx** | `/etc/nginx/nginx.conf` + `/etc/nginx/sites-enabled/your-domain` | -| **DB container** | `honey-mysql` | -| **DB name** | `honey_db` | -| **Backend containers** | `honey-backend`, `honey-backend-new` (rolling) | +| **DB container** | `honey-mysql` | +| **DB name** | `honey_db` | +| **Backend containers** | `honey-backend`, `honey-backend-new` (rolling) | | **phpMyAdmin** | Container `honey-phpmyadmin`, port 8081 → proxy via Nginx secret path | ### Deploy commands (summary) - **Backend (rolling):** - `cd /opt/app/backend && chmod +x scripts/rolling-update.sh && sudo ./scripts/rolling-update.sh` + `cd /opt/app/backend/honey-be && chmod +x scripts/rolling-update.sh && sudo ./scripts/rolling-update.sh` (Password is loaded from the secret file inside the script.) - **Frontend:** diff --git a/scripts/fix-nginx-redirect-loop.md b/scripts/fix-nginx-redirect-loop.md new file mode 100644 index 0000000..6c4ea9d --- /dev/null +++ b/scripts/fix-nginx-redirect-loop.md @@ -0,0 +1,77 @@ +# Fix 301 redirect loop (Certbot duplicate 443 block) + +## Cause + +Certbot added a **second** HTTPS server block that only has: +- `location / { return 301 https://$host$request_uri; }` +- `listen 443 ssl` + SSL cert paths + +That block is matched first for `https://testforapp.website/`, so every request gets 301 → same URL → loop. Your real HTTPS block (frontend, API, phpMyAdmin) is never used for `/`. + +## Fix on VPS + +1. **Open the site config** + ```bash + sudo nano /etc/nginx/sites-enabled/testforapp.website + ``` + +2. **Find and remove the Certbot-only HTTPS block** + + Look for a block that looks like this (it may be at the **top** of the file, before the `map` and your big HTTPS server): + + ```nginx + server { + server_name testforapp.website; + + location /.well-known/acme-challenge/ { + root /var/www/certbot; + } + + location / { + return 301 https://$host$request_uri; + } + + listen [::]:443 ssl ipv6only=on; # managed by Certbot + listen 443 ssl; # managed by Certbot + ssl_certificate /etc/letsencrypt/live/testforapp.website/fullchain.pem; # managed by Certbot + ssl_certificate_key /etc/letsencrypt/live/testforapp.website/privkey.pem; # managed by Certbot + include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot + ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot + } + ``` + + **Delete this entire `server { ... }` block** (from `server {` through the closing `}`). + +3. **Ensure your main HTTPS block has SSL and listen** + + Find your main HTTPS server (the one with `# HTTPS server`, `root /opt/app/frontend/dist`, all the `location` blocks). It must have at the top of that block (right after `server {`): + + - `listen 443 ssl;` and `listen [::]:443 ssl;` + - `ssl_certificate` and `ssl_certificate_key` (and optionally `include /etc/letsencrypt/options-ssl-nginx.conf;` and `ssl_dhparam`) + + If those lines are missing, add them (copy from the block you deleted): + + ```nginx + server { + listen 443 ssl; + listen [::]:443 ssl; + server_name testforapp.website; + ssl_certificate /etc/letsencrypt/live/testforapp.website/fullchain.pem; + ssl_certificate_key /etc/letsencrypt/live/testforapp.website/privkey.pem; + include /etc/letsencrypt/options-ssl-nginx.conf; + ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; + + # ... rest of your config (root, locations, etc.) + } + ``` + +4. **Test and reload** + ```bash + sudo nginx -t && sudo systemctl reload nginx + ``` + +5. **Verify** + ```bash + curl -I -k https://127.0.0.1/ -H "Host: testforapp.website" + ``` + You should see `200 OK` (or `304`) and no `Location` header, and https://testforapp.website/ should load in the browser. diff --git a/scripts/rolling-update.sh b/scripts/rolling-update.sh index 217a2e4..947b980 100644 --- a/scripts/rolling-update.sh +++ b/scripts/rolling-update.sh @@ -44,39 +44,39 @@ COMPOSE_FILE="${PROJECT_DIR}/docker-compose.prod.yml" # Priority: sites-enabled (what Nginx actually loads) > conf.d > custom paths NGINX_CONF="${NGINX_CONF:-}" if [ -z "$NGINX_CONF" ]; then - if [ -f "/etc/nginx/sites-enabled/win-spin.live" ]; then - NGINX_CONF="/etc/nginx/sites-enabled/win-spin.live" + if [ -f "/etc/nginx/sites-enabled/honey.live" ]; then + NGINX_CONF="/etc/nginx/sites-enabled/honey.live" log "Using Nginx config: $NGINX_CONF (sites-enabled - active config)" - elif [ -f "/etc/nginx/sites-enabled/win-spin.live.conf" ]; then - NGINX_CONF="/etc/nginx/sites-enabled/win-spin.live.conf" + elif [ -f "/etc/nginx/sites-enabled/honey.live.conf" ]; then + NGINX_CONF="/etc/nginx/sites-enabled/honey.live.conf" log "Using Nginx config: $NGINX_CONF (sites-enabled - active config)" elif [ -f "/etc/nginx/conf.d/honey.conf" ]; then NGINX_CONF="/etc/nginx/conf.d/honey.conf" log "Using Nginx config: $NGINX_CONF (conf.d)" - elif [ -f "/opt/app/nginx/win-spin.live.conf" ]; then - warn "Found config at /opt/app/nginx/win-spin.live.conf" + elif [ -f "/opt/app/nginx/honey.live.conf" ]; then + warn "Found config at /opt/app/nginx/honey.live.conf" warn "Checking if it's symlinked to /etc/nginx/sites-enabled/..." - if [ -L "/etc/nginx/sites-enabled/win-spin.live" ] || [ -L "/etc/nginx/sites-enabled/win-spin.live.conf" ]; then + if [ -L "/etc/nginx/sites-enabled/honey.live" ] || [ -L "/etc/nginx/sites-enabled/honey.live.conf" ]; then # Find the actual target - local target=$(readlink -f /etc/nginx/sites-enabled/win-spin.live 2>/dev/null || readlink -f /etc/nginx/sites-enabled/win-spin.live.conf 2>/dev/null) + local target=$(readlink -f /etc/nginx/sites-enabled/honey.live 2>/dev/null || readlink -f /etc/nginx/sites-enabled/honey.live.conf 2>/dev/null) if [ -n "$target" ]; then NGINX_CONF="$target" log "Using Nginx config: $NGINX_CONF (symlink target)" else - NGINX_CONF="/opt/app/nginx/win-spin.live.conf" + NGINX_CONF="/opt/app/nginx/honey.live.conf" warn "Using custom path - will update this file, but you may need to copy to sites-enabled" fi else - NGINX_CONF="/opt/app/nginx/win-spin.live.conf" + NGINX_CONF="/opt/app/nginx/honey.live.conf" warn "Using custom path - will update this file, but you may need to copy to sites-enabled" fi else error "Cannot find Nginx config file." error "Searched:" - error " - /etc/nginx/sites-enabled/win-spin.live" - error " - /etc/nginx/sites-enabled/win-spin.live.conf" + error " - /etc/nginx/sites-enabled/honey.live" + error " - /etc/nginx/sites-enabled/honey.live.conf" error " - /etc/nginx/conf.d/honey.conf" - error " - /opt/app/nginx/win-spin.live.conf" + error " - /opt/app/nginx/honey.live.conf" error "" error "Please set NGINX_CONF environment variable with the correct path." exit 1 diff --git a/scripts/rolling-update.staged.sh b/scripts/rolling-update.staged.sh index 079abe5..82bdb74 100644 --- a/scripts/rolling-update.staged.sh +++ b/scripts/rolling-update.staged.sh @@ -44,39 +44,39 @@ COMPOSE_FILE="${PROJECT_DIR}/docker-compose.staged.yml" # Priority: sites-enabled (what Nginx actually loads) > conf.d > custom paths NGINX_CONF="${NGINX_CONF:-}" if [ -z "$NGINX_CONF" ]; then - if [ -f "/etc/nginx/sites-enabled/win-spin.live" ]; then - NGINX_CONF="/etc/nginx/sites-enabled/win-spin.live" + if [ -f "/etc/nginx/sites-enabled/honey.live" ]; then + NGINX_CONF="/etc/nginx/sites-enabled/honey.live" log "Using Nginx config: $NGINX_CONF (sites-enabled - active config)" - elif [ -f "/etc/nginx/sites-enabled/win-spin.live.conf" ]; then - NGINX_CONF="/etc/nginx/sites-enabled/win-spin.live.conf" + elif [ -f "/etc/nginx/sites-enabled/honey.live.conf" ]; then + NGINX_CONF="/etc/nginx/sites-enabled/honey.live.conf" log "Using Nginx config: $NGINX_CONF (sites-enabled - active config)" elif [ -f "/etc/nginx/conf.d/honey.conf" ]; then NGINX_CONF="/etc/nginx/conf.d/honey.conf" log "Using Nginx config: $NGINX_CONF (conf.d)" - elif [ -f "/opt/app/nginx/win-spin.live.conf" ]; then - warn "Found config at /opt/app/nginx/win-spin.live.conf" + elif [ -f "/opt/app/nginx/honey.live.conf" ]; then + warn "Found config at /opt/app/nginx/honey.live.conf" warn "Checking if it's symlinked to /etc/nginx/sites-enabled/..." - if [ -L "/etc/nginx/sites-enabled/win-spin.live" ] || [ -L "/etc/nginx/sites-enabled/win-spin.live.conf" ]; then + if [ -L "/etc/nginx/sites-enabled/honey.live" ] || [ -L "/etc/nginx/sites-enabled/honey.live.conf" ]; then # Find the actual target - local target=$(readlink -f /etc/nginx/sites-enabled/win-spin.live 2>/dev/null || readlink -f /etc/nginx/sites-enabled/win-spin.live.conf 2>/dev/null) + local target=$(readlink -f /etc/nginx/sites-enabled/honey.live 2>/dev/null || readlink -f /etc/nginx/sites-enabled/honey.live.conf 2>/dev/null) if [ -n "$target" ]; then NGINX_CONF="$target" log "Using Nginx config: $NGINX_CONF (symlink target)" else - NGINX_CONF="/opt/app/nginx/win-spin.live.conf" + NGINX_CONF="/opt/app/nginx/honey.live.conf" warn "Using custom path - will update this file, but you may need to copy to sites-enabled" fi else - NGINX_CONF="/opt/app/nginx/win-spin.live.conf" + NGINX_CONF="/opt/app/nginx/honey.live.conf" warn "Using custom path - will update this file, but you may need to copy to sites-enabled" fi else error "Cannot find Nginx config file." error "Searched:" - error " - /etc/nginx/sites-enabled/win-spin.live" - error " - /etc/nginx/sites-enabled/win-spin.live.conf" + error " - /etc/nginx/sites-enabled/honey.live" + error " - /etc/nginx/sites-enabled/honey.live.conf" error " - /etc/nginx/conf.d/honey.conf" - error " - /opt/app/nginx/win-spin.live.conf" + error " - /opt/app/nginx/honey.live.conf" error "" error "Please set NGINX_CONF environment variable with the correct path." exit 1 diff --git a/src/main/java/com/honey/honey/config/AdminSecurityConfig.java b/src/main/java/com/honey/honey/config/AdminSecurityConfig.java index 980a8dc..c84fe8d 100644 --- a/src/main/java/com/honey/honey/config/AdminSecurityConfig.java +++ b/src/main/java/com/honey/honey/config/AdminSecurityConfig.java @@ -21,12 +21,15 @@ import org.springframework.security.web.authentication.UsernamePasswordAuthentic import org.springframework.security.web.util.matcher.AntPathRequestMatcher; import org.springframework.security.web.util.matcher.OrRequestMatcher; import org.springframework.security.web.util.matcher.RequestMatcher; +import org.springframework.beans.factory.annotation.Value; import org.springframework.web.cors.CorsConfiguration; import org.springframework.web.cors.CorsConfigurationSource; import org.springframework.web.cors.UrlBasedCorsConfigurationSource; import java.util.Arrays; import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; @Configuration @EnableWebSecurity @@ -34,6 +37,9 @@ import java.util.List; @RequiredArgsConstructor public class AdminSecurityConfig { + @Value("${FRONTEND_URL:}") + private String frontendUrl; + private final JwtAuthenticationFilter jwtAuthenticationFilter; private final AdminDetailsService adminDetailsService; @@ -107,12 +113,18 @@ public class AdminSecurityConfig { @Bean public CorsConfigurationSource corsConfigurationSource() { + List allowedOrigins = Stream.concat( + Stream.of( + "http://localhost:5173", + "http://localhost:3000" + ), + frontendUrl != null && !frontendUrl.isBlank() + ? Arrays.stream(frontendUrl.split("\\s*,\\s*")).filter(s -> !s.isBlank()) + : Stream.empty() + ).distinct().collect(Collectors.toList()); + CorsConfiguration configuration = new CorsConfiguration(); - configuration.setAllowedOrigins(Arrays.asList( - "http://localhost:5173", - "http://localhost:3000", - "https://win-spin.live" // Main domain (admin panel is on same domain with secret path) - )); + configuration.setAllowedOrigins(allowedOrigins); configuration.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS")); configuration.setAllowedHeaders(List.of("*")); configuration.setAllowCredentials(true);