31 Commits
Author SHA1 Message Date
zwitschi 8f1e227d50 fix: Dockerfile parameter
CI / test (push) Successful in 1m35s
CI / docker (push) Successful in 3m12s
CI / release (push) Skipped
2026-08-04 13:44:00 +02:00
zwitschi 0891606321 fix: context
CI / test (push) Successful in 1m35s
CI / docker (push) Successful in 2m46s
CI / release (push) Skipped
2026-08-04 13:17:13 +02:00
zwitschi c6f28ff7af fix: rearrange steps again
CI / test (push) Successful in 1m35s
CI / docker (push) Failing after 30s
CI / release (push) Skipped
2026-08-04 13:00:23 +02:00
zwitschi 072a91961f fix: rearrange steps again
CI / test (push) Successful in 6m3s
CI / docker (push) Failing after 28s
CI / release (push) Skipped
2026-08-04 12:51:34 +02:00
zwitschi 7184240dd4 fix: move copy step up
CI / test (push) Successful in 1m34s
CI / docker (push) Failing after 15s
CI / release (push) Skipped
2026-08-04 12:46:47 +02:00
zwitschi 9cd0419c0c fix: only one media folder
CI / test (push) Successful in 6m5s
CI / docker (push) Failing after 1m52s
CI / release (push) Skipped
2026-08-04 12:37:11 +02:00
zwitschi d3266f8feb fix: reorder copy steps
CI / test (push) Successful in 1m36s
CI / docker (push) Failing after 29s
CI / release (push) Skipped
2026-08-04 12:33:05 +02:00
zwitschi b532507374 fix: adjust copy
CI / test (push) Successful in 1m32s
CI / docker (push) Failing after 15s
CI / release (push) Skipped
2026-08-04 12:27:44 +02:00
zwitschi 5c1a8cbe4f reorder copy steps
CI / test (push) Successful in 1m40s
CI / docker (push) Failing after 24s
CI / release (push) Skipped
2026-08-04 12:19:17 +02:00
zwitschi 502fefff33 fix: move demodata into playout build context, revert root media changes
CI / test (push) Successful in 1m30s
CI / docker (push) Failing after 30s
CI / release (push) Skipped
2026-08-04 12:10:38 +02:00
zwitschi bc3a950d99 fix: use repo root as build context for playout to include demodata
CI / test (push) Failing after 13s
CI / docker (push) Skipped
CI / release (push) Skipped
2026-08-04 12:01:03 +02:00
zwitschi 1af9e59a56 rename data directory for initial demo data
CI / test (push) Failing after 2m26s
CI / docker (push) Skipped
CI / release (push) Skipped
2026-08-04 11:54:44 +02:00
zwitschi 13d25a7ae0 remove empty line
CI / test (push) Failing after 16s
CI / docker (push) Skipped
CI / release (push) Skipped
2026-08-04 11:49:49 +02:00
zwitschi fc799da000 copy full folder instead of playlist.txt
CI / test (push) Failing after 2m25s
CI / docker (push) Skipped
CI / release (push) Skipped
2026-08-04 11:46:21 +02:00
zwitschi 2b091adce8 move demo-data to its own folder
CI / test (push) Failing after 2m24s
CI / docker (push) Skipped
CI / release (push) Skipped
2026-08-04 11:40:22 +02:00
zwitschi c54210434e feat: bundle demo media and playlist in playout image for out-of-the-box HLS
CI / test (push) Failing after 2m30s
CI / docker (push) Skipped
CI / release (push) Skipped
2026-08-04 11:34:20 +02:00
zwitschi fcb68cfd8d fix: add curl to chat runtime image for healthcheck
CI / test (push) Successful in 1m39s
CI / docker (push) Successful in 3m9s
CI / release (push) Skipped
2026-08-04 11:00:49 +02:00
zwitschi cdecdd2d4a fix: add curl to chat runtime image for healthcheck 2026-08-04 11:00:00 +02:00
zwitschi 47ffae5dea fix: make playlist volume rw so playout can create demo playlist
CD / deploy (push) Successful in 37s
CI / test (push) Successful in 6m2s
CI / docker (push) Successful in 2m54s
CI / release (push) Skipped
2026-08-04 10:30:02 +02:00
zwitschi 43c1a31660 fix: nginx build context, playout health check retry loop
CI / test (push) Successful in 1m39s
CI / docker (push) Successful in 3m4s
CI / release (push) Skipped
CD / deploy (push) Failing after 1m17s
2026-08-04 10:21:18 +02:00
zwitschi afb9a41404 fix: use docker exec for playout health check in CD workflow
CD / deploy (push) Failing after 23s
CI / test (push) Successful in 6m6s
CI / docker (push) Failing after 2m42s
CI / release (push) Skipped
2026-08-04 10:12:38 +02:00
zwitschi 1f2082e0fb fix: use named volume for prometheus config, seed in CD workflow
CD / deploy (push) Failing after 26s
CI / test (push) Successful in 6m2s
CI / docker (push) Failing after 2m41s
CI / release (push) Skipped
2026-08-04 10:05:59 +02:00
zwitschi 7ce6a24a77 reinstated indentation for env
CD / deploy (push) Failing after 25s
CI / test (push) Successful in 5m59s
CI / docker (push) Failing after 2m43s
CI / release (push) Skipped
2026-08-04 09:45:58 +02:00
zwitschi 450a93150c ci: fix indentation in .env generation step of CD workflow
CI / test (push) Successful in 6m4s
CI / docker (push) Failing after 3m6s
CI / release (push) Skipped
2026-08-04 09:08:52 +02:00
zwitschi dc53bce6c9 ci: remove GitHub Actions cache configuration from Docker build steps
CD / deploy (push) Failing after 1m8s
CI / test (push) Successful in 7m49s
CI / docker (push) Failing after 2m49s
CI / release (push) Skipped
2026-08-04 08:59:45 +02:00
zwitschi 32f2b7c7ab ci: use ubuntu-latest runner for docker and release jobs
CD / deploy (push) Failing after 1m54s
CI / test (push) Successful in 1m59s
CI / docker (push) Failing after 3m41s
CI / release (push) Skipped
2026-08-04 08:24:12 +02:00
zwitschi 72422f4336 feat: add Prometheus and Grafana configurations and document Alpine Docker setup
CD / deploy (push) Failing after 34s
CI / test (push) Successful in 1m40s
CI / docker (push) Canceled after 0s
CI / release (push) Canceled after 0s
2026-08-03 22:02:09 +02:00
zwitschi 78508ed16e ci: configure CD workflow to deploy to remote Docker host and document test environment
CI / test (push) Successful in 1m33s
CD / deploy (push) Failing after 15s
CI / docker (push) Canceled after 0s
CI / release (push) Canceled after 0s
2026-08-03 19:57:58 +02:00
zwitschi 8fd80c1a4c ci: generate production .env dynamically in CD workflow and update secrets documentation
CI / test (push) Successful in 1m59s
CD / deploy (push) Failing after 16s
CI / docker (push) Canceled after 0s
CI / release (push) Canceled after 0s
2026-08-03 19:37:35 +02:00
zwitschi 5fc9bc6191 build: integrate frontend build into Nginx Dockerfile, remove separate frontend image
CD / deploy (push) Failing after 1m38s
CI / test (push) Successful in 2m7s
CI / docker (push) Canceled after 0s
CI / release (push) Canceled after 0s
2026-08-03 19:17:53 +02:00
zwitschi 3c2b29ad26 refactor: rename Gitea environment variables to CI registry prefix
CD / deploy (push) Failing after 17s
CI / test (push) Successful in 1m36s
CI / docker (push) Canceled after 0s
CI / release (push) Canceled after 0s
update SETUP.md and .env.example
2026-08-03 19:02:30 +02:00
19 changed files with 388 additions and 134 deletions

No files matched your search

+7 -10
View File
@@ -31,10 +31,12 @@ ADMIN_SESSION_TTL_HOURS=12
# VITE_PUBLIC_HLS_URL=http://localhost/hls/playlist.m3u8
# Gitea Container Registry (for self-hosted Docker images)
GITEA_REGISTRY_URL=git.allucanget.biz
GITEA_USERNAME=your-gitea-username
GITEA_TOKEN=your-gitea-token
GITEA_REGISTRY_NAMESPACE=zwitschi/linearvoid
CI_REGISTRY_URL=git.allucanget.biz
CI_REGISTRY_USER=your-gitea-username
CI_REGISTRY_TOKEN=your-gitea-token
CI_REGISTRY_NAMESPACE=zwitschi/linearvoid
# Gitea API access (optional, for CI/release automation)
CI_SERVER_URL=https://git.allucanget.biz
# Chat / Application Database (compose-safe PostgreSQL by default, SQLite optional)
DATABASE_URL=postgres://linearvoid:linearvoid@db:5432/linearvoid?sslmode=disable
@@ -61,9 +63,4 @@ GRAFANA_PORT=3001
# Development flags
NODE_ENV=development
GO_ENV=development
# Gitea Configuration
GITEA_SERVER_URL=https://git.allucanget.biz
GITEA_TOKEN=your-gitea-token
GITEA_USERNAME=your-gitea-username
GO_ENV=development
+61 -12
View File
@@ -1,13 +1,17 @@
name: CD
on:
push:
workflow_run:
workflows: ["CI"]
types:
- completed
branches: [main]
workflow_dispatch:
jobs:
deploy:
runs-on: [ubuntu-latest]
if: ${{ github.event.workflow_run.conclusion == 'success' || github.event_name == 'workflow_dispatch' }}
environment: production
steps:
- name: Checkout code
@@ -22,28 +26,73 @@ jobs:
uses: docker/login-action@v3
with:
registry: git.allucanget.biz
username: ${{ secrets.GITEA_USERNAME }}
password: ${{ secrets.GITEA_TOKEN }}
username: ${{ secrets.CI_REGISTRY_USER }}
password: ${{ secrets.CI_REGISTRY_TOKEN }}
- name: Pull latest images
- name: Prepare environment
run: |
docker pull git.allucanget.biz/zwitschi/linearvoid/playout:latest || true
docker pull git.allucanget.biz/zwitschi/linearvoid/chat:latest || true
docker pull git.allucanget.biz/zwitschi/linearvoid/frontend:latest || true
docker pull git.allucanget.biz/zwitschi/linearvoid/nginx:latest || true
cat > .env << 'EOF'
DATABASE_URL="${{ secrets.DATABASE_URL }}"
REDIS_URL=redis://redis:6379/0
PLAYLIST_PATH=/app/playlist/playlist.txt
HLS_SEGMENT_DURATION=6
HLS_WINDOW_SIZE=24
LOG_LEVEL=info
ADMIN_USERNAME="${{ secrets.ADMIN_USERNAME }}"
ADMIN_PASSWORD="${{ secrets.ADMIN_PASSWORD }}"
ADMIN_SESSION_TTL_HOURS=12
NGINX_PORT=80
EOF
- name: Seed Prometheus config
run: |
mkdir -p monitoring/prometheus
cat > monitoring/prometheus/prometheus.yml << 'EOF'
global:
scrape_interval: 15s
evaluation_interval: 15s
scrape_configs:
- job_name: 'prometheus'
static_configs:
- targets: ['localhost:9090']
- job_name: 'linearvoid'
static_configs:
- targets: ['playout:8081', 'chat:3000', 'nginx:80']
EOF
- name: Deploy with docker-compose
env:
DOCKER_HOST: tcp://192.168.88.40:2375
run: |
docker compose -f docker-compose.prod.yml pull
docker compose -f docker-compose.prod.yml up -d --remove-orphans
- name: Health check
env:
DOCKER_HOST: tcp://192.168.88.40:2375
run: |
sleep 10
curl -f http://localhost:8080/health || exit 1
curl -f http://localhost:3000/health || exit 1
curl -f http://localhost:8081/health || exit 1
# Wait for playout to stabilize (it may restart during init)
sleep 20
curl -f http://192.168.88.40:3000/health || exit 1
# Retry playout health check since it may still be initializing
for i in $(seq 1 6); do
if docker exec linearvoid-playout curl -sf http://localhost:8081/health; then
echo "Playout is healthy"
exit 0
fi
echo "Playout not ready yet (attempt $i/6), waiting 5s..."
sleep 5
done
echo "Playout failed to become healthy after 6 attempts"
docker logs linearvoid-playout --tail 50
exit 1
curl -f http://192.168.88.40:3000/health || exit 1
docker exec linearvoid-playout curl -sf http://localhost:8081/health || exit 1
- name: Cleanup old images
env:
DOCKER_HOST: tcp://192.168.88.40:2375
run: |
docker image prune -f --filter "until=24h"
+23 -39
View File
@@ -48,7 +48,7 @@ jobs:
docker:
needs: test
runs-on: [self-hosted, linux, docker]
runs-on: [ubuntu-latest]
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
steps:
- name: Checkout code
@@ -63,8 +63,8 @@ jobs:
uses: docker/login-action@v3
with:
registry: git.allucanget.biz
username: ${{ secrets.GITEA_USERNAME }}
password: ${{ secrets.GITEA_TOKEN }}
username: ${{ secrets.CI_REGISTRY_USER }}
password: ${{ secrets.CI_REGISTRY_TOKEN }}
- name: Extract metadata for Playout
id: meta-playout
@@ -82,11 +82,10 @@ jobs:
uses: docker/build-push-action@v5
with:
context: ./backend/playout
file: ./backend/playout/Dockerfile
push: true
tags: ${{ steps.meta-playout.outputs.tags }}
labels: ${{ steps.meta-playout.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
- name: Extract metadata for Chat
id: meta-chat
@@ -107,30 +106,6 @@ jobs:
push: true
tags: ${{ steps.meta-chat.outputs.tags }}
labels: ${{ steps.meta-chat.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
- name: Extract metadata for Frontend
id: meta-frontend
uses: docker/metadata-action@v5
with:
images: git.allucanget.biz/zwitschi/linearvoid/frontend
tags: |
type=ref,event=branch
type=ref,event=pr
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=raw,value=latest,enable={{is_default_branch}}
- name: Build and push Frontend image
uses: docker/build-push-action@v5
with:
context: ./frontend
push: true
tags: ${{ steps.meta-frontend.outputs.tags }}
labels: ${{ steps.meta-frontend.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
- name: Extract metadata for Nginx
id: meta-nginx
@@ -145,18 +120,27 @@ jobs:
type=raw,value=latest,enable={{is_default_branch}}
- name: Build and push Nginx image
uses: docker/build-push-action@v5
with:
context: ./infrastructure/nginx
push: true
tags: ${{ steps.meta-nginx.outputs.tags }}
labels: ${{ steps.meta-nginx.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
run: |
TAGS="${{ steps.meta-nginx.outputs.tags }}"
LABELS="${{ steps.meta-nginx.outputs.labels }}"
TAG_ARGS=""
for tag in $TAGS; do
TAG_ARGS="$TAG_ARGS --tag $tag"
done
LABEL_ARGS=""
while IFS= read -r label; do
[ -n "$label" ] && LABEL_ARGS="$LABEL_ARGS --label $label"
done <<< "$LABELS"
docker buildx build \
--push \
--file infrastructure/nginx/Dockerfile \
$TAG_ARGS \
$LABEL_ARGS \
.
release:
needs: docker
runs-on: [self-hosted, linux, docker]
runs-on: [ubuntu-latest]
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/')
steps:
- name: Checkout code
@@ -169,4 +153,4 @@ jobs:
with:
generate_release_notes: true
env:
GITHUB_TOKEN: ${{ secrets.GITEA_TOKEN }}
GITHUB_TOKEN: ${{ secrets.CI_REGISTRY_TOKEN }}
+2 -2
View File
@@ -13,7 +13,7 @@ jobs:
uses: actions/checkout@v4
with:
fetch-depth: 0
token: ${{ secrets.GITEA_TOKEN }}
token: ${{ secrets.CI_REGISTRY_TOKEN }}
- name: Setup Node.js
uses: actions/setup-node@v4
@@ -42,7 +42,7 @@ jobs:
- name: Create Pull Request
uses: peter-evans/create-pull-request@v5
with:
token: ${{ secrets.GITEA_TOKEN }}
token: ${{ secrets.CI_REGISTRY_TOKEN }}
commit-message: "chore: update dependencies"
title: "chore: weekly dependency updates"
body: |
+10 -28
View File
@@ -27,8 +27,8 @@ jobs:
uses: docker/login-action@v3
with:
registry: git.allucanget.biz
username: ${{ secrets.GITEA_USERNAME }}
password: ${{ secrets.GITEA_TOKEN }}
username: ${{ secrets.CI_REGISTRY_USER }}
password: ${{ secrets.CI_REGISTRY_TOKEN }}
- name: Extract version
id: version
@@ -75,24 +75,6 @@ jobs:
tags: ${{ steps.meta-chat.outputs.tags }}
labels: ${{ steps.meta-chat.outputs.labels }}
- name: Extract metadata for Frontend
id: meta-frontend
uses: docker/metadata-action@v5
with:
images: git.allucanget.biz/zwitschi/linearvoid/frontend
tags: |
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=raw,value=latest
- name: Build and push Frontend image
uses: docker/build-push-action@v5
with:
context: ./frontend
push: true
tags: ${{ steps.meta-frontend.outputs.tags }}
labels: ${{ steps.meta-frontend.outputs.labels }}
- name: Extract metadata for Nginx
id: meta-nginx
uses: docker/metadata-action@v5
@@ -106,15 +88,15 @@ jobs:
- name: Build and push Nginx image
uses: docker/build-push-action@v5
with:
context: ./infrastructure/nginx
context: .
file: infrastructure/nginx/Dockerfile
push: true
tags: ${{ steps.meta-nginx.outputs.tags }}
labels: ${{ steps.meta-nginx.outputs.labels }}
- name: Create Release
uses: softprops/action-gh-release@v1
with:
generate_release_notes: true
tag_name: ${{ steps.version.outputs.version }}
env:
GITHUB_TOKEN: ${{ secrets.GITEA_TOKEN }}
- name: Create Release on Gitea
run: |
curl -X POST "$CI_SERVER_URL/api/v1/repos/$CI_REPO_OWNER/$CI_REPO_NAME/releases" \
-H "Authorization: token ${{ secrets.CI_REGISTRY_TOKEN }}" \
-H "Content-Type: application/json" \
-d "{\"tag_name\":\"${{ steps.version.outputs.version }}\",\"name\":\"${{ steps.version.outputs.version }}\",\"body\":\"Release ${{ steps.version.outputs.version }}\n\nSee [CHANGELOG.md](https://git.allucanget.biz/zwitschi/linearvoid/src/branch/main/CHANGELOG.md) for details.\",\"draft\":false,\"prerelease\":false}"
+10
View File
@@ -9,3 +9,13 @@ Agents instructions for the linearvoid project.
- Update documentation first before implementing changes to ensure traceability of decisions.
- Plan any task in the `TODO.md` file with clear steps and mark them as completed when done. Move completed sections to DONE.md. Write entry in root `CHANGELOG.md` for any significant changes. Avoid using in-memory todo lists or update them frequently to sync from the `TODO.md` file.
- Before taking any action except for planning and writing `TODO.md`, always ask user for confirmation, accept `go`, `ok`, `yes`, `next`, or `continue` to proceed. If the user says `no`, `stop`, or `cancel`, abort the action and return to planning.
## Security & Secrets Protection
- **Never commit raw secrets or credentials** to the repository.
- **Never print or log raw secrets** in terminal commands or output files.
- **Use Gitea Actions Secrets** for all production credentials and environment variables.
- **Avoid hardcoding sensitive values** in workflow files (`.yml`). Instead, reference them using `${{ secrets.SECRET_NAME }}`.
- **Do not use the `GITEA_` prefix** for custom secrets or environment variables in Gitea, as it is reserved internally and will be silently ignored or returned as empty. Use the `CI_` prefix instead (e.g., `CI_REGISTRY_USER`, `CI_REGISTRY_TOKEN`).
- **Keep `.env` gitignored** and use `.env.example` as the template for local development.
- **For CD deployments**, generate the `.env` file dynamically on the runner using a secure step that populates values from Gitea Actions Secrets.
+3 -4
View File
@@ -50,6 +50,9 @@ data/
*.sqlite
*.sqlite3
# Demo data (tracked in backend/playout/demodata/ for out-of-the-box playout)
!backend/playout/demodata/
# Docker
.dockerignore
**/.dockerignore
@@ -75,10 +78,6 @@ Thumbs.db
*.tfstate.backup
.terraform/
# Monitoring & temp
prometheus/
grafana/
# Agent / Tool files
.github/instructions/TODO.md
.github/instructions/DONE.md
+129 -18
View File
@@ -12,6 +12,13 @@ This guide covers prerequisites, environment setup, running the application loca
- **Git**: For cloning and version control.
- **Cloudflare Account**: For R2 storage and CDN (free tier sufficient for most use cases).
- **Gitea Account**: Required if you want to push images to the self-hosted registry or use the repository's CI/CD flow.
- Set up the required Actions secrets in Gitea at: [https://git.allucanget.biz/zwitschi/linearvoid/settings/actions/secrets](https://git.allucanget.biz/zwitschi/linearvoid/settings/actions/secrets)
- Add the following secrets:
- `CI_REGISTRY_USER`: Your Gitea username (e.g., `zwitschi`)
- `CI_REGISTRY_TOKEN`: A Gitea Personal Access Token with `write:packages` scope
- `ADMIN_USERNAME`: The administrator username for the production admin panel (e.g., `admin`)
- `ADMIN_PASSWORD`: A strong password for the production admin panel
- `DATABASE_URL`: The production database connection string (e.g., `postgres://linearvoid:<password>@db:5432/linearvoid?sslmode=disable`)
- **Hetzner Cloud Account** (optional for production): For the US-East VPS (Ashburn, VA recommended per ADR-001).
## Development Prerequisites
@@ -70,10 +77,12 @@ This guide covers prerequisites, environment setup, running the application loca
# VITE_PUBLIC_HLS_URL=http://localhost/hls/playlist.m3u8
# Gitea Container Registry (for self-hosted Docker images)
GITEA_REGISTRY_URL=git.allucanget.biz
GITEA_USERNAME=your-gitea-username
GITEA_TOKEN=your-gitea-token
GITEA_REGISTRY_NAMESPACE=zwitschi/linearvoid
CI_REGISTRY_URL=git.allucanget.biz
CI_REGISTRY_USER=your-gitea-username
CI_REGISTRY_TOKEN=your-gitea-token
CI_REGISTRY_NAMESPACE=zwitschi/linearvoid
# Gitea API access (optional, for CI/release automation)
CI_SERVER_URL=https://git.allucanget.biz
# Chat / Database
DATABASE_URL=postgres://linearvoid:linearvoid@db:5432/linearvoid?sslmode=disable
@@ -99,11 +108,6 @@ This guide covers prerequisites, environment setup, running the application loca
# Development flags
NODE_ENV=development
GO_ENV=development
# Gitea API access (optional, for CI/release automation)
GITEA_SERVER_URL=https://git.allucanget.biz
GITEA_TOKEN=your-gitea-token
GITEA_USERNAME=your-gitea-username
```
4. **Check local value overrides for Docker Compose**:
@@ -162,24 +166,33 @@ This guide covers prerequisites, environment setup, running the application loca
2. **Create an access token**:
- Generate a token with the scopes needed for package/registry access and, if applicable, repository automation.
- Store it as `GITEA_TOKEN` in `.env`.
- Store it as `CI_REGISTRY_TOKEN` in `.env`.
3. **Configure registry variables**:
- Set `GITEA_REGISTRY_URL` to the Gitea host.
- Set `GITEA_REGISTRY_NAMESPACE` to the image namespace, for example `zwitschi/linearvoid`.
- Set `GITEA_USERNAME` to the account that will push or pull images.
3. **Configure Gitea Actions Secrets**:
- Go to the repository settings page: [https://git.allucanget.biz/zwitschi/linearvoid/settings/actions/secrets](https://git.allucanget.biz/zwitschi/linearvoid/settings/actions/secrets)
- Add the following secrets to enable automated CI/CD and production deployment:
- `CI_REGISTRY_USER`: Your Gitea username (e.g., `zwitschi`).
- `CI_REGISTRY_TOKEN`: Your Gitea Personal Access Token with `write:packages` scope.
- `ADMIN_USERNAME`: The administrator username for the production admin panel (e.g., `admin`).
- `ADMIN_PASSWORD`: A strong password for the production admin panel.
- `DATABASE_URL`: The production database connection string (e.g., `postgres://linearvoid:<password>@db:5432/linearvoid?sslmode=disable`).
4. **Authenticate Docker to the registry**:
4. **Configure registry variables**:
- Set `CI_REGISTRY_URL` to the Gitea host.
- Set `CI_REGISTRY_NAMESPACE` to the image namespace, for example `zwitschi/linearvoid`.
- Set `CI_REGISTRY_USER` to the account that will push or pull images.
5. **Authenticate Docker to the registry**:
```bash
docker login git.allucanget.biz
```
5. **Build and publish images when needed**:
6. **Build and publish images when needed**:
- Local development uses `docker-compose.yml`, which builds from source.
- Production uses `docker-compose.prod.yml`, which expects the `playout`, `chat`, and `nginx` images to exist in the configured Gitea registry namespace.
6. **Validate image availability**:
7. **Validate image availability**:
- Before a production rollout, verify that the expected `:latest` tags can be pulled from the registry on the target server.
## Running the Application
@@ -230,6 +243,104 @@ This guide covers prerequisites, environment setup, running the application loca
docker compose down
```
### Local Test Environment Deployment (Remote Docker Host)
For testing deployments on a local network server without setting up a full production VPS, you can deploy directly to a remote Docker host (e.g., a home lab server or test machine) using Docker's TCP socket.
#### Test Environment Prerequisites
- A remote server running Docker at IP `192.168.88.40` (running Alpine Linux).
- Docker TCP socket enabled and listening on port `2375` on the remote server.
##### Enabling Docker TCP Socket on Alpine Linux
To allow the Gitea Actions runner (or your local machine) to deploy to the remote Alpine host, you must configure the Docker daemon to listen on the TCP socket:
1. Edit `/etc/conf.d/docker` on the Alpine host:
```bash
vi /etc/conf.d/docker
```
2. Update the `DOCKER_OPTS` line to include both the unix socket and the TCP socket:
```bash
DOCKER_OPTS="-H unix:///var/run/docker.sock -H tcp://0.0.0.0:2375"
```
3. Restart the Docker service:
```bash
rc-service docker restart
```
4. Verify that Docker is listening on port `2375`:
```bash
netstat -tlnp | grep 2375
```
5. Ensure any firewall (like `awall` or `iptables`) on the Alpine host allows incoming traffic on port `2375` from your local network subnet (e.g., `192.168.88.0/24`).
#### Deployment Steps
1. **Configure Docker CLI to use the Remote Host**:
You can point your local Docker CLI to the remote server by setting the `DOCKER_HOST` environment variable:
- **PowerShell (Windows)**:
```powershell
$env:DOCKER_HOST="tcp://192.168.88.40:2375"
```
- **Bash (Linux/macOS)**:
```bash
export DOCKER_HOST="tcp://192.168.88.40:2375"
```
2. **Verify Connection**:
Run `docker info` to confirm your local CLI is communicating with the remote server at `192.168.88.40`:
```bash
docker info
```
The output should show the remote server's hostname and system details.
3. **Deploy the Stack**:
With `DOCKER_HOST` set, any `docker` or `docker compose` commands you run locally will execute directly on the remote server.
- **Deploy from Registry (Production-like)**:
```bash
docker compose -f docker-compose.prod.yml pull
docker compose -f docker-compose.prod.yml up -d --remove-orphans
```
- **Build and Deploy from Source**:
```bash
docker compose up -d --build
```
4. **Access the Test Environment**:
- Playout Health: `http://192.168.88.40:8081/health`
- Chat Health: `http://192.168.88.40:3000/health`
- Nginx Entrypoint: `http://192.168.88.40:8080` (or the port mapped to `NGINX_PORT` in `.env`)
5. **Reset to Local Docker Host**:
Once testing is complete, clear the `DOCKER_HOST` variable to point back to your local machine:
- **PowerShell**:
```powershell
Remove-Item env:DOCKER_HOST
```
- **Bash**:
```bash
unset DOCKER_HOST
```
### Production Setup Guide
1. **Prepare the target host**:
@@ -260,7 +371,7 @@ This guide covers prerequisites, environment setup, running the application loca
```
5. **Publish or verify required images in Gitea**:
- Ensure the `playout`, `chat`, and `nginx` images exist under `${GITEA_REGISTRY_URL}/${GITEA_REGISTRY_NAMESPACE}`.
- Ensure the `playout`, `chat`, and `nginx` images exist under `${CI_REGISTRY_URL}/${CI_REGISTRY_NAMESPACE}`.
- Log in to the registry from the VPS before pulling.
6. **Deploy the production stack**:
+1 -1
View File
@@ -9,7 +9,7 @@ RUN go mod tidy
RUN CGO_ENABLED=1 GOOS=linux go build -o /chat-server ./src/main.go
FROM alpine:3.20
RUN apk --no-cache add ca-certificates
RUN apk --no-cache add ca-certificates curl curl
WORKDIR /app
COPY --from=builder /chat-server /usr/local/bin/chat-server
+8 -10
View File
@@ -16,20 +16,18 @@ RUN apk add --no-cache \
ffmpeg \
curl \
bash
# Set working directory for playout
WORKDIR /app
COPY --from=builder /playlist-manager /app/playlist-manager
# Copy the playout start script and the built playlist manager binary
COPY start-playout.sh /app/
COPY *.sh /app/
RUN chmod +x /app/*.sh /app/playlist-manager
# Copy demo data for out-of-the-box playout
COPY demodata/ /app/
# Copy the built playlist manager binary from the builder stage
COPY --from=builder /playlist-manager /app/playlist-manager
# Create directories for playlist, media, and HLS output
RUN mkdir -p /app/playlist /app/media /app/hls
RUN mkdir -p /app/hls
RUN chmod +x /app/start-playout.sh /app/playlist-manager
# Expose HLS directory (will be served by nginx)
VOLUME ["/app/hls"]
# Default command runs the playout loop (which calls the Go validator)
CMD ["./start-playout.sh"]
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,3 @@
# Linear Void Demo Playlist
file '/app/media/demo1.mp4'
file '/app/media/demo2.mp4'
+3 -2
View File
@@ -6,7 +6,7 @@ services:
env_file:
- .env
volumes:
- ./data/playlist:/app/playlist:ro
- ./data/playlist:/app/playlist:rw
- ./data/media:/app/media:ro
- ./data/hls:/app/hls:rw
networks:
@@ -102,7 +102,7 @@ services:
container_name: linearvoid-prometheus
restart: unless-stopped
volumes:
- ./monitoring/prometheus/prometheus.yml:/etc/prometheus/prometheus.yml
- prometheus-config:/etc/prometheus
- prometheus-data:/prometheus
ports:
- "9090:9090"
@@ -142,4 +142,5 @@ volumes:
redis-data:
postgres-data:
prometheus-data:
prometheus-config:
grafana-data:
+5 -5
View File
@@ -1,6 +1,6 @@
services:
playout:
image: ${GITEA_REGISTRY_URL:-git.allucanget.biz}/${GITEA_REGISTRY_NAMESPACE:-zwitschi/linearvoid}/playout:latest
image: ${CI_REGISTRY_URL:-git.allucanget.biz}/${CI_REGISTRY_NAMESPACE:-zwitschi/linearvoid}/playout:latest
build:
context: ./backend/playout
dockerfile: Dockerfile
@@ -24,7 +24,7 @@ services:
start_period: 10s
chat:
image: ${GITEA_REGISTRY_URL:-git.allucanget.biz}/${GITEA_REGISTRY_NAMESPACE:-zwitschi/linearvoid}/chat:latest
image: ${CI_REGISTRY_URL:-git.allucanget.biz}/${CI_REGISTRY_NAMESPACE:-zwitschi/linearvoid}/chat:latest
build:
context: ./backend/chat
dockerfile: Dockerfile
@@ -51,10 +51,10 @@ services:
start_period: 10s
nginx:
image: ${GITEA_REGISTRY_URL:-git.allucanget.biz}/${GITEA_REGISTRY_NAMESPACE:-zwitschi/linearvoid}/nginx:latest
image: ${CI_REGISTRY_URL:-git.allucanget.biz}/${CI_REGISTRY_NAMESPACE:-zwitschi/linearvoid}/nginx:latest
build:
context: ./infrastructure/nginx
dockerfile: Dockerfile
context: .
dockerfile: infrastructure/nginx/Dockerfile
container_name: linearvoid-nginx
restart: unless-stopped
ports:
+59
View File
@@ -217,6 +217,65 @@ Notable aspects: Manifest freshness ensures all viewers stay on same timeline; c
## Deployment View
### CI/CD Pipeline Architecture
The deployment follows a two-stage pipeline: **CI builds and pushes images**, then **CD pulls and deploys** them to the production host.
```mermaid
flowchart LR
subgraph "Gitea Actions"
CI[CI Workflow<br/>Build + Push Images]
CD[CD Workflow<br/>Pull + Deploy]
end
subgraph "Gitea Container Registry"
REGISTRY[git.allucanget.biz<br/>playout:latest<br/>chat:latest<br/>nginx:latest]
end
subgraph "Production Host (192.168.88.40)"
DOCKER[Docker Engine<br/>via TCP :2375]
PLAYOUT[linearvoid-playout]
CHAT[linearvoid-chat]
NGINX[linearvoid-nginx]
REDIS[linearvoid-redis]
DB[linearvoid-db]
PROM[linearvoid-prometheus]
GRAF[linearvoid-grafana]
end
CI -->|push| REGISTRY
REGISTRY -->|pull| CD
CD -->|deploy via DOCKER_HOST| DOCKER
DOCKER --> PLAYOUT
DOCKER --> CHAT
DOCKER --> NGINX
DOCKER --> REDIS
DOCKER --> DB
DOCKER --> PROM
DOCKER --> GRAF
classDef ci fill:#e1f5fe,stroke:#0288d1
classDef reg fill:#fff3e0,stroke:#f57c00
classDef prod fill:#e8f5e9,stroke:#388e3c
class CI,CD ci
class REGISTRY reg
class DOCKER,PLAYOUT,CHAT,NGINX,REDIS,DB,PROM,GRAF prod
```
**Pipeline Sequence:**
1. **CI Workflow** (`.gitea/workflows/ci.yml`):
- Triggers on `push` to `main` or `pull_request`
- Runs `test` job: frontend lint/build, Go validation, `docker compose build`
- Runs `docker` job (after `test` passes, only on `main` push):
- Builds `playout`, `chat`, `nginx` images with Buildx
- Pushes to Gitea Container Registry with `main` and `latest` tags
2. **CD Workflow** (`.gitea/workflows/cd.yml`):
- Triggers on `workflow_run` when CI completes successfully, or manually via `workflow_dispatch`
- Pulls latest images from registry
- Generates `.env` from secrets
- Seeds Prometheus config into named volume
- Deploys via `docker compose -f docker-compose.prod.yml up -d` to remote host (`192.168.88.40:2375`)
- Runs health checks with retry logic for playout initialization
### Infrastructure Level 1
**Overview Diagram**: See Mermaid in Building Block section (VPS + Cloudflare R2/Pages/CDN).
+15 -3
View File
@@ -1,9 +1,21 @@
# Stage 1: Build frontend
FROM node:20-alpine AS builder
WORKDIR /build
COPY frontend/package*.json ./
RUN npm ci
COPY frontend/ .
RUN npm run build
# Stage 2: Nginx with bundled frontend
FROM nginx:alpine
COPY nginx.conf /etc/nginx/nginx.conf
COPY infrastructure/nginx/nginx.conf /etc/nginx/nginx.conf
# Create directories for HLS and frontend
RUN mkdir -p /srv/frontend /srv/hls /srv/frontend/admin
# Copy prebuilt frontend from builder stage
COPY --from=builder /build/dist/ /srv/frontend/
# Create HLS directory
RUN mkdir -p /srv/hls
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
+39
View File
@@ -0,0 +1,39 @@
{
"title": "Linear Void Monitoring",
"panels": [
{
"title": "Concurrent Viewers",
"type": "timeseries",
"targets": [{ "expr": "linearvoid_chat_connected_clients" }]
},
{
"title": "Stored Messages",
"type": "stat",
"targets": [
{
"expr": "linearvoid_chat_messages_total"
}
]
},
{
"title": "Moderation Actions",
"type": "timeseries",
"targets": [{ "expr": "linearvoid_chat_moderation_actions_total" }]
},
{
"title": "Redis Reachable",
"type": "stat",
"targets": [{ "expr": "linearvoid_chat_redis_up" }]
},
{
"title": "Admin Sessions",
"type": "timeseries",
"targets": [{ "expr": "linearvoid_chat_admin_sessions_active" }]
},
{
"title": "Chat Scrape Health",
"type": "stat",
"targets": [{ "expr": "up{job=\"chat\"}" }]
}
]
}
+10
View File
@@ -0,0 +1,10 @@
global:
scrape_interval: 15s
scrape_configs:
- job_name: "chat"
static_configs:
- targets: ["chat:3000"]
- job_name: "prometheus"
static_configs:
- targets: ["prometheus:9090"]