diff --git a/deploy/vserver.env.example b/deploy/vserver.env.example index 157916d..f14a7d0 100644 --- a/deploy/vserver.env.example +++ b/deploy/vserver.env.example @@ -1,14 +1,18 @@ # SSH host or alias from ~/.ssh/config. -REMOTE_HOST=myvserver +# This can also be a literal user@host value, for example root@myvserver. +REMOTE_HOST=root@myvserver # Remote filesystem layout. -REMOTE_APP_DIR=/opt/rolemasterdb -REMOTE_DATA_DIR=/opt/rolemasterdb/data +REMOTE_APP_DIR=/root/docker/rolemasterdb +REMOTE_DATA_DIR=/root/docker/rolemasterdb/data # Docker names on the remote host. IMAGE_NAME=rolemasterdb CONTAINER_NAME=rolemasterdb +# Bind the container only on loopback so nginx is the public entrypoint. +HOST_BIND_ADDRESS=127.0.0.1 + # Port mapping: host:container. HOST_PORT=8080 CONTAINER_PORT=8080 diff --git a/docs/vserver_docker_deploy.md b/docs/vserver_docker_deploy.md index 6a9551b..c2a11a2 100644 --- a/docs/vserver_docker_deploy.md +++ b/docs/vserver_docker_deploy.md @@ -1,12 +1,18 @@ # Docker Deployment To A Linux VServer -This repo now includes a deployment script for shipping `RolemasterDb.App` to a remote Linux server that you reach with: +This repo now includes a deployment script for shipping `RolemasterDb.App` to a remote Linux server that you reach as `root`, for example with: ```bash ssh myvserver ``` -The script publishes the app locally, uploads a release bundle over `scp`, builds the Docker image on the server, and replaces the running container. +The intended public URL is: + +```text +https://rolemasterdb.franktovar.de +``` + +The script publishes the app locally, uploads a release bundle over `scp`, builds the Docker image on the server, and replaces the running container. Nginx remains a one-time manual setup on the host and proxies to the container over localhost. ## Files @@ -19,10 +25,12 @@ The script publishes the app locally, uploads a release bundle over `scp`, build On the vserver: 1. Install Docker. -2. Make sure the SSH user can run `docker`, or set `REMOTE_USE_SUDO=1` in the deploy config. -3. Open the host port you want to expose, for example `8080`. +2. Install nginx. +3. Install a certificate tool such as `certbot` plus the nginx plugin if you want automated certificate provisioning. +4. Make sure `rolemasterdb.franktovar.de` resolves to this server. Your wildcard `A` and `AAAA` records already satisfy that. The deploy script stores releases under `REMOTE_APP_DIR/releases/` and keeps the SQLite database in `REMOTE_DATA_DIR/rolemaster.db`. +The container should stay behind nginx, so the default deploy config binds it to `127.0.0.1:8080`. ## Local Setup @@ -34,13 +42,78 @@ cp deploy/vserver.env.example deploy/vserver.env Adjust the values in `deploy/vserver.env` as needed: -- `REMOTE_HOST`: your SSH host or alias, for example `myvserver` -- `REMOTE_APP_DIR`: base directory on the server, for example `/opt/rolemasterdb` +- `REMOTE_HOST`: your SSH host or alias, for example `root@myvserver` +- `REMOTE_APP_DIR`: base directory on the server, default `/root/docker/rolemasterdb` - `REMOTE_DATA_DIR`: persistent directory for the SQLite database -- `HOST_PORT`: public port on the vserver -- `REMOTE_USE_SUDO`: set to `1` if remote Docker commands need `sudo` +- `HOST_BIND_ADDRESS`: default `127.0.0.1` so only nginx can reach the container +- `HOST_PORT`: nginx upstream port on the host, default `8080` +- `REMOTE_USE_SUDO`: keep `0` when you deploy as `root` - `REMOTE_ENV_FILE`: optional existing env file on the server to pass through to `docker run` +## One-Time Server Setup + +Create the base directories on the server: + +```bash +ssh myvserver +mkdir -p /root/docker/rolemasterdb/data +``` + +Deploy once so the container exists: + +```bash +./scripts/deploy-vserver.sh +``` + +Create an nginx site config at `/etc/nginx/sites-available/rolemasterdb.franktovar.de`: + +```nginx +server { + listen 80; + listen [::]:80; + server_name rolemasterdb.franktovar.de; + + location / { + proxy_pass http://127.0.0.1:8080; + 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_read_timeout 300; + } +} +``` + +Enable it and reload nginx: + +```bash +ln -s /etc/nginx/sites-available/rolemasterdb.franktovar.de /etc/nginx/sites-enabled/rolemasterdb.franktovar.de +nginx -t +systemctl reload nginx +``` + +At this point `http://rolemasterdb.franktovar.de` should work. + +## HTTPS Setup + +If `certbot` and the nginx plugin are installed, request the certificate: + +```bash +certbot --nginx -d rolemasterdb.franktovar.de +``` + +That should update the nginx config to serve HTTPS and normally also install an HTTP-to-HTTPS redirect. Verify the generated config and then reload nginx if needed: + +```bash +nginx -t +systemctl reload nginx +``` + +After that, `https://rolemasterdb.franktovar.de` should terminate TLS in nginx and proxy traffic to `127.0.0.1:8080`. + ## Deploy Run: @@ -61,6 +134,7 @@ Or point at a different config file: 2. Precreates `publish/wwwroot/components/layout` and `publish/wwwroot/components/shared` before publish. This is required because the current project otherwise fails to publish two JavaScript static assets. 3. Bundles the publish output, `src/RolemasterDb.App/rolemaster.db`, and the runtime Dockerfile into `artifacts/deploy//`. + The publish output already includes `src/RolemasterDb.App/import-artifacts`, so those files are shipped as part of every release bundle. 4. Uploads the bundle to the vserver. 5. Extracts the bundle on the vserver. 6. Seeds `REMOTE_DATA_DIR/rolemaster.db` on first deploy only. @@ -69,7 +143,7 @@ Or point at a different config file: ```text --restart unless-stopped --p HOST_PORT:CONTAINER_PORT +-p HOST_BIND_ADDRESS:HOST_PORT:CONTAINER_PORT -v REMOTE_DATA_DIR:/app/data ConnectionStrings__RolemasterDb=Data Source=/app/data/rolemaster.db ``` @@ -81,7 +155,9 @@ Running the same deploy command again will: - publish a fresh release bundle - upload a new timestamped release - keep the existing database in `REMOTE_DATA_DIR` +- keep the imported artifact files inside the image for that release - rebuild the image and restart the container +- leave the nginx config and TLS setup untouched ## Useful Remote Commands @@ -96,3 +172,10 @@ Follow logs: ```bash ssh myvserver docker logs -f rolemasterdb ``` + +Check nginx: + +```bash +ssh myvserver nginx -t +ssh myvserver systemctl status nginx +``` diff --git a/scripts/deploy-vserver.sh b/scripts/deploy-vserver.sh index 9215cf8..dc8f4be 100755 --- a/scripts/deploy-vserver.sh +++ b/scripts/deploy-vserver.sh @@ -26,6 +26,7 @@ REMOTE_APP_DIR="${REMOTE_APP_DIR:-/opt/rolemasterdb}" REMOTE_DATA_DIR="${REMOTE_DATA_DIR:-$REMOTE_APP_DIR/data}" IMAGE_NAME="${IMAGE_NAME:-rolemasterdb}" CONTAINER_NAME="${CONTAINER_NAME:-rolemasterdb}" +HOST_BIND_ADDRESS="${HOST_BIND_ADDRESS:-}" HOST_PORT="${HOST_PORT:-8080}" CONTAINER_PORT="${CONTAINER_PORT:-8080}" REMOTE_USE_SUDO="${REMOTE_USE_SUDO:-0}" @@ -89,6 +90,7 @@ ssh "$REMOTE_HOST" bash -s -- \ "$REMOTE_DATA_DIR" \ "$IMAGE_NAME" \ "$CONTAINER_NAME" \ + "$HOST_BIND_ADDRESS" \ "$HOST_PORT" \ "$CONTAINER_PORT" \ "$REMOTE_USE_SUDO" \ @@ -100,10 +102,11 @@ remote_app_dir="$2" remote_data_dir="$3" image_name="$4" container_name="$5" -host_port="$6" -container_port="$7" -remote_use_sudo="$8" -remote_env_file="$9" +host_bind_address="$6" +host_port="$7" +container_port="$8" +remote_use_sudo="$9" +remote_env_file="${10}" release_dir="$remote_app_dir/releases/$release_id" tarball_path="$release_dir/release.tar.gz" @@ -132,13 +135,18 @@ docker_run_args=( -d --name "$container_name" --restart unless-stopped - -p "$host_port:$container_port" -e "ASPNETCORE_ENVIRONMENT=Production" -e "ASPNETCORE_URLS=http://+:$container_port" -e "ConnectionStrings__RolemasterDb=Data Source=/app/data/rolemaster.db" -v "$remote_data_dir:/app/data" ) +if [[ -n "$host_bind_address" ]]; then + docker_run_args+=(-p "$host_bind_address:$host_port:$container_port") +else + docker_run_args+=(-p "$host_port:$container_port") +fi + if [[ -n "$remote_env_file" ]]; then docker_run_args+=(--env-file "$remote_env_file") fi @@ -152,7 +160,11 @@ ln -sfn "$release_dir" "$remote_app_dir/current" echo "Deployment complete." echo "Release: $release_id" echo "Container: $container_name" -echo "Host port: $host_port" +if [[ -n "$host_bind_address" ]]; then + echo "Published at: $host_bind_address:$host_port -> $container_port" +else + echo "Published at: $host_port -> $container_port" +fi EOF echo "Local bundle ready at $tarball_path"