diff --git a/.gitignore b/.gitignore index e845c18..310892e 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ inventory +passwords/ diff --git a/files/docker-compose.yml b/files/docker-compose.yml index 740fbe2..214b4ee 100644 --- a/files/docker-compose.yml +++ b/files/docker-compose.yml @@ -3,7 +3,7 @@ version: "3.3" services: gitea: - image: gitea/gitea:1.10 + image: gitea/gitea:1.11 restart: always volumes: - ./volumes/gitea:/data @@ -16,6 +16,42 @@ services: - redis - postfix + weblate: + image: weblate/weblate:3.11.2-1 + restart: always + ports: + - 127.0.0.1:3001:8080 + environment: + - WEBLATE_EMAIL_HOST=postfix:25 + - WEBLATE_ALLOWED_HOSTS=127.0.0.1 + - WEBLATE_ADMIN_PASSWORD=${WEBLATE_ADMIN_PASSWORD} + - WEBLATE_ADMIN_NAME="Weblate admin" + - WEBLATE_ADMIN_EMAIL=noreply@${WEBLATE_HOSTNAME} + - WEBLATE_SERVER_EMAIL=noreply@${WEBLATE_HOSTNAME} + - WEBLATE_DEFAULT_FROM_EMAIL=noreply@${WEBLATE_HOSTNAME} + - REDIS_HOST=redis + - REDIS_DB=2 + - POSTGRES_PASSWORD=${WEBLATE_POSTGRES_PASSWORD} + - POSTGRES_USER=weblate + - POSTGRES_DATABASE=weblate + - POSTGRES_HOST=postgres + - POSTGRES_PORT= + volumes: + - ./volumes/weblate:/app/data + depends_on: + - postgres + - redis + + postgres: + image: postgres:12-alpine + restart: always + environment: + - POSTGRES_USER=weblate + - POSTGRES_PASSWORD=${WEBLATE_POSTGRES_PASSWORD} + volumes: + - ./volumes/postgres:/var/lib/postgresql/data + restart: always + redis: image: redis:5.0-alpine restart: always diff --git a/gitea.yml b/gitea.yml index 6ce81c9..10a2f29 100644 --- a/gitea.yml +++ b/gitea.yml @@ -20,12 +20,20 @@ with_items: - { path: '/gitea/', owner: 'root' } - { path: '/gitea/volumes/', owner: 'root' } + - { path: '/gitea/volumes/gitea/', owner: 'root' } + - { path: '/gitea/volumes/redis/', owner: 'root' } + - { path: '/gitea/volumes/weblate/', owner: '1000' } + - { path: '/gitea/volumes/postgres/', owner: 'root' } - name: add all templates template: src={{item.src}} dest={{item.dest}} mode={{item.mode}} with_items: - { src: 'templates/gitea.conf', dest: '/etc/nginx/sites-enabled/gitea.conf', mode: '0600' } + - { src: 'templates/weblate.conf', dest: '/etc/nginx/sites-enabled/weblate.conf', mode: '0600' } - { src: 'templates/env', dest: '/gitea/.env', mode: '0600' } + vars: + weblate_admin_password: "{{ lookup('password', 'passwords/{{ inventory_hostname }}/weblate_admin_password chars=ascii_letters,digits') }}" + weblate_postgres_password: "{{ lookup('password', 'passwords/{{ inventory_hostname }}/weblate_postgres_password chars=ascii_letters,digits') }}" - name: copy all files copy: src={{item.src}} dest={{item.dest}} mode={{item.mode}} @@ -50,7 +58,9 @@ services: nginx - name: request letsencrypt certificates - shell: certbot certonly --nginx --agree-tos -d '{{ domain }}' -m '{{ letsencrypt_contact_email }}' -n + shell: | + certbot certonly --nginx --agree-tos -d '{{ domain }}' -m '{{ letsencrypt_contact_email }}' -n + certbot certonly --nginx --agree-tos -d 'weblate.{{ domain }}' -m '{{ letsencrypt_contact_email }}' -n - name: start docker-compose docker_compose: @@ -68,3 +78,10 @@ user=root job="certbot certonly -d {{ domain }} -n --webroot --deploy-hook 'nginx -s reload'" + - name: renew weblate certificates + cron: + special_time=daily + name=certbot-renew-gitea + user=root + job="certbot certonly -d weblate.{{ domain }} -n --webroot --deploy-hook 'nginx -s reload'" + diff --git a/templates/env b/templates/env index b09eb23..df8e19a 100644 --- a/templates/env +++ b/templates/env @@ -1 +1,4 @@ GITEA_HOSTNAME={{ domain }} +WEBLATE_HOSTNAME=weblate.{{ domain }} +WEBLATE_ADMIN_PASSWORD={{ weblate_admin_password }} +WEBLATE_POSTGRES_PASSWORD={{ weblate_postgres_password }} diff --git a/templates/weblate.conf b/templates/weblate.conf new file mode 100644 index 0000000..ff80989 --- /dev/null +++ b/templates/weblate.conf @@ -0,0 +1,50 @@ +server { + # TODO: http redirect seems broken + listen 80; + server_name weblate.{{ domain }}; + location /.well-known/acme-challenge/ { + root /var/www/certbot; + } + location / { + return 301 https://$host$request_uri; + } +} + +server { + listen 443 ssl http2; + server_name weblate.{{ domain }}; + + ssl_certificate /etc/letsencrypt/live/weblate.{{ domain }}/fullchain.pem; + ssl_certificate_key /etc/letsencrypt/live/weblate.{{ domain }}/privkey.pem; + + ssl_protocols TLSv1.2 TLSv1.3; + ssl_ciphers 'ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256'; + ssl_prefer_server_ciphers on; + + ssl_session_timeout 1d; + ssl_session_cache shared:SSL:50m; + ssl_session_tickets off; + + ssl_stapling on; + ssl_stapling_verify on; + + add_header X-Frame-Options SAMEORIGIN; + add_header X-Content-Type-Options nosniff; + add_header X-XSS-Protection "1; mode=block"; + add_header Strict-Transport-Security "max-age=15768000"; + add_header Referrer-Policy "same-origin"; + fastcgi_hide_header X-Powered-By; + server_tokens off; + + client_max_body_size 100M; + + # No compression for json to avoid BREACH attack. + gzip on; + gzip_types text/plain text/xml text/css application/xml application/javascript image/svg+xml image/svg; + gzip_proxied any; + gzip_vary on; + + location / { + proxy_pass http://127.0.0.1:3001; + } +}