1031 lines
35 KiB
YAML
1031 lines
35 KiB
YAML
---
|
|
- 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: "git.nitrokite.com"
|
|
gitea_root_url: "https://{{ gitea_domain }}" # HTTPS with domain
|
|
gitea_runner_instance_url: "http://localhost:3000" # Runners connect via localhost to avoid external network
|
|
|
|
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
|
|
- certbot
|
|
- python3-certbot-nginx
|
|
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, runner_count + 1) | list }}"
|
|
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, runner_count + 1) | 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: Configure runner container volumes
|
|
ansible.builtin.lineinfile:
|
|
path: "{{ runner_base_dir }}/runner-{{ item }}/config.yaml"
|
|
insertafter: "^container:"
|
|
line: " options: -v /opt/api-artifacts:/opt/api-artifacts -v /etc/systemd/system:/etc/systemd/system:ro"
|
|
regexp: "^ options:"
|
|
loop: "{{ range(1, runner_count + 1) | list }}"
|
|
|
|
- name: Allow all volume mounts in runner config
|
|
ansible.builtin.replace:
|
|
path: "{{ runner_base_dir }}/runner-{{ item }}/config.yaml"
|
|
regexp: "^ valid_volumes: \\[\\]$"
|
|
replace: " valid_volumes:\n - '**'"
|
|
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: Check if runners are already registered
|
|
ansible.builtin.stat:
|
|
path: "{{ runner_base_dir }}/runner-{{ item }}/.runner"
|
|
register: runner_registered
|
|
loop: "{{ range(1, runner_count + 1) | list }}"
|
|
|
|
- name: Generate runner registration tokens for unregistered runners
|
|
ansible.builtin.shell: |
|
|
sudo -u {{ gitea_user }} {{ gitea_bin }} --config /etc/gitea/app.ini actions generate-runner-token
|
|
register: runner_tokens_raw
|
|
loop: "{{ range(1, runner_count + 1) | list }}"
|
|
when: not runner_registered.results[item - 1].stat.exists
|
|
|
|
- name: Register runners with generated tokens
|
|
become: true
|
|
become_user: "{{ gitea_user }}"
|
|
ansible.builtin.shell: |
|
|
cd {{ runner_base_dir }}/runner-{{ item.item }} && \
|
|
timeout 30 /usr/local/bin/act_runner register \
|
|
--instance {{ gitea_runner_instance_url }} \
|
|
--token {{ item.stdout | trim }} \
|
|
--config {{ runner_base_dir }}/runner-{{ item.item }}/config.yaml \
|
|
--name runner-{{ item.item }} \
|
|
--no-interactive
|
|
loop: "{{ runner_tokens_raw.results | default([]) }}"
|
|
when: item is not skipped
|
|
ignore_errors: true
|
|
|
|
- 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: Restart runners to apply volume mount changes
|
|
ansible.builtin.systemd:
|
|
name: "gitea-runner-{{ item }}"
|
|
state: restarted
|
|
loop: "{{ range(1, runner_count + 1) | list }}"
|
|
|
|
- name: Configure nginx default site
|
|
ansible.builtin.copy:
|
|
dest: /etc/nginx/sites-available/default_block
|
|
mode: "0644"
|
|
content: |
|
|
# Default server block - reject requests without matching server_name
|
|
server {
|
|
listen 80 default_server;
|
|
listen [::]:80 default_server;
|
|
server_name _;
|
|
return 444; # Close connection without response
|
|
}
|
|
- name: Configure nginx gitea site (HTTP only - initial)
|
|
ansible.builtin.copy:
|
|
dest: /etc/nginx/sites-available/gitea
|
|
mode: "0644"
|
|
content: |
|
|
# Gitea server - HTTP only for initial setup
|
|
server {
|
|
listen 80;
|
|
listen [::]:80;
|
|
server_name git.nitrokite.com;
|
|
|
|
# Allow large artifact uploads
|
|
client_max_body_size 500M;
|
|
|
|
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: Configure main nitrokite marketing site (HTTP only - initial)
|
|
ansible.builtin.copy:
|
|
dest: /etc/nginx/sites-available/nitrokite-marketing
|
|
mode: "0644"
|
|
content: |
|
|
# Marketing site - HTTP only for initial setup
|
|
server {
|
|
listen 80;
|
|
listen [::]:80;
|
|
server_name nitrokite.com www.nitrokite.com;
|
|
|
|
root /var/www/html;
|
|
index index.html index.htm;
|
|
|
|
location / {
|
|
try_files $uri $uri/ =404;
|
|
}
|
|
}
|
|
- name: Configure nginx API server site (HTTP only - initial)
|
|
ansible.builtin.copy:
|
|
dest: /etc/nginx/sites-available/api-server
|
|
mode: "0644"
|
|
content: |
|
|
# API Server - HTTP only for initial setup
|
|
server {
|
|
listen 80;
|
|
listen [::]:80;
|
|
server_name api.nitrokite.com;
|
|
|
|
location / {
|
|
proxy_pass http://127.0.0.1:8080;
|
|
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: Configure nginx app server site (HTTP only - initial)
|
|
ansible.builtin.copy:
|
|
dest: /etc/nginx/sites-available/app
|
|
mode: "0644"
|
|
content: |
|
|
# App Server - HTTP only for initial setup
|
|
server {
|
|
listen 80;
|
|
listen [::]:80;
|
|
server_name app.nitrokite.com;
|
|
|
|
root /var/www/app;
|
|
index index.html;
|
|
|
|
location / {
|
|
try_files $uri $uri/ /index.html;
|
|
}
|
|
}
|
|
|
|
- name: Configure nginx grafana site (HTTP only - initial)
|
|
ansible.builtin.copy:
|
|
dest: /etc/nginx/sites-available/grafana
|
|
mode: "0644"
|
|
content: |
|
|
# Grafana - HTTP only for initial setup
|
|
server {
|
|
listen 80;
|
|
listen [::]:80;
|
|
server_name grafana.nitrokite.com;
|
|
|
|
location / {
|
|
proxy_pass http://127.0.0.1:3001;
|
|
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 default block site
|
|
ansible.builtin.file:
|
|
src: /etc/nginx/sites-available/default_block
|
|
dest: /etc/nginx/sites-enabled/default_block
|
|
state: link
|
|
force: true
|
|
- name: Enable nginx nitrokite marketing site
|
|
ansible.builtin.file:
|
|
src: /etc/nginx/sites-available/nitrokite-marketing
|
|
dest: /etc/nginx/sites-enabled/nitrokite-marketing
|
|
state: link
|
|
force: true
|
|
- name: Enable nginx gitea site
|
|
ansible.builtin.file:
|
|
src: /etc/nginx/sites-available/gitea
|
|
dest: /etc/nginx/sites-enabled/gitea
|
|
state: link
|
|
force: true
|
|
- name: Enable nginx API server site
|
|
ansible.builtin.file:
|
|
src: /etc/nginx/sites-available/api-server
|
|
dest: /etc/nginx/sites-enabled/api-server
|
|
state: link
|
|
force: true
|
|
- name: Enable nginx app site
|
|
ansible.builtin.file:
|
|
src: /etc/nginx/sites-available/app
|
|
dest: /etc/nginx/sites-enabled/app
|
|
state: link
|
|
force: true
|
|
- name: Enable nginx grafana site
|
|
ansible.builtin.file:
|
|
src: /etc/nginx/sites-available/grafana
|
|
dest: /etc/nginx/sites-enabled/grafana
|
|
state: link
|
|
force: true
|
|
|
|
- name: Disable default nginx site
|
|
ansible.builtin.file:
|
|
path: /etc/nginx/sites-enabled/default
|
|
state: absent
|
|
|
|
- name: Test nginx configuration
|
|
ansible.builtin.shell: |
|
|
nginx -t
|
|
args:
|
|
executable: /bin/bash
|
|
register: nginx_test
|
|
ignore_errors: true
|
|
|
|
- name: Reload nginx if config is valid
|
|
ansible.builtin.systemd:
|
|
name: nginx
|
|
state: reloaded
|
|
when: nginx_test.rc == 0
|
|
|
|
- name: Allow firewall (SSH + HTTP + HTTPS)
|
|
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
|
|
|
|
# ==========================================
|
|
# SSL CERTIFICATE SETUP
|
|
# ==========================================
|
|
# Temporarily stop nginx to use standalone mode for certificate generation
|
|
- name: Stop nginx for SSL certificate generation
|
|
ansible.builtin.systemd:
|
|
name: nginx
|
|
state: stopped
|
|
|
|
- name: Obtain Let's Encrypt certificate for nitrokite.com
|
|
ansible.builtin.shell: |
|
|
certbot certonly --standalone --non-interactive --agree-tos \
|
|
--email faridonfire@gmail.com \
|
|
-d nitrokite.com
|
|
args:
|
|
creates: /etc/letsencrypt/live/nitrokite.com/fullchain.pem
|
|
ignore_errors: true
|
|
|
|
- name: Obtain Let's Encrypt certificate for git.nitrokite.com
|
|
ansible.builtin.shell: |
|
|
certbot certonly --standalone --non-interactive --agree-tos \
|
|
--email faridonfire@gmail.com \
|
|
-d git.nitrokite.com
|
|
args:
|
|
creates: /etc/letsencrypt/live/git.nitrokite.com/fullchain.pem
|
|
ignore_errors: true
|
|
|
|
- name: Obtain Let's Encrypt certificate for api.nitrokite.com
|
|
ansible.builtin.shell: |
|
|
certbot certonly --standalone --non-interactive --agree-tos \
|
|
--email faridonfire@gmail.com \
|
|
-d api.nitrokite.com
|
|
args:
|
|
creates: /etc/letsencrypt/live/api.nitrokite.com/fullchain.pem
|
|
ignore_errors: true
|
|
|
|
- name: Obtain Let's Encrypt certificate for app.nitrokite.com
|
|
ansible.builtin.shell: |
|
|
certbot certonly --standalone --non-interactive --agree-tos \
|
|
--email faridonfire@gmail.com \
|
|
-d app.nitrokite.com
|
|
args:
|
|
creates: /etc/letsencrypt/live/app.nitrokite.com/fullchain.pem
|
|
ignore_errors: true
|
|
|
|
- name: Obtain Let's Encrypt certificate for grafana.nitrokite.com
|
|
ansible.builtin.shell: |
|
|
certbot certonly --standalone --non-interactive --agree-tos \
|
|
--email faridonfire@gmail.com \
|
|
-d grafana.nitrokite.com
|
|
args:
|
|
creates: /etc/letsencrypt/live/grafana.nitrokite.com/fullchain.pem
|
|
ignore_errors: true
|
|
|
|
- name: Set up automatic certificate renewal
|
|
ansible.builtin.cron:
|
|
name: "Certbot automatic renewal"
|
|
minute: "0"
|
|
hour: "0,12"
|
|
job: "/usr/bin/certbot renew --quiet --post-hook 'systemctl reload nginx'"
|
|
|
|
# Now update nginx configs to use HTTPS with SSL certificates
|
|
- name: Update nginx default site with SSL
|
|
ansible.builtin.copy:
|
|
dest: /etc/nginx/sites-available/default_block
|
|
mode: "0644"
|
|
content: |
|
|
# Default server block - reject requests without matching server_name
|
|
server {
|
|
listen 80 default_server;
|
|
listen [::]:80 default_server;
|
|
server_name _;
|
|
return 444;
|
|
}
|
|
|
|
server {
|
|
listen 443 ssl default_server;
|
|
listen [::]:443 ssl default_server;
|
|
server_name _;
|
|
ssl_reject_handshake on;
|
|
}
|
|
|
|
- name: Check if git.nitrokite.com certificate exists
|
|
ansible.builtin.stat:
|
|
path: /etc/letsencrypt/live/git.nitrokite.com/fullchain.pem
|
|
register: git_cert
|
|
|
|
- name: Update nginx gitea site with SSL
|
|
ansible.builtin.copy:
|
|
dest: /etc/nginx/sites-available/gitea
|
|
mode: "0644"
|
|
content: |
|
|
server {
|
|
listen 80;
|
|
listen [::]:80;
|
|
server_name git.nitrokite.com;
|
|
return 301 https://$server_name$request_uri;
|
|
}
|
|
|
|
server {
|
|
listen 443 ssl http2;
|
|
listen [::]:443 ssl http2;
|
|
server_name git.nitrokite.com;
|
|
|
|
ssl_certificate /etc/letsencrypt/live/git.nitrokite.com/fullchain.pem;
|
|
ssl_certificate_key /etc/letsencrypt/live/git.nitrokite.com/privkey.pem;
|
|
include /etc/letsencrypt/options-ssl-nginx.conf;
|
|
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
|
|
|
|
client_max_body_size 500M;
|
|
|
|
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;
|
|
}
|
|
}
|
|
when: git_cert.stat.exists
|
|
|
|
- name: Check if nitrokite.com certificate exists
|
|
ansible.builtin.stat:
|
|
path: /etc/letsencrypt/live/nitrokite.com/fullchain.pem
|
|
register: nitrokite_cert
|
|
|
|
- name: Update nginx marketing site with SSL
|
|
ansible.builtin.copy:
|
|
dest: /etc/nginx/sites-available/nitrokite-marketing
|
|
mode: "0644"
|
|
content: |
|
|
server {
|
|
listen 80;
|
|
listen [::]:80;
|
|
server_name nitrokite.com www.nitrokite.com;
|
|
return 301 https://nitrokite.com$request_uri;
|
|
}
|
|
|
|
server {
|
|
listen 443 ssl http2;
|
|
listen [::]:443 ssl http2;
|
|
server_name nitrokite.com www.nitrokite.com;
|
|
|
|
ssl_certificate /etc/letsencrypt/live/nitrokite.com/fullchain.pem;
|
|
ssl_certificate_key /etc/letsencrypt/live/nitrokite.com/privkey.pem;
|
|
include /etc/letsencrypt/options-ssl-nginx.conf;
|
|
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
|
|
|
|
root /var/www/html;
|
|
index index.html index.htm;
|
|
|
|
location / {
|
|
try_files $uri $uri/ =404;
|
|
}
|
|
}
|
|
when: nitrokite_cert.stat.exists
|
|
|
|
- name: Check if api.nitrokite.com certificate exists
|
|
ansible.builtin.stat:
|
|
path: /etc/letsencrypt/live/api.nitrokite.com/fullchain.pem
|
|
register: api_cert
|
|
|
|
- name: Update nginx API server site with SSL
|
|
ansible.builtin.copy:
|
|
dest: /etc/nginx/sites-available/api-server
|
|
mode: "0644"
|
|
content: |
|
|
server {
|
|
listen 80;
|
|
listen [::]:80;
|
|
server_name api.nitrokite.com;
|
|
return 301 https://$server_name$request_uri;
|
|
}
|
|
|
|
server {
|
|
listen 443 ssl http2;
|
|
listen [::]:443 ssl http2;
|
|
server_name api.nitrokite.com;
|
|
|
|
ssl_certificate /etc/letsencrypt/live/api.nitrokite.com/fullchain.pem;
|
|
ssl_certificate_key /etc/letsencrypt/live/api.nitrokite.com/privkey.pem;
|
|
include /etc/letsencrypt/options-ssl-nginx.conf;
|
|
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
|
|
|
|
location / {
|
|
proxy_pass http://127.0.0.1:8080;
|
|
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;
|
|
}
|
|
}
|
|
when: api_cert.stat.exists
|
|
|
|
- name: Check if app.nitrokite.com certificate exists
|
|
ansible.builtin.stat:
|
|
path: /etc/letsencrypt/live/app.nitrokite.com/fullchain.pem
|
|
register: app_cert
|
|
|
|
- name: Update nginx app site with SSL
|
|
ansible.builtin.copy:
|
|
dest: /etc/nginx/sites-available/app
|
|
mode: "0644"
|
|
content: |
|
|
server {
|
|
listen 80;
|
|
listen [::]:80;
|
|
server_name app.nitrokite.com;
|
|
return 301 https://$server_name$request_uri;
|
|
}
|
|
|
|
server {
|
|
listen 443 ssl http2;
|
|
listen [::]:443 ssl http2;
|
|
server_name app.nitrokite.com;
|
|
|
|
ssl_certificate /etc/letsencrypt/live/app.nitrokite.com/fullchain.pem;
|
|
ssl_certificate_key /etc/letsencrypt/live/app.nitrokite.com/privkey.pem;
|
|
include /etc/letsencrypt/options-ssl-nginx.conf;
|
|
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
|
|
|
|
root /var/www/app;
|
|
index index.html;
|
|
|
|
location / {
|
|
try_files $uri $uri/ /index.html;
|
|
}
|
|
}
|
|
when: app_cert.stat.exists
|
|
|
|
- name: Check if grafana.nitrokite.com certificate exists
|
|
ansible.builtin.stat:
|
|
path: /etc/letsencrypt/live/grafana.nitrokite.com/fullchain.pem
|
|
register: grafana_cert
|
|
|
|
- name: Update nginx grafana site with SSL
|
|
ansible.builtin.copy:
|
|
dest: /etc/nginx/sites-available/grafana
|
|
mode: "0644"
|
|
content: |
|
|
server {
|
|
listen 80;
|
|
listen [::]:80;
|
|
server_name grafana.nitrokite.com;
|
|
return 301 https://$server_name$request_uri;
|
|
}
|
|
|
|
server {
|
|
listen 443 ssl http2;
|
|
listen [::]:443 ssl http2;
|
|
server_name grafana.nitrokite.com;
|
|
|
|
ssl_certificate /etc/letsencrypt/live/grafana.nitrokite.com/fullchain.pem;
|
|
ssl_certificate_key /etc/letsencrypt/live/grafana.nitrokite.com/privkey.pem;
|
|
include /etc/letsencrypt/options-ssl-nginx.conf;
|
|
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
|
|
|
|
location / {
|
|
proxy_pass http://127.0.0.1:3001;
|
|
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;
|
|
}
|
|
}
|
|
when: grafana_cert.stat.exists
|
|
|
|
- name: Test nginx configuration with SSL
|
|
ansible.builtin.shell: |
|
|
nginx -t
|
|
args:
|
|
executable: /bin/bash
|
|
register: nginx_ssl_test
|
|
ignore_errors: true
|
|
|
|
- name: Start nginx with SSL configuration
|
|
ansible.builtin.systemd:
|
|
name: nginx
|
|
state: started
|
|
when: nginx_ssl_test.rc == 0
|
|
|
|
# ==========================================
|
|
# MARKETING WEBSITE DEPLOYMENT
|
|
# ==========================================
|
|
- name: Create /var/www/html directory
|
|
ansible.builtin.file:
|
|
path: /var/www/html
|
|
state: directory
|
|
mode: '0755'
|
|
owner: www-data
|
|
group: www-data
|
|
|
|
- name: Deploy marketing website files
|
|
ansible.builtin.synchronize:
|
|
src: ../../marketing/dist/
|
|
dest: /var/www/html/
|
|
delete: true
|
|
recursive: true
|
|
rsync_opts:
|
|
- "--chmod=D755,F644"
|
|
when: nitrokite_cert.stat.exists
|
|
|
|
- name: Set ownership of marketing website files
|
|
ansible.builtin.file:
|
|
path: /var/www/html
|
|
owner: www-data
|
|
group: www-data
|
|
recurse: true
|
|
when: nitrokite_cert.stat.exists
|
|
|
|
# ==========================================
|
|
# 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
|
|
|
|
# ==========================================
|
|
# FRONTEND APP DEPLOYMENT INFRASTRUCTURE
|
|
# ==========================================
|
|
- name: Create frontend deployment directory structure
|
|
ansible.builtin.file:
|
|
path: "{{ item }}"
|
|
state: directory
|
|
owner: www-data
|
|
group: www-data
|
|
mode: "0755"
|
|
loop:
|
|
- /opt/frontend-artifacts
|
|
- /opt/frontend-artifacts/staging
|
|
- /opt/frontend-artifacts/backups
|
|
- /var/www/app
|
|
|
|
- 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 + Frontend Infrastructure Complete!"
|
|
- "============================================"
|
|
- ""
|
|
- "Gitea:"
|
|
- " URL: https://git.nitrokite.com"
|
|
- " 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)"
|
|
- ""
|
|
- "Frontend App Deployment:"
|
|
- " Artifact directory: /opt/frontend-artifacts/"
|
|
- " Web directory: /var/www/app/"
|
|
- " URL: https://app.nitrokite.com"
|
|
- ""
|
|
- "Marketing Website:"
|
|
- " Web directory: /var/www/html/"
|
|
- " URL: https://nitrokite.com"
|
|
- ""
|
|
- "Next Steps:"
|
|
- "1. Create repository in Gitea and push your 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 <commit-sha>"
|
|
- ""
|
|
- "Management Commands:"
|
|
- " - View builds: ls /opt/api-artifacts/builds/"
|
|
- " - View frontend staging: ls /opt/frontend-artifacts/staging/"
|
|
- " - 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
|