mirror of
https://github.com/fscotto/infra.git
synced 2026-10-04 22:09:50 +00:00
Prepare gated Gitea HTTPS and SSH cutover
This commit is contained in:
@@ -178,6 +178,10 @@ atlas_gitea_dataset: "{{ atlas_zfs_pool }}/services/data/gitea"
|
||||
atlas_gitea_mountpoint: "{{ atlas_app_data_mountpoint }}/gitea"
|
||||
atlas_gitea_quadlet_dir: "{{ atlas_gitea_home }}/.config/containers/systemd"
|
||||
atlas_gitea_image: docker.gitea.com/gitea:1.25.2-rootless
|
||||
atlas_gitea_production_enabled: false
|
||||
atlas_gitea_bind_address: "{{ ansible_host }}"
|
||||
atlas_gitea_http_port: 3000
|
||||
atlas_gitea_ssh_port: 2222
|
||||
atlas_gitea_staging_bind_address: 127.0.0.1
|
||||
atlas_gitea_staging_http_port: 3001
|
||||
atlas_gitea_staging_ssh_port: 2223
|
||||
|
||||
@@ -14,10 +14,25 @@
|
||||
- atlas_gitea_gid | int != atlas_admin_gid | int
|
||||
- atlas_gitea_gid | int != atlas_immich_gid | int
|
||||
- atlas_gitea_staging_bind_address == '127.0.0.1'
|
||||
- not (atlas_gitea_production_enabled | bool) or atlas_manage_firewall | bool
|
||||
- not (atlas_gitea_production_enabled | bool) or atlas_gitea_bind_address == ansible_host
|
||||
fail_msg: >-
|
||||
Rootless Gitea preparation requires Atlas storage, an isolated service
|
||||
identity and dataset, and loopback-only staging ports.
|
||||
|
||||
- name: Inspect the final-restore marker before production activation
|
||||
ansible.builtin.stat:
|
||||
path: "{{ atlas_gitea_mountpoint }}/.final-sha256"
|
||||
register: atlas_gitea_final_marker
|
||||
when: atlas_gitea_production_enabled | bool
|
||||
|
||||
- name: Refuse production activation without the final consistent restore
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- atlas_gitea_final_marker.stat.isreg | default(false)
|
||||
fail_msg: Restore the final stopped-source Gitea export before enabling production.
|
||||
when: atlas_gitea_production_enabled | bool
|
||||
|
||||
- name: Create the dedicated Gitea group
|
||||
ansible.builtin.group:
|
||||
name: "{{ atlas_gitea_group }}"
|
||||
@@ -114,7 +129,7 @@
|
||||
state: started
|
||||
when: not ansible_check_mode
|
||||
|
||||
- name: Render the disabled rootless Gitea Quadlet
|
||||
- name: Render the rootless Gitea Quadlet
|
||||
ansible.builtin.template:
|
||||
src: atlas-gitea.container.j2
|
||||
dest: "{{ atlas_gitea_quadlet_dir }}/atlas-gitea.container"
|
||||
@@ -122,6 +137,20 @@
|
||||
group: "{{ atlas_gitea_group }}"
|
||||
mode: "0644"
|
||||
|
||||
- name: Permit only Aegis to reach production Gitea HTTP and SSH
|
||||
ansible.posix.firewalld:
|
||||
rich_rule: >-
|
||||
rule family="ipv4" source address="{{ atlas_aegis_ip }}"
|
||||
port port="{{ item }}" protocol="tcp" accept
|
||||
zone: "{{ atlas_firewalld_zone }}"
|
||||
state: "{{ 'enabled' if atlas_gitea_production_enabled | bool else 'disabled' }}"
|
||||
permanent: true
|
||||
immediate: true
|
||||
loop:
|
||||
- "{{ atlas_gitea_http_port }}"
|
||||
- "{{ atlas_gitea_ssh_port }}"
|
||||
when: atlas_manage_firewall | bool
|
||||
|
||||
- name: Reload the rootless Gitea user manager without starting Gitea
|
||||
become_user: "{{ atlas_gitea_username }}"
|
||||
ansible.builtin.systemd:
|
||||
@@ -131,3 +160,17 @@
|
||||
XDG_RUNTIME_DIR: "/run/user/{{ atlas_gitea_uid }}"
|
||||
DBUS_SESSION_BUS_ADDRESS: "unix:path=/run/user/{{ atlas_gitea_uid }}/bus"
|
||||
when: not ansible_check_mode
|
||||
|
||||
- name: Start and enable the rootless Gitea user Quadlet after final restore
|
||||
become_user: "{{ atlas_gitea_username }}"
|
||||
ansible.builtin.systemd:
|
||||
name: atlas-gitea.service
|
||||
scope: user
|
||||
state: started
|
||||
enabled: true
|
||||
environment:
|
||||
XDG_RUNTIME_DIR: "/run/user/{{ atlas_gitea_uid }}"
|
||||
DBUS_SESSION_BUS_ADDRESS: "unix:path=/run/user/{{ atlas_gitea_uid }}/bus"
|
||||
when:
|
||||
- atlas_gitea_production_enabled | bool
|
||||
- not ansible_check_mode
|
||||
|
||||
@@ -1,14 +1,19 @@
|
||||
# Managed by Ansible. Staging only: no [Install], no automatic start.
|
||||
# Managed by Ansible. Staging does not start automatically.
|
||||
[Unit]
|
||||
Description=Atlas rootless Gitea staging target
|
||||
Description=Atlas rootless Gitea
|
||||
RequiresMountsFor={{ atlas_gitea_mountpoint }}
|
||||
|
||||
[Container]
|
||||
ContainerName=atlas-gitea
|
||||
Image={{ atlas_gitea_image }}
|
||||
UserNS=keep-id:uid=1000,gid=1000
|
||||
{% if atlas_gitea_production_enabled | bool %}
|
||||
PublishPort={{ atlas_gitea_bind_address }}:{{ atlas_gitea_http_port }}:3000
|
||||
PublishPort={{ atlas_gitea_bind_address }}:{{ atlas_gitea_ssh_port }}:2222
|
||||
{% else %}
|
||||
PublishPort={{ atlas_gitea_staging_bind_address }}:{{ atlas_gitea_staging_http_port }}:3000
|
||||
PublishPort={{ atlas_gitea_staging_bind_address }}:{{ atlas_gitea_staging_ssh_port }}:2222
|
||||
{% endif %}
|
||||
Volume={{ atlas_gitea_mountpoint }}/data:/var/lib/gitea:Z
|
||||
Volume={{ atlas_gitea_mountpoint }}/config:/etc/gitea:Z
|
||||
NoNewPrivileges=true
|
||||
@@ -18,3 +23,8 @@ DropCapability=all
|
||||
Restart=on-failure
|
||||
RestartSec=10
|
||||
TimeoutStartSec=900
|
||||
{% if atlas_gitea_production_enabled | bool %}
|
||||
|
||||
[Install]
|
||||
WantedBy=default.target
|
||||
{% endif %}
|
||||
|
||||
@@ -44,7 +44,7 @@
|
||||
when: server_backup_export_enabled | bool
|
||||
|
||||
- name: Install Prometheus backup export helper
|
||||
tags: [services, backup, prometheus_backup]
|
||||
tags: [services, backup, prometheus_backup, gitea_cutover]
|
||||
ansible.builtin.template:
|
||||
src: prometheus-backup-export.sh.j2
|
||||
dest: /usr/local/sbin/prometheus-backup-export
|
||||
|
||||
61
ansible/roles/profile_server/tasks/gitea_ssh_proxy.yml
Normal file
61
ansible/roles/profile_server/tasks/gitea_ssh_proxy.yml
Normal file
@@ -0,0 +1,61 @@
|
||||
---
|
||||
- name: Validate the Prometheus Gitea SSH cutover inputs
|
||||
tags: [services, gitea_cutover]
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- server_gitea_cutover_tools_enabled | bool
|
||||
- server_gitea_atlas_address is match('^[0-9]{1,3}(\.[0-9]{1,3}){3}$')
|
||||
- server_gitea_ssh_public_port | int > 1024
|
||||
- server_gitea_ssh_public_port | int < 65536
|
||||
- server_gitea_ssh_target_port | int > 1024
|
||||
- server_gitea_ssh_target_port | int < 65536
|
||||
- server_gitea_ssh_public_port | int != 22
|
||||
fail_msg: Keep administrative SSH on 22 and provide the Atlas rootless Gitea SSH endpoint.
|
||||
when: server_gitea_on_atlas | bool
|
||||
|
||||
- name: Install the Gitea SSH socket proxy units without activating them
|
||||
tags: [services, gitea_cutover]
|
||||
ansible.builtin.template:
|
||||
src: "{{ item }}.j2"
|
||||
dest: "/etc/systemd/system/{{ item }}"
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0644"
|
||||
loop:
|
||||
- prometheus-gitea-ssh-proxy.socket
|
||||
- prometheus-gitea-ssh-proxy.service
|
||||
loop_control:
|
||||
label: "{{ item }}"
|
||||
register: server_gitea_ssh_proxy_units
|
||||
when: server_gitea_cutover_tools_enabled | bool
|
||||
|
||||
- name: Reload systemd after Gitea SSH proxy unit changes
|
||||
tags: [services, gitea_cutover]
|
||||
ansible.builtin.systemd:
|
||||
daemon_reload: true
|
||||
when:
|
||||
- server_gitea_cutover_tools_enabled | bool
|
||||
- server_gitea_ssh_proxy_units is changed
|
||||
- not ansible_check_mode
|
||||
|
||||
- name: Manage the public Gitea SSH socket separately from administrative SSH
|
||||
tags: [services, gitea_cutover]
|
||||
ansible.builtin.systemd:
|
||||
name: prometheus-gitea-ssh-proxy.socket
|
||||
state: "{{ 'started' if server_gitea_on_atlas | bool else 'stopped' }}"
|
||||
enabled: "{{ server_gitea_on_atlas | bool }}"
|
||||
when:
|
||||
- server_gitea_cutover_tools_enabled | bool
|
||||
- not ansible_check_mode
|
||||
|
||||
- name: Open only the public Gitea SSH port after cutover
|
||||
tags: [services, gitea_cutover]
|
||||
ansible.posix.firewalld:
|
||||
port: "{{ server_gitea_ssh_public_port }}/tcp"
|
||||
zone: "{{ server_firewalld_zone }}"
|
||||
state: "{{ 'enabled' if server_gitea_on_atlas | bool else 'disabled' }}"
|
||||
permanent: true
|
||||
immediate: true
|
||||
when:
|
||||
- server_gitea_cutover_tools_enabled | bool
|
||||
- server_firewall_backend == 'firewalld'
|
||||
@@ -37,7 +37,7 @@
|
||||
label: "{{ item.dest }}"
|
||||
|
||||
- name: Render server templates
|
||||
tags: [dotfiles, dotfiles:server]
|
||||
tags: [dotfiles, dotfiles:server, gitea_cutover]
|
||||
ansible.builtin.template:
|
||||
src: "{{ item.src }}"
|
||||
dest: "{{ item.dest if item.dest.startswith('/') else server_user_home ~ '/' ~ item.dest }}"
|
||||
@@ -62,6 +62,9 @@
|
||||
- name: Import explicit Prometheus Gitea final-export tasks
|
||||
ansible.builtin.import_tasks: gitea_final_export.yml
|
||||
|
||||
- name: Import Prometheus Gitea SSH proxy tasks
|
||||
ansible.builtin.import_tasks: gitea_ssh_proxy.yml
|
||||
|
||||
- name: Ensure server SSH authorized key fragments directory exists
|
||||
tags: [services, ssh]
|
||||
ansible.builtin.file:
|
||||
|
||||
@@ -59,7 +59,7 @@ stack_stopped=true
|
||||
systemctl stop "$stack_unit"
|
||||
tar --acls --xattrs --selinux "${excludes[@]}" -C / -cf "$stage/payload.tar" "${paths[@]}"
|
||||
systemctl start "$stack_unit"
|
||||
for container in nginx-proxy-manager gitea; do
|
||||
for container in nginx-proxy-manager{% if not server_gitea_on_atlas | bool %} gitea{% endif %}; do
|
||||
running=false
|
||||
for _ in {1..30}; do
|
||||
if [[ $(podman inspect --format '{{ '{{.State.Running}}' }}' "$container" 2>/dev/null) == true ]]; then
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
[Unit]
|
||||
Description=Forward public Gitea SSH to Atlas through Aegis
|
||||
Requires=prometheus-gitea-ssh-proxy.socket
|
||||
After=network-online.target wg-quick@wg0.service
|
||||
|
||||
[Service]
|
||||
ExecStart=/usr/lib/systemd/systemd-socket-proxyd {{ server_gitea_atlas_address }}:{{ server_gitea_ssh_target_port }}
|
||||
DynamicUser=true
|
||||
NoNewPrivileges=true
|
||||
ProtectSystem=strict
|
||||
ProtectHome=true
|
||||
PrivateTmp=true
|
||||
@@ -0,0 +1,9 @@
|
||||
[Unit]
|
||||
Description=Public Gitea SSH socket on Prometheus
|
||||
|
||||
[Socket]
|
||||
ListenStream=0.0.0.0:{{ server_gitea_ssh_public_port }}
|
||||
NoDelay=true
|
||||
|
||||
[Install]
|
||||
WantedBy=sockets.target
|
||||
Reference in New Issue
Block a user