# Deploying twotalesvfx.com Same setup as the other apps (vfxreview, xbid): ``` git push main ──► Gitea Actions runner ──► cd /opt/ttvfx_web && git pull docker compose up -d --build │ Cloudflare ──► Nginx Proxy Manager ──► ttvfx-web:80 (nginx, static files) (npm_default network) ``` Files involved: | File | What it does | |------|--------------| | `Dockerfile` | Builds the static export with Node 20, then serves `out/` from `nginx:alpine` | | `docker/nginx.conf` | nginx config inside the container (routes, 404 page, caching, relative redirects) | | `docker-compose.yml` | One service, container name `ttvfx-web`, joined to the external `npm_default` network, no host ports | | `.gitea/workflows/deploy.yml` | On push to `main`: pull in `/opt/ttvfx_web` and rebuild | There is no `.env` and no database. The site is fully static. --- ## One-time setup on the server ### 1. SSH in ```bash ssh @178.104.56.62 # git.twotalesvfx.com's IP; use your usual host if different ``` ### 2. Check how the existing apps are set up, and copy that ```bash ls -la /opt # note the owner of /opt/xbid and /opt/vfxreview git -C /opt/xbid remote -v # note HOW it authenticates (https+token, ssh, ...) docker network ls | grep npm_default ``` `npm_default` must exist. It's the Nginx Proxy Manager network. ### 3. Clone into /opt/ttvfx_web Use the same remote style as `/opt/xbid` so the runner's `git pull` can authenticate. For example, if xbid uses an https remote: ```bash sudo git clone https://git.twotalesvfx.com/pinto/ttvfx.git /opt/ttvfx_web ``` Then match the ownership of the other app folders (replace `` with what `ls -la /opt` showed): ```bash sudo chown -R : /opt/ttvfx_web ``` If xbid's remote URL contains a token or uses SSH, clone this one the same way (e.g. `git clone git@git.twotalesvfx.com:pinto/ttvfx.git /opt/ttvfx_web`). ### 4. First build by hand ```bash cd /opt/ttvfx_web docker compose up -d --build docker ps --filter name=ttvfx-web # should be "Up" ``` The first build takes a minute or two (npm install + Next build). Later builds reuse the cache. Check that it serves from inside the proxy network: ```bash docker run --rm --network npm_default curlimages/curl -sI http://ttvfx-web/ # -> HTTP/1.1 200 OK ``` ### 5. Nginx Proxy Manager: add the proxy host In the NPM web UI → **Hosts → Proxy Hosts → Add Proxy Host**: - **Domain names:** `twotalesvfx.com`, `www.twotalesvfx.com` - **Scheme:** `http` - **Forward hostname / IP:** `ttvfx-web` - **Forward port:** `80` - Tick **Block common exploits**. Websockets aren't needed. - **SSL tab:** request a new Let's Encrypt certificate, then tick **Force SSL** and **HTTP/2**. - Cloudflare is proxying the domain. If the HTTP challenge fails, either grey-cloud the two records for a minute while the cert is issued, or use **DNS challenge → Cloudflare** with an API token. Use whichever you did for `review.` / `xbid.`. ### 6. Cloudflare DNS The apex currently returns **522** (Cloudflare can't reach the origin). Point it at this server: | Type | Name | Value | Proxy | |------|-------|------------------|---------| | A | `@` | `178.104.56.62` | Proxied | | A | `www` | `178.104.56.62` | Proxied | (Confirm the server's IP with `curl -4 ifconfig.me` on the box.) Leave `review`, `git`, `xbid` etc. alone. Set **SSL/TLS → Full (strict)**, the same as the other subdomains. ### 7. Turn on auto-deploy The workflow is already in the repo (`.gitea/workflows/deploy.yml`). In Gitea, open **pinto/ttvfx → Settings → Repository → Enable Actions** if it isn't enabled already (it's on for the other repos). The existing runner picks the job up. It uses the same `ubuntu-latest` label and `/opt:/opt` mount as xbid. Test it: push any commit to `main` and watch **Actions** in the repo. The job should run `git pull` and `docker compose up -d --build`, then the site updates. ### 8. Private shot previews at /drops/ `/drops/` is a view-only screening page for vendors and clients. nginx asks for a login before it serves the page or any clip. The login file and the clips stay on the server only, in `docker/auth/` and `docker/drops/`, and are never committed. The page reads `docker/drops/manifest.json` to know which clips to show. **Share a drop.** On your PC, convert the clips to web MP4 without re-encoding, then copy them up: ```bash ffmpeg -i SHOT.mov -map 0:v -map 0:a? -c copy -movflags +faststart -map_metadata -1 SHOT.mp4 scp SHOT.mp4 manifest.json @178.104.56.62:/opt/ttvfx_web/docker/drops/ ``` `manifest.json` looks like this (`src` is the file name in `docker/drops/`): ```json { "title": "DH2 shot previews", "note": "For review only.", "files": [{ "name": "DH2_VFX_027_210_REF", "src": "DH2_VFX_027_210_REF.mp4" }] } ``` Then set a password on the server. Use a new password for each vendor and drop: ```bash cd /opt/ttvfx_web mkdir -p docker/auth printf "vendor:$(openssl passwd -apr1)\n" > docker/auth/drops.htpasswd # prompts for the password docker compose up -d --force-recreate # first time only, so the folders get mounted ``` Send `https://twotalesvfx.com/drops/`, the username `vendor` and the password through two different channels (for example, the link by email and the password by WhatsApp). **Take it down.** No deploy is needed. Delete the clips and the login, and the page returns "taken down" or 500 straight away: ```bash rm /opt/ttvfx_web/docker/drops/* /opt/ttvfx_web/docker/auth/drops.htpasswd ``` If the login file is missing, `/drops/` returns 500, so it is never left open. Nothing in `/drops/` is cached by browsers or Cloudflare. --- ## Day to day ```bash git push # from your machine. That's it, the runner deploys. ``` Manual redeploy or logs on the server: ```bash cd /opt/ttvfx_web && git pull && docker compose up -d --build docker logs -f ttvfx-web ``` ## Troubleshooting | Symptom | Fix | |---------|-----| | Action fails on `git pull` with *dubious ownership* | `git config --global --add safe.directory /opt/ttvfx_web` (run it however you did for xbid), or fix ownership to match `/opt/xbid` | | Action fails on `git pull` with *authentication* | The clone's remote URL needs the same credentials as `/opt/xbid` (`git -C /opt/xbid remote -v`) | | `network npm_default declared as external, but could not be found` | NPM's network has a different name on this box. Check `docker network ls` and update `docker-compose.yml` | | NPM shows **502 Bad Gateway** | Container not running (`docker ps`) or wrong forward host. It must be `ttvfx-web`, port `80` | | Cloudflare **522** | DNS `@`/`www` still point at the wrong IP, or ports 80/443 are firewalled | | Old content after deploy | Purge Cloudflare cache (Caching → Purge Everything). HTML isn't cached long, but video and images are cached for 7 days |