diff --git a/.dockerignore b/.dockerignore index ea2e09c..f191855 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1 +1,2 @@ src/db.sqlite3 +.claude diff --git a/.gitignore b/.gitignore index e881bdf..68679e7 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,9 @@ # Created by https://www.toptal.com/developers/gitignore/api/django # Edit at https://www.toptal.com/developers/gitignore?templates=django +### Claude ### +.claude + ### Django ### *.log *.pot diff --git a/.woodpecker.yaml b/.woodpecker.yaml new file mode 100644 index 0000000..910b64e --- /dev/null +++ b/.woodpecker.yaml @@ -0,0 +1,7 @@ +steps: + - name: test + image: python:3.13-slim + commands: + - "pip install -r requirements.txt" + - "cd ./src" + - "python manage.py test apps" diff --git a/ansible.cfg b/ansible.cfg new file mode 100644 index 0000000..c25f752 --- /dev/null +++ b/ansible.cfg @@ -0,0 +1,3 @@ +[defaults] +inventory = infra/inventory.ini +vault_password_file = ~/.vault-pass.txt diff --git a/infra/cicd-playbook.yaml b/infra/cicd-playbook.yaml new file mode 100644 index 0000000..82dfd07 --- /dev/null +++ b/infra/cicd-playbook.yaml @@ -0,0 +1,114 @@ +- hosts: cicd + + tasks: + - name: Install Docker + ansible.builtin.apt: + name: docker.io + state: latest + update_cache: true + become: true + + - name: Install Nginx + ansible.builtin.apt: + name: nginx + state: latest + become: true + + - name: Add out user to the docker group, so we don't need sudo/become + ansible.builtin.user: + name: '{{ ansible_user }}' + groups: docker + append: true # don't remove any existing groups + become: true + + - name: Reset ssh connection to allow the user/group change to take effect + ansible.builtin.meta: reset_connection + + - name: Install docker-compose-plugin & certbot + ansible.builtin.apt: + name: + - docker-compose-v2 + - certbot + - python3-certbot-nginx + state: latest + become: true + + - name: Create /opt/cicd/ directory tree + ansible.builtin.file: + path: "/opt/cicd/nginx" + state: directory + become: true + + - name: Cp docker-compose.yaml to server + ansible.builtin.copy: + src: cicd/docker-compose.yaml + dest: /opt/cicd/docker-compose.yaml + become: true + + - name: Template .env to /opt/cicd/ + ansible.builtin.template: + src: cicd/.env.j2 + dest: /opt/cicd/.env + mode: "0600" + become: true + + - name: Deploy nginx config (Gitea) + ansible.builtin.copy: + src: cicd/nginx/gitea.conf + dest: /etc/nginx/sites-available/gitea + become: true + notify: Restart nginx + + - name: Deploy nginx config (Woodpecker) + ansible.builtin.copy: + src: cicd/nginx/woodpecker.conf + dest: /etc/nginx/sites-available/woodpecker + become: true + notify: Restart nginx + + - name: Enable nginx site (Gitea) + ansible.builtin.file: + src: /etc/nginx/sites-available/gitea + dest: /etc/nginx/sites-enabled/gitea + state: link + become: true + notify: Restart nginx + + - name: Enable nginx site (Woodpecker) + ansible.builtin.file: + src: /etc/nginx/sites-available/woodpecker + dest: /etc/nginx/sites-enabled/woodpecker + state: link + become: true + notify: Restart nginx + + - name: Remove default nginx site + ansible.builtin.file: + path: /etc/nginx/sites-enabled/default + state: absent + become: true + notify: Restart nginx + + - name: Obtain SSL certs via certbot + ansible.builtin.command: + cmd: > + certbot --nginx + -d gitea.earthmanrpg.me + -d ci.earthmanrpg.me + --non-interactive + --agree-tos + -m discodedisco@outlook.com + creates: /etc/letsencrypt/live/gitea.earthmanrpg.me/fullchain.pem + become: true + + - name: Run docker compose -f /opt/cicd/docker-compose.yaml up -d + ansible.builtin.command: + cmd: docker compose -f /opt/cicd/docker-compose.yaml up -d + become: true + + handlers: + - name: Restart nginx + ansible.builtin.service: + name: nginx + state: restarted + become: true \ No newline at end of file diff --git a/infra/cicd/.env.j2 b/infra/cicd/.env.j2 new file mode 100644 index 0000000..002ba75 --- /dev/null +++ b/infra/cicd/.env.j2 @@ -0,0 +1,4 @@ +WOODPECKER_ADMIN={{ woodpecker_admin }} +WOODPECKER_AGENT_SECRET={{ woodpecker_agent_secret }} +WOODPECKER_GITEA_CLIENT={{ woodpecker_gitea_client }} +WOODPECKER_GITEA_SECRET={{ woodpecker_gitea_secret }} \ No newline at end of file diff --git a/infra/cicd/docker-compose.yaml b/infra/cicd/docker-compose.yaml new file mode 100644 index 0000000..f8479f4 --- /dev/null +++ b/infra/cicd/docker-compose.yaml @@ -0,0 +1,58 @@ +services: + gitea: + image: docker.gitea.com/gitea:1.24 + restart: unless-stopped + environment: + - USER_UID=1000 + - USER_GID=1000 + - GITEA__server__ROOT_URL=https://gitea.earthmanrpg.me/ + - GITEA__server__DOMAIN=gitea.earthmanrpg.me + - GITEA__server__SSH_DOMAIN=gitea.earthmanrpg.me + - GITEA__webhook__ALLOWED_HOST_LIST=external,loopback + volumes: + - ./data/gitea:/data # Gitea stores repos, db, config here + - /etc/timezone:/etc/timezone:ro + - /etc/localtime:/etc/localtime:ro + ports: + - "127.0.0.1:3000:3000" # http (only localhost, nginx proxies) + - "222:22" # ssh (public, for git push) + networks: + - cicd + + woodpecker-server: + image: woodpeckerci/woodpecker-server:v3 + restart: unless-stopped + depends_on: + - gitea + environment: + - WOODPECKER_HOST=https://ci.earthmanrpg.me + - WOODPECKER_OPEN=false + - WOODPECKER_ADMIN=${WOODPECKER_ADMIN} + - WOODPECKER_GITEA=true + - WOODPECKER_GITEA_URL=https://gitea.earthmanrpg.me + - WOODPECKER_GITEA_CLIENT=${WOODPECKER_GITEA_CLIENT} + - WOODPECKER_GITEA_SECRET=${WOODPECKER_GITEA_SECRET} + - WOODPECKER_AGENT_SECRET=${WOODPECKER_AGENT_SECRET} + volumes: + - ./data/woodpecker-server:/var/lib/woodpecker + ports: + - "127.0.0.1:8000:8000" # (only nginx proxies) + networks: + - cicd + + woodpecker-agent: + image: woodpeckerci/woodpecker-agent:v3 + restart: unless-stopped + depends_on: + - woodpecker-server + environment: + - WOODPECKER_SERVER=woodpecker-server:9000 + - WOODPECKER_AGENT_SECRET=${WOODPECKER_AGENT_SECRET} + - WOODPECKER_MAX_WORKFLOWS=2 + volumes: + - /var/run/docker.sock:/var/run/docker.sock + networks: + - cicd + +networks: + cicd: \ No newline at end of file diff --git a/infra/cicd/nginx/gitea.conf b/infra/cicd/nginx/gitea.conf new file mode 100644 index 0000000..65f2603 --- /dev/null +++ b/infra/cicd/nginx/gitea.conf @@ -0,0 +1,14 @@ +server { + listen 80; + server_name gitea.earthmanrpg.me; + + client_max_body_size 100m; + + location / { + proxy_pass http://127.0.0.1:3000; + 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/infra/cicd/nginx/woodpecker.conf b/infra/cicd/nginx/woodpecker.conf new file mode 100644 index 0000000..66c6b5e --- /dev/null +++ b/infra/cicd/nginx/woodpecker.conf @@ -0,0 +1,16 @@ +server { + listen 80; + server_name ci.earthmanrpg.me; + + location / { + proxy_pass http://127.0.0.1:8000; + 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; + + # WebSocket support (Woodpecker live log streaming) + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + } +} diff --git a/infra/group_vars/cicd/vault.yaml b/infra/group_vars/cicd/vault.yaml new file mode 100644 index 0000000..1efbe50 --- /dev/null +++ b/infra/group_vars/cicd/vault.yaml @@ -0,0 +1,18 @@ +$ANSIBLE_VAULT;1.1;AES256 +32353432336330376365646535626462343239653737383565353163623230393338633536653231 +3536303839613765643136366535333962376665306662620a383634393561653139306139366631 +34663365316264656235396538653035366437663030383962373936336464306632333662623737 +3834366363383135330a643064383065653565636334636336366138383464663562363030313636 +30356332663332613565313339633036653463636264643531383231613739653061333134333337 +36643365303637653635613838626466353163386331383864623936326636303734326566626561 +32633562336663303866376335363935343031363465353834303133376363376665333363343535 +35616532616264663631626135353134623965613539336562353963363534336261663530343331 +30636332633538313838363434386331323837376663666463393736383830363966373662316539 +39343466626633313734363732386135333636663735646430616130353765616133393165366231 +62613130306164323938373964313938613634396665363161646264323332316664656265643231 +35393032343263316464333530623365303430623235363862393435646433616566336136353236 +34636630373530393464663839656661313364333830383966353739346335643635393030396335 +30653332306163346431353535613338323163333032333435613139383366336338326635363165 +34353033346362646131353339666561353766366239643661313334646365666236616232623865 +37636162613334353632346664343034303861393833363535343063623831613735303762643064 +31303161306364356436393665363939643736353238333037393936303136336566 diff --git a/infra/group_vars/production/vault.yaml b/infra/group_vars/production/vault.yaml deleted file mode 100644 index b898ca8..0000000 --- a/infra/group_vars/production/vault.yaml +++ /dev/null @@ -1,22 +0,0 @@ -$ANSIBLE_VAULT;1.1;AES256 -33333861646565323863616363316265346132643135656533613763336462656361353562396163 -6461313530323231383264373737323934346239316232340a356130646163636436663162386438 -30636530373663643166616665313738303437636562643035303138613861656165663939613433 -3465316130343732660a376536373136636162316665386536363536306130383033623735396634 -66383262643630356536633366373933313637646539643137356330316463356139613738386661 -34663231656232666630653162653732363431613461396464623133303965636432626536306634 -64373530313532353531633263383335653239386530343330326237343862386436633666646235 -31613530383834656462366535643030356562313237363735386337356165663564336564623862 -34623863623738653735393734336635616135383036306231623464653432616265626233306230 -63646466323135363466393832636466646434303564653032323366346430306336363435653761 -30336437373231376261326264616131653833616236623365393334303834626162343761623037 -36666665663866643263383835626336353030626337303461396665343731666465653662396164 -34306261356130363037643637303632663830383331346334313336663163303730306265393031 -39653231373139616465326561313633306433653461653931663164363565363636316433323933 -61333536323936306538343336663966633161633565666231393261643062636239323264623364 -38376266393937376133366561663931356236396131376137653536636539613738356466363334 -32326165316434636631613366376235633337356135333531623861343039346261656239613036 -65303836323538373832646531343234666330363161343337623539633464303161343765363331 -35386233303563346662633239346363373931333764383233623161313965623266656364383037 -32393738356532346665613031346338363738666265303765363438663062663237353033393262 -6137 diff --git a/infra/inventory.ini b/infra/inventory.ini index 28cd6cf..21e666d 100644 --- a/infra/inventory.ini +++ b/infra/inventory.ini @@ -1,7 +1,10 @@ [staging] -staging.earthmanrpg.me +staging.earthmanrpg.me ansible_user=discoman ansible_ssh_private_key_file=~/.ssh/id_ed25519_wsl_python-tdd [production] -www.earthmanrpg.me -earthmanrpg.me -dashboard.earthmanrpg.me +www.earthmanrpg.me ansible_user=discoman ansible_ssh_private_key_file=~/.ssh/id_ed25519_wsl_python-tdd +earthmanrpg.me ansible_user=discoman ansible_ssh_private_key_file=~/.ssh/id_ed25519_wsl_python-tdd +dashboard.earthmanrpg.me ansible_user=discoman ansible_ssh_private_key_file=~/.ssh/id_ed25519_wsl_python-tdd + +[cicd] +gitea.earthmanrpg.me ansible_user=root ansible_ssh_private_key_file=~/.ssh/id_ed25519_wsl_python-tdd