Vendor Galaxy Roles and Collections
Some checks failed
/ Ansible Lint (push) Failing after 5m45s
/ Ansible Lint (pull_request) Failing after 4m59s

This commit is contained in:
Stefan Bethke 2026-02-06 22:07:16 +01:00
commit 2aed20393f
3553 changed files with 387444 additions and 2 deletions

View file

@ -0,0 +1,19 @@
debops.reprepro - Manage local APT repositories using Ansible
Copyright (C) 2014-2021 Maciej Delmanowski <drybjed@gmail.com>
Copyright (C) 2015-2021 DebOps <https://debops.org/>
SPDX-License-Identifier: GPL-3.0-only
This Ansible role is part of DebOps.
DebOps is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License version 3, as
published by the Free Software Foundation.
DebOps is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with DebOps. If not, see https://www.gnu.org/licenses/.

View file

@ -0,0 +1,388 @@
---
# .. vim: foldmarker=[[[,]]]:foldmethod=marker
# .. Copyright (C) 2014-2021 Maciej Delmanowski <drybjed@gmail.com>
# .. Copyright (C) 2015-2022 DebOps <https://debops.org/>
# .. SPDX-License-Identifier: GPL-3.0-only
# .. _reprepro__ref_defaults:
# debops.reprepro default variables
# =================================
# .. contents:: Sections
# :local:
#
# .. include:: ../../../../includes/global.rst
# APT packages [[[
# ----------------
# .. envvar:: reprepro__base_packages [[[
#
# The list of default APT packages to install for repository management.
reprepro__base_packages: [ 'reprepro', 'dpkg-dev' ]
# ]]]
# .. envvar:: reprepro__packages [[[
#
# List of additional APT packages to install with :command:`reprepro`.
reprepro__packages: []
# ]]]
# ]]]
# UNIX environment [[[
# --------------------
# .. envvar:: reprepro__user [[[
#
# UNIX system account which manages local APT repositories.
reprepro__user: 'reprepro'
# ]]]
# .. envvar:: reprepro__group [[[
#
# UNIX system group which manages local APT repositories.
reprepro__group: 'reprepro'
# ]]]
# .. envvar:: reprepro__additional_groups [[[
#
# List of additional UNIX groups which the :command:`reprepro` account should
# be a member of.
reprepro__additional_groups:
# Allow direct SSH logins for administrators
- '{{ ansible_local.system_groups.local_prefix + "sshusers" }}'
# ]]]
# .. envvar:: reprepro__home [[[
#
# Path where :command:`reprepro` home directory is stored.
reprepro__home: '{{ (ansible_local.fhs.home | d("/var/local"))
+ "/" + reprepro__user }}'
# ]]]
# .. envvar:: reprepro__comment [[[
#
# The GECOS field of the :command:`reprepro` UNIX account.
reprepro__comment: 'Local APT repositories'
# ]]]
# .. envvar:: reprepro__data_root [[[
#
# Path where :command:`reprepro` data files are stored, separated by instances.
reprepro__data_root: '{{ reprepro__home + "/repositories" }}'
# ]]]
# .. envvar:: reprepro__public_root [[[
#
# Path where public contents of the APT repositories are stored, separated by
# instances.
reprepro__public_root: '{{ (ansible_local.fhs.www | d("/srv/www"))
+ "/reprepro" }}'
# ]]]
# .. envvar:: reprepro__spool_root [[[
#
# Path where uploads are stored for the incoming queue, separated by instances.
reprepro__spool_root: '{{ (ansible_local.fhs.spool | d("/var/spool"))
+ "/reprepro" }}'
# ]]]
# .. envvar:: reprepro__admin_sshkeys [[[
#
# List of public SSH keys which allow access to the :command:`reprepro` UNIX
# account via SSH. By default this role will add the SSH keys of the person
# currently executing the role to the account's SSH keyring.
reprepro__admin_sshkeys:
- '{{ lookup("pipe", "ssh-add -L | grep ^\\\(sk-\\\)\\\?ssh || cat ~/.ssh/*.pub || cat ~/.ssh/authorized_keys || true") }}' # noqa jinja[spacing]
# ]]]
# ]]]
# Global reprepro configuration [[[
# ---------------------------------
# .. envvar:: reprepro__fqdn [[[
#
# The default Fully Qualified Domain Name used in various parts of the
# configuration.
reprepro__fqdn: '{{ ansible_fqdn }}'
# ]]]
# .. envvar:: reprepro__domain [[[
#
# The default DNS domain used in various parts of the configuration.
reprepro__domain: '{{ ansible_domain }}'
# ]]]
# .. envvar:: reprepro__origin [[[
#
# The value of the "Origin:" field defined for all APT repositories managed by
# :command:`reprepro`. This variable needs to be referenced in the
# :file:`conf/distributions` configuration to be effective.
reprepro__origin: '{{ ansible_local.machine.organization
| d(reprepro__domain.split(".")[0] | capitalize) }}'
# ]]]
# .. envvar:: reprepro__mail_from [[[
#
# The default e-mail sender address for e-mails sent on each repository change.
reprepro__mail_from: '{{ reprepro__user + "@" + reprepro__fqdn }}'
# ]]]
# .. envvar:: reprepro__mail_to [[[
#
# The default e-mail recipient for e-mails sent on each repository change.
reprepro__mail_to: '{{ "root@" + reprepro__domain }}'
# ]]]
# .. envvar:: reprepro__max_body_size [[[
#
# Maximum size of a single upload to the incoming queue.
reprepro__max_body_size: '50M'
# ]]]
# .. envvar:: reprepro__auth_realm [[[
#
# The default realm string used when access controls are enabled in a given APT
# repository.
reprepro__auth_realm: 'Access to this APT repository is restricted'
# ]]]
# ]]]
# GnuPG environment [[[
# ---------------------
# .. envvar:: reprepro__gpg_snapshot_name [[[
#
# Name of the snapshot file which contains :command:`reprepro` GnuPG snapshot.
# It will be backed up on Ansible Controller.
reprepro__gpg_snapshot_name: 'gnupg.tar'
# ]]]
# .. envvar:: reprepro__gpg_snapshot_path [[[
#
# Directory on Ansible Controller where :command:`reprepro` GnuPG snapshot will
# be archived.
reprepro__gpg_snapshot_path: '{{ secret + "/reprepro/snapshots/" + inventory_hostname }}'
# ]]]
# .. envvar:: reprepro__gpg_key_type [[[
#
# Settings for GPG key used to sign :command:`reprepro` repositories.
reprepro__gpg_key_type: 'RSA'
# ]]]
# .. envvar:: reprepro__gpg_key_length [[[
#
# Length of the GPG key used by :command:`reprepro`.
reprepro__gpg_key_length: '4096'
# ]]]
# .. envvar:: reprepro__gpg_name [[[
#
# String used as the name of the GPG key used to sign :command:`reprepro` APT
# repositories.
reprepro__gpg_name: '{{ reprepro__origin + " Automatic Signing Key" }}'
# ]]]
# .. envvar:: reprepro__gpg_email [[[
#
# E-mail address of the GPG key used to sign :command:`reprepro` APT
# repositories.
reprepro__gpg_email: '{{ "apt-packages@" + reprepro__domain }}'
# ]]]
# .. envvar:: reprepro__gpg_expire_days [[[
#
# Duration in days after which the GPG keys for the APT repositories will
# expire (default: 10 years).
reprepro__gpg_expire_days: '{{ (365 * 10) }}'
# ]]]
# .. envvar:: reprepro__gpg_public_filename [[[
#
# Filename of the GPG public key published in the root of the APT repositories
# managed by :ref:`debops.reprepro` role.
reprepro__gpg_public_filename: '{{ reprepro__domain + ".asc" }}'
# ]]]
# .. envvar:: reprepro__gpg_uploaders_keys [[[
#
# List of GPG fingerprints of people or services that are allowed to upload
# packages to APT repositories managed by :command:`reprepro`. They will be
# added to the UNIX account by the :ref:`debops.keyring` role. See its
# documentation for more details.
reprepro__gpg_uploaders_keys: []
# ]]]
# ]]]
# Local APT repository instances [[[
# ----------------------------------
# The variables below define list of APT repository instances managed by
# :ref:`debops.reprepro` role. See :ref:`reprepro__ref_instances` documentation
# for more details.
# .. envvar:: reprepro__default_instances [[[
#
# List of default APT repository instances managed by :ref:`debops.reprepro`.
reprepro__default_instances:
- name: 'main'
fqdn: '{{ reprepro__fqdn }}'
incoming:
- name: 'incoming'
Allow:
- 'forky'
- 'testing>forky'
- 'trixie'
- 'stable>trixie'
- 'bookworm'
- 'oldstable>bookworm'
- 'bullseye'
- 'oldoldstable>bullseye'
Options:
- 'multiple_distributions'
Cleanup:
- 'on_deny'
- 'on_error'
distributions:
- name: 'forky'
Description: 'Packages for Debian GNU/Linux 14 (Forky)'
Origin: '{{ reprepro__origin }}'
Codename: 'forky'
Suite: 'testing'
Architectures: [ 'source', 'amd64', 'arm64', 'armhf',
'ppc64el', 'riscv64', 's390x' ]
Components: [ 'main', 'contrib', 'non-free', 'non-free-firmware' ]
Uploaders: 'uploaders/anybody'
SignWith: 'default'
DebIndices: [ 'Packages', 'Release', '.', '.gz', '.xz' ]
DscIndices: [ 'Sources', 'Release', '.gz', '.xz' ]
Log: |
packages.forky.log
--type=dsc email-changes.sh
state: 'present'
- name: 'trixie'
Description: 'Packages for Debian GNU/Linux 13 (Trixie)'
Origin: '{{ reprepro__origin }}'
Codename: 'trixie'
Suite: 'stable'
Architectures: [ 'source', 'amd64', 'arm64', 'armel', 'armhf',
'ppc64el', 'riscv64', 's390x' ]
Components: [ 'main', 'contrib', 'non-free', 'non-free-firmware' ]
Uploaders: 'uploaders/anybody'
SignWith: 'default'
DebIndices: [ 'Packages', 'Release', '.', '.gz', '.xz' ]
DscIndices: [ 'Sources', 'Release', '.gz', '.xz' ]
Log: |
packages.trixie.log
--type=dsc email-changes.sh
state: 'present'
- name: 'bookworm'
Description: 'Packages for Debian GNU/Linux 12 (Bookworm)'
Origin: '{{ reprepro__origin }}'
Codename: 'bookworm'
Suite: 'oldstable'
Architectures: [ 'source', 'amd64', 'arm64', 'armel', 'armhf', 'i386',
'mips64el', 'mipsel', 'ppc64el', 'riscv64', 's390x' ]
Components: [ 'main', 'contrib', 'non-free', 'non-free-firmware' ]
Uploaders: 'uploaders/anybody'
SignWith: 'default'
DebIndices: [ 'Packages', 'Release', '.', '.gz', '.xz' ]
DscIndices: [ 'Sources', 'Release', '.gz', '.xz' ]
Log: |
packages.bookworm.log
--type=dsc email-changes.sh
state: 'present'
- name: 'bullseye'
Description: 'Packages for Debian GNU/Linux 11 (Bullseye)'
Origin: '{{ reprepro__origin }}'
Codename: 'bullseye'
Suite: 'oldoldstable'
Architectures: [ 'source', 'amd64', 'arm64', 'armel', 'armhf', 'i386',
'mips64el', 'mipsel', 'ppc64el', 's390x' ]
Components: [ 'main', 'contrib', 'non-free' ]
Uploaders: 'uploaders/anybody'
SignWith: 'default'
DebIndices: [ 'Packages', 'Release', '.', '.gz', '.xz' ]
DscIndices: [ 'Sources', 'Release', '.gz', '.xz' ]
Log: |
packages.bullseye.log
--type=dsc email-changes.sh
state: 'present'
uploaders:
- name: 'anybody'
raw: |
allow * by any key
state: 'present'
# ]]]
# .. envvar:: reprepro__instances [[[
#
# List of APT repository instances that should be created on all hosts in the
# Ansible inventory.
reprepro__instances: []
# ]]]
# .. envvar:: reprepro__group_instances [[[
#
# List of APT repository instances that should be created on hosts in
# a specific Ansible inventory group.
reprepro__group_instances: []
# ]]]
# .. envvar:: reprepro__host_instances [[[
#
# List of APT repository instances that should be created on specific hosts in
# the Ansible inventory.
reprepro__host_instances: []
# ]]]
# .. envvar:: reprepro__combined_instances [[[
#
# Variable which combines all APT repository instances and is used in role
# tasks and templates.
reprepro__combined_instances: '{{ reprepro__default_instances
+ reprepro__instances
+ reprepro__group_instances
+ reprepro__host_instances }}'
# ]]]
# ]]]
# Configuration for other Ansible roles [[[
# -----------------------------------------
# .. envvar:: reprepro__keyring__dependent_gpg_user [[[
#
# UNIX account which will contain GPG keys managed by the :ref:`debops.keyring`
# Ansible role.
reprepro__keyring__dependent_gpg_user: '{{ reprepro__user }}'
# ]]]
# .. envvar:: reprepro__keyring__dependent_gpg_keys [[[
#
# List of GPG keys managed by the :ref:`debops.keyring` Ansible role.
reprepro__keyring__dependent_gpg_keys:
- user: '{{ reprepro__user }}'
group: '{{ reprepro__group }}'
home: '{{ reprepro__home }}'
- '{{ q("flattened", reprepro__gpg_uploaders_keys) }}'
# ]]]
# .. envvar:: reprepro__nginx__dependent_servers [[[
#
# Server configuration for the :ref:`debops.nginx` Ansible role.
reprepro__nginx__dependent_servers: '{{ reprepro__env_nginx_servers }}'
# ]]]
# ]]]

View file

@ -0,0 +1,32 @@
---
# Copyright (C) 2014-2021 Maciej Delmanowski <drybjed@gmail.com>
# Copyright (C) 2015-2022 DebOps <https://debops.org/>
# SPDX-License-Identifier: GPL-3.0-only
# Ensure that custom Ansible plugins and modules included in the main DebOps
# collection are available to roles in other collections.
collections: [ 'debops.debops' ]
dependencies: []
galaxy_info:
author: 'Maciej Delmanowski'
description: 'Manage local APT repositories with reprepro'
company: 'DebOps'
license: 'GPL-3.0-only'
min_ansible_version: '1.9.0'
platforms:
- name: 'Ubuntu'
versions: [ 'all' ]
- name: 'Debian'
versions: [ 'all' ]
galaxy_tags:
- development
- packaging
- apt
- repository

View file

@ -0,0 +1,93 @@
---
# Copyright (C) 2014-2021 Maciej Delmanowski <drybjed@gmail.com>
# Copyright (C) 2015-2021 DebOps <https://debops.org/>
# SPDX-License-Identifier: GPL-3.0-only
- name: Check if GnuPG directory exists
ansible.builtin.stat:
path: '{{ reprepro__home + "/.gnupg/gpg.conf" }}'
register: reprepro__register_gpg
- name: Check if GnuPG snapshot exists on Ansible Controller
ansible.builtin.stat:
path: '{{ reprepro__gpg_snapshot_path + "/" + reprepro__gpg_snapshot_name }}'
register: reprepro__register_gpg_snapshot
delegate_to: 'localhost'
become: False
- name: Restore GnuPG snapshots
ansible.builtin.unarchive:
src: '{{ reprepro__gpg_snapshot_path + "/" + reprepro__gpg_snapshot_name }}'
dest: '{{ reprepro__home }}'
mode: 'u=rwX,g=,o='
when: reprepro__register_gpg_snapshot.stat.exists and
not reprepro__register_gpg.stat.exists
- name: Ensure that ~/.gnupg directory exists
ansible.builtin.file:
path: '{{ reprepro__home + "/.gnupg" }}'
state: 'directory'
owner: '{{ reprepro__user }}'
group: '{{ reprepro__group }}'
mode: '0700'
- name: Configure reprepro GnuPG instance
ansible.builtin.template:
src: 'home/reprepro/gnupg/gpg.conf.j2'
dest: '{{ reprepro__home + "/.gnupg/gpg.conf" }}'
owner: '{{ reprepro__user }}'
group: '{{ reprepro__group }}'
mode: '0644'
- name: Check if private keys are present
ansible.builtin.find:
paths: '{{ reprepro__home + "/.gnupg/private-keys-v1.d/" }}'
register: reprepro__register_private_keys
- name: Create repository key template
ansible.builtin.template:
src: 'home/reprepro/gnupg-key-template.j2'
dest: '{{ reprepro__home + "/.gnupg-key-template" }}'
owner: '{{ reprepro__user }}'
group: '{{ reprepro__group }}'
mode: '0644'
when: reprepro__register_private_keys.matched == 0
- name: Generate automatic signing key
ansible.builtin.command: 'gpg --batch --gen-key .gnupg-key-template'
args:
chdir: '{{ reprepro__home }}'
register: reprepro__register_keygen
changed_when: reprepro__register_keygen.changed | bool
become: True
become_user: '{{ reprepro__user }}'
when: reprepro__register_private_keys.matched == 0
- name: Archive ~/.gnupg directory
community.general.archive:
path: '{{ reprepro__home + "/.gnupg" }}'
dest: '{{ reprepro__home + "/" + reprepro__gpg_snapshot_name }}'
owner: '{{ reprepro__user }}'
group: '{{ reprepro__group }}'
mode: '0600'
register: reprepro__register_gpg_archive
- name: Upload ~/.gnupg archive to Ansible Controller
ansible.builtin.fetch: # noqa no-handler
src: '{{ reprepro__home + "/" + reprepro__gpg_snapshot_name }}'
dest: '{{ reprepro__gpg_snapshot_path + "/" + reprepro__gpg_snapshot_name }}'
flat: True
when: reprepro__register_gpg_archive is changed
- name: Remove old automatic signing key
ansible.builtin.file: # noqa no-handler
path: '{{ reprepro__home + "/" + reprepro__gpg_public_filename }}'
state: 'absent'
when: reprepro__register_keygen is changed
- name: Export automatic signing key
ansible.builtin.shell: 'gpg --armor --export "{{ reprepro__gpg_email }}" > "{{ reprepro__home + "/" + reprepro__gpg_public_filename }}"'
args:
creates: '{{ reprepro__home + "/" + reprepro__gpg_public_filename }}'
become: True
become_user: '{{ reprepro__user }}'

View file

@ -0,0 +1,117 @@
---
# Copyright (C) 2014-2021 Maciej Delmanowski <drybjed@gmail.com>
# Copyright (C) 2015-2021 DebOps <https://debops.org/>
# SPDX-License-Identifier: GPL-3.0-only
- name: Create reprepro spool directory
ansible.builtin.file:
path: '{{ reprepro__spool_root + "/" + repo.name }}'
state: 'directory'
owner: '{{ reprepro__user }}'
group: '{{ reprepro__group }}'
mode: '0755'
when: repo.state | d('present') not in ['absent', 'ignore']
- name: Create directory for reprepro uploads
ansible.builtin.file:
path: '{{ reprepro__spool_root + "/" + repo.name + "/incoming" }}'
state: 'directory'
owner: '{{ reprepro__user }}'
group: 'www-data'
mode: '0730'
when: repo.state | d('present') not in ['absent', 'ignore']
- name: Create reprepro internal directories
ansible.builtin.file:
path: '{{ reprepro__data_root + "/" + repo.name + "/" + item }}'
state: 'directory'
owner: '{{ reprepro__user }}'
group: '{{ reprepro__group }}'
mode: '0755'
loop: [ 'conf/uploaders', 'tmp' ]
when: repo.state | d('present') not in ['absent', 'ignore']
- name: Create public reprepro repository
ansible.builtin.file:
path: '{{ reprepro__public_root + "/sites/" + repo.name + "/public" }}'
state: 'directory'
owner: '{{ reprepro__user }}'
group: '{{ reprepro__group }}'
mode: '0755'
when: repo.state | d('present') not in ['absent', 'ignore'] and
not repo.outdir | d()
- name: Copy GPG public key to public space
ansible.builtin.copy:
src: '{{ reprepro__home + "/" + reprepro__gpg_public_filename }}'
dest: '{{ reprepro__public_root + "/sites/" + repo.name + "/public/" + reprepro__gpg_public_filename }}'
remote_src: True
owner: '{{ reprepro__user }}'
group: '{{ reprepro__group }}'
mode: '0644'
when: repo.state | d('present') not in ['absent', 'ignore'] and
not repo.outdir | d()
- name: Manage reprepro configuration files
ansible.builtin.template:
src: 'home/reprepro/repositories/instance/conf/{{ item }}.j2'
dest: '{{ reprepro__data_root + "/" + repo.name + "/conf/" + item }}'
owner: '{{ reprepro__user }}'
group: '{{ reprepro__group }}'
mode: '0644'
loop: [ 'distributions', 'incoming', 'options', 'pulls', 'updates' ]
register: reprepro__register_config
when: (repo.state | d('present') not in ['absent', 'ignore'] and
(item in repo.keys() or item in ['options']))
- name: Configure uploaders configuration files
ansible.builtin.template:
src: 'home/reprepro/repositories/instance/conf/uploaders/template.j2'
dest: '{{ reprepro__data_root + "/" + repo.name + "/conf/uploaders/" + item.name }}'
owner: '{{ reprepro__user }}'
group: '{{ reprepro__group }}'
mode: '0644'
loop: '{{ repo.uploaders }}'
register: reprepro__register_uploaders
when: repo.state | d('present') not in ['absent', 'ignore'] and 'uploaders' in repo.keys()
- name: Initialize reprepro repositories
ansible.builtin.command: 'reprepro export'
args:
chdir: '{{ reprepro__data_root + "/" + repo.name }}'
become: True
become_user: '{{ reprepro__user }}'
register: reprepro__register_export
changed_when: reprepro__register_export.changed | bool
when: (repo.state | d('present') not in ['absent', 'ignore'] and repo.distributions | d() and
(reprepro__register_config is changed or reprepro__register_uploaders is changed))
- name: Generate symlinks
ansible.builtin.command: 'reprepro --delete createsymlinks'
args:
chdir: '{{ reprepro__data_root + "/" + repo.name }}'
become: True
become_user: '{{ reprepro__user }}'
changed_when: False
when: (repo.state | d('present') not in ['absent', 'ignore'] and repo.distributions | d())
- name: Enable incoming queue monitoring
ansible.builtin.systemd:
name: '{{ "reprepro-incoming@" + repo.name + ".path" }}'
state: '{{ "started"
if (repo.state | d("present") not in ["absent", "ignore"])
else "stopped" }}'
enabled: '{{ True
if (repo.state | d("present") not in ["absent", "ignore"])
else False }}'
when: repo.incoming | d()
- name: Manage reprepro scripts
ansible.builtin.template:
src: 'home/reprepro/repositories/instance/conf/{{ item }}.j2'
dest: '{{ reprepro__data_root + "/" + repo.name + "/conf/" + item }}'
owner: '{{ reprepro__user }}'
group: '{{ reprepro__group }}'
mode: '0755'
with_items: [ 'email-changes.sh' ]
when: repo.state | d('present') not in ['absent', 'ignore']

View file

@ -0,0 +1,90 @@
---
# Copyright (C) 2014-2021 Maciej Delmanowski <drybjed@gmail.com>
# Copyright (C) 2015-2021 DebOps <https://debops.org/>
# SPDX-License-Identifier: GPL-3.0-only
- name: Import DebOps global handlers
ansible.builtin.import_role:
name: 'global_handlers'
- name: Import DebOps secret role
ansible.builtin.import_role:
name: 'secret'
- name: Install packages for Debian repository management
ansible.builtin.package:
name: '{{ q("flattened", (reprepro__base_packages + reprepro__packages)) }}'
state: 'present'
register: reprepro__register_packages
until: reprepro__register_packages is succeeded
- name: Create UNIX system group for reprepro
ansible.builtin.group:
name: '{{ reprepro__group }}'
system: True
state: 'present'
- name: Create UNIX system account for reprepro
ansible.builtin.user:
name: '{{ reprepro__user }}'
group: '{{ reprepro__group }}'
groups: '{{ reprepro__additional_groups }}'
append: True
system: True
shell: '/bin/bash'
home: '{{ reprepro__home }}'
comment: '{{ reprepro__comment }}'
state: 'present'
- name: Add admin SSH keys to reprepro UNIX account
ansible.posix.authorized_key:
key: "{{ (reprepro__admin_sshkeys
if reprepro__admin_sshkeys is string
else '\n'.join(q('flattened', reprepro__admin_sshkeys))) | string }}"
state: 'present'
user: '{{ reprepro__user }}'
exclusive: False
when: reprepro__admin_sshkeys | d()
- name: Generate queue processing services
ansible.builtin.template:
src: '{{ "etc/systemd/system/" + item + ".j2" }}'
dest: '{{ "/etc/systemd/system/" + item }}'
mode: '0644'
loop:
- 'reprepro-incoming@.service'
- 'reprepro-incoming@.path'
notify: [ 'Reload systemd daemon' ]
when: ansible_service_mgr == 'systemd'
- name: Flush handlers when needed
ansible.builtin.meta: 'flush_handlers'
- name: Manage the GnuPG environment for reprepro
ansible.builtin.import_tasks: 'configure_gnupg.yml'
- name: Manage reprepro instances
ansible.builtin.include_tasks: 'configure_reprepro.yml'
loop_control:
loop_var: 'repo'
label: '{{ {"name": repo.name, "state": repo.state | d("present"),
"outdir": (repo.outdir | d(reprepro__public_root + "/sites/" + repo.name + "/public"))} }}'
loop: '{{ q("flattened", reprepro__combined_instances)
| debops.debops.parse_kv_items(merge_keys=["distributions", "incoming", "pulls", "updates"]) }}'
- name: Make sure Ansible fact directory exists
ansible.builtin.file:
path: '/etc/ansible/facts.d'
state: 'directory'
mode: '0755'
- name: Generate reprepro Ansible local facts
ansible.builtin.template:
src: 'etc/ansible/facts.d/reprepro.fact.j2'
dest: '/etc/ansible/facts.d/reprepro.fact'
mode: '0755'
notify: [ 'Refresh host facts' ]
tags: [ 'meta::facts' ]
- name: Refresh host facts when needed
ansible.builtin.meta: 'flush_handlers'

View file

@ -0,0 +1,8 @@
---
# Copyright (C) 2021 Maciej Delmanowski <drybjed@gmail.com>
# Copyright (C) 2021 DebOps <https://debops.org/>
# SPDX-License-Identifier: GPL-3.0-only
- name: Prepare reprepro environment
ansible.builtin.set_fact:
reprepro__env_nginx_servers: '{{ lookup("template", "lookup/reprepro__env_nginx_servers.j2") | from_yaml }}'

View file

@ -0,0 +1,26 @@
#!{{ ansible_python['executable'] }}
# -*- coding: utf-8 -*-
# Copyright (C) 2021 Maciej Delmanowski <drybjed@gmail.com>
# Copyright (C) 2021 DebOps <https://debops.org/>
# SPDX-License-Identifier: GPL-3.0-only
# {{ ansible_managed }}
from __future__ import print_function
from json import dumps
from sys import exit
import os
def cmd_exists(cmd):
return any(
os.access(os.path.join(path, cmd), os.X_OK)
for path in os.environ["PATH"].split(os.pathsep)
)
output = {'configured': True,
'installed': cmd_exists('reprepro')}
print(dumps(output, sort_keys=True, indent=4))

View file

@ -0,0 +1,17 @@
{# Copyright (C) 2021 Maciej Delmanowski <drybjed@gmail.com>
# Copyright (C) 2021 DebOps <http://debops.org/>
# SPDX-License-Identifier: GPL-3.0-only
#}
# {{ ansible_managed }}
[Unit]
Description=Icoming queue monitor for reprepro %i instance
After=syslog.target network-online.target
Wants=network.target
[Path]
PathExistsGlob={{ reprepro__spool_root + '/%i/incoming/*.changes' }}
Unit=reprepro-incoming@%i.service
[Install]
WantedBy=multi-user.target

View file

@ -0,0 +1,17 @@
{# Copyright (C) 2021 Maciej Delmanowski <drybjed@gmail.com>
# Copyright (C) 2021 DebOps <http://debops.org/>
# SPDX-License-Identifier: GPL-3.0-only
#}
# {{ ansible_managed }}
[Unit]
Description=Icoming queue processor for reprepro %i instance
After=syslog.target network-online.target
Wants=network.target
[Service]
Type=oneshot
User={{ reprepro__user }}
Group={{ reprepro__group }}
WorkingDirectory={{ reprepro__data_root + '/%i' }}
ExecStart=/usr/bin/reprepro processincoming incoming

View file

@ -0,0 +1,14 @@
{# Copyright (C) 2014-2021 Maciej Delmanowski <drybjed@gmail.com>
# Copyright (C) 2015-2021 DebOps <https://debops.org/>
# SPDX-License-Identifier: GPL-3.0-only
#}
Key-Type: {{ reprepro__gpg_key_type }}
Subkey-Type: {{ reprepro__gpg_key_type }}
Key-Length: {{ reprepro__gpg_key_length }}
Subkey-Length: {{ reprepro__gpg_key_length }}
Name-Real: {{ reprepro__gpg_name }}
Name-Email: {{ reprepro__gpg_email }}
Expire-Date: {{ reprepro__gpg_expire_days }}
%no-ask-passphrase
%no-protection
%commit

View file

@ -0,0 +1,33 @@
{# Copyright (C) 2014-2021 Maciej Delmanowski <drybjed@gmail.com>
# Copyright (C) 2015-2022 DebOps <https://debops.org/>
# SPDX-License-Identifier: GPL-3.0-only
#}
{% set digest_preference = 'SHA512 SHA384 SHA256 SHA224' %}
# This file is managed remotely, all changes will be lost
# GPG Configuration Options
default-recipient-self
list-options show-uid-validity show-keyserver-urls
verify-options show-uid-validity
display-charset utf-8
utf8-strings
trust-model pgp
ask-cert-level
default-cert-level 0
keyid-format 0xlong
no-greeting
# GPG Input/Output Options
armor
fixed-list-mode
# OpenPGP Options
personal-digest-preferences {{ digest_preference }}
# GPG Esoteric Options
default-preference-list {{ digest_preference }} AES256 AES192 AES CAST5 BZIP2 ZLIB ZIP Uncompressed
cert-digest-algo SHA512
no-comments
no-emit-version
sig-notation issuer-fpr@notations.openpgp.fifthhorseman.net=%g

View file

@ -0,0 +1,42 @@
{# Copyright (C) 2021 Maciej Delmanowski <drybjed@gmail.com>
# Copyright (C) 2021 DebOps <https://debops.org/>
# SPDX-License-Identifier: GPL-3.0-only
# Format: deb822
#}
# {{ ansible_managed }}
{% if repo.distributions | d() %}
{% for section in repo.distributions %}
{% if section.state | d('present') not in [ 'absent', 'ignore' ] %}
{% if not loop.first %}
{{ '' }}
{% endif %}
{% if section.comment | d() %}
{{ section.comment | regex_replace('\n$', '') | comment(prefix='', postfix='') }}
{% endif %}
{% if section.raw | d() %}
{{ section.raw | regex_replace('\n$', '') }}
{% else %}
{{ 'Codename: {}'.format(section.Codename | d(section.name)) }}
{% for key, value in section | dictsort %}
{% if key not in [ 'name', 'Codename', 'value', 'id', 'comment', 'state', 'weight', 'real_weight', 'separator', 'section' ] %}
{% set section_value = value %}
{% if value is not string and value is not mapping and value is iterable %}
{% set section_value = (value | join(' ')) %}
{% endif %}
{% if section_value is iterable and section_value.split('\n') | count > 1 %}
{% set value_first = section_value.split('\n')[0] %}
{% set value_rest = section_value.split('\n')[1:] | join('\n') %}
{{ '{}: {}'.format(key, value_first) }}
{{ '{}'.format(value_rest | regex_replace('\n$','') | indent(1, true)) }}
{% else %}
{{ '{}: {}'.format(key, section_value) }}
{% endif %}
{% endif %}
{% endfor %}
{% endif %}
{% endif %}
{% endfor %}
{% else %}
{{ '# No distributions defined for {}'.format(repo.name) }}
{% endif %}

View file

@ -0,0 +1,120 @@
#!/usr/bin/env bash
# Copyright (C) 2013 Vincent Bernat <vincent@bernat.ch>
# Copyright (C) 2014-2021 Maciej Delmanowski <drybjed@gmail.com>
# Copyright (C) 2015-2021 DebOps <https://debops.org/>
# SPDX-License-Identifier: GPL-3.0-only
# {{ ansible_managed }}
# Send information about repository changes to administrator
# Sourced from: https://gist.github.com/vincentbernat/7404733
INSTANCE="{{ repo.name }}"
MAIL_FROM="{{ (repo.mail_name | d('Reprepro ' + repo.name + ' repository')) + ' <' + (repo.mail_from | d(reprepro__mail_from)) + '>' }}"
read -r -a MAIL_TO <<< "{{ repo.mail_to | d(reprepro__mail_to) }}"
[ -n "${REPREPRO_OUT_DIR}" ] || {
echo "needs to be run by reprepro!" >&2
exit 1
}
cd "${REPREPRO_OUT_DIR}" || exit 1
dsc() {
dsc="$(readlink --canonicalize "$1")"
tmp="$(mktemp -d)"
trap 'rm -rf -- $tmp' EXIT
(cd -- "$tmp" && dpkg-source --no-copy -x "$dsc" > /dev/null)
cat <<EOF > "$tmp/mail"
✂--------------------- Changelog --------------------------
EOF
changelog="$(echo "$tmp"/*/debian/changelog)"
# shellcheck disable=SC2129
dpkg-parsechangelog -l"$changelog" \
${OLDVERSION:+--since "$OLDVERSION"} \
${VERSION:+--to "$VERSION"} \
>> "$tmp/mail"
cat <<EOF >> "$tmp/mail"
✂--------------------- DSC file ---------------------------
EOF
cat "$dsc" >> "$tmp/mail"
mail -r "${MAIL_FROM}" \
-s "[PACKAGES] ($INSTANCE) $CODENAME/$COMPONENT: $ACTION $NAME" \
-- "${MAIL_TO[@]}" < "$tmp/mail"
}
ACTION="$1"
CODENAME="$2"
PACKAGETYPE="$3"
COMPONENT="$4"
ARCHITECTURE="$5"
NAME="$6"
[ "$PACKAGETYPE" = "dsc" ] || {
echo "Should be a DSC package, not $PACKAGETYPE" >&2
exit 1
}
[ "$ARCHITECTURE" = "source" ] || {
echo "Should be a source package, not $ARCHITECTURE" >&2
exit 1
}
shift 6
case "$ACTION" in
add|info)
VERSION="$1" ; shift
[ "$1" = "--" ] || exit 2
shift
while [ "$#" -gt 0 ] ; do
case "$1" in
*.dsc)
dsc "$1"
;;
--)
exit 2
;;
esac
shift
done
;;
remove)
OLDVERSION="$1" ; shift
[ "$1" = "--" ] || exit 2
shift
while [ "$#" -gt 0 ] ; do
case "$1" in
*.dsc)
dsc "$1"
;;
--)
exit 2
;;
esac
shift
done
;;
replace)
VERSION="$1" ; shift
OLDVERSION="$1" ; shift
[ "$1" = "--" ] || exit 2
shift
while [ "$#" -gt 0 ] && [ "$1" != "--" ]; do
case "$1" in
*.dsc)
dsc "$1"
;;
esac
shift
done
[ "$1" = "--" ] || exit 2
# Ignore replaced dsc
;;
esac
exit 0

View file

@ -0,0 +1,48 @@
{# Copyright (C) 2021 Maciej Delmanowski <drybjed@gmail.com>
# Copyright (C) 2021 DebOps <https://debops.org/>
# SPDX-License-Identifier: GPL-3.0-only
# Format: deb822
#}
# {{ ansible_managed }}
{% if repo.incoming | d() %}
{% for section in repo.incoming %}
{% if section.state | d('present') not in [ 'absent', 'ignore' ] %}
{% if not loop.first %}
{{ '' }}
{% endif %}
{% if section.comment | d() %}
{{ section.comment | regex_replace('\n$', '') | comment(prefix='', postfix='') }}
{% endif %}
{% if section.raw | d() %}
{{ section.raw | regex_replace('\n$', '') }}
{% else %}
{{ 'Name: {}'.format(section.Name | d(section.name)) }}
{% if 'IncomingDir' not in section.keys() %}
{% set _ = section.update({'IncomingDir': (reprepro__spool_root + '/' + repo.name + '/' + (section.Name | d(section.name)))}) %}
{% endif %}
{% if 'TempDir' not in section.keys() %}
{% set _ = section.update({'TempDir': 'tmp'}) %}
{% endif %}
{% for key, value in section | dictsort %}
{% if key not in [ 'name', 'Name', 'value', 'id', 'comment', 'state', 'weight', 'real_weight', 'separator', 'section' ] %}
{% set section_value = value %}
{% if value is not string and value is not mapping and value is iterable %}
{% set section_value = (value | join(' ')) %}
{% endif %}
{% if section_value is iterable and section_value.split('\n') | count > 1 %}
{% set value_first = section_value.split('\n')[0] %}
{% set value_rest = section_value.split('\n')[1:] | join('\n') %}
{{ '{}: {}'.format(key, value_first) }}
{{ '{}'.format(value_rest | regex_replace('\n$','') | indent(1, true)) }}
{% else %}
{{ '{}: {}'.format(key, section_value) }}
{% endif %}
{% endif %}
{% endfor %}
{% endif %}
{% endif %}
{% endfor %}
{% else %}
{{ '# No incoming sources defined for {}'.format(repo.name) }}
{% endif %}

View file

@ -0,0 +1,21 @@
{# Copyright (C) 2014-2019 Maciej Delmanowski <drybjed@gmail.com>
# Copyright (C) 2015-2019 DebOps <https://debops.org/>
# SPDX-License-Identifier: GPL-3.0-only
#}
# This file is managed by Ansible, all changes will be lost
{% set default_options = [
{'basedir': (repo.basedir | d(reprepro__data_root + '/' + repo.name))},
{'outdir': (repo.outdir | d(reprepro__public_root + '/sites/' + repo.name + '/public/' + (repo.os | d('debian'))))},
{'waitforlock': 3},
{'verbose': ''}
] %}
{% for option in ((default_options + repo.options | d([])) | debops.debops.parse_kv_config) %}
{% if option.state | d('present') not in [ 'absent', 'ignore' ] %}
{% if option.value | d() %}
{{ '{} {}'.format(option.name, option.value) }}
{% else %}
{{ '{}'.format(option.name) }}
{% endif %}
{% endif %}
{% endfor %}

View file

@ -0,0 +1,42 @@
{# Copyright (C) 2021 Maciej Delmanowski <drybjed@gmail.com>
# Copyright (C) 2021 DebOps <https://debops.org/>
# SPDX-License-Identifier: GPL-3.0-only
# Format: deb822
#}
# {{ ansible_managed }}
{% if repo.pulls | d() %}
{% for section in repo.pulls %}
{% if section.state | d('present') not in [ 'absent', 'ignore' ] %}
{% if not loop.first %}
{{ '' }}
{% endif %}
{% if section.comment | d() %}
{{ section.comment | regex_replace('\n$', '') | comment(prefix='', postfix='') }}
{% endif %}
{% if section.raw | d() %}
{{ section.raw | regex_replace('\n$', '') }}
{% else %}
{{ 'Name: {}'.format(section.Name | d(section.name)) }}
{% for key, value in section | dictsort %}
{% if key not in [ 'name', 'Name', 'value', 'id', 'comment', 'state', 'weight', 'real_weight', 'separator', 'section' ] %}
{% set section_value = value %}
{% if value is not string and value is not mapping and value is iterable %}
{% set section_value = (value | join(' ')) %}
{% endif %}
{% if section_value is iterable and section_value.split('\n') | count > 1 %}
{% set value_first = section_value.split('\n')[0] %}
{% set value_rest = section_value.split('\n')[1:] | join('\n') %}
{{ '{}: {}'.format(key, value_first) }}
{{ '{}'.format(value_rest | regex_replace('\n$','') | indent(1, true)) }}
{% else %}
{{ '{}: {}'.format(key, section_value) }}
{% endif %}
{% endif %}
{% endfor %}
{% endif %}
{% endif %}
{% endfor %}
{% else %}
{{ '# No pull rules defined for {}'.format(repo.name) }}
{% endif %}

View file

@ -0,0 +1,42 @@
{# Copyright (C) 2021 Maciej Delmanowski <drybjed@gmail.com>
# Copyright (C) 2021 DebOps <https://debops.org/>
# SPDX-License-Identifier: GPL-3.0-only
# Format: deb822
#}
# {{ ansible_managed }}
{% if repo.updates | d() %}
{% for section in repo.updates %}
{% if section.state | d('present') not in [ 'absent', 'ignore' ] %}
{% if not loop.first %}
{{ '' }}
{% endif %}
{% if section.comment | d() %}
{{ section.comment | regex_replace('\n$', '') | comment(prefix='', postfix='') }}
{% endif %}
{% if section.raw | d() %}
{{ section.raw | regex_replace('\n$', '') }}
{% else %}
{{ 'Name: {}'.format(section.Name | d(section.name)) }}
{% for key, value in section | dictsort %}
{% if key not in [ 'name', 'Name', 'value', 'id', 'comment', 'state', 'weight', 'real_weight', 'separator', 'section' ] %}
{% set section_value = value %}
{% if value is not string and value is not mapping and value is iterable %}
{% set section_value = (value | join(' ')) %}
{% endif %}
{% if section_value is iterable and section_value.split('\n') | count > 1 %}
{% set value_first = section_value.split('\n')[0] %}
{% set value_rest = section_value.split('\n')[1:] | join('\n') %}
{{ '{}: {}'.format(key, value_first) }}
{{ '{}'.format(value_rest | regex_replace('\n$','') | indent(1, true)) }}
{% else %}
{{ '{}: {}'.format(key, section_value) }}
{% endif %}
{% endif %}
{% endfor %}
{% endif %}
{% endif %}
{% endfor %}
{% else %}
{{ '# No update sources defined for {}'.format(repo.name) }}
{% endif %}

View file

@ -0,0 +1,14 @@
{# Copyright (C) 2021 Maciej Delmanowski <drybjed@gmail.com>
# Copyright (C) 2021 DebOps <https://debops.org/>
# SPDX-License-Identifier: GPL-3.0-only
#}
# {{ ansible_managed }}
{% if item.comment | d() %}
{{ item.comment | regex_replace('\n$','') | comment(prefix='', postfix='') }}
{% endif %}
{% if item.raw | d() %}
{{ '{}'.format(item.raw | regex_replace('\n$','')) }}
{% else %}
{{ '# No "{}" uploaders defined for {}'.format(item.name, repo.name) }}
{% endif %}

View file

@ -0,0 +1,94 @@
{# Copyright (C) 2021 Maciej Delmanowski <drybjed@gmail.com>
# Copyright (C) 2021 DebOps <https://debops.org/>
# SPDX-License-Identifier: GPL-3.0-only
#}
{% set output = [] %}
{% for repo in q("flattened", reprepro__combined_instances) | debops.debops.parse_kv_items(merge_keys=["distributions", "incoming", "pulls", "updates"]) %}
{% if repo.fqdn | d() %}
{% set repo_data_http = {
'by_role': 'debops.reprepro',
'state': (repo.state if repo.public | d(True) else 'absent'),
'enabled': True,
'ssl': False,
'name': repo.fqdn,
'filename': ('debops.reprepro_' + repo.name + '_http'),
'root': (repo.outdir | d(reprepro__public_root + '/sites/' + repo.name + '/public')),
'webroot_create': False,
'location_list': [
{'pattern': '/',
'allow': repo.allow | d([]),
'options': 'try_files $uri $uri/ =404;\nautoindex on;\nautoindex_exact_size off;\nautoindex_localtime on;'
},
{'pattern': '~ /(.+)/(.*)',
'allow': repo.allow | d([]),
'options': 'try_files $uri $uri/ =404;\nautoindex on;\nautoindex_exact_size off;\nautoindex_localtime on;'
}
]
} %}
{% set _ = output.append(repo_data_http) %}
{% set location_list_https = [
{'pattern': '/',
'allow': repo.allow | d([]),
'options': 'try_files $uri $uri/ =404;\nautoindex on;\nautoindex_exact_size off;\nautoindex_localtime on;'
}
] %}
{% if repo.public | d(True) %}
{% set _ = location_list_https.append(
{'pattern': '~ /(.+)/(.*)',
'allow': repo.allow | d([]),
'options': 'try_files $uri $uri/ =404;\nautoindex on;\nautoindex_exact_size off;\nautoindex_localtime on;'
}
) %}
{% else %}
{% set _ = location_list_https.append(
{'pattern': '~ /(.+)/(.*)',
'allow': repo.allow | d([]),
'options': 'try_files $uri $uri/ =404;\nautoindex on;\nautoindex_exact_size off;\nautoindex_localtime on;',
'access_policy': repo.access_policy | d(''),
'auth_basic_realm': repo.auth_realm | d(reprepro__auth_realm)
}
) %}
{% endif %}
{% if repo.upload_map | d() %}
{% for path, alias in repo.upload_map | dictsort %}
{% if alias | d() %}
{% set alias_path = alias %}
{% else %}
{% set alias_path = (reprepro__spool_root + '/' + repo.name + '/incoming') %}
{% endif %}
{% set _ = location_list_https.append(
{'pattern': '^~ /' + (path | regex_replace('^/','')),
'allow': repo.allow_upload | d([]),
'options': ('alias ' + alias_path + ';\ndav_access all:rw;\ndav_methods PUT;\nlimit_except PUT {\n deny all;\n}')
}
) %}
{% endfor %}
{% else %}
{% set _ = location_list_https.append(
{'pattern': '^~ /upload',
'allow': repo.allow_upload | d([]),
'options': ('alias ' + reprepro__spool_root + '/' + repo.name + '/incoming;\ndav_access all:rw;\ndav_methods PUT;\nlimit_except PUT {\n deny all;\n}')
}
) %}
{% endif %}
{% set repo_data_https = {
'by_role': 'debops.reprepro',
'state': (repo.state if (ansible_local.pki.enabled | d()) | bool else 'absent'),
'enabled': True,
'name': repo.fqdn,
'filename': ('debops.reprepro_' + repo.name + '_https'),
'root': (repo.outdir | d(reprepro__public_root + '/sites/' + repo.name + '/public')),
'webroot_create': False,
'options': ('client_max_body_size ' + repo.max_body_size | d(reprepro__max_body_size) + ';'),
'location_list': location_list_https
} %}
{% if repo.public | d(True) %}
{% set _ = repo_data_https.update({'listen': False}) %}
{% endif %}
{% if repo.pki_realm | d() %}
{% set _ = repo_data_https.update({'pki_realm': repo.pki_realm}) %}
{% endif %}
{% set _ = output.append(repo_data_https) %}
{% endif %}
{% endfor %}
{{ output | to_yaml }}