Add Forgejo Actions CI/CD with act_runner
- Add gitea/act_runner:0.3.0 to docker-compose stack on runner-jobs network - Add act_runner config template and directory provisioning - Add FORGEJO_RUNNER_TOKEN to env template - Add CI deploy SSH public key to authorized_keys via base role - Create .forgejo/workflows/deploy.yml: syntax-check on PR, deploy on push to master - Add .claude/launch.json with ansible-playbook configurations Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
652737239d
commit
d2d5f12d5a
13 changed files with 248 additions and 6 deletions
29
.claude/launch.json
Normal file
29
.claude/launch.json
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
{
|
||||||
|
"version": "0.0.1",
|
||||||
|
"configurations": [
|
||||||
|
{
|
||||||
|
"name": "Deploy (dry run)",
|
||||||
|
"runtimeExecutable": "ansible-playbook",
|
||||||
|
"runtimeArgs": ["playbooks/deploy.yml", "--check", "-i", "inventory/"],
|
||||||
|
"port": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Deploy",
|
||||||
|
"runtimeExecutable": "ansible-playbook",
|
||||||
|
"runtimeArgs": ["playbooks/deploy.yml", "-i", "inventory/"],
|
||||||
|
"port": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Syntax check",
|
||||||
|
"runtimeExecutable": "ansible-playbook",
|
||||||
|
"runtimeArgs": ["playbooks/deploy.yml", "--syntax-check", "-i", "inventory/"],
|
||||||
|
"port": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Bootstrap (first-time, as root)",
|
||||||
|
"runtimeExecutable": "ansible-playbook",
|
||||||
|
"runtimeArgs": ["playbooks/bootstrap.yml", "-u", "root", "-i", "inventory/"],
|
||||||
|
"port": 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
51
.forgejo/workflows/deploy.yml
Normal file
51
.forgejo/workflows/deploy.yml
Normal file
|
|
@ -0,0 +1,51 @@
|
||||||
|
name: CI/CD
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [master]
|
||||||
|
pull_request:
|
||||||
|
branches: [master]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
syntax-check:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
container:
|
||||||
|
image: python:3.12-slim
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Install ansible
|
||||||
|
run: pip install ansible --quiet
|
||||||
|
|
||||||
|
- name: Syntax check
|
||||||
|
run: ansible-playbook playbooks/deploy.yml --syntax-check -i inventory/
|
||||||
|
|
||||||
|
deploy:
|
||||||
|
needs: syntax-check
|
||||||
|
if: ${{ github.ref == 'refs/heads/master' && github.event_name == 'push' }}
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
container:
|
||||||
|
image: python:3.12-slim
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: |
|
||||||
|
apt-get update -qq && apt-get install -y openssh-client --no-install-recommends
|
||||||
|
pip install ansible --quiet
|
||||||
|
ansible-galaxy collection install ansible.posix community.general community.docker --quiet
|
||||||
|
|
||||||
|
- name: Configure SSH
|
||||||
|
run: |
|
||||||
|
mkdir -p ~/.ssh
|
||||||
|
echo "${{ secrets.SSH_PRIVATE_KEY }}" > ~/.ssh/id_ed25519
|
||||||
|
chmod 600 ~/.ssh/id_ed25519
|
||||||
|
ssh-keyscan -p 22 87.249.49.32 >> ~/.ssh/known_hosts
|
||||||
|
|
||||||
|
- name: Write vault password
|
||||||
|
run: |
|
||||||
|
echo "${{ secrets.VAULT_PASSWORD }}" > ~/.vault-password-file
|
||||||
|
chmod 600 ~/.vault-password-file
|
||||||
|
|
||||||
|
- name: Deploy
|
||||||
|
run: ansible-playbook playbooks/deploy.yml -i inventory/
|
||||||
|
|
@ -23,3 +23,7 @@ plane_secret_key: "{{ vault_plane_secret_key }}"
|
||||||
plane_minio_password: "{{ vault_plane_minio_password }}"
|
plane_minio_password: "{{ vault_plane_minio_password }}"
|
||||||
traefik_dashboard_htpasswd: "{{ vault_traefik_dashboard_htpasswd }}"
|
traefik_dashboard_htpasswd: "{{ vault_traefik_dashboard_htpasswd }}"
|
||||||
syncthing_basic_auth_htpasswd: "{{ vault_syncthing_basic_auth_htpasswd }}"
|
syncthing_basic_auth_htpasswd: "{{ vault_syncthing_basic_auth_htpasswd }}"
|
||||||
|
forgejo_runner_token: "{{ vault_forgejo_runner_token }}"
|
||||||
|
|
||||||
|
# CI/CD deploy key (public key — not a secret)
|
||||||
|
ci_deploy_pubkey: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIF6kK8+/9cMo9sFUIQAupPfcD3A6UixmAzB0r8jAf0kz ci-deploy@forgejo-runner"
|
||||||
|
|
|
||||||
|
|
@ -20,3 +20,9 @@
|
||||||
create: true
|
create: true
|
||||||
mode: "0440"
|
mode: "0440"
|
||||||
validate: "visudo -cf %s"
|
validate: "visudo -cf %s"
|
||||||
|
|
||||||
|
- name: Add CI deploy public key to authorized_keys
|
||||||
|
ansible.posix.authorized_key:
|
||||||
|
user: "{{ deploy_user }}"
|
||||||
|
state: present
|
||||||
|
key: "{{ ci_deploy_pubkey }}"
|
||||||
|
|
|
||||||
|
|
@ -17,3 +17,4 @@ plane_redis_image: "redis:7-alpine"
|
||||||
# Рекомендуется перейти на alpine/minio или собирать из исходников.
|
# Рекомендуется перейти на alpine/minio или собирать из исходников.
|
||||||
plane_minio_image: "minio/minio:RELEASE.2025-04-22T22-12-26Z" # https://hub.docker.com/r/minio/minio/tags
|
plane_minio_image: "minio/minio:RELEASE.2025-04-22T22-12-26Z" # https://hub.docker.com/r/minio/minio/tags
|
||||||
syncthing_image: "syncthing/syncthing:1.27" # https://hub.docker.com/r/syncthing/syncthing/tags
|
syncthing_image: "syncthing/syncthing:1.27" # https://hub.docker.com/r/syncthing/syncthing/tags
|
||||||
|
act_runner_image: "gitea/act_runner:0.3.0" # https://hub.docker.com/r/gitea/act_runner/tags
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,24 @@
|
||||||
mode: "0644"
|
mode: "0644"
|
||||||
notify: Restart stack
|
notify: Restart stack
|
||||||
|
|
||||||
|
- name: Deploy Traefik dynamic routes
|
||||||
|
ansible.builtin.template:
|
||||||
|
src: traefik/dynamic/routes.yml.j2
|
||||||
|
dest: "{{ services_root }}/traefik/dynamic/routes.yml"
|
||||||
|
owner: "{{ deploy_user }}"
|
||||||
|
group: "{{ deploy_group }}"
|
||||||
|
mode: "0644"
|
||||||
|
notify: Restart stack
|
||||||
|
|
||||||
|
- name: Deploy act_runner config
|
||||||
|
ansible.builtin.template:
|
||||||
|
src: act_runner_config.yaml.j2
|
||||||
|
dest: "{{ services_root }}/act_runner/config.yaml"
|
||||||
|
owner: "{{ deploy_user }}"
|
||||||
|
group: "{{ deploy_group }}"
|
||||||
|
mode: "0644"
|
||||||
|
notify: Restart stack
|
||||||
|
|
||||||
- name: Create acme.json for Let's Encrypt certificates
|
- name: Create acme.json for Let's Encrypt certificates
|
||||||
ansible.builtin.file:
|
ansible.builtin.file:
|
||||||
path: "{{ services_root }}/traefik/acme.json"
|
path: "{{ services_root }}/traefik/acme.json"
|
||||||
|
|
|
||||||
|
|
@ -24,3 +24,4 @@
|
||||||
- plane/media
|
- plane/media
|
||||||
- syncthing/config
|
- syncthing/config
|
||||||
- syncthing/data
|
- syncthing/data
|
||||||
|
- act_runner
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@
|
||||||
- "{{ plane_redis_image }}"
|
- "{{ plane_redis_image }}"
|
||||||
- "{{ plane_minio_image }}"
|
- "{{ plane_minio_image }}"
|
||||||
- "{{ syncthing_image }}"
|
- "{{ syncthing_image }}"
|
||||||
|
- "{{ act_runner_image }}"
|
||||||
register: pull_result
|
register: pull_result
|
||||||
changed_when: "'Status: Downloaded newer image' in pull_result.stdout"
|
changed_when: "'Status: Downloaded newer image' in pull_result.stdout"
|
||||||
retries: 5
|
retries: 5
|
||||||
|
|
|
||||||
23
roles/services/templates/act_runner_config.yaml.j2
Normal file
23
roles/services/templates/act_runner_config.yaml.j2
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
# Generated by Ansible — do not edit manually
|
||||||
|
log:
|
||||||
|
level: info
|
||||||
|
|
||||||
|
runner:
|
||||||
|
capacity: 2
|
||||||
|
timeout: 3h
|
||||||
|
insecure: false
|
||||||
|
|
||||||
|
cache:
|
||||||
|
enabled: true
|
||||||
|
|
||||||
|
container:
|
||||||
|
# Job containers run on runner-jobs network (has internet access)
|
||||||
|
network: runner-jobs
|
||||||
|
privileged: false
|
||||||
|
valid_volumes:
|
||||||
|
- "**"
|
||||||
|
docker_host: ""
|
||||||
|
force_pull: false
|
||||||
|
|
||||||
|
host:
|
||||||
|
workdir_parent:
|
||||||
|
|
@ -13,9 +13,13 @@ networks:
|
||||||
forgejo-db:
|
forgejo-db:
|
||||||
driver: bridge
|
driver: bridge
|
||||||
internal: true
|
internal: true
|
||||||
|
forgejo-ssh:
|
||||||
|
driver: bridge
|
||||||
plane-internal:
|
plane-internal:
|
||||||
driver: bridge
|
driver: bridge
|
||||||
internal: true
|
internal: true
|
||||||
|
runner-jobs:
|
||||||
|
driver: bridge
|
||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
vaultwarden_data:
|
vaultwarden_data:
|
||||||
|
|
@ -27,6 +31,7 @@ volumes:
|
||||||
plane_media:
|
plane_media:
|
||||||
syncthing_config:
|
syncthing_config:
|
||||||
syncthing_data:
|
syncthing_data:
|
||||||
|
act_runner_data:
|
||||||
|
|
||||||
services:
|
services:
|
||||||
|
|
||||||
|
|
@ -39,13 +44,10 @@ services:
|
||||||
ports:
|
ports:
|
||||||
- "80:80"
|
- "80:80"
|
||||||
- "443:443"
|
- "443:443"
|
||||||
environment:
|
|
||||||
- DOCKER_API_VERSION=1.45
|
|
||||||
networks:
|
networks:
|
||||||
- proxy
|
- proxy
|
||||||
- backend
|
- backend
|
||||||
volumes:
|
volumes:
|
||||||
- /var/run/docker.sock:/var/run/docker.sock:ro
|
|
||||||
- {{ services_root }}/traefik/traefik.yml:/etc/traefik/traefik.yml:ro
|
- {{ services_root }}/traefik/traefik.yml:/etc/traefik/traefik.yml:ro
|
||||||
- {{ services_root }}/traefik/dynamic:/etc/traefik/dynamic:ro
|
- {{ services_root }}/traefik/dynamic:/etc/traefik/dynamic:ro
|
||||||
- {{ services_root }}/traefik/acme.json:/acme/acme.json
|
- {{ services_root }}/traefik/acme.json:/acme/acme.json
|
||||||
|
|
@ -93,6 +95,7 @@ services:
|
||||||
networks:
|
networks:
|
||||||
- backend
|
- backend
|
||||||
- forgejo-db
|
- forgejo-db
|
||||||
|
- forgejo-ssh
|
||||||
volumes:
|
volumes:
|
||||||
- forgejo_data:/data
|
- forgejo_data:/data
|
||||||
- /etc/timezone:/etc/timezone:ro
|
- /etc/timezone:/etc/timezone:ro
|
||||||
|
|
@ -332,3 +335,25 @@ services:
|
||||||
- "traefik.http.routers.syncthing.middlewares=syncthing-auth"
|
- "traefik.http.routers.syncthing.middlewares=syncthing-auth"
|
||||||
- "traefik.http.middlewares.syncthing-auth.basicauth.users={{ syncthing_basic_auth_htpasswd }}"
|
- "traefik.http.middlewares.syncthing-auth.basicauth.users={{ syncthing_basic_auth_htpasswd }}"
|
||||||
- "traefik.http.services.syncthing.loadbalancer.server.port=8384"
|
- "traefik.http.services.syncthing.loadbalancer.server.port=8384"
|
||||||
|
|
||||||
|
# ── Forgejo Actions Runner ─────────────────────────────────────────────────
|
||||||
|
# backend — для связи с Forgejo по внутренней сети (http://forgejo:3000)
|
||||||
|
# runner-jobs — сеть с интернет-доступом для job-контейнеров
|
||||||
|
act_runner:
|
||||||
|
image: {{ act_runner_image }}
|
||||||
|
container_name: act_runner
|
||||||
|
restart: unless-stopped
|
||||||
|
depends_on:
|
||||||
|
- forgejo
|
||||||
|
environment:
|
||||||
|
- GITEA_INSTANCE_URL=http://forgejo:3000
|
||||||
|
- GITEA_RUNNER_REGISTRATION_TOKEN=${FORGEJO_RUNNER_TOKEN}
|
||||||
|
- GITEA_RUNNER_NAME=vps-runner
|
||||||
|
- CONFIG_FILE=/data/config.yaml
|
||||||
|
volumes:
|
||||||
|
- act_runner_data:/data
|
||||||
|
- /var/run/docker.sock:/var/run/docker.sock
|
||||||
|
- {{ services_root }}/act_runner/config.yaml:/data/config.yaml:ro
|
||||||
|
networks:
|
||||||
|
- backend
|
||||||
|
- runner-jobs
|
||||||
|
|
|
||||||
|
|
@ -10,3 +10,4 @@ DOMAIN_GIT={{ domain_git }}
|
||||||
DOMAIN_PLANE={{ domain_plane }}
|
DOMAIN_PLANE={{ domain_plane }}
|
||||||
DOMAIN_SYNC={{ domain_sync }}
|
DOMAIN_SYNC={{ domain_sync }}
|
||||||
DOMAIN_TRAEFIK={{ domain_traefik }}
|
DOMAIN_TRAEFIK={{ domain_traefik }}
|
||||||
|
FORGEJO_RUNNER_TOKEN={{ forgejo_runner_token }}
|
||||||
|
|
|
||||||
85
roles/services/templates/traefik/dynamic/routes.yml.j2
Normal file
85
roles/services/templates/traefik/dynamic/routes.yml.j2
Normal file
|
|
@ -0,0 +1,85 @@
|
||||||
|
# Traefik dynamic routing config — generated by Ansible
|
||||||
|
# Do not edit manually; re-run ansible-playbook deploy.yml
|
||||||
|
|
||||||
|
http:
|
||||||
|
routers:
|
||||||
|
traefik-dashboard:
|
||||||
|
rule: "Host(`{{ domain_traefik }}`)"
|
||||||
|
entrypoints: [websecure]
|
||||||
|
tls:
|
||||||
|
certresolver: letsencrypt
|
||||||
|
service: api@internal
|
||||||
|
middlewares: [traefik-auth]
|
||||||
|
|
||||||
|
vaultwarden:
|
||||||
|
rule: "Host(`{{ domain_vault }}`)"
|
||||||
|
entrypoints: [websecure]
|
||||||
|
tls:
|
||||||
|
certresolver: letsencrypt
|
||||||
|
service: vaultwarden
|
||||||
|
|
||||||
|
forgejo:
|
||||||
|
rule: "Host(`{{ domain_git }}`)"
|
||||||
|
entrypoints: [websecure]
|
||||||
|
tls:
|
||||||
|
certresolver: letsencrypt
|
||||||
|
service: forgejo
|
||||||
|
|
||||||
|
plane-api:
|
||||||
|
rule: "Host(`{{ domain_plane }}`) && (PathPrefix(`/api/`) || PathPrefix(`/auth/`))"
|
||||||
|
entrypoints: [websecure]
|
||||||
|
tls:
|
||||||
|
certresolver: letsencrypt
|
||||||
|
service: plane-api
|
||||||
|
|
||||||
|
plane:
|
||||||
|
rule: "Host(`{{ domain_plane }}`)"
|
||||||
|
entrypoints: [websecure]
|
||||||
|
tls:
|
||||||
|
certresolver: letsencrypt
|
||||||
|
service: plane-web
|
||||||
|
|
||||||
|
syncthing:
|
||||||
|
rule: "Host(`{{ domain_sync }}`)"
|
||||||
|
entrypoints: [websecure]
|
||||||
|
tls:
|
||||||
|
certresolver: letsencrypt
|
||||||
|
service: syncthing
|
||||||
|
middlewares: [syncthing-auth]
|
||||||
|
|
||||||
|
services:
|
||||||
|
vaultwarden:
|
||||||
|
loadBalancer:
|
||||||
|
servers:
|
||||||
|
- url: "http://vaultwarden:80"
|
||||||
|
|
||||||
|
forgejo:
|
||||||
|
loadBalancer:
|
||||||
|
servers:
|
||||||
|
- url: "http://forgejo:3000"
|
||||||
|
|
||||||
|
plane-api:
|
||||||
|
loadBalancer:
|
||||||
|
servers:
|
||||||
|
- url: "http://plane-api:8000"
|
||||||
|
|
||||||
|
plane-web:
|
||||||
|
loadBalancer:
|
||||||
|
servers:
|
||||||
|
- url: "http://plane-web:3000"
|
||||||
|
|
||||||
|
syncthing:
|
||||||
|
loadBalancer:
|
||||||
|
servers:
|
||||||
|
- url: "http://syncthing:8384"
|
||||||
|
|
||||||
|
middlewares:
|
||||||
|
traefik-auth:
|
||||||
|
basicAuth:
|
||||||
|
users:
|
||||||
|
- "{{ traefik_dashboard_htpasswd }}"
|
||||||
|
|
||||||
|
syncthing-auth:
|
||||||
|
basicAuth:
|
||||||
|
users:
|
||||||
|
- "{{ syncthing_basic_auth_htpasswd }}"
|
||||||
|
|
@ -34,9 +34,6 @@ certificatesResolvers:
|
||||||
entryPoint: web
|
entryPoint: web
|
||||||
|
|
||||||
providers:
|
providers:
|
||||||
docker:
|
|
||||||
exposedByDefault: false
|
|
||||||
network: backend
|
|
||||||
file:
|
file:
|
||||||
directory: /etc/traefik/dynamic
|
directory: /etc/traefik/dynamic
|
||||||
watch: true
|
watch: true
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue