Files
honey-be/VPS_SETUP_FROM_SCRATCH.md
Mykhailo Svishchov b30d47c576
Some checks failed
Deploy to Railway / deploy (push) Has been cancelled
Network Test / test_network (push) Has been cancelled
vps setup
2026-03-07 14:48:29 +02:00

17 KiB
Raw Blame History

Honey VPS Setup from Scratch (Inferno)

This guide walks through setting up a new VPS for the Honey app with the same layout as your existing lottery VPS: backend + MySQL + phpMyAdmin in Docker, frontend and admin panel served by Nginx, logging under /opt/app/logs, secrets in /run/secrets, and MySQL backups to a backup VPS.

Target layout (mirrors your lottery setup):

  • Containers: backend (honey-be), MySQL (honey_db), phpMyAdmin
  • Served by Nginx: frontend (honey-fe), admin panel (honey-admin)
  • Paths: /opt/app for app files, /run/secrets for config, /opt/app/logs for logs
  • Nginx: main config + site config (e.g. nginx.conf + sites-enabled/your-domain)

1. VPS basics

1.1 System update and installs

sudo apt update && sudo apt upgrade -y
# Docker
curl -fsSL https://get.docker.com -o get-docker.sh
sudo sh get-docker.sh
sudo usermod -aG docker $USER

# Docker Compose (plugin)
sudo apt-get update
sudo apt-get install -y docker-compose-plugin

# Nginx + Certbot
sudo apt install -y nginx certbot python3-certbot-nginx

Log out and back in so docker group applies.

1.2 Directory structure under /opt/app

Create the same layout as lottery (backend, frontend, admin, nginx, data, logs, backups, mysql):

sudo mkdir -p /opt/app/backend
sudo mkdir -p /opt/app/frontend
sudo mkdir -p /opt/app/admin
sudo mkdir -p /opt/app/admin-panel
sudo mkdir -p /opt/app/nginx
sudo mkdir -p /opt/app/data/avatars
sudo mkdir -p /opt/app/logs
sudo mkdir -p /opt/app/backups
sudo mkdir -p /opt/app/mysql/data
sudo mkdir -p /opt/app/mysql/conf

sudo chown -R $USER:$USER /opt/app
sudo chmod -R 755 /opt/app

1.3 Git

sudo apt install git -y
git config --global alias.st status
ssh-keygen -t ed25519 -C "your_email@example.com" (replace email)
cat ~/.ssh/id_ed25519.pub (copy the key and add to origin)

curl -fsSL https://tailscale.com/install.sh | sh
sudo tailscale up (and login in local browser to tailscale)

Clone all repositories to respective folders.


2. Backend (honey-be) on VPS

2.1 Secret file (honey-config.properties)

Backend reads /run/secrets/honey-config.properties (see ConfigLoader and docker-compose.prod.yml). Create it from the template; do not commit real values.

On the VPS:

sudo mkdir -p /run/secrets
sudo cp /opt/app/backend/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

Edit and set real values:

sudo nano /run/secrets/honey-config.properties

Notes:

  • SPRING_DATASOURCE_URL - set to new DB URL
  • SPRING_DATASOURCE_PASSWORD - just generate new secret
  • TELEGRAM_BOT_TOKEN - token for Telegram bot
  • FRONTEND_URL - put new domain here
  • APP_ADMIN_JWT_SECRET - generate new secret using openssl rand -base64 48 on VPS and put here
  • APP_TELEGRAM_WEBHOOK_TOKEN - generate a new secret and set it using POST https://api.telegram.org/bot<token>/setWebhook?url=https://<domain>/api/telegram/webhook/<secret>&max_connections=100
  • PMA_ABSOLUTE_URI - generate a new secret and set it. Don't forget to set the same to nginx

Create 2 files admin_api_url and admin_base_path with URL and secret path in /run/secrets folder.

2.3 Load DB password for Docker Compose

docker-compose.prod.yml expects DB_ROOT_PASSWORD (and MySQL healthcheck uses it). The repo has scripts/load-db-password.sh which reads the secret file; its currently wired to lottery path. For Honey, either:

  • Edit scripts/load-db-password.sh and set:
    • SECRET_FILE="/run/secrets/honey-config.properties"
  • Or create a small wrapper that exports the same variables from honey-config.properties.

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.

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:

mkdir -p /opt/app/backend/config
mkdir -p /opt/app/logs

Either copy from the JAR or from source:

# From backend dir
cp src/main/resources/logback-spring.xml /opt/app/backend/config/
# or extract from built JAR:
# unzip -p target/honey-be-*.jar BOOT-INF/classes/logback-spring.xml > /opt/app/backend/config/logback-spring.xml

Optional: run the existing setup script (it may still reference lottery paths; adjust or run the copy above):

cd /opt/app/backend
./scripts/setup-logging.sh

Edit log level at any time:

nano /opt/app/backend/config/logback-spring.xml
# e.g. change <logger name="com.honey" level="INFO"/> to DEBUG
# Logback rescans periodically (e.g. 30s); no restart needed if scan is enabled

2.5 MySQL my.cnf (optional)

If you use a custom MySQL config in prod (e.g. for buffer pool):

# Create /opt/app/mysql/conf/my.cnf with your tuning; then in docker-compose.prod.yml
# the volume is already: /opt/app/mysql/conf/my.cnf:/etc/mysql/conf.d/my.cnf:ro

Note: on Staged VPS it has 4G RAM, so don't forget to change it for PROD accordingly.

2.6 First start (backend + DB only)

cd /opt/app/backend
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

Check:

docker ps
curl -s http://127.0.0.1:8080/actuator/health/readiness

Backend should listen only on 127.0.0.1:8080 (Nginx will proxy to it). Do not expose 8080 to the internet.


3. Nginx

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.

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 repos nginx.conf.template as reference; adapt server_name, SSL paths, and add an admin location. Example skeleton:

# 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;
    }
}

Enable and test:

sudo ln -s /etc/nginx/sites-available/honey.conf /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx

3.3 SSL (Lets Encrypt)

sudo certbot --nginx -d your-domain.com

Certbot will adjust the server block for certificates. Reload Nginx if needed.


4. phpMyAdmin

4.1 Start phpMyAdmin container

docker-compose.prod.yml already defines a phpmyadmin service (port 8081, same network as db). Start it:

cd /opt/app/backend
source scripts/load-db-password.sh
docker compose -f docker-compose.prod.yml up -d phpmyadmin

Do not expose 8081 publicly. Proxy it via Nginx under a secret path (e.g. /your-secret-pma-path/), as in the example above. Set PMA_ABSOLUTE_URI in the secret file so phpMyAdmin generates correct URLs:

In /run/secrets/honey-config.properties add (or use env when running compose):

PMA_ABSOLUTE_URI=https://your-domain.com/your-secret-pma-path/

Then reload Nginx and open https://your-domain.com/your-secret-pma-path/. Login: user root, password = SPRING_DATASOURCE_PASSWORD from the same secret file.

4.3 Optional: UFW for phpMyAdmin

If you ever expose 8081 temporarily, restrict it:

sudo ufw allow from YOUR_IP to any port 8081
sudo ufw reload

Prefer keeping 8081 bound to 127.0.0.1 and using only Nginx proxy.


5. Frontend (honey-fe)

5.1 Build locally and upload

On your machine (e.g. in honey-test-fe or your honey-fe repo):

cd honey-test-fe   # or honey-fe
npm install
npm run build
scp -r dist/* root@YOUR_VPS_IP:/opt/app/frontend/dist/

Or with rsync:

rsync -avz dist/ root@YOUR_VPS_IP:/opt/app/frontend/dist/

Ensure the apps API base URL is correct for production (e.g. relative "" or VITE_API_BASE_URL for your domain).


6. Admin panel (honey-admin)

6.1 Build with secret (on VPS or locally)

Admin often has a build that injects a public URL or env. From the repo:

cd honey-admin
npm install
npm run build:with-secret

Then copy the built output to the Nginx admin root:

If you build on the VPS:

cd /opt/app/admin/honey-admin
npm run build:with-secret
cp -r dist/* /opt/app/admin-panel/

If you build locally:

scp -r dist/* root@YOUR_VPS_IP:/opt/app/admin-panel/

The Nginx location for admin (e.g. /your-secret-admin-path/) must serve this directory and support SPA routing (try_files to index.html).


7. Rolling backend updates

Use the existing rolling-update script so Nginx switches to a new backend container with no downtime.

7.1 Script adaptation for Honey

The script in the repo may still reference lottery container names and Nginx paths. For Honey:

  • Containers: honey-backend (primary), honey-backend-new (standby).
  • Nginx: same idea as lottery: one upstream backend with server 127.0.0.1:8080 and optionally server 127.0.0.1:8082 backup;. The script flips which port is primary.

Edit scripts/rolling-update.sh and replace:

  • lottery-backendhoney-backend
  • lottery-backend-newhoney-backend-new

The script auto-detects Nginx config from paths like /etc/nginx/sites-enabled/win-spin.live. For Honey, either:

  • Symlink or name your site config so the script finds it (e.g. add a similar check for honey.conf in the script), or
  • Set the path explicitly before running: export NGINX_CONF=/etc/nginx/sites-enabled/your-domain && sudo ./scripts/rolling-update.sh

7.2 Run rolling update

From the backend directory, run (no need to source load-db-password.sh — the script does it):

cd /opt/app/backend
chmod +x scripts/rolling-update.sh
sudo ./scripts/rolling-update.sh

The script loads DB_ROOT_PASSWORD from the secret file if not set, then: builds the new image, starts backend-new on 8082, health-checks it, points Nginx to 8082, reloads Nginx, then stops the old backend.


8. Logging

  • App logs: /opt/app/logs/ (mounted into backend container; path can be set via LOG_DIR / logback).
  • Config: /opt/app/backend/config/logback-spring.xml (edit to change level; no restart if scan is enabled).
  • Nginx: /var/log/nginx/access.log, /var/log/nginx/error.log.

View backend logs:

docker logs -f honey-backend
# or
tail -f /opt/app/logs/honey-be.log

9. MySQL backups to backup VPS

9.1 Backup script for Honey

Copy and adapt the existing scripts/backup-database.sh (or create a Honey-specific one). Set:

  • MYSQL_CONTAINER="honey-mysql"
  • MYSQL_DATABASE="honey_db"
  • SECRET_FILE="/run/secrets/honey-config.properties"
  • BACKUP_FILENAME="honey_db_backup_${TIMESTAMP}.sql" (and .gz if compressing)
  • Remote path and retention (e.g. BACKUP_VPS_PATH, keep last 30 days) to match your backup server.

Ensure the script runs as root (or with sudo) so it can read /run/secrets/honey-config.properties, and that it uses the same DB_PASSWORD / SPRING_DATASOURCE_PASSWORD as in the secret file.

9.2 SSH key to backup VPS

On the Honey VPS:

ssh-keygen -t ed25519 -C "backup@honey-vps" -f ~/.ssh/backup_key
ssh-copy-id -i ~/.ssh/backup_key.pub user@BACKUP_VPS_IP

Test:

ssh -i ~/.ssh/backup_key user@BACKUP_VPS_IP "echo OK"

9.3 Cron (daily at 2 AM)

sudo crontab -e

Add:

0 2 * * * /opt/app/backend/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.


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
Logs /opt/app/logs (+ logback config in /opt/app/backend/config)
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)
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
    (Password is loaded from the secret file inside the script.)

  • Frontend:
    Local: npm run build then scp -r dist/* root@VPS:/opt/app/frontend/dist/

  • Admin:
    On VPS: cd /opt/app/admin/honey-admin && npm run build:with-secret && cp -r dist/* /opt/app/admin-panel/
    Or build locally and scp -r dist/* root@VPS:/opt/app/admin-panel/

  • Log level:
    Edit /opt/app/backend/config/logback-spring.xml (no restart if scan enabled).


11. Checklist after setup

  • /opt/app structure created; ownership and permissions correct.
  • /run/secrets/honey-config.properties created and filled (no placeholders).
  • load-db-password.sh (and backup/rolling scripts) use Honey secret path and container/db names.
  • Backend + DB + phpMyAdmin start; health check returns 200.
  • Nginx site config in place; nginx -t OK; HTTPS works.
  • Frontend and admin builds deployed to /opt/app/frontend/dist and /opt/app/admin-panel.
  • API and WebSocket work through Nginx; avatars and admin paths load.
  • phpMyAdmin reachable only via Nginx secret path; 8081 not public.
  • Rolling update script updated for honey-backend / honey-backend-new and tested.
  • Backup script adapted for honey_db / honey-mysql; cron runs and backups appear on backup VPS.
  • Logs under /opt/app/logs and logback config under /opt/app/backend/config; log level change works.

This gives you the same layout and workflow as your lottery VPS, but for Honey (honey-be, honey-fe, honey-admin) with Nginx, phpMyAdmin, logging, and backups.