diff --git a/.env.example b/.env.example index fe17131..3a86749 100644 --- a/.env.example +++ b/.env.example @@ -1,110 +1,196 @@ -COMPOSE_PROJECT_NAME= -COMPOSE_SERVER_DOMAIN= -COMPOSE_FILES= +# ============================================================================= +# Example .env file for os2display-docker-server +# Cantains environment variables that needs local configuration. +# Run task env:build to create .env and and prompt you for values that must be changed. +# You can also copy this file to .env and manually adjust the values as needed for your setup. +# Lines starting with # are comments. +# ============================================================================= + + +# Domain name where the server will be accessible +COMPOSE_SERVER_DOMAIN= + +# Version of the OS2display Docker images (applies to all services) +# Find all version here: https://github.com/itk-dev/os2display-api-service/pkgs/container/os2display-api-service +COMPOSE_IMAGE_VERSION= + +# Which infrastructure services to include is controlled by the `COMPOSE_FILES` variable in `.env`. +# It is a comma-separated list of Docker Compose files to load. +COMPOSE_FILES=docker-compose.yml,docker-compose.mariadb.yml,docker-compose.traefik.yml + +# Docker Compose project name (used for container prefixes) +COMPOSE_PROJECT_NAME=os2display -COMPOSE_IMAGE_VERSION=latest ### # PHP ### +# Maximum time in seconds a PHP script is allowed to run before being terminated PHP_MAX_EXECUTION_TIME=30 +# Maximum amount of memory a PHP script can allocate PHP_MEMORY_LIMIT=128M +# Maximum size of POST data that PHP will accept PHP_POST_MAX_SIZE=140M +# Maximum size of an uploaded file PHP_UPLOAD_MAX_FILESIZE=128M ### # APPLICATION -#### +### +# Application environment (prod, dev, test) APP_ENV=prod +# Secret key used for encryption and security-related operations (MUST BE CHANGED) APP_SECRET= +# Comma-separated list of trusted proxy IPs for handling X-Forwarded headers APP_TRUSTED_PROXIES=127.0.0.1,REMOTE_ADDR -APP_DATABASE_URL= +# Database connection string for MariaDB/MySQL +APP_DATABASE_URL=mysql://os2display:@mariadb:3306/os2display?serverVersion=mariadb-10.5.13 +# Regular expression defining allowed origins for CORS requests APP_CORS_ALLOW_ORIGIN='^https?://(localhost|127\.0\.0\.1)(:[0-9]+)?$' +# Default date format for API responses (ISO 8601 with milliseconds) APP_DEFAULT_DATE_FORMAT='Y-m-d\TH:i:s.v\Z' +# Source for key vault configuration (ENVIRONMENT, FILE, etc.) APP_KEY_VAULT_SOURCE=ENVIRONMENT +# JSON configuration for key vault when using ENVIRONMENT source APP_KEY_VAULT_JSON="{}" +# Time interval for activation code expiration (ISO 8601 duration format, P2D = 2 days) APP_ACTIVATION_CODE_EXPIRE_INTERVAL=P2D +# Enable tracking of screen information and statistics APP_TRACK_SCREEN_INFO=false +# Interval in seconds between screen info updates APP_TRACK_SCREEN_INFO_UPDATE_INTERVAL_SECONDS=300 # JWT +# Path to JWT private key file for signing tokens APP_JWT_SECRET_KEY=%kernel.project_dir%/config/jwt/private.pem +# Path to JWT public key file for verifying tokens APP_JWT_PUBLIC_KEY=%kernel.project_dir%/config/jwt/public.pem +# Passphrase for JWT private key (MUST BE CHANGED) APP_JWT_PASSPHRASE= +# Time-to-live in seconds for JWT access tokens (3600 = 1 hour) APP_JWT_TOKEN_TTL=3600 +# Time-to-live in seconds for screen JWT tokens (1296000 = 15 days) APP_JWT_SCREEN_TOKEN_TTL=1296000 +# Time-to-live in seconds for JWT refresh tokens (2592000 = 30 days) APP_JWT_REFRESH_TOKEN_TTL=2592000 +# Time-to-live in seconds for screen JWT refresh tokens (2592000 = 30 days) APP_JWT_SCREEN_REFRESH_TOKEN_TTL=2592000 # Internal OIDC provider +# URL to OIDC provider's metadata/discovery endpoint APP_INTERNAL_OIDC_METADATA_URL= +# Client ID registered with the OIDC provider APP_INTERNAL_OIDC_CLIENT_ID= +# Client secret for authentication with the OIDC provider APP_INTERNAL_OIDC_CLIENT_SECRET= +# Redirect URI for OIDC callback after authentication APP_INTERNAL_OIDC_REDIRECT_URI= +# Leeway time in seconds for token validation clock skew tolerance APP_INTERNAL_OIDC_LEEWAY=30 +# Name of the claim containing user's name APP_INTERNAL_OIDC_CLAIM_NAME=navn +# Name of the claim containing user's email APP_INTERNAL_OIDC_CLAIM_EMAIL=email +# Name of the claim containing user's group memberships APP_INTERNAL_OIDC_CLAIM_GROUPS=groups # External OIDC provider +# URL to external OIDC provider's metadata/discovery endpoint APP_EXTERNAL_OIDC_METADATA_URL= +# Client ID registered with the external OIDC provider APP_EXTERNAL_OIDC_CLIENT_ID= +# Client secret for authentication with the external OIDC provider APP_EXTERNAL_OIDC_CLIENT_SECRET= +# Redirect URI for external OIDC callback after authentication APP_EXTERNAL_OIDC_REDIRECT_URI= +# Leeway time in seconds for external token validation clock skew tolerance APP_EXTERNAL_OIDC_LEEWAY=30 +# Salt used for hashing external OIDC user identifiers APP_EXTERNAL_OIDC_HASH_SALT= +# Name of the claim containing user's sign-in identifier APP_EXTERNAL_OIDC_CLAIM_ID=signinname +# Redirect URI for CLI-based OIDC authentication flow APP_OIDC_CLI_REDIRECT= - +# Prefix for Redis cache keys to avoid collisions APP_REDIS_CACHE_PREFIX=display +# Connection string for Redis cache server APP_REDIS_CACHE_DSN=redis://redis:6379/0 +# API endpoint for calendar location data APP_CALENDAR_API_FEED_SOURCE_LOCATION_ENDPOINT= +# API endpoint for calendar resource data APP_CALENDAR_API_FEED_SOURCE_RESOURCE_ENDPOINT= +# API endpoint for calendar event data APP_CALENDAR_API_FEED_SOURCE_EVENT_ENDPOINT= +# JSON object for custom field mappings in calendar feeds APP_CALENDAR_API_FEED_SOURCE_CUSTOM_MAPPINGS='{}' +# JSON object for modifying calendar event data APP_CALENDAR_API_FEED_SOURCE_EVENT_MODIFIERS='{}' +# Date format used by the calendar API source APP_CALENDAR_API_FEED_SOURCE_DATE_FORMAT= +# Timezone used by the calendar API source APP_CALENDAR_API_FEED_SOURCE_DATE_TIMEZONE= +# Cache expiration time in seconds for calendar feed data APP_CALENDAR_API_FEED_SOURCE_CACHE_EXPIRE_SECONDS=300 +# Cache expiration time in seconds for event database API v2 responses APP_EVENTDATABASE_API_V2_CACHE_EXPIRE_SECONDS=300 ### -# Admin configuration +# ADMIN CONFIGURATION ### +# API key for Rejseplanen (Danish journey planner) integration APP_ADMIN_REJSEPLANEN_APIKEY= +# Enable display of screen online/offline status in admin interface APP_ADMIN_SHOW_SCREEN_STATUS=false +# Enable touch button regions feature in admin interface APP_ADMIN_TOUCH_BUTTON_REGIONS=false +# JSON array defining available login methods for admin interface APP_ADMIN_LOGIN_METHODS='[{"type":"username-password","enabled":true,"provider":"username-password","label":""}]' +# Enable enhanced preview mode with additional features APP_ADMIN_ENHANCED_PREVIEW=false ### -# Client configuration +# CLIENT CONFIGURATION ### +# Timeout in milliseconds for checking login status APP_CLIENT_LOGIN_CHECK_TIMEOUT=20000 +# Timeout in milliseconds before attempting to refresh authentication token (300000 = 5 minutes) APP_CLIENT_REFRESH_TOKEN_TIMEOUT=300000 +# Interval in milliseconds for checking release timestamp updates (600000 = 10 minutes) APP_CLIENT_RELEASE_TIMESTAMP_INTERVAL_TIMEOUT=600000 +# Interval in milliseconds for running the content scheduling checks (60000 = 1 minute) APP_CLIENT_SCHEDULING_INTERVAL=60000 +# Interval in milliseconds for pulling new content from server (90000 = 1.5 minutes) APP_CLIENT_PULL_STRATEGY_INTERVAL=90000 +# JSON configuration for client color scheme with location coordinates APP_CLIENT_COLOR_SCHEME='{"type":"library","lat":56.0,"lng":10.0}' +# Enable debug mode for client-side logging and diagnostics APP_CLIENT_DEBUG=false - +# Maximum upload size allowed by Nginx for PHP-FPM requests NGINX_FPM_UPLOAD_MAX=140M # Ensure corret IP's gets send to logs, should be the docker network (e.g. 172.16.0.0/16) NGINX_SET_REAL_IP_FROM='0.0.0.0' - -# Note: When updating the database connection details, ensure that the following MariaDB variables -# are also updated to match the connection string above. These variables are used by the Docker -# Compose setup to configure the built-in MariaDB service. -MARIADB_USER=db -MARIADB_PASSWORD=db -MARIADB_ROOT_PASSWORD=dbrootpassword -MARIADB_DATABASE=db +### +# DATABASE CONFIGURATION +### +# These variables are used by the Docker Compose setup to configure the built-in MariaDB service. +# Note: When updating the database connection details, ensure that the MariaDB variables +# are also updated to match the connection string above. + +# MariaDB username for the application database +MARIADB_USER=os2display +# Password for the MariaDB application user (MUST BE CHANGED) +MARIADB_PASSWORD= +# Password for the MariaDB root user (MUST BE CHANGED) +MARIADB_ROOT_PASSWORD= +# Name of the MariaDB database for the application +MARIADB_DATABASE=os2display +# Hostname of the MariaDB database server (Docker service name) DB_HOST=mariadb diff --git a/README.md b/README.md index 443c2f5..44f6e58 100644 --- a/README.md +++ b/README.md @@ -36,105 +36,35 @@ sudo usermod -aG docker deploy ## HTTPS Requirement -This project can only run in secure mode using HTTPS (port 443). You must provide a valid domain name and an SSL certificate. +This project can only run in secure mode using HTTPS (port 443). -1. Use a fully qualified domain name (FQDN) that resolves to your server's IP address. -2. Place the certificate file (`docker.crt`) and private key file (`docker.key`) in the `traefik/ssl` directory. -3. Set the domain name in `.env` via the `COMPOSE_SERVER_DOMAIN` variable. +You can configure an external reverse proxy til handle SSL termination or you can go with the default +installation options, that sets up Traefik as an integrated reverse proxy. -## Configuration - -Before running `task install`, generate the configuration file using one of these methods: - -**Option A** — Interactive prompt (recommended): - -```bash -task _env:build -``` - -This reads `.env.example`, prompts for each placeholder value, and writes `.env`. - -**Option B** — Manual copy and edit: - -```bash -cp .env.example .env -``` - -Edit `.env` with your local settings. The key variables are described below. - -### Domain, Name and Version +If you go with internal Traefik service, you must provide the SSL certificate file (`docker.crt`) and private file (`docker.key`) in the `traefik/ssl` directory. -| Variable | Description | Default | -|----------|-------------|---------| -| `COMPOSE_PROJECT_NAME` | Docker Compose project name (used for container prefixes) | `os2display` | -| `COMPOSE_SERVER_DOMAIN` | Domain name where the server will be accessible | `os2display.local.itkdev.dk` | -| `COMPOSE_IMAGE_VERSION` | Version of the os2display Docker images (applies to all services) | `latest` | - -### Infrastructure Options - -Which infrastructure services to include is controlled by the `COMPOSE_FILES` variable in `.env`. It is a comma-separated list of Docker Compose files to load. - -| Value | Purpose | -|-------|---------| -| `docker-compose.yml` | **Required.** Core services (os2display API, nginx, redis) | -| `docker-compose.mariadb.yml` | Built-in MariaDB database. Omit if using an external database | -| `docker-compose.traefik.yml` | Built-in Traefik reverse proxy. Omit if using an external proxy | - -Default: `docker-compose.yml,docker-compose.mariadb.yml` - -### Database - -| Variable | Description | Default | -|----------|-------------|---------| -| `APP_DATABASE_URL` | Doctrine database connection URL | `mysql://db:db@mariadb:3306/db?serverVersion=mariadb-10.5.13` | -| `MARIADB_USER` | MariaDB user (only when using built-in MariaDB) | `db` | -| `MARIADB_PASSWORD` | MariaDB password (only when using built-in MariaDB) | `db` | -| `MARIADB_ROOT_PASSWORD` | MariaDB root password (only when using built-in MariaDB) | `dbrootpassword` | -| `MARIADB_DATABASE` | MariaDB database name (only when using built-in MariaDB) | `db` | - -### Secrets - -| Variable | Description | Default | -|----------|-------------|---------| -| `APP_SECRET` | Symfony application secret | `CHANGE_ME` | -| `APP_JWT_PASSPHRASE` | JWT key pair passphrase | `CHANGE_ME` | -| `APP_ADMIN_LOGIN_METHODS` | JSON array configuring admin login methods (required) | `[{"type":"username-password","enabled":true,...}]` | - -**NOTE:** Change both `APP_SECRET` and `APP_JWT_PASSPHRASE` to secure values before running in production. - -### OIDC (OpenID Connect) - -**Internal provider**: +## Configuration +You need to create a `.env` with your local settings. The installation package provides an example configuration file (`.env.example`) for you to copy. -| Variable | Description | -|----------|-------------| -| `APP_INTERNAL_OIDC_METADATA_URL` | OIDC metadata URL provided by the IdP | -| `APP_INTERNAL_OIDC_CLIENT_ID` | OIDC client ID | -| `APP_INTERNAL_OIDC_CLIENT_SECRET` | OIDC client secret | -| `APP_INTERNAL_OIDC_REDIRECT_URI` | OIDC redirect URI | +If you run `task install` with no `.env` present, the installer will interactively prompt you for the most important settings and create `.env` for you. -**External provider**: +See [.env.example](.env.example) for descriptions of all configurable variables. -| Variable | Description | -|----------|-------------| -| `APP_EXTERNAL_OIDC_METADATA_URL` | OIDC metadata URL provided by the IdP | -| `APP_EXTERNAL_OIDC_CLIENT_ID` | OIDC client ID | -| `APP_EXTERNAL_OIDC_CLIENT_SECRET` | OIDC client secret | -| `APP_EXTERNAL_OIDC_REDIRECT_URI` | OIDC redirect URI | ## Installation -1. Generate or edit `.env` with your chosen settings. -2. Place your SSL certificate files (`docker.crt` and `docker.key`) in the `traefik/ssl` directory. -3. Run the install task: +You install by executing the install task: ```bash task install ``` -The install process will: +With default installation options the install process will: - Create the external `frontend` Docker network (if it doesn't exist) -- Pull Docker images +- Create and populate `.env` if it does not exists +- Check that the installer version matches the OS2Display version +- Check that the SSL certificate and key is present +- Pull Docker images (OS2display, MariaDB, Traefik) - Start all containers - Generate JWT key pair - Run `app:update` (database migrations and other setup tasks) @@ -150,19 +80,9 @@ After installation, the application is available at: For a full list of tasks, run: ```bash -task --list +task ``` -| Task | Description | -|------|-------------| -| `task install` | Install the project (pull images, start containers, generate JWT keys, add tenant/user) | -| `task purge` | Remove all containers. Use `-- --volumes` to also delete volumes, `-- --network` to remove the frontend network | -| `task db:backup` | Perform a database dump (only when using the built-in MariaDB). Saves to the `db_backups/` directory | -| `task compose -- ` | Run `docker compose` with the correct `-f` flags derived from `COMPOSE_FILES` in `.env` | -| `task console -- ` | Run a Symfony console command inside the os2display container | -| `task open:admin` | Open the admin interface in the default browser | -| `task open:client` | Open the client interface in the default browser | - ### Common compose commands via task ```bash diff --git a/Taskfile.yml b/Taskfile.yml index cebc5be..84f04b3 100644 --- a/Taskfile.yml +++ b/Taskfile.yml @@ -14,6 +14,9 @@ tasks: COMPOSE_FILES=$(grep ^COMPOSE_FILES= .env | cut -d '=' -f 2 | tr -d ' "') if [ -z "$COMPOSE_FILES" ]; then echo "Error: COMPOSE_FILES is not set in .env" + echo "" + echo "Check that the configuration file (.env) exists and is properly configured. Consult the README for more information." + echo "" exit 1 fi FILE_ARGS="" @@ -22,6 +25,8 @@ tasks: FILE_ARGS="$FILE_ARGS -f $FILE" done unset IFS + + echo "Running: docker compose$FILE_ARGS {{.CLI_ARGS}}" docker compose $FILE_ARGS {{.CLI_ARGS}} console: @@ -30,17 +35,31 @@ tasks: - task compose -- exec os2display bin/console {{.CLI_ARGS}} install: - desc: Install the project + desc: Install OS2Display. cmds: - - echo "Installing" + - echo "Welcome! Thank you for choosing OS2Display." + - echo "" - | # Check if the external network 'frontend' exists, create if not + echo "Checking external network 'frontend'" if ! docker network ls --format '{{.Name}}' | grep -wq frontend; then echo "Creating external network 'frontend'" docker network create frontend else echo "External network 'frontend' already exists" fi + - | + # Create .env if it doesn't exist + echo "Checking for configuration file (.env)" + if [ ! -f .env ]; then + echo "Configuration file not found, creating .env from .env.example by prompting for placeholder values" + task env:build + else + echo "Configuration file (.env) already exists" + fi + - task: check:version + - task: check:certificates + - echo "Installing version $(grep ^COMPOSE_IMAGE_VERSION= .env | cut -d '=' -f 2) of OS2Display" - task compose -- pull - task compose -- up --detach --remove-orphans --wait - echo "Create jwt key pair" @@ -48,13 +67,80 @@ tasks: - task console -- app:update - task console -- app:tenant:add - task console -- app:user:add + - sleep 5 + - task console -- cache:clear + - echo "Install complete!" + - | + DOMAIN=$(grep ^COMPOSE_SERVER_DOMAIN= .env | cut -d '=' -f 2) + echo "" + echo "====================================================" + echo "OS2display is now available via the URLs below" + echo "====================================================" + echo "Admin: https://$DOMAIN/admin" + echo "Screen: https://$DOMAIN/client" + echo "====================================================" + + uninstall: + desc: Uninstall OS2Display. Removes the database, all containers and volumes and the network. And the .env file. Use with caution! + cmds: + - | + echo "⚠️ WARNING: This will permanently remove:" + echo " - All Docker containers" + echo " - All Docker volumes (including the database)" + echo " - The 'frontend' Docker network" + echo " - The .env configuration file" + echo "" + echo "This action cannot be undone!" + echo "" + printf "Are you sure you want to continue? (yes/no): " + read CONFIRM + if [ "$CONFIRM" != "yes" ]; then + echo "Uninstall cancelled." + exit 1 + fi + - task purge -- --volumes --network + - | + if [ -f .env ]; then + echo "Removing .env file" + rm .env + else + echo ".env file does not exist" + fi + + upgrade: + desc: Upgrade OS2Display - stop containers, pull new images, start containers, and run app update + cmds: + - task: check:version + - echo "Stopping and removing containers..." + - task compose -- down + - echo "Upgrading to version $(grep ^COMPOSE_IMAGE_VERSION= .env | cut -d '=' -f 2)..." + - echo "Pulling new image versions..." + - task compose -- pull + - echo "Starting containers..." + - task compose -- up -d + - echo "Running application update..." + - task console -- app:update + - echo "Clearing cache..." - task console -- cache:clear + - echo "Upgrade complete!" purge: desc: Remove all containers (use -- --volumes to also delete volumes, -- --network to remove the frontend network) cmds: - - task compose -- down --remove-orphans {{.CLI_ARGS}} - | + # Filter out --network flag before passing to docker compose + COMPOSE_ARGS="" + REMOVE_NETWORK=false + for arg in {{.CLI_ARGS}}; do + if [ "$arg" = "--network" ]; then + REMOVE_NETWORK=true + else + COMPOSE_ARGS="$COMPOSE_ARGS $arg" + fi + done + task compose -- down --remove-orphans $COMPOSE_ARGS + - | + # Check if --network flag was provided if echo "{{.CLI_ARGS}}" | grep -q -- '--network'; then if docker network ls --format '{{.Name}}' | grep -wq frontend; then echo "Removing external network 'frontend'" @@ -110,10 +196,10 @@ tasks: echo "Opening $URL" xdg-open "$URL" 2>/dev/null || open "$URL" 2>/dev/null || echo "Could not open browser. Visit: $URL" - _env:build: - internal: true + env:build: desc: Build .env from .env.example by prompting for placeholder values cmds: + - | INPUT=".env.example" OUTPUT=".env" @@ -152,3 +238,61 @@ tasks: echo "" echo "$OUTPUT has been created." + + check:version: + desc: Check if installer version matches OS2Display version + internal: true + cmds: + - | + # Check if installer version matches OS2Display version + if [ -f VERSION ]; then + INSTALLER_VERSION=$(cat VERSION) + IMAGE_VERSION=$(grep ^COMPOSE_IMAGE_VERSION= .env | cut -d '=' -f 2 | tr -d ' "') + + # Extract major.minor version (e.g., 3.0 from 3.0.0-beta1) + INSTALLER_MAIN=$(echo "$INSTALLER_VERSION" | cut -d '.' -f 1-2) + IMAGE_MAIN=$(echo "$IMAGE_VERSION" | cut -d '.' -f 1-2) + + if [ "$INSTALLER_MAIN" != "$IMAGE_MAIN" ]; then + echo "" + echo "⚠️ WARNING: Version mismatch detected!" + echo " Installer version: $INSTALLER_VERSION" + echo " OS2Display version: $IMAGE_VERSION" + echo "" + echo " The .env.example file may not be compatible with the OS2Display" + echo " version you are installing. We encourage you to download the" + echo " installer version that matches your OS2Display version." + echo "" + printf "Do you want to continue anyway? (yes/no): " + read CONFIRM + if [ "$CONFIRM" != "yes" ]; then + echo "Operation cancelled." + exit 1 + fi + fi + fi + + + + check:certificates: + desc: Check if Traefik SSL certificates exist when Traefik is enabled + internal: true + cmds: + - | + # Check if Traefik is enabled and SSL certificates exist + COMPOSE_FILES=$(grep ^COMPOSE_FILES= .env | cut -d '=' -f 2 | tr -d ' "') + if echo "$COMPOSE_FILES" | grep -q "docker-compose.traefik.yml"; then + echo "You have choosen an internal Traefik setup. Checking for SSL certificates..." + if [ ! -f traefik/ssl/docker.crt ] || [ ! -f traefik/ssl/docker.key ]; then + echo "" + echo "⚠️ ERROR: Traefik SSL certificates not found!" + echo " The COMPOSE_FILES variable includes docker-compose.traefik.yml," + echo " but the required SSL certificate files are missing:" + echo " - traefik/ssl/docker.crt" + echo " - traefik/ssl/docker.key" + echo "" + echo "Please create these files and then re-run install." + echo "" + exit 1 + fi + fi \ No newline at end of file diff --git a/VERSION b/VERSION new file mode 100644 index 0000000..daf2dc9 --- /dev/null +++ b/VERSION @@ -0,0 +1 @@ +3.0.0-beta1 \ No newline at end of file diff --git a/docker-compose.traefik.yml b/docker-compose.traefik.yml index 6ffc7ec..45e546e 100644 --- a/docker-compose.traefik.yml +++ b/docker-compose.traefik.yml @@ -28,7 +28,7 @@ services: - $PWD/traefik/dynamic-conf.yaml:/config/dynamic-conf.yaml:ro socket-proxy: - image: itkdev/docker-socket-proxy + image: ghcr.io/tecnativa/docker-socket-proxy:latest user: root restart: unless-stopped networks: diff --git a/traefik/traefik.yml b/traefik/traefik.yml index 46d3370..8e3e6df 100644 --- a/traefik/traefik.yml +++ b/traefik/traefik.yml @@ -4,6 +4,9 @@ api: insecure: true debug: true +ping: + entryPoint: "web" + entryPoints: web: address: ":80"