From 5282bdcf6da435e8afdb228bc97ad07f2345db10 Mon Sep 17 00:00:00 2001 From: Jonathan DeMasi Date: Tue, 23 Dec 2025 19:18:39 -0700 Subject: [PATCH] init --- ansible/ansible.cfg | 8 ++ ansible/gitea.yaml | 8 ++ .../production/group_vars/all.yaml | 1 + .../production/group_vars/webservers.yaml | 1 + .../host_vars/git.jthan.io/vars.yaml | 1 + ansible/inventories/production/hosts.ini | 2 + ansible/requirements.txt | 10 +++ ansible/roles/common/tasks/main.yaml | 16 ++++ ansible/roles/gitea/handlers/main.yaml | 5 ++ ansible/roles/gitea/tasks/main.yaml | 86 +++++++++++++++++++ .../gitea/tasks/templates/gitea.service.j2 | 85 ++++++++++++++++++ ansible/roles/lego/tasks/copy_certs.yaml | 35 ++++++++ ansible/roles/lego/tasks/initial_cert.yaml | 15 ++++ ansible/roles/lego/tasks/main.yaml | 19 ++++ ansible/roles/nginx/handlers/main.yaml | 4 + ansible/roles/nginx/tasks/main.yaml | 57 ++++++++++++ .../roles/nginx/tasks/templates/nginx.conf.j2 | 18 ++++ .../roles/nginx/tasks/templates/vhost.conf.j2 | 48 +++++++++++ ansible/site.yaml | 4 + ansible/webservers.yaml | 7 ++ 20 files changed, 430 insertions(+) create mode 100644 ansible/ansible.cfg create mode 100644 ansible/gitea.yaml create mode 100644 ansible/inventories/production/group_vars/all.yaml create mode 100644 ansible/inventories/production/group_vars/webservers.yaml create mode 100644 ansible/inventories/production/host_vars/git.jthan.io/vars.yaml create mode 100644 ansible/inventories/production/hosts.ini create mode 100644 ansible/requirements.txt create mode 100644 ansible/roles/common/tasks/main.yaml create mode 100644 ansible/roles/gitea/handlers/main.yaml create mode 100644 ansible/roles/gitea/tasks/main.yaml create mode 100644 ansible/roles/gitea/tasks/templates/gitea.service.j2 create mode 100644 ansible/roles/lego/tasks/copy_certs.yaml create mode 100644 ansible/roles/lego/tasks/initial_cert.yaml create mode 100644 ansible/roles/lego/tasks/main.yaml create mode 100644 ansible/roles/nginx/handlers/main.yaml create mode 100644 ansible/roles/nginx/tasks/main.yaml create mode 100644 ansible/roles/nginx/tasks/templates/nginx.conf.j2 create mode 100644 ansible/roles/nginx/tasks/templates/vhost.conf.j2 create mode 100644 ansible/site.yaml create mode 100644 ansible/webservers.yaml diff --git a/ansible/ansible.cfg b/ansible/ansible.cfg new file mode 100644 index 0000000..f4ff74f --- /dev/null +++ b/ansible/ansible.cfg @@ -0,0 +1,8 @@ +[defaults] +inventory = ./inventories/production/hosts.ini + +[privilege_escalation] +become = True +become_method = sudo +become_user = root +become_ask_pass = False diff --git a/ansible/gitea.yaml b/ansible/gitea.yaml new file mode 100644 index 0000000..dbda05f --- /dev/null +++ b/ansible/gitea.yaml @@ -0,0 +1,8 @@ +--- +# file: gitea.yaml +- hosts: gitea + roles: + - common + - nginx + - gitea + diff --git a/ansible/inventories/production/group_vars/all.yaml b/ansible/inventories/production/group_vars/all.yaml new file mode 100644 index 0000000..05402f4 --- /dev/null +++ b/ansible/inventories/production/group_vars/all.yaml @@ -0,0 +1 @@ +lego_version: "4.30.1" diff --git a/ansible/inventories/production/group_vars/webservers.yaml b/ansible/inventories/production/group_vars/webservers.yaml new file mode 100644 index 0000000..aa5cdf1 --- /dev/null +++ b/ansible/inventories/production/group_vars/webservers.yaml @@ -0,0 +1 @@ +letsencrypt_email: "me@jthan.io" diff --git a/ansible/inventories/production/host_vars/git.jthan.io/vars.yaml b/ansible/inventories/production/host_vars/git.jthan.io/vars.yaml new file mode 100644 index 0000000..1c5bb3a --- /dev/null +++ b/ansible/inventories/production/host_vars/git.jthan.io/vars.yaml @@ -0,0 +1 @@ +gitea_version: 1.25.3 diff --git a/ansible/inventories/production/hosts.ini b/ansible/inventories/production/hosts.ini new file mode 100644 index 0000000..d8d7db7 --- /dev/null +++ b/ansible/inventories/production/hosts.ini @@ -0,0 +1,2 @@ +[gitea] +git.jthan.io diff --git a/ansible/requirements.txt b/ansible/requirements.txt new file mode 100644 index 0000000..cc2ea29 --- /dev/null +++ b/ansible/requirements.txt @@ -0,0 +1,10 @@ +ansible==13.1.0 +ansible-core==2.20.1 +cffi==2.0.0 +cryptography==46.0.3 +Jinja2==3.1.6 +MarkupSafe==3.0.3 +packaging==25.0 +pycparser==2.23 +PyYAML==6.0.3 +resolvelib==1.2.1 diff --git a/ansible/roles/common/tasks/main.yaml b/ansible/roles/common/tasks/main.yaml new file mode 100644 index 0000000..556c924 --- /dev/null +++ b/ansible/roles/common/tasks/main.yaml @@ -0,0 +1,16 @@ +- name: Set the hostname per inventory + hostname: + name: "{{ inventory_hostname }}" + use: systemd + +- name: Install firewalld + package: + name: firewalld + state: latest + +- name: Start and enable firewalld + service: + name: firewalld + state: started + enabled: true + diff --git a/ansible/roles/gitea/handlers/main.yaml b/ansible/roles/gitea/handlers/main.yaml new file mode 100644 index 0000000..4f03876 --- /dev/null +++ b/ansible/roles/gitea/handlers/main.yaml @@ -0,0 +1,5 @@ +- name: Restart gitea + service: + name: gitea + state: restarted + diff --git a/ansible/roles/gitea/tasks/main.yaml b/ansible/roles/gitea/tasks/main.yaml new file mode 100644 index 0000000..9fdbc24 --- /dev/null +++ b/ansible/roles/gitea/tasks/main.yaml @@ -0,0 +1,86 @@ +- name: Download gitea + get_url: + url: https://dl.gitea.com/gitea/{{ gitea_version }}/gitea-{{ gitea_version }}-linux-amd64 + dest: /usr/local/bin/gitea + mode: 0755 + +- name: Create a git group + group: + name: git + state: present + gid: 1050 + +- name: Create a git user + user: + name: git + uid: 1050 + group: 1050 + comment: "git user" + shell: /bin/bash + state: present + create_home: yes + +- name: Create gitea data and logging directories + file: + path: "{{ item }}" + state: directory + mode: 0750 + owner: git + group: git + loop: + - /var/lib/gitea + - /var/lib/gitea/custom + - /var/lib/gitea/log + - /var/lib/gitea/data + +- name: Create gitea config directory + file: + path: "/etc/gitea" + state: directory + mode: 0770 + owner: root + group: git + +- name: Install gitea service + template: + src: templates/gitea.service.j2 + dest: /etc/systemd/system/gitea.service + owner: root + group: root + mode: 0640 + register: gitea_service + notify: Restart gitea + +- name: systemctl daemon-reload to pickup gitea service changes + systemd_service: + daemon_reload: true + when: gitea_service.changed + notify: Restart gitea + +- name: Check if gitea web installer wrote its config + stat: + path: /etc/gitea/app.ini" + register: gitea_config_check + +- name: Start and enable gitea + service: + name: gitea + state: started + enabled: true + +- name: Update gitea config directory permissions post install + file: + path: "/etc/gitea" + state: directory + mode: 0750 + owner: root + group: git + when: gitea_config_check.stat.exists + +- name: Update gitea config file permissions post install + file: + path: "/etc/gitea/app.ini" + mode: 0640 + owner: root + group: git + when: gitea_config_check.stat.exists diff --git a/ansible/roles/gitea/tasks/templates/gitea.service.j2 b/ansible/roles/gitea/tasks/templates/gitea.service.j2 new file mode 100644 index 0000000..c091722 --- /dev/null +++ b/ansible/roles/gitea/tasks/templates/gitea.service.j2 @@ -0,0 +1,85 @@ +[Unit] +Description=Gitea (Git with a cup of tea) +After=network.target +### +# Don't forget to add the database service dependencies +### +# +#Wants=mysql.service +#After=mysql.service +# +#Wants=mariadb.service +#After=mariadb.service +# +#Wants=postgresql.service +#After=postgresql.service +# +#Wants=memcached.service +#After=memcached.service +# +#Wants=redis.service +#After=redis.service +# +### +# If using socket activation for main http/s +### +# +#After=gitea.main.socket +#Requires=gitea.main.socket +# +### +# (You can also provide gitea an http fallback and/or ssh socket too) +# +# An example of /etc/systemd/system/gitea.main.socket +### +## +## [Unit] +## Description=Gitea Web Socket +## PartOf=gitea.service +## +## [Socket] +## Service=gitea.service +## ListenStream= +## NoDelay=true +## +## [Install] +## WantedBy=sockets.target +## +### + +[Service] +# Uncomment the next line if you have repos with lots of files and get a HTTP 500 error because of that +# LimitNOFILE=524288:524288 +RestartSec=2s +Type=simple +User=git +Group=git +WorkingDirectory=/var/lib/gitea/ +# If using Unix socket: tells systemd to create the /run/gitea folder, which will contain the gitea.sock file +# (manually creating /run/gitea doesn't work, because it would not persist across reboots) +#RuntimeDirectory=gitea +ExecStart=/usr/local/bin/gitea web --config /etc/gitea/app.ini +Restart=always +Environment=USER=git HOME=/home/git GITEA_WORK_DIR=/var/lib/gitea +# If you install Git to directory prefix other than default PATH (which happens +# for example if you install other versions of Git side-to-side with +# distribution version), uncomment below line and add that prefix to PATH +# Don't forget to place git-lfs binary on the PATH below if you want to enable +# Git LFS support +#Environment=PATH=/path/to/git/bin:/bin:/sbin:/usr/bin:/usr/sbin +# If you want to bind Gitea to a port below 1024, uncomment +# the two values below, or use socket activation to pass Gitea its ports as above +### +#CapabilityBoundingSet=CAP_NET_BIND_SERVICE +#AmbientCapabilities=CAP_NET_BIND_SERVICE +### +# In some cases, when using CapabilityBoundingSet and AmbientCapabilities option, you may want to +# set the following value to false to allow capabilities to be applied on gitea process. The following +# value if set to true sandboxes gitea service and prevent any processes from running with privileges +# in the host user namespace. +### +#PrivateUsers=false +### + +[Install] +WantedBy=multi-user.target diff --git a/ansible/roles/lego/tasks/copy_certs.yaml b/ansible/roles/lego/tasks/copy_certs.yaml new file mode 100644 index 0000000..176fecc --- /dev/null +++ b/ansible/roles/lego/tasks/copy_certs.yaml @@ -0,0 +1,35 @@ +- name: Find certificates to copy + find: + paths: /root/.lego/certificates + recurse: true + patterns: + - "*.crt" + register: certs_to_copy + +- name: Copy certificates to nginx ssl directory + copy: + remote_src: true + src: "{{ item.path }}" + dest: /etc/nginx/ssl + owner: nginx + mode: 0600 + with_items: "{{ certs_to_copy.files }}" + +- name: Find keys to copy + find: + paths: /root/.lego/certificates + recurse: true + patterns: + - "*.key" + register: keys_to_copy + + +- name: Copy keys to nginx ssl directory + copy: + remote_src: true + src: "{{ item.path }}" + dest: /etc/nginx/ssl + owner: nginx + mode: 0600 + with_items: "{{ keys_to_copy.files }}" + diff --git a/ansible/roles/lego/tasks/initial_cert.yaml b/ansible/roles/lego/tasks/initial_cert.yaml new file mode 100644 index 0000000..8a1085b --- /dev/null +++ b/ansible/roles/lego/tasks/initial_cert.yaml @@ -0,0 +1,15 @@ +- name: Stop nginx to generate initial lego cert + service: + name: nginx + state: stopped + +- name: Generate initial cert + command: + cmd: /usr/local/bin/lego -a --email="{{ letsencrypt_email }}" --domains="{{ inventory_hostname | default(cert_domain) }}" --key-type {{ cert_key_type | default('rsa4096') }} --http run + chdir: /root + creates: "/root/.lego/certificates/{{ inventory_hostname | default(cert_domain) }}.crt" + +- name: Start nginx after generating initial lego cert + service: + name: nginx + state: started diff --git a/ansible/roles/lego/tasks/main.yaml b/ansible/roles/lego/tasks/main.yaml new file mode 100644 index 0000000..628fd15 --- /dev/null +++ b/ansible/roles/lego/tasks/main.yaml @@ -0,0 +1,19 @@ +- name: Download and untar lego + unarchive: + src: https://github.com/go-acme/lego/releases/download/v{{ lego_version }}/lego_v{{ lego_version }}_linux_amd64.tar.gz + dest: /usr/local/bin + remote_src: yes + +- name: Check if certs exist + stat: + path: /root/.lego/certificates/{{ inventory_hostname | default(cert_domain) }}.crt + register: cert_check + +- name: Generate an initial cert if not present + include_tasks: + file: initial_cert.yaml + when: not cert_check.stat.exists + +- name: Copy certificates + include_tasks: + file: copy_certs.yaml diff --git a/ansible/roles/nginx/handlers/main.yaml b/ansible/roles/nginx/handlers/main.yaml new file mode 100644 index 0000000..fe0526b --- /dev/null +++ b/ansible/roles/nginx/handlers/main.yaml @@ -0,0 +1,4 @@ + - name: Restart nginx + service: + name: nginx + state: restarted diff --git a/ansible/roles/nginx/tasks/main.yaml b/ansible/roles/nginx/tasks/main.yaml new file mode 100644 index 0000000..fd830a2 --- /dev/null +++ b/ansible/roles/nginx/tasks/main.yaml @@ -0,0 +1,57 @@ +- name: Install nginx + package: + name: nginx + state: latest + +- name: Install nginx.conf + template: + src: templates/nginx.conf.j2 + dest: /etc/nginx/nginx.conf + owner: nginx + group: nginx + mode: '0644' + notify: Restart nginx + +- name: Create nginx ssl directory + file: + path: /etc/nginx/ssl + state: directory + mode: '0755' + +- name: Generate dhparams + command: + cmd: openssl dhparam -out /etc/nginx/ssl/dhparam.pem 4096 + creates: /etc/nginx/ssl/dhparam.pem + notify: Restart nginx + +- name: Start and enable nginx + service: + name: nginx + state: started + enabled: true + +- name: Permanently enable http service + ansible.posix.firewalld: + service: http + state: enabled + permanent: true + immediate: true + offline: true + +- name: Permanently enable https service + ansible.posix.firewalld: + service: https + state: enabled + permanent: true + immediate: true + offline: true + +- name: Create nginx vhosts + template: + src: templates/vhost.conf.j2 + dest: /etc/nginx/conf.d/{{ inventory_hostname }}.conf + owner: nginx + group: nginx + mode: '0644' + notify: Restart nginx + diff --git a/ansible/roles/nginx/tasks/templates/nginx.conf.j2 b/ansible/roles/nginx/tasks/templates/nginx.conf.j2 new file mode 100644 index 0000000..4088c29 --- /dev/null +++ b/ansible/roles/nginx/tasks/templates/nginx.conf.j2 @@ -0,0 +1,18 @@ +user nginx; +worker_processes 2; +error_log /var/log/nginx/main_error.log; +events { + worker_connections 1024; +} +http { + include mime.types; + default_type application/octet-stream; + 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; + error_log /var/log/nginx/http_error.log; + sendfile on; + keepalive_timeout 65; + include conf.d/*; +} diff --git a/ansible/roles/nginx/tasks/templates/vhost.conf.j2 b/ansible/roles/nginx/tasks/templates/vhost.conf.j2 new file mode 100644 index 0000000..3b17627 --- /dev/null +++ b/ansible/roles/nginx/tasks/templates/vhost.conf.j2 @@ -0,0 +1,48 @@ +server { + listen [::]:80 ipv6only=off default_server; + server_name {{ inventory_hostname }}; + root /srv/http/{{ inventory_hostname }}/html; + + # Allow lego to renew certs here using its own http server, we just proxy + location /.well-known/acme-challenge { + proxy_pass http://127.0.0.1:81; + proxy_set_header Host $host; + } + + location / { + return 301 https://{{ inventory_hostname }}$request_uri; + index index.htm index.html; + } + +} + +server { + listen [::]:443 ssl ipv6only=off default_server; + http2 on; + ssl_certificate /etc/nginx/ssl/{{ inventory_hostname}}.crt; + ssl_certificate_key /etc/nginx/ssl/{{ inventory_hostname}}.key; + ssl_trusted_certificate /etc/nginx/ssl/{{ inventory_hostname}}.issuer.crt; + ssl_session_timeout 1d; + ssl_session_cache shared:SSL:50m; + ssl_session_tickets off; + ssl_protocols TLSv1.3; + ssl_ciphers 'ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:AES:CAMELLIA:DES-CBC3-SHA:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!aECDH:!EDH-DSS-DES-CBC3-SHA:!EDH-RSA-DES-CBC3-SHA:!KRB5-DES-CBC3-SHA'; + ssl_dhparam /etc/nginx/ssl/dhparam.pem; + ssl_prefer_server_ciphers on; + add_header Strict-Transport-Security max-age=15768000; + ssl_stapling on; + ssl_stapling_verify on; + server_name {{ inventory_hostname }}; + + location / { + client_max_body_size 512M; + proxy_pass http://localhost:3000; + proxy_set_header Connection $http_connection; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } + +} diff --git a/ansible/site.yaml b/ansible/site.yaml new file mode 100644 index 0000000..8c634bd --- /dev/null +++ b/ansible/site.yaml @@ -0,0 +1,4 @@ +--- +# file: site.yaml +- import_playbook: webservers.yaml +- import_playbook: gitea.yaml diff --git a/ansible/webservers.yaml b/ansible/webservers.yaml new file mode 100644 index 0000000..86c04e3 --- /dev/null +++ b/ansible/webservers.yaml @@ -0,0 +1,7 @@ +--- +# file: webservers.yaml +- hosts: webservers + roles: + - common + - nginx + - lego