No description
  • HTML 55%
  • TypeScript 21.5%
  • Go 14.1%
  • CSS 5.8%
  • JavaScript 3.2%
  • Other 0.4%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
Sebastian Leheis 3fb97a045f
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
Fix block text truncation in timetable by allowing dynamic height
- Remove line-clamp CSS restrictions from .timetable-band-content to allow
  text to wrap freely instead of being capped at 2 lines
- Measure actual content height after rendering and apply dynamic block
  height (max of time-duration height, measured content height, 20px minimum)
  so blocks automatically grow taller when they need more space for names/
  locations instead of truncating content

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-19 22:23:18 +02:00
backend Fix shiftplan WebSocket connection and null reference error 2026-08-19 00:43:31 +02:00
config Document shiftplan and modern theme configuration 2026-08-19 00:52:06 +02:00
frontend Fix block text truncation in timetable by allowing dynamic height 2026-08-19 22:23:18 +02:00
graphify-out Add Graphity 2026-08-17 19:08:06 +02:00
migration Add Firebase-to-Postgres signup migration script 2026-08-10 23:33:37 +02:00
.dockerignore Initial Commit 2026-08-10 22:53:18 +02:00
.env.example Fix crash on empty signups state; add config validation and DEBUG dump logging 2026-08-10 23:13:28 +02:00
.gitignore Add optional band end times and separate app config for debug settings 2026-08-17 23:04:45 +02:00
.woodpecker.yaml Add Pipeline 2026-08-18 23:37:18 +02:00
docker-compose.yml Add image from pipeline 2026-08-19 00:09:18 +02:00
Dockerfile Initial Commit 2026-08-10 22:53:18 +02:00
README.md Document shiftplan and modern theme configuration 2026-08-19 00:52:06 +02:00
sboa26-signup-firebase.html Initial Commit 2026-08-10 22:53:18 +02:00

Festival Track - Live Signup Board

Festival Track is a live festival signup board that replaces the legacy Firebase-based HTML file. It allows festival participants to sign up for performances in real time, with live synchronization across all connected browsers—no page reload necessary.

Prerequisites

  • Docker (version 20.10+)
  • Docker Compose (version 2.0+)
  • A text editor to configure config/festival.ini

Quick Start

  1. Prepare environment variables:

    cp .env.example .env
    

    Edit .env and set a strong password for POSTGRES_PASSWORD. Never commit this file to version control.

  2. Configure the festival lineup:

    cp config/festival.ini.example config/festival.ini
    

    config/festival.ini is gitignored (just like .env) so your real lineup and participant names never end up in version control -- only the generic .example template is tracked. Edit config/festival.ini to set:

    • Event metadata ([event] section): title, tagline, description, footer
    • Participant names ([participants] section): comma-separated list of names
    • Days and stages: [days] section lists the day order; one [<DayName>] section per day with stages=... line; one [<DayName>.<StageName>] section per stage with Band Name = HH:MM entries (or Band Name = HH:MM-HH:MM for optional end times)
  3. Copy the app config:

    cp config/app.ini.example config/app.ini
    

    config/app.ini is gitignored (like festival.ini and .env) so local debug settings never end up in version control. Edit config/app.ini if you need app-wide or debug settings (currently used for time overrides during testing).

  4. Start the services:

    docker compose up -d --build
    

    This builds the Docker image and starts both the PostgreSQL database and the web app.

  5. Access the app: Open your browser to http://localhost:8080 (or the port you set in .env via APP_PORT).

Configuration

Environment Variables (.env)

  • POSTGRES_USER: Database user (default: festival)
  • POSTGRES_PASSWORD: Required. Choose a strong, unique password.
  • POSTGRES_DB: Database name (default: festival)
  • APP_PORT: Host port for the web app (default: 8080)
  • DEBUG: Set to true to dump the fully parsed config (all days, stages, bands, and participants, each with the generated id) to docker compose logs app at startup. Default: off. Useful for verifying festival.ini was parsed as expected.

Config validation

At startup the app validates both festival.ini and app.ini and refuses to start if validation fails.

For festival.ini, the app checks: no participants, duplicate participant names, no days, a day with no stages, a stage with no bands, a band with an empty name/time, a band time that isn't HH:MM, a band end time (if present) that isn't HH:MM, a day with a missing/malformed date, or two bands whose generated ids collide. A sparse lineup (e.g. a day with only one stage) is valid and will not be rejected.

For app.ini, the app checks: if debug is enabled, the now field must be present and match the format YYYY-MM-DDTHH:MM:SS; and, if set, [theme] variant must be classic or modern.

INI Configuration (config/festival.ini)

Example structure:

[event]
title = Festival Name
eyebrow = Location / Year
subtitle = Description for participants
footer = Footer text

[participants]
names = Alice, Bob, Charlie, Diana

[days]
order = Day 1, Day 2, Day 3

[Day 1]
date = 2026-08-13
stages = Main Stage, Side Stage

[Day 1.Main Stage]
Band A = 14:00
Band B = 15:30
Band C = 17:00

[Day 1.Side Stage]
Band X = 14:30
Band Y = 16:00
Band Z = 17:00-17:45

Every day section needs a date = YYYY-MM-DD -- the real calendar date that day starts on. This is what lets the "Als Nächstes" section on the page work out what's genuinely coming up next (across the whole event, not just the currently selected day tab), instead of guessing from the system clock and whichever day happens to be selected. A band timed before 06:00 (e.g. 01:00) is treated as the tail end of that day's program, past midnight into the following real date -- you don't need to adjust anything for that, just set each day's date to the date its program actually starts.

Bands can optionally have an end time: use the format Band Name = HH:MM-HH:MM to specify both start and end times. If only a start time is needed, use Band Name = HH:MM as usual. Bands with an end time show up in the "Jetzt läuft" section while they're running (see below); bands without one are unaffected and behave exactly as before.

Optionally, festival.ini can also define work-shift areas so participants can sign up to help out (bar, setup, etc.) via the /shiftplan.html page -- live-synced the same way as band signups. This is entirely optional: omit the [shifts] section and the shift-planning page simply has nothing to show.

[shifts]
areas = Bar, Setup

[shift.Bar]
locations = Main Counter, Chill Counter

[shift.Setup]

areas = ... in [shifts] lists the areas in display order (same idea as stages = ... for a day). Each [shift.<AreaName>] section is optional; if present, its locations = ... line lists specific places within that area someone could work (e.g. two different bar counters). If an area has no locations line, there's no location picker for it -- just the one implicit place. On the /shiftplan.html page, each person picks a day, area, optional location, and a start/end hour (always full hours) for their own shift; everyone sees everyone else's shifts in a graphical timetable, filterable to one person or shown all at once.

Important: Configuration changes require restarting the app service:

docker compose restart app

App Configuration (config/app.ini)

This file contains app-wide settings (theme, debug). It has the same structure as festival.ini and app.ini.example -- it's gitignored so local settings stay local.

Currently, config/app.ini supports:

[debug]
enabled = false
now = 2026-08-13T15:00:00

[theme]
variant = classic
  • [debug] enabled: When set to true, the frontend treats the now timestamp as the fixed current time instead of the real system clock. Useful for testing time-dependent features (e.g. "currently playing", "coming up next") without waiting for the real event.
  • [debug] now: The fixed timestamp to use when debug is enabled. Format: YYYY-MM-DDTHH:MM:SS. This field is required if enabled = true.

While debug mode is active, a banner is shown at the top of the page ("DEBUG-MODUS AKTIV") displaying the fixed timestamp currently in effect, so it's always obvious the page isn't showing real time.

  • [theme] variant: classic (default) is the original fixed dark look -- no light/dark switch is shown. modern is a newer look that adds a light/dark toggle button at the top of every page; the chosen light/dark mode is remembered per browser (defaulting to the system's own light/dark preference on first visit). Which variant is active is a deploy-time choice made here, not something an individual visitor can change -- only light vs. dark within modern is a per-browser runtime choice.

When you change app.ini, restart the app service for changes to take effect:

docker compose restart app

"Jetzt läuft" (currently playing)

After selecting a person, the page shows a "Jetzt läuft" section above "Als Nächstes" listing any bands with an end time that person is signed up for and that are running right now (start time <= now < end time). Multiple bands can be shown at once if overlapping performances are both currently running. The section is hidden entirely when nothing is currently playing. Bands without an end time never appear here, since there's no defined time window to check against.

Architecture

  • Frontend: TypeScript single-page application (SPA) built with esbuild. Assets are statically embedded in the Go binary.
  • Backend: Go 1.25 REST + WebSocket server that handles:
    • Config queries (/api/config)
    • Health checks (/healthz)
    • Live WebSocket broadcasts for signup changes (/ws)
    • Static file serving (frontend assets)
  • Database: PostgreSQL 16 running in a separate container with persistent data volume.
  • Networking: Compose-internal network; database is not exposed to the host. Only the web app port (APP_PORT) is published.

Deployment Notes

TLS/HTTPS

This deployment runs plain HTTP. For production or public-facing use, deploy behind a reverse proxy (Caddy, nginx, Traefik) to provide HTTPS termination. Example with Caddy:

caddy reverse-proxy --from :443 --to localhost:8080

Stopping and Cleanup

Stop services without removing data:

docker compose down

Remove everything including the database volume:

docker compose down -v

Logs

View app logs:

docker compose logs -f app

View database logs:

docker compose logs -f db

Development

To rebuild the image after code changes:

docker compose up -d --build

To run without daemon mode (see logs in real time):

docker compose up --build

Press Ctrl+C to stop.