feat(deployment): update Semaphore stack and Traefik configuration

- Add QUICKSTART.md and SETUP_REPOSITORY.md for Semaphore stack
- Add playbooks directory for Semaphore deployment
- Update Semaphore docker-compose.yml, env.example, and README
- Add Traefik local configuration files
- Disable semaphore.yml in Traefik dynamic config
- Update docker-compose.local.yml and build-image workflow
This commit is contained in:
2025-11-02 22:55:51 +01:00
parent 0c4ff1283c
commit 77c656af62
15 changed files with 1280 additions and 77 deletions

View File

@@ -0,0 +1,103 @@
---
# CI Tests Playbook f?r Semaphore
# F?hrt PHP Tests und Quality Checks aus
- name: Run CI Tests and Quality Checks
hosts: localhost
gather_facts: no
vars:
repo_url: "{{ repo_url | default('https://git.michaelschiemer.de/michael/michaelschiemer.git') }}"
repo_branch: "{{ repo_branch | default('main') }}"
build_dir: "/tmp/ci-build"
tasks:
- name: Clean up build directory
file:
path: "{{ build_dir }}"
state: absent
- name: Checkout repository
git:
repo: "{{ repo_url }}"
dest: "{{ build_dir }}"
version: "{{ repo_branch }}"
force: yes
register: git_result
- name: Display checked out commit
debug:
msg: "Checked out commit: {{ git_result.after }}"
- name: Install Composer if not present
get_url:
url: https://getcomposer.org/installer
dest: /tmp/composer-installer.php
mode: '0755'
when: ansible_facts.os_family == "Debian"
- name: Install Composer (Debian)
shell: php /tmp/composer-installer.php && mv composer.phar /usr/local/bin/composer
when: ansible_facts.os_family == "Debian"
args:
creates: /usr/local/bin/composer
- name: Install PHP dependencies
command: composer install --no-interaction --prefer-dist --optimize-autoloader --ignore-platform-req=php
args:
chdir: "{{ build_dir }}"
register: composer_result
- name: Display composer output
debug:
var: composer_result.stdout_lines
when: composer_result.stdout_lines is defined
- name: Run PHP tests
command: ./vendor/bin/pest
args:
chdir: "{{ build_dir }}"
register: test_result
ignore_errors: yes
- name: Display test results
debug:
msg: "{{ test_result.stdout_lines }}"
when: test_result.stdout_lines is defined
- name: Run PHPStan
command: composer phpstan
args:
chdir: "{{ build_dir }}"
register: phpstan_result
ignore_errors: yes
when: test_result.rc == 0
- name: Display PHPStan results
debug:
msg: "{{ phpstan_result.stdout_lines }}"
when: phpstan_result.stdout_lines is defined and phpstan_result.rc == 0
- name: Run code style check
command: composer cs
args:
chdir: "{{ build_dir }}"
register: cs_result
ignore_errors: yes
when: test_result.rc == 0
- name: Display code style results
debug:
msg: "{{ cs_result.stdout_lines }}"
when: cs_result.stdout_lines is defined
- name: Fail if tests failed
fail:
msg: "Tests failed! Check output above."
when: test_result.rc != 0
- name: Summary
debug:
msg:
- "? CI Tests completed successfully!"
- "Commit: {{ git_result.after }}"
- "Branch: {{ repo_branch }}"

View File

@@ -0,0 +1,129 @@
---
# Production Deployment Playbook f?r Semaphore
# Deployed die Anwendung auf Production-Server
- name: Deploy to Production
hosts: production
gather_facts: yes
become: yes
vars:
registry_url: "{{ registry_url | default('registry.michaelschiemer.de') }}"
image_name: "{{ image_name | default('framework') }}"
image_tag: "{{ image_tag | default('latest') }}"
registry_user: "{{ registry_user | default('admin') }}"
registry_password: "{{ registry_password | required }}"
deployment_path: "{{ deployment_path | default('~/deployment/stacks/application') }}"
repo_url: "{{ repo_url | default('https://git.michaelschiemer.de/michael/michaelschiemer.git') }}"
tasks:
- name: Display deployment info
debug:
msg:
- "?? Starting production deployment..."
- "Host: {{ inventory_hostname }}"
- "Registry: {{ registry_url }}"
- "Image: {{ image_name }}:{{ image_tag }}"
- "Path: {{ deployment_path }}"
- name: Ensure deployment directory exists
file:
path: "{{ deployment_path }}"
state: directory
mode: '0755'
- name: Login to Docker registry
docker_login:
username: "{{ registry_user }}"
password: "{{ registry_password }}"
registry_url: "{{ registry_url }}"
- name: Pull Docker image
docker_image:
name: "{{ registry_url }}/{{ image_name }}:{{ image_tag }}"
source: pull
- name: Check if docker-compose files exist
stat:
path: "{{ deployment_path }}/docker-compose.base.yml"
register: base_compose_exists
- name: Check if docker-compose.production.yml exists
stat:
path: "{{ deployment_path }}/docker-compose.production.yml"
register: production_compose_exists
- name: Copy docker-compose files if missing
copy:
src: "{{ item.src }}"
dest: "{{ deployment_path }}/{{ item.dest }}"
mode: '0644'
loop:
- { src: "docker-compose.base.yml", dest: "docker-compose.base.yml" }
- { src: "docker-compose.production.yml", dest: "docker-compose.production.yml" }
when: not (item.dest == "docker-compose.base.yml" and base_compose_exists.stat.exists) or
not (item.dest == "docker-compose.production.yml" and production_compose_exists.stat.exists)
delegate_to: localhost
become: no
- name: Update docker-compose.production.yml with new image
replace:
path: "{{ deployment_path }}/docker-compose.production.yml"
regexp: 'image:.*{{ image_name }}:.*'
replace: 'image: {{ registry_url }}/{{ image_name }}:{{ image_tag }}'
- name: Update docker-compose.production.yml with new image (alternative format)
replace:
path: "{{ deployment_path }}/docker-compose.production.yml"
regexp: 'image:.*{{ image_name }}@.*'
replace: 'image: {{ registry_url }}/{{ image_name }}:{{ image_tag }}'
- name: Ensure Docker networks exist
docker_network:
name: traefik-public
state: present
ignore_errors: yes
- name: Deploy services
docker_compose_v2:
project_src: "{{ deployment_path }}"
files:
- "{{ deployment_path }}/docker-compose.base.yml"
- "{{ deployment_path }}/docker-compose.production.yml"
pull: missing
state: present
recreate: always
- name: Wait for services to start
pause:
seconds: 15
- name: Check container status
command: docker compose -f {{ deployment_path }}/docker-compose.base.yml -f {{ deployment_path }}/docker-compose.production.yml ps
register: container_status
- name: Display container status
debug:
var: container_status.stdout_lines
- name: Health check
uri:
url: https://michaelschiemer.de/health
method: GET
status_code: [200, 201, 204]
validate_certs: no
register: health_result
retries: 10
delay: 10
until: health_result.status == 200
ignore_errors: yes
- name: Display health check result
debug:
msg: "? Health check passed!" if health_result.status == 200 else "?? Health check failed"
- name: Summary
debug:
msg:
- "? Production deployment completed!"
- "Image: {{ registry_url }}/{{ image_name }}:{{ image_tag }}"
- "URL: https://michaelschiemer.de"

View File

@@ -0,0 +1,110 @@
---
# Staging Deployment Playbook f?r Semaphore
# Deployed die Anwendung auf Staging-Server
- name: Deploy to Staging
hosts: staging
gather_facts: yes
become: yes
vars:
registry_url: "{{ registry_url | default('registry.michaelschiemer.de') }}"
image_name: "{{ image_name | default('framework') }}"
image_tag: "{{ image_tag | default('latest') }}"
registry_user: "{{ registry_user | default('admin') }}"
registry_password: "{{ registry_password | required }}"
deployment_path: "{{ deployment_path | default('~/deployment/stacks/staging') }}"
repo_url: "{{ repo_url | default('https://git.michaelschiemer.de/michael/michaelschiemer.git') }}"
tasks:
- name: Display deployment info
debug:
msg:
- "?? Starting staging deployment..."
- "Host: {{ inventory_hostname }}"
- "Registry: {{ registry_url }}"
- "Image: {{ image_name }}:{{ image_tag }}"
- "Path: {{ deployment_path }}"
- name: Ensure deployment directory exists
file:
path: "{{ deployment_path }}"
state: directory
mode: '0755'
- name: Login to Docker registry
docker_login:
username: "{{ registry_user }}"
password: "{{ registry_password }}"
registry_url: "{{ registry_url }}"
- name: Pull Docker image
docker_image:
name: "{{ registry_url }}/{{ image_name }}:{{ image_tag }}"
source: pull
- name: Check if docker-compose files exist
stat:
path: "{{ deployment_path }}/docker-compose.base.yml"
register: base_compose_exists
- name: Check if docker-compose.staging.yml exists
stat:
path: "{{ deployment_path }}/docker-compose.staging.yml"
register: staging_compose_exists
- name: Copy docker-compose files if missing
copy:
src: "{{ item.src }}"
dest: "{{ deployment_path }}/{{ item.dest }}"
mode: '0644'
loop:
- { src: "docker-compose.base.yml", dest: "docker-compose.base.yml" }
- { src: "docker-compose.staging.yml", dest: "docker-compose.staging.yml" }
when: not (item.dest == "docker-compose.base.yml" and base_compose_exists.stat.exists) or
not (item.dest == "docker-compose.staging.yml" and staging_compose_exists.stat.exists)
delegate_to: localhost
become: no
- name: Update docker-compose.staging.yml with new image
replace:
path: "{{ deployment_path }}/docker-compose.staging.yml"
regexp: 'image:.*{{ image_name }}:.*'
replace: 'image: {{ registry_url }}/{{ image_name }}:{{ image_tag }}'
- name: Ensure Docker networks exist
docker_network:
name: "{{ item }}"
state: present
loop:
- traefik-public
- staging-internal
ignore_errors: yes
- name: Deploy services
docker_compose_v2:
project_src: "{{ deployment_path }}"
files:
- "{{ deployment_path }}/docker-compose.base.yml"
- "{{ deployment_path }}/docker-compose.staging.yml"
pull: missing
state: present
recreate: always
- name: Wait for services to start
pause:
seconds: 15
- name: Check container status
command: docker compose -f {{ deployment_path }}/docker-compose.base.yml -f {{ deployment_path }}/docker-compose.staging.yml ps
register: container_status
- name: Display container status
debug:
var: container_status.stdout_lines
- name: Summary
debug:
msg:
- "? Staging deployment completed!"
- "Image: {{ registry_url }}/{{ image_name }}:{{ image_tag }}"
- "URL: https://staging.michaelschiemer.de"

View File

@@ -0,0 +1,103 @@
---
# Docker Build Playbook f?r Semaphore
# Baut Docker Image und pusht es zur Registry
- name: Build and Push Docker Image
hosts: localhost
gather_facts: no
vars:
repo_url: "{{ repo_url | default('https://git.michaelschiemer.de/michael/michaelschiemer.git') }}"
repo_branch: "{{ repo_branch | default('main') }}"
build_dir: "/tmp/ci-build"
registry_url: "{{ registry_url | default('registry.michaelschiemer.de') }}"
image_name: "{{ image_name | default('framework') }}"
image_tag: "{{ image_tag | default('latest') }}"
registry_user: "{{ registry_user | default('admin') }}"
registry_password: "{{ registry_password | required }}"
tasks:
- name: Clean up build directory
file:
path: "{{ build_dir }}"
state: absent
- name: Checkout repository
git:
repo: "{{ repo_url }}"
dest: "{{ build_dir }}"
version: "{{ repo_branch }}"
force: yes
register: git_result
- name: Get short commit SHA
shell: echo "{{ git_result.after }}" | cut -c1-7
register: short_sha
- name: Generate image tags
set_fact:
tags:
- "{{ registry_url }}/{{ image_name }}:latest"
- "{{ registry_url }}/{{ image_name }}:{{ image_tag }}"
- "{{ registry_url }}/{{ image_name }}:git-{{ short_sha.stdout }}"
- name: Display image tags
debug:
msg:
- "??? Building Docker image..."
- "Registry: {{ registry_url }}"
- "Image: {{ image_name }}"
- "Tags: {{ tags | join(', ') }}"
- name: Ensure Docker is available
command: docker --version
register: docker_version
- name: Display Docker version
debug:
msg: "Docker version: {{ docker_version.stdout }}"
- name: Login to Docker registry
docker_login:
username: "{{ registry_user }}"
password: "{{ registry_password }}"
registry_url: "{{ registry_url }}"
register: login_result
- name: Verify registry login
debug:
msg: "? Successfully logged in to {{ registry_url }}"
when: login_result.failed == false
- name: Build Docker image
docker_image:
name: "{{ registry_url }}/{{ image_name }}"
tag: "{{ image_tag }}"
source: build
build:
path: "{{ build_dir }}"
dockerfile: Dockerfile.production
push: yes
state: present
register: build_result
- name: Tag image with additional tags
docker_image:
name: "{{ registry_url }}/{{ image_name }}:{{ image_tag }}"
repository: "{{ registry_url }}/{{ image_name }}"
tag: "{{ item }}"
source: local
push: yes
state: present
loop:
- "latest"
- "git-{{ short_sha.stdout }}"
when: build_result.changed
- name: Summary
debug:
msg:
- "? Docker image built and pushed successfully!"
- "Registry: {{ registry_url }}"
- "Image: {{ image_name }}"
- "Tags: {{ tags | join(', ') }}"
- "Commit: {{ git_result.after }}"