task 1-4
This commit is contained in:
@@ -0,0 +1,60 @@
|
||||
---
|
||||
# Install Docker CE (rootful) from the official Docker apt repository.
|
||||
# ponytail: rootful Docker only for this PoC; rootless is explicitly deferred (see plan.md).
|
||||
|
||||
- name: Gather OS release facts
|
||||
ansible.builtin.setup:
|
||||
gather_subset:
|
||||
- distribution
|
||||
|
||||
- name: Install prerequisite packages
|
||||
ansible.builtin.apt:
|
||||
name:
|
||||
- ca-certificates
|
||||
- curl
|
||||
- gnupg
|
||||
state: present
|
||||
update_cache: true
|
||||
|
||||
- name: Create keyrings directory
|
||||
ansible.builtin.file:
|
||||
path: /etc/apt/keyrings
|
||||
state: directory
|
||||
mode: "0755"
|
||||
|
||||
- name: Download Docker GPG key
|
||||
ansible.builtin.get_url:
|
||||
url: "https://download.docker.com/linux/{{ ansible_facts['distribution'] | lower }}/gpg"
|
||||
dest: /etc/apt/keyrings/docker.asc
|
||||
mode: "0644"
|
||||
force: false
|
||||
|
||||
- name: Add Docker apt repository
|
||||
ansible.builtin.deb822_repository:
|
||||
name: docker
|
||||
types: deb
|
||||
uris: "https://download.docker.com/linux/{{ ansible_facts['distribution'] | lower }}"
|
||||
suites: "{{ ansible_facts['distribution_release'] }}"
|
||||
components: stable
|
||||
architectures: "{{ 'amd64' if ansible_facts['architecture'] == 'x86_64' else ansible_facts['architecture'] }}"
|
||||
signed_by: /etc/apt/keyrings/docker.asc
|
||||
state: present
|
||||
|
||||
- name: Update apt cache after adding Docker repository
|
||||
ansible.builtin.apt:
|
||||
update_cache: true
|
||||
|
||||
- name: Install Docker packages
|
||||
ansible.builtin.apt:
|
||||
name:
|
||||
- docker-ce
|
||||
- docker-ce-cli
|
||||
- containerd.io
|
||||
- docker-compose-plugin
|
||||
state: present
|
||||
|
||||
- name: Ensure Docker service is enabled and running
|
||||
ansible.builtin.systemd_service:
|
||||
name: docker
|
||||
enabled: true
|
||||
state: started
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
woodpecker_domain: "ci.poc.mehl.mx"
|
||||
zot_domain: "registry.ci.poc.mehl.mx"
|
||||
zot_port: 5000
|
||||
letsencrypt_email: "dqdpoq@dwqiofiw.de"
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
- name: Restart nginx
|
||||
ansible.builtin.systemd_service:
|
||||
name: nginx
|
||||
state: restarted
|
||||
@@ -0,0 +1,32 @@
|
||||
---
|
||||
# Two-phase TLS setup: nginx first serves plain HTTP (for the certbot
|
||||
# webroot challenge), then certbot obtains real certs, then the TLS
|
||||
# vhosts (proxying to Woodpecker and Zot) are deployed and nginx reloaded.
|
||||
|
||||
- name: Ensure certbot webroot directory exists
|
||||
ansible.builtin.file:
|
||||
path: "{{ certbot_webroot }}"
|
||||
state: directory
|
||||
mode: "0755"
|
||||
|
||||
- name: Include geerlingguy.nginx role (HTTP-only vhosts for ACME challenge)
|
||||
ansible.builtin.include_role:
|
||||
name: geerlingguy.nginx
|
||||
|
||||
- name: Include geerlingguy.certbot role (obtain certificates)
|
||||
ansible.builtin.include_role:
|
||||
name: geerlingguy.certbot
|
||||
|
||||
- name: Deploy TLS vhost for Woodpecker
|
||||
ansible.builtin.template:
|
||||
src: woodpecker.conf.j2
|
||||
dest: "/etc/nginx/sites-enabled/{{ woodpecker_domain }}.conf"
|
||||
mode: "0644"
|
||||
notify: Restart nginx
|
||||
|
||||
- name: Deploy TLS vhost for Zot
|
||||
ansible.builtin.template:
|
||||
src: zot.conf.j2
|
||||
dest: "/etc/nginx/sites-enabled/{{ zot_domain }}.conf"
|
||||
mode: "0644"
|
||||
notify: Restart nginx
|
||||
@@ -0,0 +1,36 @@
|
||||
server {
|
||||
listen 443 ssl;
|
||||
listen [::]:443 ssl;
|
||||
server_name {{ woodpecker_domain }};
|
||||
|
||||
ssl_certificate /etc/letsencrypt/live/{{ woodpecker_domain }}/fullchain.pem;
|
||||
ssl_certificate_key /etc/letsencrypt/live/{{ woodpecker_domain }}/privkey.pem;
|
||||
|
||||
client_max_body_size 64m;
|
||||
|
||||
location / {
|
||||
proxy_pass http://127.0.0.1:8000;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Host $http_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;
|
||||
|
||||
# Required for Woodpecker's websocket-based log streaming.
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection "upgrade";
|
||||
}
|
||||
}
|
||||
|
||||
server {
|
||||
listen 80;
|
||||
listen [::]:80;
|
||||
server_name {{ woodpecker_domain }};
|
||||
|
||||
location /.well-known/acme-challenge/ {
|
||||
root /var/www/letsencrypt;
|
||||
}
|
||||
location / {
|
||||
return 301 https://$host$request_uri;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
server {
|
||||
listen 443 ssl;
|
||||
listen [::]:443 ssl;
|
||||
server_name {{ zot_domain }};
|
||||
|
||||
ssl_certificate /etc/letsencrypt/live/{{ zot_domain }}/fullchain.pem;
|
||||
ssl_certificate_key /etc/letsencrypt/live/{{ zot_domain }}/privkey.pem;
|
||||
|
||||
# Large blob/layer uploads for container images and OCI artifacts.
|
||||
client_max_body_size 0;
|
||||
|
||||
location / {
|
||||
proxy_pass http://127.0.0.1:{{ zot_port }};
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Host $http_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;
|
||||
# Ensure a well-formed Content-Length reaches Zot instead of
|
||||
# chunked transfer encoding, which Zot rejects on blob uploads.
|
||||
proxy_request_buffering on;
|
||||
}
|
||||
}
|
||||
|
||||
server {
|
||||
listen 80;
|
||||
listen [::]:80;
|
||||
server_name {{ zot_domain }};
|
||||
|
||||
location /.well-known/acme-challenge/ {
|
||||
root /var/www/letsencrypt;
|
||||
}
|
||||
location / {
|
||||
return 301 https://$host$request_uri;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
---
|
||||
# HTTP-only vhosts, used for the initial certbot webroot challenge and
|
||||
# permanently for redirecting to HTTPS afterwards.
|
||||
nginx_vhosts:
|
||||
- server_name: "{{ woodpecker_domain }}"
|
||||
filename: "{{ woodpecker_domain }}.conf"
|
||||
root: "/var/www/letsencrypt"
|
||||
extra_parameters: |
|
||||
location /.well-known/acme-challenge/ {
|
||||
root /var/www/letsencrypt;
|
||||
}
|
||||
location / {
|
||||
return 301 https://$host$request_uri;
|
||||
}
|
||||
|
||||
- server_name: "{{ zot_domain }}"
|
||||
filename: "{{ zot_domain }}.conf"
|
||||
root: "/var/www/letsencrypt"
|
||||
extra_parameters: |
|
||||
location /.well-known/acme-challenge/ {
|
||||
root /var/www/letsencrypt;
|
||||
}
|
||||
location / {
|
||||
return 301 https://$host$request_uri;
|
||||
}
|
||||
|
||||
certbot_create_if_missing: true
|
||||
certbot_create_method: webroot
|
||||
certbot_webroot: /var/www/letsencrypt
|
||||
certbot_admin_email: "{{ letsencrypt_email }}"
|
||||
certbot_certs:
|
||||
- domains:
|
||||
- "{{ woodpecker_domain }}"
|
||||
- domains:
|
||||
- "{{ zot_domain }}"
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
woodpecker_version: "v3.18.1"
|
||||
woodpecker_host: "https://ci.poc.mehl.mx"
|
||||
woodpecker_gitea_url: "https://src.mehl.mx"
|
||||
woodpecker_admin_user: "mxmehl"
|
||||
@@ -0,0 +1,6 @@
|
||||
---
|
||||
- name: Restart woodpecker
|
||||
ansible.builtin.command:
|
||||
cmd: docker compose up -d --force-recreate
|
||||
chdir: /opt/woodpecker
|
||||
changed_when: true
|
||||
@@ -0,0 +1,29 @@
|
||||
---
|
||||
# ponytail: UID/GID 1000 is hardcoded to match the woodpecker-server/agent
|
||||
# images' built-in "woodpecker" user (checked via /etc/passwd in the image).
|
||||
# If a future image version changes this UID, ownership here must follow.
|
||||
|
||||
- name: Create woodpecker directories
|
||||
ansible.builtin.file:
|
||||
path: "{{ item }}"
|
||||
state: directory
|
||||
mode: "0755"
|
||||
owner: "1000"
|
||||
group: "1000"
|
||||
loop:
|
||||
- /opt/woodpecker
|
||||
- /opt/woodpecker/server-data
|
||||
- /opt/woodpecker/agent-config
|
||||
|
||||
- name: Deploy woodpecker compose file
|
||||
ansible.builtin.template:
|
||||
src: compose.yaml.j2
|
||||
dest: /opt/woodpecker/compose.yaml
|
||||
mode: "0600"
|
||||
notify: Restart woodpecker
|
||||
|
||||
- name: Start woodpecker via docker compose
|
||||
ansible.builtin.command:
|
||||
cmd: docker compose up -d
|
||||
chdir: /opt/woodpecker
|
||||
changed_when: true
|
||||
@@ -0,0 +1,34 @@
|
||||
services:
|
||||
woodpecker-server:
|
||||
image: woodpeckerci/woodpecker-server:{{ woodpecker_version }}
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "127.0.0.1:8000:8000"
|
||||
volumes:
|
||||
- /opt/woodpecker/server-data:/var/lib/woodpecker/
|
||||
environment:
|
||||
- WOODPECKER_OPEN=false
|
||||
- WOODPECKER_HOST={{ woodpecker_host }}
|
||||
- WOODPECKER_GITEA=true
|
||||
- WOODPECKER_GITEA_URL={{ woodpecker_gitea_url }}
|
||||
- WOODPECKER_GITEA_CLIENT={{ woodpecker_gitea_client }}
|
||||
- WOODPECKER_GITEA_SECRET={{ woodpecker_gitea_secret }}
|
||||
- WOODPECKER_AGENT_SECRET={{ woodpecker_agent_secret }}
|
||||
- WOODPECKER_ADMIN={{ woodpecker_admin_user }}
|
||||
# ponytail: only this specific plugin image is allow-listed for
|
||||
# privileged mode (needed to build images), not privileged mode in
|
||||
# general - keep this list as narrow as possible.
|
||||
- WOODPECKER_PLUGINS_PRIVILEGED=woodpeckerci/plugin-docker-buildx
|
||||
|
||||
woodpecker-agent:
|
||||
image: woodpeckerci/woodpecker-agent:{{ woodpecker_version }}
|
||||
command: agent
|
||||
restart: unless-stopped
|
||||
depends_on:
|
||||
- woodpecker-server
|
||||
volumes:
|
||||
- /opt/woodpecker/agent-config:/etc/woodpecker
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
environment:
|
||||
- WOODPECKER_SERVER=woodpecker-server:9000
|
||||
- WOODPECKER_AGENT_SECRET={{ woodpecker_agent_secret }}
|
||||
@@ -0,0 +1,8 @@
|
||||
---
|
||||
zot_version: "v2.1.6"
|
||||
zot_port: 5000
|
||||
zot_admin_user: zotadmin
|
||||
# ponytail: PoC-only default password, overridden via inventory/vault for real use.
|
||||
zot_admin_password: "changeme-poc-only"
|
||||
zot_retention_keep_count: 10
|
||||
zot_retention_keep_within: "720h"
|
||||
@@ -0,0 +1,6 @@
|
||||
---
|
||||
- name: Restart zot
|
||||
ansible.builtin.command:
|
||||
cmd: docker compose up -d --force-recreate
|
||||
chdir: /opt/zot
|
||||
changed_when: true
|
||||
@@ -0,0 +1,52 @@
|
||||
---
|
||||
# Deploy Zot OCI registry via Docker Compose, with htpasswd basic auth
|
||||
# and a simple retention policy.
|
||||
# ponytail: single zot instance, no TLS termination here (assumed behind
|
||||
# plain HTTP for the PoC); add a reverse proxy with TLS before any real use.
|
||||
|
||||
- name: Create zot directories
|
||||
ansible.builtin.file:
|
||||
path: "{{ item }}"
|
||||
state: directory
|
||||
mode: "0755"
|
||||
loop:
|
||||
- /opt/zot
|
||||
- /opt/zot/data
|
||||
- /opt/zot/auth
|
||||
|
||||
- name: Ensure apache2-utils (htpasswd) is installed
|
||||
ansible.builtin.apt:
|
||||
name: apache2-utils
|
||||
state: present
|
||||
|
||||
- name: Check if htpasswd file already exists
|
||||
ansible.builtin.stat:
|
||||
path: /opt/zot/auth/htpasswd
|
||||
register: zot_htpasswd_stat
|
||||
|
||||
- name: Generate htpasswd file for zot user
|
||||
ansible.builtin.command:
|
||||
cmd: >-
|
||||
htpasswd -cbB /opt/zot/auth/htpasswd
|
||||
{{ zot_admin_user }} {{ zot_admin_password }}
|
||||
when: not zot_htpasswd_stat.stat.exists
|
||||
changed_when: true
|
||||
|
||||
- name: Deploy zot configuration
|
||||
ansible.builtin.template:
|
||||
src: config.json.j2
|
||||
dest: /opt/zot/config.json
|
||||
mode: "0644"
|
||||
notify: Restart zot
|
||||
|
||||
- name: Deploy zot compose file
|
||||
ansible.builtin.template:
|
||||
src: compose.yaml.j2
|
||||
dest: /opt/zot/compose.yaml
|
||||
mode: "0644"
|
||||
|
||||
- name: Start zot via docker compose
|
||||
ansible.builtin.command:
|
||||
cmd: docker compose up -d
|
||||
chdir: /opt/zot
|
||||
changed_when: true
|
||||
@@ -0,0 +1,11 @@
|
||||
services:
|
||||
zot:
|
||||
image: ghcr.io/project-zot/zot-linux-amd64:{{ zot_version }}
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "127.0.0.1:{{ zot_port }}:5000"
|
||||
volumes:
|
||||
- /opt/zot/config.json:/etc/zot/config.json:ro
|
||||
- /opt/zot/auth/htpasswd:/etc/zot/htpasswd:ro
|
||||
- /opt/zot/data:/var/lib/registry
|
||||
command: serve /etc/zot/config.json
|
||||
@@ -0,0 +1,42 @@
|
||||
{
|
||||
"distSpecVersion": "1.1.1",
|
||||
"storage": {
|
||||
"rootDirectory": "/var/lib/registry",
|
||||
"retention": {
|
||||
"dryRun": false,
|
||||
"delay": "24h",
|
||||
"policies": [
|
||||
{
|
||||
"repositories": ["**"],
|
||||
"deleteReferrers": false,
|
||||
"deleteUntagged": true,
|
||||
"keepTags": [
|
||||
{
|
||||
"mostRecentlyPushedCount": {{ zot_retention_keep_count }},
|
||||
"pushedWithin": "{{ zot_retention_keep_within }}"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"http": {
|
||||
"address": "0.0.0.0",
|
||||
"port": "5000",
|
||||
"auth": {
|
||||
"htpasswd": {
|
||||
"path": "/etc/zot/htpasswd"
|
||||
}
|
||||
},
|
||||
"accessControl": {
|
||||
"repositories": {
|
||||
"**": {
|
||||
"defaultPolicy": ["read", "create", "update", "delete"]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"log": {
|
||||
"level": "info"
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user