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 }}"
|
||||
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"
|
||||
|
|
|
|||
|
|
@ -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 }}"
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -24,3 +24,4 @@
|
|||
- plane/media
|
||||
- syncthing/config
|
||||
- syncthing/data
|
||||
- act_runner
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
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:
|
||||
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
|
||||
|
|
|
|||
|
|
@ -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 }}
|
||||
|
|
|
|||
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
|
||||
|
||||
providers:
|
||||
docker:
|
||||
exposedByDefault: false
|
||||
network: backend
|
||||
file:
|
||||
directory: /etc/traefik/dynamic
|
||||
watch: true
|
||||
|
|
|
|||
Loading…
Reference in a new issue