diff --git a/group_vars/all/09_networks.yml b/group_vars/all/09_networks.yml index 779c1012..f6f06dbd 100644 --- a/group_vars/all/09_networks.yml +++ b/group_vars/all/09_networks.yml @@ -96,6 +96,8 @@ defaults_networks: subnet: 192.168.103.160/28 web-svc-logout: subnet: 192.168.103.176/28 + web-app-chess: + subnet: 192.168.103.192/28 # /24 Networks / 254 Usable Clients web-app-bigbluebutton: diff --git a/group_vars/all/09_ports.yml b/group_vars/all/09_ports.yml index 8f624c06..636049c9 100644 --- a/group_vars/all/09_ports.yml +++ b/group_vars/all/09_ports.yml @@ -71,6 +71,7 @@ ports: web-app-mig: 8047 web-svc-logout: 8048 web-app-bookwyrm: 8049 + web-app-chess: 8050 web-app-bigbluebutton: 48087 # This port is predefined by bbb. @todo Try to change this to a 8XXX port public: # The following ports should be changed to 22 on the subdomain via stream mapping diff --git a/roles/web-app-chess/README.md b/roles/web-app-chess/README.md index 8a2ad5bf..98e3e198 100644 --- a/roles/web-app-chess/README.md +++ b/roles/web-app-chess/README.md @@ -1,2 +1,25 @@ -# Todo -- Implement https://joinbookwyrm.com/de/ \ No newline at end of file +# web-app-chess + +## Description + +**castling.club** is a federated chess server built on the ActivityPub protocol. +It provides an open and decentralized way to play chess online, where games and moves are visible across the Fediverse. + +## Overview + +Instead of relying on closed platforms, castling.club uses an arbiter actor (“the King”) to validate moves and mediate matches. +This ensures fair play, federation with platforms like Mastodon or Friendica, and community visibility of ongoing games. +The service runs as a lightweight Node.js app backed by PostgreSQL. + +## Features + +- **Federated Chess Matches:** Challenge and play with others across the Fediverse. +- **Rule Enforcement:** The arbiter validates each move for correctness. +- **Open Identities:** Use your existing Fediverse account; no new silo account needed. +- **Game Visibility:** Matches and moves can appear in social timelines. +- **Lightweight Service:** Built with Node.js and PostgreSQL for efficiency. + +## Further Resources + +- [castling.club GitHub Repository](https://github.com/stephank/castling.club) +- [ActivityPub Specification (W3C)](https://www.w3.org/TR/activitypub/) diff --git a/roles/web-app-chess/config/main.yml b/roles/web-app-chess/config/main.yml new file mode 100644 index 00000000..5ce30b24 --- /dev/null +++ b/roles/web-app-chess/config/main.yml @@ -0,0 +1,34 @@ +# roles/web-app-chess/config/main.yml +credentials: {} +docker: + services: + database: + enabled: true # Use central DB role (recommended) + application: + image: "node" # Base image family; final image is custom + version: "20-bullseye" # >=16 as required upstream + name: "web-app-chess" + backup: + no_stop_required: true + volumes: + data: "chess_data" +features: + matomo: false + css: false + desktop: false + central_database: true + logout: false + oidc: false +server: + csp: + whitelist: {} + flags: {} + domains: + canonical: + - "chess.{{ PRIMARY_DOMAIN }}" + aliases: [] +rbac: + roles: {} +source: + repo: "https://github.com/stephank/castling.club.git" + ref: "main" diff --git a/roles/web-app-chess/meta/main.yml b/roles/web-app-chess/meta/main.yml index 5e9b28ae..d2b8a8be 100644 --- a/roles/web-app-chess/meta/main.yml +++ b/roles/web-app-chess/meta/main.yml @@ -1,7 +1,7 @@ --- galaxy_info: author: "Kevin Veen-Birchenbach" - description: "Stub role for deploying a Chess web application via Docker Compose (implementation pending)." + description: "Federated chess server based on ActivityPub. Play and follow games across the Fediverse with verified rules and open identities." license: "Infinito.Nexus NonCommercial License" license_url: "https://s.infinito.nexus/license" company: | @@ -10,13 +10,16 @@ galaxy_info: https://www.veen.world galaxy_tags: - chess - - docker + - federation + - activitypub + - social repository: "https://s.infinito.nexus/code" issue_tracker_url: "https://s.infinito.nexus/issues" - documentation: "https://s.infinito.nexus/code/tree/main/roles/web-app-chess" + documentation: "https://github.com/stephank/castling.club" + logo: + class: "fas fa-chess-king" min_ansible_version: "2.9" platforms: - name: Any versions: [ all ] dependencies: [] - diff --git a/roles/web-app-chess/tasks/02_assets.yml b/roles/web-app-chess/tasks/02_assets.yml new file mode 100644 index 00000000..986a0c05 --- /dev/null +++ b/roles/web-app-chess/tasks/02_assets.yml @@ -0,0 +1,10 @@ +- block: + - name: "load docker, db and proxy for {{ application_id }}" + include_role: + name: sys-stk-full-stateful + + - name: "Place entrypoint and other assets" + include_tasks: 02_assets.yml + + - include_tasks: utils/run_once.yml + when: run_once_web_app_chess is not defined \ No newline at end of file diff --git a/roles/web-app-chess/tasks/main.yml b/roles/web-app-chess/tasks/main.yml new file mode 100644 index 00000000..34513c37 --- /dev/null +++ b/roles/web-app-chess/tasks/main.yml @@ -0,0 +1,8 @@ +--- +- block: + - name: "load docker, db and proxy for {{ application_id }}" + include_role: + name: sys-stk-full-stateful + + - include_tasks: utils/run_once.yml + when: run_once_web_app_chess is not defined \ No newline at end of file diff --git a/roles/web-app-chess/templates/Dockerfile.j2 b/roles/web-app-chess/templates/Dockerfile.j2 new file mode 100644 index 00000000..5dff1966 --- /dev/null +++ b/roles/web-app-chess/templates/Dockerfile.j2 @@ -0,0 +1,47 @@ +# Multi-stage build for castling.club +# Stage 1: build +FROM node:{{ CHESS_VERSION }} AS build + +ARG CHESS_REPO_URL={{ CHESS_REPO_URL }} +ARG CHESS_REPO_REF={{ CHESS_REPO_REF }} + +RUN apt-get update && apt-get install -y --no-install-recommends \ + git ca-certificates openssl dumb-init python3 build-essential \ + && rm -rf /var/lib/apt/lists/* + +WORKDIR /src +RUN git clone --depth 1 --branch "${CHESS_REPO_REF}" "${CHESS_REPO_URL}" ./ + +# Yarn is preinstalled in Node images via corepack; enable it. +RUN corepack enable + +# Install deps and build TS +RUN yarn install --frozen-lockfile && yarn build + +# Stage 2: runtime +FROM node:{{ CHESS_VERSION }} + +ENV NODE_ENV=production +ENV PORT={{ container_port }} +WORKDIR /app + +# Minimal runtime packages + dumb-init +RUN apt-get update && apt-get install -y --no-install-recommends \ + openssl dumb-init postgresql-client \ + && rm -rf /var/lib/apt/lists/* + +# Copy built app +COPY --from=build /src /app + +# Create data dir for signing keys & cache +RUN mkdir -p /app/data && chown -R node:node /app +VOLUME ["/app/data"] + +# Entrypoint script +COPY docker-entrypoint.sh /usr/local/bin/docker-entrypoint.sh +RUN chmod +x /usr/local/bin/docker-entrypoint.sh + +USER node +EXPOSE {{ container_port }} +ENTRYPOINT ["dumb-init", "--"] +CMD ["docker-entrypoint.sh"] diff --git a/roles/web-app-chess/templates/docker-compose.yml.j2 b/roles/web-app-chess/templates/docker-compose.yml.j2 new file mode 100644 index 00000000..bff1a0b8 --- /dev/null +++ b/roles/web-app-chess/templates/docker-compose.yml.j2 @@ -0,0 +1,29 @@ +{% include 'roles/docker-compose/templates/base.yml.j2' %} + application: + build: + context: . + dockerfile: Dockerfile + args: + CHESS_REPO_URL: "{{ CHESS_REPO_URL }}" + CHESS_REPO_REF: "{{ CHESS_REPO_REF }}" + image: "castling_custom" + container_name: "{{ CHESS_CONTAINER }}" + hostname: "{{ CHESS_HOSTNAME }}" + environment: + - NODE_ENV=production + ports: + - "127.0.0.1:{{ ports.localhost.http[application_id] }}:{{ container_port }}" + volumes: + - 'data:/app/data' + env_file: + - .env +{% include 'roles/docker-container/templates/healthcheck/curl.yml.j2' %} +{% include 'roles/docker-container/templates/base.yml.j2' %} +{% include 'roles/docker-container/templates/depends_on/dmbs_excl.yml.j2' %} +{% include 'roles/docker-container/templates/networks.yml.j2' %} + +{% include 'roles/docker-compose/templates/volumes.yml.j2' %} + data: + name: {{ CHESS_DATA_VOLUME }} + +{% include 'roles/docker-compose/templates/networks.yml.j2' %} diff --git a/roles/web-app-chess/templates/docker-entrypoint.sh b/roles/web-app-chess/templates/docker-entrypoint.sh new file mode 100644 index 00000000..ab89f119 --- /dev/null +++ b/roles/web-app-chess/templates/docker-entrypoint.sh @@ -0,0 +1,27 @@ +#!/usr/bin/env bash +set -euo pipefail + +APP_KEY_FILE="${APP_KEY_FILE:-/app/data/{{ CHESS_KEY_FILENAME }}}" +APP_KEY_PUB="${APP_KEY_FILE}.pub" + +# 1) Generate signing key pair if missing +if [[ ! -f "${APP_KEY_FILE}" || ! -f "${APP_KEY_PUB}" ]]; then + echo "[chess] generating RSA signing key pair at ${APP_KEY_FILE}" + /app/tools/gen-signing-key.sh "${APP_KEY_FILE}" +fi + +# 2) Wait for PostgreSQL if env is provided +if [[ -n "${PGHOST:-}" ]]; then + echo "[chess] waiting for PostgreSQL at ${PGHOST}:${PGPORT:-5432}..." + until pg_isready -h "${PGHOST}" -p "${PGPORT:-5432}" -U "${PGUSER:-postgres}" >/dev/null 2>&1; do + sleep 1 + done +fi + +# 3) Run migrations (idempotent) +echo "[chess] running migrations" +yarn migrate up + +# 4) Start app +echo "[chess] starting server on port ${PORT:-5080}" +exec yarn start diff --git a/roles/web-app-chess/templates/env.j2 b/roles/web-app-chess/templates/env.j2 new file mode 100644 index 00000000..4cc0ba88 --- /dev/null +++ b/roles/web-app-chess/templates/env.j2 @@ -0,0 +1,16 @@ +# App basics +APP_SCHEME="{{ 'https' if WEB_PROTOCOL == 'https' else 'http' }}" +APP_DOMAIN="{{ CHESS_HOSTNAME }}" +APP_ADMIN_URL="{{ CHESS_ADMIN_URL }}" +APP_ADMIN_EMAIL="{{ CHESS_ADMIN_EMAIL }}" +APP_KEY_FILE="/app/data/{{ CHESS_KEY_FILENAME }}" +APP_HMAC_SECRET="{{ CHESS_HMAC_SECRET }}" +NODE_ENV="production" +PORT="{{ container_port }}" + +# PostgreSQL (libpq envs) +PGHOST="{{ database_host }}" +PGPORT="{{ database_port }}" +PGDATABASE="{{ database_name }}" +PGUSER="{{ database_username }}" +PGPASSWORD="{{ database_password }}" diff --git a/roles/web-app-chess/vars/main.yml b/roles/web-app-chess/vars/main.yml index 92b1527f..e7ffcba1 100644 --- a/roles/web-app-chess/vars/main.yml +++ b/roles/web-app-chess/vars/main.yml @@ -1 +1,25 @@ -application_id: web-app-chess \ No newline at end of file +# General +application_id: "web-app-chess" +database_type: "postgres" +container_port: 5080 +container_hostname: "{{ domains | get_domain(application_id) }}" + +# App URLs & meta +#CHESS_URL: "{{ domains | get_url(application_id, WEB_PROTOCOL) }}" +CHESS_HOSTNAME: "{{ container_hostname }}" +CHESS_ADMIN_URL: "" +CHESS_ADMIN_EMAIL: "" + +# Docker image +#CHESS_IMAGE: "{{ applications | get_app_conf(application_id, 'docker.services.application.image') }}" +CHESS_VERSION: "{{ applications | get_app_conf(application_id, 'docker.services.application.version') }}" +CHESS_CONTAINER: "{{ applications | get_app_conf(application_id, 'docker.services.application.name') }}" +CHESS_DATA_VOLUME: "{{ applications | get_app_conf(application_id, 'docker.volumes.data') }}" + +# Build source +CHESS_REPO_URL: "{{ applications | get_app_conf(application_id, 'source.repo') }}" +CHESS_REPO_REF: "{{ applications | get_app_conf(application_id, 'source.ref') }}" + +# Security +CHESS_HMAC_SECRET: "{{ lookup('password', '/dev/null length=63 chars=ascii_letters,digits') }}" +CHESS_KEY_FILENAME: "signing-key"