4 Commits

Author SHA1 Message Date
c7c6bed56c docs: add Nginx reverse proxy configuration guide
All checks were successful
Build and Push Presejpacky Docker Image / build-and-push (push) Successful in 8s
2026-06-07 23:55:23 +02:00
f106ba0248 feat: support dynamic base path in Express server via proxy headers
All checks were successful
Build and Push Presejpacky Docker Image / build-and-push (push) Successful in 6s
2026-06-07 23:53:34 +02:00
bd0672e49d ci: update build-presejpacky workflow to match validated Gitea template
All checks were successful
Build and Push Presejpacky Docker Image / build-and-push (push) Successful in 50s
2026-06-07 23:29:40 +02:00
7f4b463e4c chore: adjust paddys-endzone container and speed settings 2026-06-07 17:01:08 +02:00
5 changed files with 178 additions and 55 deletions

View File

@@ -1,6 +1,12 @@
name: Build and Push Presejpacky Docker Image
on:
workflow_dispatch:
inputs:
tag:
description: 'Image tag'
required: true
default: 'latest'
push:
branches:
- main
@@ -12,35 +18,29 @@ on:
jobs:
build-and-push:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Log in to Gitea Registry
uses: docker/login-action@v3
with:
registry: gitea.home.hrajfrisbee.cz
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Docker metadata
id: meta
uses: docker/metadata-action@v5
with:
images: |
gitea.home.hrajfrisbee.cz/${{ github.repository_owner }}/flat-stack-presejpacky
tags: |
type=raw,value=latest,enable=${{ github.ref == 'refs/heads/main' }}
type=match,pattern=presejpacky-v(.*),group=1
- name: Login to Gitea registry
run: echo "${{ secrets.REGISTRY_TOKEN }}" | docker login -u ${{ github.actor }} --password-stdin gitea.home.hrajfrisbee.cz
- name: Build and push image
uses: docker/build-push-action@v6
with:
context: drills/flat-stack-presejpacky
file: drills/flat-stack-presejpacky/Dockerfile
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
run: |
TAG=${{ github.ref_name }}
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
TAG=${{ inputs.tag }}
elif [ "${{ github.ref }}" = "refs/heads/main" ]; then
TAG="latest"
fi
IMAGE=gitea.home.hrajfrisbee.cz/${{ github.repository_owner }}/flat-stack-presejpacky:$TAG
docker build -f drills/flat-stack-presejpacky/Dockerfile \
--build-arg GIT_TAG=$TAG \
--build-arg GIT_COMMIT=${{ github.sha }} \
--build-arg BUILD_DATE=$(date -u +%Y-%m-%dT%H:%M:%SZ) \
-t $IMAGE drills/flat-stack-presejpacky
docker push $IMAGE

View File

@@ -0,0 +1,113 @@
# Nginx Reverse Proxy Configuration Guide
This guide describes how to configure an Nginx reverse proxy to host the `flat-stack-presejpacky` container, either at the root domain or under a specific subpath (e.g., `/presejpacky`), using dynamic path rewriting based on HTTP headers.
## Overview
The application is built to be path-agnostic at compile time. At runtime, the Express server inspects HTTP headers to dynamically rewrite script and style references in `index.html` and strip the path prefix from incoming resource requests.
This enables running the same Docker image under any subpath without rebuilds.
The server checks for the presence of the following headers (in order):
1. `X-Forwarded-Prefix`
2. `X-Base-Path`
---
## 1. Hosting under a Subpath (e.g., `/presejpacky`)
To serve the application on a subpath, configure Nginx to pass the request path prefix in the `X-Forwarded-Prefix` header.
### Option A: Stripping the prefix in Nginx (Recommended)
If your `proxy_pass` directive has a trailing slash, Nginx automatically strips the matched URI prefix before forwarding the request to the container.
```nginx
server {
listen 80;
server_name drills.home.hrajfrisbee.cz;
location /presejpacky/ {
# Forward requests to the container, stripping "/presejpacky"
proxy_pass http://presejpacky-container:3000/;
# Forward host and IP headers
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;
# Pass the subpath prefix so the container can dynamically prefix asset paths
proxy_set_header X-Forwarded-Prefix /presejpacky;
}
# Redirect requests missing a trailing slash (e.g., /presejpacky -> /presejpacky/)
# to ensure relative browser assets resolve correctly.
location = /presejpacky {
return 301 $scheme://$host$request_uri/;
}
}
```
### Option B: Preserving the prefix in Nginx
If your `proxy_pass` directive does not have a trailing slash, Nginx forwards the request with the subpath prefix intact. The Express server's built-in middleware will strip it.
```nginx
server {
listen 80;
server_name drills.home.hrajfrisbee.cz;
location /presejpacky/ {
# Forward requests with "/presejpacky" intact
proxy_pass http://presejpacky-container:3000;
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 X-Forwarded-Prefix /presejpacky;
}
location = /presejpacky {
return 301 $scheme://$host$request_uri/;
}
}
```
---
## 2. Hosting on a Root Domain (e.g., `https://presejpacky.example.com`)
If you want to dedicate an entire domain or subdomain to the application, no prefix stripping or rewriting is required.
```nginx
server {
listen 80;
server_name presejpacky.example.com;
location / {
proxy_pass http://presejpacky-container:3000;
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;
}
}
```
---
## 3. Verifying the Setup
You can verify that the headers are being set and processed correctly by inspecting the source of the returned HTML in the browser:
- If Nginx is correctly sending `X-Forwarded-Prefix: /presejpacky`, the references in the `<head>` and `<body>` tags of `index.html` should be rendered with the prefix:
```html
<link rel="stylesheet" href="/presejpacky/assets/index-XYZ.css">
<script type="module" src="/presejpacky/assets/index-XYZ.js"></script>
```
- If the headers are not set or not forwarded, the paths will fallback to the root:
```html
<link rel="stylesheet" href="/assets/index-XYZ.css">
<script type="module" src="/assets/index-XYZ.js"></script>
```

View File

@@ -1,6 +1,7 @@
import express from 'express';
import path from 'path';
import { fileURLToPath } from 'url';
import fs from 'fs';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
@@ -8,12 +9,44 @@ const __dirname = path.dirname(__filename);
const app = express();
const PORT = process.env.PORT || 3000;
// Middleware to strip base path prefix from request headers if present
app.use((req, res, next) => {
const prefix = (req.headers['x-forwarded-prefix'] as string) || (req.headers['x-base-path'] as string) || '';
const cleanPrefix = prefix.endsWith('/') ? prefix.slice(0, -1) : prefix;
if (cleanPrefix && req.url.startsWith(cleanPrefix)) {
req.url = req.url.substring(cleanPrefix.length);
if (!req.url.startsWith('/')) {
req.url = '/' + req.url;
}
}
next();
});
// Serve static files from the 'dist' directory
app.use(express.static(path.join(__dirname, 'dist')));
// Fallback to index.html for Single Page Application routing
// Fallback to index.html for Single Page Application routing, injecting base path from headers
app.get('*', (req, res) => {
res.sendFile(path.join(__dirname, 'dist', 'index.html'));
const indexPath = path.join(__dirname, 'dist', 'index.html');
fs.readFile(indexPath, 'utf8', (err, html) => {
if (err) {
return res.status(500).send('Error reading index.html');
}
const prefix = (req.headers['x-forwarded-prefix'] as string) || (req.headers['x-base-path'] as string) || '';
const cleanPrefix = prefix.endsWith('/') ? prefix.slice(0, -1) : prefix;
// Dynamically prefix asset URLs in index.html with the base path
let modifiedHtml = html;
if (cleanPrefix) {
modifiedHtml = html
.replace(/(href|src)="\/assets\//g, `$1="${cleanPrefix}/assets/`)
.replace(/(href|src)="\/vite.svg"/g, `$1="${cleanPrefix}/vite.svg"`);
}
res.send(modifiedHtml);
});
});
app.listen(PORT, () => {

View File

@@ -25,7 +25,8 @@
.container {
width: 100%;
max-width: 650px;
height: 80%;
max-width: 450px;
display: flex;
flex-direction: column;
gap: 15px;
@@ -133,8 +134,8 @@
<div class="speed-row">
<label for="speedSelect">Animation Speed:</label>
<select id="speedSelect">
<option value="1">Normal</option>
<option value="2" selected>Faster</option>
<option value="1" selected>Normal</option>
<option value="2">Faster</option>
<option value="4">High Speed</option>
<option value="8">Insane (No Skips)</option>
</select>

View File

@@ -1,25 +1 @@
Can you create ultimate frisbee drill animation for me?
```
Drill: Paddy's endzone
Players: 2 short vertical stacks in the endzone separated by slightly bigger space in the middle, players in the stack are close together, one at the back and other on the front each of them moving independently on the other, one player in front of the endzone, about 10-20 metres away and 10 metres from the sideline
Field:
- Field has lines as ultimate frisbee field would have
- make proportions of the endzone and rest of the field realistic
- Field takes up whole width of the canvas
- It is enough to have only half of the field in the picture
Setup: H1 at the disc, H2,H4,H6 in the back stack - starting with H2 closest to back endzone libe, H3, H5, H7 in the front stack - starting with H3 closest to player with disc, whole front stack is fully in the endzone
Extra details:
- when you are showing pass in the animation make it at least in 3 frames
- make default speed faster and faster even faster, possibly add 2 more speed options, faster speed should not skip frames, it just should move quicker from one to another
- make sure players do not cut out of bounds
- allow to go through animation one step at a time
Steps:
1. H2 cuts to the oposite side of H1 with cut parallel to the back line and H1 throws around forehand over both stacks and hits H2 about 3 metres off the sideline curving to the receiver
2. H1 moves into position closest to the front of end zone of the back stack, whole back stack moves so that player closest to the back of endzone is on the same position last player was on the start of the drill
3. H3 cuts out of the endzone to similar place where H1 was, but on the other side and gets pass from H2
4. H2 moves to the back of the front stack which moves in the way it now starts on the same spot as when we begun the drill
5. H4 cuts in the same way H2 cut in the step 1, only to the oposite side and H3 throws around backhand both stacks and hits H4 about 3 metres off the sideline curving to the receiver
7. H3 moves into position closest to the front of end zone of the back stack, whole back stack moves so that player closest to the back of endzone is on the same position last player was on the start of the drill
6. H5 cuts out of the endzone to place where H1 was, gets pass from H4, H4 moves to the back of the front stack
```