Skip to content

MSAlpaka/SilentOakRanch

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

SilentOakRanch

Production Ready – v1.0.0

Project overview & tech stack

  • Symfony 7.3 backend running on PHP 8.3 FPM.
  • React with Redux Toolkit on Node.js 20, built via Vite, Vitest, ESLint, and the i18n tooling.
  • Docker Compose orchestrates the PHP backend, PostgreSQL 15, the static frontend, and the Traefik-based ingress/ACME stack used in production.
  • Live status & monitoring: https://status.silentoakranch.de

Dependencies

Composer packages are pinned to stable versions for reproducible builds. Notable constraints include endroid/qr-code-bundle (^6.0) and stripe/stripe-php (^14.0).

Setup

After cloning, align your local tooling with the containers and CI pipeline (PHP 8.3 and Node.js 20) before installing dependencies:

cd backend
composer install --ignore-platform-req=ext-sodium
cd ../frontend
npm ci
npm run build

If you want Composer to run inside the PHP 8.3 container instead of installing PHP locally, execute:

docker compose run --rm backend composer install --ignore-platform-req=ext-sodium

Environment configuration

Create a project-wide .env file from the template before starting Docker Compose or running Symfony commands:

cp .env.example .env

Populate every mandatory entry from .env.example:

  • POSTGRES_USER, POSTGRES_PASSWORD, POSTGRES_DB, DATABASE_URL – configure the PostgreSQL 15 service that the backend reaches via the internal host db:5432.
  • APP_ENV, APP_SECRET – choose the Symfony environment (dev for local, prod for deployments) and generate a 64-character secret, e.g. php -r 'echo bin2hex(random_bytes(32));'.
  • DOMAIN, BOOKING_DOMAIN, LETSENCRYPT_EMAIL, TRUSTED_PROXIES, TRUSTED_HOSTS – define the public hostnames and proxy settings consumed by Traefik and the application containers.
  • STATUS_DASH_AUTH_USERS, OAUTH2_PROXY_CLIENT_ID, OAUTH2_PROXY_CLIENT_SECRET, OAUTH2_PROXY_COOKIE_SECRET, OAUTH2_PROXY_OIDC_ISSUER_URL – protect the monitoring dashboards (basic auth for Uptime Kuma, OAuth2 Proxy in front of Grafana).
  • ALERTMANAGER_SMTP_*, ALERTMANAGER_EMAIL_TO, ALERTMANAGER_TELEGRAM_* – configure alert delivery targets.
  • STORAGE_BOX_REMOTE – rclone remote name for Hetzner Storage Box uploads used by scripts/backup.sh.
  • STRIPE_SECRET_KEY, VITE_STRIPE_PUBLISHABLE_KEY – supply the backend secret key and frontend publishable key used during Stripe Checkout. The publishable key must be available when the frontend builds; export it before running npm run build or provide it via .env so docker compose build frontend receives the same value. Otherwise Vite renders the Stripe button in a disabled state. Mirror the value in the VITE_STRIPE_PUBLISHABLE_KEY GitHub secret so the CI and deployment workflows inject the key as a Docker build argument.
  • JWT_SECRET_KEY, JWT_PUBLIC_KEY, JWT_PASSPHRASE – point to the LexikJWT key pair and provide the matching passphrase (generate the keys with docker compose run --rm backend php bin/console lexik:jwt:generate-keypair --overwrite).
  • AGREEMENT_SIGNATURE_CERTIFICATE_PATH, AGREEMENT_SIGNATURE_PRIVATE_KEY_PATH, AGREEMENT_SIGNATURE_PRIVATE_KEY_PASSPHRASE – configure the X.509 certificate and encrypted private key used to sign digitally generated agreements. Keep the files outside the Git repository (e.g. ./shared/agreements/signing/). Docker Compose mounts that directory into the backend container at /var/www/backend/config/agreements/, so dropping the certificate and key there satisfies the paths.
  • SIGNATURE_API_URL, SIGNATURE_TTL_DAYS – configure the optional external PKCS7 validation endpoint and signature validity window for the contract verification API.
  • MESSENGER_TRANSPORT_DSN, WHATSAPP_DSN, SMS_DSN – configure the messenger transports for asynchronous processing and notifications.
  • SMTP_HOST, SMTP_PORT, SMTP_USERNAME, SMTP_PASSWORD – configure outbound mail delivery.

Create ./shared/backend/var on the host (the repository provides the directory and .gitkeep files) so the bind-mounted var/ path used by the backend remains writable between deployments. Agreements and invoices generated by the Symfony services are stored there alongside their signature metadata.

Create ./shared/audit and keep it writable by the backend container; the append-only WORM replica for audit events is written there via the audit-log volume.

Optional development helpers such as VAR_DUMPER_SERVER can stay commented out or be set as needed. Docker Compose loads .env via env_file so every container receives the same credentials.

Backend setup

cd backend && composer install --ignore-platform-req=ext-sodium

Frontend setup

cd frontend && npm ci

Frontend notes

  • React Router is configured with future flags (v7_startTransition and v7_relativeSplatPath) preparing the project for React Router v7.
  • The frontend package.json uses "type": "module" so tooling like PostCSS runs in ES module mode.

Dev commands

composer install
vendor/bin/phpunit
npm run lint
npm test
npm run build

Repository helper CLI

Use the bundled repo helper to discover the major components that make up Silent Oak Ranch. The command reads codex.json and renders a short table so you can quickly jump to the relevant directory:

./repo list --all

Passing --all expands the table with supporting directories such as docs/, monitoring/, proxy/, scripts/, and the shared bind mounts that power the Docker services.

Testing

The backend uses PHPUnit 12. To run the test suite without deprecations or skipped tests:

cd backend
./bin/phpunit --testdox

Tests that depend on external services should be tagged with @group external and are excluded from the core suite by default.

Static Analysis

Static analysis is handled by PHPStan. Run it from the backend directory so the bundled configuration is picked up automatically:

cd backend
vendor/bin/phpstan analyse

The default memory limit is --memory-limit=512M. Override it locally if needed:

cd backend
vendor/bin/phpstan analyse --memory-limit=1G
  • PHPStan-Level 8 prüft den MeController auf korrekte Typen; Fehler durch falsche Signatur wurden korrigiert.

Doctrine Schema Fix

The Horse ↔ StallUnit relationship now stores a nullable stall_unit_id foreign key on the horse table. Horses reference stall units through a ManyToOne association, and each stall unit provides the inverse side with OneToMany or OneToOne mapping depending on configuration. Removing a stall unit automatically sets horse.stall_unit_id to NULL instead of deleting the horse. After pulling these changes, regenerate and apply migrations:

php bin/console doctrine:migrations:diff
php bin/console doctrine:migrations:migrate

Once migrations are applied, validate the ORM mapping and run the test suite to check for regressions:

php bin/console doctrine:schema:validate
./bin/phpunit --testdox

Security

All npm vulnerabilities have been resolved. Keep dependencies current and check for new issues regularly:

# PHP dependencies
composer update
composer audit

# JavaScript dependencies
npm update
npm audit
npm audit fix

Workaround note for ext-sodium

Some environments lack the ext-sodium PHP extension. Use composer install --ignore-platform-req=ext-sodium to bypass the requirement during installation.

Pferdewaage

The Pferdewaage service guides horse owners from reservation to result:

  1. Booking - reserve a timeslot online.
  2. Confirmation - receive a confirmation containing a QR code.
  3. Payment - complete payment to secure the slot.
  4. Weighing - on site, scan the QR code for check-in and automatic weight capture.

Flow: booking → confirmation → payment → weighing.

Release & tagging

git remote add origin <REMOTE_URL> git push -u origin main git tag v1.0.0 git push origin v1.0.0

Deployment

GitHub Actions automates the Docker workflows:

  • .github/workflows/ci.yml builds the PHP 8.3 backend image, runs PHPStan and PHPUnit inside it, and lints/tests the frontend with Node.js 20.
  • .github/workflows/deploy.yml uses Docker Buildx to build the backend and frontend images, exports them as artifacts, and synchronises them to the target host before starting the stack and applying Doctrine migrations during deployment.

ℹ️ Für einen Gesamtüberblick über das hybride WordPress/Symfony-Setup inklusive Compose-Services, Plugin-Konfiguration und HMAC-Fluss siehe docs/hybrid-setup.md.

ℹ️ Details zur Audit- und Signatur-Compliance inklusive WORM-Speicherstrategie finden sich in docs/audit-compliance.md.

Deployment Steps

To deploy with Docker Compose manually:

  1. Prepare .env as described in Environment configuration. For production ensure PostgreSQL credentials remain pointed at the internal db service, replace the secret/API placeholders (Stripe, SMTP, messenger DSNs, domain settings), and generate the LexikJWT key pair inside the backend container:

    docker compose run --rm backend php bin/console lexik:jwt:generate-keypair --overwrite

    The keys are written to /var/www/backend/config/jwt/private.pem and /var/www/backend/config/jwt/public.pem; keep the corresponding paths and JWT_PASSPHRASE in sync inside .env.

  2. Start the stack (image builds complete without a database connection because migrations now run at deploy time):

    docker compose up -d --build
  3. Apply database migrations once the containers are online:

    docker compose exec backend php bin/console doctrine:migrations:migrate --no-interaction
  4. Verify that Symfony can connect to PostgreSQL from inside the backend container:

    docker compose run --rm backend php bin/console doctrine:query:sql 'SELECT 1'

    The command executes a lightweight SQL query via Doctrine and confirms that the configured credentials work end to end.

  • All Composer and NPM dependencies are installed automatically during the image build.
  • The frontend is served as an Nginx static server.

TLS automation

The nginx-proxy and acme-companion services share the same certificate, vhost, and webroot volumes. The companion container requests and renews Let's Encrypt certificates automatically. To confirm the proxy is managing certificates correctly, check the logs after bringing the stack online:

docker compose logs proxy

Systemd integration and health monitoring

Production hosts typically wrap docker compose up -d in a dedicated systemd unit with Restart=always so systemctl restart silentoakranch.service maps to a full container restart (docker compose restart). Even without systemd you can refresh a single service with docker restart <container_name> during maintenance windows. Pair restart automation with routine health checks, for example:

docker compose ps --format 'table {{.Name}}\t{{.State}}\t{{.Health}}'
docker inspect --format '{{json .State.Health}}' silentoakranch-backend-1

The first command highlights failing health checks at a glance, while the second prints the raw probe history for detailed troubleshooting.

Successful output shows the generated certificates under /etc/nginx/certs and that renewal jobs run without errors.

Production Operation Checklist

  • CI status – confirm the latest runs of ci.yml and deploy.yml in GitHub Actions are green before promoting a build.
  • SSL renewal – review the acme-companion logs weekly to ensure Let's Encrypt renewals finish without rate-limit warnings.
  • Restart policy – verify systemd units and Docker Compose services use restart: always/unless-stopped to survive host reboots.
  • Backup routine – schedule nightly PostgreSQL dumps (e.g. pg_dump to ./shared/backups/) and copy them off-site alongside uploaded agreements.

PostgreSQL data persistence

PostgreSQL stores its data in the named Docker volume db-data declared in docker-compose.yml. The former ./db/data bind mount is no longer used; remove any legacy db/ directory after switching to the volume so stale files do not confuse local setups. Named volumes are managed by Docker and survive docker compose down, meaning database contents persist across redeployments. Only prune them intentionally—for example with docker volume prune or docker volume rm db-data—after creating a backup (e.g. via pg_dump) to avoid losing production data.

Generated documents & signing assets

  • ./shared/backend/var/var/www/backend/var: bind-mounted storage that keeps the PDF agreements and invoices produced by AgreementService and InvoiceService. Subdirectories (agreements/ per-user folders and invoices/) are committed with .gitkeep files so the structure exists before the container starts. Ensure the host path is writable by the container user (typically www-data) or relax permissions accordingly.
  • ./shared/agreements/signing/var/www/backend/config/agreements: stores the X.509 certificate and private key referenced by AGREEMENT_SIGNATURE_CERTIFICATE_PATH and AGREEMENT_SIGNATURE_PRIVATE_KEY_PATH. Keep the real files out of Git and copy them into this directory prior to deploying.

Existing environments that previously stored agreements and invoices inside the container should back up /var/www/backend/var before upgrading. After pulling the update, stop the stack, copy the files into ./shared/backend/var on the host, and redeploy. The new bind-mount ensures the historical documents remain accessible across future container rebuilds.

The included nginx-proxy and acme-companion automatically request and renew TLS certificates via Let's Encrypt. Ensure the companion sees the proxy by exporting NGINX_PROXY_CONTAINER=proxy (as done in docker-compose.yml) or by giving the proxy container that name before starting the stack; otherwise certificate discovery fails. The proxy routes requests based on the path:

JWT key management

The backend expects the LexikJWT keys in config/jwt. To keep them outside of the Docker image the directory is bind-mounted from the host (./shared/jwt/backend/var/www/backend/config/jwt). The mount point is ignored by Git so generated keys never end up in the repository.

Local development

  • Ensure that .env contains a JWT_PASSPHRASE. The default value is only meant for local testing – change it before exposing the stack.
  • Start the containers with docker compose up -d --build. The runtime entrypoint checks for config/jwt/private.pem and config/jwt/public.pem. If either file is missing, php bin/console lexik:jwt:generate-keypair --overwrite --no-interaction is executed automatically and the freshly generated pair is stored on the mounted volume.
  • To rotate keys manually run docker compose run --rm backend php bin/console lexik:jwt:generate-keypair --overwrite --no-interaction and restart the backend container.

Production

  • Keep the shared/jwt/backend mount (or a similar host path) persistent so that redeployments reuse the existing key pair. Adjust ownership/permissions on the host to restrict access to the files.

  • Alternatively, load the keys via container secrets. For example:

    docker secret create backend_jwt_private config/jwt/private.pem
    docker secret create backend_jwt_public config/jwt/public.pem

    Mount the secrets into the container and point JWT_SECRET_KEY/JWT_PUBLIC_KEY to the secret paths before starting the stack.

  • Whenever the passphrase or keys change, restart the backend service so Symfony reloads the credentials.

Deployment-Skript

CI builds artifacts for each successful workflow run. After verifying the build in CI, deploy the artifact manually:

scripts/deploy.sh <build-id> <target-dir>

Use a dry run to test the commands without making changes:

bash scripts/deploy.sh --dry-run 123 /tmp/deploy

In dry-run mode the script logs each command instead of executing it. It also creates dummy artifact and target directories, making it safe for testing. Real deployments run the commands and apply the downloaded artifacts to the target directory. Full automation with Docker or Kubernetes may be added later.

Rechnungen

Der Rechnungsprozess umfasst folgende Schritte:

  1. Payment – Nach erfolgreicher Zahlung wird eine Rechnung erzeugt.
  2. PDF-Erstellung – Das System erstellt eine gebrandete PDF-Datei mit ausgewiesener Mehrwertsteuer.
  3. E-Mail-Versand – Die Rechnung wird an die hinterlegte Adresse versendet.
  4. Portal-Anzeige – Im Kundenportal steht die Rechnung zusätzlich zum Download bereit.

Jede Rechnung muss konsistentes Branding tragen und die gesetzliche Umsatzsteuer klar anzeigen.

Reko-Dokumentation

Die Reko-Dokumentation unterteilt sich in drei Pakete:

  • BASIS – einfache Erstellung und Verwaltung von Reko-Einträgen.
  • STANDARD – alle BASIS-Funktionen plus Kategorisierung und Filter.
  • PREMIUM – kompletter Funktionsumfang inklusive Export.

Einträge anlegen

  1. Im Bereich Reko-Dokumentation „Neuer Eintrag“ wählen.
  2. Pflichtfelder wie Datum, Kategorie und Beschreibung ausfüllen.
  3. Speichern.

Export (nur PREMIUM)

Premium-Nutzende können ihre Reko-Einträge als CSV oder Excel exportieren.

Terminverwaltung

Der Terminprozess begleitet Nutzer*innen von der Anfrage bis zur optionalen Rechnungsstellung:

Anfrage -> Bestätigung -> Erinnerungen -> Durchführung -> (Rechnung)

Erinnerungen werden zwingend per E-Mail versendet. Optional können zusätzliche Hinweise per WhatsApp oder SMS erfolgen.

Termin-Erinnerungen

Bestätigungs-, Erinnerungs- und Absage-E-Mails werden mit deutschen und englischen Texten versendet.

Verträge

Der Vertragsbereich unterstützt die komplette Verwaltung von Vertragsdokumenten:

  1. Anlage – Neue Verträge werden im System erfasst.
  2. Verwaltung – Bestehende Verträge lassen sich prüfen, bearbeiten und archivieren.
  3. Signatur – Optional können Verträge digital signiert werden.

About

No description, website, or topics provided.

Resources

License

Contributing

Security policy

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published