chore: lots of changes
This commit is contained in:
8
.dockerignore
Normal file
8
.dockerignore
Normal file
@@ -0,0 +1,8 @@
|
||||
.git
|
||||
vendor
|
||||
storage/logs/*
|
||||
storage/app/*
|
||||
storage/framework/cache/*
|
||||
storage/framework/sessions/*
|
||||
storage/framework/views/*
|
||||
*.log
|
||||
15
.editorconfig
Normal file
15
.editorconfig
Normal file
@@ -0,0 +1,15 @@
|
||||
root = true
|
||||
|
||||
[*]
|
||||
charset = utf-8
|
||||
end_of_line = lf
|
||||
indent_style = space
|
||||
indent_size = 4
|
||||
trim_trailing_whitespace = true
|
||||
insert_final_newline = true
|
||||
|
||||
[*.md]
|
||||
trim_trailing_whitespace = false
|
||||
|
||||
[*.{yml,yaml,json}]
|
||||
indent_size = 2
|
||||
18
.gitignore
vendored
18
.gitignore
vendored
@@ -1,7 +1,23 @@
|
||||
# Editor / IDE
|
||||
.idea/
|
||||
|
||||
# System
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
|
||||
# Build / Runtime
|
||||
vendor/
|
||||
node_modules/
|
||||
.env
|
||||
*.log
|
||||
*.retry
|
||||
ansible/.vault_pass
|
||||
*.Zone.Identifier
|
||||
|
||||
# Backup Dateien
|
||||
*~
|
||||
|
||||
|
||||
.php-cs-fixer.php
|
||||
|
||||
|
||||
#ssl/*.pem
|
||||
|
||||
111
Makefile
111
Makefile
@@ -1,16 +1,107 @@
|
||||
.PHONY: deploy setup stop build restart
|
||||
# ----------------------------------
|
||||
# Projekt: michaelschiemer.de
|
||||
# Docker & Ansible Makefile
|
||||
# ----------------------------------
|
||||
|
||||
setup:
|
||||
ansible-playbook -i ansible/inventory.ini ansible/setup.yml
|
||||
PROJECT_NAME = michaelschiemer
|
||||
ENV ?= dev
|
||||
|
||||
deploy:
|
||||
ansible-playbook -i ansible/inventory.ini ansible/deploy.yml
|
||||
# Standart Docker Compose Befehle
|
||||
|
||||
stop:
|
||||
docker compose down
|
||||
up: ## Startet alle Docker-Container
|
||||
./bin/up
|
||||
|
||||
down: ## Stoppt alle Container
|
||||
./bin/down
|
||||
|
||||
build:
|
||||
docker compose build --no-cache
|
||||
docker compose build
|
||||
|
||||
restart: stop build
|
||||
docker compose up -d
|
||||
restart: ## Neustart aller Container
|
||||
./bin/restart
|
||||
|
||||
logs: ## Zeigt Logs aus Docker
|
||||
docker compose logs -f
|
||||
|
||||
ps: ## Docker PS
|
||||
docker compose ps
|
||||
|
||||
reload: ## Dump Autoload & Restart PHP
|
||||
docker-compose exec php composer dump-autoload -o
|
||||
docker-compose restart php
|
||||
|
||||
|
||||
# Wähle dev- oder prod-PHP-Konfig je nach ENV
|
||||
phpinfo:
|
||||
@echo "Aktive PHP-Konfiguration: php.$(ENV).ini"
|
||||
|
||||
# Ansible Deployment
|
||||
|
||||
setup: ## Führt Ansible Setup aus
|
||||
./bin/setup
|
||||
|
||||
deploy: ## Führt Ansible Deploy aus
|
||||
./bin/deploy
|
||||
|
||||
test: ## Führt Tests aus (Platzhalter)
|
||||
./bin/test
|
||||
|
||||
# Cleanup temporärer/metadaten-Dateien
|
||||
clean: ## Entfernt temporäre Dateien
|
||||
find . -type f -name "*Zone.Identifier" -delete
|
||||
find . -type f -name "*.retry" -delete
|
||||
|
||||
# Projektstatus
|
||||
status: ## Zeigt Container-Status
|
||||
@echo "Aktuelles Projekt: $(PROJECT_NAME)"
|
||||
@echo "Umgebung: $(ENV)"
|
||||
|
||||
doctor: ## Prüft ob Komponenten installiert sind
|
||||
@echo "🔍 Prüfe Voraussetzungen..."
|
||||
@which docker > /dev/null || echo "❌ Docker fehlt"
|
||||
@which ansible-playbook > /dev/null || echo "❌ Ansible fehlt"
|
||||
@test -f .env || echo "⚠️ .env-Datei fehlt"
|
||||
|
||||
# Helfer: Automatische Zielübersicht
|
||||
help: ## Zeigt diese Hilfe an
|
||||
@echo ""
|
||||
@echo "🛠 Verfügbare Make-Befehle:"
|
||||
@grep -E '^[a-zA-Z_-]+:.*?## ' Makefile | sort | awk 'BEGIN {FS = ":.*?## "}; {printf " \033[36m%-16s\033[0m %s\n", $$1, $$2}'
|
||||
@echo ""
|
||||
|
||||
|
||||
composer: ## Use Composer
|
||||
docker compose exec php composer $(ARGS)
|
||||
|
||||
fix-perms: ## Fix permissions
|
||||
sudo chown -R $(USER):$(USER) .
|
||||
|
||||
cs:
|
||||
@$(MAKE) composer ARGS="cs"
|
||||
|
||||
cs-fix-file: ## Fix code style for a specific file
|
||||
docker compose exec -e PHP_CS_FIXER_IGNORE_ENV=1 php ./vendor/bin/php-cs-fixer fix $(subst \,/,$(FILE))
|
||||
|
||||
cs-fix: ## Fix code style for all PHP files
|
||||
docker compose exec -e PHP_CS_FIXER_IGNORE_ENV=1 php ./vendor/bin/php-cs-fixer fix
|
||||
|
||||
health:
|
||||
ansible-playbook ansible/check.yml
|
||||
|
||||
|
||||
# Konfiguration
|
||||
ANSIBLE_INVENTORY=ansible/inventory/hosts.ini
|
||||
PLAYBOOK_DIR=ansible/playbooks/deploy
|
||||
|
||||
.PHONY: dev staging production
|
||||
|
||||
dev:
|
||||
ansible-playbook -i $(ANSIBLE_INVENTORY) $(PLAYBOOK_DIR)/dev.yml #--ask-become-pass
|
||||
|
||||
staging:
|
||||
ansible-playbook -i $(ANSIBLE_INVENTORY) $(PLAYBOOK_DIR)/staging.yml
|
||||
|
||||
production:
|
||||
ansible-playbook -i $(ANSIBLE_INVENTORY) $(PLAYBOOK_DIR)/production.yml
|
||||
|
||||
.PHONY: up down build restart logs ps phpinfo deploy setup clean status
|
||||
|
||||
14
README.md
Normal file
14
README.md
Normal file
@@ -0,0 +1,14 @@
|
||||
## 🚀 Quick Start
|
||||
|
||||
```bash
|
||||
# Starten
|
||||
make up
|
||||
|
||||
# Logs anzeigen
|
||||
make logs
|
||||
|
||||
# Setup-Playbook (Server einmalig vorbereiten)
|
||||
make setup
|
||||
|
||||
# Deployment (Code + Compose auf Server bringen)
|
||||
make deploy
|
||||
26
ansible.cfg
Normal file
26
ansible.cfg
Normal file
@@ -0,0 +1,26 @@
|
||||
[defaults]
|
||||
#inventory = ./ansible/inventory.ini
|
||||
inventory = ./ansible/inventory/hosts.ini
|
||||
roles_path = ./ansible/roles
|
||||
remote_tmp = ~/.ansible/tmp
|
||||
forks = 5
|
||||
timeout = 10
|
||||
retry_files_enabled = False
|
||||
deprecation_warnings = False
|
||||
interpreter_python = auto_silent
|
||||
#stdout_callback = json
|
||||
host_key_checking = False
|
||||
command_warnings = False
|
||||
gathering = smart
|
||||
fact_caching = jsonfile
|
||||
fact_caching_connection = .ansible/cache
|
||||
fact_caching_timeout = 3600
|
||||
|
||||
[privilege_escalation]
|
||||
become = true
|
||||
become_method = sudo
|
||||
become_user = root
|
||||
|
||||
[ssh_connection]
|
||||
pipelining = true
|
||||
ssh_args = -o ControlMaster=auto -o ControlPersist=60s
|
||||
10
ansible/client-configs/michael.conf
Normal file
10
ansible/client-configs/michael.conf
Normal file
@@ -0,0 +1,10 @@
|
||||
[Interface]
|
||||
PrivateKey = +DcT11ipmMwPXpzEqmCPGwy7cSmseG1YzZWk+tTtM30=
|
||||
Address = 10.8.0.2/32
|
||||
DNS = 1.1.1.1
|
||||
|
||||
[Peer]
|
||||
PublicKey = 3qFEUREx6VfqrKoGVtzHt2ojgaly7LvwxjPQPNsFyxM=
|
||||
Endpoint = 94.16.110.151:51820
|
||||
AllowedIPs = 10.8.0.0/24, 94.16.110.151/32
|
||||
PersistentKeepalive = 25
|
||||
@@ -1,4 +0,0 @@
|
||||
- hosts: web
|
||||
become: false
|
||||
roles:
|
||||
- deploy
|
||||
39
ansible/group_vars/all.yml
Normal file
39
ansible/group_vars/all.yml
Normal file
@@ -0,0 +1,39 @@
|
||||
# Basis-Konfiguration
|
||||
app_name: michaelschiemer
|
||||
app_domain: test.michaelschiemer.de
|
||||
app_email: kontakt@michaelschiemer.de
|
||||
|
||||
# Verzeichnisse
|
||||
project_root: "{{ playbook_dir | dirname }}"
|
||||
app_root: /var/www/{{ app_name }}
|
||||
app_public: "{{ app_root }}/public"
|
||||
|
||||
# Docker
|
||||
docker_version: "20.10"
|
||||
docker_compose_version: "2.24.5"
|
||||
|
||||
# Benutzer
|
||||
deploy_user: deploy
|
||||
|
||||
# Let's Encrypt
|
||||
letsencrypt_enabled: true
|
||||
letsencrypt_certbot_method: webroot # oder standalone oder nginx
|
||||
|
||||
|
||||
#netcup_customer_id: "218722"
|
||||
#netcup_api_key: "dmJINUMyNjRmOG1aNDViajZHN2JkOTFRUjU3ckE5ZjJ1Zm1vUz"
|
||||
#netcup_api_password: "iGWL8Hl4m93DgESsP/MPXmtDd0hEVkZ3480Na0psTlXRALnopl"
|
||||
#netcup_vserver_id: "v2202309206672239295"
|
||||
|
||||
|
||||
# fallback_ip:
|
||||
|
||||
wg_all_clients_private_keys:
|
||||
michael: "PITbFZ3UfY5vD5dYUCELO37Qo2W8I4R8+r6D9CeMrm4="
|
||||
|
||||
|
||||
|
||||
wireguard_clients:
|
||||
- name: michael
|
||||
address: 10.8.0.2
|
||||
public_key: DEIN_PUBLIC_KEY
|
||||
4
ansible/group_vars/vpn.yml
Normal file
4
ansible/group_vars/vpn.yml
Normal file
@@ -0,0 +1,4 @@
|
||||
wg_privkey: "HIER_DEIN_PRIVATER_KEY_ODER_DATEIPFAD"
|
||||
|
||||
wg_all_clients_private_keys:
|
||||
michael: "PITbFZ3UfY5vD5dYUCELO37Qo2W8I4R8+r6D9CeMrm4="
|
||||
0
ansible/group_vars/web.yml
Normal file
0
ansible/group_vars/web.yml
Normal file
@@ -1,2 +1,8 @@
|
||||
#[web]
|
||||
#localhost ansible_connection=local
|
||||
|
||||
[web]
|
||||
localhost ansible_connection=local
|
||||
94.16.110.151 ansible_user=deploy ansible_ssh_private_key_file=/mnt/c/Users/Mike/.ssh/test.michaelschiemer.de
|
||||
|
||||
[vpn]
|
||||
94.16.110.151 ansible_user=deploy
|
||||
|
||||
12
ansible/inventory/hosts.ini
Normal file
12
ansible/inventory/hosts.ini
Normal file
@@ -0,0 +1,12 @@
|
||||
[localhost]
|
||||
127.0.0.1 ansible_connection=local
|
||||
|
||||
[staging]
|
||||
94.16.110.151 ansible_user=deploy ansible_ssh_private_key_file=/mnt/c/Users/Mike/.ssh/test.michaelschiemer.de
|
||||
|
||||
[production]
|
||||
|
||||
|
||||
[vpn]
|
||||
94.16.110.151 ansible_user=deploy
|
||||
|
||||
40
ansible/playbooks/check.yml
Normal file
40
ansible/playbooks/check.yml
Normal file
@@ -0,0 +1,40 @@
|
||||
#- name: Check ob /ping erreichbar ist
|
||||
# uri:
|
||||
# url: "http://localhost/ping"
|
||||
# status_code: 200
|
||||
# return_content: yes
|
||||
# register: ping_response
|
||||
#
|
||||
#- debug:
|
||||
# var: ping_response.content
|
||||
|
||||
- name: Healthcheck nach dem Deployment
|
||||
hosts: localhost
|
||||
connection: local
|
||||
gather_facts: false
|
||||
become: false
|
||||
|
||||
vars:
|
||||
healthcheck_url: "http://127.0.0.1:8080/ping"
|
||||
max_retries: 10
|
||||
delay_between_retries: 3
|
||||
|
||||
tasks:
|
||||
- name: Warte, bis der Webserver erreichbar ist
|
||||
uri:
|
||||
url: "{{ healthcheck_url }}"
|
||||
status_code: 200
|
||||
return_content: yes
|
||||
register: healthcheck_response
|
||||
retries: "{{ max_retries }}"
|
||||
delay: "{{ delay_between_retries }}"
|
||||
until: >
|
||||
healthcheck_response is defined and
|
||||
healthcheck_response.status is defined and
|
||||
healthcheck_response.status == 200
|
||||
failed_when: healthcheck_response.status != 200
|
||||
ignore_errors: false
|
||||
|
||||
- name: Ausgabe des Healthcheck-Resultats
|
||||
debug:
|
||||
msg: "Healthcheck erfolgreich: {{ healthcheck_response.content }}"
|
||||
24
ansible/playbooks/deploy.yml
Normal file
24
ansible/playbooks/deploy.yml
Normal file
@@ -0,0 +1,24 @@
|
||||
- name: Deployment in jeweilige Umgebung
|
||||
hosts: all
|
||||
become: true
|
||||
gather_facts: false
|
||||
|
||||
vars:
|
||||
docker_compose_project_path: "/var/www/michaelschiemer/"
|
||||
env_file_path: "/var/www/michaelschiemer/.env"
|
||||
|
||||
deploy_root: /var/www/michaelschiemer
|
||||
deploy_public: "{{ deploy_root }}/public"
|
||||
deploy_user: deploy
|
||||
|
||||
|
||||
app_domain: "example.com" # Passe ggf. an
|
||||
project_root: "{{ playbook_dir }}/../.."
|
||||
|
||||
|
||||
roles:
|
||||
- app
|
||||
- nginx
|
||||
- php
|
||||
- redis
|
||||
|
||||
26
ansible/playbooks/deploy/dev.yml
Normal file
26
ansible/playbooks/deploy/dev.yml
Normal file
@@ -0,0 +1,26 @@
|
||||
- name: Deployment für DEV (localhost)
|
||||
hosts: localhost
|
||||
become: true
|
||||
gather_facts: false
|
||||
|
||||
vars:
|
||||
docker_compose_project_path: "/home/michael/dev/michaelschiemer"
|
||||
env_file_path: "/var/www/michaelschiemer/.env"
|
||||
|
||||
deploy_root: /var/www/michaelschiemer
|
||||
deploy_public: "{{ deploy_root }}/public"
|
||||
deploy_user: deploy
|
||||
|
||||
app_domain: "localhost" # Passe ggf. an
|
||||
project_root: "/home/michael/dev/michaelschiemer"
|
||||
|
||||
|
||||
roles:
|
||||
#- app
|
||||
- nginx
|
||||
- php
|
||||
- redis
|
||||
|
||||
tasks:
|
||||
- name: Common Deployment Tasks
|
||||
import_tasks: ../deploy/includes/deploy_common.yml
|
||||
29
ansible/playbooks/deploy/includes/deploy_common.yml
Normal file
29
ansible/playbooks/deploy/includes/deploy_common.yml
Normal file
@@ -0,0 +1,29 @@
|
||||
- name: Docker Compose Files & Konfigurationen synchronisieren
|
||||
ansible.builtin.copy:
|
||||
src: "{{ item.src }}"
|
||||
dest: "{{ docker_compose_project_path }}/{{ item.dest }}"
|
||||
owner: root
|
||||
group: root
|
||||
mode: 0644
|
||||
loop:
|
||||
- { src: '{{ project_root }}/docker-compose.yml', dest: 'docker-compose.yml' }
|
||||
- { src: '{{ project_root }}/.env', dest: '.env' }
|
||||
# Weitere Konfigdateien nach Bedarf (z.B. nginx.conf, redis.conf, ...)
|
||||
- { src: '{{ project_root }}/docker/nginx/nginx.conf', dest: 'nginx.conf' }
|
||||
|
||||
- name: "Docker Compose: Container hochfahren (Build & Start)"
|
||||
ansible.builtin.command: |
|
||||
docker-compose -f {{ docker_compose_project_path }}/docker-compose.yml up -d --build
|
||||
args:
|
||||
chdir: "{{ docker_compose_project_path }}"
|
||||
|
||||
- name: Status prüfen
|
||||
ansible.builtin.command: |
|
||||
docker-compose -f {{ docker_compose_project_path }}/docker-compose.yml ps
|
||||
args:
|
||||
chdir: "{{ docker_compose_project_path }}"
|
||||
register: compose_ps
|
||||
|
||||
- name: Ergebnis anzeigen
|
||||
ansible.builtin.debug:
|
||||
var: compose_ps.stdout_lines
|
||||
23
ansible/playbooks/deploy/production.yml
Normal file
23
ansible/playbooks/deploy/production.yml
Normal file
@@ -0,0 +1,23 @@
|
||||
- name: Deployment für PRODUCTION
|
||||
hosts: production
|
||||
become: true
|
||||
gather_facts: false
|
||||
|
||||
vars:
|
||||
docker_compose_project_path: "/var/www/www.michaelschiemer.de/"
|
||||
env_file_path: "/var/www/www.michaelschiemer.de/.env"
|
||||
deploy_root: /var/www/www.michaelschiemer.de
|
||||
deploy_public: "{{ deploy_root }}/public"
|
||||
deploy_user: deploy
|
||||
app_domain: "michaelschiemer.de"
|
||||
project_root: "{{ playbook_dir }}/../.."
|
||||
|
||||
roles:
|
||||
- app
|
||||
- nginx
|
||||
- php
|
||||
- redis
|
||||
|
||||
tasks:
|
||||
- name: Common Deployment Tasks
|
||||
import_tasks: ../deploy/includes/deploy_common.yml
|
||||
23
ansible/playbooks/deploy/staging.yml
Normal file
23
ansible/playbooks/deploy/staging.yml
Normal file
@@ -0,0 +1,23 @@
|
||||
- name: Deployment für STAGING
|
||||
hosts: staging
|
||||
become: true
|
||||
gather_facts: false
|
||||
|
||||
vars:
|
||||
docker_compose_project_path: "/var/www/stage.michaelschiemer/"
|
||||
env_file_path: "/var/www/stage.michaelschiemer/.env"
|
||||
deploy_root: /var/www/stage.michaelschiemer
|
||||
deploy_public: "{{ deploy_root }}/public"
|
||||
deploy_user: deploy
|
||||
app_domain: "stage.example.com"
|
||||
project_root: "{{ playbook_dir }}/../.."
|
||||
|
||||
roles:
|
||||
- app
|
||||
- nginx
|
||||
- php
|
||||
- redis
|
||||
|
||||
tasks:
|
||||
- name: Common Deployment Tasks
|
||||
import_tasks: ../deploy/includes/deploy_common.yml
|
||||
11
ansible/playbooks/setup.yml
Normal file
11
ansible/playbooks/setup.yml
Normal file
@@ -0,0 +1,11 @@
|
||||
---
|
||||
- name: Basis Setup für alle Zielsysteme
|
||||
hosts: all
|
||||
become: true
|
||||
#gather_facts: true
|
||||
|
||||
roles:
|
||||
#- common
|
||||
- docker
|
||||
#- webserver
|
||||
#- app
|
||||
6
ansible/playbooks/test.yml
Normal file
6
ansible/playbooks/test.yml
Normal file
@@ -0,0 +1,6 @@
|
||||
- hosts: web
|
||||
become: true
|
||||
gather_facts: true
|
||||
|
||||
roles:
|
||||
- console
|
||||
7
ansible/playbooks/wireguard.yml
Normal file
7
ansible/playbooks/wireguard.yml
Normal file
@@ -0,0 +1,7 @@
|
||||
# ansible/wireguard.yml
|
||||
- hosts: vpn
|
||||
become: false
|
||||
gather_facts: false
|
||||
|
||||
roles:
|
||||
- wireguard
|
||||
134
ansible/roles/app/tasks/main.yml
Normal file
134
ansible/roles/app/tasks/main.yml
Normal file
@@ -0,0 +1,134 @@
|
||||
- name: Zielverzeichnis erstellen
|
||||
file:
|
||||
path: "{{ deploy_root }}"
|
||||
state: directory
|
||||
owner: "{{ deploy_user }}"
|
||||
group: "{{ deploy_user }}"
|
||||
mode: '0755'
|
||||
|
||||
- name: SSL-Verzeichnis sicherstellen
|
||||
file:
|
||||
path: "{{ deploy_root }}/ssl"
|
||||
state: directory
|
||||
owner: "{{ deploy_user }}"
|
||||
group: "{{ deploy_user }}"
|
||||
mode: '0755'
|
||||
|
||||
- name: SSL-Zertifikate prüfen
|
||||
stat:
|
||||
path: "/etc/letsencrypt/live/{{ app_domain }}/fullchain.pem"
|
||||
register: ssl_certs
|
||||
|
||||
- name: SSL-Zertifikate kopieren (falls vorhanden)
|
||||
copy:
|
||||
src: "{{ item.src }}"
|
||||
dest: "{{ item.dest }}"
|
||||
remote_src: yes
|
||||
owner: "{{ deploy_user }}"
|
||||
group: "{{ deploy_user }}"
|
||||
mode: '0644'
|
||||
loop:
|
||||
- { src: "/etc/letsencrypt/live/{{ app_domain }}/fullchain.pem", dest: "{{ deploy_root }}/ssl/fullchain.pem" }
|
||||
- { src: "/etc/letsencrypt/live/{{ app_domain }}/privkey.pem", dest: "{{ deploy_root }}/ssl/privkey.pem" }
|
||||
when: ssl_certs.stat.exists
|
||||
|
||||
- name: public-Verzeichnis synchronisieren
|
||||
synchronize:
|
||||
src: "{{ playbook_dir }}/../../public/"
|
||||
dest: "{{ deploy_public }}/"
|
||||
delete: yes
|
||||
recursive: yes
|
||||
|
||||
- name: Projekt-Stammdaten kopieren
|
||||
copy:
|
||||
src: "{{ playbook_dir }}/../../docker-compose.yml"
|
||||
dest: "{{ deploy_root }}/docker-compose.yml"
|
||||
owner: "{{ deploy_user }}"
|
||||
group: "{{ deploy_user }}"
|
||||
mode: '0644'
|
||||
|
||||
- name: .env-Datei prüfen
|
||||
stat:
|
||||
path: "{{ project_root }}/.env"
|
||||
register: env_file
|
||||
|
||||
- name: .env kopieren (falls vorhanden)
|
||||
copy:
|
||||
src: "{{ project_root }}/.env"
|
||||
dest: "{{ deploy_root }}/.env"
|
||||
mode: '0644'
|
||||
when: env_file.stat.exists
|
||||
|
||||
- name: Quellcode synchronisieren
|
||||
synchronize:
|
||||
src: "{{ playbook_dir }}/../../src/"
|
||||
dest: "{{ deploy_root }}/src/"
|
||||
delete: yes
|
||||
recursive: yes
|
||||
|
||||
- name: Docker-Verzeichnis prüfen
|
||||
stat:
|
||||
path: "{{ project_root }}/docker"
|
||||
register: docker_dir
|
||||
delegate_to: localhost
|
||||
become: false
|
||||
|
||||
- name: Docker-Configs synchronisieren (falls vorhanden)
|
||||
synchronize:
|
||||
src: "{{ project_root }}/docker/"
|
||||
dest: "{{ deploy_root }}/docker/"
|
||||
delete: yes
|
||||
recursive: yes
|
||||
when: docker_dir.stat.exists
|
||||
|
||||
- name: Rechte im Zielverzeichnis korrigieren
|
||||
file:
|
||||
path: "{{ deploy_root }}"
|
||||
state: directory
|
||||
owner: "{{ deploy_user }}"
|
||||
group: "{{ deploy_user }}"
|
||||
mode: '0755'
|
||||
recurse: yes
|
||||
|
||||
# Cache-Verzeichnis für UID/GID 1000 (z.B. appuser im Container)
|
||||
- name: Stelle Schreibrechte für Cache-Verzeichnis her
|
||||
file:
|
||||
path: "{{ deploy_root }}/cache"
|
||||
state: directory
|
||||
owner: 1000
|
||||
group: 1000
|
||||
mode: '0775'
|
||||
recurse: yes
|
||||
|
||||
- name: Docker Compose neu bauen und starten
|
||||
shell: |
|
||||
docker compose down
|
||||
docker compose up -d --build
|
||||
args:
|
||||
chdir: "{{ deploy_root }}"
|
||||
|
||||
- name: PHP-Container für Composer starten
|
||||
shell: docker compose up -d php
|
||||
args:
|
||||
chdir: "{{ deploy_root }}"
|
||||
|
||||
- name: Kurze Wartezeit bis PHP-Container bereit
|
||||
wait_for:
|
||||
timeout: 5
|
||||
|
||||
- name: Composer Abhängigkeiten installieren
|
||||
shell: docker compose exec -T php composer install --no-interaction
|
||||
args:
|
||||
chdir: "{{ deploy_root }}"
|
||||
register: composer_result
|
||||
ignore_errors: yes
|
||||
|
||||
- name: Composer-Ergebnis anzeigen
|
||||
debug:
|
||||
var: composer_result.stdout_lines
|
||||
when: composer_result.stdout is defined
|
||||
|
||||
- name: Composer-Fehler anzeigen
|
||||
debug:
|
||||
var: composer_result.stderr_lines
|
||||
when: composer_result.stderr is defined
|
||||
26
ansible/roles/common/tasks/main.yml
Normal file
26
ansible/roles/common/tasks/main.yml
Normal file
@@ -0,0 +1,26 @@
|
||||
# Grundlegende Systemkonfiguration
|
||||
- name: Basis-Pakete aktualisieren und installieren
|
||||
apt:
|
||||
name:
|
||||
- sudo
|
||||
- vim
|
||||
- htop
|
||||
- git
|
||||
- zip
|
||||
- unzip
|
||||
- curl
|
||||
- wget
|
||||
state: present
|
||||
update_cache: yes
|
||||
become: true
|
||||
|
||||
# Passwordless sudo für den deploy-Benutzer einrichten
|
||||
- name: Konfiguriere passwordless sudo für deploy-Benutzer
|
||||
lineinfile:
|
||||
path: "/etc/sudoers.d/{{ deploy_user }}"
|
||||
line: "{{ deploy_user }} ALL=(ALL) NOPASSWD: ALL"
|
||||
state: present
|
||||
create: yes
|
||||
mode: '0440'
|
||||
validate: 'visudo -cf %s'
|
||||
become: true
|
||||
9
ansible/roles/console/tasks/main.yml
Normal file
9
ansible/roles/console/tasks/main.yml
Normal file
@@ -0,0 +1,9 @@
|
||||
- name: Füge Funktion für ms (mit Argumenten) hinzu
|
||||
blockinfile:
|
||||
path: "/home/{{ ansible_user }}/.bashrc"
|
||||
marker: "# {mark} ms docker alias"
|
||||
block: |
|
||||
ms() {
|
||||
docker compose exec php php ms "$@"
|
||||
}
|
||||
become: false
|
||||
9
ansible/roles/docker/README.md
Normal file
9
ansible/roles/docker/README.md
Normal file
@@ -0,0 +1,9 @@
|
||||
# Rolle: Docker
|
||||
|
||||
Diese Rolle installiert Docker Engine, CLI, Compose-Plugin sowie (optional) Docker Compose V1 als Fallback.
|
||||
- Fügt den gewünschten User zur Docker-Gruppe hinzu.
|
||||
- Startet und aktiviert den Docker-Dienst.
|
||||
|
||||
## Variablen
|
||||
- `docker_compose_version`: Version von Docker Compose V1 für Fallback (Standard: 1.29.2).
|
||||
- `docker_user`: Benutzer, der in die Gruppe `docker` aufgenommen werden soll (Standard: aktueller Ansible-User).
|
||||
3
ansible/roles/docker/defaults/main.yml
Normal file
3
ansible/roles/docker/defaults/main.yml
Normal file
@@ -0,0 +1,3 @@
|
||||
docker_compose_version: "v2.29.2"
|
||||
docker_install_compose: true
|
||||
docker_user: "{{ ansible_user || default('michael' }}"
|
||||
4
ansible/roles/docker/handlers/main.yml
Normal file
4
ansible/roles/docker/handlers/main.yml
Normal file
@@ -0,0 +1,4 @@
|
||||
- name: restart docker
|
||||
ansible.builtin.service:
|
||||
name: docker
|
||||
state: restarted
|
||||
58
ansible/roles/docker/tasks/main.yml
Normal file
58
ansible/roles/docker/tasks/main.yml
Normal file
@@ -0,0 +1,58 @@
|
||||
- name: Docker-Abhängigkeiten installieren
|
||||
apt:
|
||||
name:
|
||||
- apt-transport-https
|
||||
- ca-certificates
|
||||
- curl
|
||||
- gnupg
|
||||
- lsb-release
|
||||
state: present
|
||||
update_cache: yes
|
||||
|
||||
- name: Docker GPG-Schlüssel hinzufügen
|
||||
apt_key:
|
||||
url: https://download.docker.com/linux/{{ ansible_distribution | lower }}/gpg
|
||||
state: present
|
||||
|
||||
- name: Docker Repository hinzufügen
|
||||
apt_repository:
|
||||
repo: "deb [arch=amd64] https://download.docker.com/linux/{{ ansible_distribution | lower }} {{ ansible_distribution_release }} stable"
|
||||
state: present
|
||||
|
||||
- name: Docker Engine installieren
|
||||
apt:
|
||||
name:
|
||||
- docker-ce
|
||||
- docker-ce-cli
|
||||
- containerd.io
|
||||
- docker-compose-plugin
|
||||
state: present
|
||||
update_cache: yes
|
||||
|
||||
- name: Docker Compose installieren (V1 als Fallback)
|
||||
get_url:
|
||||
url: "https://github.com/docker/compose/releases/download/v{{ docker_compose_version }}/docker-compose-linux-x86_64"
|
||||
dest: /usr/local/bin/docker-compose
|
||||
mode: '0755'
|
||||
|
||||
- name: Benutzer zur Docker-Gruppe hinzufügen
|
||||
user:
|
||||
name: "{{ ansible_user }}"
|
||||
groups: docker
|
||||
append: yes
|
||||
|
||||
- name: Docker-Service starten und aktivieren
|
||||
service:
|
||||
name: docker
|
||||
state: started
|
||||
enabled: yes
|
||||
notify: restart docker
|
||||
|
||||
|
||||
- name: Starte Docker-Container via Compose
|
||||
community.docker.docker_compose_v2:
|
||||
#project_src: "{{ playbook_dir | dirname }}/../" # ggf. anpassen auf deinen Compose-Pfad!
|
||||
project_src: "{{ app_root }}"
|
||||
build: always
|
||||
recreate: always
|
||||
|
||||
5
ansible/roles/nginx/defaults/main.yml
Normal file
5
ansible/roles/nginx/defaults/main.yml
Normal file
@@ -0,0 +1,5 @@
|
||||
nginx_conf_template: nginx.conf.j2
|
||||
nginx_default_site_template: default.conf.j2
|
||||
nginx_ssl_src_dir: "{{ app_root }}/ssl"
|
||||
nginx_ssl_dest_dir: "/var/www/michaelschiemer/ssl"
|
||||
nginx_target_dir: "/var/www/michaelschiemer/docker/nginx"
|
||||
0
ansible/roles/nginx/files/docker-entrypoint.sh
Normal file
0
ansible/roles/nginx/files/docker-entrypoint.sh
Normal file
2
ansible/roles/nginx/handlers/main.yml
Normal file
2
ansible/roles/nginx/handlers/main.yml
Normal file
@@ -0,0 +1,2 @@
|
||||
- name: reload nginx
|
||||
ansible.builtin.command: docker exec <nginx_container_name> nginx -s reload
|
||||
37
ansible/roles/nginx/tasks/main.yml
Normal file
37
ansible/roles/nginx/tasks/main.yml
Normal file
@@ -0,0 +1,37 @@
|
||||
- name: Stelle das nginx-Verzeichnis sicher
|
||||
ansible.builtin.file:
|
||||
path: "{{ nginx_target_dir }}"
|
||||
state: directory
|
||||
recurse: yes
|
||||
mode: '0755'
|
||||
|
||||
- name: Kopiere nginx-Konfiguration (nginx.conf)
|
||||
ansible.builtin.template:
|
||||
src: "{{ nginx_conf_template }}"
|
||||
dest: "{{ nginx_target_dir }}/nginx.conf"
|
||||
mode: '0644'
|
||||
|
||||
- name: Kopiere default site conf
|
||||
ansible.builtin.template:
|
||||
src: "{{ nginx_default_site_template }}"
|
||||
dest: "{{ nginx_target_dir }}/default.conf"
|
||||
mode: '0644'
|
||||
|
||||
- name: Kopiere docker-entrypoint Skript
|
||||
ansible.builtin.copy:
|
||||
src: docker-entrypoint.sh
|
||||
dest: "{{ nginx_target_dir }}/docker-entrypoint.sh"
|
||||
mode: '0755'
|
||||
|
||||
- name: Baue und starte Nginx-Container (optional, wenn Compose separat genutzt wird, dann hier nicht nötig)
|
||||
ansible.builtin.command: docker-compose up -d --build web
|
||||
args:
|
||||
chdir: "{{ docker_compose_project_path }}"
|
||||
when: nginx_target_dir is defined
|
||||
register: nginx_compose_result
|
||||
ignore_errors: true
|
||||
|
||||
- name: Zeige Compose-Resultat
|
||||
ansible.builtin.debug:
|
||||
var: nginx_compose_result.stdout_lines
|
||||
when: nginx_compose_result is defined
|
||||
0
ansible/roles/nginx/templates/default.conf.j2
Normal file
0
ansible/roles/nginx/templates/default.conf.j2
Normal file
0
ansible/roles/nginx/templates/nginx.conf.j2
Normal file
0
ansible/roles/nginx/templates/nginx.conf.j2
Normal file
4
ansible/roles/setup/handlers/main.yml
Normal file
4
ansible/roles/setup/handlers/main.yml
Normal file
@@ -0,0 +1,4 @@
|
||||
- name: Reload nginx
|
||||
service:
|
||||
name: nginx
|
||||
state: reloaded
|
||||
@@ -1,3 +1,47 @@
|
||||
- name: Test-Task Setup-Rolle lokal
|
||||
debug:
|
||||
msg: "Setup-Rolle ist vorbereitet – echte Installation folgt auf Server."
|
||||
- name: Docker installieren
|
||||
apt:
|
||||
name:
|
||||
- docker.io
|
||||
- docker-compose
|
||||
state: present
|
||||
update_cache: yes
|
||||
|
||||
- name: Certbot + Plugin installieren
|
||||
apt:
|
||||
name:
|
||||
- certbot
|
||||
- python3-certbot-nginx
|
||||
state: present
|
||||
update_cache: yes
|
||||
|
||||
- name: Challenge-Verzeichnis für Let's Encrypt anlegen
|
||||
file:
|
||||
path: /var/www/html/.well-known/acme-challenge
|
||||
state: directory
|
||||
owner: www-data
|
||||
group: www-data
|
||||
mode: '0755'
|
||||
recurse: yes
|
||||
|
||||
- name: Füge Let's Encrypt Challenge-Pfad in den Nginx-Vhost ein
|
||||
blockinfile:
|
||||
path: /etc/nginx/sites-available/default
|
||||
marker: "# {mark} ANSIBLE LETSENCRYPT"
|
||||
insertafter: "^\\s*server\\s*{"
|
||||
block: |
|
||||
location ^~ /.well-known/acme-challenge/ {
|
||||
root /var/www/html;
|
||||
allow all;
|
||||
default_type "text/plain";
|
||||
}
|
||||
notify: Reload nginx
|
||||
|
||||
|
||||
|
||||
- name: Let's Encrypt Zertifikat anfordern
|
||||
command: >
|
||||
certbot --nginx -n --agree-tos --redirect
|
||||
-m kontakt@michaelschiemer.de
|
||||
-d test.michaelschiemer.de
|
||||
args:
|
||||
creates: /etc/letsencrypt/live/test.michaelschiemer.de/fullchain.pem
|
||||
|
||||
18
ansible/roles/system_update/tasks/main.yml
Normal file
18
ansible/roles/system_update/tasks/main.yml
Normal file
@@ -0,0 +1,18 @@
|
||||
- name: Systempakete aktualisieren
|
||||
apt:
|
||||
update_cache: yes
|
||||
upgrade: safe
|
||||
autoremove: yes
|
||||
autoclean: yes
|
||||
register: upgrade_result
|
||||
become: true
|
||||
|
||||
- name: Zeige ggf. Anzahl aktualisierter Pakete
|
||||
debug:
|
||||
msg: "Anzahl aktualisierter Pakete: {{ upgrade_result.stdout_lines | default([]) | length }}"
|
||||
|
||||
- name: Reboot durchführen, wenn notwendig
|
||||
reboot:
|
||||
msg: "Reboot wegen Kernel-/System-Update erforderlich"
|
||||
pre_reboot_delay: 30
|
||||
when: upgrade_result.changed
|
||||
50
ansible/roles/webserver/tasks/main.yml
Normal file
50
ansible/roles/webserver/tasks/main.yml
Normal file
@@ -0,0 +1,50 @@
|
||||
- name: Certbot + Plugin installieren
|
||||
apt:
|
||||
name:
|
||||
- certbot
|
||||
- python3-certbot-nginx
|
||||
state: present
|
||||
update_cache: yes
|
||||
when: letsencrypt_enabled
|
||||
|
||||
- name: Challenge-Verzeichnis für Let's Encrypt anlegen
|
||||
file:
|
||||
path: "{{ app_public }}/.well-known/acme-challenge"
|
||||
state: directory
|
||||
owner: www-data
|
||||
group: www-data
|
||||
mode: '0755'
|
||||
recurse: yes
|
||||
when: letsencrypt_enabled and letsencrypt_certbot_method == 'webroot'
|
||||
|
||||
- name: Stoppe Nginx für Standalone-Methode
|
||||
service:
|
||||
name: nginx
|
||||
state: stopped
|
||||
when: letsencrypt_enabled and letsencrypt_certbot_method == 'standalone'
|
||||
|
||||
- name: Let's Encrypt Zertifikat anfordern (Standalone)
|
||||
command: >
|
||||
certbot certonly --standalone -n --agree-tos
|
||||
-m {{ app_email }}
|
||||
-d {{ app_domain }}
|
||||
args:
|
||||
creates: /etc/letsencrypt/live/{{ app_domain }}/fullchain.pem
|
||||
when: letsencrypt_enabled and letsencrypt_certbot_method == 'standalone'
|
||||
|
||||
- name: Let's Encrypt Zertifikat anfordern (Webroot)
|
||||
command: >
|
||||
certbot certonly --webroot -w {{ app_public }} -n --agree-tos
|
||||
-m {{ app_email }}
|
||||
-d {{ app_domain }}
|
||||
args:
|
||||
creates: /etc/letsencrypt/live/{{ app_domain }}/fullchain.pem
|
||||
when: letsencrypt_enabled and letsencrypt_certbot_method == 'webroot'
|
||||
|
||||
- name: Kopiere SSL-Zertifikate für Docker
|
||||
copy:
|
||||
src: "/etc/letsencrypt/live/{{ app_domain }}/"
|
||||
dest: "{{ app_root }}/ssl/"
|
||||
remote_src: yes
|
||||
mode: '0644'
|
||||
when: letsencrypt_enabled
|
||||
6
ansible/roles/wireguard/defaults/main.yml
Normal file
6
ansible/roles/wireguard/defaults/main.yml
Normal file
@@ -0,0 +1,6 @@
|
||||
wireguard_interface: wg0
|
||||
wireguard_port: 51820
|
||||
wireguard_address: 10.8.0.1/24
|
||||
wireguard_server_ip: 94.16.110.151 # oder deine Domain
|
||||
|
||||
wireguard_network: "10.8.0.0/24"
|
||||
133
ansible/roles/wireguard/tasks/configure.yml
Normal file
133
ansible/roles/wireguard/tasks/configure.yml
Normal file
@@ -0,0 +1,133 @@
|
||||
# --------------------------------------------------------
|
||||
# WireGuard installieren
|
||||
# --------------------------------------------------------
|
||||
|
||||
- name: Stelle sicher, dass WireGuard installiert ist
|
||||
apt:
|
||||
name: wireguard
|
||||
state: present
|
||||
update_cache: yes
|
||||
become: true
|
||||
when: ansible_connection != "local"
|
||||
|
||||
# --------------------------------------------------------
|
||||
# Server-Schlüssel erzeugen und speichern
|
||||
# --------------------------------------------------------
|
||||
|
||||
- name: Prüfe ob privater Server-Schlüssel existiert
|
||||
stat:
|
||||
path: /etc/wireguard/privatekey
|
||||
register: privkey_file
|
||||
become: true
|
||||
when: ansible_connection != "local"
|
||||
|
||||
- name: Erstelle Schlüsselpaar für Server (wenn nicht vorhanden)
|
||||
command: wg genkey
|
||||
register: server_private_key
|
||||
when: ansible_connection != "local" and (not privkey_file.stat.exists | default(true))
|
||||
|
||||
- name: Speichere privaten Schlüssel
|
||||
copy:
|
||||
content: "{{ server_private_key.stdout }}"
|
||||
dest: /etc/wireguard/privatekey
|
||||
mode: "0600"
|
||||
when: server_private_key.stdout is defined and server_private_key.stdout is defined
|
||||
|
||||
- name: Lies privaten Schlüssel ein
|
||||
slurp:
|
||||
src: /etc/wireguard/privatekey
|
||||
become: true
|
||||
when: ansible_connection != "local"
|
||||
|
||||
- name: Erzeuge öffentlichen Server-Schlüssel
|
||||
command: "echo '{{ wg_privkey }}' | wg pubkey"
|
||||
register: wg_pubkey
|
||||
when: ansible_connection != "local"
|
||||
|
||||
- name: Privaten Server-Schlüssel anzeigen
|
||||
debug:
|
||||
msg: "{{ server_private_key }}"
|
||||
when: ansible_connection != "local"
|
||||
|
||||
# --------------------------------------------------------
|
||||
# Client-Key-Erzeugung lokal (einmalig pro Client)
|
||||
# --------------------------------------------------------
|
||||
|
||||
- name: Generiere privaten Schlüssel für Clients (auf dem Server)
|
||||
command: wg genkey
|
||||
args:
|
||||
creates: "/etc/wireguard/client-{{ item.name }}.key"
|
||||
loop: "{{ wireguard_clients }}"
|
||||
loop_control:
|
||||
label: "{{ item.name }}"
|
||||
register: client_private_keys
|
||||
when: ansible_connection != "local"
|
||||
|
||||
|
||||
- name: Erzeuge öffentlichen Schlüssel für Clients
|
||||
command: "echo '{{ client_privkey_result.stdout }}' | wg pubkey"
|
||||
register: client_pubkey_result
|
||||
when:
|
||||
- ansible_connection != "local"
|
||||
- client_privkey_result is defined
|
||||
- client_privkey_result.stdout is defined
|
||||
|
||||
- name: wireguard_clients mit public_key anreichern
|
||||
set_fact:
|
||||
wireguard_clients: "{{ wireguard_clients_with_pubkey | default([]) + [ item.0 | combine({'public_key': item.1.stdout|trim }) ] }}"
|
||||
loop: "{{ wireguard_clients | zip(client_public_keys.results) | list }}"
|
||||
when: client_public_keys is defined
|
||||
|
||||
- name: Aktuelles wireguard_clients-Set überschreiben
|
||||
set_fact:
|
||||
wireguard_clients: "{{ wireguard_clients_with_pubkey }}"
|
||||
when: wireguard_clients_with_pubkey is defined
|
||||
|
||||
# --------------------------------------------------------
|
||||
# Konfigurationsdatei erzeugen
|
||||
# --------------------------------------------------------
|
||||
|
||||
#- debug:
|
||||
# var: wireguard_clients
|
||||
|
||||
- name: Render wg0.conf
|
||||
template:
|
||||
src: wg0.conf.j2
|
||||
dest: /etc/wireguard/wg0.conf
|
||||
when: wg_privkey is defined and wg_privkey != ""
|
||||
|
||||
# --------------------------------------------------------
|
||||
# IP Forwarding & WireGuard aktivieren
|
||||
# --------------------------------------------------------
|
||||
|
||||
- name: Aktiviere IP-Forwarding
|
||||
sysctl:
|
||||
name: net.ipv4.ip_forward
|
||||
value: 1
|
||||
state: present
|
||||
sysctl_set: yes
|
||||
reload: yes
|
||||
become: true
|
||||
when: ansible_connection != "local"
|
||||
|
||||
- name: Starte und aktiviere WireGuard
|
||||
systemd:
|
||||
name: wg-quick@wg0
|
||||
enabled: true
|
||||
state: started
|
||||
daemon_reload: yes
|
||||
become: true
|
||||
when: ansible_connection != "local"
|
||||
|
||||
- name: Verteilt für jeden Client die Client-Config
|
||||
template:
|
||||
src: client.conf.j2
|
||||
dest: "/etc/wireguard/clients/{{ item.name }}.conf"
|
||||
owner: root
|
||||
group: root
|
||||
mode: 0600
|
||||
loop: "{{ wireguard_clients }}"
|
||||
#delegate_to: localhost
|
||||
run_once: true
|
||||
become: true
|
||||
when: ansible_connection != "local"
|
||||
54
ansible/roles/wireguard/tasks/failsafe.yml
Normal file
54
ansible/roles/wireguard/tasks/failsafe.yml
Normal file
@@ -0,0 +1,54 @@
|
||||
---
|
||||
# roles/wireguard/tasks/failsafe.yml
|
||||
# Sicherstellt, dass SSH über VPN funktioniert und ein Fallback vorhanden ist
|
||||
|
||||
- name: Stelle sicher, dass wireguard_network gesetzt ist
|
||||
assert:
|
||||
that:
|
||||
- wireguard_network is defined
|
||||
fail_msg: "wireguard_network muss gesetzt sein (z. B. 10.8.0.0/24)"
|
||||
|
||||
- name: Automatisch externe IP als fallback_ip setzen (nur wenn nicht gesetzt)
|
||||
shell: curl -s ifconfig.me
|
||||
register: detected_fallback_ip
|
||||
when: fallback_ip is not defined
|
||||
changed_when: false
|
||||
|
||||
- name: Setze fallback_ip dynamisch als Ansible-Fact (wenn nicht gesetzt)
|
||||
set_fact:
|
||||
fallback_ip: "{{ detected_fallback_ip.stdout }}"
|
||||
when: fallback_ip is not defined
|
||||
|
||||
- name: (Optional) Erlaube temporär Fallback-SSH von aktueller IP
|
||||
ufw:
|
||||
rule: allow
|
||||
port: 22
|
||||
proto: tcp
|
||||
from_ip: "{{ fallback_ip }}"
|
||||
|
||||
- name: Erlaube SSH-Zugriff über VPN
|
||||
ufw:
|
||||
rule: allow
|
||||
port: 22
|
||||
proto: tcp
|
||||
from_ip: "{{ wireguard_network }}"
|
||||
|
||||
- name: (Warnung) Prüfe ob VPN-Interface aktiv ist
|
||||
shell: ip a show dev wg0
|
||||
register: vpn_interface_check
|
||||
failed_when: false
|
||||
|
||||
- name: Hinweis, wenn VPN-Interface nicht aktiv ist
|
||||
debug:
|
||||
msg: "⚠️ VPN-Interface wg0 scheint nicht aktiv zu sein. SSH über VPN wird nicht funktionieren."
|
||||
when: vpn_interface_check.rc != 0
|
||||
|
||||
- name: (Optional) SSH von überall blockieren – nur wenn VPN aktiv
|
||||
when:
|
||||
- ssh_lockdown | default(false)
|
||||
- vpn_interface_check.rc == 0
|
||||
ufw:
|
||||
rule: deny
|
||||
port: 22
|
||||
proto: tcp
|
||||
from_ip: 0.0.0.0/0
|
||||
83
ansible/roles/wireguard/tasks/firewall.yml
Normal file
83
ansible/roles/wireguard/tasks/firewall.yml
Normal file
@@ -0,0 +1,83 @@
|
||||
# Beispiel: Passe jeden Task in dieser Datei so an:
|
||||
- name: Aktiviere Firewall-Regeln für WireGuard
|
||||
ufw:
|
||||
rule: allow
|
||||
port: "{{ wireguard_port }}"
|
||||
proto: udp
|
||||
become: true
|
||||
when: ansible_connection != "local"
|
||||
- name: Prüfe, ob UFW installiert ist
|
||||
command: which ufw
|
||||
register: ufw_installed
|
||||
ignore_errors: true
|
||||
changed_when: false
|
||||
|
||||
- name: Installiere UFW (falls nicht vorhanden)
|
||||
apt:
|
||||
name: ufw
|
||||
state: present
|
||||
update_cache: yes
|
||||
when: ufw_installed.rc != 0
|
||||
|
||||
# Setze Standardrichtlinien (erst Konfiguration, dann am Ende aktivieren)
|
||||
- name: Setze Policy für eingehenden Traffic auf "deny"
|
||||
ufw:
|
||||
direction: incoming
|
||||
policy: deny
|
||||
|
||||
- name: Setze Policy für ausgehenden Traffic auf "allow"
|
||||
ufw:
|
||||
direction: outgoing
|
||||
policy: allow
|
||||
|
||||
# WireGuard-Port freigeben (UDP)
|
||||
- name: WireGuard-Port erlauben
|
||||
ufw:
|
||||
rule: allow
|
||||
port: "{{ wireguard_port | default(51820) }}"
|
||||
proto: udp
|
||||
|
||||
# SSH von bestimmter IP erlauben
|
||||
- name: SSH von deiner IP erlauben (empfohlen)
|
||||
ufw:
|
||||
rule: allow
|
||||
port: 22
|
||||
proto: tcp
|
||||
from_ip: "{{ fallback_ip }}"
|
||||
when: fallback_ip is defined and fallback_ip | length > 0
|
||||
|
||||
# Temporär für Tests: SSH für alle erlauben (nur bei Bedarf!)
|
||||
- name: SSH von überall erlauben (fail-safe, NUR während Setup/Test)
|
||||
ufw:
|
||||
rule: allow
|
||||
port: 22
|
||||
proto: tcp
|
||||
when: (not (fallback_ip is defined and fallback_ip | length > 0)) or (enable_ssh_from_anywhere | default(false))
|
||||
|
||||
# Masquerading für WireGuard
|
||||
- name: NAT für WireGuard aktivieren
|
||||
iptables:
|
||||
table: nat
|
||||
chain: POSTROUTING
|
||||
out_interface: "{{ wireguard_exit_interface | default('eth0') }}"
|
||||
source: "{{ wireguard_network }}"
|
||||
jump: MASQUERADE
|
||||
|
||||
- name: WireGuard Kernel-Modul laden
|
||||
modprobe:
|
||||
name: wireguard
|
||||
state: present
|
||||
|
||||
# UFW ganz am Schluss aktivieren
|
||||
- name: UFW aktivieren
|
||||
ufw:
|
||||
state: enabled
|
||||
|
||||
- name: Aktive UFW-Regeln anzeigen (zum Debuggen)
|
||||
command: ufw status verbose
|
||||
register: ufw_status
|
||||
changed_when: false
|
||||
|
||||
- name: Zeige UFW-Regeln im Ansible-Output
|
||||
debug:
|
||||
var: ufw_status.stdout
|
||||
60
ansible/roles/wireguard/tasks/generate_client_single.yml
Normal file
60
ansible/roles/wireguard/tasks/generate_client_single.yml
Normal file
@@ -0,0 +1,60 @@
|
||||
- name: Key-Verzeichnis für Client anlegen
|
||||
file:
|
||||
path: "{{ role_path }}/client-keys/{{ client.name }}"
|
||||
state: directory
|
||||
mode: "0700"
|
||||
become: true
|
||||
|
||||
- name: Existenz des privaten Schlüssels prüfen
|
||||
stat:
|
||||
path: "{{ role_path }}/client-keys/{{ client.name }}/private.key"
|
||||
register: client_private_key_stat
|
||||
|
||||
- name: Privaten Schlüssel generieren (nur falls nicht vorhanden)
|
||||
command: wg genkey
|
||||
register: genpriv
|
||||
args:
|
||||
chdir: "{{ role_path }}/client-keys/{{ client.name }}"
|
||||
when: not client_private_key_stat.stat.exists
|
||||
|
||||
- name: Privaten Schlüssel speichern (nur falls nicht vorhanden)
|
||||
copy:
|
||||
content: "{{ genpriv.stdout }}"
|
||||
dest: "{{ role_path }}/client-keys/{{ client.name }}/private.key"
|
||||
mode: "0600"
|
||||
when: not client_private_key_stat.stat.exists
|
||||
|
||||
- name: Public Key aus privaten Schlüssel generieren (bei Neuerstellung)
|
||||
command: wg pubkey
|
||||
args:
|
||||
stdin: "{{ genpriv.stdout }}"
|
||||
chdir: "{{ role_path }}/client-keys/{{ client.name }}"
|
||||
register: genpub
|
||||
when: not client_private_key_stat.stat.exists
|
||||
|
||||
- name: Bestehenden privaten Schlüssel laden (falls vorhanden)
|
||||
slurp:
|
||||
src: "{{ role_path }}/client-keys/{{ client.name }}/private.key"
|
||||
register: loaded_private
|
||||
when: client_private_key_stat.stat.exists
|
||||
|
||||
- name: Public Key aus gespeichertem Private Key erzeugen (falls vorhanden)
|
||||
command: wg pubkey
|
||||
args:
|
||||
stdin: "{{ loaded_private.content | b64decode }}"
|
||||
chdir: "{{ role_path }}/client-keys/{{ client.name }}"
|
||||
register: genpub_existing
|
||||
when: client_private_key_stat.stat.exists
|
||||
|
||||
- name: Public Key für Client in Datei schreiben
|
||||
copy:
|
||||
content: >
|
||||
{{ (genpub.stdout if not client_private_key_stat.stat.exists else genpub_existing.stdout) }}
|
||||
dest: "{{ role_path }}/client-keys/{{ client.name }}/public.key"
|
||||
mode: "0644"
|
||||
|
||||
- name: Variablen für Client setzen (private/public key, Adresse)
|
||||
set_fact:
|
||||
"wg_{{ client.name }}_private_key": "{{ (genpriv.stdout if not client_private_key_stat.stat.exists else loaded_private.content | b64decode) }}"
|
||||
"wg_{{ client.name }}_public_key": "{{ (genpub.stdout if not client_private_key_stat.stat.exists else genpub_existing.stdout) }}"
|
||||
"wg_{{ client.name }}_address": "{{ client.address }}"
|
||||
39
ansible/roles/wireguard/tasks/generate_clients.yml
Normal file
39
ansible/roles/wireguard/tasks/generate_clients.yml
Normal file
@@ -0,0 +1,39 @@
|
||||
- name: Schleife über alle WireGuard-Clients
|
||||
include_tasks: generate_client_single.yml
|
||||
loop: "{{ wireguard_clients }}"
|
||||
loop_control:
|
||||
loop_var: client
|
||||
|
||||
- name: Generiere privaten Schlüssel für jeden Client
|
||||
shell: "wg genkey"
|
||||
register: wg_client_private_keys
|
||||
loop: "{{ wireguard_clients }}"
|
||||
loop_control:
|
||||
label: "{{ item.name }}"
|
||||
# kein delegate_to mehr!
|
||||
run_once: true # ggf. auch entfernen, siehe Anmerkung unten
|
||||
|
||||
- name: Setze globale Client-Key-Facts für alle Clients
|
||||
set_fact:
|
||||
wg_all_clients_private_keys: >-
|
||||
{{
|
||||
wg_all_clients_private_keys | default({}) | combine({
|
||||
item.1.name: item.0.stdout
|
||||
})
|
||||
}}
|
||||
loop: "{{ wg_client_private_keys.results | zip(wireguard_clients) | list }}"
|
||||
delegate_to: localhost
|
||||
run_once: true
|
||||
|
||||
|
||||
- name: Generiere Private Keys für Clients
|
||||
command: "wg genkey"
|
||||
register: client_keys_raw
|
||||
loop: "{{ wireguard_clients }}"
|
||||
loop_control:
|
||||
loop_var: client
|
||||
changed_when: false
|
||||
|
||||
- name: Mappe Keys nach Namen
|
||||
set_fact:
|
||||
wg_all_clients_private_keys: "{{ dict(wireguard_clients | map(attribute='name') | list | zip(client_keys_raw.results | map(attribute='stdout') | list)) }}"
|
||||
7
ansible/roles/wireguard/tasks/install.yml
Normal file
7
ansible/roles/wireguard/tasks/install.yml
Normal file
@@ -0,0 +1,7 @@
|
||||
- name: Stelle sicher, dass WireGuard installiert ist
|
||||
apt:
|
||||
name: wireguard
|
||||
state: present
|
||||
update_cache: yes
|
||||
become: true
|
||||
when: ansible_connection != "local"
|
||||
22
ansible/roles/wireguard/tasks/main.yml
Normal file
22
ansible/roles/wireguard/tasks/main.yml
Normal file
@@ -0,0 +1,22 @@
|
||||
#- include_tasks: install.yml
|
||||
#- include_tasks: configure.yml
|
||||
#- include_tasks: generate_clients.yml
|
||||
#- include_tasks: firewall.yml
|
||||
|
||||
|
||||
- name: Installiere WireGuard
|
||||
import_tasks: install.yml
|
||||
when: ansible_connection != "local"
|
||||
|
||||
- name: Konfiguriere WireGuard
|
||||
import_tasks: configure.yml
|
||||
|
||||
- name: Generiert .conf Dateien
|
||||
import_tasks: generate_clients.yml
|
||||
|
||||
- name: Setze Firewall-Regeln
|
||||
import_tasks: firewall.yml
|
||||
when: ansible_connection != "local"
|
||||
|
||||
- name: Wende VPN-Failsafe-Regeln an
|
||||
import_tasks: failsafe.yml
|
||||
10
ansible/roles/wireguard/templates/client.conf.j2
Normal file
10
ansible/roles/wireguard/templates/client.conf.j2
Normal file
@@ -0,0 +1,10 @@
|
||||
[Interface]
|
||||
PrivateKey = {{ wg_all_clients_private_keys[item.name] }}
|
||||
Address = {{ item.address }}/32
|
||||
DNS = 1.1.1.1
|
||||
|
||||
[Peer]
|
||||
PublicKey = {{ item.public_key }}
|
||||
Endpoint = {{ wireguard_server_ip }}:{{ wireguard_port }}
|
||||
AllowedIPs = {{ wireguard_network }}, {{ wireguard_server_ip }}/32
|
||||
PersistentKeepalive = 25
|
||||
12
ansible/roles/wireguard/templates/wg0.conf.j2
Normal file
12
ansible/roles/wireguard/templates/wg0.conf.j2
Normal file
@@ -0,0 +1,12 @@
|
||||
[Interface]
|
||||
Address = {{ wireguard_address }}
|
||||
PrivateKey = {{ wg_privkey | b64decode | trim }}
|
||||
ListenPort = {{ wireguard_port }}
|
||||
PostUp = iptables -A FORWARD -i {{ wireguard_interface }} -j ACCEPT; iptables -A FORWARD -o {{ wireguard_interface }} -j ACCEPT; iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
|
||||
PostDown = iptables -D FORWARD -i {{ wireguard_interface }} -j ACCEPT; iptables -D FORWARD -o {{ wireguard_interface }} -j ACCEPT; iptables -t nat -D POSTROUTING -o eth0 -j MASQUERADE
|
||||
|
||||
{% for client in wireguard_clients %}
|
||||
[Peer]
|
||||
PublicKey = {{ client.public_key }}
|
||||
AllowedIPs = {{ client.address }}/32
|
||||
{% endfor %}
|
||||
@@ -1,4 +0,0 @@
|
||||
- hosts: web
|
||||
become: false
|
||||
roles:
|
||||
- setup
|
||||
7
ansible/wireguard-create-config.yml
Normal file
7
ansible/wireguard-create-config.yml
Normal file
@@ -0,0 +1,7 @@
|
||||
# ansible/wireguard-create-config.yml
|
||||
- hosts: vpn
|
||||
gather_facts: false
|
||||
roles:
|
||||
- role: wireguard
|
||||
tasks_from: generate_clients # Zum Beispiel, je nach Task
|
||||
# tasks_from: generate_client_single # Oder für einzelne Clients
|
||||
9
ansible/wireguard-install-server.yml
Normal file
9
ansible/wireguard-install-server.yml
Normal file
@@ -0,0 +1,9 @@
|
||||
# ansible/wireguard-install-server.yml
|
||||
- hosts: vpn
|
||||
become: true
|
||||
gather_facts: true
|
||||
roles:
|
||||
- role: wireguard
|
||||
tasks_from: install # z.B., je nach Namensschema deiner Rolle
|
||||
- role: wireguard
|
||||
tasks_from: configure # Für Config/Firewall usw.
|
||||
@@ -1,3 +0,0 @@
|
||||
FROM nginx:stable-alpine
|
||||
|
||||
COPY nginx/default.conf /etc/nginx/conf.d/default.conf
|
||||
@@ -1,2 +0,0 @@
|
||||
<?php
|
||||
phpinfo();
|
||||
@@ -1,17 +0,0 @@
|
||||
server {
|
||||
listen 80;
|
||||
server_name localhost;
|
||||
|
||||
root /var/www/html;
|
||||
index index.php index.html;
|
||||
|
||||
location / {
|
||||
try_files $uri $uri/ =404;
|
||||
}
|
||||
|
||||
location ~ \.php$ {
|
||||
include fastcgi_params;
|
||||
fastcgi_pass php:9000;
|
||||
fastcgi_param SCRIPT_FILENAME /var/www/html$fastcgi_script_name;
|
||||
}
|
||||
}
|
||||
11
bin/check-env
Normal file
11
bin/check-env
Normal file
@@ -0,0 +1,11 @@
|
||||
#!/bin/sh
|
||||
if [ ! -f .env ]; then
|
||||
echo "❌ .env fehlt!"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! grep -q "APP_PORT=" .env; then
|
||||
echo "⚠️ APP_PORT nicht gesetzt"
|
||||
fi
|
||||
|
||||
# TODO In make up oder make deploy einbauen.
|
||||
4
bin/deploy
Executable file
4
bin/deploy
Executable file
@@ -0,0 +1,4 @@
|
||||
#!/bin/sh -l
|
||||
# Führt das Ansible-Deploy-Playbook aus
|
||||
|
||||
/home/michael/.local/bin/ansible-playbook -i ansible/inventory.ini ansible/playbooks/deploy.yml
|
||||
3
bin/down
Executable file
3
bin/down
Executable file
@@ -0,0 +1,3 @@
|
||||
#!/bin/sh
|
||||
# Stoppt alle laufenden Container
|
||||
docker compose down
|
||||
3
bin/logs
Executable file
3
bin/logs
Executable file
@@ -0,0 +1,3 @@
|
||||
#!/bin/sh
|
||||
# Zeigt die Live-Logs aller Container
|
||||
docker compose logs -f
|
||||
3
bin/restart
Executable file
3
bin/restart
Executable file
@@ -0,0 +1,3 @@
|
||||
#!/bin/sh
|
||||
# Stoppt und startet alle Container neu
|
||||
docker compose down && docker compose up -d
|
||||
3
bin/setup
Executable file
3
bin/setup
Executable file
@@ -0,0 +1,3 @@
|
||||
#!/bin/sh
|
||||
# Führt das Ansible-Deploy-Playbook aus
|
||||
/home/michael/.local/bin/ansible-playbook -i ansible/inventory.ini ansible/playbooks/setup.yml
|
||||
3
bin/test
Executable file
3
bin/test
Executable file
@@ -0,0 +1,3 @@
|
||||
#!/bin/sh
|
||||
# Platzhalter für Tests – kann später durch phpunit, etc. ersetzt werden
|
||||
docker compose exec php ./vendor/bin/pest # --coverage
|
||||
3
bin/up
Executable file
3
bin/up
Executable file
@@ -0,0 +1,3 @@
|
||||
#!/bin/sh
|
||||
# Startet Docker-Container im Hintergrund
|
||||
docker compose up -d
|
||||
39
composer.json
Normal file
39
composer.json
Normal file
@@ -0,0 +1,39 @@
|
||||
{
|
||||
"name": "michaelschiemer/website",
|
||||
"description": "michaelschiemer.de website",
|
||||
"type": "project",
|
||||
"license": "proprietary",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"App\\": "src/"
|
||||
}
|
||||
},
|
||||
"authors": [
|
||||
{
|
||||
"name": "Michael Schiemer",
|
||||
"email": "kontakt@michaelschiemer.de"
|
||||
}
|
||||
],
|
||||
"minimum-stability": "stable",
|
||||
"require-dev": {
|
||||
"pestphp/pest": "^3.8",
|
||||
"friendsofphp/php-cs-fixer": "^3.75"
|
||||
},
|
||||
"config": {
|
||||
"allow-plugins": {
|
||||
"pestphp/pest-plugin": true
|
||||
}
|
||||
},
|
||||
"require": {
|
||||
"predis/predis": "^3.0",
|
||||
"ext-dom": "*",
|
||||
"ext-libxml": "*",
|
||||
"ext-curl": "*"
|
||||
},
|
||||
|
||||
"scripts": {
|
||||
"cs": "php-cs-fixer fix --dry-run --diff",
|
||||
"cs-fix": "php-cs-fixer fix --allow-risky=yes || true",
|
||||
"reload": "composer dump-autoload -o"
|
||||
}
|
||||
}
|
||||
1
config/app.php
Normal file
1
config/app.php
Normal file
@@ -0,0 +1 @@
|
||||
<?php
|
||||
@@ -1,16 +1,99 @@
|
||||
version: "3.9"
|
||||
x-docker-settings: &docker-settings
|
||||
build:
|
||||
context: .
|
||||
args:
|
||||
- BUILDKIT_INLINE_CACHE=1
|
||||
|
||||
services:
|
||||
web:
|
||||
build: ./app
|
||||
build:
|
||||
context: ./docker/nginx
|
||||
dockerfile: Dockerfile
|
||||
ports:
|
||||
- "${APP_PORT}:80"
|
||||
- "${APP_PORT:-8000}:80"
|
||||
- "127.0.0.1:8080:80"
|
||||
- "${APP_SSL_PORT:-443}:443"
|
||||
environment:
|
||||
- APP_ENV=${APP_ENV:-development}
|
||||
volumes:
|
||||
- ./app/html:/var/www/html
|
||||
- ./:/var/www/html:cached
|
||||
- ./ssl:/etc/nginx/ssl:ro # SSL-Zertifikate mounten
|
||||
depends_on:
|
||||
- php
|
||||
php:
|
||||
condition: service_started
|
||||
restart: unless-stopped
|
||||
networks:
|
||||
- frontend
|
||||
- backend
|
||||
env_file:
|
||||
- .env
|
||||
|
||||
php:
|
||||
image: php:${PHP_VERSION}-fpm
|
||||
container_name: php
|
||||
build:
|
||||
context: .
|
||||
dockerfile: docker/php/Dockerfile
|
||||
args:
|
||||
- ENV=${APP_ENV:-dev}
|
||||
- COMPOSER_INSTALL_FLAGS=${COMPOSER_INSTALL_FLAGS:---no-scripts --no-autoloader}
|
||||
|
||||
volumes:
|
||||
- ./app/html:/var/www/html
|
||||
# Shared Volume für Composer-Cache über Container-Neustarts hinweg
|
||||
- composer-cache:/root/.composer/cache
|
||||
# Bindet das Projektverzeichnis für Live-Änderungen ein
|
||||
- ./:/var/www/html:cached
|
||||
# Verhindert Überschreiben der Vendor-Verzeichnisse
|
||||
#- /var/www/html/vendor
|
||||
|
||||
#- cache-volume:/var/www/html/cache:rw
|
||||
environment:
|
||||
PHP_IDE_CONFIG: "serverName=docker"
|
||||
APP_ENV: ${APP_ENV:-development}
|
||||
|
||||
healthcheck:
|
||||
test: [ "CMD", "php", "-v" ]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
|
||||
restart: unless-stopped
|
||||
networks:
|
||||
- backend
|
||||
#- cache
|
||||
# backend:
|
||||
# aliases:
|
||||
# - php
|
||||
# cache:
|
||||
env_file:
|
||||
- .env
|
||||
|
||||
redis:
|
||||
image: redis:8-alpine
|
||||
volumes:
|
||||
- ./docker/redis/redis.conf:/usr/local/etc/redis/redis.conf
|
||||
- redis_data:/data
|
||||
command: ["redis-server", "/usr/local/etc/redis/redis.conf"]
|
||||
healthcheck:
|
||||
test: ["CMD", "redis-cli", "ping"]
|
||||
interval: 30s
|
||||
timeout: 5s
|
||||
retries: 3
|
||||
|
||||
restart: unless-stopped
|
||||
networks:
|
||||
- cache
|
||||
env_file:
|
||||
- .env
|
||||
|
||||
networks:
|
||||
frontend:
|
||||
driver: bridge
|
||||
backend:
|
||||
driver: bridge
|
||||
cache:
|
||||
driver: bridge
|
||||
|
||||
volumes:
|
||||
redis_data:
|
||||
composer-cache:
|
||||
#cache-volume:
|
||||
|
||||
33
docker/nginx/Dockerfile
Normal file
33
docker/nginx/Dockerfile
Normal file
@@ -0,0 +1,33 @@
|
||||
FROM nginx:alpine
|
||||
|
||||
# Standard-Konfiguration entfernen
|
||||
RUN rm /etc/nginx/conf.d/default.conf
|
||||
|
||||
# Verzeichnisse erstellen mit korrekten Berechtigungen
|
||||
RUN mkdir -p /var/cache/nginx /var/log/nginx /etc/nginx/template && \
|
||||
chmod -R 777 /var/cache/nginx /var/log/nginx
|
||||
|
||||
# Kopiere die Template-Konfiguration
|
||||
COPY ./nginx.conf /etc/nginx/nginx.conf
|
||||
COPY ./default.conf /etc/nginx/conf.d/default.conf
|
||||
|
||||
# Kopiere die SSL-Zertifikate
|
||||
COPY ./ssl/ /etc/nginx/ssl/
|
||||
|
||||
# Startup-Skript zum Ersetzen der Variablen
|
||||
COPY ./docker-entrypoint.sh /
|
||||
RUN chmod +x /docker-entrypoint.sh
|
||||
|
||||
#Install Netcat
|
||||
RUN apk add --no-cache netcat-openbsd
|
||||
|
||||
|
||||
# Als user www-data laufen lassen
|
||||
RUN addgroup -g 1000 www && adduser -D -G www -u 1000 www-data \
|
||||
&& chown -R www-data:www /var/cache/nginx /var/log/nginx /etc/nginx
|
||||
USER www-data
|
||||
|
||||
EXPOSE 80 443
|
||||
|
||||
ENTRYPOINT ["/docker-entrypoint.sh"]
|
||||
CMD ["nginx", "-g", "daemon off;"]
|
||||
228
docker/nginx/default.conf
Normal file
228
docker/nginx/default.conf
Normal file
@@ -0,0 +1,228 @@
|
||||
# FastCGI-Cache-Einstellungen
|
||||
fastcgi_cache_path /var/cache/nginx levels=1:2 keys_zone=PHPCACHE:100m inactive=60m;
|
||||
fastcgi_cache_key "$scheme$request_method$host$request_uri";
|
||||
fastcgi_cache_use_stale error timeout invalid_header http_500;
|
||||
fastcgi_ignore_headers Cache-Control Expires Set-Cookie;
|
||||
|
||||
# Hardcoded Umgebungsmodus basierend auf Template-Ersetzung
|
||||
map $http_host $env_mode {
|
||||
default "${APP_ENV}";
|
||||
}
|
||||
|
||||
# Dynamische Cache-Kontrolle basierend auf Umgebungsvariable
|
||||
map $env_mode $should_skip_cache {
|
||||
default 0; # Standard (Produktion): Cache aktivieren
|
||||
development 1; # Entwicklung: Cache deaktivieren
|
||||
testing 1; # Testing: Cache deaktivieren
|
||||
}
|
||||
|
||||
# Skip-Cache für Sessions und basierend auf Umgebung
|
||||
map $http_cookie$should_skip_cache $skip_cache {
|
||||
"~PHPSESSID" 1; # Sessions nie cachen
|
||||
"1" 1; # Cache überspringen, wenn should_skip_cache = 1
|
||||
default 0; # Ansonsten cachen
|
||||
}
|
||||
|
||||
map $host $block_health {
|
||||
default 1; # Blockiere alles
|
||||
localhost 0; # Erlaube nur Host "localhost"
|
||||
}
|
||||
|
||||
upstream php-upstream {
|
||||
server php:9000; # „php“ ist durch Network-Alias immer erreichbar
|
||||
}
|
||||
|
||||
server {
|
||||
listen 80;
|
||||
server_name localhost;
|
||||
return 301 https://$host$request_uri;
|
||||
}
|
||||
|
||||
server {
|
||||
# Korrigierte HTTP/2 Syntax
|
||||
listen 443 ssl;
|
||||
http2 on; # Neue Syntax für HTTP/2
|
||||
server_name localhost;
|
||||
|
||||
#ssl_certificate /etc/nginx/ssl/localhost+2.pem;
|
||||
#ssl_certificate_key /etc/nginx/ssl/localhost+2-key.pem;
|
||||
ssl_certificate /etc/nginx/ssl/fullchain.pem;
|
||||
ssl_certificate_key /etc/nginx/ssl/privkey.pem;
|
||||
|
||||
|
||||
ssl_protocols TLSv1.2 TLSv1.3;
|
||||
ssl_prefer_server_ciphers on;
|
||||
ssl_ciphers ECDHE-RSA-AES256-GCM-SHA512:DHE-RSA-AES256-GCM-SHA512:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES256-GCM-SHA384;
|
||||
|
||||
# Verbesserte SSL-Konfiguration
|
||||
ssl_session_timeout 1d;
|
||||
ssl_session_cache shared:SSL:10m;
|
||||
ssl_session_tickets off;
|
||||
|
||||
# OCSP Stapling (auskommentiert, wenn Zertifikate fehlen)
|
||||
# ssl_stapling on;
|
||||
# ssl_stapling_verify on;
|
||||
resolver 1.1.1.1 1.0.0.1 valid=300s;
|
||||
resolver_timeout 5s;
|
||||
|
||||
root /var/www/html/public;
|
||||
index index.php index.html;
|
||||
|
||||
# Debug-Header für die Entwicklung
|
||||
add_header X-Environment $env_mode always;
|
||||
|
||||
# Sicherheits-Header
|
||||
add_header X-Content-Type-Options nosniff;
|
||||
add_header X-Frame-Options DENY always;
|
||||
add_header X-XSS-Protection "1; mode=block" always;
|
||||
add_header Referrer-Policy "no-referrer-when-downgrade" always;
|
||||
add_header Permissions-Policy "geolocation=(), microphone=()" always;
|
||||
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains" always;
|
||||
|
||||
# CSP Header
|
||||
add_header Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self'; object-src 'none'" always;
|
||||
|
||||
# Buffer-Größen anpassen
|
||||
client_body_buffer_size 10K;
|
||||
client_header_buffer_size 1k;
|
||||
client_max_body_size 10m;
|
||||
large_client_header_buffers 2 1k;
|
||||
|
||||
# Verbesserte Gzip-Kompression
|
||||
gzip on;
|
||||
gzip_vary on;
|
||||
gzip_proxied any;
|
||||
gzip_comp_level 6;
|
||||
gzip_buffers 16 8k;
|
||||
gzip_http_version 1.1;
|
||||
gzip_min_length 256;
|
||||
gzip_types
|
||||
application/atom+xml
|
||||
application/javascript
|
||||
application/json
|
||||
application/ld+json
|
||||
application/manifest+json
|
||||
application/rss+xml
|
||||
application/vnd.geo+json
|
||||
application/vnd.ms-fontobject
|
||||
application/x-font-ttf
|
||||
application/x-web-app-manifest+json
|
||||
application/xhtml+xml
|
||||
application/xml
|
||||
font/opentype
|
||||
image/bmp
|
||||
image/svg+xml
|
||||
image/x-icon
|
||||
text/cache-manifest
|
||||
text/css
|
||||
text/plain
|
||||
text/vcard
|
||||
text/vnd.rim.location.xloc
|
||||
text/vtt
|
||||
text/x-component
|
||||
text/x-cross-domain-policy;
|
||||
|
||||
# Logs
|
||||
access_log /var/log/nginx/access.log combined;
|
||||
error_log /var/log/nginx/error.log error;
|
||||
|
||||
location / {
|
||||
try_files $uri $uri/ /index.php?$query_string;
|
||||
autoindex off;
|
||||
}
|
||||
|
||||
# Caching Header für statische Dateien
|
||||
location ~* \.(jpg|jpeg|png|gif|ico|svg|woff|woff2|ttf|eot)$ {
|
||||
expires 1y;
|
||||
add_header Cache-Control "public, immutable, max-age=31536000";
|
||||
}
|
||||
|
||||
location ~* \.(css|js)$ {
|
||||
expires 1w;
|
||||
add_header Cache-Control "public, max-age=604800";
|
||||
}
|
||||
|
||||
location ~* \.(json|xml)$ {
|
||||
expires 1d;
|
||||
add_header Cache-Control "public, max-age=86400";
|
||||
}
|
||||
|
||||
location ~ \.php$ {
|
||||
try_files $uri =404;
|
||||
include fastcgi_params;
|
||||
fastcgi_pass php-upstream;
|
||||
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
|
||||
fastcgi_param PATH_INFO $fastcgi_path_info;
|
||||
fastcgi_split_path_info ^(.+\.php)(/.+)$;
|
||||
|
||||
# Wichtig: APP_ENV an PHP weitergeben
|
||||
fastcgi_param APP_ENV $env_mode;
|
||||
|
||||
# Timeout-Einstellungen
|
||||
fastcgi_read_timeout 60s;
|
||||
fastcgi_connect_timeout 60s;
|
||||
fastcgi_send_timeout 60s;
|
||||
|
||||
# Caching-Einstellungen
|
||||
fastcgi_buffer_size 128k;
|
||||
fastcgi_buffers 4 256k;
|
||||
fastcgi_busy_buffers_size 256k;
|
||||
|
||||
# Cache FastCGI-Antworten
|
||||
fastcgi_cache_bypass $skip_cache;
|
||||
fastcgi_no_cache $skip_cache;
|
||||
|
||||
fastcgi_cache PHPCACHE;
|
||||
fastcgi_cache_valid 200 60m;
|
||||
|
||||
# Debug-Header hinzufügen
|
||||
add_header X-Cache-Status $upstream_cache_status;
|
||||
add_header X-Cache-Environment $env_mode;
|
||||
add_header X-Cache-Skip $skip_cache;
|
||||
|
||||
# Für bessere Performance
|
||||
fastcgi_keep_conn on;
|
||||
}
|
||||
|
||||
# Sicherheitseinstellungen
|
||||
location ~ /\.(?!well-known).* {
|
||||
deny all;
|
||||
}
|
||||
|
||||
server_tokens off;
|
||||
limit_req zone=mylimit burst=20 nodelay;
|
||||
|
||||
location ~* /(?:uploads|files)/.*\.php$ {
|
||||
deny all;
|
||||
}
|
||||
|
||||
# Healthcheck-Endpunkt
|
||||
location = /ping {
|
||||
access_log off;
|
||||
add_header Content-Type text/plain;
|
||||
return 200 'pong';
|
||||
}
|
||||
|
||||
location = /health {
|
||||
if ($block_health) {
|
||||
return 404;
|
||||
}
|
||||
|
||||
try_files /health.php =404;
|
||||
|
||||
allow 127.0.0.1; # Lokal erlaubt (Ansible, Docker, Monitoring intern)
|
||||
allow ::1;
|
||||
allow 192.168.0.0/16; # Optional: internes Netz (z.B. für internen Loadbalancer)
|
||||
deny all;
|
||||
error_page 403 =404;
|
||||
}
|
||||
|
||||
|
||||
error_page 404 /errors/404.html;
|
||||
error_page 403 /errors/403.html;
|
||||
error_page 500 502 503 504 /errors/50x.html;
|
||||
|
||||
location /errors/ {
|
||||
internal; # Verhindert direkten Zugriff
|
||||
}
|
||||
}
|
||||
20
docker/nginx/docker-entrypoint.sh
Normal file
20
docker/nginx/docker-entrypoint.sh
Normal file
@@ -0,0 +1,20 @@
|
||||
#!/bin/sh
|
||||
set -e
|
||||
|
||||
until nc -z -w 2 php 9000; do
|
||||
echo "Warte auf PHP-FPM..."
|
||||
sleep 1
|
||||
done
|
||||
|
||||
|
||||
# Optional: eigene Umgebungsvariable mit Default setzen
|
||||
export APP_ENV="${APP_ENV:-production}"
|
||||
|
||||
echo "Starte Nginx mit APP_ENV=$APP_ENV"
|
||||
|
||||
# Ersetze Umgebungsvariablen wie ${APP_ENV} in der Nginx-Config per envsubst
|
||||
envsubst '${APP_ENV}' < /etc/nginx/conf.d/default.conf > /etc/nginx/conf.d/default.conf.tmp
|
||||
mv /etc/nginx/conf.d/default.conf.tmp /etc/nginx/conf.d/default.conf
|
||||
|
||||
# Starte Nginx (Foreground)
|
||||
exec nginx -g 'daemon off;'
|
||||
37
docker/nginx/nginx.conf
Normal file
37
docker/nginx/nginx.conf
Normal file
@@ -0,0 +1,37 @@
|
||||
worker_processes auto;
|
||||
pid /tmp/nginx.pid;
|
||||
|
||||
events {
|
||||
worker_connections 1024;
|
||||
}
|
||||
|
||||
http {
|
||||
include /etc/nginx/mime.types;
|
||||
default_type application/octet-stream;
|
||||
|
||||
server_tokens off;
|
||||
|
||||
# Rate-Limiting für besseren DDoS-Schutz
|
||||
limit_req_zone $binary_remote_addr zone=mylimit:10m rate=10r/s;
|
||||
|
||||
# Logging-Einstellungen
|
||||
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
|
||||
'$status $body_bytes_sent "$http_referer" '
|
||||
'"$http_user_agent" "$http_x_forwarded_for"';
|
||||
|
||||
access_log /var/log/nginx/access.log main;
|
||||
error_log /var/log/nginx/error.log warn;
|
||||
|
||||
sendfile on;
|
||||
tcp_nopush on;
|
||||
tcp_nodelay on;
|
||||
keepalive_timeout 65;
|
||||
types_hash_max_size 2048;
|
||||
|
||||
# TLS-Einstellungen
|
||||
ssl_session_cache shared:SSL:10m;
|
||||
ssl_session_timeout 10m;
|
||||
|
||||
# Include server configs
|
||||
include /etc/nginx/conf.d/*.conf;
|
||||
}
|
||||
28
docker/nginx/ssl/localhost+2-key.pem
Normal file
28
docker/nginx/ssl/localhost+2-key.pem
Normal file
@@ -0,0 +1,28 @@
|
||||
-----BEGIN PRIVATE KEY-----
|
||||
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDAzwS8FGSCDwDg
|
||||
7QX8OpGkX1SbSwbUyzXNjEta319BvAH2OfcFFCj6u/iqfL7gKOM83t8u71VBFsCx
|
||||
ZlxX2Ilyu2+r72sCdGBXcK6riTHrkjTs4uV6YV98eJuYhvAzSijpsRQjwnwQ587c
|
||||
axtCXZhOzee3Tnbtzq4plqmOKR10D+cvrOZxuoKI914blXpGe8ds3vWEixewrex0
|
||||
CYhzPj/zEF3yfCoSXeTmFBUbmmH/JwcCK8uO5t6XR1Dyo3M4GOMrmGtO7U4nuL6e
|
||||
7JsbZfPaEW9wKtDjEwFDJSLy0ALEpiNWvbW4OaZWNkJk0jfKYwyBunNSs62B4307
|
||||
oF8lqVo1AgMBAAECggEAbPlU0ryv5fZ256nvlRTBVmbvGep4zPKh0TA3MwBHBY8u
|
||||
iK1QWVWAp95v+GQTOfzCGphZCl0JEYW7mUiibqAbZ3Za8pGaKMP/48vzXU5ooZ18
|
||||
PlsrmlTItEAyqS2zOznyD8se9+snViK+f0QmHwdpWzjze15kx5nmQ+k8ofXJCNwq
|
||||
q3dJIMI/WNuc0e/mMHYjZBsIwuoUi6YJHCE6RkWhGcnvlyXdKUV73/n8Loy6DUtW
|
||||
VmshXag7+GfbVZIesMCjfnJ0gr9OG+XrFl6AcggzFA1ZHRoQliraVYGB2duQlIpW
|
||||
o1wJMhFSGFPZxvl67hwXHJeo7ghHHfqNYXS1OuhV7QKBgQDBrvyzLtav51LzqOUY
|
||||
2HPvaH86arbARc4Fy6ZJ0TaSlmKQ5GzRG0lG2CR03oZz+OcMV/BU8xUMM7CX0zUq
|
||||
9RAmbE7rvXYOvqTe8pcdHeKKflzsr5p0HNROaeZdpMu8xoK1KLelAo6UCEBUGEny
|
||||
oMtQWapuYvmdlHR2el2ICRGNzwKBgQD+1/iM1LcF9CYvEc8Sly9XuoRsdUCxavQa
|
||||
sssv7eG5kkL8HroNs1pGZU8lNuZaT1V0ekWVOFk+X3+dGgCXg5/e/CluK9K7qOHX
|
||||
3IkyUnZLEH5sDXGMGBzYA9AQTaB1PMTQYku6GNWYab6LFQTvpvvLcIILaFHokq8p
|
||||
D/dGVJH8uwKBgQCBOxDBPe9hTye6DGdQPJyekUrS34EwqWLd2xQJDN8sz8rUgpVY
|
||||
sKwj6PPqRs/PcbQ4ODTTeZ4BljuuEe7XyswL1xiRksjC7dF0MMlDVD1jywyVoFWe
|
||||
Q94ks+RRdzO5sXplBdYC88HOY/MIKWytxzvhUPK21LNYwUU0CFGAAw0DYQKBgQD4
|
||||
mT/qSdscoLXa9tl0fiz9vIJPtvXb3MSxgra5U6n9t9NGVMcUdGBdCZjyaaK+eGOZ
|
||||
U2mrjiNouAop++KV6x26jWvxACj7TVy6kXT4tP6WbUmWKGsaya7hfp6qOL+NfjFU
|
||||
Qn8y0+URYB4zWNbO3asFIwSJEkPMx8K9IMkMP5WF3wKBgCYiqAhPDF4WxA3fAqP7
|
||||
95px8Clrety0mwOtE/rMQRf1nKJ78oA4pr+/VXRbyghAxtD4psbmBQofX3iwnn3B
|
||||
o1DV3FLpNw004mvcKGScUcNwHQtWAtWX2nVDcxes5R2DgN+lpmWmf5Tq47p0r5ZP
|
||||
nRb92drrnf8FoBv78CxLjIu+
|
||||
-----END PRIVATE KEY-----
|
||||
25
docker/nginx/ssl/localhost+2.pem
Normal file
25
docker/nginx/ssl/localhost+2.pem
Normal file
@@ -0,0 +1,25 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIEHjCCAoagAwIBAgIQLqhFNHvvWJKUpuypArU2CjANBgkqhkiG9w0BAQsFADBb
|
||||
MR4wHAYDVQQKExVta2NlcnQgZGV2ZWxvcG1lbnQgQ0ExGDAWBgNVBAsMD21pY2hh
|
||||
ZWxATWlrZS1QQzEfMB0GA1UEAwwWbWtjZXJ0IG1pY2hhZWxATWlrZS1QQzAeFw0y
|
||||
NTA1MTgxOTUyMDlaFw0yNzA4MTgxOTUyMDlaMEMxJzAlBgNVBAoTHm1rY2VydCBk
|
||||
ZXZlbG9wbWVudCBjZXJ0aWZpY2F0ZTEYMBYGA1UECwwPbWljaGFlbEBNaWtlLVBD
|
||||
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwM8EvBRkgg8A4O0F/DqR
|
||||
pF9Um0sG1Ms1zYxLWt9fQbwB9jn3BRQo+rv4qny+4CjjPN7fLu9VQRbAsWZcV9iJ
|
||||
crtvq+9rAnRgV3Cuq4kx65I07OLlemFffHibmIbwM0oo6bEUI8J8EOfO3GsbQl2Y
|
||||
Ts3nt0527c6uKZapjikddA/nL6zmcbqCiPdeG5V6RnvHbN71hIsXsK3sdAmIcz4/
|
||||
8xBd8nwqEl3k5hQVG5ph/ycHAivLjubel0dQ8qNzOBjjK5hrTu1OJ7i+nuybG2Xz
|
||||
2hFvcCrQ4xMBQyUi8tACxKYjVr21uDmmVjZCZNI3ymMMgbpzUrOtgeN9O6BfJala
|
||||
NQIDAQABo3YwdDAOBgNVHQ8BAf8EBAMCBaAwEwYDVR0lBAwwCgYIKwYBBQUHAwEw
|
||||
HwYDVR0jBBgwFoAUhhzxUvThIGRX4MSoX91Vzm1zZ9AwLAYDVR0RBCUwI4IJbG9j
|
||||
YWxob3N0hwR/AAABhxAAAAAAAAAAAAAAAAAAAAABMA0GCSqGSIb3DQEBCwUAA4IB
|
||||
gQDUFLYZPo8RrfZh/vwT15LcIce8brdVegms6DvPK9lMZX6C4sGf4+rTJCwPuqHW
|
||||
dqVZAhHdvcsyGI15xvVPT4qSh89RN1JB9uIHCk+weIzp+Rn06MMrB49m4abAvWp2
|
||||
hB8bCo80hMVIsCb3Wr9sHg7CsJItsdGz8jHYCvHpvPLR7gWhYjm1g0meglT3tZqd
|
||||
TsKDMb3Vj/vsivEueM6Oj/of8xbamVSSkqljWbRls7Ti7xqXMbmf7nl0WvG9IXg3
|
||||
5Ucv1AWJIFEeLnMM5V0nEbO3sAhbNMLXieGPBWHXOgHuvVnQyu1mBESjgc5bjwfN
|
||||
UjYBHluFkF9aYw3mGcFqAlb1FpGoMtHwTw0uGZzHzj5FY8oZix5edq/upriV6cU2
|
||||
t0tidlfhvkJNSSO4zjAPjU1wd+/QRZwY2PcB5kBxs5MzSmiMlEjTkGgHWqMWMBf1
|
||||
NPbyaxtjL69xBVonxpqD6BLJ2qLatgCs6fkZZF7AT38OFXr8Cv5vxt1rR5fs1P6X
|
||||
mI0=
|
||||
-----END CERTIFICATE-----
|
||||
69
docker/php/Dockerfile
Normal file
69
docker/php/Dockerfile
Normal file
@@ -0,0 +1,69 @@
|
||||
# Dockerfile für PHP-FPM
|
||||
FROM php:8.4-fpm AS base
|
||||
|
||||
# System-Abhängigkeiten: Werden selten geändert, daher ein eigener Layer
|
||||
RUN apt-get update && apt-get install -y \
|
||||
git \
|
||||
unzip \
|
||||
libzip-dev \
|
||||
zip \
|
||||
&& docker-php-ext-install zip pdo pdo_mysql \
|
||||
&& docker-php-ext-install opcache \
|
||||
&& apt-get clean \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Composer installieren
|
||||
RUN curl -sS https://getcomposer.org/installer | php \
|
||||
&& mv composer.phar /usr/local/bin/composer
|
||||
|
||||
# Installiere Xdebug nur im Entwicklungsmodus
|
||||
ARG ENV=prod
|
||||
RUN if [ "$ENV" = "dev" ]; then \
|
||||
pecl install xdebug \
|
||||
&& docker-php-ext-enable xdebug; \
|
||||
fi
|
||||
|
||||
WORKDIR /var/www/html
|
||||
|
||||
# Kopiere zuerst nur composer.json/lock für besseres Layer-Caching
|
||||
COPY composer.json composer.lock ./
|
||||
|
||||
# Installiere Abhängigkeiten - variiert je nach Umgebung
|
||||
RUN if [ "$ENV" = "prod" ]; then \
|
||||
composer install --no-dev --no-scripts --no-autoloader --optimize-autoloader; \
|
||||
else \
|
||||
composer install --no-scripts --no-autoloader; \
|
||||
fi
|
||||
|
||||
# Kopiere PHP-Konfigurationen
|
||||
COPY docker/php/php.common.ini /usr/local/etc/php/php.common.ini
|
||||
COPY docker/php/php.${ENV}.ini /usr/local/etc/php/php.ini
|
||||
|
||||
# Wenn dev, kopiere auch xdebug-Konfiguration
|
||||
RUN if [ "$ENV" = "dev" ]; then \
|
||||
mkdir -p /usr/local/etc/php/conf.d/; \
|
||||
fi
|
||||
COPY docker/php/xdebug.ini /usr/local/etc/php/conf.d/docker-php-ext-xdebug.ini
|
||||
|
||||
# Kopiere den Rest des Projekts
|
||||
COPY . .
|
||||
|
||||
# Optimiere Autoloader
|
||||
RUN composer dump-autoload --optimize
|
||||
|
||||
# <<--- ALLE zusätzlichen System-Dateien und chmod noch als root!
|
||||
COPY docker/php/docker-entrypoint.sh /usr/local/bin/docker-entrypoint.sh
|
||||
RUN chmod +x /usr/local/bin/docker-entrypoint.sh
|
||||
|
||||
# Danach erst den Nutzer wechseln!
|
||||
RUN groupadd -g 1000 appuser && useradd -u 1000 -g appuser -m appuser
|
||||
RUN chown -R appuser:appuser /var/www/html
|
||||
|
||||
USER appuser
|
||||
|
||||
RUN mkdir -p /var/www/html/cache && \
|
||||
chown -R 1000:1000 /var/www/html/cache && \
|
||||
chmod -R 775 /var/www/html/cache
|
||||
|
||||
ENTRYPOINT ["/usr/local/bin/docker-entrypoint.sh"]
|
||||
CMD ["php-fpm"]
|
||||
4
docker/php/docker-entrypoint.sh
Normal file
4
docker/php/docker-entrypoint.sh
Normal file
@@ -0,0 +1,4 @@
|
||||
#!/bin/bash
|
||||
chown -R www-data:www-data /var/www/html/cache
|
||||
chmod -R 775 /var/www/html/cache
|
||||
exec "$@"
|
||||
8
docker/php/php.common.ini
Normal file
8
docker/php/php.common.ini
Normal file
@@ -0,0 +1,8 @@
|
||||
expose_php = Off
|
||||
|
||||
session.cookie_secure = 1
|
||||
session.cookie_httponly = 1
|
||||
session.cookie_samesite = Lax
|
||||
|
||||
|
||||
date.timezone = Europe/Berlin
|
||||
29
docker/php/php.development.ini
Normal file
29
docker/php/php.development.ini
Normal file
@@ -0,0 +1,29 @@
|
||||
; php.ini für Entwicklung
|
||||
include = php.common.ini
|
||||
|
||||
|
||||
[opcache]
|
||||
opcache.enable=1
|
||||
opcache.enable_cli=0
|
||||
opcache.memory_consumption=128
|
||||
opcache.max_accelerated_files=10000
|
||||
; Häufigere Validierung im Dev-Modus
|
||||
opcache.revalidate_freq=0
|
||||
; Timestamps-Validierung einschalten für Entwicklung
|
||||
opcache.validate_timestamps=1
|
||||
|
||||
opcache.file_cache=
|
||||
realpath_cache_ttl=0
|
||||
|
||||
opcache.interned_strings_buffer=16
|
||||
|
||||
|
||||
display_errors = On
|
||||
display_startup_errors = On
|
||||
error_reporting = E_ALL
|
||||
memory_limit = 512M
|
||||
upload_max_filesize = 20M
|
||||
post_max_size = 25M
|
||||
max_execution_time = 60
|
||||
|
||||
; Xdebug-Einstellungen können auch hier hinzugefügt werden, falls gewünscht
|
||||
31
docker/php/php.prod.ini
Normal file
31
docker/php/php.prod.ini
Normal file
@@ -0,0 +1,31 @@
|
||||
; php.ini für Produktion
|
||||
include = php.common.ini
|
||||
|
||||
[opcache]
|
||||
; Aktiviere OPcache
|
||||
opcache.enable=1
|
||||
; Aktiviere OPcache für CLI-Anwendungen (optional)
|
||||
opcache.enable_cli=0
|
||||
; Maximale Speichernutzung für Cache in MB
|
||||
opcache.memory_consumption=128
|
||||
; Maximale Anzahl an gecachten Dateien
|
||||
opcache.max_accelerated_files=10000
|
||||
; Wie oft wird der Cache validiert (0 = bei jedem Request, empfohlen für Entwicklung)
|
||||
; In Produktion höher setzen für bessere Performance
|
||||
opcache.revalidate_freq=60
|
||||
; Cache-Zeitstempel prüfen (0 für Produktionsumgebungen)
|
||||
opcache.validate_timestamps=0
|
||||
; Performance-Optimierungen
|
||||
opcache.interned_strings_buffer=16
|
||||
; JIT (Just-In-Time Compilation) - Optional für PHP 8.0+
|
||||
opcache.jit_buffer_size=100M
|
||||
opcache.jit=1255
|
||||
|
||||
|
||||
display_errors = Off
|
||||
display_startup_errors = Off
|
||||
error_reporting = E_ALL & ~E_DEPRECATED & ~E_STRICT
|
||||
memory_limit = 256M
|
||||
upload_max_filesize = 10M
|
||||
post_max_size = 12M
|
||||
max_execution_time = 30
|
||||
7
docker/php/xdebug.ini
Normal file
7
docker/php/xdebug.ini
Normal file
@@ -0,0 +1,7 @@
|
||||
; Xdebug 3 Konfiguration
|
||||
xdebug.mode=${XDEBUG_MODE:-off}
|
||||
xdebug.client_host=host.docker.internal
|
||||
xdebug.client_port=9003
|
||||
xdebug.start_with_request=yes
|
||||
xdebug.log=/var/log/xdebug.log
|
||||
xdebug.idekey=PHPSTORM
|
||||
7
docker/redis/redis.conf
Normal file
7
docker/redis/redis.conf
Normal file
@@ -0,0 +1,7 @@
|
||||
bind 0.0.0.0
|
||||
#protected-mode yes
|
||||
dir /data
|
||||
save 900 1
|
||||
save 300 10
|
||||
save 60 10000
|
||||
appendonly yes
|
||||
25
docs/ARCHITECURE.md
Normal file
25
docs/ARCHITECURE.md
Normal file
@@ -0,0 +1,25 @@
|
||||
# Architektur-Prinzipien
|
||||
|
||||
Dieses Dokument beschreibt die grundlegenden Architekturprinzipien unseres Frameworks.
|
||||
|
||||
## 1. Immutabilität und Unveränderlichkeit
|
||||
|
||||
Wo immer möglich, sollten Objekte unveränderlich (immutable) sein. Dies verbessert die Voraussagbarkeit und Testbarkeit.
|
||||
|
||||
## 2. Final by Default
|
||||
|
||||
Alle Klassen sollten standardmäßig als `final` deklariert werden, es sei denn, es gibt einen konkreten Grund für Vererbung.
|
||||
Begründung:
|
||||
- Vermeidet unbeabsichtigte Vererbungshierarchien
|
||||
- Verbessert die Kapselung
|
||||
- Ermöglicht interne Änderungen, ohne Kinderklassen zu beeinflussen
|
||||
|
||||
## 3. Explizite über Implizite
|
||||
|
||||
- Alle Abhängigkeiten sollten explizit injiziert werden
|
||||
- Keine globalen Zustände oder Singletons
|
||||
- Typen immer explizit deklarieren
|
||||
|
||||
## 4. Modularität
|
||||
|
||||
Jedes Modul sollte in sich geschlossen sein und minimale Abhängigkeiten nach außen haben.
|
||||
@@ -8,7 +8,7 @@ Dieses Projekt verwendet `.env`-Dateien zur Konfiguration von Docker Compose und
|
||||
|
||||
```env
|
||||
COMPOSE_PROJECT_NAME=michaelschiemer
|
||||
APP_PORT=8080
|
||||
APP_PORT=8000
|
||||
PHP_VERSION=8.2
|
||||
```
|
||||
|
||||
|
||||
42
docs/features.md
Normal file
42
docs/features.md
Normal file
@@ -0,0 +1,42 @@
|
||||
# Framework Features
|
||||
|
||||
## Core Features
|
||||
- [ ] Routing mit Unterstützung für PHP-Attribute
|
||||
- [ ] Dependency Injection Container
|
||||
- [ ] Request/Response Abstraktion
|
||||
- [ ] Template-Engine
|
||||
- [ ] Error/Exception Handling
|
||||
- [ ] Konfigurationssystem
|
||||
|
||||
## Database Features
|
||||
- [ ] PDO-Wrapper
|
||||
- [ ] Query Builder
|
||||
- [ ] Migrations-System
|
||||
- [ ] Schema Manager
|
||||
- [ ] Entity-Mapping (optional)
|
||||
|
||||
## Security Features
|
||||
- [ ] CSRF-Schutz
|
||||
- [ ] XSS-Filtierung
|
||||
- [ ] Input-Validierung
|
||||
- [ ] Authentifizierung
|
||||
- [ ] Autorisierung/Rechtemanagement
|
||||
|
||||
## Module: Music
|
||||
- [ ] Album-Verwaltung
|
||||
- [ ] Track-Management
|
||||
- [ ] Playlists
|
||||
- [ ] Integrationsmöglichkeit mit Spotify/SoundCloud
|
||||
|
||||
## Module: Content
|
||||
- [ ] Blog-System
|
||||
- [ ] Markdown-Support
|
||||
- [ ] Medienbibliothek
|
||||
- [ ] SEO-Optimierung
|
||||
- [ ] Kommentarsystem
|
||||
|
||||
## Admin Interface
|
||||
- [ ] Dashboard
|
||||
- [ ] Content-Editor
|
||||
- [ ] Benutzer-/Rechteverwaltung
|
||||
- [ ] Statistiken
|
||||
13
package.json
Normal file
13
package.json
Normal file
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"devDependencies": {
|
||||
"@types/jest": "^29.2.5",
|
||||
"jest": "^29.3.1",
|
||||
"vite": "^6.3.5"
|
||||
},
|
||||
"scripts": {
|
||||
"test": "jest",
|
||||
"dev": "vite",
|
||||
"build": "vite build",
|
||||
"preview": "vite preview"
|
||||
}
|
||||
}
|
||||
18
phpunit.xml
Normal file
18
phpunit.xml
Normal file
@@ -0,0 +1,18 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:noNamespaceSchemaLocation="vendor/phpunit/phpunit/phpunit.xsd"
|
||||
bootstrap="vendor/autoload.php"
|
||||
colors="true"
|
||||
>
|
||||
<testsuites>
|
||||
<testsuite name="Test Suite">
|
||||
<directory suffix="Test.php">./tests</directory>
|
||||
</testsuite>
|
||||
</testsuites>
|
||||
<source>
|
||||
<include>
|
||||
<directory>app</directory>
|
||||
<directory>src</directory>
|
||||
</include>
|
||||
</source>
|
||||
</phpunit>
|
||||
1
public/assets/css-CKd28aW2.js
Normal file
1
public/assets/css-CKd28aW2.js
Normal file
@@ -0,0 +1 @@
|
||||
|
||||
1
public/assets/css-CLfz37Tz.css
Normal file
1
public/assets/css-CLfz37Tz.css
Normal file
@@ -0,0 +1 @@
|
||||
p{color:red}html{background:#00f}
|
||||
12
public/assets/css/styles.css
Normal file
12
public/assets/css/styles.css
Normal file
@@ -0,0 +1,12 @@
|
||||
* {
|
||||
|
||||
}
|
||||
|
||||
|
||||
footer > nav {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
footer > nav > li {
|
||||
flex-direction: row;
|
||||
}
|
||||
1
public/assets/js-DjO_n7Y6.js
Normal file
1
public/assets/js-DjO_n7Y6.js
Normal file
@@ -0,0 +1 @@
|
||||
import"./css-CKd28aW2.js";
|
||||
0
public/favico.ico
Normal file
0
public/favico.ico
Normal file
7
public/health.php
Normal file
7
public/health.php
Normal file
@@ -0,0 +1,7 @@
|
||||
<?php
|
||||
|
||||
header('Content-Type: application/json');
|
||||
|
||||
$status = ['status' => 'ok'];
|
||||
|
||||
echo json_encode($status);
|
||||
84
public/index.php
Normal file
84
public/index.php
Normal file
@@ -0,0 +1,84 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use App\Framework\Core\Discovery;
|
||||
use App\Framework\Core\DynamicRoute;
|
||||
use App\Framework\Core\PhpObjectExporter;
|
||||
use App\Framework\Core\RouteCache;
|
||||
use App\Framework\Core\RouteMapper;
|
||||
use App\Framework\Core\StaticRoute;
|
||||
use App\Framework\ErrorHandling\ErrorHandler;
|
||||
use App\Framework\Http\HttpMethod;
|
||||
use App\Framework\Router\RouteCollection;
|
||||
|
||||
require __DIR__ . '/../vendor/autoload.php';
|
||||
|
||||
// Fehleranzeige für die Entwicklung aktivieren
|
||||
error_reporting(E_ALL);
|
||||
ini_set('display_errors', 1);
|
||||
|
||||
$rfl = new ReflectionClass(Discovery::class);;
|
||||
$ghost = $rfl->newLazyGhost(function (Discovery $object) {
|
||||
// Initialize object in-place
|
||||
$object->__construct();
|
||||
});
|
||||
|
||||
|
||||
/*$clientrequest = new \App\Framework\HttpClient\ClientRequest(HttpMethod::GET, 'https://jsonplaceholder.typicode.com/posts');
|
||||
|
||||
$client = new \App\Framework\HttpClient\CurlHttpClient();
|
||||
|
||||
var_dump($client->send($clientrequest));*/
|
||||
|
||||
$emitter = new \App\Framework\Http\ResponseEmitter();
|
||||
ErrorHandler::register($emitter);
|
||||
|
||||
#echo dirname(__DIR__) . '/cache/routes.cache.php';
|
||||
|
||||
$discovery = new Discovery(new \App\Framework\Core\RouteMapper());
|
||||
|
||||
$results = $discovery->discover(__DIR__ . '/../src/Application/');
|
||||
|
||||
|
||||
$rc = new \App\Framework\Core\RouteCompiler();
|
||||
|
||||
$routes = $rc->compile($results[\App\Framework\Attributes\Route::class]);
|
||||
|
||||
$cacheFile = dirname(__DIR__) . '/cache/routes.cache.php';
|
||||
|
||||
$routeCache = new \App\Framework\Core\RouteCache($cacheFile);
|
||||
|
||||
$routeCache->save($routes);
|
||||
|
||||
$request = new \App\Framework\Http\HttpRequest(
|
||||
method: \App\Framework\Http\HttpMethod::tryFrom($_SERVER['REQUEST_METHOD'] ?? 'GET'),
|
||||
path:parse_url( $_SERVER['REQUEST_URI'] ?? '/', PHP_URL_PATH),
|
||||
);
|
||||
|
||||
#var_dump("<pre>", $routeCache->load());
|
||||
|
||||
$router = new \App\Framework\Router\HttpRouter(new RouteCollection($routeCache->load()));
|
||||
|
||||
$match = $router->match($request->method->value, $request->path);
|
||||
|
||||
|
||||
$dispatcher = new \App\Framework\Router\RouteDispatcher();
|
||||
$return = $dispatcher->dispatch($match);
|
||||
|
||||
$responder = new \App\Framework\Router\RouteResponder();
|
||||
$response = $responder->respond($return);
|
||||
|
||||
$emitter = new \App\Framework\Http\ResponseEmitter();
|
||||
$emitter->emit($response);
|
||||
|
||||
/*$redis = new Predis\Client([
|
||||
'scheme' => 'tcp',
|
||||
'host' => 'redis', // Service-Name aus docker-compose
|
||||
'port' => 6379,
|
||||
]);
|
||||
|
||||
$redis->set('hello', 'world');
|
||||
echo $redis->get('hello'); // Gibt: world aus*/
|
||||
|
||||
exit;
|
||||
7
resources/css/styles.css
Normal file
7
resources/css/styles.css
Normal file
@@ -0,0 +1,7 @@
|
||||
p {
|
||||
color: red;
|
||||
}
|
||||
|
||||
html {
|
||||
background: blue;
|
||||
}
|
||||
1
resources/js/main.js
Normal file
1
resources/js/main.js
Normal file
@@ -0,0 +1 @@
|
||||
import '../css/styles.css';
|
||||
21
src/Application/Api/IrkEndpoint.php
Normal file
21
src/Application/Api/IrkEndpoint.php
Normal file
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
namespace App\Application\Api;
|
||||
|
||||
use App\Framework\Attributes\Route;
|
||||
use App\Framework\Router\ActionResult;
|
||||
|
||||
class IrkEndpoint
|
||||
{
|
||||
#[Route(method: 'GET', path: '/irk-impressum')]
|
||||
public function impressum()
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
#[Route(method: 'GET', path: '/irk-datenschutz')]
|
||||
public function datenschutz()
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
16
src/Application/EPK/ShowEpk.php
Normal file
16
src/Application/EPK/ShowEpk.php
Normal file
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
namespace App\Application\EPK;
|
||||
|
||||
use App\Framework\Attributes\Route;
|
||||
use App\Framework\Router\ActionResult;
|
||||
use App\Framework\Router\ResultType;
|
||||
|
||||
class ShowEpk
|
||||
{
|
||||
#[Route(path: '/epk')]
|
||||
public function epk(): ActionResult
|
||||
{
|
||||
return new ActionResult(ResultType::Html, 'epk', ['text' => 'EPK!']);
|
||||
}
|
||||
}
|
||||
33
src/Application/Website/ShowHome.php
Normal file
33
src/Application/Website/ShowHome.php
Normal file
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Application\Website;
|
||||
|
||||
use App\Framework\Attributes\Route;
|
||||
use App\Framework\Http\Request;
|
||||
use App\Framework\Router\ActionResult;
|
||||
use App\Framework\Router\ResultType;
|
||||
|
||||
class ShowHome
|
||||
{
|
||||
#[Route(method: 'GET', path: '/')]
|
||||
public function __invoke(): ActionResult
|
||||
{
|
||||
return new ActionResult(
|
||||
ResultType::Html,
|
||||
'test',
|
||||
['name' => 'Michael','title' => 'HalloWeltTitel'],
|
||||
);
|
||||
}
|
||||
|
||||
#[Route(method: 'GET', path: '/epk')]
|
||||
public function impressum(string $test = 'hallo'): ActionResult
|
||||
{
|
||||
return new ActionResult(
|
||||
ResultType::Plain,
|
||||
'test',
|
||||
['text' => 'EPK!'],
|
||||
);
|
||||
}
|
||||
}
|
||||
32
src/Application/Website/ShowImpressum.php
Normal file
32
src/Application/Website/ShowImpressum.php
Normal file
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Application\Website;
|
||||
|
||||
use App\Framework\Attributes\Route;
|
||||
use App\Framework\Router\ActionResult;
|
||||
use App\Framework\Router\ResultType;
|
||||
|
||||
class ShowImpressum
|
||||
{
|
||||
#[Route(method: 'GET', path: '/impressum')]
|
||||
public function impressum(string $test = 'hallo'): ActionResult
|
||||
{
|
||||
return new ActionResult(
|
||||
ResultType::Html,
|
||||
'impressum',
|
||||
['text' => 'Hallo Welt!'],
|
||||
);
|
||||
}
|
||||
|
||||
#[Route(method: 'GET', path: '/datenschutz')]
|
||||
public function datenschutz(string $test = 'hallo'): ActionResult
|
||||
{
|
||||
return new ActionResult(
|
||||
ResultType::Html,
|
||||
'impressum',
|
||||
['title' => 'Datenschutz!'],
|
||||
);
|
||||
}
|
||||
}
|
||||
18
src/Framework/Attributes/Route.php
Normal file
18
src/Framework/Attributes/Route.php
Normal file
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Framework\Attributes;
|
||||
|
||||
use Attribute;
|
||||
|
||||
#[Attribute(\Attribute::TARGET_METHOD, \Attribute::IS_REPEATABLE)]
|
||||
|
||||
class Route
|
||||
{
|
||||
public function __construct(
|
||||
public string $path,
|
||||
public string $method = 'GET',
|
||||
) {
|
||||
}
|
||||
}
|
||||
9
src/Framework/Attributes/Singleton.php
Normal file
9
src/Framework/Attributes/Singleton.php
Normal file
@@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
namespace App\Framework\Attributes;
|
||||
|
||||
#[\Attribute(\Attribute::TARGET_CLASS)]
|
||||
final class Singleton
|
||||
{
|
||||
|
||||
}
|
||||
17
src/Framework/Core/AttributeMapper.php
Normal file
17
src/Framework/Core/AttributeMapper.php
Normal file
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Framework\Core;
|
||||
|
||||
interface AttributeMapper
|
||||
{
|
||||
public function getAttributeClass(): string;
|
||||
|
||||
/**
|
||||
* @param object $reflectionTarget ReflectionClass|ReflectionMethod
|
||||
* @param object $attributeInstance
|
||||
* @return array|null
|
||||
*/
|
||||
public function map(object $reflectionTarget, object $attributeInstance): ?array;
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user