From 84173625495543354778d14c79121fb2fcbc13e0 Mon Sep 17 00:00:00 2001 From: Luciano Giacchetta Date: Wed, 8 Apr 2026 18:05:00 -0300 Subject: [PATCH] minor: Add Gitea to SCM role --- .gitea/workflows/update-gitea-version.yml | 81 ++++++++++++++ README.md | 85 ++++++++++++++ defaults/main.yml | 66 +++++++++++ handlers/main.yml | 6 + meta/main.yml | 22 ++++ molecule/default/Dockerfile | 14 +++ molecule/default/converge.yml | 6 + molecule/default/molecule.yml | 72 ++++++++++++ molecule/default/requirements.yml | 4 + molecule/default/verify.yml | 45 ++++++++ tasks/main.yml | 129 ++++++++++++++++++++++ templates/app.ini.j2 | 14 +++ templates/gitea-service.j2 | 24 ++++ vars/main.yml | 4 + 14 files changed, 572 insertions(+) create mode 100644 .gitea/workflows/update-gitea-version.yml create mode 100644 README.md create mode 100644 defaults/main.yml create mode 100644 handlers/main.yml create mode 100644 meta/main.yml create mode 100644 molecule/default/Dockerfile create mode 100644 molecule/default/converge.yml create mode 100644 molecule/default/molecule.yml create mode 100644 molecule/default/requirements.yml create mode 100644 molecule/default/verify.yml create mode 100644 tasks/main.yml create mode 100644 templates/app.ini.j2 create mode 100644 templates/gitea-service.j2 create mode 100644 vars/main.yml diff --git a/.gitea/workflows/update-gitea-version.yml b/.gitea/workflows/update-gitea-version.yml new file mode 100644 index 0000000..68dc483 --- /dev/null +++ b/.gitea/workflows/update-gitea-version.yml @@ -0,0 +1,81 @@ +#SPDX-License-Identifier: GPL-3.0-only +--- +name: Update Gitea Version + +on: + schedule: + - cron: '0 6 * * *' # Every day at 06:00 UTC + workflow_dispatch: + pull_request: + branches: + - main + +jobs: + update-version: + runs-on: fedora-latest + steps: + - name: Add ~/.local/bin to PATH + run: echo "$HOME/.local/bin" >> "$GITEA_PATH" + + - name: Checkout + uses: actions/checkout@v6 + with: + path: ansible_role_scm + + - name: Check versions + id: check + working-directory: ansible_role_scm + run: | + LATEST=$(curl -sf https://api.github.com/repos/go-gitea/gitea/releases/latest | jq -r '.tag_name' | sed 's/^v//') + if ! [[ "$LATEST" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then + echo "::error::Refusing to bump to non-stable version '$LATEST'" + exit 1 + fi + CURRENT=$(grep "^gitea_version:" defaults/main.yml | sed "s/gitea_version: '//;s/'//") + echo "latest=$LATEST" >> "$GITHUB_OUTPUT" + if [ "$LATEST" = "$CURRENT" ]; then + echo "needs_update=false" >> "$GITHUB_OUTPUT" + echo "Already on latest: $CURRENT" + else + echo "needs_update=true" >> "$GITHUB_OUTPUT" + echo "Update available: $CURRENT -> $LATEST" + fi + + - name: Install Molecule + if: steps.check.outputs.needs_update == 'true' + run: pip install ansible molecule molecule-plugins[podman] + + - name: Install Ansible collections + if: steps.check.outputs.needs_update == 'true' + run: ansible-galaxy collection install containers.podman + + - name: Update gitea_version + if: steps.check.outputs.needs_update == 'true' + working-directory: ansible_role_scm + run: | + sed -i "s/^gitea_version: '.*'$/gitea_version: '${{ steps.check.outputs.latest }}'/" defaults/main.yml + UPDATED=$(grep "^gitea_version:" defaults/main.yml | sed "s/gitea_version: '//;s/'//") + if [ "$UPDATED" != "${{ steps.check.outputs.latest }}" ]; then + echo "::error::Failed to update gitea_version (expected '${{ steps.check.outputs.latest }}', got '$UPDATED')" + exit 1 + fi + echo "Verified: gitea_version updated to $UPDATED" + + - name: Run Molecule tests + if: steps.check.outputs.needs_update == 'true' + working-directory: ansible_role_scm + run: molecule test + + - name: Commit and push + if: steps.check.outputs.needs_update == 'true' && github.ref == 'refs/heads/main' + working-directory: ansible_role_scm + run: | + git config user.name "giabot" + git config user.email "bot@mail.gianet.us" + git remote set-url origin "https://giabot:${{ secrets.GITEA_TOKEN }}@gianet.us/engineering/ansible_role_scm.git" + git add defaults/main.yml + git commit -m "patch: update gitea_version to ${{ steps.check.outputs.latest }}" + git tag "${{ steps.check.outputs.latest }}" + git tag -f latest + git push origin main "${{ steps.check.outputs.latest }}" + git push -f origin latest diff --git a/README.md b/README.md new file mode 100644 index 0000000..73e472b --- /dev/null +++ b/README.md @@ -0,0 +1,85 @@ +# Ansible Role: SCM + + +Software Code Management role. Currently installs and manages [Gitea](https://about.gitea.com/) on Debian and Ubuntu systems. The role downloads a versioned upstream binary, keeps a previous version for quick rollback via a symlink, creates a dedicated system user, writes `app.ini` fully from variables, and manages the systemd unit. + +Requirements +------------ + +This role requires Ansible 2.12 or higher. The target system should be Debian or Ubuntu. + +Role Variables +-------------- + +The following variables are defined in `defaults/main.yml`: + +| Variable | Description | Default Value | +|----------|-------------|---------------| +| `gitea_name` | Service name (used for binary, unit, paths) | `gitea` | +| `gitea_version` | Gitea version to install (no leading `v`) | `1.25.5` | +| `gitea_arch` | Architecture suffix of the upstream release | `amd64` | +| `gitea_opt` | Install directory (holds versioned binaries + symlink) | `/opt/{{ gitea_name }}` | +| `gitea_etc` | Config directory (`app.ini` lives here) | `/etc/{{ gitea_name }}` | +| `gitea_home` | Data directory / `WorkingDirectory` | `/var/lib/{{ gitea_name }}` | +| `gitea_url` | Full download URL of the `linux-` binary | upstream GitHub release URL | +| `gitea_keep_versions` | Previous versioned binaries to keep for rollback | `1` | +| `gitea_user_create` | Whether this role should create the system user/group | `true` | +| `gitea_user` / `gitea_group` | Service user and group | `git` / `git` | +| `gitea_uid` / `gitea_gid` | Optional fixed uid/gid | unset (system-assigned) | +| `gitea_user_home` | Home directory for the service user | `/home/{{ gitea_user }}` | +| `gitea_user_shell` | Login shell for the service user | `/bin/bash` | +| `gitea_app_ini` | Dict rendered verbatim into `app.ini` | minimal sqlite3 defaults | + +### About `gitea_app_ini` + +`app.ini` is fully driven from this dictionary. Keys become INI sections; the reserved key `DEFAULT` is rendered at the top of the file **without** a section header (matching Gitea's convention). Section names with dots (e.g. `cron.update_checker`, `repository.signing`) are preserved verbatim. Override this dict in your playbook to inject any setting Gitea supports. + +Dependencies +------------ + +None. + +Example Playbook +---------------- + +```yaml +- hosts: gitea_servers + roles: + - role: ansible_role_scm + vars: + gitea_version: '1.25.5' + gitea_user: 'git' + gitea_app_ini: + DEFAULT: + APP_NAME: 'My Gitea' + RUN_USER: 'git' + WORK_PATH: '/var/lib/gitea' + RUN_MODE: 'prod' + server: + DOMAIN: 'git.example.com' + HTTP_PORT: 3000 + ROOT_URL: 'https://git.example.com/' + DISABLE_SSH: true + database: + DB_TYPE: 'postgres' + HOST: '127.0.0.1:5432' + NAME: 'giteadb' + USER: 'gitea' + PASSWD: '{{ vault_gitea_db_password }}' + SSL_MODE: 'disable' + security: + INSTALL_LOCK: true + INTERNAL_TOKEN: '{{ vault_gitea_internal_token }}' +``` + +License +------- + +GPL-3.0-only + +Author Information +------------------ + ++ Luciano Giacchetta ++ Giacchetta Networks LLC ++ https://gianet.us/engineering/ansible_role_scm diff --git a/defaults/main.yml b/defaults/main.yml new file mode 100644 index 0000000..dc71aaa --- /dev/null +++ b/defaults/main.yml @@ -0,0 +1,66 @@ +#SPDX-License-Identifier: GPL-3.0-only +--- + +## Install configuration +gitea_name: 'gitea' +gitea_version: '1.25.5' +gitea_arch: 'amd64' +gitea_opt: '/opt/{{ gitea_name }}' +gitea_etc: '/etc/{{ gitea_name }}' +gitea_home: '/var/lib/{{ gitea_name }}' +gitea_url: 'https://github.com/go-gitea/gitea/releases/download/v{{ gitea_version }}/gitea-{{ gitea_version }}-linux-{{ gitea_arch }}' + +## Keep the current + N previous versioned binaries on disk for quick rollback. +## The active symlink target and the just-installed version are always preserved. +gitea_keep_versions: 1 + +## Service user / group +## Set gitea_user_create: false if the user is provisioned by another role. +gitea_user_create: true +gitea_user: 'git' +gitea_group: 'git' +gitea_uid: ~ +gitea_gid: ~ +gitea_user_home: '/home/{{ gitea_user }}' +## Gitea's built-in SSH server (DISABLE_SSH = false) requires a real login shell +## so OpenSSH can exec `gitea serv`. Default to /bin/bash; override to /bin/false +## if you do not expose SSH git operations. +gitea_user_shell: '/bin/bash' + +## app.ini contents — fully templated from this dict. +## The special 'DEFAULT' key is rendered at the top of app.ini without a section +## header (matching Gitea's convention). All other keys are rendered as +## [section-name] preserving dots in section names (e.g. 'cron.update_checker'). +## Override this dict in your playbook to inject every setting you need. +gitea_app_ini: + DEFAULT: + APP_NAME: 'Gitea: Git with a cup of tea' + RUN_USER: '{{ gitea_user }}' + WORK_PATH: '{{ gitea_home }}' + RUN_MODE: 'prod' + server: + PROTOCOL: 'http' + DOMAIN: 'localhost' + HTTP_PORT: 3000 + ROOT_URL: 'http://localhost:3000/' + APP_DATA_PATH: '{{ gitea_home }}/data' + DISABLE_SSH: false + SSH_PORT: 22 + database: + DB_TYPE: 'sqlite3' + PATH: '{{ gitea_home }}/data/gitea.db' + repository: + ROOT: '{{ gitea_home }}/data/gitea-repositories' + session: + PROVIDER: 'file' + log: + MODE: 'console' + LEVEL: 'info' + ROOT_PATH: '{{ gitea_home }}/log' + security: + INSTALL_LOCK: true + service: + DISABLE_REGISTRATION: true + openid: + ENABLE_OPENID_SIGNIN: false + ENABLE_OPENID_SIGNUP: false diff --git a/handlers/main.yml b/handlers/main.yml new file mode 100644 index 0000000..5ad21f9 --- /dev/null +++ b/handlers/main.yml @@ -0,0 +1,6 @@ +#SPDX-License-Identifier: GPL-3.0-only +--- +- name: gitea_restart + ansible.builtin.systemd: + name: '{{ gitea_name }}.service' + state: restarted diff --git a/meta/main.yml b/meta/main.yml new file mode 100644 index 0000000..43a4757 --- /dev/null +++ b/meta/main.yml @@ -0,0 +1,22 @@ +galaxy_info: + role_name: "ansible_role_scm" + namespace: "gianet" + author: "Luciano Giacchetta" + description: "Software Code Management Role (Gitea)" + company: "Giacchetta Networks LLC" + issue_tracker_url: "https://gianet.us/engineering/ansible_role_scm/issues" + license: "GPL-3.0-only" + min_ansible_version: "2.12" + platforms: + - name: "Debian" + versions: + - "all" + - name: "Ubuntu" + versions: + - "all" + galaxy_tags: + - "scm" + - "git" + - "gitea" + +dependencies: [] diff --git a/molecule/default/Dockerfile b/molecule/default/Dockerfile new file mode 100644 index 0000000..dd844bc --- /dev/null +++ b/molecule/default/Dockerfile @@ -0,0 +1,14 @@ +ARG MOLECULE_DISTRO=docker.io/library/debian:stable +FROM ${MOLECULE_DISTRO} + +RUN apt-get update && \ + apt-get install -y --no-install-recommends \ + python3 \ + systemd \ + systemd-sysv \ + dbus \ + ca-certificates && \ + apt-get clean && \ + rm -rf /var/lib/apt/lists/* + +CMD ["/usr/sbin/init"] diff --git a/molecule/default/converge.yml b/molecule/default/converge.yml new file mode 100644 index 0000000..c184f4d --- /dev/null +++ b/molecule/default/converge.yml @@ -0,0 +1,6 @@ +#SPDX-License-Identifier: GPL-3.0-only +--- +- name: Converge + hosts: all + roles: + - role: ansible_role_scm diff --git a/molecule/default/molecule.yml b/molecule/default/molecule.yml new file mode 100644 index 0000000..e1971b9 --- /dev/null +++ b/molecule/default/molecule.yml @@ -0,0 +1,72 @@ +#SPDX-License-Identifier: GPL-3.0-only +--- +dependency: + name: galaxy + options: + requirements-file: molecule/default/requirements.yml + +driver: + name: podman + +platforms: + - name: debian-stable + image: docker.io/library/debian:stable + pre_build_image: false + dockerfile: Dockerfile + buildargs: + MOLECULE_DISTRO: docker.io/library/debian:stable + privileged: true + systemd: always + command: /usr/sbin/init + + - name: debian-oldstable + image: docker.io/library/debian:oldstable + pre_build_image: false + dockerfile: Dockerfile + buildargs: + MOLECULE_DISTRO: docker.io/library/debian:oldstable + privileged: true + systemd: always + command: /usr/sbin/init + + - name: ubuntu-latest + image: docker.io/library/ubuntu:latest + pre_build_image: false + dockerfile: Dockerfile + buildargs: + MOLECULE_DISTRO: docker.io/library/ubuntu:latest + privileged: true + systemd: always + command: /usr/sbin/init + + - name: ubuntu-jammy + image: docker.io/library/ubuntu:jammy + pre_build_image: false + dockerfile: Dockerfile + buildargs: + MOLECULE_DISTRO: docker.io/library/ubuntu:jammy + privileged: true + systemd: always + command: /usr/sbin/init + +provisioner: + name: ansible + env: + ANSIBLE_ROLES_PATH: "${MOLECULE_PROJECT_DIRECTORY}/.." + playbooks: + converge: converge.yml + verify: verify.yml + +scenario: + test_sequence: + - dependency + - destroy + - syntax + - create + - converge + - idempotence + - verify + - destroy + +verifier: + name: ansible diff --git a/molecule/default/requirements.yml b/molecule/default/requirements.yml new file mode 100644 index 0000000..ad7a05e --- /dev/null +++ b/molecule/default/requirements.yml @@ -0,0 +1,4 @@ +#SPDX-License-Identifier: GPL-3.0-only +--- +collections: + - name: containers.podman diff --git a/molecule/default/verify.yml b/molecule/default/verify.yml new file mode 100644 index 0000000..6ad7e9f --- /dev/null +++ b/molecule/default/verify.yml @@ -0,0 +1,45 @@ +#SPDX-License-Identifier: GPL-3.0-only +--- +- name: Verify + hosts: all + tasks: + - name: Check gitea symlink + ansible.builtin.stat: + path: /opt/gitea/gitea + register: gitea_symlink + + - name: Assert gitea symlink points at a versioned binary + ansible.builtin.assert: + that: + - gitea_symlink.stat.exists + - gitea_symlink.stat.islnk + - gitea_symlink.stat.lnk_source is match('/opt/gitea/gitea-.*-linux-.*') + + - name: Check gitea versioned binary + ansible.builtin.stat: + path: '{{ gitea_symlink.stat.lnk_source }}' + register: gitea_binary + + - name: Assert gitea binary is executable + ansible.builtin.assert: + that: + - gitea_binary.stat.exists + - gitea_binary.stat.executable + + - name: Check app.ini exists + ansible.builtin.stat: + path: /etc/gitea/app.ini + register: gitea_ini + + - name: Assert app.ini exists + ansible.builtin.assert: + that: + - gitea_ini.stat.exists + + - name: Gather service facts + ansible.builtin.service_facts: + + - name: Assert gitea service is present + ansible.builtin.assert: + that: + - "'gitea.service' in ansible_facts.services" diff --git a/tasks/main.yml b/tasks/main.yml new file mode 100644 index 0000000..032aca7 --- /dev/null +++ b/tasks/main.yml @@ -0,0 +1,129 @@ +#SPDX-License-Identifier: GPL-3.0-only +--- +- name: "Create Gitea System Group" + when: gitea_user_create + ansible.builtin.group: + name: '{{ gitea_group }}' + gid: '{{ gitea_gid | default(omit, true) }}' + system: true + +- name: "Create Gitea System User" + when: gitea_user_create + ansible.builtin.user: + name: '{{ gitea_user }}' + group: '{{ gitea_group }}' + uid: '{{ gitea_uid | default(omit, true) }}' + home: '{{ gitea_user_home }}' + shell: '{{ gitea_user_shell }}' + system: true + create_home: true + +- name: "Create Install Folder" + ansible.builtin.file: + path: '{{ gitea_opt }}' + state: directory + owner: '{{ gitea_user }}' + group: '{{ gitea_group }}' + mode: '0755' + +- name: "Create Config Folder" + ansible.builtin.file: + path: '{{ gitea_etc }}' + state: directory + owner: '{{ gitea_user }}' + group: '{{ gitea_group }}' + mode: '0750' + +- name: "Create Data Folders" + ansible.builtin.file: + path: '{{ item }}' + state: directory + owner: '{{ gitea_user }}' + group: '{{ gitea_group }}' + mode: '0750' + loop: + - '{{ gitea_home }}' + - '{{ gitea_home }}/custom' + - '{{ gitea_home }}/data' + - '{{ gitea_home }}/log' + +- name: "Download Versioned Binary" + register: download_version + ansible.builtin.get_url: + url: '{{ gitea_url }}' + dest: '{{ gitea_opt }}/gitea-{{ gitea_version }}-linux-{{ gitea_arch }}' + checksum: 'sha256:{{ gitea_url }}.sha256' + owner: '{{ gitea_user }}' + group: '{{ gitea_group }}' + mode: '0755' + +- name: "Symlink Active Binary" + register: gitea_symlink + notify: gitea_restart + ansible.builtin.file: + src: '{{ gitea_opt }}/gitea-{{ gitea_version }}-linux-{{ gitea_arch }}' + dest: '{{ gitea_opt }}/{{ gitea_name }}' + state: link + owner: '{{ gitea_user }}' + group: '{{ gitea_group }}' + follow: false + +- name: "List Installed Versioned Binaries" + ansible.builtin.find: + paths: '{{ gitea_opt }}' + patterns: 'gitea-*-linux-{{ gitea_arch }}' + file_type: file + recurse: false + register: gitea_installed_binaries + +- name: "Compute Versioned Binaries To Prune" + ansible.builtin.set_fact: + gitea_binaries_to_prune: >- + {{ + ( + gitea_installed_binaries.files + | sort(attribute='mtime', reverse=true) + | rejectattr('path', 'equalto', gitea_opt ~ '/gitea-' ~ gitea_version ~ '-linux-' ~ gitea_arch) + | list + )[gitea_keep_versions:] + }} + +- name: "Prune Old Versioned Binaries" + ansible.builtin.file: + path: '{{ item.path }}' + state: absent + loop: '{{ gitea_binaries_to_prune }}' + loop_control: + label: '{{ item.path }}' + +- name: "Template App Config" + notify: gitea_restart + ansible.builtin.template: + src: '../templates/app.ini.j2' + dest: '{{ gitea_etc }}/app.ini' + owner: '{{ gitea_user }}' + group: '{{ gitea_group }}' + mode: '0640' + backup: false + +- name: "Template Gitea Service" + register: template_gitea_service + ansible.builtin.template: + src: '../templates/gitea-service.j2' + dest: '{{ systemd_conf }}/{{ gitea_name }}.service' + owner: 'root' + group: 'root' + mode: '0644' + backup: false + +- name: "Enable Gitea Service" + when: template_gitea_service.changed + ansible.builtin.systemd: + name: '{{ gitea_name }}.service' + daemon_reload: true + enabled: true + +- name: "Start Gitea Service" + ansible.builtin.systemd: + name: '{{ gitea_name }}.service' + state: started diff --git a/templates/app.ini.j2 b/templates/app.ini.j2 new file mode 100644 index 0000000..1ea6195 --- /dev/null +++ b/templates/app.ini.j2 @@ -0,0 +1,14 @@ +; {{ ansible_managed }} +; SPDX-License-Identifier: GPL-3.0-only +{% if 'DEFAULT' in gitea_app_ini %} +{% for key, value in gitea_app_ini['DEFAULT'].items() %} +{{ key }} = {{ value }} +{% endfor %} +{% endif %} +{% for section, entries in gitea_app_ini.items() if section != 'DEFAULT' %} + +[{{ section }}] +{% for key, value in entries.items() %} +{{ key }} = {{ value }} +{% endfor %} +{% endfor %} diff --git a/templates/gitea-service.j2 b/templates/gitea-service.j2 new file mode 100644 index 0000000..a618cd7 --- /dev/null +++ b/templates/gitea-service.j2 @@ -0,0 +1,24 @@ +# {{ ansible_managed }} +# SPDX-License-Identifier: GPL-3.0-only +[Unit] +Description=Gitea (Git with a cup of tea) +After=network-online.target +Wants=network-online.target +AssertFileIsExecutable={{ gitea_opt }}/{{ gitea_name }} +AssertPathExists={{ gitea_etc }}/app.ini + +[Service] +RestartSec=2s +Type=simple +User={{ gitea_user }} +Group={{ gitea_group }} +WorkingDirectory={{ gitea_home }} +ExecStart={{ gitea_opt }}/{{ gitea_name }} web --config {{ gitea_etc }}/app.ini +Restart=always +Environment=USER={{ gitea_user }} HOME={{ gitea_user_home }} GITEA_WORK_DIR={{ gitea_home }} +# Uncomment to bind ports < 1024 without running as root +#CapabilityBoundingSet=CAP_NET_BIND_SERVICE +#AmbientCapabilities=CAP_NET_BIND_SERVICE + +[Install] +WantedBy=multi-user.target diff --git a/vars/main.yml b/vars/main.yml new file mode 100644 index 0000000..5b74b9d --- /dev/null +++ b/vars/main.yml @@ -0,0 +1,4 @@ +#SPDX-License-Identifier: GPL-3.0-only +--- +_download: '/tmp' +systemd_conf: '/etc/systemd/system'