--- - name: Install Gitea (Ubuntu 24.04 minimal) hosts: git become: true vars: gitea_version: "1.23.3" gitea_arch: "linux-amd64" gitea_bin: /usr/local/bin/gitea gitea_user: git gitea_home: /var/lib/gitea gitea_http_addr: "0.0.0.0" # Listen on all interfaces gitea_http_port: 3000 gitea_domain: "{{ ansible_host }}" # Use the server's IP address gitea_root_url: "http://{{ ansible_host }}" # HTTP with IP and port gitea_internal_token: "5982634986925861987263497269412869416237419264" # Change to a secure random token postgres_db: gitea postgres_user: gitea postgres_password: "12409768234691236" # PostgreSQL admin user for debugging and management postgres_admin_user: pgadmin postgres_admin_password: "{{ lookup('env', 'POSTGRES_ADMIN_PASSWORD') | default('ChangeMe_SecurePassword_2025', true) }}" # Golang API Server database user api_server_db_user: apiserver api_server_db_password: "{{ lookup('env', 'API_SERVER_DB_PASSWORD') | default('ChangeMe_ApiServer_2025', true) }}" # NEW: Option to create separate database for API server create_separate_apiserver_db: true # Set to false to share gitea database # Gitea Runner configuration runner_version: "0.2.11" runner_count: 5 # Number of runners to deploy runner_base_dir: /var/lib/gitea-runner runner_token: "CHANGE_ME_TO_REGISTRATION_TOKEN" # Get from Gitea admin panel tasks: - name: Install packages ansible.builtin.apt: update_cache: true name: - ca-certificates - curl - git - nginx - ufw - postgresql - postgresql-contrib - python3-psycopg # Important on Ubuntu 24.04 - docker.io state: present - name: Start and enable Docker ansible.builtin.service: name: docker state: started enabled: true - name: Create git system user ansible.builtin.user: name: "{{ gitea_user }}" system: true create_home: true home: "{{ gitea_home }}" shell: /usr/sbin/nologin groups: docker append: true - name: Create Gitea directories ansible.builtin.file: path: "{{ item }}" state: directory owner: "{{ gitea_user }}" group: "{{ gitea_user }}" mode: "0750" loop: - /etc/gitea - "{{ gitea_home }}" - "{{ gitea_home }}/custom" - "{{ gitea_home }}/data" - "{{ gitea_home }}/log" - "{{ gitea_home }}/repositories" - "{{ gitea_home }}/data/actions_artifacts" - "{{ gitea_home }}/data/actions_cache" - name: Download gitea binary ansible.builtin.get_url: url: "https://dl.gitea.com/gitea/{{ gitea_version }}/gitea-{{ gitea_version }}-{{ gitea_arch }}" dest: "{{ gitea_bin }}" mode: "0755" - name: Ensure postgres running ansible.builtin.service: name: postgresql state: started enabled: true - name: Create postgres user become_user: postgres community.postgresql.postgresql_user: name: "{{ postgres_user }}" password: "{{ postgres_password }}" - name: Create postgres database become_user: postgres community.postgresql.postgresql_db: name: "{{ postgres_db }}" owner: "{{ postgres_user }}" - name: Create PostgreSQL admin user for production debugging become_user: postgres community.postgresql.postgresql_user: name: "{{ postgres_admin_user }}" password: "{{ postgres_admin_password }}" role_attr_flags: SUPERUSER,CREATEDB,CREATEROLE,REPLICATION,LOGIN comment: "Admin user for debugging and managing databases, users, and roles" - name: Create PostgreSQL user for golang-api-server become_user: postgres community.postgresql.postgresql_user: name: "{{ api_server_db_user }}" password: "{{ api_server_db_password }}" role_attr_flags: LOGIN comment: "Database user for golang-api-server application" - name: Create separate API server database become_user: postgres community.postgresql.postgresql_db: name: apiserver owner: "{{ api_server_db_user }}" when: create_separate_apiserver_db - name: Grant privileges to api-server user on their database become_user: postgres community.postgresql.postgresql_privs: database: "{{ 'apiserver' if create_separate_apiserver_db else postgres_db }}" state: present privs: ALL type: database role: "{{ api_server_db_user }}" - name: Configure PostgreSQL to listen on all interfaces ansible.builtin.lineinfile: path: /etc/postgresql/16/main/postgresql.conf regexp: '^#?listen_addresses\s*=' line: "listen_addresses = '*'" backup: yes notify: restart postgresql - name: Allow remote connections to PostgreSQL ansible.builtin.lineinfile: path: /etc/postgresql/16/main/pg_hba.conf line: "host all all 0.0.0.0/0 scram-sha-256" insertafter: EOF backup: yes notify: restart postgresql - name: Write Gitea config ansible.builtin.copy: dest: /etc/gitea/app.ini owner: "{{ gitea_user }}" group: "{{ gitea_user }}" mode: "0640" content: | APP_NAME = Gitea RUN_USER = {{ gitea_user }} RUN_MODE = prod WORK_PATH = {{ gitea_home }} [server] DOMAIN = {{ gitea_domain }} ROOT_URL = {{ gitea_root_url }} HTTP_ADDR = {{ gitea_http_addr }} HTTP_PORT = {{ gitea_http_port }} START_SSH_SERVER = false DISABLE_SSH = false APP_DATA_PATH = {{ gitea_home }}/data [database] DB_TYPE = postgres HOST = 127.0.0.1:5432 NAME = {{ postgres_db }} USER = {{ postgres_user }} PASSWD = {{ postgres_password }} SSL_MODE = disable [security] INSTALL_LOCK = true INTERNAL_TOKEN = {{ gitea_internal_token}} [actions] ENABLED = true [actions.artifacts] STORAGE_TYPE = local MINIO_BASE_PATH = actions_artifacts/ [actions.artifacts.local] PATH = {{ gitea_home }}/data/actions_artifacts [log] MODE = file LEVEL = info ROOT_PATH = {{ gitea_home }}/log - name: Install systemd unit ansible.builtin.copy: dest: /etc/systemd/system/gitea.service mode: "0644" content: | [Unit] Description=Gitea After=network.target postgresql.service Wants=postgresql.service [Service] Type=simple User={{ gitea_user }} Group={{ gitea_user }} WorkingDirectory={{ gitea_home }} Environment=HOME={{ gitea_home }} ExecStart={{ gitea_bin }} web --config /etc/gitea/app.ini Restart=always RestartSec=2s LimitNOFILE=1048576 [Install] WantedBy=multi-user.target - name: Start gitea ansible.builtin.systemd: daemon_reload: true name: gitea state: started enabled: true # Gitea Runner Installation - name: Delete all runners from Gitea database directly become_user: postgres community.postgresql.postgresql_query: db: "{{ postgres_db }}" query: "DELETE FROM action_runner;" ignore_errors: true - name: Stop all existing runner services ansible.builtin.systemd: name: "gitea-runner-{{ item }}" state: stopped enabled: false loop: "{{ range(1, 21) | list }}" # Check up to 20 possible runners ignore_errors: true failed_when: false - name: Remove all existing runner systemd units ansible.builtin.file: path: "/etc/systemd/system/gitea-runner-{{ item }}.service" state: absent loop: "{{ range(1, 21) | list }}" ignore_errors: true - name: Reload systemd after removing runner units ansible.builtin.systemd: daemon_reload: true - name: Remove all existing runner directories ansible.builtin.file: path: "{{ runner_base_dir }}" state: absent - name: Recreate base runner directory ansible.builtin.file: path: "{{ runner_base_dir }}" state: directory owner: "{{ gitea_user }}" group: "{{ gitea_user }}" mode: "0750" - name: Download Gitea runner binary ansible.builtin.get_url: url: "https://dl.gitea.com/act_runner/{{ runner_version }}/act_runner-{{ runner_version }}-linux-amd64" dest: /usr/local/bin/act_runner mode: "0755" - name: Create runner directories ansible.builtin.file: path: "{{ runner_base_dir }}/runner-{{ item }}" state: directory owner: "{{ gitea_user }}" group: "{{ gitea_user }}" mode: "0750" loop: "{{ range(1, runner_count + 1) | list }}" - name: Generate runner config ansible.builtin.shell: | /usr/local/bin/act_runner generate-config > {{ runner_base_dir }}/runner-{{ item }}/config.yaml args: creates: "{{ runner_base_dir }}/runner-{{ item }}/config.yaml" loop: "{{ range(1, runner_count + 1) | list }}" - name: Set runner config ownership ansible.builtin.file: path: "{{ runner_base_dir }}/runner-{{ item }}/config.yaml" owner: "{{ gitea_user }}" group: "{{ gitea_user }}" mode: "0640" loop: "{{ range(1, runner_count + 1) | list }}" - name: Generate runner registration tokens ansible.builtin.shell: | sudo -u {{ gitea_user }} {{ gitea_bin }} --config /etc/gitea/app.ini actions generate-runner-token register: runner_tokens loop: "{{ range(1, runner_count + 1) | list }}" - name: Register runners with generated tokens become: true become_user: "{{ gitea_user }}" ansible.builtin.shell: | cd {{ runner_base_dir }}/runner-{{ item.item }} && \ /usr/local/bin/act_runner register \ --instance {{ gitea_root_url }} \ --token {{ item.stdout | trim }} \ --config {{ runner_base_dir }}/runner-{{ item.item }}/config.yaml \ --name runner-{{ item.item }} \ --no-interactive loop: "{{ runner_tokens.results }}" args: creates: "{{ runner_base_dir }}/runner-{{ item.item }}/.runner" - name: Create runner systemd units ansible.builtin.copy: dest: "/etc/systemd/system/gitea-runner-{{ item }}.service" mode: "0644" content: | [Unit] Description=Gitea Runner {{ item }} After=network.target gitea.service Wants=gitea.service [Service] Type=simple User={{ gitea_user }} Group={{ gitea_user }} WorkingDirectory={{ runner_base_dir }}/runner-{{ item }} ExecStart=/usr/local/bin/act_runner daemon --config {{ runner_base_dir }}/runner-{{ item }}/config.yaml Restart=always RestartSec=10s [Install] WantedBy=multi-user.target loop: "{{ range(1, runner_count + 1) | list }}" - name: Enable and start runner services ansible.builtin.systemd: daemon_reload: true name: "gitea-runner-{{ item }}" enabled: true state: started loop: "{{ range(1, runner_count + 1) | list }}" - name: Configure nginx reverse proxy (HTTP only for now) ansible.builtin.copy: dest: /etc/nginx/sites-available/gitea mode: "0644" content: | server { listen 80; server_name {{ ansible_host }}; location / { proxy_pass http://127.0.0.1:{{ gitea_http_port }}; 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; } } - name: Enable nginx site ansible.builtin.file: src: /etc/nginx/sites-available/gitea dest: /etc/nginx/sites-enabled/gitea state: link force: true - name: Disable default nginx site ansible.builtin.file: path: /etc/nginx/sites-enabled/default state: absent - name: Test and reload nginx ansible.builtin.shell: | nginx -t && systemctl reload nginx args: executable: /bin/bash - name: Allow firewall (SSH + HTTP) ansible.builtin.ufw: rule: allow name: "{{ item }}" loop: - "OpenSSH" - "Nginx Full" - name: Allow PostgreSQL port ansible.builtin.ufw: rule: allow port: 5432 - name: Enable UFW ansible.builtin.ufw: state: enabled policy: deny # ========================================== # API SERVER DEPLOYMENT INFRASTRUCTURE # ========================================== - name: Create apiserver system user ansible.builtin.user: name: apiserver system: true create_home: true home: /opt/api-artifacts shell: /bin/bash groups: docker append: true - name: Create deployment directory structure ansible.builtin.file: path: "{{ item }}" state: directory owner: apiserver group: apiserver mode: "0755" loop: - /opt/api-artifacts - /opt/api-artifacts/builds - /opt/api-artifacts/scripts - /opt/api-artifacts/config - name: Copy deployment scripts from local repository ansible.builtin.copy: src: "{{ playbook_dir }}/../scripts/{{ item }}" dest: /opt/api-artifacts/scripts/{{ item }} owner: apiserver group: apiserver mode: "0755" loop: - deploy.sh - promote.sh - rollback.sh - health-check.sh - migrate.sh - name: Create production environment file ansible.builtin.copy: dest: /opt/api-artifacts/config/.env.production owner: apiserver group: apiserver mode: "0600" content: | DATABASE_URL=postgresql://{{ api_server_db_user }}:{{ api_server_db_password }}@localhost:5432/{{ 'apiserver' if create_separate_apiserver_db else postgres_db }}?sslmode=disable AUTO_MIGRATE=false PORT=8080 GIN_MODE=release - name: Copy systemd service file ansible.builtin.copy: src: "{{ playbook_dir }}/../systemd/api-server.service" dest: /etc/systemd/system/api-server.service owner: root group: root mode: "0644" register: systemd_service - name: Reload systemd daemon ansible.builtin.systemd: daemon_reload: true when: systemd_service.changed - name: Enable api-server service (but don't start - no binary yet) ansible.builtin.systemd: name: api-server.service enabled: true failed_when: false - name: Install golang-migrate CLI ansible.builtin.shell: | curl -L https://github.com/golang-migrate/migrate/releases/download/v4.19.1/migrate.linux-amd64.tar.gz | tar xvz mv migrate /usr/local/bin/ chmod +x /usr/local/bin/migrate args: creates: /usr/local/bin/migrate - name: Display setup summary ansible.builtin.debug: msg: - "============================================" - "Gitea + API Server Infrastructure Complete!" - "============================================" - "" - "Gitea:" - " URL: {{ gitea_root_url }}" - " Runners: {{ runner_count }} runners active" - "" - "API Server Deployment:" - " Artifact directory: /opt/api-artifacts/" - " Database: {{ 'apiserver' if create_separate_apiserver_db else postgres_db }}" - " Database user: {{ api_server_db_user }}" - " Systemd service: api-server.service (enabled)" - "" - "Next Steps:" - "1. Create repository in Gitea and push your golang-api-server code" - "2. Push will trigger Gitea Actions build automatically" - "3. After build completes, SSH to server and promote:" - " sudo /opt/api-artifacts/scripts/promote.sh" - "" - "Management Commands:" - " - View builds: ls /opt/api-artifacts/builds/" - " - Check logs: journalctl -u api-server -f" - " - Rollback: sudo /opt/api-artifacts/scripts/rollback.sh" handlers: - name: restart postgresql ansible.builtin.service: name: postgresql state: restarted