Add Forgejo Actions CI/CD with act_runner
Some checks failed
CI/CD / syntax-check (push) Failing after 12s
CI/CD / deploy (push) Has been skipped

- 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:
jack 2026-03-21 21:28:15 +07:00
parent 652737239d
commit d2d5f12d5a
13 changed files with 248 additions and 6 deletions

29
.claude/launch.json Normal file
View 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
}
]
}

View 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/

View file

@ -23,3 +23,7 @@ plane_secret_key: "{{ vault_plane_secret_key }}"
plane_minio_password: "{{ vault_plane_minio_password }}"
traefik_dashboard_htpasswd: "{{ vault_traefik_dashboard_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"

View file

@ -20,3 +20,9 @@
create: true
mode: "0440"
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 }}"

View file

@ -17,3 +17,4 @@ plane_redis_image: "redis:7-alpine"
# Рекомендуется перейти на alpine/minio или собирать из исходников.
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
act_runner_image: "gitea/act_runner:0.3.0" # https://hub.docker.com/r/gitea/act_runner/tags

View file

@ -26,6 +26,24 @@
mode: "0644"
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
ansible.builtin.file:
path: "{{ services_root }}/traefik/acme.json"

View file

@ -24,3 +24,4 @@
- plane/media
- syncthing/config
- syncthing/data
- act_runner

View file

@ -15,6 +15,7 @@
- "{{ plane_redis_image }}"
- "{{ plane_minio_image }}"
- "{{ syncthing_image }}"
- "{{ act_runner_image }}"
register: pull_result
changed_when: "'Status: Downloaded newer image' in pull_result.stdout"
retries: 5

View 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:

View file

@ -13,9 +13,13 @@ networks:
forgejo-db:
driver: bridge
internal: true
forgejo-ssh:
driver: bridge
plane-internal:
driver: bridge
internal: true
runner-jobs:
driver: bridge
volumes:
vaultwarden_data:
@ -27,6 +31,7 @@ volumes:
plane_media:
syncthing_config:
syncthing_data:
act_runner_data:
services:
@ -39,13 +44,10 @@ services:
ports:
- "80:80"
- "443:443"
environment:
- DOCKER_API_VERSION=1.45
networks:
- proxy
- backend
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
- {{ services_root }}/traefik/traefik.yml:/etc/traefik/traefik.yml:ro
- {{ services_root }}/traefik/dynamic:/etc/traefik/dynamic:ro
- {{ services_root }}/traefik/acme.json:/acme/acme.json
@ -93,6 +95,7 @@ services:
networks:
- backend
- forgejo-db
- forgejo-ssh
volumes:
- forgejo_data:/data
- /etc/timezone:/etc/timezone:ro
@ -332,3 +335,25 @@ services:
- "traefik.http.routers.syncthing.middlewares=syncthing-auth"
- "traefik.http.middlewares.syncthing-auth.basicauth.users={{ syncthing_basic_auth_htpasswd }}"
- "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

View file

@ -10,3 +10,4 @@ DOMAIN_GIT={{ domain_git }}
DOMAIN_PLANE={{ domain_plane }}
DOMAIN_SYNC={{ domain_sync }}
DOMAIN_TRAEFIK={{ domain_traefik }}
FORGEJO_RUNNER_TOKEN={{ forgejo_runner_token }}

View 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 }}"

View file

@ -34,9 +34,6 @@ certificatesResolvers:
entryPoint: web
providers:
docker:
exposedByDefault: false
network: backend
file:
directory: /etc/traefik/dynamic
watch: true