minor: Add Gitea to SCM role

This commit is contained in:
2026-04-08 18:05:00 -03:00
commit 8417362549
14 changed files with 572 additions and 0 deletions

View File

@@ -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

85
README.md Normal file
View File

@@ -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-<arch>` 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

66
defaults/main.yml Normal file
View File

@@ -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

6
handlers/main.yml Normal file
View File

@@ -0,0 +1,6 @@
#SPDX-License-Identifier: GPL-3.0-only
---
- name: gitea_restart
ansible.builtin.systemd:
name: '{{ gitea_name }}.service'
state: restarted

22
meta/main.yml Normal file
View File

@@ -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: []

View File

@@ -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"]

View File

@@ -0,0 +1,6 @@
#SPDX-License-Identifier: GPL-3.0-only
---
- name: Converge
hosts: all
roles:
- role: ansible_role_scm

View File

@@ -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

View File

@@ -0,0 +1,4 @@
#SPDX-License-Identifier: GPL-3.0-only
---
collections:
- name: containers.podman

View File

@@ -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"

129
tasks/main.yml Normal file
View File

@@ -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

14
templates/app.ini.j2 Normal file
View File

@@ -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 %}

View File

@@ -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

4
vars/main.yml Normal file
View File

@@ -0,0 +1,4 @@
#SPDX-License-Identifier: GPL-3.0-only
---
_download: '/tmp'
systemd_conf: '/etc/systemd/system'