diff --git a/.env b/.env index beb9d16..98b66e0 100644 --- a/.env +++ b/.env @@ -27,6 +27,10 @@ CRON_PASSWORD= ###> doctrine/doctrine-bundle ### # Siehe https://www.doctrine-project.org/projects/doctrine-dbal/en/latest/reference/configuration.html#connecting-using-a-url DATABASE_URL="mysql://db_user:db_password@localhost:3306/db_name" +MYSQL_ROOT_PASSWORD=changeThisToASecurePassword +MYSQL_DATABASE=db_name +MYSQL_USER=db_user +MYSQL_PASSWORD=db_password ###< doctrine/doctrine-bundle ### ###> symfony/messenger ### @@ -37,4 +41,7 @@ MESSENGER_TRANSPORT_DSN=doctrine://default MAILER_DSN=native://default ###< symfony/mailer ### -PHP_BINARY=/usr/bin/php \ No newline at end of file +PHP_BINARY=/usr/bin/php + +### Docker ### +TZ=Europe/Berlin diff --git a/.github/workflows/docker-image.yml b/.github/workflows/docker-image.yml new file mode 100644 index 0000000..57632a4 --- /dev/null +++ b/.github/workflows/docker-image.yml @@ -0,0 +1,73 @@ +name: Docker Image CI + +on: + workflow_dispatch: + release: + types: [ published ] + +env: + # Use docker.io for Docker Hub if empty + REGISTRY: ghcr.io + # github.repository as / + IMAGE_NAME: ${{ github.repository }} + +jobs: + build: + runs-on: ubuntu-latest + permissions: + contents: read + packages: write + # This is used to complete the identity challenge + # with sigstore/fulcio when running outside of PRs. + id-token: write + + steps: + - name: Checkout repository + uses: actions/checkout@v3 + + - name: Log in to the Container registry + uses: docker/login-action@65b78e6e13532edd9afa3aa52ac7964289d1a9c1 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + # Set image name to lower case because ghcr.io complains about uppercase + - name: downcase image name + run: | + echo "IMAGE_NAME_LOWER=${IMAGE_NAME@L}" >> "${GITHUB_ENV}" + + - name: Set env + run: echo "RELEASE_VERSION=${GITHUB_REF#refs/*/}" >> $GITHUB_ENV + + - name: Extract Docker metadata + id: meta + uses: docker/metadata-action@96383f45573cb7f253c731d3b3ab81c87ef81934 # v5.0.0 + with: + images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME_LOWER }} + + # Build and push Docker image with Buildx (don't push on PR) + # https://github.com/docker/build-push-action + - name: Build and push Docker image to latest + if: ${{ !github.event.release.prerelease && github.event_name != 'push' }} + id: build-and-push-latest + uses: docker/build-push-action@0565240e2d4ab88bba5387d719585280857ece09 # v5.0.0 + with: + context: . + push: ${{ github.event_name != 'pull_request' }} + tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME_LOWER }}:latest + labels: ${{ steps.meta.outputs.labels }} + build-args: version_info=${{ env.RELEASE_VERSION }} (${{ env.CURRENT_DATE }}) + + # Build and push Docker image with Buildx (don't push on PR) + # https://github.com/docker/build-push-action + - name: Build and push Docker image to ${{ env.RELEASE_VERSION }} and unstable + if: ${{ github.event_name != 'push' && github.event_name != 'pull_request' }} + id: build-and-push-tag + uses: docker/build-push-action@0565240e2d4ab88bba5387d719585280857ece09 # v5.0.0 + with: + context: . + push: ${{ github.event_name != 'pull_request' }} + tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME_LOWER }}:${{ env.RELEASE_VERSION }} , ${{ env.REGISTRY }}/${{ env.IMAGE_NAME_LOWER }}:unstable + labels: ${{ steps.meta.outputs.labels }} + build-args: version_info=${{ env.RELEASE_VERSION }} (${{ env.CURRENT_DATE }}) diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..bc8359e --- /dev/null +++ b/Dockerfile @@ -0,0 +1,122 @@ +# Use the official PHP image with FPM as the base image +FROM php:8.2-fpm AS base + +# Install dependencies and PHP extensions +RUN apt-get update && apt-get install -y \ + unzip \ + libxml2-dev \ + libssl-dev \ + libzip-dev \ + libpng-dev \ + libfreetype6-dev \ + libjpeg62-turbo-dev \ + libonig-dev \ + libxslt1-dev \ + libmcrypt-dev \ + libsodium-dev \ + nginx \ + openssl \ + && docker-php-ext-configure gd --with-freetype --with-jpeg \ + && docker-php-ext-install -j$(nproc) \ + ctype \ + dom \ + filter \ + iconv \ + intl \ + mbstring \ + pdo_mysql \ + phar \ + simplexml \ + sodium \ + xml \ + xmlwriter \ + zip \ + gd \ + xsl \ + && apt-get clean \ + && rm -rf /var/lib/apt/lists/* + +# Set memory limit for PHP +RUN echo "memory_limit=512M" > /usr/local/etc/php/conf.d/memory-limit.ini +ENV PHP_MEMORY_LIMIT=512M + +FROM base AS composer + +# Install Composer +COPY --from=composer:2 /usr/bin/composer /usr/bin/composer + +# Set COMPOSER_ALLOW_SUPERUSER environment variable +ENV COMPOSER_ALLOW_SUPERUSER=1 + +# Set working directory +WORKDIR /var/www/html + +# Copy the composer.json and composer.lock files into the container +COPY . . + +# Install PHP dependencies including symfony/runtime +RUN composer install --no-dev --classmap-authoritative --no-scripts + +FROM base AS node + +# Set working directory +WORKDIR /var/www/html + +COPY --from=composer /var/www/html/vendor /var/www/html/vendor + +# Copy the package.json and package-lock.json files into the container +COPY . . + +# Install Node.js dependencies +RUN apt-get update && apt-get install -y \ + curl \ + && apt-get clean \ + && rm -rf /var/lib/apt/lists/* + +# Install Node.js and npm +RUN curl -fsSL https://deb.nodesource.com/setup_18.x | bash - \ + && apt-get install -y nodejs \ + && npm install -g npm@latest + +# Install Node.js dependencies and build the assets +RUN npm install \ + && npm run build \ + && php bin/console assets:install + +FROM base AS runner + +WORKDIR /var/www/html + +# Copy necessary files into the container +COPY . . + +# Remove unnecessary files +RUN rm -rf ./docs +RUN rm -rf ./.github +RUN rm -rf ./docker-compose.yml +RUN rm -rf ./Dockerfile +RUN rm -rf ./.gitignore + +# Copy build files from the previous stages +COPY --from=node /var/www/html/public /var/www/html/public +COPY --from=composer /var/www/html/vendor /var/www/html/vendor + +# Output of assets? --> Needs to be copied to the final image - maybe separate stage + +# Remove the .htaccess file because we are using Nginx +RUN rm -rf ./public/.htaccess + +# Copy the Nginx configuration file into the container +COPY nginx.conf /etc/nginx/sites-enabled/default + +# Copy the startup script into the container +COPY startup.sh /usr/local/bin/startup.sh + +# Ensure the startup script is executable +RUN chmod +x /usr/local/bin/startup.sh + +# Expose port 80 +EXPOSE 80 + +# Use the startup script as the entrypoint +ENTRYPOINT ["/usr/local/bin/startup.sh"] diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..c047c8f --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,34 @@ +services: + web: + build: . + restart: always + ports: + - "8080:80" + depends_on: + db: + condition: service_healthy + env_file: + - .env.local + volumes: + - certs:/var/www/html/certs + # nginx configuration file + # - ./nginx.conf:/etc/nginx/sites-enabled/default + + db: + image: mariadb:10.4 + restart: always + env_file: + - .env.local + volumes: + - db_data:/var/lib/mysql + healthcheck: + test: mysqladmin ping -h 127.0.0.1 -u $$MYSQL_USER --password=$$MYSQL_PASSWORD + interval: 5s + timeout: 20s + retries: 10 + +volumes: + db_data: + certs: + + diff --git a/docs/docs/install/docker_installation.md b/docs/docs/install/docker_installation.md new file mode 100644 index 0000000..e772490 --- /dev/null +++ b/docs/docs/install/docker_installation.md @@ -0,0 +1,310 @@ +--- +sidebar_position: 6 +--- + +# Docker Installation + +Der IdP der SchulIT Software kann alternativ auch über Docker installiert werden. Diese Variante erfordert Wissen über `Docker` und `Docker Compose`, ist aber in der Regel weniger fehleranfällig und das berühmte Phänomen "Auf meiner Maschine läuft's aber" tritt nicht auf. + +## Voraussetzungen + +* Ein Server mit Docker und Docker Compose +* Terminal Zugriff (z.B. via SSH) auf diesen Server +* Server hat eine aktive Internetverbindung + +## Installation mit Docker Image aus Docker Hub (empfohlen) + +Stelle eine Verbindung mit dem Terminal des Servers her. + +### Dateistruktur + +Erstelle ein Verzeichnis in dem die Daten des Dienstes gespeichert werden sollen. Hier: `/home/docker/schulit/idp` + +```bash +mkdir /home/docker/schulit/idp +``` + +:::tip Backups +Bitte sichere dieses Verzeichnis in regelmäßigen Abständen für Backups der Datenbank und des Dienstes +::: + +Als nächstes erstellen wir zwei Ordner, wo die DB und der IdP ihre Daten ablegen: + +```bash +cd /home/docker/schulit/idp +mkdir ./certs +mkdir ./db_data +mkdir ./own_assets +``` + +### Docker Compose + +Erstelle nun die Docker Compose Datei. Diese Datei beschreibt die Konfiguration eines Dienstes in Docker. + +```bash +nano docker-compose.yml +``` + +Kopiere den Inhalt aus unserer Vorlage und passe ihn nach deinen belieben an. + +```yml title=docker-compose.yml +version: '3.8' + +services: + web: + image: simonfrank/schulit-idp:latest # use a fixed image tag like v1.0 in prod environments + restart: always + ports: + # change the first port to any port you like + - "8080:80" + depends_on: + db: + condition: service_healthy + env_file: + - .env + volumes: + - /home/docker/schulit/idp/certs:/var/www/html/certs + # if you want to modify the apperance of this app mount this folder + # - /home/docker/schulit/idp/own_assets:/var/www/html/public/own_assets + # nginx configuration file - uncomment below if you want to use an own nginx config + + # - ./nginx.conf:/etc/nginx/sites-enabled/default + + + + + db: + image: mariadb:10.4 + restart: always + env_file: + - .env + volumes: + - /home/docker/schulit/idp/db_data:/var/lib/mysql + healthcheck: + test: mysqladmin ping -h 127.0.0.1 -u $$MYSQL_USER --password=$$MYSQL_PASSWORD + interval: 5s + timeout: 20s + retries: 10 +``` + +Drücke zum Speicher `strg` + `o` und anschließend zum Verlassen des Text Editors `strg` + `x`. + +### Umgebungsvariablen anlegen + +Als nächstes legen wir die Umgebungsvariablen für den IdP an. + +Erstelle dazu eine neue Datei `.env` + +```bash +nano .env +``` + +Diese Datei sollte die folgenden Variablen aus dem Template benutzen. Hier sollten einige Anpassungen vorgenommen werden. Mehr Infos zu den Anpassungen findest du auf der Seite [Konfigurationsdatei](./configuration). + +```text title=.env +###> symfony/framework-bundle ### +APP_ENV=prod +APP_SECRET=ChangeThisToASecretString +###< symfony/framework-bundle ### + +###> schulit/adauth-bundle ### +ADAUTH_ENABLED=false +ADAUTH_URL="tls://dc01.ad.schulit.de:55117" +ADAUTH_PEERNAME="dc01.ad.schulit.de" +ADAUTH_PEERFINGERPRINT="" +###< schulit/adauth-bundle ### + +###> schulit/common-bundle ### +APP_URL="https://sso.schulit.de/" +APP_NAME="SchulIT Single Sign-On" +# Pfade relativ zum public/-Verzeichnis +APP_LOGO="" # Müssen in der Compose gemountet werden und Dateien dann in "own_asstes" abgelegt werden +APP_SMALLLOGO="" # Müssen in der Compose gemountet werden und Dateien dann in "own_asstes" abgelegt werden +###< schulit/common-bundle + +###> CUSTOM ### +SAML_ENTITY_ID="https://sso.schulit.de/" +MAILER_FROM="noreply@sso.schulit.de" +CRON_PASSWORD= +###< CUSTOM ### + +###> doctrine/doctrine-bundle ### +# Siehe https://www.doctrine-project.org/projects/doctrine-dbal/en/latest/reference/configuration.html#connecting-using-a-url +DATABASE_URL="mysql://db_user:db_password@localhost:3306/db_name" +MYSQL_ROOT_PASSWORD=changeThisToASecurePassword +MYSQL_DATABASE=db_name +MYSQL_USER=db_user +MYSQL_PASSWORD=db_password +###< doctrine/doctrine-bundle ### + +###> symfony/messenger ### +MESSENGER_TRANSPORT_DSN=doctrine://default +###< symfony/messenger ### + +###> symfony/mailer ### +MAILER_DSN=native://default +###< symfony/mailer ### + +PHP_BINARY=/usr/bin/php + +### Docker ### +TZ=Europe/Berlin +``` + +Drücke zum Speicher `strg` + `o` und anschließend zum Verlassen des Text Editors `strg` + `x`. + +### Webserver konfigurieren (optional) + +Falls du die Config vom nginx Webserver anpassen möchtest, musst du das zweite Volume in der Compose einkommentieren. Anschließend kannst du die Config anpassen. + +Die Default Config, die wir im Container verwenden ist die folgende: + +```text title=nginx.conf +server { + listen 80; + server_name localhost; + + root /var/www/html/public; + + location / { + # try to serve file directly, fallback to index.php + try_files $uri /index.php$is_args$args; + } + + # optionally disable falling back to PHP script for the asset directories; + # nginx will return a 404 error when files are not found instead of passing the + # request to Symfony (improves performance but Symfony's 404 page is not displayed) + # location /bundles { + # try_files $uri =404; + # } + + location ~ ^/index\.php(/|$) { + fastcgi_pass 127.0.0.1:9000; + fastcgi_split_path_info ^(.+\.php)(/.*)$; + include fastcgi_params; + + # When you are using symlinks to link the document root to the + # current version of your application, you should pass the real + # application path instead of the path to the symlink to PHP + # FPM. + # Otherwise, PHP's OPcache may not properly detect changes to + # your PHP files (see https://github.com/zendtech/ZendOptimizerPlus/issues/126 + # for more information). + # Caveat: When PHP-FPM is hosted on a different machine from nginx + # $realpath_root may not resolve as you expect! In this case try using + # $document_root instead. + fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name; + fastcgi_param DOCUMENT_ROOT $realpath_root; + + proxy_set_header Host $http_host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + # Prevents URIs that include the front controller. This will 404: + # http://domain.tld/index.php/some-path + # Remove the internal directive to allow URIs like this + # internal; + } + + # return 404 for all other php files not matching the front controller + # this prevents access to other php files you don't want to be accessible. + location ~ \.php$ { + return 404; + } + + # Optional logging + error_log /var/log/nginx/error.log; + access_log /var/log/nginx/access.log; +} +``` + +### Dienst starten + +Als nächstes können wir den Dienst starten: + +```bash +docker compose up +``` + +Der Container startet jetzt. Dies kann 1-2 Minuten dauern. + +### IdP Konfigurieren + +Als letzten Schritt müssen wir jetzt noch einen Admin User anlegen. Das geht über die Konsole des Dienstes im Docker Container. + +```bash +docker exec -it sh +``` + +`` ist dabei entweder der Name oder die ID des Containers. Um diese herauszufinden führe folgenden Befehl aus und kopiere die ID oder den Namen + +```bash +$ docker ps +CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES +af38ee24c451 idp-web "docker-php-entrypoi…" 2 hours ago Up 3 minutes 9000/tcp, 0.0.0.0:8080->80/tcp idp-web-1 +``` + +In diesem Fall wäre der Name `idp-web-1` und die ID `af38ee24c451`. + +Wir würden also +```bash +docker exec -it af38ee24c451 sh +``` +ausführen. + +Wir legen jetzt einen Admin User an: + +:::caution Achtung +Der Benutzer muss als Administrator (Schritt 7) angelegt werden. +::: + +```bash +$ php bin/console app:add-user + Benutzername: + > admin@example.com + + Vorname: + > Erika + + Nachname: + > Mustermann + + E-Mail: + > admin@example.com + + Passwort: + > + + Passwort wiederholen: + > + + Ist der Benutzer ein Administrator? (yes/no) [yes]: + > yes + + Benutzertyp wählen [user]: + [0] user + > user + + [OK] Benutzer erfolgreich erstellt +``` + +Das war's! Logge dich unter [http://server_ip:8080](http://server_ip:8080) in den IdP ein. + +## Installation über Repository +Falls Anpassungen am Quellcode vorgenommen wurden, ist auch eine Installation über das Repository möglich. + +In dem Fall liegt eine leicht modifizierte docker-compose Datei im Repo. Diese setzt die Verfügbarkeit einer lokalen `.env` Datei voraus. Diese kann mit folgendem Befehl angelegt und modifiziert werden: + +```bash +cp .env .env.local +``` + +Wenn alle Einstellungen vorgenommen wurden, kann der Build des Containers und das Deployment über den Befehl + +```bash +docker compose up --build +``` + +gestartet werden. + +Anschließend muss auch ein Admin User angelegt werden (s.o.) \ No newline at end of file diff --git a/nginx.conf b/nginx.conf new file mode 100644 index 0000000..60ea4e6 --- /dev/null +++ b/nginx.conf @@ -0,0 +1,56 @@ +server { + listen 80; + server_name localhost; + + root /var/www/html/public; + + location / { + # try to serve file directly, fallback to index.php + try_files $uri /index.php$is_args$args; + } + + # optionally disable falling back to PHP script for the asset directories; + # nginx will return a 404 error when files are not found instead of passing the + # request to Symfony (improves performance but Symfony's 404 page is not displayed) + # location /bundles { + # try_files $uri =404; + # } + + location ~ ^/index\.php(/|$) { + fastcgi_pass 127.0.0.1:9000; + fastcgi_split_path_info ^(.+\.php)(/.*)$; + include fastcgi_params; + + # When you are using symlinks to link the document root to the + # current version of your application, you should pass the real + # application path instead of the path to the symlink to PHP + # FPM. + # Otherwise, PHP's OPcache may not properly detect changes to + # your PHP files (see https://github.com/zendtech/ZendOptimizerPlus/issues/126 + # for more information). + # Caveat: When PHP-FPM is hosted on a different machine from nginx + # $realpath_root may not resolve as you expect! In this case try using + # $document_root instead. + fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name; + fastcgi_param DOCUMENT_ROOT $realpath_root; + + proxy_set_header Host $http_host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + # Prevents URIs that include the front controller. This will 404: + # http://domain.tld/index.php/some-path + # Remove the internal directive to allow URIs like this + # internal; + } + + # return 404 for all other php files not matching the front controller + # this prevents access to other php files you don't want to be accessible. + location ~ \.php$ { + return 404; + } + + # Optional logging + error_log /var/log/nginx/error.log; + access_log /var/log/nginx/access.log; +} diff --git a/startup.sh b/startup.sh new file mode 100644 index 0000000..6c4edfc --- /dev/null +++ b/startup.sh @@ -0,0 +1,37 @@ +#!/bin/sh + +CONTAINER_ALREADY_STARTED="IDP_CONTAINER_ALREADY_STARTED" +# Check if the container has already been started +if [ ! -e $CONTAINER_ALREADY_STARTED ]; then + touch $CONTAINER_ALREADY_STARTED + echo "-- First container startup --" + + # Check if the SAML certificate does not exist + if [ ! -f /var/www/html/certs/idp.crt ] || [ ! -f /var/www/html/certs/idp.key ]; then + echo "Creating SAML certificate..." + + # Create SAML certificate + php bin/console app:create-certificate --type saml --no-interaction + fi + + # Run database migrations + php bin/console doctrine:migrations:migrate --no-interaction + + # Perform initial setup + php bin/console app:setup + + # Register cron jobs + php bin/console shapecode:cron:scan + + # Update Browscap + php bin/console app:browscap:update +fi + +# Start PHP-FPM +php-fpm & + +# Ensure the var directory is writable +chown -R www-data:www-data /var/www/html/var + +# Start Nginx +nginx -g 'daemon off;'