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.ansible - Install Ansible on a Debian/Ubuntu host using Ansible
Copyright (C) 2018 Maciej Delmanowski <drybjed@gmail.com>
Copyright (C) 2018 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,112 @@
---
# .. vim: foldmarker=[[[,]]]:foldmethod=marker
# .. Copyright (C) 2018 Maciej Delmanowski <drybjed@gmail.com>
# .. Copyright (C) 2018 DebOps <https://debops.org/>
# .. SPDX-License-Identifier: GPL-3.0-only
# .. _ansible__ref_defaults:
# debops.ansible default variables
# ================================
# .. contents:: Sections
# :local:
#
# .. include:: ../../../../includes/global.rst
# Installation, APT packages [[[
# ------------------------------
# .. envvar:: ansible__deploy_type [[[
#
# Select how Ansible should be installed by the role:
#
# - ``system``: install Ansible using current OS packages, possibly from the
# backports repository.
#
# - ``upstream``: install Ansible from the upstream APT repository on
# Launchpad (PPA, usable on Debian as well).
#
# - ``bootstrap``: install APT packages required for Ansible and build a local
# ``.deb`` package using the upstream GitHub repository.
#
ansible__deploy_type: '{{ "upstream"
if (ansible_distribution_release in
["trusty", "xenial"])
else "system" }}'
# ]]]
# .. envvar:: ansible__upstream_apt_key [[[
#
# The OpenPGP key of the Ansible upstream APT repository.
ansible__upstream_apt_key: '6125 E2A8 C77F 2818 FB7B D15B 93C4 A3FD 7BB9 C367'
# ]]]
# .. envvar:: ansible__upstream_apt_repository [[[
#
# The APT repository URI of the upstream Ansible repository.
ansible__upstream_apt_repository: 'deb http://ppa.launchpad.net/ansible/ansible/ubuntu xenial main'
# ]]]
# .. envvar:: ansible__base_packages [[[
#
# List of APT packages to install for Ansible support.
ansible__base_packages:
- '{{ "ansible"
if (ansible__deploy_type in ["system", "upstream"])
else [] }}'
# ]]]
# .. envvar:: ansible__packages [[[
#
# List of additional APT packages to install with Ansible.
ansible__packages: []
# ]]]
# .. envvar:: ansible__bootstrap_version [[[
#
# Specify the :command:`git` repository branch, tag or commit id which should
# be used by the :command:`bootstrap-ansible` script to build the Ansible
# ``.deb`` package.
ansible__bootstrap_version: 'devel'
# ]]]
# ]]]
# Configuration for other Ansible roles [[[
# -----------------------------------------
# .. envvar:: ansible__apt_preferences__dependent_list [[[
#
# Configuration for the :ref:`debops.apt_preferences` Ansible role.
ansible__apt_preferences__dependent_list:
- package: 'ansible'
backports: [ "stretch", "buster" ]
reason: 'Compatibility with upstream release'
by_role: 'debops_ansible'
state: '{{ "absent"
if (ansible__deploy_type == "upstream")
else "present" }}'
- package: 'ansible'
pin: 'release o=LP-PPA-ansible-ansible'
priority: '600'
by_role: 'debops_ansible'
filename: 'debops_ansible_upstream.pref'
reason: 'Recent version from upstream PPA'
state: '{{ "present"
if (ansible__deploy_type == "upstream")
else "absent" }}'
# ]]]
# .. envvar:: ansible__keyring__dependent_apt_keys [[[
#
# Configuration for the :ref:`debops.keyring` Ansible role.
ansible__keyring__dependent_apt_keys:
- id: '{{ ansible__upstream_apt_key }}'
repo: '{{ ansible__upstream_apt_repository }}'
state: '{{ "present" if (ansible__deploy_type == "upstream") else "absent" }}'
# ]]]
# ]]]

View file

@ -0,0 +1,160 @@
#!/usr/bin/env bash
# bootstrap-ansible: download and build Ansible on a Debian/Ubuntu host
# Copyright (C) 2014-2018 Maciej Delmanowski <drybjed@gmail.com>
# Copyright (C) 2014-2018 DebOps <https://debops.org/>
# SPDX-License-Identifier: GPL-3.0-or-later
# This program is free software; you can redistribute
# it and/or modify it under the terms of the
# GNU General Public License as published by the Free
# Software Foundation; either version 3 of the License,
# or (at your option) any later version.
#
# This program 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 this program; if not,
# write to the Free Software Foundation, Inc., 59
# Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
# An on-line copy of the GNU General Public License can
# be downloaded from the FSF web page at:
# https://www.gnu.org/copyleft/gpl.html
# Usage: ./bootstrap-ansible [branch] [build_directory]
set -o nounset -o pipefail -o errexit
DEBIAN_VERSION="$(sed 's/\..*//' /etc/debian_version)"
readonly DEBIAN_VERSION
ansible_branch="${1:-devel}"
build_dir="${2:-}"
if [ -z "${build_dir}" ] ; then
build_dir="$(mktemp -d)"
# shellcheck disable=SC2064
trap "rm -rf ${build_dir}" EXIT
fi
install_ansible_requirements () {
local -a apt_packages
apt_packages=( git devscripts cdbs debhelper dpkg-dev fakeroot sshpass \
asciidoc xmlto build-essential lsb-release dh-python )
apt_packages+=(
python-crypto \
python-cryptography \
python-dnspython \
python-httplib2 \
python-jinja2 \
python-ldap \
python-nose \
python-paramiko \
python-passlib \
python-setuptools \
python-sphinx \
python-yaml
)
if [ "${DEBIAN_VERSION}" -gt 8 ] ; then
apt_packages+=( python-packaging )
fi
sudo apt-get --no-install-recommends -y install "${apt_packages[@]}"
}
build_ansible_deb () {
# Build Debian package
if [ -n "$(grep 'local_deb' Makefile || true)" ] ; then
LANG=C make local_deb > /tmp/ansible-deb-build.log \
|| tail -n 50 /tmp/ansible-deb-build.log
else
LANG=C make deb > /tmp/ansible-deb-build.log \
|| tail -n 50 /tmp/ansible-deb-build.log
fi
# Check if .deb package with new method is present
if [ -n "$(find deb-build/unstable/ -name "ansible_*_all.deb" 2>/dev/null)" ]; then
sudo dpkg -i deb-build/unstable/ansible_*_all.deb
# Otherwise, look for package generated with old method
elif [ -n "$(find .. -name "ansible_*_all.deb" 2>/dev/null)" ]; then
sudo dpkg -i ../ansible_*_all.deb
fi
}
bootstrap_ansible_deb () {
local ansible_branch
local build_dir
local ansible_git_repo
local ansible_source_dir
ansible_branch="${1:-devel}"
build_dir="${2:-$(mktemp -d)}"
ansible_git_repo="${3:-https://github.com/ansible/ansible}"
ansible_source_dir="ansible"
if [ ! -d "${build_dir}" ] ; then
mkdir -p "${build_dir}"
fi
cd "${build_dir}" || exit 1
if [ -d "${ansible_source_dir}" ] ; then
cd "${ansible_source_dir}" || exit 1
local old_git_checkout
local current_branch_name
local current_branch_name
local current_branch_name
old_git_checkout="$(git rev-parse HEAD)"
current_branch_name="$(git symbolic-ref HEAD 2>/dev/null)" ||
current_branch_name="(unnamed branch)" # detached HEAD
current_branch_name=${current_branch_name##refs/heads/}
if [ "${current_branch_name}" != "${ansible_branch}" ] ; then
git checkout "${ansible_branch}"
fi
git pull --quiet
git submodule update
local current_git_checkout
current_git_checkout="$(git rev-parse HEAD)"
if [ "${old_git_checkout}" != "${current_git_checkout}" ] ; then
build_ansible_deb
fi
else
install_ansible_requirements
git clone --branch "${ansible_branch}" --recursive "${ansible_git_repo}" "${ansible_source_dir}"
cd "${ansible_source_dir}" || exit 1
build_ansible_deb
fi
}
bootstrap_ansible_deb "${ansible_branch}" "${build_dir}"

View file

@ -0,0 +1,29 @@
---
# Copyright (C) 2018 Maciej Delmanowski <drybjed@gmail.com>
# Copyright (C) 2018-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: 'Install Ansible on Debian/Ubuntu host using Ansible'
company: 'DebOps'
license: 'GPL-3.0-only'
min_ansible_version: '2.4.0'
platforms:
- name: 'Ubuntu'
versions: [ 'all' ]
- name: 'Debian'
versions: [ 'all' ]
galaxy_tags:
- ansible

View file

@ -0,0 +1,45 @@
---
# Copyright (C) 2018 Maciej Delmanowski <drybjed@gmail.com>
# Copyright (C) 2018 DebOps <https://debops.org/>
# SPDX-License-Identifier: GPL-3.0-only
- name: Import DebOps global handlers
ansible.builtin.import_role:
name: 'global_handlers'
- name: Install required packages
ansible.builtin.package:
name: '{{ q("flattened", (ansible__base_packages
+ ansible__packages)) }}'
state: 'present'
register: ansible__register_packages
until: ansible__register_packages is succeeded
- name: Bootstrap Ansible from source
ansible.builtin.script: 'script/bootstrap-ansible "{{ ansible__bootstrap_version }}"'
when: (ansible__deploy_type == 'bootstrap' and
(ansible_local is undefined or
(ansible_local.ansible is undefined or
not (ansible_local.ansible.installed | d()) | bool or
(ansible_local.ansible.deploy_type | d(ansible__deploy_type) != 'bootstrap'))))
- name: Make sure that Ansible local fact directory exists
ansible.builtin.file:
path: '/etc/ansible/facts.d'
state: 'directory'
owner: 'root'
group: 'root'
mode: '0755'
- name: Save Ansible local facts
ansible.builtin.template:
src: 'etc/ansible/facts.d/ansible.fact.j2'
dest: '/etc/ansible/facts.d/ansible.fact'
owner: 'root'
group: 'root'
mode: '0755'
notify: [ 'Refresh host facts' ]
tags: [ 'meta::facts' ]
- name: Re-read local facts if they have been modified
ansible.builtin.meta: 'flush_handlers'

View file

@ -0,0 +1,29 @@
#!{{ ansible_python['executable'] }}
# -*- coding: utf-8 -*-
# Copyright (C) 2018 Maciej Delmanowski <drybjed@gmail.com>
# Copyright (C) 2018 DebOps <https://debops.org/>
# SPDX-License-Identifier: GPL-3.0-only
# {{ ansible_managed }}
from __future__ import print_function
from json import loads, 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 = loads('''{{ {"installed": False,
"deploy_type": ansible__deploy_type}
| to_nice_json }}''')
output['installed'] = cmd_exists('ansible-playbook')
print(dumps(output, sort_keys=True, indent=4))

View file

@ -0,0 +1,19 @@
debops.ansible_plugins - Custom Ansible plugins used by DebOps
Copyright (C) 2017 Maciej Delmanowski <drybjed@gmail.com>
Copyright (C) 2017 DebOps <https://debops.org/>
SPDX-License-Identifier: GPL-3.0-only
This repository 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,193 @@
# Copyright (C) 2017 Maciej Delmanowski <drybjed@gmail.com>
# Copyright (C) 2017 DebOps <https://debops.org/>
# SPDX-License-Identifier: GPL-3.0-or-later
# This file 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 as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# 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/>.
# Make coding more python3-ish
from __future__ import (absolute_import, division, print_function)
from operator import itemgetter
try:
unicode = unicode
except NameError:
# py3
unicode = str
__metaclass__ = type
def _mangle_recipients(name, *args):
"""Modify the recipient address if it's the same as alias
to prevent mail forwarding loops
Ref: https://serverfault.com/questions/471604/
"""
input_args = []
# Flatten the input list
for sublist in list(args):
for item in sublist:
input_args.append(item)
if name in input_args:
return [w.replace(name, '\\' + name) for w in input_args]
else:
return input_args
def _update_recipients(current_data, new_data, *args, **kwargs):
"""Replace the current list of recipients with a new one"""
for selector in args:
if selector in new_data:
new_recipients = ([new_data.get(selector)]
if isinstance(new_data.get(selector),
(str, unicode))
else new_data.get(selector))
current_data.update({'recipients':
_mangle_recipients(current_data.get('name'),
new_recipients)})
def _add_recipients(current_data, new_data, *args, **kwargs):
"""Add mail recipients to an existing list of recipients"""
for selector in args:
if selector in new_data:
current_recipients = current_data.get('recipients', [])
current_recipients.extend([new_data.get(selector)]
if isinstance(new_data.get(selector),
(str, unicode))
else new_data.get(selector))
current_data.update({'recipients':
_mangle_recipients(current_data.get('name'),
current_recipients)})
def _del_recipients(current_data, new_data, *args, **kwargs):
"""Remove mail recipients from an existing list"""
for selector in args:
if selector in new_data:
deleted_recipients = ([new_data.get(selector)]
if isinstance(new_data.get(selector),
(str, unicode))
else new_data.get(selector))
current_recipients = current_data.get('recipients', [])
new_recipients = [x for x in current_recipients
if x not in deleted_recipients]
current_data.update({'recipients': new_recipients})
def etc_aliases_parse_recipients(*args, **kwargs):
"""Return a parsed list of mail aliases and recipients"""
input_args = []
parsed_aliases = {}
# Flatten the input list
for sublist in list(args):
for item in sublist:
input_args.append(item)
for element in input_args:
if isinstance(element, dict):
if (any(x in element for x in ['name', 'alias']) and
element.get('state', 'present') != 'ignore'):
alias_name = element.get('alias', element.get('name'))
current_alias = (parsed_aliases[alias_name].copy()
if alias_name in parsed_aliases
else {})
current_alias.update({
'name': alias_name, # in case of a new entry
'state': element.get('state',
current_alias.get('state',
'present')),
'weight': int(element.get('weight',
current_alias.get('weight', 0))),
'section': element.get('section',
current_alias.get('section',
'unknown'))
})
_update_recipients(current_alias, element, 'dest', 'to')
_add_recipients(current_alias, element,
'add_dest', 'add_to', 'cc', 'bcc')
_del_recipients(current_alias, element,
'del_dest', 'del_to')
if 'real_name' in element or 'real_alias' in element:
current_alias['real_name'] = (
element.get('real_name',
element.get('real_alias')))
if 'comment' in element:
current_alias['comment'] = element.get('comment')
if not current_alias.get('recipients', ''):
current_alias['state'] = 'comment'
parsed_aliases.update({alias_name: current_alias})
# These parameters are special and should not be interpreted
# directly as mail aliases.
elif not all(x in ['name', 'alias', 'state', 'comment',
'section', 'weight', 'dest', 'to',
'add_dest', 'add_to', 'cc', 'bcc',
'del_dest', 'del_to']
for x in element):
for key, value in element.items():
current_alias = parsed_aliases.get(key, {}).copy()
current_alias.update({
'name': key,
'recipients': (_mangle_recipients(
current_alias.get('name'), [value]
if isinstance(value, (str, unicode))
else value)),
'state': 'present',
'weight': int(element.get('weight',
current_alias.get('weight', 0))),
'section': current_alias.get('section', 'unknown')
})
current_alias.update({
'recipients': (_mangle_recipients(
current_alias.get('name'),
([value]
if isinstance(value, (str, unicode))
else value)))
})
if not current_alias.get('recipients', ''):
current_alias['state'] = 'comment'
parsed_aliases[key] = current_alias
# Expand the dictionary of aliases into a list,
# and return sorted by weight.
return sorted(parsed_aliases.values(), key=itemgetter('weight', 'name'))
class FilterModule(object):
"""Register custom filter plugins in Ansible"""
def filters(self):
return {'etc_aliases_parse_recipients': etc_aliases_parse_recipients}

View file

@ -0,0 +1,60 @@
# Copyright (C) 2015 Maciej Delmanowski <drybjed@gmail.com>
# SPDX-License-Identifier: GPL-3.0-or-later
#
# This file is part of Ansible
#
# Ansible is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Ansible 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 Ansible. If not, see <https://www.gnu.org/licenses/>.
from ansible import errors
try:
import fnmatch
except Exception as e:
raise errors.AnsibleFilterError('fnmatch python library not found')
def globmatch_filter(value, pattern):
''' Return string or list of items matching given glob pattern(s). '''
if not isinstance(pattern, (list, tuple)):
pattern = [pattern]
if isinstance(value, (list, tuple)):
_ret = []
for element in pattern:
for entry in value:
if fnmatch.fnmatch(str(entry), str(element)):
if entry not in _ret:
_ret.append(entry)
if _ret:
return _ret
else:
return list()
else:
for element in pattern:
if fnmatch.fnmatch(str(value), str(element)):
return value
class FilterModule(object):
''' Return string or list of items matching given glob pattern(s). '''
def filters(self):
return {
'globmatch': globmatch_filter
}

View file

@ -0,0 +1,152 @@
# (c) 2014 Tim Raasveld <info@webtrein.nl>
# https://github.com/timraasveld/ansible-string-split-filter/
# (c) 2014 Maciej Delmanowski <drybjed@gmail.com>
# https://debops.org/
# SPDX-License-Identifier: CC0-1.0
# License: CC0 1.0 Universal
#
# Statement of Purpose
#
# The laws of most jurisdictions throughout the world automatically confer
# exclusive Copyright and Related Rights (defined below) upon the creator and
# subsequent owner(s) (each and all, an "owner") of an original work of
# authorship and/or a database (each, a "Work").
#
# Certain owners wish to permanently relinquish those rights to a Work for the
# purpose of contributing to a commons of creative, cultural and scientific
# works ("Commons") that the public can reliably and without fear of later
# claims of infringement build upon, modify, incorporate in other works, reuse
# and redistribute as freely as possible in any form whatsoever and for any
# purposes, including without limitation commercial purposes. These owners may
# contribute to the Commons to promote the ideal of a free culture and the
# further production of creative, cultural and scientific works, or to gain
# reputation or greater distribution for their Work in part through the use and
# efforts of others.
#
# For these and/or other purposes and motivations, and without any expectation
# of additional consideration or compensation, the person associating CC0 with
# a Work (the "Affirmer"), to the extent that he or she is an owner of
# Copyright and Related Rights in the Work, voluntarily elects to apply CC0 to
# the Work and publicly distribute the Work under its terms, with knowledge of
# his or her Copyright and Related Rights in the Work and the meaning and
# intended legal effect of CC0 on those rights.
#
# 1. Copyright and Related Rights. A Work made available under CC0 may be
# protected by copyright and related or neighboring rights ("Copyright and
# Related Rights"). Copyright and Related Rights include, but are not limited
# to, the following:
#
# i. the right to reproduce, adapt, distribute, perform, display,
# communicate, and translate a Work;
#
# ii. moral rights retained by the original author(s) and/or performer(s);
#
# iii. publicity and privacy rights pertaining to a person's image or
# likeness depicted in a Work;
#
# iv. rights protecting against unfair competition in regards to a Work,
# subject to the limitations in paragraph 4(a), below;
#
# v. rights protecting the extraction, dissemination, use and reuse of data
# in a Work;
#
# vi. database rights (such as those arising under Directive 96/9/EC of the
# European Parliament and of the Council of 11 March 1996 on the legal
# protection of databases, and under any national implementation thereof,
# including any amended or successor version of such directive); and
#
# vii. other similar, equivalent or corresponding rights throughout the world
# based on applicable law or treaty, and any national implementations
# thereof.
#
# 2. Waiver. To the greatest extent permitted by, but not in contravention of,
# applicable law, Affirmer hereby overtly, fully, permanently, irrevocably and
# unconditionally waives, abandons, and surrenders all of Affirmer's Copyright
# and Related Rights and associated claims and causes of action, whether now
# known or unknown (including existing as well as future claims and causes of
# action), in the Work (i) in all territories worldwide, (ii) for the maximum
# duration provided by applicable law or treaty (including future time
# extensions), (iii) in any current or future medium and for any number of
# copies, and (iv) for any purpose whatsoever, including without limitation
# commercial, advertising or promotional purposes (the "Waiver"). Affirmer
# makes the Waiver for the benefit of each member of the public at large and to
# the detriment of Affirmer's heirs and successors, fully intending that such
# Waiver shall not be subject to revocation, rescission, cancellation,
# termination, or any other legal or equitable action to disrupt the quiet
# enjoyment of the Work by the public as contemplated by Affirmer's express
# Statement of Purpose.
#
# 3. Public License Fallback. Should any part of the Waiver for any reason be
# judged legally invalid or ineffective under applicable law, then the Waiver
# shall be preserved to the maximum extent permitted taking into account
# Affirmer's express Statement of Purpose. In addition, to the extent the
# Waiver is so judged Affirmer hereby grants to each affected person
# a royalty-free, non transferable, non sublicensable, non exclusive,
# irrevocable and unconditional license to exercise Affirmer's Copyright and
# Related Rights in the Work (i) in all territories worldwide, (ii) for the
# maximum duration provided by applicable law or treaty (including future time
# extensions), (iii) in any current or future medium and for any number of
# copies, and (iv) for any purpose whatsoever, including without limitation
# commercial, advertising or promotional purposes (the "License"). The License
# shall be deemed effective as of the date CC0 was applied by Affirmer to the
# Work. Should any part of the License for any reason be judged legally invalid
# or ineffective under applicable law, such partial invalidity or
# ineffectiveness shall not invalidate the remainder of the License, and in
# such case Affirmer hereby affirms that he or she will not (i) exercise any of
# his or her remaining Copyright and Related Rights in the Work or (ii) assert
# any associated claims and causes of action with respect to the Work, in
# either case contrary to Affirmer's express Statement of Purpose.
#
# 4. Limitations and Disclaimers.
#
# a. No trademark or patent rights held by Affirmer are waived, abandoned,
# surrendered, licensed or otherwise affected by this document.
#
# b. Affirmer offers the Work as-is and makes no representations or
# warranties of any kind concerning the Work, express, implied, statutory or
# otherwise, including without limitation warranties of title,
# merchantability, fitness for a particular purpose, non infringement, or the
# absence of latent or other defects, accuracy, or the present or absence of
# errors, whether or not discoverable, all to the greatest extent permissible
# under applicable law.
#
# c. Affirmer disclaims responsibility for clearing rights of other persons
# that may apply to the Work or any use thereof, including without limitation
# any person's Copyright and Related Rights in the Work. Further, Affirmer
# disclaims responsibility for obtaining any necessary consents, permissions
# or other rights required for any use of the Work.
#
# d. Affirmer understands and acknowledges that Creative Commons is not a
# party to this document and has no duty or obligation with respect to this
# CC0 or use of the Work.
#
# For more information, please see
# <http://creativecommons.org/publicdomain/zero/1.0/>
import re
def split_string(string, separator=None, maxsplit=-1):
try:
return string.split(separator, maxsplit)
except Exception:
return list(string)
def split_regex(string, seperator_pattern):
try:
return re.split(seperator_pattern, string)
except Exception:
return list(string)
class FilterModule(object):
''' A filter to split a string into a list. '''
def filters(self):
return {
'split': split_string,
'split_regex': split_regex,
}

View file

@ -0,0 +1,53 @@
# Copyright (C) 2017, Matt Martz <matt@sivel.net>
# GNU General Public License v3.0+
# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
# SPDX-License-Identifier: GPL-3.0-or-later
# Make coding more python3-ish
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
import functools
from ansible.plugins.inventory.toml import HAS_TOML, toml_dumps
try:
from ansible.plugins.inventory.toml import toml
except ImportError:
pass
from ansible.errors import AnsibleFilterError
from ansible.module_utils._text import to_text
from ansible.module_utils.common._collections_compat import MutableMapping
from ansible.module_utils.six import string_types
def _check_toml(func):
@functools.wraps(func)
def inner(o):
if not HAS_TOML:
raise AnsibleFilterError('The %s filter plugin requires '
'the python "toml" library' % func.__name__)
return func(o)
return inner
@_check_toml
def from_toml(o):
if not isinstance(o, string_types):
raise AnsibleFilterError('from_toml requires a string, got %s' % type(o))
return toml.loads(to_text(o, errors='surrogate_or_strict'))
@_check_toml
def to_toml(o):
if not isinstance(o, MutableMapping):
raise AnsibleFilterError('to_toml requires a dict, got %s' % type(o))
return to_text(toml_dumps(o), errors='surrogate_or_strict')
class FilterModule(object):
def filters(self):
return {
'to_toml': to_toml,
'from_toml': from_toml
}

View file

@ -0,0 +1,116 @@
# cran.py: install or remove R packages
# Homepage: https://github.com/yutannihilation/ansible-module-cran
# Copyright (C) 2016 Hiroaki Yutani <yutani.ini@gmail.com>
# Copyright (C) 2017 Maciej Delmanowski <drybjed@gmail.com>
# Copyright (C) 2017 DebOps <https://debops.org/>
# SPDX-License-Identifier: MIT
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
from ansible.module_utils.basic import AnsibleModule
DOCUMENTATION = '''
---
module: cran
short_description: Install R packages.
options:
name:
description:
- The name of an R package.
required: true
default: null
state:
description:
- The state of module
required: false
choices: ['present', 'absent']
default: present
repo:
description:
- The repository
required: false
default: "https://cran.rstudio.com/"
'''
RSCRIPT = '/usr/bin/Rscript'
def get_installed_version(module):
cmnd = [RSCRIPT, '--slave', '--no-save', '--no-restore-history', '-e',
'p <- installed.packages(); cat(p[p[,1] == "{name:}",'
'3])'.format(name=module.params['name'])]
(rc, stdout, stderr) = module.run_command(cmnd, check_rc=False)
return stdout.strip() if rc == 0 else None
def install(module):
cmnd = [RSCRIPT, '--slave', '--no-save', '--no-restore-history', '-e',
'install.packages(pkgs="{name:}",repos="{repos:}")'
''.format(name=module.params['name'],
repos=module.params['repo'])]
(rc, stdout, stderr) = module.run_command(cmnd, check_rc=True)
return stderr
def uninstall(module):
cmnd = [RSCRIPT, '--slave', '--no-save', '--no-restore-history', '-e',
'remove.packages(pkgs="{name:}")'
''.format(name=module.params['name'])]
(rc, stdout, stderr) = module.run_command(cmnd, check_rc=True)
return stderr
def main():
module = AnsibleModule(
argument_spec=dict(
state=dict(default='present', choices=['present', 'absent']),
name=dict(required=True),
repo=dict(default='https://cran.rstudio.com/')
)
)
state = module.params['state']
name = module.params['name']
changed = False
version = get_installed_version(module)
if state == 'present' and not version:
stderr = install(module)
version = get_installed_version(module)
if not version:
module.fail_json(
msg='Failed to install {name:}: {err:}'.format(
name=name, err=stderr, version=version))
changed = True
elif state == 'absent' and version:
stderr = uninstall(module)
version = get_installed_version(module)
if version:
module.fail_json(
msg='Failed to install {name:}: {err:}'.format(
name=name, err=stderr))
changed = True
module.exit_json(changed=changed, name=name, version=version)
if __name__ == '__main__':
main()

View file

@ -0,0 +1,409 @@
# Copyright (c) 2017-2018, Yann Amar <quidame@poivron.org>
# Copyright (c) 2019, Maciej Delmanowski <drybjed@gmail.com>
# Copyright (c) 2019, DebOps https://debops.org/
# SPDX-License-Identifier: GPL-3.0-or-later
#
# GNU General Public License v3.0+
# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
# Source: https://github.com/quidame/ansible-module-dpkg_divert
from __future__ import absolute_import, division, print_function
from ansible.module_utils.basic import AnsibleModule
import os.path
import errno
import os
import re
__metaclass__ = type
ANSIBLE_METADATA = {
'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'
}
DOCUMENTATION = '''
---
module: dpkg_divert
short_description: Override a package's version of a file
description:
- A diversion is for C(dpkg) the knowledge that only a given I(package)
is allowed to install a file at a given I(path). Other packages shipping
their own version of this file will be forced to I(divert) it, i.e. to
install it at another location. It allows one to keep changes in a file
provided by a debian package by preventing its overwrite at package
upgrade.
- This module manages diversions of debian packages files using the
C(dpkg-divert)(1) commandline tool. It can either create or remove a
diversion for a given file, but also update an existing diversion to
modify its holder and/or its divert path.
- It's a feature of this module to mimic C(dpkg-divert)'s behaviour
regarding the renaming of files when removing as well as adding a
diversion; existing files are never overwritten.
version_added: "2.4"
author: "quidame@poivron.org"
options:
path:
description:
- The original and absolute path of the file to be diverted or
undiverted. This path is unique, i.e. it is not possible to get
two diversions for the same I(path).
required: true
type: 'path'
aliases: [ 'name' ]
state:
description:
- When I(state=absent), remove the diversion of the specified
I(path); when I(state=present), create the diversion if it does
not exist, or update its I(package) holder or I(divert) path,
if any, and if I(force) is C(True).
- Unless I(force) is C(True), the removal of I(path)'s diversion
only happens if the diversion matches the I(divert) and
I(package) values, if any.
type: 'string'
default: 'present'
choices: [ 'absent', 'present' ]
package:
description:
- The name of the package whose copy of file is not diverted, also
known as the diversion holder or the package the diversion
belongs to.
- The actual package does not have to be installed or even to exist
for its name to be valid. If not specified, the diversion is held
by 'LOCAL', which is reserved for local diversions.
- Removing or updating a diversion fails if the diversion exists
and belongs to another package, unless I(force) is C(True).
divert:
description:
- The location where the versions of file will be diverted.
- The default suffix is C(.distrib) for diversions defined by a
package and C(.dpkg-divert) for 'LOCAL' diversions.
type: 'path'
rename:
description:
- Actually move the file aside (or back).
- Renaming is skipped (but module doesn't fail) in case the
destination file already exists. This is a C(dpkg-divert)
feature, and its purpose is to never overwrite a file. It also
makes the command itself idempotent, and the module's I(force)
parameter has no effect on this behaviour.
- Also, I(rename) is ignored if the diversion entry is unchanged
in the diversion database (adding an already existing diversion
or removing a non-existing one).
type: 'bool'
default: true
delete:
description:
- When I(yes), delete the file in place of the original before
reverting. This only applies with I(state=absent) to avoid
C(dpkg-divert) command complaining about existing file in place
of the diverted one.
type: 'bool'
default: false
force:
description:
- Force to divert file when diversion already exists and is hold
by another I(package) or points to another I(divert). There is
no need to use it for I(remove) action if I(divert) or I(package)
are not used.
- This doesn't override the rename's lock feature, i.e. it doesn't
help to force I(rename), but only to force the diversion for
dpkg.
type: 'bool'
default: false
requirements: [ dpkg-divert, env ]
'''
EXAMPLES = '''
# Divert /etc/screenrc to /etc/screenrc.dpkg-divert and rename the file
- name: Create local diversion
dpkg_divert: path=/etc/screenrc
# Divert /etc/screenrc to /etc/screenrc.distrib for package 'branding' and
# rename the file
- name: Create diversion for APT package
dpkg_divert:
name: /etc/screenrc
package: branding
- name: Delete the file in place of the original and remove the diversion
dpkg_divert:
name: /etc/screenrc
state: absent
delete: yes
- name: remove the screenrc diversion only if belonging to 'branding'
dpkg_divert:
name: /etc/screenrc
package: branding
state: absent
# Divert screenrc to screenrc.dpkg-divert, but don't rename the file
- name: Divert with custom rename
dpkg_divert:
path: /etc/screenrc
divert: /etc/screenrc.dpkg-divert
rename: no
# Divert and rename screenrc to screenrc.dpkg-divert, even if diversion is
# already set
- name: Divert with custom rename
dpkg_divert:
path: /etc/screenrc
divert: /etc/screenrc.dpkg-divert
rename: yes
force: yes
# Remove the screenrc diversion and maybe move the diverted file to its
# original place
- name: Remove diversion and rename file
dpkg_divert:
path: /etc/screenrc
state: absent
rename: yes
'''
def main():
# Mimic the behaviour of the dpkg-divert(1) command: '--add' is implicit
# when not using '--remove'; '--rename' takes care to never overwrite
# existing files; and options are intended to not conflict between them.
# 'force' is an option of the module, not of the command, and implies to
# run the command twice. Its purpose is to allow one to re-divert a file
# with another target path or to 'give' it to another package, in one task.
# This is very easy because one of the values is unique in the diversion
# database, and dpkg-divert itself is idempotent (does nothing when nothing
# needs doing).
module = AnsibleModule(
argument_spec=dict(
path=dict(required=True, type='path', aliases=['name']),
state=dict(required=False, type='str', default='present',
choices=['absent', 'present']),
package=dict(required=False, type='str', default='LOCAL'),
divert=dict(required=False, type='path'),
rename=dict(required=False, type='bool', default=True),
delete=dict(required=False, type='bool', default=False),
force=dict(required=False, type='bool', default=False),
),
supports_check_mode=True,
)
path = module.params['path']
state = module.params['state']
package = module.params['package']
divert = module.params['divert']
rename = module.params['rename']
delete = module.params['delete']
force = module.params['force']
DPKG_DIVERT = module.get_bin_path('dpkg-divert', required=True)
# We need to parse the command's output, which is localized.
# So we have to reset environment variable (LC_ALL).
ENVIRONMENT = module.get_bin_path('env', required=True)
# Start to build the commandline we'll have to run
COMMANDLINE = [ENVIRONMENT, 'LC_ALL=C', DPKG_DIVERT, path]
# Then insert options as requested in the task parameters:
if state == 'absent':
COMMANDLINE.insert(3, '--remove')
elif state == 'present':
COMMANDLINE.insert(3, '--add')
if rename:
COMMANDLINE.insert(3, '--rename')
if divert:
COMMANDLINE.insert(3, '--divert')
COMMANDLINE.insert(4, divert)
else:
if package == 'LOCAL':
COMMANDLINE.insert(3, '--divert')
COMMANDLINE.insert(4, '.'.join([path, 'dpkg-divert']))
elif package:
COMMANDLINE.insert(3, '--divert')
COMMANDLINE.insert(4, '.'.join([path, 'distrib']))
if package == 'LOCAL':
COMMANDLINE.insert(3, '--local')
elif package:
COMMANDLINE.insert(3, '--package')
COMMANDLINE.insert(4, package)
# dpkg-divert has a useful --test option that we will use in check mode or
# when needing to parse output before actually doing anything.
TESTCOMMAND = list(COMMANDLINE)
TESTCOMMAND.insert(3, '--test')
if module.check_mode:
COMMANDLINE = list(TESTCOMMAND)
cmd = ' '.join(COMMANDLINE)
# `dpkg-divert --listpackage FILE` always returns 0, but not diverted files
# provide no output.
rc, listpackage, _ = module.run_command(
[DPKG_DIVERT, '--listpackage', path])
rc, placeholder, _ = module.run_command(TESTCOMMAND)
# There is probably no need to do more than that. Please read the first
# sentence of the next comment for a better understanding of the following
# `if` statement:
if rc == 0 or not force or not listpackage:
# If requested, delete the file to make way for the reverted one, but
# only of the diversion currently exists.
if not module.check_mode:
if state == 'absent' and listpackage and delete:
try:
os.unlink(path)
except OSError as e:
# It may already have been removed
if e.errno != errno.ENOENT:
raise AnsibleModuleError(
results={'msg': "unlinking failed: %s "
% to_native(e), 'path': path})
# In the check mode, the 'dpkg-divert' command still tests the
# diversion removal for real and returns with an error when a changed
# file is in place. In that specific case, we instead simulate a file
# deletion and diversion removal ourselves to have the check mode
# succeed.
if (module.check_mode and state == 'absent' and delete and
listpackage and os.path.exists(path)):
fake_stdout = ['Deleting', path, 'and', 'removing']
if package == 'LOCAL':
fake_stdout.append('local')
fake_stdout.extend(['diversion', 'of', path, 'to'])
if divert:
fake_stdout.append(divert)
else:
if package == 'LOCAL':
fake_stdout.append('.'.join([path, 'dpkg-divert']))
elif package:
fake_stdout.append('.'.join([path, 'distrib']))
rc, stdout, stderr = [0, ' '.join(fake_stdout), '']
else:
rc, stdout, stderr = module.run_command(COMMANDLINE, check_rc=True)
if re.match('^(Leaving|No diversion)', stdout):
module.exit_json(changed=False, stdout=stdout,
stderr=stderr, cmd=cmd)
else:
module.exit_json(changed=True, stdout=stdout,
stderr=stderr, cmd=cmd)
# So, here we are: the test failed AND force is true AND a diversion exists
# for the file. Anyway, we have to remove it first (then stop here, or add
# a new diversion for the same file), and without failure. Cases of failure
# with dpkg-divert are:
# - The diversion does not belong to the same package (or LOCAL)
# - The divert filename is not the same (e.g. path.distrib != path.divert)
# So: force removal by stripping '--package' and '--divert' options... and
# their arguments. Fortunately, this module accepts only a few parameters,
# so we can rebuild a whole command line from scratch at no cost:
FORCEREMOVE = [ENVIRONMENT, 'LC_ALL=C', DPKG_DIVERT, '--remove', path]
module.check_mode and FORCEREMOVE.insert(3, '--test')
rename and FORCEREMOVE.insert(3, '--rename')
forcerm = ' '.join(FORCEREMOVE)
if state == 'absent':
rc, stdout, stderr = module.run_command(FORCEREMOVE, check_rc=True)
module.exit_json(changed=True, stdout=stdout,
stderr=stderr, cmd=forcerm)
# The situation is that we want to modify the settings (package or divert)
# of an existing diversion. dpkg-divert does not handle this, and we have
# to remove the diversion and set a new one. First, get state info:
rc, truename, _ = module.run_command([DPKG_DIVERT, '--truename', path])
rc, rmout, rmerr = module.run_command(FORCEREMOVE, check_rc=True)
if module.check_mode:
module.exit_json(changed=True, cmd=[forcerm, cmd],
msg=[rmout,
"*** RUNNING IN CHECK MODE ***",
"The next step can't be actually performed - "
"even dry-run - without error (since the "
"previous removal didn't happen) but is "
"supposed to achieve the task."])
old = truename.rstrip()
if divert:
new = divert
else:
if package == 'LOCAL':
new = '.'.join([path, 'dpkg-divert'])
elif package:
new = '.'.join([path, 'distrib'])
# Store state of files as they may change
old_exists = os.path.isfile(old)
new_exists = os.path.isfile(new)
# RENAMING NOT REMAINING
# The behaviour of this module is to NEVER overwrite a file, i.e. never
# change file contents but only file paths and only if not conflicting,
# as does dpkg-divert. It means that if there is already a diversion for
# a given file and the divert file exists too, the divert file must be
# moved from old to new divert paths between the two dpkg-divert commands,
# because:
#
# src = /etc/screenrc (tweaked ; exists)
# old = /etc/screentc.distrib (default ; exists)
# new = /etc/screenrc.ansible (not existing yet)
#
# Without extra move:
# 1. dpkg-divert --rename --remove src
# => dont move old to src because src exists
# 2. dpkg-divert --rename --divert new --add src
# => move src to new because new doesn't exist
# Results:
# - old still exists with default contents
# - new holds the tweaked contents
# - src is missing
# => confusing, kind of breakage
#
# With extra move:
# 1. dpkg-divert --rename --remove src
# => dont move old to src because src exists
# 2. os.path.rename(old, new) [conditional]
# => move old to new because new doesn't exist
# 3. dpkg-divert --rename --divert new --add src
# => dont move src to new because new exists
# Results:
# - old does not exist anymore
# - src is still the same tweaked file
# - new exists with default contents
# => idempotency for next times, and no breakage
#
if rename and old_exists and not new_exists:
os.rename(old, new)
rc, stdout, stderr = module.run_command(COMMANDLINE)
rc == 0 and module.exit_json(changed=True, stdout=stdout, stderr=stderr,
cmd=[forcerm, cmd], msg=[rmout, stdout])
# Damn! FORCEREMOVE succeeded and COMMANDLINE failed. Try to restore old
# state and end up with a 'failed' status anyway.
if (rename and (old_exists and not os.path.isfile(old)) and
(os.path.isfile(new) and not new_exists)):
os.rename(new, old)
RESTORE = [ENVIRONMENT, 'LC_ALL=C', DPKG_DIVERT, '--divert', old, path]
old_pkg = listpackage.rstrip()
if old_pkg == "LOCAL":
RESTORE.insert(3, '--local')
else:
RESTORE.insert(3, '--package')
RESTORE.insert(4, old_pkg)
rename and RESTORE.insert(3, '--rename')
module.run_command(RESTORE, check_rc=True)
module.exit_json(failed=True, changed=True, stdout=stdout,
stderr=stderr, cmd=[forcerm, cmd])
if __name__ == '__main__':
main()

View file

@ -0,0 +1,408 @@
# Copyright (c) 2016, Peter Sagerson <psagers@ignorare.net>
# Copyright (c) 2016, Jiri Tyr <jiri.tyr@gmail.com>
# Copyright (c) 2017, Alexander Korinek <noles@a3k.net>
# Copyright (c) 2019, Maciej Delmanowski <drybjed@gmail.com>
# SPDX-License-Identifier: GPL-3.0-or-later
#
# GNU General Public License v3.0+
# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils._text import to_native, to_bytes
import traceback
import re
try:
import ldap
import ldap.sasl
HAS_LDAP = True
except ImportError:
HAS_LDAP = False
__metaclass__ = type
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = """
---
module: ldap_attrs
short_description: Add or remove multiple LDAP attribute values.
description:
- Add or remove multiple LDAP attribute values.
notes:
- This only deals with attributes on existing entries. To add or remove
whole entries, see M(ldap_entry).
- The default authentication settings will attempt to use a SASL EXTERNAL
bind over a UNIX domain socket. This works well with the default Ubuntu
install for example, which includes a cn=peercred,cn=external,cn=auth ACL
rule allowing root to modify the server configuration. If you need to use
a simple bind to access your server, pass the credentials in I(bind_dn)
and I(bind_pw).
- For I(state=present) and I(state=absent), all value comparisons are
performed on the server for maximum accuracy. For I(state=exact), values
have to be compared in Python, which obviously ignores LDAP matching
rules. This should work out in most cases, but it is theoretically
possible to see spurious changes when target and actual values are
semantically identical but lexically distinct.
version_added: '2.5'
author:
- Jiri Tyr (@jtyr)
- Alexander Korinek (@noles)
requirements:
- python-ldap
options:
bind_dn:
required: false
default: null
description:
- A DN to bind with. If this is omitted, we'll try a SASL bind with
the EXTERNAL mechanism. If this is blank, we'll use an anonymous
bind.
bind_pw:
required: false
default: null
description:
- The password to use with I(bind_dn).
dn:
required: true
description:
- The DN of the entry to modify.
server_uri:
required: false
default: ldapi:///
description:
- A URI to the LDAP server. The default value lets the underlying
LDAP client library look for a UNIX domain socket in its default
location.
start_tls:
required: false
choices: ['yes', 'no']
default: 'no'
description:
- If true, we'll use the START_TLS LDAP extension.
state:
required: false
choices: [present, absent, exact]
default: present
description:
- The state of the attribute values. If C(present), all given
values will be added if they're missing. If C(absent), all given
values will be removed if present. If C(exact), the set of values
will be forced to exactly those provided and no others. If
I(state=exact) and I(value) is empty, all values for this
attribute will be removed.
attributes:
required: true
description:
- The attribute(s) and value(s) to add or remove. The complex argument
format is required in order to pass a list of strings (see examples).
ordered:
required: false
choices: ['yes', 'no']
default: 'no'
description:
- If C(yes), prepend list values with X-ORDERED index numbers in all
attributes specified in the current task. This is useful mostly with
I(olcAccess) attribute to easily manage LDAP Access Control Lists.
validate_certs:
required: false
choices: ['yes', 'no']
default: 'yes'
description:
- If C(no), SSL certificates will not be validated. This should only be
used on sites using self-signed certificates.
"""
EXAMPLES = """
- name: Configure directory number 1 for example.com
ldap_attrs:
dn: olcDatabase={1}hdb,cn=config
attributes:
olcSuffix: dc=example,dc=com
state: exact
# The complex argument format is required here to pass a list of ACL strings.
- name: Set up the ACL
ldap_attrs:
dn: olcDatabase={1}hdb,cn=config
attributes:
olcAccess:
- >-
{0}to attrs=userPassword,shadowLastChange
by self write
by anonymous auth
by dn="cn=admin,dc=example,dc=com" write
by * none'
- >-
{1}to dn.base="dc=example,dc=com"
by dn="cn=admin,dc=example,dc=com" write
by * read
state: exact
# An alternative approach with automatic X-ORDERED numbering
- name: Set up the ACL
ldap_attrs:
dn: olcDatabase={1}hdb,cn=config
attributes:
olcAccess:
- >-
to attrs=userPassword,shadowLastChange
by self write
by anonymous auth
by dn="cn=admin,dc=example,dc=com" write
by * none'
- >-
to dn.base="dc=example,dc=com"
by dn="cn=admin,dc=example,dc=com" write
by * read
ordered: yes
state: exact
- name: Declare some indexes
ldap_attrs:
dn: olcDatabase={1}hdb,cn=config
attributes:
olcDbIndex:
- objectClass eq
- uid eq
- name: Set up a root user, which we can use later to bootstrap the directory
ldap_attrs:
dn: olcDatabase={1}hdb,cn=config
attributes:
olcRootDN: cn=root,dc=example,dc=com
olcRootPW: "{SSHA}tabyipcHzhwESzRaGA7oQ/SDoBZQOGND"
state: exact
- name: Get rid of an unneeded attribute
ldap_attrs:
dn: uid=jdoe,ou=people,dc=example,dc=com
attributes:
shadowExpire: ""
state: exact
server_uri: ldap://localhost/
bind_dn: cn=admin,dc=example,dc=com
bind_pw: password
#
# The same as in the previous example but with the authentication details
# stored in the ldap_auth variable:
#
# ldap_auth:
# server_uri: ldap://localhost/
# bind_dn: cn=admin,dc=example,dc=com
# bind_pw: password
- name: Get rid of an unneeded attribute
ldap_attrs:
dn: uid=jdoe,ou=people,dc=example,dc=com
attributes:
shadowExpire: ""
state: exact
params: "{{ ldap_auth }}"
"""
RETURN = """
modlist:
description: list of modified parameters
returned: success
type: list
sample: '[[2, "olcRootDN", ["cn=root,dc=example,dc=com"]]]'
"""
class LdapAttr(object):
def __init__(self, module):
# Shortcuts
self.module = module
self.bind_dn = self.module.params['bind_dn']
self.bind_pw = self.module.params['bind_pw']
self.dn = self.module.params['dn']
self.server_uri = self.module.params['server_uri']
self.start_tls = self.module.params['start_tls']
self.state = self.module.params['state']
self.verify_cert = self.module.params['validate_certs']
self.attrs = self.module.params['attributes']
self.ordered = self.module.params['ordered']
# Establish connection
self.connection = self._connect_to_ldap()
def _order_values(self, values):
""" Prepend X-ORDERED index numbers to attribute's values. """
ordered_values = []
if isinstance(values, list):
for index, value in enumerate(values):
cleaned_value = re.sub(r'^\{\d+\}', '', value)
ordered_values.append('{' + str(index) + '}' + cleaned_value)
return ordered_values
def _normalize_values(self, values):
""" Normalize attribute's values. """
norm_values = []
if isinstance(values, list):
if self.ordered:
norm_values = list(map(to_bytes,
self._order_values(list(map(str,
values)))))
else:
norm_values = list(map(to_bytes, values))
elif values != "":
norm_values = [to_bytes(str(values))]
return norm_values
def add(self):
modlist = []
for name, values in self.module.params['attributes'].items():
norm_values = self._normalize_values(values)
for value in norm_values:
if self._is_value_absent(name, value):
modlist.append((ldap.MOD_ADD, name, value))
return modlist
def delete(self):
modlist = []
for name, values in self.module.params['attributes'].items():
norm_values = self._normalize_values(values)
for value in norm_values:
if self._is_value_present(name, value):
modlist.append((ldap.MOD_DELETE, name, value))
return modlist
def exact(self):
modlist = []
for name, values in self.module.params['attributes'].items():
norm_values = self._normalize_values(values)
try:
results = self.connection.search_s(
self.dn, ldap.SCOPE_BASE, attrlist=[name])
except ldap.LDAPError as e:
self.module.fail_json(
msg="Cannot search for attribute %s" % name,
details=to_native(e))
current = results[0][1].get(name, [])
if frozenset(norm_values) != frozenset(current):
if len(current) == 0:
modlist.append((ldap.MOD_ADD, name, norm_values))
elif len(norm_values) == 0:
modlist.append((ldap.MOD_DELETE, name, None))
else:
modlist.append((ldap.MOD_REPLACE, name, norm_values))
return modlist
def _is_value_present(self, name, value):
""" True if the target attribute has the given value. """
try:
is_present = bool(
self.connection.compare_s(self.dn, name, value))
except ldap.NO_SUCH_ATTRIBUTE:
is_present = False
return is_present
def _is_value_absent(self, name, value):
""" True if the target attribute doesn't have the given value. """
return not self._is_value_present(name, value)
def _connect_to_ldap(self):
if not self.verify_cert:
ldap.set_option(ldap.OPT_X_TLS_REQUIRE_CERT, ldap.OPT_X_TLS_NEVER)
connection = ldap.initialize(self.server_uri)
if self.start_tls:
try:
connection.start_tls_s()
except ldap.LDAPError as e:
self.module.fail_json(msg="Cannot start TLS.",
details=to_native(e))
try:
if self.bind_dn is not None:
connection.simple_bind_s(self.bind_dn, self.bind_pw)
else:
connection.sasl_interactive_bind_s('', ldap.sasl.external())
except ldap.LDAPError as e:
self.module.fail_json(
msg="Cannot bind to the server.", details=to_native(e))
return connection
def main():
module = AnsibleModule(
argument_spec={
'bind_dn': dict(default=None),
'bind_pw': dict(default='', no_log=True),
'dn': dict(required=True),
'params': dict(type='dict'),
'server_uri': dict(default='ldapi:///'),
'start_tls': dict(default=False, type='bool'),
'state': dict(
default='present',
choices=['present', 'absent', 'exact']),
'attributes': dict(required=True, type='dict'),
'ordered': dict(default=False, type='bool'),
'validate_certs': dict(default=True, type='bool'),
},
supports_check_mode=True,
)
if not HAS_LDAP:
module.fail_json(
msg="Missing required 'ldap' module (pip install python-ldap)")
# Update module parameters with user's parameters if defined
if 'params' in module.params and isinstance(module.params['params'], dict):
module.params.update(module.params['params'])
# Remove the params
module.params.pop('params', None)
# Instantiate the LdapAttr object
ldap = LdapAttr(module)
state = module.params['state']
# Perform action
if state == 'present':
modlist = ldap.add()
elif state == 'absent':
modlist = ldap.delete()
elif state == 'exact':
modlist = ldap.exact()
changed = False
if len(modlist) > 0:
changed = True
if not module.check_mode:
try:
ldap.connection.modify_s(ldap.dn, modlist)
except Exception as e:
module.fail_json(msg="Attribute action failed.",
details=to_native(e),
exception=traceback.format_exc())
module.exit_json(changed=changed, modlist=modlist)
if __name__ == '__main__':
main()

View file

@ -0,0 +1,117 @@
# Copyright (C) 2021 David Härdeman <david@hardeman.nu>
# Copyright (C) 2021 DebOps <https://debops.org/>
#
# Based on community.general.dig, which is:
# (c) 2015, Jan-Piet Mens <jpmens(at)gmail.com>
# (c) 2017 Ansible Project
#
# SPDX-License-Identifier: GPL-3.0-or-later
from __future__ import (absolute_import, division, print_function)
from operator import itemgetter
from ansible.errors import AnsibleError
from ansible.plugins.lookup import LookupBase
from ansible.module_utils.common.text.converters import to_native
__metaclass__ = type
try:
import dns.exception
import dns.name
import dns.resolver
import dns.reversename
import dns.rdataclass
from dns.rdatatype import SRV
except ImportError:
raise AnsibleError("dig_srv: dnspython library is not installed")
def make_rdata_dict(rdata):
supported_types = {
SRV: ['priority', 'weight', 'port', 'target'],
}
rd = {}
if rdata.rdtype not in supported_types:
raise AnsibleError("dig_srv: unknown rdtype returned")
fields = supported_types[rdata.rdtype]
for f in fields:
val = rdata.__getattribute__(f)
if isinstance(val, dns.name.Name):
val = dns.name.Name.to_text(val)
if f == "target":
rd[f] = val.rstrip('.')
else:
rd[f] = val
return rd
class LookupModule(LookupBase):
def run(self, terms, variables=None, **kwargs):
if len(terms) != 3:
raise AnsibleError("dig_srv: three arguments expected")
myres = dns.resolver.Resolver(configure=True)
edns_size = 4096
myres.use_edns(0, ednsflags=dns.flags.DO, payload=edns_size)
domain = terms[0]
if not domain.endswith('.'):
domain += '.'
default_domain = terms[1]
default_port = terms[2]
qtype = 'SRV'
rdclass = dns.rdataclass.from_text('IN')
ret = []
try:
answers = myres.query(domain, qtype, rdclass=rdclass)
for rdata in answers:
try:
rd = make_rdata_dict(rdata)
rd['owner'] = answers.canonical_name.to_text().rstrip('.')
rd['type'] = dns.rdatatype.to_text(rdata.rdtype)
rd['ttl'] = answers.rrset.ttl
rd['class'] = dns.rdataclass.to_text(rdata.rdclass)
rd['dig_srv_src'] = 'dns'
ret.append(rd)
except Exception as e:
raise AnsibleError("dig_srv: can't parse response %s" % to_native(e))
except (dns.resolver.NXDOMAIN, dns.resolver.NoAnswer):
ret.append({
"class": "IN",
"owner": domain.rstrip('.'),
"port": default_port,
"priority": 0,
"target": default_domain,
"ttl": 0,
"type": "SRV",
"weight": 0,
"dig_srv_src": "fallback"
})
except dns.resolver.Timeout:
raise AnsibleError("dig_srv: timeout")
except dns.exception.DNSException as e:
raise AnsibleError("dig_srv: unhandled exception %s" % to_native(e))
for r in ret:
r.update({"target_port": r["target"] + ":" + str(r["port"])})
# This is in reverse order of importance, i.e. least important first.
# Note that the TTL field shows the remaining TTL when a RR is cached,
# so sorting on that field is not a good idea.
ret.sort(key=itemgetter("port"))
ret.sort(key=itemgetter("target"))
ret.sort(key=itemgetter("weight"), reverse=True)
ret.sort(key=itemgetter("priority"))
return ret

View file

@ -0,0 +1,195 @@
# (c) 2015, Robert Chady <rchady@sitepen.com>
# Based on `runner/lookup_plugins/file.py` for Ansible
# (c) 2012, Michael DeHaan <michael.dehaan@gmail.com>
# SPDX-License-Identifier: GPL-3.0-or-later
#
# This file is part of Debops.
# This file is NOT part of Ansible yet.
#
# Debops is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Ansible 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/>.
'''
This file implements the `file_src` lookup filter for Ansible. In difference
to the `file` filter, this searches values based on the `file-paths`
variable (colon separated) as configured in DebOps.
NOTE: This means this filter relies on DebOps.
'''
__author__ = "Robert Chady <rchady@sitepen.com>"
__copyright__ = "Copyright 2015 by Robert Chady <rchady@sitepen.com>"
__license__ = "GNU General Public LIcense version 3 (GPL v3) or later"
import os
try:
import debops
debops_version = debops.__version__.__version__
conf_section = 'override_paths'
conf_key = 'files_path'
except AttributeError:
try:
from debops import *
from debops.cmds import *
debops_version = '2.3.0'
conf_section = 'paths'
conf_key = 'file-paths'
except ImportError:
pass
except ModuleNotFoundError:
conf_section = ''
conf_key = ''
pass
try:
from ansible.plugins.lookup import LookupBase
except ImportError:
LookupBase = object
from distutils.version import LooseVersion
from ansible import __version__ as __ansible_version__
if LooseVersion(__ansible_version__) < LooseVersion("2.0"):
from ansible import utils, errors
class LookupModule(object):
def __init__(self, basedir, *args, **kwargs):
self.basedir = basedir
def run(self, terms, inject=None, **kwargs):
terms = utils.listify_lookup_plugin_terms(
terms, self.basedir, inject)
ret = []
config = {}
places = []
# this can happen if the variable contains a string,
# strictly not desired for lookup plugins, but users may
# try it, so make it work.
if not isinstance(terms, list):
terms = [terms]
try:
project_config = debops.config.Configuration()
project_dir = debops.projectdir.ProjectDir(
config=project_config)
project_root = project_dir.path
if project_dir.config.get(['project', 'type']) == 'modern':
config = project_dir.config.get([])
else:
config = project_dir.config.get(['views', 'system'])
except NameError:
try:
project_root = find_debops_project(required=False)
config = read_config(project_root)
except NameError:
pass
except NotADirectoryError:
# This is not a DebOps project directory, so continue as normal
pass
if conf_section in config and conf_key in config[conf_section]:
custom_places = (
config[conf_section][conf_key].split(':'))
for custom_path in custom_places:
if os.path.isabs(custom_path):
places.append(custom_path)
else:
places.append(os.path.join(
project_root, custom_path))
for term in terms:
if '_original_file' in inject:
relative_path = utils.path_dwim_relative(
inject['_original_file'], 'files',
'', self.basedir, check=False)
places.append(relative_path)
for path in places:
template = os.path.join(path, term)
if template and os.path.exists(template):
ret.append(template)
break
else:
raise errors.AnsibleError(
"could not locate file in lookup: %s"
% term)
return ret
else:
from ansible.errors import AnsibleError
from ansible.plugins.lookup import LookupBase
class LookupModule(LookupBase):
def run(self, terms, variables=None, **kwargs):
ret = []
config = {}
places = []
# this can happen if the variable contains a string,
# strictly not desired for lookup plugins, but users may
# try it, so make it work.
if not isinstance(terms, list):
terms = [terms]
try:
project_config = debops.config.Configuration()
project_dir = debops.projectdir.ProjectDir(
config=project_config)
project_root = project_dir.path
if project_dir.config.get(['project', 'type']) == 'modern':
config = project_dir.config.get([])
else:
config = project_dir.config.get(['views', 'system'])
except NameError:
try:
project_root = find_debops_project(required=False)
config = read_config(project_root)
except NameError:
pass
except NotADirectoryError:
# This is not a DebOps project directory, so continue as normal
pass
if conf_section in config and conf_key in config[conf_section]:
custom_places = (
config[conf_section][conf_key].split(':'))
for custom_path in custom_places:
if os.path.isabs(custom_path):
places.append(custom_path)
else:
places.append(os.path.join(
project_root, custom_path))
for term in terms:
if 'role_path' in variables:
relative_path = self._loader.path_dwim_relative(
variables['role_path'], 'files', '')
places.append(relative_path)
for path in places:
template = os.path.join(path, term)
if template and os.path.exists(template):
ret.append(template)
break
else:
raise AnsibleError(
"could not locate file in lookup: %s"
% term)
return ret

View file

@ -0,0 +1,66 @@
# Copyright (C) 2012 Michael DeHaan <michael.dehaan@gmail.com>
# Copyright (C) 2015 Hartmut Goebel <h.goebel@crazy-compilers.com>
# SPDX-License-Identifier: GPL-3.0-or-later
# Based on `runner/lookup_plugins/items.py` for Ansible
#
# This file is part of Debops.
# This file is NOT part of Ansible yet.
#
# Debops is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Ansible 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/>.
'''
This file implements the `with_lists` lookup filter for Ansible. In
differenceto `with_items`, this one does *not* flatten the lists passed to.
Example:
- debug: msg="{{item.0}} -- {{item.1}} -- {{item.2}}"
with_lists:
- ["General", "Verbosity", "0"]
- ["Mapping", "Nobody-User", "nobody"]
- ["Mapping", "Nobody-Group", "nogroup"]
Output (shortened):
"msg": "General -- Verbosity -- 0"
"msg": "Mapping -- Nobody-User -- nobody"
"msg": "Mapping -- Nobody-Group -- nogroup"
'''
import ansible.utils as utils
import ansible.errors as errors
try:
from ansible.plugins.lookup import LookupBase
except ImportError:
LookupBase = object
class LookupModule(LookupBase):
def __init__(self, basedir=None, **kwargs):
self.basedir = basedir
def run(self, terms, inject=None, **kwargs):
terms = utils.listify_lookup_plugin_terms(terms, self.basedir, inject)
if not isinstance(terms, (list, set)):
raise errors.AnsibleError("with_list expects a list or a set")
for i, elem in enumerate(terms):
if not isinstance(elem, (list, tuple)):
raise errors.AnsibleError(
"with_list expects a list (or a set) of lists"
" or tuples, but elem %i is not")
return terms

View file

@ -0,0 +1,198 @@
# (c) 2015, Robert Chady <rchady@sitepen.com>
# Based on `runner/lookup_plugins/file.py` for Ansible
# (c) 2012, Michael DeHaan <michael.dehaan@gmail.com>
# SPDX-License-Identifier: GPL-3.0-or-later
#
# This file is part of Debops.
# This file is NOT part of Ansible yet.
#
# Debops is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Ansible 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/>.
'''
This file implements the `task_src` lookup filter for Ansible. In difference
to the `file` filter, this searches values based on the `task-paths`
variable (colon separated) as configured in DebOps.
NOTE: This means this filter relies on DebOps.
'''
__author__ = "Robert Chady <rchady@sitepen.com>"
__copyright__ = "Copyright 2015 by Robert Chady <rchady@sitepen.com>"
__license__ = "GNU General Public LIcense version 3 (GPL v3) or later"
import os
try:
import debops
debops_version = debops.__version__.__version__
conf_section = 'override_paths'
conf_key = 'tasks_path'
except AttributeError:
try:
from debops import *
from debops.cmds import *
debops_version = '2.3.0'
conf_section = 'paths'
conf_key = 'task-paths'
except ImportError:
pass
except ModuleNotFoundError:
conf_section = ''
conf_key = ''
pass
try:
from ansible.plugins.lookup import LookupBase
except ImportError:
LookupBase = object
from distutils.version import LooseVersion
from ansible import __version__ as __ansible_version__
if LooseVersion(__ansible_version__) < LooseVersion("2.0"):
from ansible import utils, errors
class LookupModule(object):
def __init__(self, basedir, *args, **kwargs):
self.basedir = basedir
def run(self, terms, inject=None, **kwargs):
terms = utils.listify_lookup_plugin_terms(
terms, self.basedir, inject)
ret = []
config = {}
places = []
# this can happen if the variable contains a string,
# strictly not desired for lookup plugins, but users may
# try it, so make it work.
if not isinstance(terms, list):
terms = [terms]
try:
project_config = debops.config.Configuration()
project_dir = debops.projectdir.ProjectDir(
config=project_config)
project_root = project_dir.path
if project_dir.config.get(['project', 'type']) == 'modern':
config = project_dir.config.get([])
else:
config = project_dir.config.get(['views', 'system'])
except NameError:
try:
project_root = find_debops_project(required=False)
config = read_config(project_root)
except NameError:
pass
except NotADirectoryError:
# This is not a DebOps project directory, so continue as normal
pass
if conf_section in config and conf_key in config[conf_section]:
custom_places = (
config[conf_section][conf_key].split(':'))
for custom_path in custom_places:
if os.path.isabs(custom_path):
places.append(custom_path)
else:
places.append(os.path.join(
project_root, custom_path))
for term in terms:
if '_original_file' in inject:
relative_path = utils.path_dwim_relative(
inject['_original_file'], 'tasks', '',
self.basedir, check=False)
places.append(relative_path)
for path in places:
template = os.path.join(path, term)
if template and os.path.exists(template):
ret.append(template)
break
else:
raise errors.AnsibleError(
"could not locate file in lookup: %s"
% term)
return ret
else:
from ansible.errors import AnsibleError
from ansible.plugins.lookup import LookupBase
class LookupModule(LookupBase):
def run(self, terms, variables=None, **kwargs):
ret = []
config = {}
places = []
# this can happen if the variable contains a string,
# strictly not desired for lookup plugins, but users may
# try it, so make it work.
if not isinstance(terms, list):
terms = [terms]
try:
project_config = debops.config.Configuration()
project_dir = debops.projectdir.ProjectDir(
config=project_config)
project_root = project_dir.path
if project_dir.config.get(['project', 'type']) == 'modern':
config = project_dir.config.get([])
else:
config = project_dir.config.get(['views', 'system'])
except NameError:
try:
project_root = find_debops_project(required=False)
config = read_config(project_root)
except NameError:
pass
except NotADirectoryError:
# This is not a DebOps project directory, so continue as normal
pass
if conf_section in config and conf_key in config[conf_section]:
custom_places = (
config[conf_section][conf_key].split(':'))
for custom_path in custom_places:
if os.path.isabs(custom_path):
places.append(custom_path)
else:
places.append(os.path.join(
project_root, custom_path))
for term in terms:
if 'role_path' in variables:
relative_path = (
self._loader.path_dwim_relative(
variables['role_path'],
'tasks', ''))
places.append(relative_path)
for path in places:
template = os.path.join(path, term)
if template and os.path.exists(template):
ret.append(template)
break
else:
raise AnsibleError(
"could not locate file in lookup: %s"
% term)
return ret

View file

@ -0,0 +1,198 @@
# (c) 2015, Robert Chady <rchady@sitepen.com>
# Based on `runner/lookup_plugins/file.py` for Ansible
# (c) 2012, Michael DeHaan <michael.dehaan@gmail.com>
# SPDX-License-Identifier: GPL-3.0-or-later
#
# This file is part of Debops.
# This file is NOT part of Ansible yet.
#
# Debops is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Ansible 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/>.
'''
This file implements the `template_src` lookup filter for Ansible. In
difference to the `template` filter, this searches values based on the
`template-paths` variable (colon separated) as configured in DebOps.
NOTE: This means this filter relies on DebOps.
'''
__author__ = "Robert Chady <rchady@sitepen.com>"
__copyright__ = "Copyright 2015 by Robert Chady <rchady@sitepen.com>"
__license__ = "GNU General Public LIcense version 3 (GPL v3) or later"
import os
try:
import debops
debops_version = debops.__version__.__version__
conf_section = 'override_paths'
conf_key = 'templates_path'
except AttributeError:
try:
from debops import *
from debops.cmds import *
debops_version = '2.3.0'
conf_section = 'paths'
conf_key = 'template-paths'
except ImportError:
pass
except ModuleNotFoundError:
conf_section = ''
conf_key = ''
pass
try:
from ansible.plugins.lookup import LookupBase
except ImportError:
LookupBase = object
from distutils.version import LooseVersion
from ansible import __version__ as __ansible_version__
if LooseVersion(__ansible_version__) < LooseVersion("2.0"):
from ansible import utils, errors
class LookupModule(object):
def __init__(self, basedir, *args, **kwargs):
self.basedir = basedir
def run(self, terms, inject=None, **kwargs):
terms = utils.listify_lookup_plugin_terms(
terms, self.basedir, inject)
ret = []
config = {}
places = []
# this can happen if the variable contains a string,
# strictly not desired for lookup plugins, but users may
# try it, so make it work.
if not isinstance(terms, list):
terms = [terms]
try:
project_config = debops.config.Configuration()
project_dir = debops.projectdir.ProjectDir(
config=project_config)
project_root = project_dir.path
if project_dir.config.get(['project', 'type']) == 'modern':
config = project_dir.config.get([])
else:
config = project_dir.config.get(['views', 'system'])
except NameError:
try:
project_root = find_debops_project(required=False)
config = read_config(project_root)
except NameError:
pass
except NotADirectoryError:
# This is not a DebOps project directory, so continue as normal
pass
if conf_section in config and conf_key in config[conf_section]:
custom_places = (
config[conf_section][conf_key].split(':'))
for custom_path in custom_places:
if os.path.isabs(custom_path):
places.append(custom_path)
else:
places.append(os.path.join(
project_root, custom_path))
for term in terms:
if '_original_file' in inject:
relative_path = (
utils.path_dwim_relative(
inject['_original_file'], 'templates',
'', self.basedir, check=False))
places.append(relative_path)
for path in places:
template = os.path.join(path, term)
if template and os.path.exists(template):
ret.append(template)
break
else:
raise errors.AnsibleError(
"could not locate file in lookup: %s"
% term)
return ret
else:
from ansible.errors import AnsibleError
from ansible.plugins.lookup import LookupBase
class LookupModule(LookupBase):
def run(self, terms, variables=None, **kwargs):
ret = []
config = {}
places = []
# this can happen if the variable contains a string,
# strictly not desired for lookup plugins, but users may
# try it, so make it work.
if not isinstance(terms, list):
terms = [terms]
try:
project_config = debops.config.Configuration()
project_dir = debops.projectdir.ProjectDir(
config=project_config)
project_root = project_dir.path
if project_dir.config.get(['project', 'type']) == 'modern':
config = project_dir.config.get([])
else:
config = project_dir.config.get(['views', 'system'])
except NameError:
try:
project_root = find_debops_project(required=False)
config = read_config(project_root)
except NameError:
pass
except NotADirectoryError:
# This is not a DebOps project directory, so continue as normal
pass
if conf_section in config and conf_key in config[conf_section]:
custom_places = (
config[conf_section][conf_key].split(':'))
for custom_path in custom_places:
if os.path.isabs(custom_path):
places.append(custom_path)
else:
places.append(os.path.join(
project_root, custom_path))
for term in terms:
if 'role_path' in variables:
relative_path = (
self._loader.path_dwim_relative(
variables['role_path'], 'templates',
''))
places.append(relative_path)
for path in places:
template = os.path.join(path, term)
if template and os.path.exists(template):
ret.append(template)
break
else:
raise AnsibleError(
"could not locate file in lookup: %s"
% term)
return ret

View file

@ -0,0 +1,30 @@
---
# Copyright (C) 2017 Maciej Delmanowski <drybjed@gmail.com>
# Copyright (C) 2017-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: 'A set of custom Ansible plugins used by DebOps roles'
company: 'DebOps'
license: 'GPL-3.0-only'
min_ansible_version: '2.3.0'
platforms:
- name: 'Ubuntu'
versions: [ 'all' ]
- name: 'Debian'
versions: [ 'all' ]
galaxy_tags:
- ansible
- debops

View file

@ -0,0 +1,19 @@
debops.apache - Manage and configure the Apache HTTP Server
Copyright (C) 2016-2017 Robin Schneider <ypid@riseup.net>
Copyright (C) 2016-2017 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/.

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,26 @@
---
# Copyright (C) 2016-2017 Robin Schneider <ypid@riseup.net>
# Copyright (C) 2016-2017 DebOps <https://debops.org/>
# SPDX-License-Identifier: GPL-3.0-only
- name: Test apache and reload
ansible.builtin.command: apache2ctl configtest
register: apache__register_reload
changed_when: apache__register_reload.changed | bool
notify: [ 'Reload apache' ]
# - name: Test apache and restart
# ansible.builtin.command: apache2ctl configtest
# notify: [ 'Restart apache' ]
- name: Reload apache
ansible.builtin.service:
name: '{{ apache__service_name }}'
state: 'reloaded'
## Listed here for completeness but not used.
## Refer to ../docs/ansible-integration.rst
# - name: Restart apache
# ansible.builtin.service:
# name: '{{ apache__service_name }}'
# state: 'restarted'

View file

@ -0,0 +1,30 @@
---
# Copyright (C) 2016-2017 Robin Schneider <ypid@riseup.net>
# Copyright (C) 2016-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:
company: 'DebOps'
author: 'Robin Schneider'
description: 'Manage and configure the Apache HTTP Server'
license: 'GPL-3.0-only'
min_ansible_version: '2.3.0'
platforms:
- name: 'Ubuntu'
versions: [ 'all' ]
- name: 'Debian'
versions: [ 'all' ]
galaxy_tags:
- web
- webserver

View file

@ -0,0 +1,20 @@
---
# vim: foldmarker=[[[,]]]:foldmethod=marker
# Copyright (C) 2016-2017 Robin Schneider <ypid@riseup.net>
# Copyright (C) 2016-2017 DebOps <https://debops.org/>
# SPDX-License-Identifier: GPL-3.0-only
- name: Enable/disable Apache modules
debops.debops.apache2_module:
name: '{{ item.key }}'
state: '{{ (item.value.enabled
if (item.value is mapping)
else item.value) | bool | ternary("present", "absent") }}'
force: '{{ item.value.force | d(False) | bool }}'
notify: [ 'Test apache and reload' ]
when: (item.key in apache__tpl_available_modules
and item.value.enabled | d(True) != omit
and apache__deploy_state == "present")
with_dict: '{{ apache__combined_modules }}'
tags: [ 'role::apache:modules' ]

View file

@ -0,0 +1,170 @@
---
# .. vim: foldmarker=[[[,]]]:foldmethod=marker
# Copyright (C) 2016-2017 Robin Schneider <ypid@riseup.net>
# Copyright (C) 2016-2022 DebOps <https://debops.org/>
# SPDX-License-Identifier: GPL-3.0-only
- name: Import custom Ansible plugins
ansible.builtin.import_role:
name: 'ansible_plugins'
# Manage optional system packages [[[1
- name: Ensure optional packages are in there desired state
ansible.builtin.package:
name: '{{ q("flattened", (apache__packages
+ apache__group_packages
+ apache__host_packages
+ apache__dependent_packages)) }}'
state: '{{ "present" if (apache__deploy_state == "present") else "absent" }}'
register: apache__register_packages
until: apache__register_packages is succeeded
# Manage Apache modules [[[1
- name: Get list of available modules
ansible.builtin.find:
file_type: 'file'
paths: [ '{{ apache__config_path + "/mods-available/" }}' ]
patterns: [ '*.load' ]
register: apache__register_mods_available
tags: [ 'role::apache:modules' ]
- name: Set list of available modules
ansible.builtin.set_fact:
apache__tpl_available_modules: '{{ apache__register_mods_available.files | d({})
| map(attribute="path")
| map("replace", apache__config_path + "/mods-available/", "")
| map("regex_replace", "\.load$", "") | list }}'
tags: [ 'role::apache:modules' ]
- name: Configure Apache module state
ansible.builtin.include_tasks: apache_module_state.yml
# Manage Apache configuration snippets [[[1
- name: Divert conf-available configuration
debops.debops.dpkg_divert:
path: '{{ apache__config_path + "/conf-available/" + item.key + ".conf" }}'
divert: '{{ (item.value.divert
| d(apache__config_path + "/conf-available/"
+ (item.value.divert_filename | d(item.key)) + ".conf"))
+ item.value.divert_suffix | d(".dpkg-divert") }}'
when: (item.value.type | d("default") in ["divert"])
with_dict: '{{ apache__combined_snippets }}'
- name: Remove conf-available snippets
ansible.builtin.file:
path: '{{ apache__config_path + "/conf-available/" + item.key + ".conf" }}'
state: 'absent'
when: (item.value.state | d("present") == "absent")
with_dict: '{{ apache__combined_snippets }}'
tags: [ 'role::apache:vhosts' ]
- name: Create conf-available snippets
ansible.builtin.template:
src: 'etc/apache2/conf-available/{{ "raw"
if (item.value.type | d("default") in ["divert", "raw"] and
item.value.raw | d())
else item.key }}.conf.j2'
dest: '{{ apache__config_path + "/conf-available/" + item.key + ".conf" }}'
owner: 'root'
group: 'root'
mode: '0644'
when: (item.value.state | d("present") != "absent" and
(item.value.type | d("default") not in ["divert", "dont-create"] or item.value.raw | d()))
with_dict: '{{ apache__combined_snippets }}'
notify: [ 'Test apache and reload' ]
- name: Enable/disable configuration snippets
ansible.builtin.file:
path: '{{ apache__config_path + "/conf-enabled/" + item.key + ".conf" }}'
src: '{{ (((item.value.enabled | d(True)
if (item.value is mapping)
else item.value | d(True)))
if (item.value.state | d("present") != "absent")
else False) | bool
| ternary("../conf-available/" + item.key + ".conf",
omit) }}'
mode: '0644'
force: '{{ ansible_check_mode | d() | bool }}'
state: '{{ (((item.value.enabled | d(True)
if (item.value is mapping)
else item.value | d(True)))
if (item.value.state | d("present") != "absent")
else False) | bool | ternary("link", "absent") }}'
when: (item.value.type | d("default") not in ["divert"])
with_dict: '{{ apache__combined_snippets }}'
notify: [ 'Test apache and reload' ]
# Manage Apache virtual hosts [[[1
- name: Divert sites-available configuration
debops.debops.dpkg_divert:
path: '{{ apache__config_path + "/sites-available/"
+ item.filename | d([item.name] | flatten | first | d("default"))
+ ".conf" }}'
divert: '{{ (item.divert
| d(apache__config_path + "/sites-available/"
+ item.divert_filename | d(item.filename | d([item.name] | flatten | first | d("default")))
+ ".conf")
+ item.divert_suffix | d(".dpkg-divert")) }}'
when: (item.type | d(apache__vhost_type) in ["divert"])
loop: '{{ q("flattened", apache__combined_vhosts) }}'
- name: Remove sites-available configuration
ansible.builtin.file:
path: '{{ apache__config_path }}/sites-available/{{ item.filename | d(item.name
if (item.name is string)
else item.name[0] | d("default")) }}.conf'
state: 'absent'
when: (item.state | d("present") == 'absent')
loop: '{{ q("flattened", apache__combined_vhosts) }}'
tags: [ 'role::apache:vhosts' ]
- name: Create sites-available configuration
ansible.builtin.template:
src: 'etc/apache2/sites-available/{{ item.type | d(apache__vhost_type) }}.conf.j2'
dest: '{{ apache__config_path }}/sites-available/{{ item.filename | d(item.name
if (item.name is string)
else item.name[0] | d("default")) }}.conf'
owner: 'root'
group: 'root'
mode: '0644'
notify: [ 'Test apache and reload' ]
when: (item.state | d("present") != "absent" and item.type | d(apache__vhost_type) not in ["divert", "dont-create"])
loop: '{{ q("flattened", apache__combined_vhosts) }}'
tags: [ 'role::apache:vhosts' ]
- name: Enable/disable Apache virtual hosts
ansible.builtin.file:
path: '{{ apache__config_path }}/sites-enabled/{{ item.filename | d(item.name
if (item.name is string)
else item.name[0] | d("default")) }}.conf'
src: '{{ ("../sites-available/" + item.filename
| d(item.name
if (item.name is string)
else item.name[0] | d("default")) + ".conf")
if (item.enabled | d(True) | bool and (item.state | d("present") != "absent"))
else omit }}'
force: '{{ item.force | d(ansible_check_mode) | bool }}'
state: '{{ item.enabled | d(True) | bool | ternary("link", "absent")
if (item.state | d("present") != "absent")
else "absent" }}'
owner: 'root'
group: 'root'
mode: '0644'
notify: [ 'Test apache and reload' ]
when: (item.type | d(apache__vhost_type) not in ["divert"])
loop: '{{ q("flattened", apache__combined_vhosts) }}'
tags: [ 'role::apache:vhosts' ]
# Manage Apache modules, part 2 [[[1
- name: Detect if the rewrite module has been used in the active configuration
ansible.builtin.shell: grep --recursive --ignore-case '^\s*RewriteEngine On' {{ apache__config_path | quote }}
register: apache__register_mod_rewrite_used
check_mode: False
failed_when: apache__register_mod_rewrite_used.rc not in [0, 1]
changed_when: False
when: apache__register_mod_rewrite_used is undefined
- name: Finish configuration of Apache module state
ansible.builtin.include_tasks: apache_module_state.yml

View file

@ -0,0 +1,41 @@
---
# .. vim: foldmarker=[[[,]]]:foldmethod=marker
# Copyright (C) 2016-2017 Robin Schneider <ypid@riseup.net>
# Copyright (C) 2016-2017 DebOps <https://debops.org/>
# SPDX-License-Identifier: GPL-3.0-only
- name: Import DebOps global handlers
ansible.builtin.import_role:
name: 'global_handlers'
# Manage required system packages [[[1
- name: Ensure base packages are in there desired state
ansible.builtin.package:
name: '{{ item }}'
state: '{{ "present" if (apache__deploy_state == "present") else "absent" }}'
loop: '{{ q("flattened", apache__base_packages) }}'
register: apache__register_base_packages
until: apache__register_base_packages is succeeded
tags: [ 'role::apache:pkgs' ]
# Ansible facts [[[1
- name: Make sure Ansible fact directory exists
ansible.builtin.file:
path: '/etc/ansible/facts.d'
state: 'directory'
owner: 'root'
group: 'root'
mode: '0755'
- name: Create local facts of Apache
ansible.builtin.template:
src: 'etc/ansible/facts.d/apache.fact.j2'
dest: '/etc/ansible/facts.d/apache.fact'
owner: 'root'
group: 'root'
mode: '0755'
notify: [ 'Refresh host facts' ]
- name: Reload facts if they were modified
ansible.builtin.meta: 'flush_handlers'

View file

@ -0,0 +1,39 @@
#!{{ ansible_python['executable'] }}
# -*- coding: utf-8 -*-
# Copyright (C) 2016-2017 Robin Schneider <ypid@riseup.net>
# Copyright (C) 2016-2017 DebOps <https://debops.org/>
# SPDX-License-Identifier: GPL-3.0-only
# {{ ansible_managed }}
from __future__ import print_function
from json import loads, dumps
import subprocess
import re
output = loads('''{{ ({
"enabled": (apache__deploy_state == "present"),
"user": apache__user,
"use_if_version": apache__config_use_if_version,
"min_version": apache__config_min_version,
}) | to_nice_json }}''')
try:
apache_v = subprocess.check_output(
['apache2', '-v']).decode('utf-8').split('\n')
for line in apache_v:
_re = re.match(r'Server version: Apache/(?P<version>[^ ]+)', line)
if _re:
output['version'] = _re.group('version')
if output['use_if_version']:
if output['min_version'] == 'current_major_minor':
output['min_version'] = '.'.join(
output['version'].split('.')[:2])
except Exception:
pass
print(dumps(output, sort_keys=True, indent=4))

View file

@ -0,0 +1,362 @@
{# Copyright (C) 2016-2017 Robin Schneider <ypid@riseup.net>
# Copyright (C) 2016-2017 DebOps <https://debops.org/>
# SPDX-License-Identifier: GPL-3.0-only
#}
# {{ ansible_managed }}
# vim: foldmarker=[[[,]]]:foldmethod=marker
{% macro get_yaml_list_for_elem(list_or_elem) %}{# [[[ #}
{{ ([ list_or_elem ]
if (list_or_elem is string or list_or_elem in [True, False])
else (list_or_elem | list)) | to_nice_yaml }}
{% endmacro %}
{% macro get_realm_yaml_list(domains, fallback_realm) %}{# [[[ #}
{% set custom_realm_list = [] %}
{% if domains and (ansible_local.pki.known_realms | d()) %}
{% for domain in (get_yaml_list_for_elem(domains) | from_yaml) %}
{% if domain in ansible_local.pki.known_realms %}
{% set _ = custom_realm_list.append(domain) %}
{% elif (domain.split('.')[1:] | join('.')) in ansible_local.pki.known_realms %}
{% set _ = custom_realm_list.append(domain.split('.')[1:] | join('.')) %}
{% endif %}
{% endfor %}
{% endif %}
{% if custom_realm_list | length == 0 %}
{% set _ = custom_realm_list.append(fallback_realm) %}
{% endif %}
{{ custom_realm_list | to_nice_yaml }}
{% endmacro %}
{% macro get_apache_version() %}{# [[[ #}
{{ ansible_local.apache.version | d("2.4.0") -}}
{% endmacro %}
{% macro get_apache_min_version() %}{# [[[ #}
{{ ansible_local.apache.min_version | d("2.4.0") -}}
{% endmacro %}
{% macro get_openssl_version() %}{# [[[ #}
{{ ansible_local.pki.openssl_version | d("1.0.2") }}
{% endmacro %}
{% macro get_gnutls_version() %}{# [[[ #}
{{ ansible_local.pki.gnutls_version | d("0.0.0") }}
{% endmacro %}
{% macro debops_indent(content, width=4, indentfirst=False) %}{# [[[ #}
{# Fixed version of the `indent` filter which does not insert trailing spaces on empty lines.
## Note that you can not use this macro like a filter but have to use it like a regular macro.
## Example: {{ indent(some_content, 4) }}
##
## Python re.sub seems to default to re.MULTILINE in Ansible.
#}
{{ content | indent(width, indentfirst) | regex_replace("[ \\t\\r\\f\\v]+(\\n|$)", "\\1") -}}
{% endmacro %}
{% macro get_min_version_wrapped(content, version, compare_operator) %}{# [[[ #}
{# Examples:
{{ get_min_version_wrapped('# 2.3.0 directive', "2.3", ">=") }}
{{ get_min_version_wrapped('# 2.4.0 directive', "2.4", ">=") }}
{{ get_min_version_wrapped('# 2.4.1 directive', "2.4.1", ">=") }}
{{ get_min_version_wrapped('# 2.5.0 directive', "2.5", ">=") }}
#}
{% set version = '==' if (version == '=') else version %}
{% set apache__tpl_compare_operator_includes_equal = True if (compare_operator in ['>=', '<=', '==']) else False %}
{% if (version is version_compare(get_apache_min_version(), compare_operator))
or (version is version_compare(get_apache_min_version(), ">")) %}
{% if apache__tpl_compare_operator_includes_equal and (version is version_compare(get_apache_min_version(), "==")) %}
{{ content -}}
{% else %}
<IfVersion {{ compare_operator }} {{ version }}>
{{ debops_indent(content, 4) }}
</IfVersion>
{% endif %}
{% endif %}
{% endmacro %}
{% macro get_version_wrapped(content, version, compare_operator, fallback_content=False) %}{# [[[ #}
{# This macro has not been extensively tested yet. If it contains bugs please try to debug it if you need it.
Examples:
{{ get_version_wrapped('# 2.3.0 directive', "2.3", ">=", '# Prior to 2.3 directive') }}
{{ get_version_wrapped('# 2.4.0 directive', "2.4", ">=", '# Prior to 2.4 directive') }}
{{ get_version_wrapped('# 2.4.1 directive', "2.4.1", ">=", '# Prior to 2.4.1 directive') }}
{{ get_version_wrapped('# 2.5.0 directive', "2.5", ">=", '# Prior to 2.5 directive') }}
#}
{% set version = '==' if (version == '=') else version %}
{% set apache__tpl_xor_compare_operator_map = {} %}
{% set apache__tpl_xor_compare_operator_list = [
('==', '!='),
('>=', '<'),
('<=', '>'),
] %}
{% for compare_operator, xor_compare_operator in apache__tpl_xor_compare_operator_list %}
{% set _ = apache__tpl_xor_compare_operator_map.update({ compare_operator: xor_compare_operator }) %}
{% set _ = apache__tpl_xor_compare_operator_map.update({ xor_compare_operator: compare_operator }) %}
{% endfor %}
{% if apache__config_use_if_version | bool %}
{{ get_min_version_wrapped(content, version, compare_operator) -}}
{% if fallback_content != False %}
{{ get_min_version_wrapped(fallback_content, version, apache__tpl_xor_compare_operator_map[compare_operator]) -}}
{% endif %}
{% else %}
{% if get_apache_version() is version_compare(version, compare_operator) %}
{{ content -}}
{% elif fallback_content != False %}
{{ fallback_content -}}
{% endif %}
{% endif %}
{% endmacro %}
{% macro get_server_name_aliases(name_item) %}{# [[[ #}
{% set apache__tpl_names = get_yaml_list_for_elem(name_item) | from_yaml %}
{% if not apache__tpl_names %}
{% set apache__tpl_names = [ apache__server_name ] %}
{% endif %}
ServerName {{ apache__tpl_names[0] | quote }}
{% if apache__tpl_names | length > 1 %}
ServerAlias {{ apache__tpl_names[1:] | map("quote") | join(" ") }}
{% endif %}
{% endmacro %}
{% macro get_listen_sockets(item_listen_list, server_port_listen_list) %}{# [[[ #}
{% set apache__tpl_listen_sockets = [] %}
{% if item_listen_list | length == 0 %}
{% set item_listen_list = server_port_listen_list %}
{% endif %}
{% for item_listen in item_listen_list %}
{% if ':' not in item_listen | string %}
{# {% if item_listen in server_port_listen_list %}
## Apparently, this is not even needed for Apache to (re)start without issues. #}
{% set _ = apache__tpl_listen_sockets.append("*:" + item_listen | string) %}
{% else %}
{% set _ = apache__tpl_listen_sockets.append(item_listen | string) %}
{% endif %}
{% endfor %}
{{ apache__tpl_listen_sockets | join(" ") -}}
{% endmacro %}
{% macro get_header_comments(item) %}{# [[[ #}
{% if item.by_role | d() %}
# Generated by Ansible role: {{ item.by_role }}
{% endif %}
{% endmacro %}
{% macro get_server_directives(item) %}{# [[[ #}
{% set sanitized_name = (get_yaml_list_for_elem(item.filename | d(item.name)) | from_yaml)[0] | replace(".", "_") %}
{% if item.name | d([]) %}
{{ get_server_name_aliases(item.name | d([])) }}
{% endif %}
ServerAdmin {{ item.server_admin | d(apache__server_admin) | quote }}
CustomLog {{ item.custom_log | d('${APACHE_LOG_DIR}/' + sanitized_name + '_access.log' + ' ' + apache__access_log_format + ' ' + item.custom_log_condition | d()) }}
ErrorLog {{ item.error_log | d('${APACHE_LOG_DIR}/' + sanitized_name + '_error.log') }}
{% endmacro %}
{% macro get_server_status_directives(item, enabled) %}{# [[[ #}
{% if enabled | bool %}
<Location {{ item.status_location | d(apache__status_location) | quote }}>
SetHandler server-status
{% if item.status_allow_localhost | d(apache__status_allow_localhost) | bool %}
Require local
{% endif %}
{% if item.status_directives | d(apache__status_directives) %}
{{ item.status_directives | d(apache__status_directives) }}
{% elif not (item.status_allow_localhost | d(apache__status_allow_localhost) | bool) %}
Require all denied
{% endif %}
</Location>
{% endif %}
{% endmacro %}
{% macro get_vhost_content_directives(item, mode='https', https_enabled=True) %}{# [[[ #}
{% set apache__tpl_use_redirect_module = 'alias' %}
{% set apache__tpl_status_enabled = item.status_enabled | d(apache__status_for_vhost_enabled) | bool %}
{{ get_server_status_directives(item, apache__tpl_status_enabled) -}}
{% if apache__tpl_status_enabled | bool and (
(mode == 'http' and (item.redirect_http | d() or item.redirect_to_https | d(apache__redirect_to_https) | bool)) or
(mode == 'https' and item.redirect_https | d())
) %}
<IfModule mod_rewrite>
RewriteEngine On
RewriteRule "^{{ item.status_location | d(apache__status_location) }}" "-" [L]
</IfModule>
{% set apache__tpl_use_redirect_module = 'rewrite' %}
{% endif %}
{% if mode == 'http' and item.redirect_http | d() %}
{{ get_redirect(item.redirect_http_code | d(307), "/", item.redirect_http, apache__tpl_use_redirect_module) }}
{% elif mode == 'http' and item.redirect_to_https | d(apache__redirect_to_https) | bool %}
{{ get_redirect(item.redirect_to_https_with_code | d("301"), "/", "https://" + (get_yaml_list_for_elem(item.name) | from_yaml)[0], apache__tpl_use_redirect_module) }}
{% elif mode == 'https' and item.redirect_https | d() %}
{{ get_redirect(item.redirect_https_code | d(307), "/", item.redirect_https, apache__tpl_use_redirect_module) }}
{% else %}
{{ get_content_directives(item) }}
{% endif %}
{% endmacro %}
{% macro get_document_root_directives(item) %}{# [[[ #}
Options {{ get_yaml_list_for_elem(item.options | d(apache__vhost_options)) | from_yaml | join(" ") }}
AllowOverride {{ get_yaml_list_for_elem(item.allow_override | d(apache__vhost_allow_override)) | from_yaml | join(" ") }}
{{ get_version_wrapped('Require all granted', "2.4", ">=",
'Order allow,deny
Allow from all') -}}
{% if item.root_directives | d() %}
{{ item.root_directives }}
{% endif %}
{% endmacro %}
{% macro get_content_directives(item) %}{# [[[ #}
{% if item.include | d() %}
{% for include_file in get_yaml_list_for_elem(item.include) | from_yaml %}
Include {{ include_file | quote }}
{% endfor %}
{% endif %}
{% if item.include_optional | d() %}
{% for include_optional_file in get_yaml_list_for_elem(item.include_optional) | from_yaml %}
IncludeOptional {{ include_optional_file | quote }}
{% endfor %}
{% endif %}
{% if item.root | d(item.document_root | d()) %}
DocumentRoot {{ item.root | d(item.document_root) | quote }}
{% if item.index | d() %}
DirectoryIndex {{ get_yaml_list_for_elem(item.index) | from_yaml | join(" ") }}
{% endif %}
{% if item.alias | d() %}
{{ get_alias(item.alias, item.alias_path | d(item.root) | d(item.document_root)) }}
{%- endif %}
<Directory {{ item.root | d(item.document_root) | quote }}>
{{ debops_indent(get_document_root_directives(item), 4) }}
</Directory>
{% endif %}
{% if item.raw_content | d() %}
{{ item.raw_content }}
{% endif %}
{% endmacro %}
{% macro get_default_tls_directives(item) %}{# [[[ #}
{# Included in server context to provide sane defaults (there might be vhosts
# not controlled by this template) and in virtual host context to ensure those
# settings are appliend and to allow per-vhost changes.
# Refer to Applied-Crypto-Hardening_bettercrypto/src/configuration/Webservers/Apache/default-ssl for details.
#}
<FilesMatch "\.(cgi|shtml|phtml|php)$">
SSLOptions +StdEnvVars
</FilesMatch>
<Directory /usr/lib/cgi-bin>
SSLOptions +StdEnvVars
</Directory>
BrowserMatch "MSIE [2-6]" \
nokeepalive ssl-unclean-shutdown \
downgrade-1.0 force-response-1.0
# MSIE 7 and newer should be able to use keepalive
BrowserMatch "MSIE [17-9]" ssl-unclean-shutdown
# TLS cipher suites set: {{ item.tls_cipher_suite_set_name | d(apache__tls_cipher_suite_set_name) | quote }}
SSLCipherSuite {{ apache__tls_cipher_suite_sets[item.tls_cipher_suite_set_name | d(apache__tls_cipher_suite_set_name)] | quote }}
SSLProtocol {{ item.tls_protocols | d(apache__tls_protocols) | join(" ") }}
SSLHonorCipherOrder {{ item.tls_honor_cipher_order | d(apache__tls_honor_cipher_order) | quote }}
SSLCompression {{ item.tls_compression | d(apache__tls_compression) | quote }}
{#
# https://raymii.org/s/tutorials/Strong_SSL_Security_On_Apache2.html
# https://github.com/sovereign/sovereign/issues/373
#}
{% if get_apache_version() is version_compare("2.4.8", ">=") and get_openssl_version() is version_compare("1.0.2", ">=") %}
# SSLOpenSSLConfCmd DHParameters {{ item.tls_dhparam_file | d(apache__tls_dhparam_file) | quote }}
{% endif %}
{% endmacro %}
{% macro get_https_directives(item) %}{# [[[ #}
SSLEngine on
{{ get_default_tls_directives(item) }}
{% set apache__tpl_pki_realm = item.pki_realm | d((get_realm_yaml_list(item.name, apache__pki_realm) | from_yaml)[0]) %}
{% set apache__tpl_pki_realm_path = apache__pki_realm_path + "/" + apache__tpl_pki_realm %}
SSLCertificateFile {{ item.tls_crt | d(apache__tpl_pki_realm_path + "/" + (item.pki_crt | d(apache__pki_crt_filename))) | quote }}
SSLCertificateKeyFile {{ item.tls_key | d(apache__tpl_pki_realm_path + "/" + (item.pki_key | d(apache__pki_key_filename))) | quote }}
{% if item.ocsp_stapling_enabled | d(apache__ocsp_stapling_enabled) | bool and get_openssl_version() is version_compare("0.9.8h", ">=") %}
SSLUseStapling on
{% endif %}
{% if item.hsts_enabled | d(apache__hsts_enabled) | bool %}
Header always set Strict-Transport-Security "max-age={{ item.hsts_max_age | d(apache__hsts_max_age) }}{{ "; includeSubDomains" if item.hsts_subdomains | d(apache__hsts_subdomains) | bool else "" }}{{ "; preload" if ((item.hsts_preload | d(apache__hsts_preload)) | bool) else "" }}"
{% endif %}
{% endmacro %}
{% macro get_http_security_headers(item) %}{# [[[ #}
{% set apache__tpl_directive_options = get_yaml_list_for_elem(item.http_sec_headers_directive_options | d(apache__http_sec_headers_directive_options)) | from_yaml | map("quote") | join(" ") %}
{% if item.csp_enabled | d(False) | bool %}
Header {{ apache__tpl_directive_options }} Content-Security-Policy "{{ item.csp | d("default-src https: ;") + (" " + item.csp_append | d(apache__http_csp_append) if (item.csp_append | d(apache__http_csp_append)) else "") }}"
{% endif %}
{% if item.csp_report_enabled | d(False) | bool %}
Header {{ apache__tpl_directive_options }} Content-Security-Policy-Report-Only "{{ item.csp_report | d(item.csp | d("default-src https: ;")) + (" " + item.csp_append | d(apache__http_csp_append) if (item.csp_append | d(apache__http_csp_append)) else "") }}"
{% endif %}
{% if item.http_frame_options | d(apache__http_frame_options) != omit %}
Header {{ apache__tpl_directive_options }} X-Frame-Options "{{ item.http_frame_options | d(apache__http_frame_options) }}"
{% endif %}
{% if item.http_xss_protection | d(apache__http_xss_protection) != omit %}
Header {{ apache__tpl_directive_options }} X-XSS-Protection "{{ item.http_xss_protection | d(apache__http_xss_protection) }}"
{% endif %}
{% if item.http_referrer_policy | d(apache__http_referrer_policy) != omit %}
Header {{ apache__tpl_directive_options }} Referrer-Policy "{{ item.http_referrer_policy | d(apache__http_referrer_policy) }}"
{% endif %}
{% if item.http_content_type_options | d(apache__http_content_type_options) != omit %}
Header {{ apache__tpl_directive_options }} X-Content-Type-Options "{{ item.http_content_type_options | d(apache__http_content_type_options) }}"
{% endif %}
{% endmacro %}
{% macro get_common_headers(item) %}{# [[[ #}
{% if item.http_clacks_overhead | d(apache__http_clacks_overhead | d(True)) | bool %}
{#
# Respect the will of the DebOps Creator.
# Ref: https://github.com/debops/ansible-nginx/commit/d6cd455c68a7584b2592053fd98d3e539054e09a
#}
Header always set X-Clacks-Overhead "GNU Terry Pratchett"
{% endif %}
{% endmacro %}
{% macro get_redirect(code, from, to, module) %}{# [[[ #}
{# Prefer the alias module but support the use of the rewrite module in case
other rewrite rules are used in the same context because the rewrite rules are
handled before all the directives from the alias module.
#}
{% set to = to + ("/" if (to[-1] != "/") else "") %}
{% if module == 'alias' %}
Redirect {{ code | string }} "{{ from }}" "{{ to }}"
{% elif module == 'rewrite' %}
<IfModule mod_rewrite>
RewriteRule "^{{ from }}?(.*)" "{{ to }}$1" [L,R={{ code }},NE]
</IfModule>
{% endif %}
{% endmacro %}
{% macro get_alias(url_path, fs_directory) %}{# [[[ #}
Alias {{ url_path | quote }} {{ (fs_directory + ("/" if (fs_directory[-1] != "/") else "")) | quote }}
{% endmacro %}
{#
# It seems that Apache does not allow to overwrite directives in the same context (server config):
# Examples: LogLevel
# It is probably not good practice to change that in server context anyway.
#}
MaxConnectionsPerChild {{ apache__mpm_max_connections_per_child | quote }}
ExtendedStatus {{ apache__status_extended_enabled | bool | ternary("On", "Off") }}
{% for module_name, value in apache__combined_modules|dictsort %}
{% if value is mapping and 'config' in value %}
<IfModule {{ module_name }}_module>
{{ value.config | indent(4) }}
</IfModule>
{% endif %}
{% endfor %}
ServerTokens {{ apache__server_tokens }}
ServerSignature {{ apache__server_signature }}
TraceEnable {{ apache__trace_enabled }}
LogLevel {{ apache__log_level }}
{{ get_common_headers(item) | indent(4) }}
{# Dont use apache__combined_modules because of heavy use of template macros. #}
<IfModule ssl_module>
{{ get_default_tls_directives(item.value | d({})) }}
{% if get_openssl_version() is version_compare("0.9.8h", ">=") %}
SSLStaplingCache {{ apache__ocsp_stapling_cache | quote }}
SSLStaplingResponseMaxAge {{ apache__ocsp_stapling_response_max_age | string }}
{% if apache__ocsp_stapling_force_url %}
SSLStaplingForceURL {{ apache__ocsp_stapling_force_url | quote }}
{% endif %}
{% endif %}
</IfModule>

View file

@ -0,0 +1,9 @@
{# Copyright (C) 2016-2017 Robin Schneider <ypid@riseup.net>
# Copyright (C) 2016-2017 DebOps <https://debops.org/>
# SPDX-License-Identifier: GPL-3.0-only
#}
# {{ ansible_managed }}
{% if apache__security_module_server_signature != omit %}
SecServerSignature "{{ apache__security_module_server_signature }}"
{% endif %}

View file

@ -0,0 +1,9 @@
{# Copyright (C) 2016-2017 Robin Schneider <ypid@riseup.net>
# Copyright (C) 2016-2017 DebOps <https://debops.org/>
# SPDX-License-Identifier: GPL-3.0-only
#}
# {{ ansible_managed }}
{% if item.value.raw | d() %}
{{ item.value.raw }}
{%- endif %}

View file

@ -0,0 +1,355 @@
{# Copyright (C) 2016-2017 Robin Schneider <ypid@riseup.net>
# Copyright (C) 2016-2017 DebOps <https://debops.org/>
# SPDX-License-Identifier: GPL-3.0-only
#}
# {{ ansible_managed }}
# vim: foldmarker=[[[,]]]:foldmethod=marker
{% macro get_yaml_list_for_elem(list_or_elem) %}{# [[[ #}
{{ ([ list_or_elem ]
if (list_or_elem is string or list_or_elem in [True, False])
else (list_or_elem | list)) | to_nice_yaml }}
{% endmacro %}
{% macro get_realm_yaml_list(domains, fallback_realm) %}{# [[[ #}
{% set custom_realm_list = [] %}
{% if domains and (ansible_local.pki.known_realms | d()) %}
{% for domain in (get_yaml_list_for_elem(domains) | from_yaml) %}
{% if domain in ansible_local.pki.known_realms %}
{% set _ = custom_realm_list.append(domain) %}
{% elif (domain.split('.')[1:] | join('.')) in ansible_local.pki.known_realms %}
{% set _ = custom_realm_list.append(domain.split('.')[1:] | join('.')) %}
{% endif %}
{% endfor %}
{% endif %}
{% if custom_realm_list | length == 0 %}
{% set _ = custom_realm_list.append(fallback_realm) %}
{% endif %}
{{ custom_realm_list | to_nice_yaml }}
{% endmacro %}
{% macro get_apache_version() %}{# [[[ #}
{{ ansible_local.apache.version | d("2.4.0") -}}
{% endmacro %}
{% macro get_apache_min_version() %}{# [[[ #}
{{ ansible_local.apache.min_version | d("2.4.0") -}}
{% endmacro %}
{% macro get_openssl_version() %}{# [[[ #}
{{ ansible_local.pki.openssl_version | d("1.0.2") }}
{% endmacro %}
{% macro get_gnutls_version() %}{# [[[ #}
{{ ansible_local.pki.gnutls_version | d("0.0.0") }}
{% endmacro %}
{% macro debops_indent(content, width=4, indentfirst=False) %}{# [[[ #}
{# Fixed version of the `indent` filter which does not insert trailing spaces on empty lines.
## Note that you can not use this macro like a filter but have to use it like a regular macro.
## Example: {{ indent(some_content, 4) }}
##
## Python re.sub seems to default to re.MULTILINE in Ansible.
#}
{{ content | indent(width, indentfirst) | regex_replace("[ \\t\\r\\f\\v]+(\\n|$)", "\\1") -}}
{% endmacro %}
{% macro get_min_version_wrapped(content, version, compare_operator) %}{# [[[ #}
{# Examples:
{{ get_min_version_wrapped('# 2.3.0 directive', "2.3", ">=") }}
{{ get_min_version_wrapped('# 2.4.0 directive', "2.4", ">=") }}
{{ get_min_version_wrapped('# 2.4.1 directive', "2.4.1", ">=") }}
{{ get_min_version_wrapped('# 2.5.0 directive', "2.5", ">=") }}
#}
{% set version = '==' if (version == '=') else version %}
{% set apache__tpl_compare_operator_includes_equal = True if (compare_operator in ['>=', '<=', '==']) else False %}
{% if (version is version_compare(get_apache_min_version(), compare_operator))
or (version is version_compare(get_apache_min_version(), ">")) %}
{% if apache__tpl_compare_operator_includes_equal and (version is version_compare(get_apache_min_version(), "==")) %}
{{ content -}}
{% else %}
<IfVersion {{ compare_operator }} {{ version }}>
{{ debops_indent(content, 4) }}
</IfVersion>
{% endif %}
{% endif %}
{% endmacro %}
{% macro get_version_wrapped(content, version, compare_operator, fallback_content=False) %}{# [[[ #}
{# This macro has not been extensively tested yet. If it contains bugs please try to debug it if you need it.
Examples:
{{ get_version_wrapped('# 2.3.0 directive', "2.3", ">=", '# Prior to 2.3 directive') }}
{{ get_version_wrapped('# 2.4.0 directive', "2.4", ">=", '# Prior to 2.4 directive') }}
{{ get_version_wrapped('# 2.4.1 directive', "2.4.1", ">=", '# Prior to 2.4.1 directive') }}
{{ get_version_wrapped('# 2.5.0 directive', "2.5", ">=", '# Prior to 2.5 directive') }}
#}
{% set version = '==' if (version == '=') else version %}
{% set apache__tpl_xor_compare_operator_map = {} %}
{% set apache__tpl_xor_compare_operator_list = [
('==', '!='),
('>=', '<'),
('<=', '>'),
] %}
{% for compare_operator, xor_compare_operator in apache__tpl_xor_compare_operator_list %}
{% set _ = apache__tpl_xor_compare_operator_map.update({ compare_operator: xor_compare_operator }) %}
{% set _ = apache__tpl_xor_compare_operator_map.update({ xor_compare_operator: compare_operator }) %}
{% endfor %}
{% if apache__config_use_if_version | bool %}
{{ get_min_version_wrapped(content, version, compare_operator) -}}
{% if fallback_content != False %}
{{ get_min_version_wrapped(fallback_content, version, apache__tpl_xor_compare_operator_map[compare_operator]) -}}
{% endif %}
{% else %}
{% if get_apache_version() is version_compare(version, compare_operator) %}
{{ content -}}
{% elif fallback_content != False %}
{{ fallback_content -}}
{% endif %}
{% endif %}
{% endmacro %}
{% macro get_server_name_aliases(name_item) %}{# [[[ #}
{% set apache__tpl_names = get_yaml_list_for_elem(name_item) | from_yaml %}
{% if not apache__tpl_names %}
{% set apache__tpl_names = [ apache__server_name ] %}
{% endif %}
ServerName {{ apache__tpl_names[0] | quote }}
{% if apache__tpl_names | length > 1 %}
ServerAlias {{ apache__tpl_names[1:] | map("quote") | join(" ") }}
{% endif %}
{% endmacro %}
{% macro get_listen_sockets(item_listen_list, server_port_listen_list) %}{# [[[ #}
{% set apache__tpl_listen_sockets = [] %}
{% if item_listen_list | length == 0 %}
{% set item_listen_list = server_port_listen_list %}
{% endif %}
{% for item_listen in item_listen_list %}
{% if ':' not in item_listen | string %}
{# {% if item_listen in server_port_listen_list %}
## Apparently, this is not even needed for Apache to (re)start without issues. #}
{% set _ = apache__tpl_listen_sockets.append("*:" + item_listen | string) %}
{% else %}
{% set _ = apache__tpl_listen_sockets.append(item_listen | string) %}
{% endif %}
{% endfor %}
{{ apache__tpl_listen_sockets | join(" ") -}}
{% endmacro %}
{% macro get_header_comments(item) %}{# [[[ #}
{% if item.by_role | d() %}
# Generated by Ansible role: {{ item.by_role }}
{% endif %}
{% endmacro %}
{% macro get_server_directives(item) %}{# [[[ #}
{% set sanitized_name = (get_yaml_list_for_elem(item.filename | d(item.name)) | from_yaml)[0] | replace(".", "_") %}
{% if item.name | d([]) %}
{{ get_server_name_aliases(item.name | d([])) }}
{% endif %}
ServerAdmin {{ item.server_admin | d(apache__server_admin) | quote }}
CustomLog {{ item.custom_log | d('${APACHE_LOG_DIR}/' + sanitized_name + '_access.log' + ' ' + apache__access_log_format + ' ' + item.custom_log_condition | d()) }}
ErrorLog {{ item.error_log | d('${APACHE_LOG_DIR}/' + sanitized_name + '_error.log') }}
{% endmacro %}
{% macro get_server_status_directives(item, enabled) %}{# [[[ #}
{% if enabled | bool %}
<Location {{ item.status_location | d(apache__status_location) | quote }}>
SetHandler server-status
{% if item.status_allow_localhost | d(apache__status_allow_localhost) | bool %}
Require local
{% endif %}
{% if item.status_directives | d(apache__status_directives) %}
{{ item.status_directives | d(apache__status_directives) }}
{% elif not (item.status_allow_localhost | d(apache__status_allow_localhost) | bool) %}
Require all denied
{% endif %}
</Location>
{% endif %}
{% endmacro %}
{% macro get_vhost_content_directives(item, mode='https', https_enabled=True) %}{# [[[ #}
{% set apache__tpl_use_redirect_module = 'alias' %}
{% set apache__tpl_status_enabled = item.status_enabled | d(apache__status_for_vhost_enabled) | bool %}
{{ get_server_status_directives(item, apache__tpl_status_enabled) -}}
{% if apache__tpl_status_enabled | bool and (
(mode == 'http' and (item.redirect_http | d() or item.redirect_to_https | d(apache__redirect_to_https) | bool)) or
(mode == 'https' and item.redirect_https | d())
) %}
<IfModule mod_rewrite>
RewriteEngine On
RewriteRule "^{{ item.status_location | d(apache__status_location) }}" "-" [L]
</IfModule>
{% set apache__tpl_use_redirect_module = 'rewrite' %}
{% endif %}
{% if mode == 'http' and item.redirect_http | d() %}
{{ get_redirect(item.redirect_http_code | d(307), "/", item.redirect_http, apache__tpl_use_redirect_module) }}
{% elif mode == 'http' and item.redirect_to_https | d(apache__redirect_to_https) | bool %}
{{ get_redirect(item.redirect_to_https_with_code | d("301"), "/", "https://" + (get_yaml_list_for_elem(item.name) | from_yaml)[0], apache__tpl_use_redirect_module) }}
{% elif mode == 'https' and item.redirect_https | d() %}
{{ get_redirect(item.redirect_https_code | d(307), "/", item.redirect_https, apache__tpl_use_redirect_module) }}
{% else %}
{{ get_content_directives(item) }}
{% endif %}
{% endmacro %}
{% macro get_document_root_directives(item) %}{# [[[ #}
Options {{ get_yaml_list_for_elem(item.options | d(apache__vhost_options)) | from_yaml | join(" ") }}
AllowOverride {{ get_yaml_list_for_elem(item.allow_override | d(apache__vhost_allow_override)) | from_yaml | join(" ") }}
{{ get_version_wrapped('Require all granted', "2.4", ">=",
'Order allow,deny
Allow from all') -}}
{% if item.root_directives | d() %}
{{ item.root_directives }}
{% endif %}
{% endmacro %}
{% macro get_content_directives(item) %}{# [[[ #}
{% if item.include | d() %}
{% for include_file in get_yaml_list_for_elem(item.include) | from_yaml %}
Include {{ include_file | quote }}
{% endfor %}
{% endif %}
{% if item.include_optional | d() %}
{% for include_optional_file in get_yaml_list_for_elem(item.include_optional) | from_yaml %}
IncludeOptional {{ include_optional_file | quote }}
{% endfor %}
{% endif %}
{% if item.root | d(item.document_root | d()) %}
DocumentRoot {{ item.root | d(item.document_root) | quote }}
{% if item.index | d() %}
DirectoryIndex {{ get_yaml_list_for_elem(item.index) | from_yaml | join(" ") }}
{% endif %}
{% if item.alias | d() %}
{{ get_alias(item.alias, item.alias_path | d(item.root) | d(item.document_root)) }}
{%- endif %}
<Directory {{ item.root | d(item.document_root) | quote }}>
{{ debops_indent(get_document_root_directives(item), 4) }}
</Directory>
{% endif %}
{% if item.raw_content | d() %}
{{ item.raw_content }}
{% endif %}
{% endmacro %}
{% macro get_default_tls_directives(item) %}{# [[[ #}
{# Included in server context to provide sane defaults (there might be vhosts
# not controlled by this template) and in virtual host context to ensure those
# settings are appliend and to allow per-vhost changes.
# Refer to Applied-Crypto-Hardening_bettercrypto/src/configuration/Webservers/Apache/default-ssl for details.
#}
<FilesMatch "\.(cgi|shtml|phtml|php)$">
SSLOptions +StdEnvVars
</FilesMatch>
<Directory /usr/lib/cgi-bin>
SSLOptions +StdEnvVars
</Directory>
BrowserMatch "MSIE [2-6]" \
nokeepalive ssl-unclean-shutdown \
downgrade-1.0 force-response-1.0
# MSIE 7 and newer should be able to use keepalive
BrowserMatch "MSIE [17-9]" ssl-unclean-shutdown
# TLS cipher suites set: {{ item.tls_cipher_suite_set_name | d(apache__tls_cipher_suite_set_name) | quote }}
SSLCipherSuite {{ apache__tls_cipher_suite_sets[item.tls_cipher_suite_set_name | d(apache__tls_cipher_suite_set_name)] | quote }}
SSLProtocol {{ item.tls_protocols | d(apache__tls_protocols) | join(" ") }}
SSLHonorCipherOrder {{ item.tls_honor_cipher_order | d(apache__tls_honor_cipher_order) | quote }}
SSLCompression {{ item.tls_compression | d(apache__tls_compression) | quote }}
{#
# https://raymii.org/s/tutorials/Strong_SSL_Security_On_Apache2.html
# https://github.com/sovereign/sovereign/issues/373
#}
{% if get_apache_version() is version_compare("2.4.8", ">=") and get_openssl_version() is version_compare("1.0.2", ">=") %}
# SSLOpenSSLConfCmd DHParameters {{ item.tls_dhparam_file | d(apache__tls_dhparam_file) | quote }}
{% endif %}
{% endmacro %}
{% macro get_https_directives(item) %}{# [[[ #}
SSLEngine on
{{ get_default_tls_directives(item) }}
{% set apache__tpl_pki_realm = item.pki_realm | d((get_realm_yaml_list(item.name, apache__pki_realm) | from_yaml)[0]) %}
{% set apache__tpl_pki_realm_path = apache__pki_realm_path + "/" + apache__tpl_pki_realm %}
SSLCertificateFile {{ item.tls_crt | d(apache__tpl_pki_realm_path + "/" + (item.pki_crt | d(apache__pki_crt_filename))) | quote }}
SSLCertificateKeyFile {{ item.tls_key | d(apache__tpl_pki_realm_path + "/" + (item.pki_key | d(apache__pki_key_filename))) | quote }}
{% if item.ocsp_stapling_enabled | d(apache__ocsp_stapling_enabled) | bool and get_openssl_version() is version_compare("0.9.8h", ">=") %}
SSLUseStapling on
{% endif %}
{% if item.hsts_enabled | d(apache__hsts_enabled) | bool %}
Header always set Strict-Transport-Security "max-age={{ item.hsts_max_age | d(apache__hsts_max_age) }}{{ "; includeSubDomains" if item.hsts_subdomains | d(apache__hsts_subdomains) | bool else "" }}{{ "; preload" if ((item.hsts_preload | d(apache__hsts_preload)) | bool) else "" }}"
{% endif %}
{% endmacro %}
{% macro get_http_security_headers(item) %}{# [[[ #}
{% set apache__tpl_directive_options = get_yaml_list_for_elem(item.http_sec_headers_directive_options | d(apache__http_sec_headers_directive_options)) | from_yaml | map("quote") | join(" ") %}
{% if item.csp_enabled | d(False) | bool %}
Header {{ apache__tpl_directive_options }} Content-Security-Policy "{{ item.csp | d("default-src https: ;") + (" " + item.csp_append | d(apache__http_csp_append) if (item.csp_append | d(apache__http_csp_append)) else "") }}"
{% endif %}
{% if item.csp_report_enabled | d(False) | bool %}
Header {{ apache__tpl_directive_options }} Content-Security-Policy-Report-Only "{{ item.csp_report | d(item.csp | d("default-src https: ;")) + (" " + item.csp_append | d(apache__http_csp_append) if (item.csp_append | d(apache__http_csp_append)) else "") }}"
{% endif %}
{% if item.http_frame_options | d(apache__http_frame_options) != omit %}
Header {{ apache__tpl_directive_options }} X-Frame-Options "{{ item.http_frame_options | d(apache__http_frame_options) }}"
{% endif %}
{% if item.http_xss_protection | d(apache__http_xss_protection) != omit %}
Header {{ apache__tpl_directive_options }} X-XSS-Protection "{{ item.http_xss_protection | d(apache__http_xss_protection) }}"
{% endif %}
{% if item.http_referrer_policy | d(apache__http_referrer_policy) != omit %}
Header {{ apache__tpl_directive_options }} Referrer-Policy "{{ item.http_referrer_policy | d(apache__http_referrer_policy) }}"
{% endif %}
{% if item.http_content_type_options | d(apache__http_content_type_options) != omit %}
Header {{ apache__tpl_directive_options }} X-Content-Type-Options "{{ item.http_content_type_options | d(apache__http_content_type_options) }}"
{% endif %}
{% endmacro %}
{% macro get_common_headers(item) %}{# [[[ #}
{% if item.http_clacks_overhead | d(apache__http_clacks_overhead | d(True)) | bool %}
{#
# Respect the will of the DebOps Creator.
# Ref: https://github.com/debops/ansible-nginx/commit/d6cd455c68a7584b2592053fd98d3e539054e09a
#}
Header always set X-Clacks-Overhead "GNU Terry Pratchett"
{% endif %}
{% endmacro %}
{% macro get_redirect(code, from, to, module) %}{# [[[ #}
{# Prefer the alias module but support the use of the rewrite module in case
other rewrite rules are used in the same context because the rewrite rules are
handled before all the directives from the alias module.
#}
{% set to = to + ("/" if (to[-1] != "/") else "") %}
{% if module == 'alias' %}
Redirect {{ code | string }} "{{ from }}" "{{ to }}"
{% elif module == 'rewrite' %}
<IfModule mod_rewrite>
RewriteRule "^{{ from }}?(.*)" "{{ to }}$1" [L,R={{ code }},NE]
</IfModule>
{% endif %}
{% endmacro %}
{% macro get_alias(url_path, fs_directory) %}{# [[[ #}
Alias {{ url_path | quote }} {{ (fs_directory + ("/" if (fs_directory[-1] != "/") else "")) | quote }}
{% endmacro %}
{% set apache__tpl_vhost_http_enabled = True if (get_listen_sockets(item.listen_http | d([]), apache__http_listen)) else False %}
{% set apache__tpl_vhost_https_enabled = True if (item.https_enabled | d(apache__https_enabled) | bool and get_listen_sockets(item.listen_https | d([]), apache__https_listen)) else False %}
{{ get_header_comments(item) }}
{% if apache__tpl_vhost_http_enabled | bool %}
# Virtual host handling HTTP [[[
<VirtualHost {{ get_listen_sockets(item.listen_http | d([]), apache__http_listen) }}>
{{ debops_indent(get_server_directives(item), 4) }}
{{ debops_indent(get_vhost_content_directives(item, mode='http', https_enabled=apache__tpl_vhost_https_enabled), 4) }}
</VirtualHost>
# ]]]
{% endif %}
{% if apache__tpl_vhost_https_enabled | bool %}
# Virtual host handling HTTPS [[[
<IfModule ssl_module>
<VirtualHost {{ get_listen_sockets(item.listen_https | d([]), apache__https_listen) }}>
{{ debops_indent(get_server_directives(item), 4) }}
{{ debops_indent(get_https_directives(item), 4) }}
{{ debops_indent(get_http_security_headers(item), 4) }}
{{ debops_indent(get_common_headers(item), 4) }}
{{ debops_indent(get_vhost_content_directives(item, mode='https'), 4) }}
</VirtualHost>
</IfModule>
# ]]]
{% endif %}

View file

@ -0,0 +1,9 @@
{# Copyright (C) 2016-2017 Robin Schneider <ypid@riseup.net>
# Copyright (C) 2016-2017 DebOps <https://debops.org/>
# SPDX-License-Identifier: GPL-3.0-only
#}
# {{ ansible_managed }}
{% if item.raw | d() %}
{{ item.raw }}
{%- endif %}

View file

@ -0,0 +1,20 @@
apparmor - Install and configure AppArmor
Copyright (C) 2015-2017 Robin Schneider <ypid@riseup.net>
Copyright (C) 2022 David Härdeman <david@hardeman.nu>
Copyright (C) 2015-2022 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,228 @@
---
# .. vim: foldmarker=[[[,]]]:foldmethod=marker
# .. Copyright (C) 2015-2017 Robin Schneider <ypid@riseup.net>
# .. Copyright (C) 2022 David Härdeman <david@hardeman.nu>
# .. Copyright (C) 2015-2022 DebOps <https://debops.org/>
# .. SPDX-License-Identifier: GPL-3.0-only
# .. _apparmor__ref_defaults:
# Default variables
# =================
#
# .. contents:: Sections
# :local:
#
# .. include:: ../../../../includes/global.rst
# Packages and installation [[[
# -----------------------------
# .. envvar:: apparmor__base_packages [[[
#
# List of base packages to install.
apparmor__base_packages:
- 'apparmor'
- 'apparmor-utils'
- 'apparmor-profiles'
- 'apparmor-profiles-extra'
# ]]]
# .. envvar:: apparmor__packages [[[
#
# List of additional packages to install.
apparmor__packages: []
# ]]]
# .. envvar:: apparmor__enabled [[[
#
# Enable or disable support the AppArmor. Since the role is included from the
# DebOps ``common`` playbook, this allows a manual override of the default
# behavior.
apparmor__enabled: '{{ ansible_local.apparmor.enabled
| d(False
if (ansible_distribution_release in ["stretch"] or
(ansible_virtualization_role | d("") == "guest"
and
ansible_virtualization_type | d("") in
["container"]))
else True) }}'
# ]]]
# .. envvar:: apparmor__manage_grub [[[
#
# Enable or disable support for adding kernel parameters via GRUB which cause
# the AppArmor security module to be enabled at boot. Note that the normal
# Debian/Ubuntu kernel packages already include and enable this support by
# default, so this is only necessary for old distributions and/or customized
# kernel builds.
apparmor__manage_grub: '{{ ansible_local.apparmor.grub_enabled
| d(True
if (apparmor__enabled | d(False) | bool and
ansible_distribution_release in ["stretch"])
else False) }}'
# ]]]
# .. envvar:: apparmor__kernel_parameters [[[
#
# Kernel parameters needed to enable AppArmor (if not already enabled by
# default, as in recent Debian/Ubuntu kernel packages). Only relevant if
# :envvar:`apparmor__manage_grub` is enabled.
apparmor__kernel_parameters:
- 'apparmor=1'
- 'security=apparmor'
# ]]]
# ]]]
# AppArmor profiles [[[
# ---------------------
# These variables control the state of individual AppArmor profiles. See
# :ref:`apparmor__ref_profiles` for more details.
# .. envvar:: apparmor__default_profiles [[[
#
# List of profiles to enable/disable, defined by the role.
apparmor__default_profiles: []
# ]]]
# .. envvar:: apparmor__profiles [[[
#
# List of profiles to enable/disable, defined for all hosts in the Ansible
# inventory.
apparmor__profiles: []
# ]]]
# .. envvar:: apparmor__group_profiles [[[
#
# List of profiles to enable/disable, defined on hosts in a specific Ansible
# inventory group.
apparmor__group_profiles: []
# ]]]
# .. envvar:: apparmor__host_profiles [[[
#
# List of profiles to enable/disable, defined on specific hosts in the Ansible
# inventory.
apparmor__host_profiles: []
# ]]]
# .. envvar:: apparmor__dependent_profiles [[[
#
# Variable definitions managed by roles using this role as dependency.
apparmor__dependent_profiles: []
# ]]]
# .. envvar:: apparmor__combined_profiles [[[
#
# A combination of the profiles to enable/disable, defined by the other
# variables, used in role tasks.
apparmor__combined_profiles: '{{ apparmor__default_profiles
+ apparmor__profiles
+ apparmor__group_profiles
+ apparmor__host_profiles
+ apparmor__dependent_profiles }}'
# ]]]
# ]]]
# AppArmor local profile modifications [[[
# ----------------------------------------
# These variables control local profile modifications. See
# :ref:`apparmor__ref_locals` for more details.
# .. envvar:: apparmor__default_locals [[[
#
# List of default local profile modifications defined by the role.
apparmor__default_locals: []
# ]]]
# .. envvar:: apparmor__locals [[[
#
# List of local profile modifications defined for all hosts in the Ansible
# inventory.
apparmor__locals: []
# ]]]
# .. envvar:: apparmor__group_locals [[[
#
# List of local profile modifications defined on hosts in a specific Ansible
# inventory group.
apparmor__group_locals: []
# ]]]
# .. envvar:: apparmor__host_locals [[[
#
# List of local profile modifications defined on specific hosts in the Ansible
# inventory.
apparmor__host_locals: []
# ]]]
# .. envvar:: apparmor__dependent_locals [[[
#
# Variable definitions managed by roles using this role as dependency.
apparmor__dependent_locals: []
# ]]]
# .. envvar:: apparmor__combined_locals [[[
#
# A combination of the local profile modifications defined by the other
# variables, used in role tasks.
apparmor__combined_locals: '{{ apparmor__default_locals
+ apparmor__locals
+ apparmor__group_locals
+ apparmor__host_locals
+ apparmor__dependent_locals }}'
# ]]]
# ]]]
# AppArmor tunables [[[
# ---------------------
# These variables control profile tunables. See :ref:`apparmor__ref_tunables`
# for more details.
# .. envvar:: apparmor__default_tunables [[[
#
# List of default tunables defined by the role.
apparmor__default_tunables: []
# ]]]
# .. envvar:: apparmor__tunables [[[
#
# List of tunables defined for all hosts in the Ansible inventory.
apparmor__tunables: []
# ]]]
# .. envvar:: apparmor__group_tunables [[[
#
# List of tunables defined on hosts in a specific Ansible inventory group.
apparmor__group_tunables: []
# ]]]
# .. envvar:: apparmor__host_tunables [[[
#
# List of tunables defined on specific hosts in the Ansible inventory.
apparmor__host_tunables: []
# ]]]
# .. envvar:: apparmor__dependent_tunables [[[
#
# Variable definitions managed by roles using this role as dependency.
apparmor__dependent_tunables: []
# ]]]
# .. envvar:: apparmor__combined_tunables [[[
#
# A combination of the tunables defined by the other variables, used in
# role tasks.
apparmor__combined_tunables: '{{ apparmor__default_tunables
+ apparmor__tunables
+ apparmor__group_tunables
+ apparmor__host_tunables
+ apparmor__dependent_tunables }}'
# ]]]
# ]]]

View file

@ -0,0 +1,32 @@
---
# Copyright (C) 2015-2017 Robin Schneider <ypid@riseup.net>
# 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: 'Robin Schneider'
description: 'Install and configure AppArmor'
company: 'DebOps'
license: 'GPL-3.0-only'
min_ansible_version: '2.1.3'
platforms:
- name: 'Ubuntu'
versions: [ 'all' ]
- name: 'Debian'
versions: [ 'all' ]
galaxy_tags:
- system
- security
- hardening
- mac

View file

@ -0,0 +1,61 @@
---
# Copyright (C) 2022 David Härdeman <david@hardeman.nu>
# Copyright (C) 2022 DebOps <https://debops.org/>
# SPDX-License-Identifier: GPL-3.0-only
- name: Create base directory for local modification {{ item.name }}
ansible.builtin.file:
path: '{{ "/etc/apparmor.d/local/" + item.name | dirname }}'
state: 'directory'
owner: 'root'
group: 'root'
mode: '0755'
when:
- item.state | d("present") == "present"
- item.name | dirname != ""
tags: [ 'role::apparmor:locals' ]
- name: Create local modification {{ item.name }}
ansible.builtin.template:
src: 'etc/apparmor.d/snippet.j2'
dest: '{{ "/etc/apparmor.d/local/" + item.name }}'
owner: 'root'
group: 'root'
mode: '0644'
when: item.state | d("present") == "present"
vars:
apparmor__var_template_title: 'AppArmor local modification'
apparmor__var_template_suffix: ','
apparmor__var_template_operator: ' '
notify: [ 'Reload all AppArmor profiles' ]
tags: [ 'role::apparmor:locals' ]
- name: Check the presence of profile {{ item.name }}
ansible.builtin.stat:
path: '{{ "/etc/apparmor.d/" + item.name }}'
register: apparmor__register_local_profile
when: item.state | d("present") == "absent"
tags: [ 'role::apparmor:locals' ]
- name: Truncate local modification {{ item.name }}
ansible.builtin.copy:
dest: '{{ "/etc/apparmor.d/local/" + item.name }}'
content: ''
owner: 'root'
group: 'root'
mode: '0644'
when:
- item.state | d("present") == "absent"
- apparmor__register_local_profile.stat.exists | d(False)
notify: [ 'Reload all AppArmor profiles' ]
tags: [ 'role::apparmor:locals' ]
- name: Remove local modification {{ item.name }}
ansible.builtin.file:
path: '{{ "/etc/apparmor.d/local/" + item.name }}'
state: 'absent'
when:
- item.state | d("present") == "absent"
- not apparmor__register_local_profile.stat.exists | d(False)
notify: [ 'Reload all AppArmor profiles' ]
tags: [ 'role::apparmor:locals' ]

View file

@ -0,0 +1,45 @@
---
# Copyright (C) 2022 David Härdeman <david@hardeman.nu>
# Copyright (C) 2022 DebOps <https://debops.org/>
# SPDX-License-Identifier: GPL-3.0-only
- name: Ensure a valid state for profile {{ item.name }}
ansible.builtin.assert:
that: item.state | d() in [ "enforce", "complain", "disable", "ignore" ]
quiet: True
tags: [ 'role::apparmor:profiles' ]
- name: Check presence of profile {{ item.name }}
ansible.builtin.stat:
path: '{{ "/etc/apparmor.d/" + item.name }}'
register: apparmor__stat_profile
when: item.state != "ignore"
tags: [ 'role::apparmor:profiles' ]
# Note: the aa-* utils happily exit with 0 even when the profile doesn't exist
- name: Ensure existence of profile {{ item.name }}
ansible.builtin.assert:
that: apparmor__stat_profile.stat.exists | d(False)
quiet: True
when: item.state != "ignore"
tags: [ 'apparmor:profiles' ]
- name: Register current AppArmor state
ansible.builtin.command: aa-status --json
register: apparmor__register_old_status
changed_when: False
when: item.state != "ignore"
tags: [ 'role::apparmor:profiles' ]
# Note: can't use --no-reload here, because aa-status won't show any changes
- name: Set profile {{ item.name + " to " + item.state }}
ansible.builtin.shell: set -o nounset -o pipefail -o errexit &&
aa-{{ item.state }} {{ ("/etc/apparmor.d/" + item.name) | quote }}
> /dev/null 2>&1 &&
aa-status --json
args:
executable: bash
register: apparmor__register_new_status
changed_when: apparmor__register_new_status.stdout != apparmor__register_old_status.stdout
when: item.state != "ignore"
tags: [ 'role::apparmor:profiles' ]

View file

@ -0,0 +1,50 @@
---
# Copyright (C) 2022 David Härdeman <david@hardeman.nu>
# Copyright (C) 2022 DebOps <https://debops.org/>
# SPDX-License-Identifier: GPL-3.0-only
# Note: these two could be merged, but it makes the output much more confusing
- name: Remove tunable {{ item.name }}
debops.debops.dpkg_divert:
path: '{{ "/etc/apparmor.d/tunables/" + item.name }}'
state: 'absent'
delete: True
when: item.state | d("present") == "absent"
notify: [ 'Reload all AppArmor profiles' ]
tags: [ 'role::apparmor:tunables' ]
- name: Divert tunable {{ item.name }}
debops.debops.dpkg_divert:
path: '{{ "/etc/apparmor.d/tunables/" + item.name }}'
state: 'present'
delete: True
when: item.state | d("present") == "present"
notify: [ 'Reload all AppArmor profiles' ]
tags: [ 'role::apparmor:tunables' ]
- name: Create base directory for tunable {{ item.name }}
ansible.builtin.file:
path: '{{ "/etc/apparmor.d/tunables/" + item.name | dirname }}'
state: 'directory'
owner: 'root'
group: 'root'
mode: '0755'
when:
- item.state | d("present") == "present"
- item.name | dirname != ""
tags: [ 'role::apparmor:tunables' ]
- name: Create tunable {{ item.name }}
ansible.builtin.template:
src: 'etc/apparmor.d/snippet.j2'
dest: '{{ "/etc/apparmor.d/tunables/" + item.name }}'
owner: 'root'
group: 'root'
mode: '0644'
vars:
apparmor__var_template_title: 'AppArmor tunable'
apparmor__var_template_suffix: ''
apparmor__var_template_operator: '='
when: item.state | d("present") == "present"
notify: [ 'Reload all AppArmor profiles' ]
tags: [ 'role::apparmor:tunables' ]

View file

@ -0,0 +1,127 @@
---
# Copyright (C) 2015-2017 Robin Schneider <ypid@riseup.net>
# Copyright (C) 2022 David Härdeman <david@hardeman.nu>
# Copyright (C) 2015-2022 DebOps <https://debops.org/>
# SPDX-License-Identifier: GPL-3.0-only
- name: Import custom Ansible plugins
ansible.builtin.import_role:
name: 'ansible_plugins'
- name: Import DebOps global handlers
ansible.builtin.import_role:
name: 'global_handlers'
- name: Install required packages
ansible.builtin.package:
name: '{{ q("flattened", (apparmor__base_packages
+ apparmor__packages)) }}'
state: 'present'
register: apparmor__register_packages
until: apparmor__register_packages is succeeded
tags: [ 'role::apparmor:pkg' ]
- name: Make sure that the Ansible local facts directory exists
ansible.builtin.file:
path: '/etc/ansible/facts.d'
state: 'directory'
owner: 'root'
group: 'root'
mode: '0755'
- name: Save AppArmor local facts
ansible.builtin.template:
src: 'etc/ansible/facts.d/apparmor.fact.j2'
dest: '/etc/ansible/facts.d/apparmor.fact'
owner: 'root'
group: 'root'
mode: '0755'
tags: [ 'meta::facts' ]
- name: Update Ansible facts if they were modified
ansible.builtin.meta: 'flush_handlers'
- name: Add AppArmor kernel parameters to GRUB configuration
ansible.builtin.template:
src: 'etc/default/grub.d/debops.apparmor.cfg.j2'
dest: '/etc/default/grub.d/debops.apparmor.cfg'
mode: '0644'
when: >
apparmor__enabled | d(False) | bool and
apparmor__manage_grub | d(False) | bool
notify: [ 'Update GRUB' ]
tags: [ 'role::apparmor:grub' ]
- name: Remove AppArmor kernel parameters from GRUB configuration
ansible.builtin.file:
path: '/etc/default/grub.d/debops.apparmor.cfg'
state: 'absent'
when: >
not apparmor__enabled | d(False) | bool or
not apparmor__manage_grub | d(False) | bool
notify: [ 'Update GRUB' ]
tags: [ 'role::apparmor:grub' ]
- name: Remove legacy GRUB configuration options
ansible.builtin.lineinfile:
dest: '/etc/default/grub'
regexp: '^GRUB_CMDLINE_LINUX="(.*?)\$GRUB_CMDLINE_LINUX_ANSIBLE_APPARMOR(.*)"'
line: 'GRUB_CMDLINE_LINUX="\1 \2"'
backrefs: yes
mode: '0644'
notify: [ 'Update GRUB' ]
tags: [ 'role::apparmor:grub' ]
- name: Configure tunables
ansible.builtin.include_tasks: handle_tunables.yml
loop: '{{ apparmor__combined_tunables | debops.debops.parse_kv_items() }}'
loop_control:
label: '{{ item.name }}'
tags: [ 'role::apparmor:tunables' ]
- name: Configure local changes to system profiles
ansible.builtin.include_tasks: handle_locals.yml
loop: '{{ apparmor__combined_locals | debops.debops.parse_kv_items() }}'
loop_control:
label: '{{ item.name }}'
tags: [ 'role::apparmor:locals' ]
- name: Configure profiles
ansible.builtin.include_tasks: handle_profiles.yml
loop: '{{ apparmor__combined_profiles | debops.debops.parse_kv_items() }}'
loop_control:
label: '{{ item.name }}'
tags: [ 'role::apparmor:profiles' ]
- name: Start and enable the AppArmor service
ansible.builtin.service:
name: 'apparmor'
state: 'started'
enabled: True
when:
- apparmor__enabled | d(False) | bool
- ansible_local.apparmor.installed | d(False) | bool
tags: [ 'role::apparmor:service' ]
- name: Reload AppArmor profiles if necessary
ansible.builtin.meta: 'flush_handlers'
tags: [ 'role::apparmor:profiles' ]
- name: Stop and disable the AppArmor service
ansible.builtin.service:
name: 'apparmor'
state: 'stopped'
enabled: False
when:
- not apparmor__enabled | d(False) | bool
- ansible_local.apparmor.installed | d(False) | bool
tags: [ 'role::apparmor:service' ]
- name: Unload all AppArmor profiles
ansible.builtin.command: aa-teardown
when:
- not apparmor__enabled | d(False) | bool
- ansible_local.apparmor.installed | d(False) | bool
register: apparmor__register_teardown
changed_when: apparmor__register_teardown.changed | bool
tags: [ 'role::apparmor:profiles' ]

View file

@ -0,0 +1,45 @@
#!{{ ansible_python['executable'] }}
# -*- coding: utf-8 -*-
# Copyright (C) 2022 David Härdeman <david@hardeman.nu>
# Copyright (C) 2022 DebOps <https://debops.org/>
# SPDX-License-Identifier: GPL-3.0-only
# {{ ansible_managed }}
from __future__ import print_function
from json import dumps, loads
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)
)
def read_kernel_bool(path):
value = 'N'
try:
with open(path, 'r') as file:
value = file.read().strip()
except Exception:
pass
return value.upper() == 'Y'
output = loads('''{{ {
"enabled": apparmor__enabled | d(False) | bool,
"grub_enabled" apparmor__manage_grub | d(False) | bool
} | to_nice_json }}''')
output.append({'installed': (cmd_exists('aa-status') and
cmd_exists('aa-teardown'))})
output.append({'kernel_enabled':
read_kernel_bool('/sys/module/apparmor/parameters/enabled')})
print(dumps(output, sort_keys=True, indent=4))

View file

@ -0,0 +1,54 @@
{#
# Copyright (C) 2022 David Härdeman <david@hardeman.nu>
# Copyright (C) 2022 DebOps <https://debops.org/>
# SPDX-License-Identifier: GPL-3.0-only
#}
# {{ ansible_managed }}
#
# {{ item.name }} - {{ appamor__var_template_title | d('Configuration snippet') }}
{% macro print_option(option, commented, loop_first, level) %}
{% if option.state | d('present') not in [ 'absent', 'init', 'ignore' ] %}
{% set prefix = ("{:^" + level | string + "}").format('') %}
{% set comment_prefix = (prefix + "# ") %}
{% if commented or option.state | d('present') == 'comment' %}
{% set commented = True %}
{% set prefix = comment_prefix %}
{% endif %}
{% if option.separator | d(False) or level == 0 or (option.comment | d() and not loop_first) %}
{{ '' }}
{% endif %}
{% if option.comment | d() %}
{{ option.comment | regex_replace('\n$', '') | comment(prefix='', decoration=comment_prefix, postfix='') -}}
{% endif %}
{% if option.raw | d() and commented %}
{{ '{}'.format(option.raw | regex_replace('\n$', '') | comment(prefix='', decoration=comment_prefix, postfix='')) -}}
{% elif option.raw | d() and not commented %}
{{ '{}{}'.format(prefix, option.raw | regex_replace('\n$', '')) }}
{% elif option.options | d() %}
{{ '{}{} {{'.format(prefix, option.option | d(option.name) | regex_replace('\n$', '')) }}
{% for option in option.options %}
{{ print_option(option, commented, loop.first, level + 1) -}}
{% endfor %}
{{ '{}}}'.format(prefix) }}
{% else %}
{% if option.operator | d() %}
{% set operator = option.operator %}
{% elif option.option | d(option.name) == '#include' %}
{% set operator = ' ' %}
{% else %}
{% set operator = apparmor__var_template_operator | d('=') %}
{% endif %}
{% set suffix = option.suffix | d(apparmor__var_template_suffix | d('')) %}
{{ '{}{}{}{}{}'.format(prefix,
option.option | d(option.name),
operator,
option.value | d(""),
suffix) }}
{% endif %}
{% endif %}
{% endmacro %}
{##}
{% for option in item.options %}
{{ print_option(option, False, loop.first, 0) -}}
{% endfor %}

View file

@ -0,0 +1,6 @@
{# Copyright (C) 2022 David Härdeman <david@hardeman.nu>
# Copyright (C) 2022 DebOps <https://debops.org/>
# SPDX-License-Identifier: GPL-3.0-only
#}
GRUB_CMDLINE_LINUX_DEBOPS_APPARMOR="{{ apparmor__kernel_parameters | d([]) | flatten | join(' ') }}"
GRUB_CMDLINE_LINUX="$GRUB_CMDLINE_LINUX $GRUB_CMDLINE_LINUX_DEBOPS_APPARMOR"

View file

@ -0,0 +1,20 @@
debops.apt - Manage APT repositories and keys using Ansible
Copyright (C) 2013-2023 Maciej Delmanowski <drybjed@gmail.com>
Copyright (C) 2015-2017 Robin Schneider <ypid@riseup.net>
Copyright (C) 2014-2023 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,793 @@
---
# .. vim: foldmarker=[[[,]]]:foldmethod=marker
# .. Copyright (C) 2013-2023 Maciej Delmanowski <drybjed@gmail.com>
# .. Copyright (C) 2015-2017 Robin Schneider <ypid@riseup.net>
# .. Copyright (C) 2014-2023 DebOps <https://debops.org/>
# .. SPDX-License-Identifier: GPL-3.0-only
# .. _apt__ref_defaults:
# debops.apt default variables
# ============================
# .. contents:: Sections
# :local:
#
# .. include:: ../../../../includes/global.rst
# General configuration [[[
# -------------------------
# .. envvar:: apt__enabled [[[
#
# Enable or disable management of the APT configuration and sources using this
# role.
apt__enabled: '{{ True if (ansible_pkg_mgr == "apt") else False }}'
# ]]]
# .. envvar:: apt__deploy_state [[[
#
# Enable (if ``present``) or disable (if ``absent``) management of the
# :file:`/etc/apt/sources.list` configuration file by the role.
apt__deploy_state: '{{ "present"
if (ansible_facts.distribution in ["Debian", "Raspbian", "Ubuntu", "Devuan"])
else "absent" }}'
# ]]]
# .. envvar:: apt__cache_valid_time [[[
#
# Update APT cache early in the playbook if it's older than 24h
# Set to False to disable update (useful when changing APT mirrors)
apt__cache_valid_time: '{{ ansible_local.core.cache_valid_time | d(60 * 60 * 24) }}'
# ]]]
# ]]]
# Packages to install [[[
# -----------------------
# .. envvar:: apt__base_packages [[[
#
# Default base packages to install for APT support. You can use the
# :ref:`debops.apt_install` role to install other packages not related to the
# package manager.
apt__base_packages:
- 'lsb-release'
- 'ca-certificates'
- '{{ "apt-transport-https"
if (ansible_distribution_release in
["stretch", "trusty", "xenial"])
else [] }}'
- 'gnupg'
# ]]]
# .. envvar:: apt__packages [[[
#
# List of additional APT packages to install for APT support. You can use the
# :ref:`debops.apt_install` role to install other packages not related to the
# package manager.
apt__packages: []
# ]]]
# ]]]
# The :file:`/etc/apt/sources.list` defaults [[[
# ----------------------------------------------
# These variables are used in configuration options of the
# :file:`/etc/apt/sources.list` configuration file. They are exposed here for
# convenience.
# .. envvar:: apt__archive_types [[[
#
# List of source types to configure for the default package sources. Supported
# choices: ``deb``, ``deb-src``.
apt__archive_types: [ 'deb', 'deb-src' ]
# ]]]
# .. envvar:: apt__archive_sources_disabled [[[
#
# If enabled, the 'deb-src' APT archive sources will be commented out by
# default to make APT updates faster.
apt__archive_sources_disabled: True
# ]]]
# .. envvar:: apt__architecture [[[
#
# The default system architecture present on the host.
apt__architecture: '{{ apt__architecture_map[ansible_facts.architecture]
| d(ansible_facts.architecture) }}'
# ]]]
# .. envvar:: apt__architecture_map [[[
#
# A YAML dictionary which defines a mapping between Ansible architectures and
# Debian/Ubuntu ports. Only definitions that are different from the detected
# ones are listed here, otherwise the value of ``ansible_architecture`` is
# used.
apt__architecture_map:
'x86_64': 'amd64'
'armv7l': 'armhf'
'aarch64': 'arm64'
# ]]]
# .. envvar:: apt__distribution [[[
#
# The Linux distribution present on the host.
# Note: this deliberately does not default to ansible_local.core.distribution
# because this local fact is set by the 'core' role, which runs later in the
# common playbook.
# Ref: https://github.com/debops/debops/issues/2046#issuecomment-1086702657
apt__distribution: '{{ ansible_facts.lsb.id | d(ansible_facts.distribution) }}'
# ]]]
# .. envvar:: apt__distribution_release [[[
#
# The Linux distribution release present on the host.
# Note: this deliberately does not default to
# ansible_local.core.distribution_release because this local fact is set by the
# 'core' role, which runs later in the common playbook.
# Ref: https://github.com/debops/debops/issues/2046#issuecomment-1086702657
apt__distribution_release: '{{ ansible_facts.lsb.codename
| d(ansible_facts.distribution_release) }}'
# ]]]
# .. envvar:: apt__distribution_version [[[
#
# The OS distribution version, used for specific APT repositories.
apt__distribution_version: '{{ ansible_facts.distribution_version }}'
# ]]]
# .. envvar:: apt__nonfree [[[
#
# Boolean. If enabled, non-free sections of a given distribution repository
# will be enabled, otherwise they won't be present.
#
# By default non-free sections are enabled on hardware-based hosts due to
# a possible requirement for non-free firmware packages. The fact script will
# also check if non-free section was enabled in the original
# :file:`sources.list` file and enable it accordingly.
apt__nonfree: '{{ ansible_facts.ansible_local.apt.nonfree
| d(True
if (ansible_facts.virtualization_role is undefined or
ansible_facts.virtualization_role != "guest")
else False) }}'
# ]]]
# .. envvar:: apt__nonfree_firmware [[[
#
# Boolean. If enabled, sections of a given distribution repository which
# contain non-free firmware packages will be enabled, otherwise they won't be
# present.
#
# By default non-free firmware sections are enabled on hardware-based hosts due
# to a possible requirement for non-free firmware packages.
apt__nonfree_firmware: '{{ True
if (ansible_facts.virtualization_role is undefined or
ansible_facts.virtualization_role != "guest")
else False }}'
# ]]]
# .. envvar:: apt__distribution_repository_map [[[
#
# YAML dictionary which maps the distribution OS to its default APT repository.
# Values from here are used in multiple entries in the
# :file:`/etc/apt/sources.list` configuration and are exposed here for
# convenience.
apt__distribution_repository_map:
'Debian': 'http://deb.debian.org/debian'
'Devuan': 'http://deb.devuan.org/merged'
'Ubuntu': '{{ "http://archive.ubuntu.com/ubuntu"
if (apt__architecture in ["amd64", "i386"])
else "http://ports.ubuntu.com/ubuntu-ports" }}'
# ]]]
# .. envvar:: apt__debian_archived_releases [[[
#
# List of Debian releases which have been archived and are not available in the
# default APT repositories. This variable is used conditionally to detect if
# a given OS release is archived.
apt__debian_archived_releases: [ 'wheezy', 'jessie', 'stretch', 'buster' ]
# ]]]
# ]]]
# The :file:`/etc/apt/sources.list` configuration entries [[[
# -----------------------------------------------------------
# .. _apt__ref_sources_defaults:
# These variables define the contents of the :file:`/etc/apt/sources.list`
# configuration file. See :ref:`apt__ref_sources` for more details.
# .. envvar:: apt__debian_sources [[[
#
# APT source entries for the Debian distribution.
apt__debian_sources:
- name: 'debian-release'
types: '{{ apt__archive_types }}'
uri: '{{ apt__distribution_repository_map.Debian | d() }}'
suites:
- '{{ apt__distribution_release }}'
components: [ 'main' ]
state: '{{ "present"
if (apt__distribution == "Debian")
else "ignore" }}'
- name: 'debian-release'
uri: 'http://archive.debian.org/debian'
state: '{{ "present"
if (apt__distribution == "Debian" and
apt__distribution_release in apt__debian_archived_releases)
else "ignore" }}'
- name: 'debian-release'
suites:
- '{{ apt__distribution_release + "-updates" }}'
state: '{{ "present"
if (apt__distribution == "Debian" and
apt__distribution_release not in apt__debian_archived_releases and
apt__distribution_version != "n/a")
else "ignore" }}'
- name: 'debian-release'
suites:
- '{{ apt__distribution_release + "-backports" }}'
state: '{{ "present"
if (apt__distribution == "Debian" and
apt__distribution_version != "n/a")
else "ignore" }}'
- name: 'debian-release'
components: [ 'non-free-firmware' ]
state: '{{ "ignore"
if (apt__distribution == "Debian" and
apt__distribution_release in ["wheezy", "jessie", "stretch",
"buster", "bullseye"])
else ("present"
if (apt__distribution == "Debian" and
apt__nonfree_firmware | bool)
else "ignore") }}'
- name: 'debian-release'
components: [ 'contrib', 'non-free' ]
state: '{{ "present"
if (apt__distribution == "Debian" and
apt__nonfree | bool)
else "ignore" }}'
- name: 'debian-release-security'
types: '{{ apt__archive_types }}'
uri: 'http://deb.debian.org/debian-security/'
suites:
- '{{ apt__distribution_release + "-security" }}'
components: [ 'main' ]
state: '{{ "present"
if (apt__distribution == "Debian" and
apt__distribution_release not in apt__debian_archived_releases and
apt__distribution_version != "n/a")
else "ignore" }}'
- name: 'debian-release-security'
uri: 'http://security.debian.org/'
suites:
# For some reason, filter doesn't accept '' string to reset the list of
# suites, so let's reset it "manually" for now.
- name: '{{ apt__distribution_release + "-security" }}'
state: 'absent'
- '{{ apt__distribution_release + "/updates" }}'
state: '{{ "present"
if (apt__distribution == "Debian" and
apt__distribution_release not in apt__debian_archived_releases and
apt__distribution_release in ["buster"] and
apt__distribution_version != "n/a")
else "ignore" }}'
- name: 'debian-release-security'
components: [ 'non-free-firmware' ]
state: '{{ "present"
if (apt__distribution == "Debian" and
apt__distribution_release not in apt__debian_archived_releases and
apt__distribution_release not in ["buster", "bullseye"] and
apt__distribution_version != "n/a" and
apt__nonfree_firmware | bool)
else "ignore" }}'
- name: 'debian-release-security'
components: [ 'contrib', 'non-free' ]
state: '{{ "present"
if (apt__distribution == "Debian" and
apt__distribution_release not in apt__debian_archived_releases and
apt__distribution_version != "n/a" and
apt__nonfree | bool)
else "ignore" }}'
# ]]]
# .. envvar:: apt__devuan_sources [[[
#
# APT source entries for the Devuan distribution.
apt__devuan_sources:
- name: 'devuan-release'
types: '{{ apt__archive_types }}'
uri: '{{ apt__distribution_repository_map.Devuan | d() }}'
suites:
- '{{ apt__distribution_release }}'
components: [ 'main' ]
state: '{{ "present"
if (apt__distribution == "Devuan")
else "ignore" }}'
- name: 'devuan-release'
uri: 'http://archive.devuan.org/merged'
state: '{{ "present"
if (apt__distribution == "Devuan" and
apt__distribution_release in ["jessie", "ascii"])
else "ignore" }}'
- name: 'devuan-release'
suites:
- '{{ apt__distribution_release + "-updates" }}'
state: '{{ "present"
if (apt__distribution == "Devuan" and
apt__distribution_release not in ["jessie", "ascii"] and
apt__distribution_version != "n/a")
else "ignore" }}'
- name: 'devuan-release'
suites:
- '{{ apt__distribution_release + "-backports" }}'
state: '{{ "present"
if (apt__distribution == "Devuan" and
apt__distribution_release not in ["jessie"] and
apt__distribution_version != "n/a")
else "ignore" }}'
- name: 'devuan-release'
components: [ 'contrib', 'non-free' ]
state: '{{ "present"
if (apt__distribution == "Devuan" and
apt__nonfree | bool)
else "ignore" }}'
- name: 'devuan-release-security'
types: '{{ apt__archive_types }}'
uri: 'http://pkgmaster.devuan.org/merged'
suites:
- '{{ apt__distribution_release + "-security" }}'
components: [ 'main' ]
state: '{{ "present"
if (apt__distribution == "Devuan" and
apt__distribution_release not in ["jessie", "ascii"] and
apt__distribution_version != "n/a")
else "ignore" }}'
- name: 'devuan-release-security'
components: [ 'contrib', 'non-free' ]
state: '{{ "present"
if (apt__distribution == "Devuan" and
apt__distribution_release not in ["jessie", "ascii"] and
apt__distribution_version != "n/a" and
apt__nonfree | bool)
else "ignore" }}'
# ]]]
# .. envvar:: apt__ubuntu_sources [[[
#
# APT source entries for the Ubuntu distribution.
apt__ubuntu_sources:
- name: 'ubuntu-release'
comment: |
See http://help.ubuntu.com/community/UpgradeNotes for how to upgrade to
newer versions of the distribution.
types: '{{ apt__archive_types }}'
uri: '{{ apt__distribution_repository_map.Ubuntu | d() }}'
suites:
- '{{ apt__distribution_release }}'
components: [ 'main' ]
state: '{{ "present"
if (apt__distribution == "Ubuntu")
else "ignore" }}'
- name: 'ubuntu-release'
components: [ 'restricted' ]
state: '{{ "present"
if (apt__distribution == "Ubuntu" and
apt__nonfree | bool)
else "ignore" }}'
- name: 'ubuntu-release-updates'
comment: |
Major bug fix updates produced after the final release of the
distribution.
types: '{{ apt__archive_types }}'
uri: '{{ apt__distribution_repository_map.Ubuntu | d() }}'
suites:
- '{{ apt__distribution_release + "-updates" }}'
components: [ 'main' ]
state: '{{ "present"
if (apt__distribution == "Ubuntu" and
apt__distribution_version != "n/a")
else "ignore" }}'
- name: 'ubuntu-release-updates'
components: [ 'restricted' ]
state: '{{ "present"
if (apt__distribution == "Ubuntu" and
apt__distribution_version != "n/a" and
apt__nonfree | bool)
else "ignore" }}'
- name: 'ubuntu-release-universe'
comment: |
N.B. software from this repository is ENTIRELY UNSUPPORTED by the Ubuntu
team. Also, please note that software in universe WILL NOT receive any
review or updates from the Ubuntu security team.
types: '{{ apt__archive_types }}'
uri: '{{ apt__distribution_repository_map.Ubuntu | d() }}'
suites:
- '{{ apt__distribution_release }}'
- '{{ apt__distribution_release + "-updates" }}'
components: [ 'universe' ]
state: '{{ "present"
if (apt__distribution == "Ubuntu")
else "ignore" }}'
- name: 'ubuntu-release-multiverse'
comment: |
N.B. software from this repository is ENTIRELY UNSUPPORTED by the Ubuntu
team, and may not be under a free licence. Please satisfy yourself as to
your rights to use the software. Also, please note that software in
multiverse WILL NOT receive any review or updates from the Ubuntu
security team.
types: '{{ apt__archive_types }}'
uri: '{{ apt__distribution_repository_map.Ubuntu | d() }}'
suites:
- '{{ apt__distribution_release }}'
- '{{ apt__distribution_release + "-updates" }}'
components: [ 'multiverse' ]
state: '{{ "present"
if (apt__distribution == "Ubuntu" and
apt__nonfree | bool)
else "ignore" }}'
- name: 'ubuntu-release-backports'
comment: |
N.B. software from this repository may not have been tested as
extensively as that contained in the main release, although it includes
newer versions of some applications which may provide useful features.
Also, please note that software in backports WILL NOT receive any review
or updates from the Ubuntu security team.
types: '{{ apt__archive_types }}'
uri: '{{ apt__distribution_repository_map.Ubuntu | d() }}'
suites:
- '{{ apt__distribution_release + "-backports" }}'
components: [ 'main' ]
state: '{{ "present"
if (apt__distribution == "Ubuntu" and
apt__distribution_version != "n/a")
else "ignore" }}'
- name: 'ubuntu-release-backports'
components: [ 'restricted' ]
state: '{{ "present"
if (apt__distribution == "Ubuntu" and
apt__distribution_version != "n/a" and
apt__nonfree | bool)
else "ignore" }}'
- name: 'ubuntu-release-backports'
components: [ 'universe' ]
state: '{{ "present"
if (apt__distribution == "Ubuntu" and
apt__distribution_version != "n/a")
else "ignore" }}'
- name: 'ubuntu-release-backports'
components: [ 'multiverse' ]
state: '{{ "present"
if (apt__distribution == "Ubuntu" and
apt__distribution_version != "n/a" and
apt__nonfree | bool)
else "ignore" }}'
- name: 'ubuntu-release-partner'
comment: |
Uncomment the following two lines to add software from Canonical's
'partner' repository.
This software is not part of Ubuntu, but is offered by Canonical and the
respective vendors as a service to Ubuntu users.
types: '{{ apt__archive_types }}'
uri: 'http://archive.canonical.com/ubuntu'
suites:
- '{{ apt__distribution_release }}'
components: [ 'partner' ]
state: '{{ "present"
if (apt__distribution == "Ubuntu" and
apt__distribution_version != "n/a" and
apt__nonfree | bool)
else "ignore" }}'
- name: 'ubuntu-release-security'
types: '{{ apt__archive_types }}'
uri: '{{ apt__distribution_repository_map.Ubuntu | d() }}'
suites:
- '{{ apt__distribution_release + "-security" }}'
components: [ 'main' ]
separate: False
state: '{{ "present"
if (apt__distribution == "Ubuntu" and
apt__distribution_version != "n/a")
else "ignore" }}'
- name: 'ubuntu-release-security'
components: [ 'restricted' ]
separate: False
state: '{{ "present"
if (apt__distribution == "Ubuntu" and
apt__distribution_version != "n/a" and
apt__nonfree | bool)
else "ignore" }}'
- name: 'ubuntu-release-universe-security'
types: '{{ apt__archive_types }}'
uri: '{{ apt__distribution_repository_map.Ubuntu | d() }}'
suites:
- '{{ apt__distribution_release + "-security" }}'
components: [ 'universe' ]
separate: False
state: '{{ "present"
if (apt__distribution == "Ubuntu" and
apt__distribution_version != "n/a")
else "ignore" }}'
- name: 'ubuntu-release-multiverse-security'
types: '{{ apt__archive_types }}'
uri: '{{ apt__distribution_repository_map.Ubuntu | d() }}'
suites:
- '{{ apt__distribution_release + "-security" }}'
components: [ 'multiverse' ]
state: '{{ "present"
if (apt__distribution == "Ubuntu" and
apt__distribution_version != "n/a" and
apt__nonfree | bool)
else "ignore" }}'
# ]]]
# .. envvar:: apt__sources [[[
#
# List of APT sources defined on all hosts in the Ansible inventory.
apt__sources: []
# ]]]
# .. envvar:: apt__group_sources [[[
#
# List of APT sources defined on hosts in a specific Ansible inventory group.
apt__group_sources: []
# ]]]
# .. envvar:: apt__host_sources [[[
#
# List of APT sources defined on specific hosts in the Ansible inventory.
apt__host_sources: []
# ]]]
# .. envvar:: apt__combined_sources [[[
#
# This variable combines all of the :file:`/etc/apt/sources.list` configuration
# lists and is used in role tasks and templates.
apt__combined_sources: '{{ apt__debian_sources
+ apt__devuan_sources
+ apt__ubuntu_sources
+ apt__sources
+ apt__group_sources
+ apt__host_sources }}'
# ]]]
# ]]]
# Extra architectures [[[
# -----------------------
# These lists define extra architectures to be enabled on the host.
# The main architecture does not need to be defined that way.
# .. envvar:: apt__extra_architectures [[[
#
# List of extra architectures to configure on all hosts in Ansible inventory.
apt__extra_architectures: []
# ]]]
# .. envvar:: apt__group_extra_architectures [[[
#
# List of extra architectures to configure on hosts in specific Ansible inventory
# group.
apt__group_extra_architectures: []
# ]]]
# .. envvar:: apt__host_extra_architectures [[[
#
# List of extra architectures to configure on specific hosts in Ansible inventory.
apt__host_extra_architectures: []
# ]]]
# ]]]
# APT packages to purge [[[
# -------------------------
# These lists define what APT packages should be purged (removed along with
# their configuration, data files and unused dependencies) from the hosts after
# APT repositories have been configured. Since this role is applied in the
# bootstrap playbooks, the packages will be purged early on; this might be
# useful in certain provisioning setups.
#
# Use only simple APT package names here. For conditional removal, refer to the
# :ref:`debops.apt_install` role.
# .. envvar:: apt__purge_packages [[[
#
# List of APT packages to purge on all hosts in the Ansible inventory.
apt__purge_packages: []
# ]]]
# .. envvar:: apt__purge_group_packages [[[
#
# List of APT packages to purge on hosts in a specific Ansible inventory group.
apt__purge_group_packages: []
# ]]]
# .. envvar:: apt__purge_host_packages [[[
#
# List of APT packages to purge on specific hosts in the Ansible inventory.
apt__purge_host_packages: []
# ]]]
# ]]]
# APT repository keys [[[
# -----------------------
# These lists define APT GPG keys to configure on hosts to enable authenticated
# access to additional APT repositories. See :ref:`apt__ref_keys` for more
# details.
# .. envvar:: apt__keys [[[
#
# List of APT GPG keys to configure on all hosts in Ansible inventory.
apt__keys: []
# ]]]
# .. envvar:: apt__group_keys [[[
#
# List of APT GPG keys to configure on hosts in specific Ansible inventory
# group.
apt__group_keys: []
# ]]]
# .. envvar:: apt__host_keys [[[
#
# List of APT GPG keys to configure on specific hosts in Ansible inventory.
apt__host_keys: []
# ]]]
# ]]]
# APT repositories [[[
# --------------------
# These lists define additional APT repositories in the
# :file:`/etc/apt/sources.list.d/` directory. See :ref:`apt__ref_repositories` for
# more details.
# .. envvar:: apt__repositories [[[
#
# List of additional APT repositories for all hosts in Ansible inventory.
apt__repositories: []
# ]]]
# .. envvar:: apt__group_repositories [[[
#
# List of additional APT repositories for hosts in specific Ansible inventory
# group.
apt__group_repositories: []
# ]]]
# .. envvar:: apt__host_repositories [[[
#
# List of additional APT repositories for specific hosts in Ansible inventory.
apt__host_repositories: []
# ]]]
# .. envvar:: apt__combined_repositories [[[
#
# Variable which combines all of the repository lists and is used in role tasks
# and templates.
apt__combined_repositories: '{{ apt__repositories
+ apt__group_repositories
+ apt__host_repositories }}'
# ]]]
# ]]]
# APT authentication files [[[
# ----------------------------
# These lists define APT authentication information for repositories which
# require HTTP Basic Authentication to access. See :ref:`apt__ref_auth_files`
# for more details.
# .. envvar:: apt__auth_files [[[
#
# Authentication configuration files which should be present on all hosts in
# the Ansible inventory.
apt__auth_files: []
# ]]]
# .. envvar:: apt__group_auth_files [[[
#
# Authentication configuration files which should be present on hosts in
# a specific Ansible inventory group.
apt__group_auth_files: []
# ]]]
# .. envvar:: apt__host_auth_files [[[
#
# Authentication configuration files which should be present on specific hosts
# in the Ansible inventory.
apt__host_auth_files: []
# ]]]
# ]]]
# APT configuration files [[[
# ---------------------------
# These lists define additional APT configuration files in
# :file:`/etc/apt/apt.conf.d/` directory.
# See :ref:`apt__ref_configuration` for more details.
# .. envvar:: apt__default_configuration [[[
#
# List of default APT configuration entries defined by the role.
apt__default_configuration:
- name: 'non-free-firmware-note'
filename: 'non-free-firmware-note.conf'
comment: 'Disable note about Debian Bookworm moving firmware to a separate section'
raw: |
APT::Get::Update::SourceListWarnings::NonFreeFirmware "false";
state: '{{ "ignore"
if (apt__distribution == "Debian" and
apt__distribution_release in ["wheezy", "jessie", "stretch",
"buster", "bullseye"])
else "present" }}'
- name: 'no-recommends'
filename: '25no-recommends.conf'
comment: 'Should APT install recommended or suggested packages?'
raw: |
APT::Install-Recommends "false";
APT::Install-Suggests "false";
state: 'present'
# ]]]
# .. envvar:: apt__configuration [[[
#
# List of additional APT configuration to add on all hosts in Ansible inventory.
apt__configuration: []
# ]]]
# .. envvar:: apt__group_configuration [[[
#
# List of additional APT configuration to add on hosts in specific Ansible
# inventory group.
apt__group_configuration: []
# ]]]
# .. envvar:: apt__host_configuration [[[
#
# List of additional APT configuration to add on specific hosts in Ansible
# inventory.
apt__host_configuration: []
# ]]]
# .. envvar:: apt__combined_configuration [[[
#
# Variable which combines all of the APT configuration lists and is used in
# role tasks and templates.
apt__combined_configuration: '{{ apt__default_configuration
+ apt__configuration
+ apt__group_configuration
+ apt__host_configuration }}'
# ]]]
# ]]]

View file

@ -0,0 +1,32 @@
---
# Copyright (C) 2013-2018 Maciej Delmanowski <drybjed@gmail.com>
# Copyright (C) 2015-2017 Robin Schneider <ypid@riseup.net>
# Copyright (C) 2014-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, Robin Schneider'
description: 'Manage APT repositories and keys'
company: 'DebOps'
license: 'GPL-3.0-only'
min_ansible_version: '2.2.0'
platforms:
- name: 'Ubuntu'
versions: [ 'all' ]
- name: 'Debian'
versions: [ 'all' ]
galaxy_tags:
- system
- packages
- apt

View file

@ -0,0 +1,239 @@
---
# .. vim: foldmarker=[[[,]]]:foldmethod=marker
# Copyright (C) 2013-2023 Maciej Delmanowski <drybjed@gmail.com>
# Copyright (C) 2015-2017 Robin Schneider <ypid@riseup.net>
# Copyright (C) 2014-2023 DebOps <https://debops.org/>
# SPDX-License-Identifier: GPL-3.0-only
- name: Import DebOps global handlers
ansible.builtin.import_role:
name: 'debops.debops.global_handlers'
- name: Import DebOps secret role
ansible.builtin.import_role:
name: 'debops.debops.secret'
- name: Validate configuration of APT repositories
ansible.builtin.assert:
that:
- 'item.filename is defined'
- 'item.repo is defined or item.uris is defined or item.state in ["divert", "absent"]'
fail_msg: 'You need to specify "filename" and either "repo" or "uris" as parameters'
quiet: True
loop: '{{ apt__combined_repositories | debops.debops.parse_kv_items }}'
loop_control:
label: '{{ item.name }}'
- name: Configure custom APT keys
ansible.builtin.apt_key:
data: '{{ item.data | d(omit) }}'
file: '{{ item.file | d(omit) }}'
id: '{{ item.id | d(omit) }}'
keyring: '{{ item.keyring | d(omit) }}'
keyserver: '{{ item.keyserver | d(omit) }}'
url: '{{ item.url | d(omit) }}'
state: '{{ item.state | d("present") }}'
loop: '{{ q("flattened", apt__keys
+ apt__group_keys
+ apt__host_keys) }}'
register: apt__register_apt_key
until: apt__register_apt_key is succeeded
when: apt__enabled | bool and (item.url | d() or item.data | d() or item.id | d() or item.file | d())
tags: [ 'role::apt:keys' ]
- name: Add/remove diversion of repository sources
debops.debops.dpkg_divert:
path: '/etc/apt/sources.list.d/{{ item.filename }}'
state: '{{ "present"
if (item.state | d("present") == "divert")
else "absent" }}'
delete: True
loop: '{{ apt__combined_repositories | debops.debops.parse_kv_items }}'
loop_control:
label: '{{ {"name": item.name, "state": item.state | d("present")} }}'
register: apt__register_divert_repositories
when: (apt__enabled | bool and item.filename and item.repo is undefined and item.uris is undefined and
(item.state | d("present")) in [ "divert", "absent" ])
- name: Configure custom APT repositories
ansible.builtin.apt_repository:
update_cache: False
repo: '{{ item.repo }}'
codename: '{{ item.codename | d(omit) }}'
filename: '{{ item.filename | regex_replace(".list$", "") | regex_replace(".sources$", "") }}'
mode: '{{ item.mode | d(omit) }}'
state: '{{ item.state | d("present") }}'
loop: '{{ apt__combined_repositories | debops.debops.parse_kv_items }}'
loop_control:
label: '{{ {"name": item.name, "state": item.state | d("present")} }}'
register: apt__register_apt_repositories
when: (apt__enabled | bool and item.repo | d() and item.uris is undefined and
(item.state | d("present")) not in [ "divert", "ignore", "init" ])
- name: Configure custom APT Deb822 repositories
ansible.builtin.deb822_repository:
uris: '{{ item.uris }}'
name: '{{ item.filename | regex_replace(".sources$", "") | regex_replace(".list$", "") }}'
allow_downgrade_to_insecure: '{{ item.allow_downgrade_to_insecure | d(omit) }}'
allow_insecure: '{{ item.allow_insecure | d(omit) }}'
allow_weak: '{{ item.allow_weak | d(omit) }}'
architectures: '{{ item.architectures | d(omit) }}'
by_hash: '{{ item.by_hash | d(omit) }}'
check_date: '{{ item.check_date | d(omit) }}'
check_valid_until: '{{ item.check_valid_until | d(omit) }}'
components: '{{ item.components | d(omit) }}'
data_max_future: '{{ item.date_max_future | d(omit) }}'
enabled: '{{ item.enabled | d(omit) }}'
inrelease_path: '{{ item.inrelease_path | d(omit) }}'
languages: '{{ item.languages | d(omit) }}'
mode: '{{ item.mode | d(omit) }}'
pdiffs: '{{ item.pdiffs | d(omit) }}'
signed_by: '{{ item.signed_by | d(omit) }}'
state: '{{ item.state | d("present") }}'
suites: '{{ item.suites | d(omit) }}'
targets: '{{ item.targets | d(omit) }}'
trusted: '{{ item.trusted | d(omit) }}'
types: '{{ item.types | d("deb") }}'
loop: '{{ apt__combined_repositories | debops.debops.parse_kv_items }}'
loop_control:
label: '{{ {"name": item.name, "state": item.state | d("present")} }}'
register: apt__register_deb822_repositories
when: (apt__enabled|bool and item.uris | d() and item.repo is undefined and
(item.state | d("present")) not in [ "divert", "ignore", "init" ])
- name: Remove APT auth configuration if requested
ansible.builtin.file:
path: '{{ "/etc/apt/auth.conf.d/" + (item.filename | d(item.name | regex_replace(".conf$", "") + ".conf")) }}'
state: 'absent'
loop: '{{ q("flattened", (apt__auth_files + apt__group_auth_files + apt__host_auth_files)) }}'
when: apt__enabled | bool and item.state | d('present') == 'absent'
no_log: '{{ debops__no_log | d(True) }}'
- name: Generate APT auth configuration
ansible.builtin.template:
src: 'etc/apt/auth.conf.d/template.conf.j2'
dest: '{{ "/etc/apt/auth.conf.d/" + (item.filename | d(item.name | regex_replace(".conf$", "") + ".conf")) }}'
mode: '0640'
loop: '{{ q("flattened", (apt__auth_files + apt__group_auth_files + apt__host_auth_files)) }}'
when: apt__enabled | bool and item.machine | d() and item.login | d() and item.password | d() and
item.state | d('present') not in ['absent', 'ignore']
no_log: '{{ debops__no_log | d(True) }}'
- name: Update APT cache on first run
ansible.builtin.apt:
update_cache: True
cache_valid_time: '{{ apt__cache_valid_time }}'
register: apt__register_apt_first_update
until: apt__register_apt_first_update is succeeded
when: (apt__enabled | bool and
not (ansible_local.apt.configured | d()) | bool)
- name: Install required packages
ansible.builtin.apt:
name: '{{ (apt__base_packages + apt__packages) | flatten }}'
state: 'present'
install_recommends: False
register: apt__register_packages
until: apt__register_packages is succeeded
when: apt__enabled | bool
- name: Enable extra architectures
ansible.builtin.command: dpkg --add-architecture {{ item }}
loop: '{{ q("flattened", apt__extra_architectures
+ apt__group_extra_architectures
+ apt__host_extra_architectures) }}'
register: apt__register_add_architecture
changed_when: apt__register_add_architecture.changed | bool
when: apt__enabled | bool and item not in ansible_facts.ansible_local.apt.foreign_architectures | d()
notify: [ 'Refresh host facts' ]
- name: Add/remove diversion of APT configuration files
debops.debops.dpkg_divert:
path: '{{ "/etc/apt/apt.conf.d/" + (item.filename | d(item.name | regex_replace(".conf$", "") + ".conf")) }}'
state: '{{ "present"
if (item.state | d("present") == "divert")
else "absent" }}'
delete: True
loop: '{{ apt__combined_configuration | debops.debops.parse_kv_items }}'
loop_control:
label: '{{ {"name": item.name, "state": item.state | d("present")} }}'
when: apt__enabled | bool and item.state | d("present") in [ "divert", "absent" ]
- name: Delete APT configuration files on remote hosts
ansible.builtin.file:
path: '{{ "/etc/apt/apt.conf.d/" + (item.filename | d(item.name | regex_replace(".conf$", "") + ".conf")) }}'
state: 'absent'
loop: '{{ apt__combined_configuration | debops.debops.parse_kv_items }}'
loop_control:
label: '{{ {"name": item.name, "state": item.state | d("present")} }}'
when: apt__enabled | bool and item.raw | d() and item.state | d('present') == 'absent'
- name: Generate APT configuration files
ansible.builtin.template:
src: 'etc/apt/apt.conf.d/template.conf.j2'
dest: '{{ "/etc/apt/apt.conf.d/" + (item.filename | d(item.name | regex_replace(".conf$", "") + ".conf")) }}'
mode: '0644'
loop: '{{ apt__combined_configuration | debops.debops.parse_kv_items }}'
loop_control:
label: '{{ {"name": item.name, "state": item.state | d("present")} }}'
when: apt__enabled | bool and item.raw | d() and
item.state | d('present') not in [ 'divert', 'absent', 'ignore', 'init' ]
- name: Make sure that Ansible local facts directory exists
ansible.builtin.file:
path: '/etc/ansible/facts.d'
state: 'directory'
mode: '0755'
tags: [ 'meta::facts' ]
- name: Save APT local facts
ansible.builtin.template:
src: 'etc/ansible/facts.d/apt.fact.j2'
dest: '/etc/ansible/facts.d/apt.fact'
mode: '0755'
notify: [ 'Refresh host facts' ]
tags: [ 'meta::facts' ]
- name: Update Ansible facts if they were modified
ansible.builtin.meta: 'flush_handlers'
- name: Add/remove diversion of /etc/apt/sources.list
debops.debops.dpkg_divert:
path: '/etc/apt/sources.list'
state: '{{ apt__deploy_state }}'
delete: True
register: apt__register_sources_diversion
when: (apt__enabled | bool and apt__deploy_state in ['absent', 'present'])
- name: Configure operating system sources.list
ansible.builtin.template:
src: 'etc/apt/sources.list.j2'
dest: '/etc/apt/sources.list'
mode: '0644'
register: apt__register_sources_template
when: (apt__enabled | bool and apt__deploy_state == 'present')
- name: Update APT cache
ansible.builtin.apt:
update_cache: True
cache_valid_time: '{{ omit
if (apt__register_sources_template is changed or
apt__register_sources_diversion is changed or
apt__register_apt_repositories is changed)
else apt__cache_valid_time }}'
register: apt__register_apt_update
until: apt__register_apt_update is succeeded
when: apt__enabled | bool
- name: Purge APT packages if requested
ansible.builtin.apt:
name: '{{ (apt__purge_packages
+ apt__purge_group_packages
+ apt__purge_host_packages) | flatten }}'
state: 'absent'
purge: True
autoremove: True
register: apt__register_purge_packages
until: apt__register_purge_packages is succeeded
when: apt__enabled | bool

View file

@ -0,0 +1,75 @@
#!{{ ansible_python['executable'] }}
# -*- coding: utf-8 -*-
# Copyright (C) 2013-2023 Maciej Delmanowski <drybjed@gmail.com>
# Copyright (C) 2015-2017 Robin Schneider <ypid@riseup.net>
# Copyright (C) 2014-2023 DebOps <https://debops.org/>
# SPDX-License-Identifier: GPL-3.0-only
# {{ ansible_managed }}
from __future__ import print_function
from json import loads, dumps
from sys import exit
import subprocess
import os
sources_list = ['/etc/apt/sources.list.dpkg-divert',
'/etc/apt/sources.list']
nonfree_components = ['non-free', 'restricted', 'multiverse']
nonfree_firmware_components = ['non-free-firmware']
output = loads('''{{ ({
"enabled": apt__enabled,
"configured": True,
"components": [],
"nonfree": False,
"nonfree_firmware": False}) | to_nice_json }}''')
try:
for sources_file in sources_list:
if (os.path.isfile(sources_file)
and os.access(sources_file, os.R_OK)):
fh = open(sources_file)
for line in fh:
if (line.startswith('deb') or
line.startswith('deb-src')):
source = line.split()
for component in source[3:]:
if component not in output['components']:
output['components'].append(component)
if component in nonfree_components:
output['nonfree'] = True
if component in nonfree_firmware_components:
output['nonfree_firmware'] = True
fh.close()
except Exception:
pass
try:
main_arch = subprocess.check_output(
['dpkg', '--print-architecture'],
stderr=subprocess.STDOUT).decode('utf-8').strip()
if main_arch:
output.update({'architecture': main_arch})
except subprocess.CalledProcessError:
pass
try:
foreign_arch = subprocess.check_output(
['dpkg', '--print-foreign-architectures'],
stderr=subprocess.STDOUT).decode('utf-8').strip()
if foreign_arch:
output.update({'foreign_architectures':
foreign_arch.split('\n')})
except subprocess.CalledProcessError:
pass
print(dumps(output, sort_keys=True, indent=4))

View file

@ -0,0 +1,12 @@
{# Copyright (C) 2023 Maciej Delmanowski <drybjed@gmail.com>
# Copyright (C) 2023 DebOps <https://debops.org/>
# SPDX-License-Identifier: GPL-3.0-only
#}
// {{ ansible_managed }}
{% if item.comment | d() %}
{{ item.comment | regex_replace('\n$', '') | comment(decoration='// ', prefix='', postfix='') -}}
{% endif %}
{% if item.raw | d() %}
{{ item.raw | regex_replace('\n$', '') -}}
{% endif %}

View file

@ -0,0 +1,12 @@
{# Copyright (C) 2021 Maciej Delmanowski <drybjed@gmail.com>
# Copyright (C) 2021 DebOps <https://debops.org/>
# SPDX-License-Identifier: GPL-3.0-or-later
#}
# {{ ansible_managed }}
# Authentication credentials for "{{ item.name }}" APT repository
{% if item.comment | d() %}
{{ item.comment | regex_replace('\n$','') | comment(prefix='', postfix='') }}
{% endif %}
{{ 'machine {} login {} password {}'.format(item.machine, item.login, item.password) }}

View file

@ -0,0 +1,84 @@
{# Copyright (C) 2013-2023 Maciej Delmanowski <drybjed@gmail.com>
# Copyright (C) 2015-2017 Robin Schneider <ypid@riseup.net>
# Copyright (C) 2014-2023 DebOps <https://debops.org/>
# SPDX-License-Identifier: GPL-3.0-only
#}
# {{ ansible_managed }}
{% for element in apt__combined_sources | debops.debops.parse_kv_items(merge_keys=['types', 'uris', 'suites', 'components']) %}
{% if element.state not in ['absent', 'ignore', 'init'] %}
{% set element_comment = ('#' if (element.state == 'comment') else '') %}
{% if element.comment | d() %}
{{ element.comment | regex_replace('\n$', '') | comment(prefix='', postfix='') -}}
{% endif %}
{% if element.raw | d() %}
{% if element_comment %}
{{ element.raw | regex_replace('\n$', '') | comment(decoration='#', prefix='', postfix='') -}}
{% else %}
{{ element.raw | regex_replace('\n$', '') }}
{% endif %}
{% else %}
{% set types = [] %}
{% set uris = [] %}
{% set suites = [] %}
{% set components = [] %}
{% if element.type | d() %}
{% set _ = types.append(element.type) %}
{% endif %}
{% if element.types | d() %}
{% set _ = types.extend(element.types | selectattr('state', 'equalto', 'present') | map(attribute='name') | list) %}
{% endif %}
{% if not types %}
{% set _ = types.extend(apt__archive_types) %}
{% endif %}
{% if element.uri | d() %}
{% set _ = uris.append(element.uri) %}
{% endif %}
{% if element.uris | d() %}
{% set _ = uris.extend(element.uris | selectattr('state', 'equalto', 'present') | map(attribute='name') | list) %}
{% endif %}
{% if element.suite | d() %}
{% set _ = suites.append(element.suite) %}
{% endif %}
{% if element.suites | d() %}
{% set suites = element.suites | selectattr('state', 'equalto', 'present') | map(attribute='name') | list %}
{% endif %}
{% if element.component | d() %}
{% set _ = components.append(element.component) %}
{% endif %}
{% if element.components | d() %}
{% set components = element.components | selectattr('state', 'equalto', 'present') | map(attribute='name') | list %}
{% endif %}
{% set source_options = '' %}
{% set parsed_options = [] %}
{% if element.options | d() %}
{% for option in element.options %}
{% if option.value is string %}
{% set option_string = option.name + '=' + option.value %}
{% else %}
{% set option_string = option.name + '=' + option.value | selectattr('state', 'equalto', 'present') | map(attribute='name') | list | join(',') %}
{% endif %}
{% set _ = parsed_options.append(option_string) %}
{% endfor %}
{% endif %}
{% if parsed_options %}
{% set source_options = ' [' + parsed_options | join(' ') + ']' %}
{% endif %}
{% for uri in uris %}
{% for suite in suites %}
{% for type in types %}
{% if type == 'deb-src' and apt__archive_sources_disabled | bool %}
{% set element_comment = '#' %}
{% else %}
{% set element_comment = ('#' if (element.state == 'comment') else '') %}
{% endif %}
{{ '{}{}{} {} {} {}'.format(element_comment, type, source_options, uri, suite, components | join(' ')) }}
{% endfor %}
{% endfor %}
{% endfor %}
{% endif %}
{% if element.separate | d(True) and not loop.last %}
{{ '' }}
{% endif %}
{% endif %}
{% endfor %}

View file

@ -0,0 +1,19 @@
debops.apt_cacher_ng - Install and manage the caching HTTP proxy Apt-Cacher NG
Copyright (C) 2016-2017,2021 Robin Schneider <ypid@riseup.net>
Copyright (C) 2016-2017,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,591 @@
---
# .. vim: foldmarker=[[[,]]]:foldmethod=marker
# .. Copyright (C) 2016-2017 Robin Schneider <ypid@riseup.net>
# .. Copyright (C) 2016-2017 DebOps <https://debops.org/>
# .. SPDX-License-Identifier: GPL-3.0-only
# .. _apt_cacher_ng__ref_defaults:
# debops.apt_cacher_ng default variables [[[
# ==========================================
# .. contents:: Sections
# :local:
#
# .. include:: ../../../../includes/global.rst
# Packages and installation [[[
# -----------------------------
# .. envvar:: apt_cacher_ng__base_packages [[[
#
# List of base packages to install.
apt_cacher_ng__base_packages:
- 'apt-cacher-ng'
# ]]]
# .. envvar:: apt_cacher_ng__enabled [[[
#
# Should the Apt-Cacher NG service be enabled?
apt_cacher_ng__enabled: True
# ]]]
# .. envvar:: apt_cacher_ng__deploy_state [[[
#
# What is the desired state which this role should achieve? Possible options:
#
# ``present``
# Default. Ensure that Apt-Cacher NG is installed and configured as requested.
#
# ``absent``
# Ensure that Apt-Cacher NG is uninstalled and it's configuration is removed.
#
# ``purge``
# Same as ``absent`` but additionally also ensures that the cache directories
# is removed.
#
apt_cacher_ng__deploy_state: 'present'
# ]]]
# .. envvar:: apt_cacher_ng__configuration_files [[[
#
# This variable allows you to change which configuration files this role is
# going to create and which permissions to use for them.
#
# ``path``
# String, required, defines the path to the configuration file on the host.
#
# ``src``
# String, optional, defines the path to the template file. Defaults to
# ``path`` with any leading ``/`` removed.
#
# ``owner``
# String, optional, defaults to ``root``. Unix user which owns the
# configuration file.
#
# ``group``
# String, optional, defaults to ``root``. Unix group of the configuration file.
#
# ``mode``
# String, optional, defaults to ``0640``. Unix permissions of the
# configuration file.
#
# ``divert``
# Boolean, optional, defaults to ``True``. Should the original configuration file
# be diverted away before creating our version of the file?
#
apt_cacher_ng__configuration_files:
- path: '/etc/apt-cacher-ng/backends_debian'
mode: '0644'
- path: '/etc/apt-cacher-ng/backends_ubuntu'
mode: '0644'
- path: '/etc/apt-cacher-ng/backends_gentoo'
mode: '0644'
divert: False
- path: '/etc/apt-cacher-ng/acng.conf'
mode: '0644'
- path: '/etc/apt-cacher-ng/security.conf'
group: 'apt-cacher-ng'
mode: '0640'
- path: '/etc/apt-cacher-ng/userinfo.html'
mode: '0644'
divert: False
# ]]]
# ]]]
# Network related settings [[[
# ----------------------------
# .. envvar:: apt_cacher_ng__port [[[
#
# TCP server port for incoming http (or HTTP proxy) connections.
# Can be set to ``9999`` to emulate :program:`apt-proxy`.
apt_cacher_ng__port: 3142
# ]]]
# .. envvar:: apt_cacher_ng__bind_address [[[
#
# List of addresses or hostnames to listen on. Each entry must be an exact
# local address which is associated with a local interface. DNS resolution is
# performed using :manpage:`getaddrinfo(3)` for all available protocols (IPv4,
# IPv6, ...). Using a protocol specific format will create binding(s) only on
# protocol specific socket(s), e.g. ``0.0.0.0`` will listen only to IPv4.
#
# Examples:
#
# .. code-block:: yaml
# :linenos:
#
# apt_cacher_ng__bind_address:
# - 'localhost'
# - '192.168.7.254'
# - 'publicNameOnMainInterface'
#
# Defaults to listening on all interfaces and protocols.
apt_cacher_ng__bind_address: []
# ]]]
# .. envvar:: apt_cacher_ng__fqdn [[[
#
# The FQDN subdomain of the Apt-Cacher NG proxy which will be used by
# :program:`nginx` webserver.
apt_cacher_ng__fqdn: 'software-cache.{{ ansible_domain }}'
# ]]]
# .. envvar:: apt_cacher_ng__proxy [[[
#
# The specification of another HTTP proxy which shall be used for downloads.
# It can include user name and password but see the manual for limitations.
#
# Examples:
#
# .. code-block:: yaml
# :linenos:
#
# apt_cacher_ng__proxy: 'https://username:proxypassword@proxy.example.net:3129'
#
# Defaults to using a direct connection.
apt_cacher_ng__proxy: ''
# ]]]
# .. envvar:: apt_cacher_ng__connect_protocol [[[
#
# Specifies the IP protocol families to use for remote connections. Order does
# matter, first specified are considered first.
#
# Examples:
#
# .. code-block:: yaml
# :linenos:
#
# apt_cacher_ng__connect_protocol:
# - 'v4'
# # - 'v6'
#
# Only use IPv4 connections for connecting to upstream mirrors.
#
# Defaults to using native order of the system's TCP/IP stack, influenced by
# the :envvar:`apt_cacher_ng__bind_address` value.
apt_cacher_ng__connect_protocol: []
# ]]]
# .. envvar:: apt_cacher_ng__offline_mode [[[
#
# Forbid outgoing connections and work without an internet connection or
# respond with 503 error where it's not possible.
apt_cacher_ng__offline_mode: False
# ]]]
# .. envvar:: apt_cacher_ng__network_timeout [[[
#
# Network timeout for outgoing connections, in seconds.
apt_cacher_ng__network_timeout: 60
# ]]]
# .. envvar:: apt_cacher_ng__max_download_speed_kib [[[
#
# It's possible to limit the processing speed of download agents to set an
# overall download speed limit. Unit: KiB/s, Default: unlimited.
apt_cacher_ng__max_download_speed_kib: ''
# ]]]
# ]]]
# Upstream mirrors [[[
# --------------------
# .. envvar:: apt_cacher_ng__upstream_mirror_debian [[[
#
# Which upstream mirror(s) should be used for Debian repositories?
# One mirror per line.
# Set to an empty string to let the package scripts from Apt-Cacher NG decide
# which upstream mirror to use.
apt_cacher_ng__upstream_mirror_debian: '{{ ansible_local.apt.default_sources_map.Debian[0]
| d("http://deb.debian.org/debian") }}'
# ]]]
# .. envvar:: apt_cacher_ng__upstream_mirror_ubuntu [[[
#
# Which upstream mirror(s) should be used for Ubuntu repositories?
# One mirror per line.
# Set to an empty string to let the package scripts from Apt-Cacher NG decide
# which upstream mirror to use.
apt_cacher_ng__upstream_mirror_ubuntu: '{{ ansible_local.apt.default_sources_map.Ubuntu[0]
| d("http://archive.ubuntu.com/ubuntu") }}'
# ]]]
# .. envvar:: apt_cacher_ng__upstream_mirror_gentoo [[[
#
# Which upstream mirror(s) should be used for Gentoo repositories?
# One mirror per line.
# Set to an empty string to let the package scripts from Apt-Cacher NG decide
# which upstream mirror to use.
apt_cacher_ng__upstream_mirror_gentoo: '{{ ansible_local.apt.default_sources_map.Gentoo[0] | d("") }}'
# ]]]
# ]]]
# Cache directory [[[
# -------------------
# .. envvar:: apt_cacher_ng__cache_dir [[[
#
# Storage directory for downloaded data and related maintenance activity.
apt_cacher_ng__cache_dir: '/var/cache/apt-cacher-ng'
# ]]]
# .. envvar:: apt_cacher_ng__cache_dir_owner [[[
#
# Unix user which owns the cache directory and it's contents.
apt_cacher_ng__cache_dir_owner: 'apt-cacher-ng'
# ]]]
# .. envvar:: apt_cacher_ng__cache_dir_group [[[
#
# Unix group of the cache directory and it's contents..
apt_cacher_ng__cache_dir_group: 'apt-cacher-ng'
# ]]]
# .. envvar:: apt_cacher_ng__dir_perms [[[
#
# Default permission set of freshly created files and directories, as octal
# numbers (see :manpage:`chmod(1)` for details).
# Can by limited by the umask value (see :manpage:`umask(2)` for details) if it's set in
# the environment of the starting shell, e.g. in apt-cacher-ng init script or
# in its configuration file.
apt_cacher_ng__dir_perms: '02755'
# ]]]
# .. envvar:: apt_cacher_ng__file_perms [[[
#
# Default permission set of freshly created files and directories, as octal
# numbers (see :manpage:`chmod(1)` for details).
# Can by limited by the umask value (see :manpage:`umask(2)` for details) if it's set in
# the environment of the starting shell, e.g. in apt-cacher-ng init script or
# in its configuration file.
apt_cacher_ng__file_perms: '00644'
# ]]]
# .. envvar:: apt_cacher_ng__cache_dir_enforce_permissions [[[
#
# Should the permissions of the cache directory and it's content be enforced
# (changed to the specified owner, group and mode)?
#
# Options:
#
# ``strict``
# Go thought all files and directories and enforce the permissions on each Ansible run.
#
# .. warning:: This can slow down the role execution time even
# when the changes have already been applied. The main factor is
# the number of files/directories in your cache directory.
#
# ``lazy``
# Check the :file:`_expending_damaged` file in the root of
# :envvar:`apt_cacher_ng__cache_dir` and only enforce permissions on all other
# files if this one file needed to be changed.
#
# ``disabled``
# Don't enforce permissions.
#
apt_cacher_ng__cache_dir_enforce_permissions: 'lazy'
# ]]]
# ]]]
# Management credentials [[[
# --------------------------
# .. envvar:: apt_cacher_ng__user [[[
#
# Username for basic authentication required to visit pages with administrative
# functionality.
apt_cacher_ng__user: 'admin'
# ]]]
# .. envvar:: apt_cacher_ng__password [[[
#
# Password for basic authentication required to visit pages with administrative
# functionality.
apt_cacher_ng__password: '{{ lookup("password", secret + "/credentials/" +
inventory_hostname + "/apt_cacher_ng/" +
apt_cacher_ng__user + "/password length=24") }}'
# ]]]
# ]]]
# Tuning, debugging and further options [[[
# -----------------------------------------
# .. envvar:: apt_cacher_ng__log_dir [[[
#
# Log file directory, can be set empty to disable logging.
apt_cacher_ng__log_dir: '/var/log/apt-cacher-ng'
# ]]]
# .. envvar:: apt_cacher_ng__support_dir [[[
#
# A place to look for additional configuration and resource files if they are not
# found in the configuration directory.
apt_cacher_ng__support_dir: '/usr/lib/apt-cacher-ng'
# ]]]
# .. envvar:: apt_cacher_ng__debug [[[
#
# A bitmask type value declaring the logging verbosity and behavior of the error
# log writing. Non-zero value triggers at least faster log file flushing.
#
# Some higher bits only working with a special debug build of apt-cacher-ng,
# see the manual for details. The setting has an alias named ``UnbufferLogs``.
# Setting ``apt_cacher_ng__debug: 1`` will result in unbuffer log writes.
#
# .. warning:: This can write significant amount of data into the
# :file:`apt-cacher.err` logfile.
#
# 0. No debug printing.
#
# 1. Log file buffers are flushed faster.
#
# 2. Some additional information appears within usual transfer/error logs.
#
# 4. Extra debug information is written to apt-cacher.err (also enables lots of additional trace
# points when apt-cacher-ng binary is built with debug configuration, see section 9.6 for
# details).
#
apt_cacher_ng__debug: 0
# ]]]
# .. envvar:: apt_cacher_ng__verbose_log [[[
#
# Enables extended client information in log entries. When set to ``True``,
# only activity type, time and transfer sizes are logged.
apt_cacher_ng__verbose_log: True
# ]]]
# .. envvar:: apt_cacher_ng__force_managed [[[
#
# Forbid downloads from locations that are directly specified in the user
# request, i.e. all downloads must be processed by the preconfigured remapping
# backends.
# Set to ``False`` by default to allow to download other repositories via the proxy like
# `download.owncloud.org <https://download.owncloud.org/download/repositories/>`_.
apt_cacher_ng__force_managed: False
# ]]]
# .. envvar:: apt_cacher_ng__expiration_threshold [[[
#
# Days before considering an unreferenced file expired (to be deleted).
#
# .. warning:: If the value is set too low and particular index files are not
# available for some days (mirror downtime) then there is a risk of removal of
# still useful package files.
#
apt_cacher_ng__expiration_threshold: 4
# ]]]
# .. envvar:: apt_cacher_ng__expiration_abort_on_problems [[[
#
# Stop expiration when a critical problem appears, issue like a failed update
# of an index file in the preparation step.
#
# .. warning:: Don't set this option to zero or empty without considering possible
# consequences like a sudden and complete cache data loss.
#
apt_cacher_ng__expiration_abort_on_problems: 'default'
# ]]]
# .. envvar:: apt_cacher_ng__dns_cache_seconds [[[
#
# There is a small in-memory cache for DNS resolution data, expired by
# this timeout (in seconds). Internal caching is disabled if set to a value
# less than zero.
apt_cacher_ng__dns_cache_seconds: 1800
# ]]]
# .. envvar:: apt_cacher_ng__log_submitted_origin [[[
#
# Trust the downstream HTTP proxy and log the X-Forwarded-For header as the
# client IP address.
apt_cacher_ng__log_submitted_origin: True
# ]]]
# .. envvar:: apt_cacher_ng__user_agent [[[
#
# The version string reported to the peer, to be displayed as HTTP client (and
# version) in the logs of the mirror.
#
# .. warning:: Expect side effects! Some archives use this header to guess
# capabilities of the client (i.e. allow redirection and/or https links) and
# change their behaviour accordingly but ACNG might not support the expected
# features.
#
# Default is the compiled in UserAgent: Yet Another HTTP Client/1.2.3p4
apt_cacher_ng__user_agent: 'default'
# ]]]
# .. envvar:: apt_cacher_ng__recompress_bz2 [[[
#
# In some cases the Import and Expiration tasks might create fresh volatile
# data for internal use by reconstructing them using patch files. This
# by-product might be recompressed with bzip2 and with some luck the resulting
# file becomes identical to the ``*.bz2`` file on the server which can be used by
# APT when requesting a complete version of this file.
# The downside of this feature is higher CPU load on the server during
# the maintenance tasks, and the outcome might have not much value in a LAN
# where all clients update their data often and regularly and therefore usually
# don't need the full version of the index file.
apt_cacher_ng__recompress_bz2: False
# ]]]
# .. envvar:: apt_cacher_ng__custom [[[
#
# Configuration block for Apt-Cacher NG for additional configuration for
# example custom remap settings.
apt_cacher_ng__custom: ''
# ]]]
# ]]]
# Network accessibility [[[
# -------------------------
# .. envvar:: apt_cacher_ng__allow [[[
#
# Allow access to Apt-Cacher NG from specified IP addresses or CIDR networks.
# If not specified, allows access from all networks.
apt_cacher_ng__allow: []
# ]]]
# .. envvar:: apt_cacher_ng__group_allow [[[
#
# Allow access to Apt-Cacher NG from specified IP addresses or CIDR networks.
# If not specified, allows access from all networks.
apt_cacher_ng__group_allow: []
# ]]]
# .. envvar:: apt_cacher_ng__host_allow [[[
#
# Allow access to Apt-Cacher NG from specified IP addresses or CIDR networks.
# If not specified, allows access from all networks.
apt_cacher_ng__host_allow: []
# ]]]
# .. envvar:: apt_cacher_ng__interfaces [[[
#
# List of network interfaces from which to allow access to Apt-Cacher NG.
# If not specified, allows access from all interfaces.
apt_cacher_ng__interfaces: []
# ]]]
# ]]]
# Role-dependent configuration [[[
# --------------------------------
# .. envvar:: apt_cacher_ng__etc_services__dependent_list [[[
#
# Configuration for the :ref:`debops.etc_services` role which registers port
# numbers for Apt-Cacher NG.
apt_cacher_ng__etc_services__dependent_list:
- name: 'acng'
port: '{{ apt_cacher_ng__port }}'
comment: 'Apt-Cacher NG caching proxy server'
delete: '{{ apt_cacher_ng__deploy_state != "present" }}'
# ]]]
# .. envvar:: apt_cacher_ng__apt_preferences__dependent_list [[[
#
# Configuration for the :ref:`debops.apt_preferences` role.
apt_cacher_ng__apt_preferences__dependent_list: []
# ]]]
# .. envvar:: apt_cacher_ng__ferm__dependent_rules [[[
#
# Configuration for :command:`ferm` firewall. It should be added when
# :ref:`debops.ferm` role is used to configure Apt-Cacher NG firewall rules.
apt_cacher_ng__ferm__dependent_rules:
- type: 'accept'
dport: [ 'acng' ]
saddr: '{{ (apt_cacher_ng__allow | d([]) | list) +
(apt_cacher_ng__group_allow | d([]) | list) +
(apt_cacher_ng__host_allow | d([]) | list) }}'
accept_any: True
interface: '{{ apt_cacher_ng__interfaces }}'
weight: '40'
by_role: 'debops.apt_cacher_ng'
name: 'http_proxy'
rule_state: '{{ apt_cacher_ng__deploy_state }}'
# ]]]
# .. envvar:: apt_cacher_ng__apparmor__dependent_config [[[
#
# Configuration for the ``debops-contrib.apparmor`` role.
apt_cacher_ng__apparmor__dependent_config:
'usr.sbin.apt-cacher-ng':
## Seems this change is not possible thought the ``@{APT_CACHE_DIR}``
## variable without changing the profile file directly?
- comment: 'Allow Apt-Cacher-Ng access to the cache directory'
by_role: 'debops.apt_cacher_ng'
delete: '{{ apt_cacher_ng__deploy_state != "present" }}'
rules:
- '{{ apt_cacher_ng__cache_dir }}/ r'
- '{{ apt_cacher_ng__cache_dir }}/** rw'
# ]]]
# .. envvar:: apt_cacher_ng__upstream_servers [[[
#
# List of upstream :program:`nginx` proxy servers.
apt_cacher_ng__upstream_servers:
- 'localhost:{{ apt_cacher_ng__port }}'
# ]]]
# .. envvar:: apt_cacher_ng__nginx__upstream [[[
#
# The :program:`nginx` upstream configuration, managed by :ref:`debops.nginx` role.
apt_cacher_ng__nginx__upstream:
enabled: True
name: 'apt-cacher-ng'
server: '{{ apt_cacher_ng__upstream_servers }}'
# ]]]
# .. envvar:: apt_cacher_ng__nginx__servers [[[
#
# List of :program:`nginx` server configurations managed by the
# :ref:`debops.nginx` role.
# There is a separate configuration for HTTP and HTTPS
# connections to allow access for hosts without SSL support installed.
apt_cacher_ng__nginx__servers:
- by_role: 'debops.apt_cacher_ng'
name: [ '{{ apt_cacher_ng__fqdn }}' ]
filename: 'debops.apt_cacher_ng_http'
enabled: True
allow: '{{ apt_cacher_ng__allow + apt_cacher_ng__group_allow + apt_cacher_ng__host_allow }}'
ssl: False
webroot_create: False
type: 'proxy'
proxy_pass: 'http://apt-cacher-ng'
proxy_options: |
if ($request_uri !~ "^/.*(\.js|\.css|\.html|\.ico)(.*)?$") {
rewrite ^/(.*)$ /$host/$1 break;
}
proxy_redirect off;
proxy_buffering off;
options: |
location ~ /acng-report.html {
return 307 https://$host$request_uri;
}
- by_role: 'debops.apt_cacher_ng'
name: [ '{{ apt_cacher_ng__fqdn }}' ]
filename: 'debops.apt_cacher_ng_https'
enabled: True
allow: '{{ apt_cacher_ng__allow + apt_cacher_ng__group_allow + apt_cacher_ng__host_allow }}'
state: '{{ "present" if (ansible_local.pki | d()) else "absent" }}'
listen: False
webroot_create: False
type: 'proxy'
proxy_pass: 'http://apt-cacher-ng'
proxy_options: |
if ($request_uri !~ "^/.*(\.js|\.css|\.html|\.ico)(.*)?$") {
rewrite ^/(.*)$ /$host/$1 break;
}
proxy_redirect off;
proxy_buffering off;
# ]]]
# ]]]
# ]]]

View file

@ -0,0 +1,33 @@
---
# Copyright (C) 2016-2017,2021 Robin Schneider <ypid@riseup.net>
# Copyright (C) 2016-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: 'Robin Schneider'
description: 'Install and manage the caching HTTP proxy Apt-Cacher NG'
company: 'DebOps'
license: 'GPL-3.0-only'
min_ansible_version: '2.0.0'
platforms:
- name: 'Ubuntu'
versions: [ 'all' ]
- name: 'Debian'
versions: [ 'all' ]
galaxy_tags:
- system
- proxy
- caching
- packages
- apt

View file

@ -0,0 +1,106 @@
---
# Copyright (C) 2016-2017,2021 Robin Schneider <ypid@riseup.net>
# Copyright (C) 2022 David Härdeman <david@hardeman.nu>
# Copyright (C) 2016-2022 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: Disable autoconfiguration
ansible.builtin.debconf:
name: 'apt-cacher-ng'
question: 'apt-cacher-ng/gentargetmode'
vtype: 'select'
value: 'No automated setup'
when: apt_cacher_ng__deploy_state == 'present'
- name: Add/remove configuration file diversions
debops.debops.dpkg_divert:
path: '{{ item.path }}'
state: '{{ "present" if apt_cacher_ng__deploy_state == "present"
else "absent" }}'
delete: True
loop: '{{ apt_cacher_ng__configuration_files }}'
when: item.divert | d(True)
- name: Install/remove packages
ansible.builtin.package:
name: '{{ q("flattened", apt_cacher_ng__base_packages) }}'
state: '{{ "present" if apt_cacher_ng__deploy_state == "present"
else "absent" }}'
register: apt_cacher_ng__register_packages
until: apt_cacher_ng__register_packages is succeeded
- name: Generate configuration files
ansible.builtin.template:
src: '{{ item.src | d(item.path | regex_replace("^/", "")) }}.j2'
dest: '{{ item.path }}'
owner: '{{ item.owner | d("root") }}'
group: '{{ item.group | d("root") }}'
mode: '{{ item.mode | d("0640") }}'
loop: '{{ apt_cacher_ng__configuration_files }}'
notify: [ 'Restart apt-cacher-ng' ]
when: apt_cacher_ng__deploy_state == 'present'
- name: Create the cache directory
ansible.builtin.file:
state: 'directory'
path: '{{ apt_cacher_ng__cache_dir }}'
owner: '{{ apt_cacher_ng__cache_dir_owner }}'
group: '{{ apt_cacher_ng__cache_dir_group }}'
mode: '{{ apt_cacher_ng__dir_perms }}'
when: apt_cacher_ng__deploy_state == 'present'
- name: Lazy check cache directory permissions
ansible.builtin.file:
state: 'file'
path: '{{ apt_cacher_ng__cache_dir }}/_expending_damaged'
owner: '{{ apt_cacher_ng__cache_dir_owner }}'
group: '{{ apt_cacher_ng__cache_dir_group }}'
mode: '{{ apt_cacher_ng__file_perms }}'
failed_when: False
register: apt_cacher_ng__register_cache_perms
when: (apt_cacher_ng__deploy_state == 'present' and
apt_cacher_ng__cache_dir_enforce_permissions == 'lazy')
# Note: doing this using native Ansible tasks is too slow
- name: Change cache directory permissions # noqa no-free-form
ansible.builtin.shell: |
chown --recursive {{ apt_cacher_ng__cache_dir_owner }}:{{ apt_cacher_ng__cache_dir_group }} .
find . -type d -exec chmod {{ apt_cacher_ng__dir_perms }} {} \;
find . -type f -exec chmod {{ apt_cacher_ng__file_perms }} {} \;
args:
chdir: '{{ apt_cacher_ng__cache_dir }}'
register: apt_cacher_ng__register_chmod
changed_when: apt_cacher_ng__register_chmod.changed | bool
when: (apt_cacher_ng__deploy_state == 'present' and
(apt_cacher_ng__cache_dir_enforce_permissions == "strict" or
(apt_cacher_ng__cache_dir_enforce_permissions == "lazy" and
apt_cacher_ng__register_cache_perms is changed)))
- name: Enable/disable service
ansible.builtin.service:
name: 'apt-cacher-ng'
state: '{{ "started" if apt_cacher_ng__enabled | d(True) else "stopped" }}'
enabled: '{{ True if apt_cacher_ng__enabled | d(True) else False }}'
when: apt_cacher_ng__deploy_state == 'present'
- name: Remove configuration files
ansible.builtin.file:
path: '{{ item.path }}'
state: 'absent'
loop: '{{ apt_cacher_ng__configuration_files }}'
when: (apt_cacher_ng__deploy_state in ['absent', 'purge'] and
not item.divert | d(True))
- name: Remove the cache directory
ansible.builtin.file:
path: '{{ apt_cacher_ng__cache_dir }}'
state: 'absent'
when: apt_cacher_ng__deploy_state == 'purge'

View file

@ -0,0 +1,590 @@
{# Copyright (C) 2016-2017,2021 Robin Schneider <ypid@riseup.net>
# Copyright (C) 2016-2017,2021 DebOps <https://debops.org/>
# SPDX-License-Identifier: GPL-3.0-only
#}
# {{ ansible_managed }}
#
# IMPORTANT NOTE:
#
# THIS FILE IS MAYBE JUST ONE OF MANY CONFIGURATION FILES IN THIS DIRECTORY.
# SETTINGS MADE IN OTHER FILES CAN OVERRIDE VALUES THAT YOU CHANGE HERE. GO
# LOOK FOR OTHER CONFIGURATION FILES! CHECK THE MANUAL AND INSTALLATION NOTES
# (like README.Debian) FOR MORE DETAILS!
#
# This is a configuration file for apt-cacher-ng, a smart caching proxy for
# software package downloads. It's supposed to be in a directory specified by
# the -c option of apt-cacher-ng, see apt-cacher-ng(8) for details.
# RULES:
# - letter case in variable names does not matter
# - names and values are separated by colon or equals sign
# - for boolean variables, zero means false, non-zero means true
# - "default value" means built-in (!) defaults, i.e. something which the
# program uses if the option is not set here or in other config files.
# That value might be explicitly mentioned in the description. Where it is
# not, there is no reason to assume any of the examples to be the default
# value! In doubt, use acngtool to query the value of the particular variable.
# Storage directory for downloaded data and related maintenance activity.
#
# Note: When the value for CacheDir is changed, change the file
# /lib/systemd/system/apt-cacher-ng.service too
#
CacheDir: {{ apt_cacher_ng__cache_dir }}
# Log file directory, can be set empty to disable logging
#
LogDir: {{ apt_cacher_ng__log_dir }}
# A place to look for additional configuration and resource files if they are not
# found in the configuration directory
#
SupportDir: {{ apt_cacher_ng__support_dir }}
# TCP server port for incoming http (or HTTP proxy) connections.
# Can be set to 9999 to emulate apt-proxy. Value of 0 turns off TCP server
# (SocketPath must be set in this case).
#
Port:{{ apt_cacher_ng__port }}
# Addresses or hostnames to listen on. Multiple addresses must be separated by
# spaces. Each entry must be an exact local address which is associated with a
# local interface. DNS resolution is performed using getaddrinfo(3) for all
# available protocols (IPv4, IPv6, ...). Using a protocol specific format will
# create binding(s) only on protocol specific socket(s), e.g. 0.0.0.0 will
# listen only to IPv4. The endpoint can also be specified as host:port (or
# [ipv6-address]:port) which allows binding on non-standard ports (Port
# directive is ignored in this case).
#
# Default: listens on all interfaces and protocols
#
# BindAddress: localhost 192.168.7.254 publicNameOnMainInterface
{{ ("BindAddress: " + apt_cacher_ng__bind_address | join(" ")) if apt_cacher_ng__bind_address else "" }}
# The specification of another HTTP proxy which shall be used for downloads.
# It can include user name and password but see the manual for limitations.
#
# Default: uses direct connection
#
# Proxy: http://www-proxy.example.net:3128
# Proxy: https://username:proxypassword@proxy.example.net:3129
{{ ("Proxy: " + apt_cacher_ng__proxy) if apt_cacher_ng__proxy else "" }}
# Repository remapping. See manual for details.
# In this example, some backends files might be generated during package
# installation using information collected on the system.
# Examples:
Remap-debrep: file:deb_mirror*.gz /debian ; file:backends_debian # Debian Archives
Remap-uburep: file:ubuntu_mirrors /ubuntu ; file:backends_ubuntu # Ubuntu Archives
Remap-klxrep: file:kali_mirrors /kali ; file:backends_kali # Kali Linux Archives
Remap-cygwin: file:cygwin_mirrors /cygwin # ; file:backends_cygwin # incomplete, please create this file or specify preferred mirrors here
Remap-sfnet: file:sfnet_mirrors # ; file:backends_sfnet # incomplete, please create this file or specify preferred mirrors here
Remap-alxrep: file:archlx_mirrors /archlinux # ; file:backend_archlx # Arch Linux
Remap-fedora: file:fedora_mirrors # Fedora Linux
Remap-epel: file:epel_mirrors # Fedora EPEL
Remap-slrep: file:sl_mirrors # Scientific Linux
Remap-gentoo: file:gentoo_mirrors.gz /gentoo ; file:backends_gentoo # Gentoo Archives
Remap-secdeb: security.debian.org security.debian.org/debian-security deb.debian.org/debian-security /debian-security ; deb.debian.org/debian-security security.debian.org
# Virtual page accessible in a web browser to see statistics and status
# information, i.e. under http://localhost:3142/acng-report.html
# NOTE: This option must be configured to run maintenance jobs (even when used
# via acngtool in cron scripts). The AdminAuth option can be used to restrict
# access to sensitive areas on that page.
#
# Default: not set, should be set by the system administrator
#
ReportPage: acng-report.html
# Socket file for accessing through local UNIX socket instead of TCP/IP. Can be
# used with inetd (via bridge tool in.acng from apt-cacher-ng package), is also
# used internally for administrative purposes.
#
# Default: /run/apt-cacher-ng/socket
#
# SocketPath: /var/run/apt-cacher-ng/socket
# If set to 1, makes log files be written to disk on every new line. Default
# is 0, buffers are flushed after the client disconnects. Technically,
# it's a convenience alias for the Debug option, see below for details.
#
# UnbufferLogs: 0
# Enables extended client information in log entries. When set to 0, only
# activity type, time and transfer sizes are logged.
#
# VerboseLog: 1
VerboseLog: {{ "1" if apt_cacher_ng__verbose_log | bool else "0" }}
# Don't detach from the starting console.
#
# ForeGround: 0
# Store the pid of the daemon process in the specified text file.
# Default: disabled
#
# PidFile: /var/run/apt-cacher-ng/pid
# Forbid outgoing connections and work without an internet connection or
# respond with 503 error where it's not possible.
#
# Offlinemode: 0
Offlinemode: {{ "1" if apt_cacher_ng__offline_mode | bool else "0" }}
# Forbid downloads from locations that are directly specified in the user
# request, i.e. all downloads must be processed by the preconfigured remapping
# backends (see above).
#
# ForceManaged: 0
ForceManaged: {{ "1" if apt_cacher_ng__force_managed | bool else "0" }}
# Days before considering an unreferenced file expired (to be deleted).
# WARNING: if the value is set too low and particular index files are not
# available for some days (mirror downtime) then there is a risk of removal of
# still useful package files.
#
# ExThreshold: 4
{{ ("ExTreshold: " + apt_cacher_ng__expiration_threshold | string) if (apt_cacher_ng__expiration_threshold != "default") else "" }}
# If the expiration is run daily, it sometimes does not make much sense to do
# it because the expected changes (i.e. removal of expired files) don't justify
# the extra processing time or additional downloads for expiration operation
# itself. This discrepancy might be especially worse if the local client
# installations are small or are rarely updated but the daily changes of
# the remote archive metadata are heavy.
#
# The following option enables a possible trade-off: the expiration run is
# suppressed until a certain amount of data has been downloaded through
# apt-cacher-ng since the last expiration execution (which might indicate that
# packages were replaced with newer versions).
#
# The number can have a suffix (k,K,m,M for Kb,KiB,Mb,MiB)
#
# ExStartTradeOff: 500m
# Stop expiration when a critical problem appears, issue like a failed update
# of an index file in the preparation step.
#
# WARNING: don't set this option to zero or empty without considering possible
# consequences like a sudden and complete cache data loss.
#
# ExAbortOnProblems: 1
{{ ("ExAbortOnProblems: " + apt_cacher_ng__expiration_abort_on_problems | string) if (apt_cacher_ng__expiration_abort_on_problems != "default") else "" }}
# Number of failed nightly expiration runs which are considered acceptable and
# do not trigger an error notification to the admin (e.g. via daily cron job)
# before the (day) count is reached. Might be useful with whacky internet
# connections.
#
# Default: a guessed value, 1 if ExThreshold is 5 or more, 0 otherwise.
#
# ExSuppressAdminNotification: 1
# Modify file names to work around limitations of some file systems.
# WARNING: experimental feature, subject to change
#
# StupidFs: 0
# Experimental feature for apt-listbugs: pass-through SOAP requests and
# responses to/from bugs.debian.org.
# Default: guessed value, true unless ForceManaged is enabled
#
# ForwardBtsSoap: 1
# There is a small in-memory cache for DNS resolution data, expired by
# this timeout (in seconds). Internal caching is disabled if set to a value
# less than zero.
#
# DnsCacheSeconds: 1800
{{ ("DnsCacheSeconds: " + apt_cacher_ng__dns_cache_seconds | string) if (apt_cacher_ng__dns_cache_seconds != "default") else "" }}
###############################################################################
#
# WARNING: don't modify thread and file matching parameters without a clear
# idea of what is happening behind the scene!
#
# Max. count of connection threads kept ready (for faster response in the
# future). Should be a sane value between 0 and average number of connections,
# and depend on the amount of spare RAM.
# MaxStandbyConThreads: 8
#
# Hard limit of active thread count for incoming connections, i.e. operation
# is refused when this value is reached (below zero = unlimited).
# MaxConThreads: -1
#
# Timeout for a forced disconnect in cases where a client connection is about
# to be closed but remote refuses to confirm the disconnect request. Setting
# this to a lower value mitigates the effects of resource starvation in case of
# a DOS attack but increases the risk of failing to flush the remaining portion
# of data.
# DisconnectTimeout: 15
# By default, if a remote suddenly reconnects, ACNG tries at least two times to
# redownload from the same or different location (if known).
# DlMaxRetries: 2
# Pigeonholing files (like static vs. volatile contents) is done by (extended)
# regular expressions.
#
# The following patterns are available for the purposes detailed, where
# the latter takes precedence over the former:
# - «PFilePattern» for static data that doesn't change silently on the server.
# - «VFilePattern» for volatile data that may change like every hour. Files
# that match both PFilePattern and VfilePattern will be treated as volatile.
# - Static data with file names that match VFilePattern may be overridden being
# treated as volatile by making it match the special static data pattern,
# «SPfilePattern».
# - «SVfilePattern» or the "special volatile data" pattern is for the
# convenience of specifying any exceptions to matches with SPfilePattern,
# for cases where data must still be treated as volatile.
# - «WfilePattern» specifies a "whitelist pattern" for the regular expiration
# job, telling it to keep the files even if they are not referenced by
# others, like crypto signatures with which clients begin their downloads.
#
# There are two versions. The pattern variables mentioned above should not be
# set without good reason, because they would override the built-in defaults
# (that might impact updates to future versions of apt-cacher-ng). There are
# also versions of those patterns ending with Ex, which may be modified by the
# local administrator. They are evaluated in addition to the regular patterns
# at runtime.
#
# To see examples of the expected syntax, run: apt-cacher-ng -p debug=1
#
# PfilePatternEx:
# VfilePatternEx:
# SPfilePatternEx:
# SVfilePatternEx:
# WfilePatternEx:
#
###############################################################################
# A bitmask type value declaring the logging verbosity and behavior of the error
# log writing. Non-zero value triggers at least faster log file flushing.
#
# Some higher bits only working with a special debug build of apt-cacher-ng,
# see the manual for details.
#
# WARNING: this can write significant amount of data into apt-cacher.err logfile.
#
# Default: 0
#
# Debug:3
{{ ("Debug: " + apt_cacher_ng__debug | string) if (apt_cacher_ng__debug != "") else "" }}
# Usually, general purpose proxies like Squid expose the IP address of the
# client user to the remote server using the X-Forwarded-For HTTP header. This
# behaviour can be optionally turned on with the Expose-Origin option.
#
# ExposeOrigin: 0
# When logging the originating IP address, trust the information supplied by
# the client in the X-Forwarded-For header.
#
# LogSubmittedOrigin: 0
LogSubmittedOrigin: {{ "1" if apt_cacher_ng__log_submitted_origin | bool else "0" }}
# The version string reported to the peer, to be displayed as HTTP client (and
# version) in the logs of the mirror.
#
# WARNING: Expect side effects! Some archives use this header to guess
# capabilities of the client (i.e. allow redirection and/or https links) and
# change their behaviour accordingly but ACNG might not support the expected
# features.
#
# Default:
#
# UserAgent: Yet Another HTTP Client/1.2.3p4
{{ ("UserAgent: " + apt_cacher_ng__user_agent) if (apt_cacher_ng__user_agent != "default") else "" }}
# In some cases the Import and Expiration tasks might create fresh volatile
# data for internal use by reconstructing them using patch files. This
# by-product might be recompressed with bzip2 and with some luck the resulting
# file becomes identical to the *.bz2 file on the server which can be used by
# APT when requesting a complete version of this file.
# The downside of this feature is higher CPU load on the server during
# the maintenance tasks, and the outcome might have not much value in a LAN
# where all clients update their data often and regularly and therefore usually
# don't need the full version of the index file.
#
# RecompBz2: 0
RecompBz2: {{ "1" if apt_cacher_ng__recompress_bz2 else "0" }}
# Network timeout for outgoing connections, in seconds.
#
# NetworkTimeout: 40
{{ ("NetworkTimeout: " + apt_cacher_ng__network_timeout | string) if (apt_cacher_ng__network_timeout != "") else "" }}
# Fast fallback timeout, in seconds. This is the time to wait before
# alternative target addresses for a client connection are tried, which can be
# useful for quick fallback to IPv4 in case of whacky IPv6 configuration.
#
# FastTimeout = 4
# Sometimes it makes sense to not store the data in cache and just return the
# package data to client while it comes in. The following DontCache* parameters
# can enable this behaviour for certain URL types. The tokens are extended
# regular expressions which the URLs are evaluated against.
#
# DontCacheRequested is applied to the URL as it comes in from the client.
# Example: exclude packages built with kernel-package for x86
# DontCacheRequested: linux-.*_10\...\.Custo._i386
# Example usecase: exclude popular private IP ranges from caching
# DontCacheRequested: 192.168.0 ^10\..* 172.30
#
# DontCacheResolved is applied to URLs after mapping to the target server. If
# multiple backend servers are specified then it's only matched against the
# download link for the FIRST possible source (due to implementation limits).
#
# Example usecase: all Ubuntu stuff comes from a local mirror (specified as
# backend), don't cache it again:
# DontCacheResolved: ubuntumirror.local.net
#
# DontCache directive sets (overrides) both, DontCacheResolved and
# DontCacheRequested. Provided for convenience, see those directives for
# details.
#
# Example:
# DontCache: .*.local.university.int
# Default permission set of freshly created files and directories, as octal
# numbers (see chmod(1) for details).
# Can by limited by the umask value (see umask(2) for details) if it's set in
# the environment of the starting shell, e.g. in apt-cacher-ng init script or
# in its configuration file.
#
# DirPerms: 00755
{{ ("DirPerms: " + apt_cacher_ng__dir_perms) if (apt_cacher_ng__dir_perms != "") else "" }}
# FilePerms: 00664
{{ ("FilePerms: " + apt_cacher_ng__file_perms) if (apt_cacher_ng__file_perms != "") else "" }}
# It's possible to use use apt-cacher-ng as a regular web server with a limited
# feature set, i.e. directory browsing, downloads of any files, Content-Type
# based on /etc/mime.types, but without sorting, CGI execution, index page
# redirection and other funny things.
# To get this behavior, mappings between virtual directories and real
# directories on the server must be defined with the LocalDirs directive.
# Virtual and real directories are separated by spaces, multiple pairs are
# separated by semi-colons. Real directories must be absolute paths.
# NOTE: Since the names of that key directories share the same namespace as
# repository names (see Remap-...) it is administrator's job to avoid conflicts
# between them or explicitly create them.
#
# LocalDirs: woo /data/debarchive/woody ; hamm /data/debarchive/hamm
LocalDirs: acng-doc /usr/share/doc/apt-cacher-ng
# Precache a set of files referenced by specified index files. This can be used
# to create a partial mirror usable for offline work. There are certain limits
# and restrictions on the path specification, see manual and the cache control
# web site for details. A list of (maybe) relevant index files could be
# retrieved via "apt-get --print-uris update" on a client machine.
#
# Example:
# PrecacheFor: debrep/dists/unstable/*/source/Sources* debrep/dists/unstable/*/binary-amd64/Packages*
# Arbitrary set of data to append to request headers sent over the wire. Should
# be a well formatted HTTP headers part including newlines (DOS style) which
# can be entered as escape sequences (\r\n).
#
# RequestAppendix: X-Tracking-Choice: do-not-track\r\n
# Specifies the IP protocol families to use for remote connections. Order does
# matter, first specified are considered first. Possible combinations:
# v6 v4
# v4 v6
# v6
# v4
# Default: use native order of the system's TCP/IP stack, influenced by the
# BindAddress value.
#
# ConnectProto: v6 v4
{% if apt_cacher_ng__connect_protocol | length >= 1 %}
ConnectProto: {{ apt_cacher_ng__connect_protocol | join(" ") }}
{% endif %}
# Regular expiration algorithm finds package files which are no longer listed
# in any index file and removes them of them after a safety period.
# This option allows to keep more versions of a package in the cache after
# the safety period is over.
#
# KeepExtraVersions: 0
# Optionally uses TCP access control provided by libwrap, see hosts_access(5)
# for details. Daemon name is apt-cacher-ng.
#
# Default: guessed on startup by looking for explicit mention of apt-cacher-ng
# in /etc/hosts.allow or /etc/hosts.deny files.
#
# UseWrap: 0
# If many machines from the same local network attempt to update index files
# (apt-get update) at nearly the same time, the known state of these index file
# is temporarily frozen and multiple requests receive the cached response
# without contacting the remote server again. This parameter (in seconds)
# specifies the length of this period before these (volatile) files are
# considered outdated.
# Setting this value too low transfers more data and increases remote server
# load, setting this too high (more than a couple of minutes) increases the
# risk of delivering inconsistent responses to the clients.
#
# FreshIndexMaxAge: 27
# Usually the users are not allowed to specify custom TCP ports of remote
# mirrors in the requests, only the default HTTP port can be used (as
# workaround, proxy administrator can create Remap- rules with custom ports).
# This restriction can be disabled by specifying a list of allowed ports or 0
# for any port.
#
# AllowUserPorts: 80
# Normally the HTTP redirection responses are forwarded to the original caller
# (i.e. APT) which starts a new download attempt from the new URL. This
# solution is ok for client configurations with proxy mode but doesn't work
# well with configurations using URL prefixes in sources.list. To work around
# this the server can restart its own download with a redirection URL,
# configured with the following option. The downside is that this might be used
# to circumvent download source policies by malicious users.
# The RedirMax option specifies how many such redirects the server is allowed
# to follow per request, 0 disables the internal redirection.
# Default: guessed on startup, 0 if ForceManaged is used and 5 otherwise.
#
# RedirMax: 5
# There some broken HTTP servers and proxy servers in the wild which don't
# support the If-Range header correctly and return incorrect data when the
# contents of a (volatile) file changed. Setting VfileUseRangeOps to zero
# disables Range-based requests while retrieving volatile files, using
# If-Modified-Since and requesting the complete file instead. Setting it to
# a negative value removes even If-Modified-Since headers.
#
# VfileUseRangeOps: 1
# Allow data pass-through mode for certain hosts when requested by the client
# using a CONNECT request. This is particularly useful to allow access to SSL
# sites (https proxying). The string is a regular expression which should cover
# the server name with port and must be correctly formatted and terminated.
# Examples:
# PassThroughPattern: private-ppa\.launchpad\.net:443$
# PassThroughPattern: .* # this would allow CONNECT to everything
#
# Default: ^(bugs\.debian\.org|changelogs\.ubuntu\.com):443$
# PassThroughPattern: ^(bugs\.debian\.org|changelogs\.ubuntu\.com):443$
# It's possible that an evil client requests a volatile file but does not
# retrieve the response and keeps the connection effectively stuck over
# many hours, blocking the particular file for other download attempts (which
# leads to not reporting file changes on server side to other users). In such
# case the file descriptor can be moved aside although this might reduce cache
# efficiency.
#
# Default time is based on the value of FreshIndexMaxAge with a safety factor.
#
# ResponseFreezeDetectTime: 60
# Keep outgoing connections alive and reuse them for later downloads from
# the same server as long as possible.
#
# ReuseConnections: 1
# Maximum number of requests sent in a batch to remote servers before the first
# response is expected. Using higher values can greatly improve average
# throughput depending on network latency and the implementation of remote
# servers. Makes most sense when also enabled on the client side, see apt.conf
# documentation for details.
#
# Default: 10 if ReuseConnections is set, 1 otherwise
#
# PipelineDepth: 10
# Path to the system directory containing trusted CA certificates used for
# outgoing connections, see OpenSSL documentation for details.
#
# CApath: /etc/ssl/certs
#
# Path to a single trusted trusted CA certificate used for outgoing
# connections, see OpenSSL documentation for details.
#
# CAfile:
# There are different ways to detect that an upstream proxy is broken and turn
# off its use and connect directly. The first is through a custom command -
# when it returns successfully, the proxy is used, otherwise not and the
# command will be rerun only after a specified period.
# Another way is to try to connect to the proxy first and detect a connection
# timeout. The connection will then be made without HTTP proxy for the life
# time of the particular download stream and it may also affect other other
# parallel downloads.
# NOTE: this operation modes are still experimental and are subject to change!
# Unwanted side effects may occur with multiple simultaneous user connections
# or with specific per-repository proxy settings.
#
# Shell command, default: not set. Executed with the default shell and
# permissions of the apt-cacher-ng's process user. Examples:
# /bin/ip route | grep -q 192.168.117
# /usr/sbin/arp | grep -q 00:22:1f:51:8e:c1
#
# OptProxyCheckCommand: ...
#
# Check interval, in seconds.
#
# OptProxyCheckInterval: 99
#
# Connection timeout in seconds, default: negative, means disabled.
#
# OptProxyTimeout: -1
# It's possible to limit the processing speed of download agents to set an
# overall download speed limit. Unit: KiB/s, Default: unlimited.
#
# MaxDlSpeed: 500
{{ ("MaxDlSpeed: " + apt_cacher_ng__max_download_speed_kib | string) if (apt_cacher_ng__max_download_speed_kib != "") else "" }}
# In special corner cases, download clients attempt to download random chunks
# of a files headers, i.e. the first kilobytes. The "don't get client stuck"
# policy converts this usually to a 200 response starting the body from the
# beginning but that confuses some clients. When this option is set to a
# certain value, this modifies the behaviour and allows to start a file
# download where the distance between available data and the specified range
# lies within that bounds. This can look like random lag for the user but
# should be harmless apart from that.
#
# MaxInresponsiveDlSize: 64000
# In mobile environments having an adhoc connection with a redirection to some
# id verification side, this redirect might damage the cache since the data is
# involuntarily stored as package data. There is a mechanism which attempts to
# detect a such situation and mitigate the mentioned effects by not storing the
# data and also dropping the DNS cache. The trigger is the occurrence of a
# specific SUBSTRING in the content type field of the final download target
# (i.e. the auth web site) and at least one followed redirection.
#
# BadRedirDetectMime: text/html
# When a BUS signal is received (typically on IO errors), a shell command can be
# executed before the daemon is terminated.
# Example:
# BusAction: ls -l /proc/$PPID/ | mail -s SIGBUS! root
# Only set this value for debugging purposes. It disables SSL security checks
# like strict host verification. 0 means no, any other value can have
# different meaning in the future.
#
# NoSSLChecks: 0
# Setting this value means: on file downloads from/via cache, tag relevant
# files. And when acngtool runs the shrink command, it will look at the day
# when the file was retrieved from cache last time (and not when it was
# originally downloaded).
#
# TrackFileUse: 0
# Controls preallocation of file system space where this feature is supported.
# This might reduce disk fragmentation and therefore improve later read
# performance. However, write performance can be reduced which could be
# exploited by malicious users.
# The value defines a size limit of how much to report to the OS as expected
# file size (starting from the beginning of the file).
# Set to zero to disable this feature completely. Default: one megabyte
#
# ReserveSpace: 1048576
{{ apt_cacher_ng__custom }}

View file

@ -0,0 +1,7 @@
{# Copyright (C) 2016-2017 Robin Schneider <ypid@riseup.net>
# Copyright (C) 2016-2017 DebOps <https://debops.org/>
# SPDX-License-Identifier: GPL-3.0-only
#}
# {{ ansible_managed }}
{{ apt_cacher_ng__upstream_mirror_debian }}

View file

@ -0,0 +1,7 @@
{# Copyright (C) 2016-2017 Robin Schneider <ypid@riseup.net>
# Copyright (C) 2016-2017 DebOps <https://debops.org/>
# SPDX-License-Identifier: GPL-3.0-only
#}
# {{ ansible_managed }}
{{ apt_cacher_ng__upstream_mirror_gentoo }}

View file

@ -0,0 +1,7 @@
{# Copyright (C) 2016-2017 Robin Schneider <ypid@riseup.net>
# Copyright (C) 2016-2017 DebOps <https://debops.org/>
# SPDX-License-Identifier: GPL-3.0-only
#}
# {{ ansible_managed }}
{{ apt_cacher_ng__upstream_mirror_ubuntu }}

View file

@ -0,0 +1,16 @@
{# Copyright (C) 2016-2017 Robin Schneider <ypid@riseup.net>
# Copyright (C) 2016-2017 DebOps <https://debops.org/>
# SPDX-License-Identifier: GPL-3.0-only
#}
# {{ ansible_managed }}
# This file contains confidential data and should be protected with file
# permissions from being read by untrusted users.
#
# NOTE: permissions are fixated with dpkg-statoverride on Debian systems.
# Read its manual page for details.
# Basic authentication with username and password, required to
# visit pages with administrative functionality. Format: username:password
AdminAuth: {{ apt_cacher_ng__user | default('admin') }}:{{ apt_cacher_ng__password | default('password') }}

View file

@ -0,0 +1,71 @@
{# Copyright (C) 2016-2017 Robin Schneider <ypid@riseup.net>
# Copyright (C) 2016-2017 DebOps <https://debops.org/>
# SPDX-License-Identifier: GPL-3.0-only
#}
<!DOCTYPE html>
<html lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
<meta http-equiv="X-UA-Compatible" content="IE=8" />
<meta name="MSSmartTagsPreventParsing" content="true" />
<title>Not Found or APT Reconfiguration required</title>
<link rel="stylesheet" type="text/css" href="/style.css" />
</head>
<body>
<table border=0 cellspacing=0 cellpadding=0 width=580 class="center">
<tr>
<td class="title" style="width:580px;">The requested page is not accessible.</td>
</tr>
<tr>
<td>
<div class="visarea" style="width:580px;text-align:left;"><br>
You attempted to browse the contents of a virtual HTTP repository.
However, the intended way of use is the configuration of APT and
related package management systems to retrieve the software packages
through this service.
<p>To configure APT for use of Apt-Cacher&nbsp;NG you need to...
<br>
<ul>
<li>
<b>EITHER:</b> Configure APT to use a HTTP proxy by specifying
it in apt.conf or related configuration files, see
apt.conf manual page for details. Server and Port need to match
the values used to visit this page. For example,
edit&nbsp;<i>/etc/apt/apt.conf</i> (or create a new file called like <i>/etc/apt/apt.conf.d/00aptproxy</i>) and add the line:
<p />
<div class="rawstyle">Acquire::http::Proxy "http://{{ apt_cacher_ng__fqdn }}";</div>
<p />
</li>
<li>
<b>OR:</b>
Edit the <i>/etc/apt/sources.list</i> file and edit the source lines
therein, replacing the mirror hostname with the hostname of this
server machine. For example:
<p /><div class="rawstyle" style="color:gray;">
deb http://ftp.debian.org/debian stable main contrib non-free<br>
deb-src http://ftp.debian.org/debian stable main contrib non-free<br>
deb https://get.docker.com/ubuntu docker main<br></div><p/>
becomes:<p/>
<div class="rawstyle">deb http://<span style="color:red">{{ apt_cacher_ng__fqdn }}/</span>ftp.debian.org/debian stable main contrib non-free<br/>
deb-src http://<span style="color:red">{{ apt_cacher_ng__fqdn }}/</span>ftp.debian.org/debian stable main contrib non-free<br/>
deb <span style="color:red">http://{{ apt_cacher_ng__fqdn }}//HTTPS///</span>get.docker.com/ubuntu docker main</div><p/>
Depending on the configuration, it might be possible to use a shortcut
for the base URLs without knowing the mirror, for example:<p/>
<div class="rawstyle">deb http://{{ apt_cacher_ng__fqdn }}/debian stable main contrib non-free</div><p/>
Ask your system administrator for details.
</li>
</ul>
<h3>Related links</h3>
<ul>
<li><a href="${cfg:ReportPage}">Statistics report and configuration page</a> for this Apt-Cacher NG installation</li>
<li><a href="https://www.unix-ag.uni-kl.de/~bloch/acng/">Project Homepage</a>
</ul>
${footer}
</div>
</td>
</tr>
</table>
</body>
</html>

View file

@ -0,0 +1,16 @@
---
# Copyright (C) 2016-2017 Robin Schneider <ypid@riseup.net>
# Copyright (C) 2016-2017 DebOps <https://debops.org/>
# SPDX-License-Identifier: GPL-3.0-only
# .. envvar:: apt_cacher_ng__apparmor__tunables_dependent
#
# Configuration for the ``debops-contrib.apparmor`` role.
#
# Example::
#
# apt_cacher_ng__apparmor__tunables_dependent: '@{APT_CACHE_DIR}+={{ apt_cacher_ng__cache_dir }}'
#
# Does not work. Error is: Variable was previously declared. Not sure if this is supposed to work?
# ``apt_cacher_ng__apparmor__dependent_config`` works without issues.
apt_cacher_ng__apparmor__tunables_dependent: ''

View file

@ -0,0 +1,20 @@
debops.apt_install - Manage APT packages using Ansible
Copyright (C) 2016-2017 Maciej Delmanowski <drybjed@gmail.com>
Copyright (C) 2016-2017 Robin Schneider <ypid@riseup.net>
Copyright (C) 2016-2017 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,435 @@
---
# .. vim: foldmarker=[[[,]]]:foldmethod=marker
# .. Copyright (C) 2016-2017 Maciej Delmanowski <drybjed@gmail.com>
# .. Copyright (C) 2016-2017 Robin Schneider <ypid@riseup.net>
# .. Copyright (C) 2016-2017 DebOps <https://debops.org/>
# .. SPDX-License-Identifier: GPL-3.0-only
# .. _apt_install__ref_defaults:
# debops.apt_install default variables [[[
# ========================================
# .. contents:: Sections
# :local:
#
# .. include:: ../../../../includes/global.rst
# Role configuration [[[
# ----------------------
# .. envvar:: apt_install__enabled [[[
#
# Enable or disable support for ``debops.apt_install`` role.
apt_install__enabled: True
# ]]]
# .. envvar:: apt_install__distribution [[[
#
# The variable that indicates host operating system distribution, used to
# conditionally select packages for installation.
apt_install__distribution: '{{ ansible_local.core.distribution
| d(ansible_lsb.id | d(ansible_distribution)) }}'
# ]]]
# .. envvar:: apt_install__distribution_release [[[
#
# The variable that indicates host distribution release, used to conditionally
# select packages for installation.
apt_install__distribution_release: '{{ ansible_local.core.distribution_release
| d(ansible_lsb.codename | d(ansible_distribution_release)) }}'
# ]]]
# .. envvar:: apt_install__archive_areas_map [[[
#
# A dictionary that maps different parts of the package archive to each
# distribution. By default the role expects all of the archives to be enabled.
apt_install__archive_areas_map:
'Debian': [ 'main', 'contrib', 'non-free' ]
'Ubuntu': [ 'main', 'restricted', 'universe', 'multiverse' ]
# ]]]
# .. envvar:: apt_install__archive_areas [[[
#
# List of package archive areas which are currently available. This list is
# used to conditionally enable packages for installation, depending on
# availability of a given archive area.
apt_install__archive_areas: '{{ ansible_local.apt.components
| d(apt_install__archive_areas_map[apt_install__distribution] | d([])) }}'
# ]]]
# .. envvar:: apt_install__condition_map [[[
#
# Definition of the values to compare :ref:`apt_install__all_packages` against.
# This map is used internally in the :envvar:`apt_install__all_packages` lookup
# template.
apt_install__condition_map:
'distribution': '{{ apt_install__distribution }}'
'release': '{{ apt_install__distribution_release }}'
'areas': '{{ apt_install__archive_areas }}'
# ]]]
# .. envvar:: apt_install__state [[[
#
# How the :command:`apt` Ansible module should install the selected packages:
#
# ``present``
# Only make sure that the packages on the list are present on the host.
#
# ``latest``
# Install the latest version of available packages, according to APT
# preferences.
#
# By default role will make sure to update the packages to their latest version
# on first run, and just keep the current version on subsequent runs.
apt_install__state: '{{ "present"
if (ansible_local | d() and ansible_local.apt_install | d() and
(ansible_local.apt_install.configured | d(True)) | bool)
else "latest" }}'
# ]]]
# .. envvar:: apt_install__no_kernel_hints [[[
#
# Enable or disable configuration of the hints about upgraded kernel requiring
# a reboot of the host, created by the :command:`needrestart` package. By
# default these hints will be disabled, which helps with non-interactive APT
# package installation done by Ansible.
apt_install__no_kernel_hints: '{{ True
if ("needrestart" in apt_install__conditional_whitelist_packages)
else False }}'
# ]]]
# .. envvar:: apt_install__recommends [[[
#
# Boolean variable that controls installation of recommended packages.
apt_install__recommends: False
# ]]]
# .. envvar:: apt_install__update_cache [[[
#
# Enable or disable APT cache updates.
apt_install__update_cache: True
# ]]]
# .. envvar:: apt_install__cache_valid_time [[[
#
# Amount of time between APT cache updates in seconds.
apt_install__cache_valid_time: '{{ ansible_local.core.cache_valid_time | d(60 * 60 * 24 * 7) }}'
# ]]]
# ]]]
# Debconf package configuration [[[
# --------------------------------
# These lists can be used to insert new values into the debconf database. This
# allow to overwrite some default answer before installing a package and avoid
# using ``dpkg-reconfigure`` to set the wanted answer.
# See :ref:`apt_install__ref_debconf` for more details.
# .. envvar:: apt_install__debconf [[[
#
# List of values to configure for all hosts in the Ansible inventory.
apt_install__debconf: []
# ]]]
# .. envvar:: apt_install__group_debconf [[[
#
# List of values to configure for hosts in specific Ansible
# inventory group.
apt_install__group_debconf: []
# ]]]
# .. envvar:: apt_install__host_debconf [[[
#
# List of values to configure for specific hosts in the Ansible
# inventory.
apt_install__host_debconf: []
# ]]]
# ]]]
# APT package lists [[[
# ---------------------
# The APT packages to install are split into multiple lists to easier
# modification. You can specify name of each package directly or use a YAML
# dictionary to better control when a package should be installed. See
# :ref:`apt_install__all_packages` for more details.
# .. envvar:: apt_install__base_packages [[[
#
# Default base packages to install.
apt_install__base_packages:
- 'ed'
- 'lsb-release'
- 'make'
- 'git'
- 'curl'
- 'rsync'
- 'bsdutils'
- 'acl'
# ]]]
# .. envvar:: apt_install__shell_packages [[[
#
# Command line creature comforts, when you need to login to the remote host.
apt_install__shell_packages:
- 'ncurses-term'
- 'tmux'
- 'less'
- 'file'
- 'psmisc'
- 'lsof'
- 'tree'
- 'htop'
- 'iftop'
- 'nload'
- 'nmon'
- 'mtr-tiny'
- 'mc'
# ]]]
# .. envvar:: apt_install__editor_packages [[[
#
# List of text editors to install.
apt_install__editor_packages:
# The role also sets ``vim.basic`` as the default editor via
# the :envvar:`apt_install__default_alternatives` variable.
# If you change this list, remember to update that variable as well,
# otherwise the playbook execution will break.
- 'vim'
# ]]]
# .. envvar:: apt_install__packages [[[
#
# List of APT packages to install on all hosts in Ansible inventory.
apt_install__packages: []
# ]]]
# .. envvar:: apt_install__group_packages [[[
#
# List of APT packages to install on hosts in a specific group in Ansible
# inventory.
apt_install__group_packages: []
# ]]]
# .. envvar:: apt_install__host_packages [[[
#
# List of APT packages to install on specific hosts in Ansible inventory.
apt_install__host_packages: []
# ]]]
# .. envvar:: apt_install__dependent_packages [[[
#
# List of APT packages to install for other Ansible roles, for usage as
# a dependent role.
apt_install__dependent_packages: []
# ]]]
# .. envvar:: apt_install__conditional_whitelist_packages [[[
#
# List of APT package names which will be used to compare against packages
# requested for installation. This list is exposed in the defaults so that you
# don't need to modify the conditional list below to enable or disable
# packages.
apt_install__conditional_whitelist_packages:
- 'irqbalance'
- 'uptimed'
- 'libpam-systemd'
- 'haveged'
- 'gnupg-curl'
- 'needrestart'
- 'open-vm-tools'
# ]]]
# .. envvar:: apt_install__conditional_packages [[[
#
# List of APT packages installed under certain conditions.
apt_install__conditional_packages:
- name: 'irqbalance'
whitelist: '{{ apt_install__conditional_whitelist_packages }}'
state: '{{ "present"
if (ansible_processor_cores >= 2 and
(ansible_virtualization_role is undefined or
ansible_virtualization_role not in ["guest"]))
else "absent" }}'
- name: 'uptimed'
whitelist: '{{ apt_install__conditional_whitelist_packages }}'
state: '{{ "present"
if (ansible_virtualization_role is undefined or
ansible_virtualization_role not in ["guest"])
else "absent" }}'
- name: 'libpam-systemd'
whitelist: '{{ apt_install__conditional_whitelist_packages }}'
state: '{{ "present" if (ansible_service_mgr == "systemd") else "absent" }}'
- name: 'haveged'
# KVM is capable of providing entropy to guests however this needs to be
# configured on the hypervisor host and thus can not always be done if one
# only controls a guest.
whitelist: '{{ apt_install__conditional_whitelist_packages }}'
state: '{{ "present"
if (ansible_virtualization_role | d("guest") in ["guest"] and
ansible_virtualization_type | d("unknown") not in ["lxc", "openvz"] and
not (ansible_local | d() and ansible_local.apt_install | d() and
ansible_local.apt_install.have_virtual_rng | d(False) | bool))
else "absent" }}'
- name: 'gnupg-curl'
# This package is needed when you want to access HKPS keyservers.
whitelist: '{{ apt_install__conditional_whitelist_packages }}'
state: '{{ "present"
if ansible_distribution_release in
["trusty", "xenial"]
else "absent" }}'
- name: 'needrestart'
whitelist: '{{ apt_install__conditional_whitelist_packages }}'
state: 'present'
- name: 'open-vm-tools'
whitelist: '{{ apt_install__conditional_whitelist_packages }}'
state: '{{ "present"
if (ansible_virtualization_role | d("guest") in ["guest"] and
ansible_virtualization_type | d("unknown") in ["VMware"])
else "absent" }}'
# ]]]
# .. envvar:: apt_install__firmware_packages [[[
#
# Certain systems require free or non-free firmware for correct operation. This
# list of packages will ensure that the required firmware is installed.
apt_install__firmware_packages:
- name: 'amd64-microcode'
# Non-free microcode firmware for AMD CPUs. These patches mitigate security
# issues like Spectre and fix other types of incorrect processor behaviour.
# The package only needs to be installed on physical machines. To protect
# virtual machines, be sure to set the correct CPU flags in QEMU:
# https://www.qemu.org/docs/master/system/target-i386.html#important-cpu-features-for-intel-x86-hosts
distribution: [ 'Debian', 'Devuan', 'Ubuntu' ]
areas: '{{ ["main"]
if ansible_distribution in ["Ubuntu"]
else ["non-free"] }}'
state: '{{ "present"
if ansible_virtualization_role == "host" and
"AuthenticAMD" in ansible_processor
else "absent" }}'
- name: 'firmware-linux-free'
distribution: [ 'Debian' ]
state: '{{ "present" if (ansible_form_factor in ["Rack Mount Chassis"])
and (ansible_virtualization_role is undefined or
ansible_virtualization_role not in ["guest"])
and (ansible_kernel.find("-pve") == "-1")
else "absent" }}'
- name: 'firmware-linux-nonfree'
distribution: [ 'Debian' ]
areas: [ 'non-free' ]
state: '{{ "present" if (ansible_form_factor in ["Rack Mount Chassis"])
and (ansible_virtualization_role is undefined or
ansible_virtualization_role not in ["guest"])
and (ansible_kernel.find("-pve") == "-1")
else "absent" }}'
- name: 'intel-microcode'
# Same as amd64-microcode, but for Intel CPUs.
distribution: [ 'Debian', 'Devuan', 'Ubuntu' ]
areas: '{{ ["main"]
if ansible_distribution in ["Ubuntu"]
else ["non-free"] }}'
state: '{{ "present" if ansible_virtualization_role == "host" and
"GenuineIntel" in ansible_processor
else "absent" }}'
- name: 'linux-firmware'
distribution: [ 'Ubuntu' ]
state: '{{ "present" if (ansible_form_factor in ["Rack Mount Chassis"])
and (ansible_virtualization_role is undefined or
ansible_virtualization_role not in ["guest"])
else "absent" }}'
- name: 'linux-firmware-nonfree'
distribution: [ 'Ubuntu' ]
release: [ 'precise', 'trusty', 'wily' ]
areas: [ 'multiverse' ]
state: '{{ "present" if (ansible_form_factor in ["Rack Mount Chassis"])
and (ansible_virtualization_role is undefined or
ansible_virtualization_role not in ["guest"])
else "absent" }}'
# ]]]
# .. envvar:: apt_install__all_packages [[[
#
# The master list of APT packages to install, passed to the lookup template for
# conditional processing.
apt_install__all_packages:
- '{{ apt_install__base_packages }}'
- '{{ apt_install__shell_packages }}'
- '{{ apt_install__editor_packages }}'
- '{{ apt_install__packages }}'
- '{{ apt_install__group_packages }}'
- '{{ apt_install__host_packages }}'
- '{{ apt_install__dependent_packages }}'
- '{{ apt_install__conditional_packages }}'
- '{{ apt_install__firmware_packages }}'
# ]]]
# ]]]
# Alternative package symlinks [[[
# --------------------------------
# These lists can be used to define alternative symlinks for certain packages
# which provide similar functionality, using ``update-alternatives`` package.
# See :ref:`apt_install__ref_alternatives` for more details.
# .. envvar:: apt_install__default_alternatives [[[
#
# List of default alternative symlinks set by the role.
apt_install__default_alternatives:
# The role also installs the ``vim`` package via the
# :envvar:`apt_install__editor_packages` variable.
- name: 'editor'
path: '/usr/bin/vim.basic'
# ]]]
# .. envvar:: apt_install__alternatives [[[
#
# List of alternative symlinks configured for all packages in the Ansible
# inventory.
apt_install__alternatives: []
# ]]]
# .. envvar:: apt_install__group_alternatives [[[
#
# List of alternative symlinks configured for hosts in specific Ansible
# inventory group.
apt_install__group_alternatives: []
# ]]]
# .. envvar:: apt_install__host_alternatives [[[
#
# List of alternative symlinks configured for specific hosts in the Ansible
# inventory.
apt_install__host_alternatives: []
# ]]]
# ]]]
# Configuration for other Ansible roles [[[
# -----------------------------------------
# .. envvar:: apt_install__apt_preferences__dependent_list [[[
#
# Configuration for the :ref:`debops.apt_preferences` role.
apt_install__apt_preferences__dependent_list:
- package: 'needrestart needrestart-*'
backports: [ 'trusty' ]
reason: 'Better support for container technologies'
by_role: 'debops.apt_install'
# ]]]
# ]]]
# ]]]

View file

@ -0,0 +1,35 @@
---
# Copyright (C) 2016-2017 Maciej Delmanowski <drybjed@gmail.com>
# Copyright (C) 2016-2017 Robin Schneider <ypid@riseup.net>
# Copyright (C) 2016-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, Robin Schneider'
description: 'Install APT packages on Debian and other compatible systems'
company: 'DebOps'
license: 'GPL-3.0-only'
min_ansible_version: '2.0.0'
platforms:
- name: 'Ubuntu'
versions: [ 'all' ]
- name: 'Debian'
versions: [ 'all' ]
galaxy_tags:
- system
- apt
- packages
- packaging
- deployment
- software

View file

@ -0,0 +1,5 @@
---
# Copyright (C) 2016-2017 Maciej Delmanowski <drybjed@gmail.com>
# Copyright (C) 2016-2017 Robin Schneider <ypid@riseup.net>
# Copyright (C) 2016-2017 DebOps <https://debops.org/>
# SPDX-License-Identifier: GPL-3.0-only

View file

@ -0,0 +1,5 @@
---
# Copyright (C) 2016-2017 Maciej Delmanowski <drybjed@gmail.com>
# Copyright (C) 2016-2017 Robin Schneider <ypid@riseup.net>
# Copyright (C) 2016-2017 DebOps <https://debops.org/>
# SPDX-License-Identifier: GPL-3.0-only

View file

@ -0,0 +1,112 @@
---
# Copyright (C) 2016-2017 Maciej Delmanowski <drybjed@gmail.com>
# Copyright (C) 2016-2017 Robin Schneider <ypid@riseup.net>
# Copyright (C) 2016-2017 DebOps <https://debops.org/>
# SPDX-License-Identifier: GPL-3.0-only
- name: Import custom Ansible plugins
ansible.builtin.import_role:
name: 'ansible_plugins'
- name: Import DebOps global handlers
ansible.builtin.import_role:
name: 'global_handlers'
- name: Import DebOps secret role
ansible.builtin.import_role:
name: 'secret'
- name: Pre hooks
ansible.builtin.include_tasks: '{{ lookup("debops.debops.task_src", "apt_install/pre_main.yml") }}'
- name: Make sure that Ansible fact directory exists
ansible.builtin.file:
path: '/etc/ansible/facts.d'
state: 'directory'
owner: 'root'
group: 'root'
mode: '0755'
when: apt_install__enabled | bool
- name: Save local Ansible facts
ansible.builtin.template:
src: 'etc/ansible/facts.d/apt_install.fact.j2'
dest: '/etc/ansible/facts.d/apt_install.fact'
owner: 'root'
group: 'root'
mode: '0755'
when: apt_install__enabled | bool
notify: [ 'Refresh host facts' ]
tags: [ 'meta::facts' ]
- name: Update Ansible facts if they were modified
ansible.builtin.meta: 'flush_handlers'
- name: Debconf module dependencies
ansible.builtin.apt:
name:
- 'debconf'
- 'debconf-utils'
state: 'present'
install_recommends: '{{ apt_install__recommends | bool }}'
register: apt_install__register_debconf_packages
until: apt_install__register_debconf_packages is succeeded
- name: Apply requested packages configuration
ansible.builtin.debconf:
name: '{{ item.name }}'
question: '{{ item.question | d(omit) }}'
selection: '{{ item.selection | d(omit) }}'
setting: '{{ item.setting | d(omit) }}'
unseen: '{{ item.unseen | d(omit) }}'
value: '{{ item.value | d(omit) }}'
answer: '{{ item.answer | d(omit) }}'
vtype: '{{ item.vtype | d(omit) }}'
loop: '{{ q("flattened", apt_install__debconf
+ apt_install__group_debconf
+ apt_install__host_debconf) }}'
when: item.name | d()
- name: Install requested APT packages
ansible.builtin.apt:
name: '{{ q("flattened", lookup("template",
"lookup/apt_install__all_packages.j2",
convert_data=False) | from_yaml) }}'
state: '{{ apt_install__state }}'
install_recommends: '{{ apt_install__recommends | bool }}'
update_cache: '{{ apt_install__update_cache | bool }}'
cache_valid_time: '{{ apt_install__cache_valid_time }}'
register: apt_install__register_packages
until: apt_install__register_packages is succeeded
when: apt_install__enabled | bool
- name: Configure alternative symlinks
community.general.alternatives:
name: '{{ item.name }}'
path: '{{ item.path }}'
link: '{{ item.link | d(omit) }}'
priority: '{{ item.priority | d(omit) }}'
loop: '{{ q("flattened", apt_install__default_alternatives
+ apt_install__alternatives
+ apt_install__group_alternatives
+ apt_install__host_alternatives) }}'
when: item.name | d() and item.path | d()
- name: Configure automatic alternatives
ansible.builtin.command: update-alternatives --auto {{ item.name }}
register: apt_install__register_alternatives
loop: '{{ q("flattened", apt_install__alternatives
+ apt_install__group_alternatives
+ apt_install__host_alternatives) }}'
when: item.name | d() and not item.path | d()
changed_when: apt_install__register_alternatives.stdout | d()
- name: Disable kernel hints about pending upgrades
ansible.builtin.template:
src: 'etc/needrestart/conf.d/no-kernel-hints.conf.j2'
dest: '/etc/needrestart/conf.d/no-kernel-hints.conf'
mode: '0644'
when: apt_install__enabled | bool and apt_install__no_kernel_hints | bool
- name: Post hooks
ansible.builtin.include_tasks: '{{ lookup("debops.debops.task_src", "apt_install/post_main.yml") }}'

View file

@ -0,0 +1,30 @@
#!{{ ansible_python['executable'] }}
# -*- coding: utf-8 -*-
# Copyright (C) 2016-2017 Maciej Delmanowski <drybjed@gmail.com>
# Copyright (C) 2016-2017 Robin Schneider <ypid@riseup.net>
# Copyright (C) 2022 David Härdeman <david@hardeman.nu>
# Copyright (C) 2016-2022 DebOps <https://debops.org/>
# SPDX-License-Identifier: GPL-3.0-only
# {{ ansible_managed }}
from __future__ import print_function
from json import dumps
import os
def dir_exists(path):
return os.path.isdir(path)
def module_loaded(mod):
return dir_exists(os.path.join('/sys/module', mod))
output = {
'configured': True,
'have_virtual_rng': module_loaded('virtio_rng')
}
print(dumps(output, sort_keys=True, indent=4))

View file

@ -0,0 +1,11 @@
{# Copyright (C) 2016-2017 Maciej Delmanowski <drybjed@gmail.com>
# Copyright (C) 2016-2017 Robin Schneider <ypid@riseup.net>
# Copyright (C) 2016-2017 DebOps <https://debops.org/>
# SPDX-License-Identifier: GPL-3.0-only
#}
# {{ ansible_managed }}
# Kernel upgrade hints are disabled to allow for non-interactive APT package
# installs. Without this, non-interactive APT operations, for example performed
# by Ansible, will hang indefinitely - waiting for user interaction.
$nrconf{kernelhints} = 0;

View file

@ -0,0 +1,47 @@
{# Copyright (C) 2016-2017 Maciej Delmanowski <drybjed@gmail.com>
# Copyright (C) 2016-2017 Robin Schneider <ypid@riseup.net>
# Copyright (C) 2016-2017 DebOps <https://debops.org/>
# SPDX-License-Identifier: GPL-3.0-only
#}
{% macro get_yaml_list_from_string_or_list(package_map, item) %}{# ((( #}
{{ ([ package_map[item] ]
if (package_map[item] is string)
else (package_map[item] | list)) | to_yaml }}
{% endmacro %}{# ))) #}
{% macro process_packages(packages, apt_install__tpl_package_list) %}{# ((( #}
{% for element in packages %}
{% if element is string %}
{% set _ = apt_install__tpl_package_list.append(element) %}
{% elif element is mapping %}
{% if element.state | d('present') == 'present' and element.name | d() %}
{% set apt_install__tpl_to_meet_condition_list = apt_install__condition_map.keys() %}
{% set apt_install__tpl_met_condition_list = [] %}
{% for condition_name_to_meet in apt_install__tpl_to_meet_condition_list %}
{% if element[condition_name_to_meet] is defined %}
{% for condition_item_to_meet in get_yaml_list_from_string_or_list(element, condition_name_to_meet) | from_yaml | map('lower') %}
{% if condition_item_to_meet in (get_yaml_list_from_string_or_list(apt_install__condition_map, condition_name_to_meet) | from_yaml | map('lower')) %}
{% set _ = apt_install__tpl_met_condition_list.append(condition_name_to_meet) %}
{% endif %}
{% endfor %}
{% else %}
{% set _ = apt_install__tpl_met_condition_list.append(condition_name_to_meet) %}
{% endif %}
{% endfor %}
{% if (apt_install__tpl_met_condition_list | unique | length) == (apt_install__tpl_to_meet_condition_list | unique | length) %}
{% set _ = apt_install__tpl_package_list.append(get_yaml_list_from_string_or_list(element, 'name') | from_yaml
| intersect(element.whitelist | d(get_yaml_list_from_string_or_list(element, 'name') | from_yaml)) | list) %}
{% endif %}
{% endif %}
{% else %}
{% for things in element %}
{% set _ = process_packages(things, apt_install__tpl_package_list) %}
{% endfor %}
{% endif %}
{% endfor %}
{% endmacro %}{# ))) #}
{% set apt_install__tpl_package_list = [] %}
{% for elements in apt_install__all_packages %}
{% set _ = process_packages(elements, apt_install__tpl_package_list) %}
{% endfor %}
{{ [ apt_install__tpl_package_list ] | to_nice_yaml }}

View file

@ -0,0 +1,19 @@
debops.apt_listchanges - Manage apt-listchanges using Ansible
Copyright (C) 2016 Maciej Delmanowski <drybjed@gmail.com>
Copyright (C) 2016 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,136 @@
---
# .. vim: foldmarker=[[[,]]]:foldmethod=marker
# .. Copyright (C) 2016 Maciej Delmanowski <drybjed@gmail.com>
# .. Copyright (C) 2016 DebOps <https://debops.org/>
# .. SPDX-License-Identifier: GPL-3.0-only
# .. _apt_listchanges__ref_defaults:
# debops.apt_listchanges default variables [[[
# ============================================
# .. contents:: Sections
# :local:
#
# .. include:: ../../../../includes/global.rst
# APT package and installation [[[
# --------------------------------
# .. envvar:: apt_listchanges__deploy_state [[[
#
# Specify if the :command:`apt-listchanges` package should be installed (``present``)
# or not installed (``absent``). All specified packages will be purged if the
# role is disabled using this variable.
apt_listchanges__deploy_state: 'present'
# ]]]
# .. envvar:: apt_listchanges__base_packages [[[
#
# List of APT packages to install.
apt_listchanges__base_packages: [ 'apt-listchanges' ]
# ]]]
# .. envvar:: apt_listchanges__packages [[[
#
# List of additional APT packages to install with :command:`apt-listpackages`.
apt_listchanges__packages: []
# ]]]
# ]]]
# Main configuration options [[[
# ------------------------------
# .. envvar:: apt_listchanges__mail_to [[[
#
# List of e-mail accounts to which messages from :command:`apt-listchanges` will be
# sent.
apt_listchanges__mail_to: '{{ ansible_local.core.admin_private_email | d(["root"]) }}'
# ]]]
# .. envvar:: apt_listchanges__apt_frontend [[[
#
# Specify which :command:`apt-listchanges` frontend to use when the script is executed
# by APT operations. By default the frontend is ``mail`` unless ``apticron`` is
# detected in which case the APT frontend is disabled, since ``apticron`` will
# send virtually the same e-mails anyway.
#
# See :command:`apt-listchanges(1)` for list of available frontends.
apt_listchanges__apt_frontend: '{{ "none"
if (ansible_local | d() and ansible_local.apticron | d() and
ansible_local.apticron.enabled | bool)
else (ansible_local.apt_listchanges.apt.frontend
if (ansible_local.apt_listchanges.apt | d() and
ansible_local.apt_listchanges.apt.frontend | d())
else "mail") }}'
# ]]]
# .. envvar:: apt_listchanges__apt_which [[[
#
# Specify what information should be sent when the script is run by APT
# operations. Choices: ``news``, ``changelogs``, ``both``. By default only
# important NEWS items will be sent.
apt_listchanges__apt_which: 'news'
# ]]]
# .. envvar:: apt_listchanges__apticron_frontend [[[
#
# Specify what frontend to use when the script is executed by ``apticron``. See
# :command:`apt-listchanges(1)` for information about available frontends.
apt_listchanges__apticron_frontend: 'mail'
# ]]]
# .. envvar:: apt_listchanges__apticron_which [[[
#
# Specify what information should be sent when the script is run by
# ``apticron``. Choices: ``news``, ``changelogs``, ``both``.
apt_listchanges__apticron_which: 'both'
# ]]]
# ]]]
# Configuration profiles [[[
# --------------------------
# .. envvar:: apt_listchanges__profiles [[[
#
# YAML dictionary with information about :command:`apt-listchanges` profiles which
# should be configured. Each value should be a YAML dictionary with key: value
# pairs which define the configuration options. See :command:`apt-listchanges(1)` for
# information about what options can be configured in a profile.
apt_listchanges__profiles:
'cmdline': '{{ apt_listchanges__profile_cmdline }}'
'apt': '{{ apt_listchanges__profile_apt }}'
'apticron': '{{ apt_listchanges__profile_apticron }}'
# ]]]
# .. envvar:: apt_listchanges__profile_cmdline [[[
#
# Profile used when :command:`apt-listchanges` is run from the command line, according
# th the manual.
apt_listchanges__profile_cmdline:
frontend: 'pager'
# ]]]
# .. envvar:: apt_listchanges__profile_apt [[[
#
# Profile used when :command:`apt-listchanges` is executed during APT operations.
apt_listchanges__profile_apt:
frontend: '{{ apt_listchanges__apt_frontend }}'
email_address: '{{ apt_listchanges__mail_to | join(",") }}'
confirm: '0'
which: '{{ apt_listchanges__apt_which }}'
save_seen: '/var/lib/apt/listchanges.db'
# ]]]
# .. envvar:: apt_listchanges__profile_apticron [[[
#
# Profile used when :command:`apt-listchanges` is run by ``apticron``.
apt_listchanges__profile_apticron:
frontend: '{{ apt_listchanges__apticron_frontend }}'
email_address: '{{ apt_listchanges__mail_to | join(",") }}'
confirm: '0'
which: '{{ apt_listchanges__apticron_which }}'
save_seen: '/var/lib/apt/listchanges.db'
# ]]]
# ]]]
# ]]]

View file

@ -0,0 +1,32 @@
---
# Copyright (C) 2016 Maciej Delmanowski <drybjed@gmail.com>
# Copyright (C) 2016-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 apt-listchanges service'
company: 'DebOps'
license: 'GPL-3.0-only'
min_ansible_version: '2.0.0'
platforms:
- name: 'Ubuntu'
versions: [ 'all' ]
- name: 'Debian'
versions: [ 'all' ]
galaxy_tags:
- apt
- changelog
- news
- debian

View file

@ -0,0 +1,39 @@
---
# Copyright (C) 2016 Maciej Delmanowski <drybjed@gmail.com>
# Copyright (C) 2016 DebOps <https://debops.org/>
# SPDX-License-Identifier: GPL-3.0-only
- name: Manage APT packages
ansible.builtin.package:
name: '{{ q("flattened", (apt_listchanges__base_packages
+ apt_listchanges__packages)) }}'
state: '{{ apt_listchanges__deploy_state }}'
purge: True
register: apt_listchanges__register_packages
until: apt_listchanges__register_packages is succeeded
- name: Configure apt-listchanges
ansible.builtin.template:
src: 'etc/apt/listchanges.conf.j2'
dest: '/etc/apt/listchanges.conf'
owner: 'root'
group: 'root'
mode: '0644'
when: apt_listchanges__deploy_state == 'present'
- name: Make sure that Ansible fact directory exists
ansible.builtin.file:
path: '/etc/ansible/facts.d'
state: 'directory'
owner: 'root'
group: 'root'
mode: '0755'
- name: Save apt-listchanges facts
ansible.builtin.template:
src: 'etc/ansible/facts.d/apt_listchanges.fact.j2'
dest: '/etc/ansible/facts.d/apt_listchanges.fact'
owner: 'root'
group: 'root'
mode: '0755'
tags: [ 'meta::facts' ]

View file

@ -0,0 +1,40 @@
#!{{ ansible_python['executable'] }}
# -*- coding: utf-8 -*-
# Copyright (C) 2016 Maciej Delmanowski <drybjed@gmail.com>
# Copyright (C) 2016 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
from os import path
try:
from configparser import ConfigParser
except ImportError:
from ConfigParser import ConfigParser
conf_file = '/etc/apt/listchanges.conf'
output = {"installed": True}
if path.isfile(conf_file):
config = ConfigParser()
config.read(conf_file)
for section in config.sections():
output[section] = {}
for name, value in config.items(section):
output[section][name] = [x.strip() for x in value.split() if x]
if len(output[section][name]) == 1:
output[section][name] = output[section][name][0]
elif len(output[section][name]) == 0:
output[section][name] = ''
else:
output = {"installed": False}
print(dumps(output, sort_keys=True, indent=4))

View file

@ -0,0 +1,13 @@
{# Copyright (C) 2016 Maciej Delmanowski <drybjed@gmail.com>
# Copyright (C) 2016 DebOps <https://debops.org/>
# SPDX-License-Identifier: GPL-3.0-only
#}
# {{ ansible_managed }}
{% for name, profile in apt_listchanges__profiles | dictsort %}
[{{ name }}]
{% for key, value in profile | dictsort %}
{{ key }} = {{ value }}
{% endfor %}
{% endfor %}

View file

@ -0,0 +1,19 @@
debops.apt_mark - Set desired APT package state using Ansible
Copyright (C) 2018 Maciej Delmanowski <drybjed@gmail.com>
Copyright (C) 2018 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,110 @@
---
# .. vim: foldmarker=[[[,]]]:foldmethod=marker
# .. Copyright (C) 2018 Maciej Delmanowski <drybjed@gmail.com>
# .. Copyright (C) 2018 DebOps <https://debops.org/>
# .. SPDX-License-Identifier: GPL-3.0-only
# .. _apt_mark__ref_defaults:
# debops.apt_mark default variables
# =================================
# .. contents:: Sections
# :local:
#
# .. include:: ../../../../includes/global.rst
# General configuration [[[
# -------------------------
# .. envvar:: apt_mark__enabled [[[
#
# Enable or disable support for marking APT package state.
apt_mark__enabled: True
# ]]]
# .. envvar:: apt_mark__autoremove_recommends_important [[[
#
# If ``True`` (default), APT will consider packages installed via
# ``Recommends:`` dependencies as important and not remove them automatically.
#
# Any entries in the ``apt_mark__*_packages`` lists will enable autoremoval of
# APT packages installed via ``Recommends:`` dependencies, which you can then
# control by marking them as installed manually or automatically.
apt_mark__autoremove_recommends_important: '{{ False
if (apt_mark__default_packages | d()
or apt_mark__packages | d()
or apt_mark__group_packages | d()
or apt_mark__host_packages | d())
else True }}'
# ]]]
# .. envvar:: apt_mark__autoremove_suggests_important [[[
#
# If ``True`` (default), APT will consider packages installed via ``Suggests:``
# dependencies as important and not remove them automatically.
#
# Any entries in the ``apt_mark__*_packages`` lists will enable autoremoval of
# APT packages installed via ``Suggests:`` dependencies, which you can then
# control by marking them as installed manually or automatically.
apt_mark__autoremove_suggests_important: '{{ False
if (apt_mark__default_packages | d()
or apt_mark__packages | d()
or apt_mark__group_packages | d()
or apt_mark__host_packages | d())
else True }}'
# ]]]
# ]]]
# APT package state configuration [[[
# -----------------------------------
# These lists define the APT packages which the role will operate on, and their
# desired state. See :ref:`apt_mark__ref_packages` for more details.
# .. envvar:: apt_mark__default_packages [[[
#
# List of APT package states defined by the role
apt_mark__default_packages: []
# ]]]
# .. envvar:: apt_mark__packages [[[
#
# List of APT package states which should be applied on all hosts in the
# Ansible inventory.
apt_mark__packages: []
# ]]]
# .. envvar:: apt_mark__group_packages [[[
#
# List of APT package states which should be applied on hosts in a specific
# Ansible inventory group.
apt_mark__group_packages: []
# ]]]
# .. envvar:: apt_mark__host_packages [[[
#
# List of APT package states which should be applied on specific hosts in the
# Ansible inventory.
apt_mark__host_packages: []
# ]]]
# .. envvar:: apt_mark__dependent_packages [[[
#
# List of APT package states defined by other Ansible roles via role dependent
# variables.
apt_mark__dependent_packages: []
# ]]]
# .. envvar:: apt_mark__combined_packages [[[
#
# Variable which combines all of the APT state lists and is passed to the role
# tasks.
apt_mark__combined_packages: '{{ apt_mark__default_packages
+ apt_mark__dependent_packages
+ apt_mark__packages
+ apt_mark__group_packages
+ apt_mark__host_packages }}'
# ]]]
# ]]]

View file

@ -0,0 +1,31 @@
#!/usr/bin/env python2
# Copyright (C) 2018 Maciej Delmanowski <drybjed@gmail.com>
# Copyright (C) 2018 DebOps <https://debops.org/>
# SPDX-License-Identifier: GPL-3.0-only
from __future__ import print_function
from json import dumps
from subprocess import Popen, PIPE
import os
import signal
def run_command(command_args):
"""Gather information about APT packages"""
process = Popen(command_args, stdout=PIPE, stderr=PIPE,
preexec_fn=os.setsid)
stdout, stderr = process.communicate()
if stdout:
return stdout.decode('utf-8').strip().split('\n')
else:
return []
output = {'installed':
run_command(["dpkg-query", "-f", "${binary:Package}\n", "-W"]),
'auto': run_command(["apt-mark", "showauto"]),
'hold': run_command(["apt-mark", "showhold"]),
'manual': run_command(["apt-mark", "showmanual"])}
print(dumps(output, sort_keys=True, indent=4))

View file

@ -0,0 +1,31 @@
#!/usr/bin/env python3
# Copyright (C) 2018 Maciej Delmanowski <drybjed@gmail.com>
# Copyright (C) 2018 DebOps <https://debops.org/>
# SPDX-License-Identifier: GPL-3.0-only
from __future__ import print_function
from json import dumps
from subprocess import Popen, PIPE
import os
import signal
def run_command(command_args):
"""Gather information about APT packages"""
process = Popen(command_args, stdout=PIPE, stderr=PIPE,
preexec_fn=os.setsid)
stdout, stderr = process.communicate()
if stdout:
return stdout.decode('utf-8').strip().split('\n')
else:
return []
output = {'installed':
run_command(["dpkg-query", "-f", "${binary:Package}\n", "-W"]),
'auto': run_command(["apt-mark", "showauto"]),
'hold': run_command(["apt-mark", "showhold"]),
'manual': run_command(["apt-mark", "showmanual"])}
print(dumps(output, sort_keys=True, indent=4))

View file

@ -0,0 +1,30 @@
---
# Copyright (C) 2018 Maciej Delmanowski <drybjed@gmail.com>
# Copyright (C) 2018 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: 'Set desired APT package state using apt-mark'
company: 'DebOps'
license: 'GPL-3.0-only'
min_ansible_version: '2.4.0'
platforms:
- name: 'Ubuntu'
versions: [ 'all' ]
- name: 'Debian'
versions: [ 'all' ]
galaxy_tags:
- system
- apt

View file

@ -0,0 +1,96 @@
---
# Copyright (C) 2018 Maciej Delmanowski <drybjed@gmail.com>
# Copyright (C) 2018 DebOps <https://debops.org/>
# SPDX-License-Identifier: GPL-3.0-only
- name: Import custom Ansible plugins
ansible.builtin.import_role:
name: 'ansible_plugins'
- name: Configure APT autoremove options
ansible.builtin.template:
src: 'etc/apt/apt.conf.d/25autoremove-recommends.conf.j2'
dest: '/etc/apt/apt.conf.d/25autoremove-recommends.conf'
owner: 'root'
group: 'root'
mode: '0644'
when: apt_mark__enabled | bool
- name: Check state of the APT packages
ansible.builtin.script: 'script/apt-mark-status{{ "2" if (ansible_python_version is version_compare("3.5", "<")) else "3" }}'
register: apt_mark__register_state
changed_when: False
check_mode: False
when: apt_mark__enabled | bool
- name: Set facts about APT package state
ansible.builtin.set_fact:
apt_mark__fact_auto: '{{ (apt_mark__register_state.stdout | from_json)["auto"] }}'
apt_mark__fact_hold: '{{ (apt_mark__register_state.stdout | from_json)["hold"] }}'
apt_mark__fact_installed: '{{ (apt_mark__register_state.stdout | from_json)["installed"] }}'
apt_mark__fact_manual: '{{ (apt_mark__register_state.stdout | from_json)["manual"] }}'
when: apt_mark__enabled | bool
- name: Set package state as installed automatically
ansible.builtin.command: apt-mark auto {{ ((item.packages | d([item.name])) | intersect(apt_mark__fact_installed)) | join(' ') }}
with_items: '{{ apt_mark__combined_packages | debops.debops.parse_kv_items }}'
register: apt_mark__register_auto
changed_when: apt_mark__register_auto.changed | bool
when: (apt_mark__enabled | bool and
(item.packages | d([item.name]) | intersect(apt_mark__fact_installed))
| symmetric_difference(apt_mark__fact_manual) and
(item.packages | d([item.name]) | intersect(apt_mark__fact_installed))
| difference(apt_mark__fact_auto) and
item.state | d('manual') in ['auto', 'auto-hold', 'auto-unhold'])
- name: Set package state as installed manually
ansible.builtin.command: apt-mark manual {{ ((item.packages | d([item.name]))
| intersect(apt_mark__fact_installed)) | join(' ') }}
with_items: '{{ apt_mark__combined_packages | debops.debops.parse_kv_items }}'
register: apt_mark__register_manual
changed_when: apt_mark__register_manual.changed | bool
when: (apt_mark__enabled | bool and
(item.packages | d([item.name]) | intersect(apt_mark__fact_installed))
| symmetric_difference(apt_mark__fact_auto) and
(item.packages | d([item.name]) | intersect(apt_mark__fact_installed))
| difference(apt_mark__fact_manual) and
item.state | d('manual') in ['manual', 'manual-hold', 'manual-unhold'])
- name: Hold current package state
ansible.builtin.command: apt-mark hold {{ ((item.packages | d([item.name]))
| intersect(apt_mark__fact_installed)) | join(' ') }}
with_items: '{{ apt_mark__combined_packages | debops.debops.parse_kv_items }}'
register: apt_mark__register_hold
changed_when: apt_mark__register_hold.changed | bool
when: (apt_mark__enabled | bool and
(item.packages | d([item.name]) | intersect(apt_mark__fact_installed))
| difference(apt_mark__fact_hold) and
item.state | d('manual') in ['hold', 'auto-hold', 'manual-hold'])
- name: Unhold current package state
ansible.builtin.command: apt-mark unhold {{ ((item.packages | d([item.name]))
| intersect(apt_mark__fact_installed)) | join(' ') }}
with_items: '{{ apt_mark__combined_packages | debops.debops.parse_kv_items }}'
register: apt_mark__register_unhold
changed_when: apt_mark__register_unhold.changed | bool
when: (apt_mark__enabled | bool and
(item.packages | d([item.name]) | intersect(apt_mark__fact_installed))
| intersect(apt_mark__fact_hold) and
item.state | d('manual') in ['unhold', 'auto-unhold', 'manual-unhold'])
- name: Make sure that Ansible local facts directory exists
ansible.builtin.file:
path: '/etc/ansible/facts.d'
state: 'directory'
owner: 'root'
group: 'root'
mode: '0755'
- name: Save apt-mark local facts
ansible.builtin.template:
src: 'etc/ansible/facts.d/apt_mark.fact.j2'
dest: '/etc/ansible/facts.d/apt_mark.fact'
owner: 'root'
group: 'root'
mode: '0755'
tags: [ 'meta::facts' ]

View file

@ -0,0 +1,18 @@
#!{{ ansible_python['executable'] }}
# -*- coding: utf-8 -*-
# Copyright (C) 2018 Maciej Delmanowski <drybjed@gmail.com>
# Copyright (C) 2018 DebOps <https://debops.org/>
# SPDX-License-Identifier: GPL-3.0-only
# {{ ansible_managed }}
from __future__ import print_function
from json import loads, dumps
from sys import exit
output = loads("""{{ {'configured': True,
'enabled': apt_mark__enabled | bool}
| to_nice_json }}""")
print(dumps(output, sort_keys=True, indent=4))

View file

@ -0,0 +1,11 @@
{# Copyright (C) 2018 Maciej Delmanowski <drybjed@gmail.com>
# Copyright (C) 2018 DebOps <https://debops.org/>
# SPDX-License-Identifier: GPL-3.0-only
#}
// {{ ansible_managed }}
// Specify if APT should treat packages installed via Recommends or Suggests
// dependencies as important. If True, they will not be considered for
// autoremoval.
APT::AutoRemove::RecommendsImportant "{{ apt_mark__autoremove_recommends_important | bool | lower }}";
APT::AutoRemove::SuggestsImportant "{{ apt_mark__autoremove_suggests_important | bool | lower }}";

View file

@ -0,0 +1,19 @@
debops.apt_mirror - Create a mirror of multiple APT repositories
Copyright (C) 2023 Maciej Delmanowski <drybjed@gmail.com>
Copyright (C) 2023 DebOps <https://debops.org/>
SPDX-License-Identifier: GPL-3.0-or-later
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,226 @@
---
# .. vim: foldmarker=[[[,]]]:foldmethod=marker
# .. Copyright (C) 2023 Maciej Delmanowski <drybjed@gmail.com>
# .. Copyright (C) 2023 DebOps <https://debops.org/>
# .. SPDX-License-Identifier: GPL-3.0-or-later
# .. _apt_mirror__ref_defaults:
# debops.apt_mirror default variables
# ===================================
# .. contents:: Sections
# :local:
#
# .. include:: ../../../../includes/global.rst
# APT packages, UNIX environment, cron defaults [[[
# -------------------------------------------------
# .. envvar:: apt_mirror__base_packages [[[
#
# List of Debian packages required for :command:`apt-mirror` support.
apt_mirror__base_packages: [ 'apt-mirror' ]
# ]]]
# .. envvar:: apt_mirror__packages [[[
#
# List of additional packages to install with :command:`apt-mirror` package.
apt_mirror__packages: []
# ]]]
# .. envvar:: apt_mirror__cron_environment [[[
#
# YAML dictionary which defines the environment variables (keys) and their
# values which will be defined in the :command:`cron` runtime environment for
# the :command:`apt-mirror` cron jobs. See :man:`crontab(5)` for details.
apt_mirror__cron_environment: {}
# ]]]
# .. envvar:: apt_mirror__cron_time [[[
#
# The default time specification for :command:`apt-mirror` cron jobs, specified
# in the :man:`crontab(5)` format. This value can be overridden per-mirror
# configuration using the ``item.cron_time`` parameter.
apt_mirror__cron_time: "0 4\t* * *"
# ]]]
# .. envvar:: apt_mirror__user [[[
#
# The UNIX account of the :command:`apt-mirror` service.
apt_mirror__user: 'apt-mirror'
# ]]]
# .. envvar:: apt_mirror__group [[[
#
# The UNIX group of the :command:`apt-mirror` service.
apt_mirror__group: 'apt-mirror'
# ]]]
# ]]]
# Web server configuration [[[
# ----------------------------
# .. envvar:: apt_mirror__fqdn [[[
#
# The Fully Qualified Domain Name on which mirrored APT repositories will be
# published.
apt_mirror__fqdn: '{{ ansible_fqdn }}'
# ]]]
# .. envvar:: apt_mirror__web_root [[[
#
# Absolute path to the directory with mirrored APT repositories.
apt_mirror__web_root: '/var/spool/apt-mirror/mirror'
# ]]]
# ]]]
# The default :command:`apt-mirror` configuration options [[[
# -----------------------------------------------------------
# .. envvar:: apt_mirror__default_options [[[
#
# List of YAML dictionaries which define default set of :command:`apt-mirror`
# configuration options. These options can be overridden in specific instances
# if necessary. See :ref:`apt_mirror__ref_configuration` for more details.
apt_mirror__default_options:
- name: 'base_path'
value: '/var/spool/apt-mirror'
state: 'comment'
- name: 'mirror_path'
value: '$base_path/mirror'
state: 'comment'
- name: 'skel_path'
value: '$base_path/skel'
state: 'comment'
- name: 'var_path'
value: '$base_path/var'
state: 'dynamic'
- name: 'cleanscript'
value: '$var_path/clean.sh'
state: 'comment'
- name: 'defaultarch'
value: '<running host architecture>'
state: 'comment'
- name: 'postmirror_script'
value: '$var_path/postmirror.sh'
state: 'comment'
- name: 'run_postmirror'
value: 0
state: 'comment'
- name: 'nthreads'
value: 20
- name: '_tilde'
value: 0
# ]]]
# ]]]
# The :command:`apt-mirror` instance configuration [[[
# ----------------------------------------------------
# The variables below define configuration of :command:`apt-mirror`
# "instances". See :ref:`apt_mirror__ref_configuration` for more details.
# .. envvar:: apt_mirror__default_configuration [[[
#
# The default instance configuration defined by the role.
apt_mirror__default_configuration:
- name: 'default'
filename: 'mirror.list'
sources:
- name: 'debian-stable'
type: 'deb'
uri: 'http://deb.debian.org/debian'
suite: 'stable'
components: [ 'main', 'contrib', 'non-free' ]
state: 'comment'
- name: 'debian-stable-src'
raw: 'deb-src http://deb.debian.org/debian stable main contrib non-free'
state: 'comment'
- name: 'clean-debian'
comment: 'Generate a clean.sh script for Debian mirror'
type: 'clean'
uri: 'http://deb.debian.org/debian'
weight: 1000
# ]]]
# .. envvar:: apt_mirror__configuration [[[
#
# The :command:`apt-mirror` instance configuration defined on all hosts in the
# Ansible inventory.
apt_mirror__configuration: []
# ]]]
# .. envvar:: apt_mirror__group_configuration [[[
#
# The :command:`apt-mirror` instance configuration defined on hosts in
# a specific Ansible inventory group.
apt_mirror__group_configuration: []
# ]]]
# .. envvar:: apt_mirror__host_configuration [[[
#
# The :command:`apt-mirror` instance configuration defined on specific hosts in the
# Ansible inventory.
apt_mirror__host_configuration: []
# ]]]
# .. envvar:: apt_mirror__combined_configuration [[[
#
# Variable which combines all instance configuration variables and is used in
# role tasks and templates.
apt_mirror__combined_configuration: '{{ apt_mirror__default_configuration
+ apt_mirror__configuration
+ apt_mirror__group_configuration
+ apt_mirror__host_configuration }}'
# ]]]
# ]]]
# Configuration for other Ansible roles [[[
# -----------------------------------------
# .. envvar:: apt_mirror__nginx__servers [[[
#
# List of :program:`nginx` server configurations managed by the
# :ref:`debops.nginx` role.
apt_mirror__nginx__dependent_servers:
- by_role: 'debops.apt_mirror'
enabled: True
ssl: False # disable port 443
filename: 'debops.apt_mirror_http'
name: '{{ apt_mirror__fqdn }}'
root: '{{ apt_mirror__web_root }}'
webroot_create: False
location:
'/': |
try_files $uri $uri/ $uri.html /index.html =404;
autoindex on;
state: 'present'
- by_role: 'debops.apt_mirror'
enabled: True
listen: False # disable port 80
filename: 'debops.apt_mirror_https'
name: '{{ apt_mirror__fqdn }}'
root: '{{ apt_mirror__web_root }}'
webroot_create: False
location:
'/': |
try_files $uri $uri/ $uri.html /index.html =404;
autoindex on;
state: '{{ "present" if (ansible_local.pki.enabled | d()) | bool else "absent" }}'
# ]]]
# ]]]

View file

@ -0,0 +1,32 @@
---
# Copyright (C) 2023 Maciej Delmanowski <drybjed@gmail.com>
# Copyright (C) 2023 DebOps <https://debops.org/>
# SPDX-License-Identifier: GPL-3.0-or-later
# 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 mirrors of multiple APT repositories'
company: 'DebOps'
license: 'GPL-3.0-or-later'
min_ansible_version: '2.14.0'
platforms:
- name: 'Ubuntu'
versions: [ 'all' ]
- name: 'Debian'
versions: [ 'all' ]
galaxy_tags:
- system
- apt
- mirror
- proxy

View file

@ -0,0 +1,119 @@
---
# Copyright (C) 2023 Maciej Delmanowski <drybjed@gmail.com>
# Copyright (C) 2023 DebOps <http://debops.org/>
# SPDX-License-Identifier: GPL-3.0-or-later
- name: Import DebOps global handlers
ansible.builtin.import_role:
name: 'global_handlers'
- name: Install required packages
ansible.builtin.package:
name: '{{ (apt_mirror__base_packages + apt_mirror__packages) | flatten }}'
state: 'present'
register: apt_mirror__register_install
until: apt_mirror__register_install is succeeded
- name: Get list of dpkg-stateoverride paths
ansible.builtin.shell: |
set -o nounset -o pipefail -o errexit && dpkg-statoverride --list | awk '{print $4}'
args:
executable: 'bash'
register: apt_mirror__register_statoverride
changed_when: False
check_mode: False
# apt-mirror stores passwords included in URLs as plaintext inside of its
# spool directory - it cannot be world-readable
- name: Fix permissions for apt-mirror spool directory
ansible.builtin.command: |
dpkg-statoverride --update --add {{ apt_mirror__user }} {{ apt_mirror__group }} 0750 /var/spool/apt-mirror/var
register: apt_mirror__register_statoverride_set
changed_when: apt_mirror__register_statoverride_set.changed | bool
when: '"/var/spool/apt-mirror/var" not in apt_mirror__register_statoverride.stdout_lines'
- name: Make sure that Ansible local facts directory exists
ansible.builtin.file:
path: '/etc/ansible/facts.d'
state: 'directory'
mode: '0755'
- name: Save apt_mirror local facts
ansible.builtin.template:
src: 'etc/ansible/facts.d/apt_mirror.fact.j2'
dest: '/etc/ansible/facts.d/apt_mirror.fact'
mode: '0755'
notify: [ 'Refresh host facts' ]
tags: [ 'meta::facts' ]
- name: Update Ansible facts if they were modified
ansible.builtin.meta: 'flush_handlers'
- name: Divert the original apt-mirror configuration
debops.debops.dpkg_divert:
path: '{{ item }}'
state: 'present'
loop:
- '/etc/apt/mirror.list'
- '/etc/cron.d/apt-mirror'
when: ansible_pkg_mgr == 'apt'
- name: Remove apt-mirror configuration if requested
ansible.builtin.file:
path: '{{ "/etc/apt/" + (item.filename | d("mirror." + item.name + ".list")) }}'
state: 'absent'
loop: '{{ apt_mirror__combined_configuration | flatten
| debops.debops.parse_kv_items(merge_keys=["sources"]) }}'
loop_control:
label: '{{ {"name": item.name, "state": item.state | d("present")} }}'
when: item.state | d('present') == 'absent'
- name: Generate apt-mirror configuration files
ansible.builtin.template:
src: 'etc/apt/mirror.list.j2'
dest: '{{ "/etc/apt/" + (item.filename | d("mirror." + item.name + ".list")) }}'
owner: '{{ apt_mirror__user }}'
group: '{{ apt_mirror__group }}'
mode: '0640'
loop: '{{ apt_mirror__combined_configuration | flatten
| debops.debops.parse_kv_items(defaults={"options": (apt_mirror__default_options | debops.debops.parse_kv_config)},
merge_keys=["sources"]) }}'
loop_control:
label: '{{ {"name": item.name, "state": item.state | d("present")} }}'
when: item.state | d('present') not in [ 'absent', 'ignore', 'init' ]
- name: Create data directories for separate mirror instances
ansible.builtin.file:
path: '{{ "/var/spool/apt-mirror/var/var." + item.name }}'
owner: '{{ apt_mirror__user }}'
group: '{{ apt_mirror__group }}'
mode: '0750'
state: 'directory'
loop: '{{ apt_mirror__combined_configuration | flatten
| debops.debops.parse_kv_items(defaults={"options": (apt_mirror__default_options | debops.debops.parse_kv_config)},
merge_keys=["sources"]) }}'
loop_control:
label: '{{ {"name": item.name, "state": item.state | d("present")} }}'
when: item.state | d('present') not in [ 'absent', 'ignore', 'init' ]
- name: Create postmirror.sh script for separate mirror instances
ansible.builtin.file:
path: '{{ "/var/spool/apt-mirror/var/var." + item.name + "/postmirror.sh" }}'
owner: '{{ apt_mirror__user }}'
group: '{{ apt_mirror__group }}'
mode: '0755'
state: 'touch'
modification_time: 'preserve'
access_time: 'preserve'
loop: '{{ apt_mirror__combined_configuration | flatten
| debops.debops.parse_kv_items(defaults={"options": (apt_mirror__default_options | debops.debops.parse_kv_config)},
merge_keys=["sources"]) }}'
loop_control:
label: '{{ {"name": item.name, "state": item.state | d("present")} }}'
when: item.state | d('present') not in [ 'absent', 'ignore', 'init' ]
- name: Generate cron configuration for apt-mirror
ansible.builtin.template:
src: 'etc/cron.d/apt-mirror.j2'
dest: '/etc/cron.d/apt-mirror'
mode: '0644'

View file

@ -0,0 +1,25 @@
#!{{ ansible_python['executable'] }}
# -*- coding: utf-8 -*-
# Copyright (C) 2023 Maciej Delmanowski <drybjed@gmail.com>
# Copyright (C) 2023 DebOps <https://debops.org/>
# SPDX-License-Identifier: GPL-3.0-or-later
# {{ ansible_managed }}
from __future__ import print_function
from json import dumps
import subprocess
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 = {'installed': cmd_exists('apt-mirror')}
print(dumps(output, sort_keys=True, indent=4))

View file

@ -0,0 +1,74 @@
{# Copyright (C) 2023 Maciej Delmanowski <drybjed@gmail.com>
# Copyright (C) 2023 DebOps <https://debops.org/>
# SPDX-License-Identifier: GPL-3.0-or-later
#}
# {{ ansible_managed }}
############# config ##################
#
{% for element in item.options %}
{% if element.name | d() and element.value is defined and element.state | d('present') not in [ 'absent', 'ignore' ] %}
{% set element_comment = ('# ' if (element.state | d('present') in [ 'init', 'comment' ]) else '') %}
{% if element.value is string and not element.value | bool %}
{% set element_value = element.value %}
{% elif element.value | bool and element.value is not iterable %}
{% if element.value | string == '1' %}
{% set element_value = element.value %}
{% else %}
{% set element_value = 'yes' %}
{% endif %}
{% elif not element.value | bool and element.value is not iterable %}
{% if element.value is not none %}
{% if element.value | int %}
{% set element_value = element.value %}
{% else %}
{% if element.value | string == '0' %}
{% set element_value = element.value %}
{% else %}
{% set element_value = 'no' %}
{% endif %}
{% endif %}
{% endif %}
{% endif %}
{% if element.state | d('present') == 'dynamic' %}
{% if element.name == 'var_path' %}
{% set element_value = '$base_path/var/var.' + item.name %}
{% endif %}
{% endif %}
{% if element.comment | d() %}
{{ '' }}
{{ element.comment | regex_replace('$\n', '') | comment(prefix='', postfix='') -}}
{% endif %}
{{ '{}set {} {}'.format(element_comment, element.name, element_value) }}
{% endif %}
{% endfor %}
#
############# end config ##############
{% for element in item.sources %}
{% if element.name | d() and element.state | d('present') not in [ 'absent', 'ignore' ] %}
{% set element_comment = ('#' if (element.state | d('present') in [ 'init', 'comment' ]) else '') %}
{% if element.raw | d() %}
{% set element_value = element.raw %}
{% elif element.type | d() and element.uri | d() %}
{% set element_entry = [] %}
{% set _ = element_entry.append(element.type) %}
{% set _ = element_entry.append(element.uri) %}
{% if element.suite | d() %}
{% set _ = element_entry.append(element.suite) %}
{% endif %}
{% if element.component | d() %}
{% set _ = element_entry.append(element.component) %}
{% endif %}
{% if element.components | d() %}
{% set _ = element_entry.extend(element.components) %}
{% endif %}
{% set element_value = element_entry | join(' ') %}
{% endif %}
{% if element.comment | d() %}
{{ '' }}
{{ element.comment | regex_replace('$\n', '') | comment(prefix='', postfix='') -}}
{% endif %}
{{ '{}{}'.format(element_comment, element_value) }}
{% endif %}
{% endfor %}

View file

@ -0,0 +1,28 @@
{# Copyright (C) 2023 Maciej Delmanowski <drybjed@gmail.com>
# Copyright (C) 2023 DebOps <https://debops.org/>
# SPDX-License-Identifier: GPL-3.0-or-later
#}
# {{ ansible_managed }}
#
# Regular cron jobs for the apt-mirror package
#
{% if apt_mirror__cron_environment %}
{{ '# Environment variables' }}
{% for key, value in apt_mirror__cron_environment.items() %}
{{ '{}={}'.format(key, value) }}
{% endfor %}
{% endif %}
{% for element in apt_mirror__combined_configuration | debops.debops.parse_kv_items %}
{% if element.name | d() and element.state | d('present') not in [ 'absent', 'ignore' ] %}
{% set element_comment = ('#' if (element.state | d('present') in [ 'init', 'comment' ]) else '') %}
{% set cron_time = element.cron_time | d(apt_mirror__cron_time) %}
{% set cron_user = element.cron_user | d(apt_mirror__user) %}
{% set cron_command = element.cron_command | d('/usr/bin/flock /var/spool/apt-mirror/var /usr/bin/apt-mirror /etc/apt/'
+ (element.filename | d('mirror.' + element.name + '.list'))
+ ' > /var/spool/apt-mirror/var/cron.' + element.name + '.log') %}
{{ '{}{}\t{}\t{}'.format(element_comment, cron_time, cron_user, cron_command) }}
{% endif %}
{% endfor %}

View file

@ -0,0 +1,20 @@
debops.apt_preferences - Manage APT preferences using Ansible
Copyright (C) 2015-2016 Maciej Delmanowski <drybjed@gmail.com>
Copyright (C) 2015-2017 Robin Schneider <ypid@riseup.net>
Copyright (C) 2015-2017 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,216 @@
---
# .. vim: foldmarker=[[[,]]]:foldmethod=marker
# .. Copyright (C) 2015-2016 Maciej Delmanowski <drybjed@gmail.com>
# .. Copyright (C) 2015-2017 Robin Schneider <ypid@riseup.net>
# .. Copyright (C) 2015-2017 DebOps <https://debops.org/>
# .. SPDX-License-Identifier: GPL-3.0-only
# .. _apt_preferences__ref_defaults:
# apt_preferences default variables [[[
# =====================================
# .. contents:: Sections
# :local:
#
# .. include:: ../../../../includes/global.rst
# APT preferences lists [[[
# -------------------------
# .. envvar:: apt_preferences__list [[[
#
# List of :manpage:`apt_preferences(5)` pins to configure in
# :file:`/etc/apt/preferences.d/`. These pins will be configured on all hosts
# in the cluster, if set in :file:`inventory/group_vars/debops_all_hosts/`. See
# :ref:`apt_preferences__list` for more details.
apt_preferences__list: []
# ]]]
# .. envvar:: apt_preferences__group_list [[[
#
# List of :manpage:`apt_preferences(5)` pins to configure in
# :file:`/etc/apt/preferences.d/`. These pins will be configured on hosts in a
# specified group, if set in :file:`inventory/group_vars/group_name/`. Only one
# group "level" is supported.
apt_preferences__group_list: []
# ]]]
# .. envvar:: apt_preferences__host_list [[[
#
# List of :manpage:`apt_preferences(5)` pins to configure in
# :file:`/etc/apt/preferences.d/`. These pins will be configured on specific
# hosts, if set in :file:`inventory/host_vars/host_name/`.
apt_preferences__host_list: []
# ]]]
# .. envvar:: apt_preferences__dependent_list [[[
#
# List of :manpage:`apt_preferences(5)` pins to configure in
# :file:`/etc/apt/preferences.d/`. This variable is meant to be used from a
# role dependency in :file:`role/meta/main.yml` or in a playbook.
apt_preferences__dependent_list: []
# ]]]
# ]]]
# APT preference presets [[[
# --------------------------
# This section contains presets of APT preferences intended for advanced users
# who want to install software from different releases but want to keep there
# main system on the stable release.
#
# When enabling these presets, you will need to set explicit preferences for
# packages you want to install from different releases.
# Examples for such explicit preferences can be found in the
# `defaults file of the ypid.packages`_ (search for "Package origin presets for
# Debian GNU/Linux").
#
# .. Debian backports have a priority of 100 by default so you will automatically
# not upgrade to packages from them.
#
# .. envvar:: apt_preferences__debian_stable_default_preset_list [[[
#
# Preset for the stable release of Debian GNU/Linux.
#
# Configuring this in APT is needed because releases (stable, testing,
# unstable) have by default the same priority 500 so when including different
# releases, APT would just select the newest version (usually unstable). To
# avoid this, APT preferences are used to change the preferences to prefer
# stable over testing over unstable.
apt_preferences__debian_stable_default_preset_list:
- package: '*'
by_role: 'debops.apt_preferences'
suffix: '_Debian'
raw: |
Explanation: Configure the installed release explicitly to slightly higher than the default priority of 500 so that release packages are preferred over third party repos.
Package: *
Pin: release o=Debian,n={{ ansible_distribution_release }}
Pin-Priority: 550
Explanation: Configure the security updates explicitly to slightly higher than the default priority of 500 so that security updates are not missing
Package: *
Pin: release o=Debian,n={{ ansible_distribution_release }}-security
Pin-Priority: 550
Explanation: Configure the stable-updates explicitly to slightly higher than the default priority of 500 so that stable-updates are not missing
Package: *
Pin: release o=Debian,n={{ ansible_distribution_release }}-updates
Pin-Priority: 550
Explanation: Configure the installed release explicitly to slightly higher than the default priority of 500 so that release packages are preferred over third party repos.
Package: *
Pin: release o=Qubes Debian,n={{ ansible_distribution_release }}
Pin-Priority: 550
Explanation: The default priority of packages from backports is 100 which is even lower then testing and unstable (500).
Explanation: Prefer backports over testing and unstable but dont automatically upgrade to them.
Package: *
Pin: release o=Debian Backports,n={{ ansible_distribution_release }}-backports
Pin-Priority: 400
Explanation: Pin NeuroDebian with priority 80 which is lower then the official Debian backports (100).
Explanation: This also works with this pinning configuration where Debian backports is
Explanation: set to 400 and Debian testing is decreased to 50.
Explanation: It is done here additionally to the neurodebian role to allow soft migration to extrepo.
Package: *
Pin: release o=NeuroDebian
Pin-Priority: 80
Explanation: In case ansible_distribution_release is not (anymore) the current stable release dont automatically upgrade to it.
Package: *
Pin: release o=Debian,a=stable
Pin-Priority: 60
Explanation: Install packages from testing if no package with the same name is available in release archives or backports or other archives.
Package: *
Pin: release o=Debian,a=testing
Pin-Priority: 50
Explanation: Only install packages from unstable if explicitly asked for or the package is pinned.
Package: *
Pin: release o=Debian,a=unstable
Pin-Priority: -1
Explanation: Only install packages from experimental if explicitly asked for or the package is pinned.
Package: *
Pin: release o=Debian,a=experimental
Pin-Priority: -1
# ]]]
# .. envvar:: apt_preferences__preset_list [[[
#
# Appropriate preset for your distribution, when one is available.
# To use it, add the following:
#
# .. code-block:: yaml
# :linenos:
#
# apt_preferences__list:
# - '{{ apt_preferences__preset_list | list }}'
#
# to your inventory.
#
apt_preferences__preset_list: '{{
((apt_preferences__debian_stable_default_preset_list | list) if (ansible_distribution == "Debian") else [])
}}'
# ]]]
# ]]]
# Role pin defaults [[[
# ---------------------
# .. envvar:: apt_preferences__priority_default [[[
#
# Default pin priority used, if custom priority is not specified in a pin
# definition.
apt_preferences__priority_default: 500
# ]]]
# .. envvar:: apt_preferences__priority_version [[[
#
# Default pin priority used if custom priority is not specified in a versioned
# pin definition.
apt_preferences__priority_version: 1001
# ]]]
# ]]]
# Role internals [[[
# ------------------
# .. envvar:: apt_preferences__next_release [[[
#
# Dictionary of future releases of current stable distributions. This is used to
# check if the role can create autogenerated pins with preference for backported
# packages from the ``<release>-backports`` repository.
#
# .. Redundant in the following places, please update all copies:
#
# * ansible/roles/debops.apt/defaults/main.yml
# * ansible/roles/debops.apt_preferences/defaults/main.yml
# * ansible/roles/debops.reprepro/defaults/main.yml
# * ansible/playbooks/tools/dist-upgrade.yml
#
apt_preferences__next_release:
# Debian releases
'stretch': 'buster'
'buster': 'bullseye'
'bullseye': 'bookworm'
'bookworm': 'trixie'
'trixie': 'forky'
# Ubuntu releases
'trusty': 'utopic'
'utopic': 'vivid'
'vivid': 'wily'
'wily': 'xenial'
'xenial': 'yakkety'
# ]]]
# .. Documentation, external links [[[
# .. _`defaults file of the ypid.packages`: https://github.com/ypid/ansible-packages/blob/master/defaults/main.yml
# ]]]
# ]]]
# ]]]

View file

@ -0,0 +1,33 @@
---
# Copyright (C) 2015-2016 Maciej Delmanowski <drybjed@gmail.com>
# Copyright (C) 2015-2017 Robin Schneider <ypid@riseup.net>
# Copyright (C) 2015-2017 DebOps <https://debops.org/>
# SPDX-License-Identifier: GPL-3.0-only
allow_duplicates: True
# 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, Robin Schneider'
description: 'Manage APT pins in /etc/apt/preferences.d/'
company: 'DebOps'
license: 'GPL-3.0-only'
min_ansible_version: '1.7.0'
platforms:
- name: 'Ubuntu'
versions: [ 'all' ]
- name: 'Debian'
versions: [ 'all' ]
galaxy_tags:
- system
- apt

View file

@ -0,0 +1,93 @@
---
# .. vim: foldmarker=[[[,]]]:foldmethod=marker
# Copyright (C) 2015-2016 Maciej Delmanowski <drybjed@gmail.com>
# Copyright (C) 2015-2017 Robin Schneider <ypid@riseup.net>
# Copyright (C) 2015-2017 DebOps <https://debops.org/>
# SPDX-License-Identifier: GPL-3.0-only
- name: Remove legacy APT preferences
ansible.builtin.file:
dest: '/etc/apt/preferences.d/{{ item.filename | d((item.role | d(item.by_role | d("pin")))
+ "_" + ((item.package.split(" ")[0]
if item.package is string
else item.package[0])
if item.package | d()
else (item.packages.split(" ")[0]
if item.packages is string
else item.packages[0]))
+ (item.suffix | d("")) + ".pref") | replace("*", "all") }}'
state: 'absent'
loop: '{{ q("flattened", apt_preferences_list | d([])
+ apt_preferences_group_list | d([])
+ apt_preferences_host_list | d([])
+ apt_preferences_dependent_list | d([])
+ apt_preferences__list
+ apt_preferences__group_list
+ apt_preferences__host_list
+ apt_preferences__dependent_list) }}'
when: ("." in item.role | d(item.by_role | d("")))
- name: Remove APT preferences
ansible.builtin.file:
dest: '/etc/apt/preferences.d/{{ item.filename | d((item.role | d(item.by_role | d("pin"))) | replace(".", "_")
+ "_" + ((item.package.split(" ")[0]
if item.package is string
else item.package[0])
if item.package | d()
else (item.packages.split(" ")[0]
if item.packages is string
else item.packages[0]))
+ (item.suffix | d("")) + ".pref") | replace("*", "all") }}'
state: 'absent'
loop: '{{ q("flattened", apt_preferences_list | d([])
+ apt_preferences_group_list | d([])
+ apt_preferences_host_list | d([])
+ apt_preferences_dependent_list | d([])
+ apt_preferences__list
+ apt_preferences__group_list
+ apt_preferences__host_list
+ apt_preferences__dependent_list) }}'
when: ((((item.state | d() and item.state == 'absent') or item.delete | d() | bool) and
(item.package | d() or item.packages | d()) and
(item.version is undefined or (item.version is defined and not item.version))) or
(item.raw is undefined and
item.pin is undefined and
(item.backports is undefined or
(item.backports is iterable and item.backports and
ansible_distribution_release not in item.backports)) and
ansible_distribution_release not in apt_preferences__next_release.keys()))
- name: Create APT preferences
ansible.builtin.template:
src: 'etc/apt/preferences.d/pin.pref.j2'
dest: '/etc/apt/preferences.d/{{ item.filename | d((item.role | d(item.by_role | d("pin"))) | replace(".", "_")
+ "_" + ((item.package.split(" ")[0]
if item.package is string
else item.package[0])
if item.package | d()
else (item.packages.split(" ")[0]
if item.packages is string
else item.packages[0]))
+ (item.suffix | d("")) + ".pref") | replace("*", "all") }}'
owner: 'root'
group: 'root'
mode: '0644'
loop: '{{ q("flattened", apt_preferences_list | d([])
+ apt_preferences_group_list | d([])
+ apt_preferences_host_list | d([])
+ apt_preferences_dependent_list | d([])
+ apt_preferences__list
+ apt_preferences__group_list
+ apt_preferences__host_list
+ apt_preferences__dependent_list) }}'
when: ((((item.state | d('present')) != 'absent' and (not (item.delete | d() | bool))) and
(item.package | d() or item.packages | d())) and
(item.raw | d() or
item.version | d() or
(item.pin is undefined and
(item.backports is undefined or
(item.backports is iterable and item.backports and
ansible_distribution_release in item.backports)) and
ansible_distribution_release in apt_preferences__next_release.keys()) or
item.pin | d()))

View file

@ -0,0 +1,37 @@
{# Copyright (C) 2015-2016 Maciej Delmanowski <drybjed@gmail.com>
# Copyright (C) 2015-2017 Robin Schneider <ypid@riseup.net>
# Copyright (C) 2015-2017 DebOps <https://debops.org/>
# SPDX-License-Identifier: GPL-3.0-only
#}
{% if item.raw is undefined %}
{% if item.role | d( item.by_role | d() ) %}
Explanation: Pin added by role: {{ item.role | d(item.by_role) }}
{% endif %}
Explanation: {{ (item.reason | d('Reason not specified')).split('\n') | join('\nExplanation: ') }}
{% if item.package | d() %}
{% if item.package is string %}
Package: {{ item.package }}
{% else %}
Package: {{ item.package | join(" ") }}
{% endif %}
{% elif item.packages | d() %}
{% if item.packages is string %}
Package: {{ item.packages }}
{% else %}
Package: {{ item.packages | join(" ") }}
{% endif %}
{% endif %}
{% if item.when | d(True) | bool %}
{% if item.version | d() %}
Pin: version {{ item.version + '*' }}
Pin-Priority: {{ item.priority | d(apt_preferences__priority_version) }}
{% else %}
Pin: {{ item.pin | d('release a=' + ansible_distribution_release + '-backports') }}
Pin-Priority: {{ item.priority | d(apt_preferences__priority_default) }}
{% endif %}
{% else %}
Explanation: Pin disabled by item.when condition
{% endif %}
{% elif item.raw | d() %}
{{ item.raw }}
{% endif %}

View file

@ -0,0 +1,18 @@
debops.apt_proxy - Manage HTTP/HTTPS/FTP proxy for APT with Ansible
Copyright (C) 2016-2017 Maciej Delmanowski <drybjed@gmail.com>
Copyright (C) 2016-2017 Robin Schneider <ypid@riseup.net>
Copyright (C) 2016-2017 DebOps <https://debops.org/>
SPDX-License-Identifier: GPL-3.0-only
This program 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.
This program 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 this program. If not, see https://www.gnu.org/licenses/

View file

@ -0,0 +1,194 @@
---
# .. vim: foldmarker=[[[,]]]:foldmethod=marker
# .. Copyright (C) 2016-2017 Maciej Delmanowski <drybjed@gmail.com>
# .. Copyright (C) 2016-2017 Robin Schneider <ypid@riseup.net>
# .. Copyright (C) 2016-2017 DebOps <https://debops.org/>
# .. SPDX-License-Identifier: GPL-3.0-only
# .. _apt_proxy__ref_defaults:
# debops.apt_proxy default variables [[[
# ======================================
# .. contents:: Sections
# :local:
#
# .. include:: ../../../../includes/global.rst
# Required packages [[[
# ---------------------
# .. envvar:: apt_proxy__base_packages [[[
#
# List of base packages to install.
apt_proxy__base_packages:
- '{{ ["python3-apt"]
if (apt_proxy__temporally_avoid_unreachable | bool)
else [] }}'
# ]]]
# ]]]
# Main configuration [[[
# ----------------------
# .. envvar:: apt_proxy__deploy_state [[[
#
# Specify if an APT proxy configuration should be present or absent. It will be
# automatically enabled if :envvar:`apt_proxy__http_url`, :envvar:`apt_proxy__https_url` or
# :envvar:`apt_proxy__ftp_url` are set in the host environment. See
# :ref:`debops.environment` role for more details.
apt_proxy__deploy_state: '{{ "present"
if (apt_proxy__http_url or
apt_proxy__https_url or
apt_proxy__ftp_url)
else "absent" }}'
# ]]]
# .. envvar:: apt_proxy__filename [[[
#
# Name of the configuration file used by the role in :file:`/etc/apt/apt.conf.d/`.
# You shouldn't change this name when ``debops.apt_proxy`` is enabled.
apt_proxy__filename: '00apt_proxy'
# ]]]
# ]]]
# HTTP proxy configuration [[[
# ----------------------------
# .. envvar:: apt_proxy__http_url [[[
#
# The URL of the HTTP proxy used by APT. If empty, HTTP proxy won't be enabled.
apt_proxy__http_url: '{{ ansible_env.http_proxy | d() }}'
# ]]]
# .. envvar:: apt_proxy__http_direct [[[
#
# List of hostnames to which APT should connect directly instead of through
# HTTP proxy.
apt_proxy__http_direct: []
# ]]]
# .. envvar:: apt_proxy__http_options [[[
#
# A YAML dictionary with key/value parameters for the HTTP proxy. Each key is
# the name of the ``Acquire::HTTP::*`` APT configuration option, and value is
# it's value. The keys containing ``::`` sequences need to be quoted.
# See :manpage:`apt.conf(5)` for more information. Example:
#
# .. code-block:: yaml
# :linenos:
#
# apt_proxy__http_options:
# User-Agent: 'Debian APT-HTTP/1.3'
#
apt_proxy__http_options: {}
# ]]]
# ]]]
# HTTPS proxy configuration [[[
# -----------------------------
# .. envvar:: apt_proxy__https_url [[[
#
# The URL of the HTTPS proxy used by APT. If empty, HTTPS proxy won't be
# enabled.
apt_proxy__https_url: '{{ ansible_env.https_proxy | d() }}'
# ]]]
# .. envvar:: apt_proxy__https_direct [[[
#
# List of hostnames to which APT should connect directly instead of through
# HTTPS proxy.
apt_proxy__https_direct: []
# ]]]
# .. envvar:: apt_proxy__https_options [[[
#
# A YAML dictionary with key/value parameters for the HTTPS proxy. Each key is
# the name of the ``Acquire::HTTPS::*`` APT configuration option, and value is
# it's value. The keys containing ``::`` sequences need to be quoted.
# See :manpage:`apt.conf(5)` for more information. Example:
#
# .. code-block:: yaml
# :linenos:
#
# apt_proxy__https_options:
# User-Agent: 'Debian APT-HTTP/1.3'
#
apt_proxy__https_options: {}
# ]]]
# ]]]
# FTP proxy configuration [[[
# ---------------------------
# .. envvar:: apt_proxy__ftp_url [[[
#
# The URL of the FTP proxy used by APT. If empty, FTP proxy won't be
# enabled.
apt_proxy__ftp_url: '{{ ansible_env.ftp_proxy | d() }}'
# ]]]
# .. envvar:: apt_proxy__ftp_direct [[[
#
# List of hostnames to which APT should connect directly instead of through
# FTP proxy.
apt_proxy__ftp_direct: []
# ]]]
# .. envvar:: apt_proxy__ftp_login [[[
#
# List of commands sent to the FTP proxy to login to it and specify what host
# to connect to. See :manpage:`apt.conf(5)` for more details.
apt_proxy__ftp_login:
- 'USER $(PROXY_USER)'
- 'PASS $(PROXY_PASS)'
- 'USER $(SITE_USER)@$(SITE):$(SITE_PORT)'
- 'PASS $(SITE_PASS)'
# ]]]
# .. envvar:: apt_proxy__ftp_options [[[
#
# A YAML dictionary with key/value parameters for the FTP proxy. Each key is
# the name of the ``Acquire::FTP::*`` APT configuration option, and value is
# it's value. The keys containing ``::`` sequences need to be quoted.
# See :manpage:`apt.conf(5)` for more information. Example:
#
# .. code-block:: yaml
# :linenos:
#
# apt_proxy__ftp_options:
# 'Proxy::Passive": 'true'
#
apt_proxy__ftp_options: {}
# ]]]
# ]]]
# Proxy online detection [[[
# --------------------------
# By default (Debian, DebOps), APT will fail when the configured proxy can not
# be reached.
#
# DebOps provides a script which hooks into APT using a
# ``Acquire::HTTP::Proxy-Auto-Detect`` script which checks if the proxy is
# currently reachable.
# With this, APT will fall back to "DIRECT" (no proxy) if the proxy
# is (temporally) not reachable.
# .. envvar:: apt_proxy__temporally_avoid_unreachable [[[
#
# Configures the behaviour if the proxy is not reachable.
# ``True`` will cause APT to silently ignore offline proxies.
# ``False`` will cause APT to return with an error if the proxy is offline.
# This is currently only supported for HTTPS and HTTP proxies.
apt_proxy__temporally_avoid_unreachable: False
# ]]]
# .. envvar:: apt_proxy__proxy_auto_detect [[[
#
# Value to set as ``Acquire::HTTP::Proxy-Auto-Detect``. Set to ``{{ omit }}`` to
# avoid setting it.
apt_proxy__proxy_auto_detect: '{{ "/usr/local/lib/get-reachable-apt-proxy"
if (apt_proxy__temporally_avoid_unreachable | bool)
else omit }}'
# ]]]
# ]]]
# ]]]

Some files were not shown because too many files have changed in this diff Show more