From: Hamatoma Date: Mon, 5 May 2025 11:02:08 +0000 (+0200) Subject: Initial commit X-Git-Url: https://gitweb.hamatoma.de/?a=commitdiff_plain;h=d5ef44efca249f7f07b4836b644fb0b378bc590c;p=ansknife.git Initial commit --- d5ef44efca249f7f07b4836b644fb0b378bc590c diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..51ee0d2 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1 @@ +# Change log of diff --git a/README.md b/README.md new file mode 100644 index 0000000..5f92e30 --- /dev/null +++ b/README.md @@ -0,0 +1,37 @@ +Project ansknife + +# Description +A collection of predefined tasks, playbooks, scripts and other. + +With that project you can create a server definition project in a short time. + +# The Project Name +ansknife is the short form of Ansible swiss knive + +# Preconditions +- we support usage of NGINX, MARIADB, PHP, LetsEncrypt + +# Glossar +- webapp: A web application: a webserver application with PHP source in a directory tree, MariaDB database and Nginx configuration + +# Creation of a New Server Definition Project +The server name may be "colibri", we take the name also for the new project. +- go to the base directory, e.g. /home/workspace/ansible/ +- clone the ansknife project: /home/workspace/ansible/ansknife will be created +- start a playbook to create the project colibri: /home/workspace/ansible/colibri will be created +``` +BASE=/home/workspace/ansible +GIT_REP=ssh://myserver.com/git/repo +PROJ=colibri +cd $BASE +git clone $GIT_REP/ansknife.git +cd ansknife +ansible-playbook playbook/create_project -e project=$PROJ +cd ../$PROJ +./SetRights +``` +- edit all files in $BASE/colibri/vars: configure your project by choosing valid/meaningful variable values +- edit $BASE/colibri/README.md, $BASE/colibri/CHANGELOG.md, inventory + + + diff --git a/SetRights b/SetRights new file mode 120000 index 0000000..c319c58 --- /dev/null +++ b/SetRights @@ -0,0 +1 @@ +scripts.templates/SetRights \ No newline at end of file diff --git a/ansible.cfg b/ansible.cfg new file mode 100644 index 0000000..930e818 --- /dev/null +++ b/ansible.cfg @@ -0,0 +1,3 @@ +[defaults] +inventory = ./inventory + diff --git "a/docu/de/01_\303\274berblick_ansible.md" "b/docu/de/01_\303\274berblick_ansible.md" new file mode 100644 index 0000000..db3d208 --- /dev/null +++ "b/docu/de/01_\303\274berblick_ansible.md" @@ -0,0 +1,37 @@ +# Überblick Ansible + +Ansible ist ein Computerkonfigurationswerkzeug. + +## Begriffe (Glossar) + +atomare Task +: Eine atomare Task ist eine Aufgabe, die nicht mehr unterteilt werden kann +: Ansible liefert viele vordefinierte atomare Tasks mit. +: Beispiel: die Task "copy" +: Jede Taskt hat spezifische Parameter, die die Aufgabe genau festlegen + +komplexe Task +: eine komplexe Task ist eine Folge von atomaren Tasks, definiert in genau einer Datei + +Playbook +: Ein Playbook ist eine Datei, in der eine zu lösende komplexe Aufgabe komplett beschrieben ist +: Im Playbook können atomare Tasks verwendet werden +: Im Playbook können komplexe Tasks statisch eingefügt ("include_task") oder dynamisch eingefügt werden ("import_task") + +Fact +: Ein "Fact" ist eine Variable. +: Eine Variable wird definiert (siehe folgendes Kapitel) +: Facts können ihren Wert ändern, z.B. mit set_fact +: Eine Variable kann in Playbooks, atomaren und komplexen Tasks verwendet werden, mit der Syntax {{ fact_name }} + + +## Facts (Variablen) + +In Ansible werden Variablen mit "facts" bezeichnet. + +# Definition +Es gibt viele Möglichkeiten, Facts zu definieren: +- direkt im Playbook: Dort gibt es den Bereich: -vars: +``` +--- +- im Playbook und ein \ No newline at end of file diff --git a/docu/de/05_yaml.md b/docu/de/05_yaml.md new file mode 100644 index 0000000..5b3ba25 --- /dev/null +++ b/docu/de/05_yaml.md @@ -0,0 +1,62 @@ +# Einführung in YAML + +Die Konfigurierung von Ansible erfolgt mit dem Dateiformat "YAML". + +Das ist eine gut lesbare, kommentierbare Textdatei. + +## Die wichtigsten Elemente: + +``` +--- +# das ist ein Kommentar +# die obigen "---" leiten die YAML-Datei ein. Das ist optional + +# Es folgt eine Liste mit dem Namen "hosts" und den Elementen "mars", "venus", "saturn" +hosts: + - mars + - venus + - saturn + +# Die Liste kann auch so geschrieben werden: +hosts: [mars, venus, saturn] + +# Es folgt eine Struktur namens "users" mit benannten Elementen "name" und "user_id": +# Der Werte für den Schlüssel "jonny" ist 101, der für den Schlüssel "eve" ist 102: +users: + jonny: 101 + eve: 102 +``` +Das kann auch in verkürzter Form formuliert werden: +``` +users: { jonny: 101, eve: 102 } +``` +'''Wichtig''': + +## Einrückungen + +Die Hierarchie der Definitionen mittels Einrückung muss strikt eingehalten werden: immer 2 Leerzeichen je Einrückungsschritt. + +## Strings +Ein String wird entweder mit "" oder mit '' geschrieben. +Wenn der Inhalt keine Leerzeichen und keine Sonderzeichen enthält, können die Anführungszeichen weggelassen werden. +``` +user: [jonny, eve] +``` +ist nur die Abkürzung von +``` +"user": ["jonny", "eve"] +``` + +## Mehrzeilige Strings +Es können auch mehrzeilige Werte eingetragen werden: +``` +content: | + Diese Zeile gehört zu content + Auch diese 2.te Zeile gehört dazu + Das ist die letzte Zeile, erkennbar an der geringeren Einrückung der Folgezeile +name: 123 +``` +'''Merke''': +- "|" leitet den mehrzeilen Wert ein +- Die Einrückung vor "Diese Zeile..." gehört nicht zum Inhalt. +- Das Ende der Einrückung wird durch eine Zeile mit geringeren Einrücktiefe bestimmt. diff --git a/docu/de/10_einrichten_ansible_auf_workstation.md b/docu/de/10_einrichten_ansible_auf_workstation.md new file mode 100644 index 0000000..b928e67 --- /dev/null +++ b/docu/de/10_einrichten_ansible_auf_workstation.md @@ -0,0 +1,50 @@ +# Einrichten einer Ansible-Umgebung auf einer Workstation mit GUI + +# Systemvoraussetzungen +Diese Beschreibung bezieht sich auf eine Linuxumgebung eines Debian-Systems. + +## Paketinstallation +``` +apt install ansible openssh +``` +## Installation Entwicklungsumgebung +Es ist sinnvoll, die Entwicklungsumgebung "Microsoft Visual Code" zu verwenden. + +- Gehe auf https://code.visualstudio.com/download +- Lade das *.deb-Paket herunter +``` +dpkg -i code*.deb +``` + +## SSH vorbereiten +Ansible verwendet SSH, um mit den entfernten Server zu kommunizieren. Daher muss ein SSH-Zugriff ermöglicht werden. + +### Erstellen eines Zertifikats +``` +ssh-keygen -t rsa -b 4096 +``` +Es wird ein Passwort abgefragt, mit dem dann das Zertifikat geschützt ist. + +'''Wichtig''': Unbedingt ein Passwort vergeben, da der Zugriff auf den entfernten Server mit Root-Rechten erfolgt, also wirklich alles offenlegt. + +## Verwendung von ssh-agent + +Damit das Passwort des Zertifikats nur einmal pro Session eingeben werden muss, kann das Programm ssh-agent verwendet werden. Dieses wird normalerweise automatisch gestartet. + +Hier ein Test, ob es läuft: +``` +ps aux | grep ssh-agent +''' +Wird hier ein Prozess angezeigt, läuft ssh-agent. + +Wenn nicht: +``` +eval $(ssh-agent) +``` + +Der Schlüssel wird so "angemeldet": +``` +ssh-add +``` +Hier wird das Passwort des Zertifikats abgefragt. Dieses ist dann bis zum Ende der Session "gültig", d.h. es wird nicht mehr benötigt. + diff --git a/docu/de/20_einrichten_ansible_auf_server.md b/docu/de/20_einrichten_ansible_auf_server.md new file mode 100644 index 0000000..847ba36 --- /dev/null +++ b/docu/de/20_einrichten_ansible_auf_server.md @@ -0,0 +1,75 @@ +# Einrichten von Ansible auf dem entfernten Server + +## Installation +``` +apt install ansible openssh +``` + +## Einrichten des Benutzers ansadm +- Für den Zugriff der Workstations wird der Benutzer ansadm mit festgelegter Id angelegt + - Am besten kein Passwort vergeben: Wenn das Passwort abgefragt wird, "x" eingeben. + - Wenn die Bestätigung abgefragt wird, "Y" eingeben + - Bei "Nochmal versuchen?" mit N antworten +- Es wird der automatische Zugang mittels Zertifikat für den Benutzer der Workstation erlaubt +- Dies geschieht in der Datei authorized_keys. +``` +adduser ansadm --uid=260 +DIR=/home/ansadm/.ssh +mkdir $DIR +chmod 700 $DIR +touch $DIR/authorized_keys +chmod 600 $DIR/authorized_keys +chown ansadm -R $DIR +``` + +### sudo ermöglichen +Dazu muss die Datei /etc/sudoers ediert werden: +Als Editor ist vi oder nano möglich: +``` +nano /etc/sudoers +``` +- Ans Ende der Datei blättern (Bild runter) +- Oberhalb die Zeile "root ALL..." suchen +- Nach dieser Zeile eintragen: +``` +ansadm ALL=NOPASSWD: ALL +``` +- Strg-o drücken (Speichern) +- Strg-x drücken (Beenden) + +### Ermitteln des öffentlichen Schlüssels: +Auf der Workstation rocket mit dem Benutzer jonny: +``` +USER=jonny +cat /home/$USER/.ssh/*.pub +``` +Es wird ausgegeben: +``` +ssh-rsa AAAAB3NzaC1yc2EA ... BCr8AEZIj jonny@rocket +``` + +## Eintrag eines Workstation-Benutzers für den Zugang zu ansadm +Für den Beispielsbenutzer jonny, der sich automatisch als ansadm anmelden will, muss ein Eintrag in die Datei /home/ansadm/.ssh/authorized_keys gemacht werden. + +Dazu wird der öffentliche Schlüssel einfach in die Datei authorized_keys eingetragen. +``` +echo >>/home/ansadm/.ssh/authorized_keys "ssh-rsa AAAAB3NzaC1yc2EA ... BCr8AEZIj jonny@rocket" +Dazu wird der öffentliche Schlüssel einfach in die Datei authorized_keys eingetragen. +``` + +## Test für Funktion +Um zu testen, ob der Zugriff von der Workstation auf den Server rocket funktioniert: + +'''Auf der Workstation:''' + +Es muss jetzt ein Anmelden als User ansadm möglich sein: +``` +ssh ansadm@rocket +``` + +Ansible testen: +``` +ansible -i inventory rocket -m ping +``` +Das setzt voraus, dass schon ein Projekt für den Server rocket aufgesetzt wurde, mit dem Inventory namens inventory. + diff --git a/docu/de/50_neues_ansknife_projekt.md b/docu/de/50_neues_ansknife_projekt.md new file mode 100644 index 0000000..80d4571 --- /dev/null +++ b/docu/de/50_neues_ansknife_projekt.md @@ -0,0 +1,117 @@ +# Einrichten eines neuen Ansible-Projekts mittels ansknife + +Das Projekt ansknife bietet die Möglichkeit, schnell ein vollständiges Ansible-Projekt aufzusetzen, indem es eine funktionierende Struktur erzeugen kann, in der nur projektspezifische Dinge angepasst werden müssen. + +## Installation des Projekts ansknife + +``` +BASE=/home/ws/ansible +cd $BASE +git clone +``` +Es wird damit das Verzeichnis $BASE/ansknife erstellt. + +## Erstellen des Projekts rocket +Mit dem Projekt rocket soll eine Ansible-Konfiguration für den Beispielserver "rocket" erstellt werden. + +Dieses Projekt muss '''immer''' als Nachbarverzeichnis zu $BASE/ansknife sein, wegen der relativen symbolischen Links. Das geschieht automatisch mit der folgenden Installation. +``` +BASE=/home/ws/ansible +cd $BASE/ansknife +ansible-playbook playbook/create_project \ + -e project=rocket \ + -e postfix_mode=send_only \ + -e ansible_base=$BASE +``` +Hier wird die einfachste Variante der postfix-Installation gewählt. Weitere Varianten siehe 60_postfix_varianten.md + +Das Ergebnis des obigen Aufrufs ist ein Nachbarverzeichnis von ansknife namens rocket: +``` +cd $BASE/rocket +code . +``` +Damit wird die Entwicklungsumgebung im richtigen Projekt gestartet. + +## Anpassen des Projekts rocket +Anpassungen müssen in folgenden Unterverzeichnissen durchgeführt werden: +- vars +- templates.local +- tasks +- playbooks + +### Anpassungen in rocket/vars +Es müssen alle Dateien angeschaut werden und projektspezifische Anpassungen vorgenommen werden. + +#### vars/common.yaml +Unbedingt überprüfen/anpassen: +- remote_www_directory: "/home/www" + - Das ist das Verzeichnis, das dann mit /srv/www erreicht wird. +- postfix_host: "hero.infeos.it" + - Das muss eine gültige DNS-Adresse sein +- postfix_domain: "infeos.it" +- postfix_receipient_email: "mail.rocket@example.com" + - Das wird als Absenderadresse für Mails von postfix benutzt, z.B. für "Post kann nicht zugestellt werden". +webmaster_email: "web.hero@hamatoma.de" + - Das wird als Absenderadresse für Mails vom System benutzt, z.B. wenn ein Cronjob missglückt + +### vars/user.yaml +Hier werden die Benutzer auf dem Server rocket eingetragen. +Benutzer, die schon im System angelegt sind, müssen die ID bekommen, die sie schon haben. +``` +id jonny +``` +Alle neuen User sollten Ids > 1500 haben, damit sie nicht mit den automatisch generierten User-Ids kollidieren. + +Wenn ein User auch root-Rechte bekommen soll, dann auch in die zweite Liste "user_sudo_members" eintragen. + +#### vars/mysql.yaml +Hier wird ein Administrator definiert, der Zugriff auf alle MySql-Datenbanken hat. +Hier normalerweise nichts ändern. + +#### vars/mysql_vault.yaml +Hier muss ein Passwort für den Adminstrator eingetragen werden. + +#### vars/firewall +Änderung nur, wenn die Firewall auch benutzt wird. + +Dann muss hier das Interface eingetragen werden, mit dem das Internet erreichbar ist: + +``` +ip addr +``` +Listet alle Interfaces auf, normalerweise ist es eines von den Ethernet-Interfaces, die mit "en" beginnen, z.B. "enp5s0". Erkennbar ist das an der öffentlichen IP-Adresse, hier "178.64.33.44" + +Ausgabe: +``` +1: lo: mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000 + link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 + inet 127.0.0.1/8 scope host lo + valid_lft forever preferred_lft forever + inet6 ::1/128 scope host noprefixroute + valid_lft forever preferred_lft forever +2: enp5s0: mtu 1500 qdisc mq state UP group default qlen 1000 + link/ether a0:36:bc:cb:94:be brd ff:ff:ff:ff:ff:ff + inet 178.64.33.44/26 brd 178.63.21.63 scope global enp5s0 + valid_lft forever preferred_lft forever + inet6 2a01:2f8:141:45b9::2/64 scope global + valid_lft forever preferred_lft forever + inet6 fe80::a236:bcfc:fecc:95be/64 scope link + valid_lft forever preferred_lft forever +2: enp7s2: mtu 1500 qdisc mq state UP group default qlen 1000 + link/ether a0:36:bc:cb:94:be brd ff:ff:ff:ff:ff:ff + inet 10.100.100.39/26 brd 178.63.21.63 scope global enp5s0 + valid_lft forever preferred_lft forever + inet6 2a01:2f8:131:55b9::2/64 scope global + valid_lft forever preferred_lft forever + inet6 fe80::a232:b8fc:fecc:95b1/64 scope link + valid_lft forever preferred_lft forever +``` + +#### vars/php.yaml + +### Anpassungen in rocket/templates.local +Es müssen alle Dateien angeschaut werden und projektspezifische Anpassungen vorgenommen werden. + +# + + diff --git a/inventory b/inventory new file mode 100644 index 0000000..3d37642 --- /dev/null +++ b/inventory @@ -0,0 +1,7 @@ +[hosts] +localhost +[hosts:vars] +ansible_python_interpreter=/usr/bin/python3 +ansible_ssh_common_args=-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null +ansible_user=ansadm +ansible_become=yes diff --git a/playbooks.templates/i_10_basic.yaml b/playbooks.templates/i_10_basic.yaml new file mode 100644 index 0000000..f063cf4 --- /dev/null +++ b/playbooks.templates/i_10_basic.yaml @@ -0,0 +1,44 @@ +--- +- name: Playbook to prepare the system + hosts: all + vars: + hostname: "{{ inventory_hostname | regex_search('[0-9a-zA-Z_]+')}}" + tasks: + - name: Prepare /media/trg directory + ansible.builtin.file: path=/media/trg state=directory + - name: Prepare /media/tmp directory + ansible.builtin.file: path=/media/trg state=directory + - name: Prepare /media/fs.cave directory + ansible.builtin.file: path=/media/fs.cave state=directory + - name: Prepare /media/fs.sys directory + ansible.builtin.file: path=/media/fs.sys state=directory + - name: Prepare /usr/local/bin directory + ansible.builtin.file: path=/usr/local/bin state=directory + - name: Prepare /usr/local/bin/local directory + ansible.builtin.file: path=/usr/local/bin/local state=directory + - name: Prepare /usr/share/pyrshell directory + ansible.builtin.file: path=/usr/share/pyrshell state=directory + - name: Symbolic link to local directory + ansible.builtin.file: src=/usr/local/bin/local dest=/usr/local/bin/{{hostname}} state=link + - name: Unpack a tar into /usr/local/bin/local + unarchive: src=../resources/needed.tgz dest=/usr/local/bin + - name: Symbolic link to /p + ansible.builtin.file: src=/usr/local/bin/std.profile dest=/p state=link + - name: Unpack a tar into /usr/share/pyrshell + unarchive: src=../resources/rsh.tgz dest=/usr/share/pyrshell + - name: Create configuration directory + ansible.builtin.file: path=/etc/config state=directory mode=0700 + - name: Create webapp.d + ansible.builtin.file: path=/etc/config/webapps.d state=directory mode=0700 + - name: Create directories in /media + ansible.builtin.file: path=/media/{{ item }} state=directory mode=0700 + with_items: [src, tmp, trg] + - name: install standard packages + apt: + name: "{{ item }}" + state: present + update_cache: true + cache_valid_time: 3600 + with_items: [htop, rsync, sudo, curl, iotop, jnettop, ssl-cert, ca-certificates, zram-tools, nfs-kernel-server, tmux] + + \ No newline at end of file diff --git a/playbooks.templates/i_11_user.yaml b/playbooks.templates/i_11_user.yaml new file mode 100644 index 0000000..4f44da7 --- /dev/null +++ b/playbooks.templates/i_11_user.yaml @@ -0,0 +1,65 @@ +--- +- name: Playbook to create the users + hosts: all + vars: + hostname: "{{ inventory_hostname | regex_search('[0-9a-zA-Z_]+')}}" + vars_files: + - ../vars/common.yaml + - ../vars/users.yaml + tasks: + # === human users + - name: Create human users + ansible.builtin.user: + name: "{{ item.key }}" + state: present + uid: "{{ item.value }}" + with_dict: "{{ user_humans }}" + - name: Enable sudo for human users + ansible.builtin.lineinfile: + path: /etc/sudoers + state: present + regexp: '^{{ item }}\s+ALL' + insertafter: '^root\s+ALL' + line: '{{ item }} ALL=NOPASSWD: ALL' + with_items: "{{ user_sudo_members }}" + + # === system users, used for rsync communication with other sites + - name: Create user bupsrv + ansible.builtin.user: name=bupsrv state=present uid=201 + - name: Create user bupsupply + ansible.builtin.user: name=bupsupply state=present uid=202 + - name: Create ssh directory for bupsupply + ansible.builtin.file: path=/home/bupsupply/.ssh state=directory group=bupsupply owner=bupsupply mode=0700 + - name: Create authorized_keys for bupsupply + ansible.builtin.file: path=/home/bupsupply/.ssh/authorized_keys state=touch group=bupsupply owner=bupsupply mode=0600 + - name: Create user bupwiki + ansible.builtin.user: name=bupwiki state=present uid=203 + - name: Create user buptmp + ansible.builtin.user: name=buptmp state=present uid=204 + - name: Create ssh directory for buptmp + ansible.builtin.file: path=/home/buptmp/.ssh state=directory group=buptmp owner=buptmp mode=0700 + - name: Create authorized_keys for buptmp + ansible.builtin.file: path=/home/buptmp/.ssh/authorized_keys state=touch group=buptmp owner=buptmp mode=0600 + - name: Create user extdata + ansible.builtin.user: name=extdata state=present uid=211 + - name: Create user extcloud + ansible.builtin.user: name=extcloud state=present uid=212 + - name: Create user extbup + ansible.builtin.user: name=extbup state=present uid=213 + - name: Create ssh directory for ext* users + ansible.builtin.file: + path: /home/{{ item }}/.ssh + state: directory + group: "{{ item }}" + owner: "{{ item }}" + mode: "0700" + with_items: [extdata, extcloud, extbup] + - name: Create keys for the ext* users + ansible.builtin.openssh_keypair: + path: /home/{{ item }}/.ssh/id_rsa + type: rsa + owner: "{{ item }}" + group: "{{ item }}" + mode: '0600' + passphrase: '' + with_items: [extdata, extcloud, extbup] diff --git a/playbooks.templates/i_15_server_packages.yaml b/playbooks.templates/i_15_server_packages.yaml new file mode 100644 index 0000000..c16e231 --- /dev/null +++ b/playbooks.templates/i_15_server_packages.yaml @@ -0,0 +1,19 @@ +--- + +- name: Playbook to install server packages + hosts: all + vars: + hostname: "{{ inventory_hostname | regex_search('[0-9a-zA-Z_]+')}}" + vars_files: + - ../vars/common.yaml + - ../vars/packages.yaml + tasks: + - name: install server packages + apt: + name: "{{ item }}" + state: present + update_cache: true + cache_valid_time: 3600 + with_items: "{{ packages_list }}" + + \ No newline at end of file diff --git a/playbooks.templates/i_17_configuration.yaml b/playbooks.templates/i_17_configuration.yaml new file mode 100644 index 0000000..b0be4ca --- /dev/null +++ b/playbooks.templates/i_17_configuration.yaml @@ -0,0 +1,26 @@ +--- +- name: Playbook to configure some system settings in configuration files + hosts: all + vars_files: + - ../vars/common.yaml + tasks: + - name: limit the total size of journald logs + lineinfile: + dest: /etc/systemd/journald.conf + regexp: ^#?\s*SystemMaxUse= + line: SystemMaxUse={{ systemd_journal_system_max_use }} + notify: + - restart systemd-journald + + - name: limit the size of each journald log file + lineinfile: + dest: /etc/systemd/journald.conf + regexp: ^#\s*SystemMaxFileSize= + line: SystemMaxFileSize={{ systemd_journal_system_max_file_size }} + notify: + - restart systemd-journald + handlers: + - name: restart systemd-journald + ansible.builtin.systemd: + name: systemd-journald + state: restarted \ No newline at end of file diff --git a/playbooks.templates/i_20_nginx.yaml b/playbooks.templates/i_20_nginx.yaml new file mode 100644 index 0000000..a404f0c --- /dev/null +++ b/playbooks.templates/i_20_nginx.yaml @@ -0,0 +1,61 @@ +--- +- name: Install and configure NGINX + hosts: all + become: yes + + vars: + user: www-data + hostname: "{{ inventory_hostname }}" + log_name: "{{ inventory_hostname | regex_search('[0-9a-zA-Z_]+') }}" + vars_files: + - ../vars/common.yaml + - ../vars/ssl-certificate.yaml + tasks: + - name: Install nginx + ansible.builtin.apt: + name: nginx + state: latest + update_cache: yes + - name: Prepare letsencrypt home directory + ansible.builtin.file: path=/srv/www/letsencrypt/.well-known/acme-challenge state=directory + - name: Add test file1 + ansible.builtin.copy: src=../templates.fix/nginx/hi1.txt dest=/srv/www/letsencrypt/.well-known/ + - name: Add test file2 + ansible.builtin.copy: src=../templates.fix/nginx/hi2.txt dest=/srv/www/letsencrypt/.well-known/acme-challenge/hi2.txt + - name: Prepare letsencrypt + ansible.builtin.copy: + src: ../templates.fix/nginx/letsencrypt.conf + dest: /etc/nginx/snippets + - name: add HTTP-variables + ansible.builtin.copy: + src: ../templates.local/nginx/http.conf + dest: /etc/nginx/snippets + - name: create a www directory + ansible.builtin.file: path={{ remote_www_directory }} state=directory owner=root group=www-data + - name: create the /srv/www link + ansible.builtin.file: src={{ remote_www_directory }} dest=/srv/www state=link + - name: Ensure nginx is running + ansible.builtin.systemd: + name: nginx + state: started + enabled: yes + - name: create a test virtual hosts + ansible.builtin.template: + src: ../templates.fix/nginx/test.site + dest: /etc/nginx/sites-available/{{hostname}} + - name: activate by link in sites-enabled + ansible.builtin.file: + src: /etc/nginx/sites-available/{{hostname}} + dest: /etc/nginx/sites-enabled/{{hostname}} + state: link + - name: create a ssh-certificate + ansible.builtin.command: openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout /etc/ssl/private/{{hostname}}.key -out /etc/ssl/certs/{{hostname}}.pem -subj "/C={{ssl_country}}/ST={{ssl_state}}/L={{ssl_locality}}/O={{ssl_organization}}/CN={{hostname}}" + args: + creates: /etc/ssl/private/{{hostname}}.key + - name: create a document root + ansible.builtin.file: dest=/srv/www/{{hostname}} state=directory owner=www-data group=www-data + - name: create a test index.html + template: src=../templates.fix/nginx/index.html dest=/srv/www/{{hostname}}/index.html + - name: create a test index.php + ansible.builtin.copy: src=../templates.fix/nginx/index.php dest=/srv/www/{{hostname}}/index.php + diff --git a/playbooks.templates/i_21_nginx_sites.yaml b/playbooks.templates/i_21_nginx_sites.yaml new file mode 100644 index 0000000..de6704d --- /dev/null +++ b/playbooks.templates/i_21_nginx_sites.yaml @@ -0,0 +1,25 @@ +--- +# This playbook maintains the NGINX configuration files in /etc/nginx//sites_available +- name: Installs and configures the sites served by + hosts: all + vars_files: + - ../vars/common.yaml + - ../vars/webapps.yaml + tasks: + - name: Set the NGINX configuration files + copy: + src: "../templates.local/nginx/sites/{{ item.webapp_name }}" + dest: "/etc/nginx/sites-available/" + owner: root + group: root + mode: 0644 + with_list: "{{ webapps_list }}" + notify: + - reload nginx + handlers: + - name: reload nginx + service: + name: nginx + state: reloaded + enabled: yes + \ No newline at end of file diff --git a/playbooks.templates/i_30_mariadb.yaml b/playbooks.templates/i_30_mariadb.yaml new file mode 100644 index 0000000..6bc7362 --- /dev/null +++ b/playbooks.templates/i_30_mariadb.yaml @@ -0,0 +1,16 @@ +--- +- name: Playbook to install and configure mariadb + hosts: all + tasks: + - name: Install mariadb + ansible.builtin.apt: + name: + - "mariadb-server" + - "python3-mysqldb" + state: latest + update_cache: yes + - name: Ensure mariadb is running + ansible.builtin.systemd: + name: mysqld + state: started + enabled: yes \ No newline at end of file diff --git a/playbooks.templates/i_40_php8.2.yaml b/playbooks.templates/i_40_php8.2.yaml new file mode 100644 index 0000000..5ae3231 --- /dev/null +++ b/playbooks.templates/i_40_php8.2.yaml @@ -0,0 +1,54 @@ +--- +- name: Playbook to install and configure PHP (all versions from 7.4) + # optional facts: + # php_version: the wanted version. Default: "8.2" + hosts: all + vars: + - php_version: "8.2" + vars_files: + - ../vars/php.yaml + pre_tasks: + - name: + ansible.builtin.apt: + name: gpg + state: present + update_cache: true + - name: add packages.sury.org (Debian case) + block: + - name: add gpg repo key + ansible.builtin.apt_key: + url: 'https://packages.sury.org/php/apt.gpg' + state: present + + - name: add apt repo + ansible.builtin.apt_repository: + repo: 'deb https://packages.sury.org/php/ {{ ansible_distribution_release|lower }} main' + state: present + filename: php + when: ansible_distribution == 'Debian' + tasks: + - name: Install PHP {{php_version}} and common modules + ansible.builtin.apt: + name: "{{ item }}" + state: present + update_cache: true + cache_valid_time: 3600 + with_items: "{{ php_packages + php_additional_packages }}" + - name: Define PHP variables in php.ini + ansible.builtin.ini_file: + dest: /etc/php/{{php_version}}/fpm/php.ini + section: "{{ item.section }}" + option: "{{ item.option }}" + value: "{{ item.value }}" + with_items: + "{{ php_ini_settings }}" + - name: Ensure PHP-FPM is running + ansible.builtin.systemd: + name: php{{php_version}}-fpm + state: started + enabled: yes + - name: Ensure Redis is running + ansible.builtin.systemd: + name: redis-server + state: started + enabled: yes \ No newline at end of file diff --git a/playbooks.templates/i_50_git_server.yaml b/playbooks.templates/i_50_git_server.yaml new file mode 100644 index 0000000..7cf1393 --- /dev/null +++ b/playbooks.templates/i_50_git_server.yaml @@ -0,0 +1,39 @@ +--- + +- name: Playbook to install and configure a git server + hosts: all + vars: + git_user: git + tasks: + - name: install packages + apt: + name: git + state: present + - name: create git user + user: + name: "{{ git_user }}" + state: present + shell: /usr/bin/git-shell + home: /home/git + - name: Prepare SSH directory for git + file: + path: /home/git/.ssh + state: directory + group: "{{git_user}}" + owner: "{{git_user}}" + mode: "0700" + - name: Prepare authorized_keys + file: + path: /home/git/.ssh/authorized_keys + state: touch + group: "{{git_user}}" + owner: "{{git_user}}" + mode: "0600" + - name: Prepare repository directory + file: + path: /home/git/repo + state: directory + group: "{{git_user}}" + owner: "{{git_user}}" + + \ No newline at end of file diff --git a/playbooks.templates/i_60_postfix.yaml b/playbooks.templates/i_60_postfix.yaml new file mode 100644 index 0000000..b782279 --- /dev/null +++ b/playbooks.templates/i_60_postfix.yaml @@ -0,0 +1,89 @@ +--- +- name: Installs the postfix mail server + # Needed facts (variables): + hosts: all + vars: + postfix_domain: "{{ inventory_hostname }}" + vars_files: + - ../vars/common.yaml + tasks: + - name: install the packages + ansible.builtin.package: + name: "{{ item }}" + state: present + with_items: + - postfix + - mailutils + - name: build the main.cf + ansible.builtin.copy: + src: "../templates.local/postfix/send_only/{{ item }}" + dest: "/etc/postfix/{{ item }}" + mode: 0640 + owner: root + group: root + with_items: + - main.cf + - master.cf + notify: + - restart postfix + - name: create alias + template: + src: "../templates.local/postfix/aliases" + dest: "/etc/aliases" + mode: 0640 + owner: root + group: root + notify: + - restart postfix + - name: create virtual alias + template: + src: "../templates.local/postfix/virtual" + dest: "/etc/postfix/virtual" + mode: 0640 + owner: root + group: root + when: postfix_mode == "email_forwarding" + notify: + - restart postfix + - name: create the mailname file + ansible.builtin.copy: + content: | + {{ postfix_host }} + dest: "/etc/mailname" + mode: 0644 + owner: root + group: root + notify: + - restart postfix + - name: create a script to activate the virtual alias + ansible.builtin.copy: + content: | + #! /bin/bash + postmap virtual + dest: "/etc/postfix/Activate" + mode: 0700 + owner: root + group: root + when: postfix_mode == "email_forwarding" + notify: + - postalias + - name: Ensure postfix is running + ansible.builtin.systemd: + name: postfix + state: started + enabled: yes + handlers: + - name: restart postfix + ansible.builtin.service: + name: postfix + state: reloaded + - name: postalias + shell: postalias /etc/aliases + - name: postmap + shell: postmap /etc/postfix/virtual + + + + + + diff --git a/playbooks.templates/i_62_postfix_dkim.yaml b/playbooks.templates/i_62_postfix_dkim.yaml new file mode 100644 index 0000000..169064f --- /dev/null +++ b/playbooks.templates/i_62_postfix_dkim.yaml @@ -0,0 +1,38 @@ +--- +# Installs the the spam reducing staff for postfix: SPF DKIM DMARYC +# Needed facts (variables): +# - +# Needed role installation: +- hosts: all + vars_files: + - ../vars/common.yaml + - ../vars/antispam.yaml + tasks: + - name: install the packages + ansible.builtin.package: + name: "{{ item }}" + state: present + with_items: [ postfix-policyd-spf-python, opendkim, opendkim-tools, opendmarc] + # ====== SPF ====== + - name: Configure the SPF staff + ansible.builtin.include_tasks: ../tasks/t_spf_configuration.yaml + # ====== DKIM ====== + - name: Configure the DKIM staff + ansible.builtin.include_tasks: ../tasks/t_dkim.yaml + # ====== DMARC ====== + - name: Configure the DMARC staff + ansible.builtin.include_tasks: ../tasks/t_dmarc.yaml + handlers: + - name: restart opendkim + ansible.builtin.systemd: + name: opendkim + state: restarted + - name: restart opendmarc + ansible.builtin.systemd: + name: opendmarc + state: restarted + - name: restart postfix + ansible.builtin.systemd: + name: postfix + state: restarted + diff --git a/playbooks.templates/i_70_webapps.yaml b/playbooks.templates/i_70_webapps.yaml new file mode 100644 index 0000000..1244cb3 --- /dev/null +++ b/playbooks.templates/i_70_webapps.yaml @@ -0,0 +1,22 @@ +--- +- name: Install and configure web applications + hosts: all + vars_files: + - ../vars/common.yaml + - ../vars/ssl-certificate.yaml + - ../vars/webapps_vault.yaml + tasks: + - name: Prepare state.infeos.net + ansible.builtin.set_fact: + webapp_name: "state.infeos.net" + domain: "{{ webapp_name }}" + shortname: state_infeos + mysql_vault.yaml: swstateinfeos + db_type: mysql + db_host: localhost + db_user: swstateinfeos + db_password: "{{ vault_db_password }}" + php_version: "8.3" + - debug: + - name: create web application for {{ webapp_name }} + include_tasks: ../tasks/t_webapp_create.yaml diff --git a/playbooks.templates/i_80_firewalld.yaml b/playbooks.templates/i_80_firewalld.yaml new file mode 100644 index 0000000..0805023 --- /dev/null +++ b/playbooks.templates/i_80_firewalld.yaml @@ -0,0 +1,40 @@ +--- +- name: Installs the firewall firewalld +# Precondition: ansible-galaxy collection install ansible.posix + hosts: all + become: true + vars_files: + - ../vars/common.yaml + - ../vars/firewalld.yaml + tasks: + - name: ensure firewalld is installed + ansible.builtin.apt: + name: firewalld + state: present + + - name: allow ssh + ansible.posix.firewalld: + service: ssh + zone: public + permanent: true + state: enabled + immediate: yes + - name: ensure firewalld is running and enabled + ansible.builtin.service: + name: firewalld + state: started + enabled: true + + - name: set interface to public zone + ansible.posix.firewalld: + interface: "{{ firewall_iface_public }}" + zone: public + permanent: true + state: enabled + immediate: yes + + - name: define host specific firewall rules + ansible.builtin.include_tasks: "../tasks/t_firewalld_local.yaml" + handlers: + - name: firewall reload + ansible.builtin.command: firewall-cmd --reload \ No newline at end of file diff --git a/playbooks.templates/i_81_fail2ban.yaml b/playbooks.templates/i_81_fail2ban.yaml new file mode 100644 index 0000000..d30863e --- /dev/null +++ b/playbooks.templates/i_81_fail2ban.yaml @@ -0,0 +1,45 @@ +--- +- name: Installs the fail2ban package to protect the server from brute-force attacks + hosts: all + become: true + vars_files: + - ../vars/common.yaml + tasks: + - name: ensure fail2ban is installed + ansible.builtin.apt: + name: fail2ban + state: present + - name: Ensure fail2ban is running + ansible.builtin.systemd: + name: fail2ban + state: started + enabled: yes + - set_fact: + email_sender: "{{ webmaster_email }}" + ban_time_sec: 3600 + + - name: configure jail.conf + ansible.builtin.lineinfile: + dest: /etc/fail2ban/jail.conf + regexp: "{{ item.rexpr }}" + line: "{{ item.line }}" + loop: + - { rexpr: '^sender\s*=', line: 'sender = {{ email_sender }}' } + - { rexpr: '^bantime\s*=', line: 'bantime = {{ ban_time_sec }}' } + - { rexpr: '^banaction = iptables-multiport$', line: 'banaction = firewallcmd-ipset' } + notify: + - restart fail2ban + - name: configure /etc/fail2ban/jail.local + ansible.builtin.copy: + content: | + [DEFAULT] + banaction = firewallcmd-ipset + dest: /etc/fail2ban/jail.local + notify: + - restart fail2ban + handlers: + - name: restart fail2ban + ansible.builtin.systemd: + name: fail2ban + state: restarted + diff --git a/playbooks.templates/i_99_test.yaml b/playbooks.templates/i_99_test.yaml new file mode 100644 index 0000000..cd7de04 --- /dev/null +++ b/playbooks.templates/i_99_test.yaml @@ -0,0 +1,30 @@ +--- +- name: Install and configure with letsencrypt + hosts: localhost + become: yes + + vars: + user: www-data + hostname: "{{ inventory_hostname }}" + log_name: "{{ inventory_hostname | regex_search('[0-9a-zA-Z_]+') }}" + webapp_name: myapp5.example.com + vars_files: + - ../vars/webapps.yaml + tasks: + - name: Test regexpr + lineinfile: + dest: /tmp/wk.conf + regexp: '^{{ item }}\s*=\s*Fail' + line: "{{ item }} = False" + with_items: [x, y] + - name: Test Dateiänderung + ansible.builtin.copy: + content: | + Line 1 + Line 2 + Line 3 added + dest: /tmp/wk.yaml + - set_fact: + target_file: "/home/tmp/xxx.yyy" + - debug: + msg: "{{ '/tmp/' + (target_file | basename) }}" \ No newline at end of file diff --git a/playbooks.templates/lets_create.yaml b/playbooks.templates/lets_create.yaml new file mode 100644 index 0000000..e2e8e7f --- /dev/null +++ b/playbooks.templates/lets_create.yaml @@ -0,0 +1,16 @@ +--- +# Creates a letsencrypt certificate for a domain +# needed facts (variables) from commandline: (e.g. -e domain=example.com +# domain: the site domain name +- hosts: all + vars_files: + - ../vars/common.yaml + - ../vars/ssl-certificate.yaml + tasks: + - name: Check pre-requisites + fail: + msg: "The variable 'domain' must be defined: use -e domain=mydomain.com" + when: domain is not defined or domain == "" + - name: create certificate for {{ domain }} + ansible.builtin.include_tasks: ../tasks/t_lets_create.yaml + diff --git a/playbooks.templates/mysql_create_admin.yaml b/playbooks.templates/mysql_create_admin.yaml new file mode 100644 index 0000000..11d51c9 --- /dev/null +++ b/playbooks.templates/mysql_create_admin.yaml @@ -0,0 +1,9 @@ +--- +- name: Creates the MySQL administrator with all privileges for all databases + hosts: all + vars_files: + - ../vars/mysql_vault.yaml + - ../vars/mysql.yaml + tasks: + - name: Create the database adminstrator {{dba_name}} + import_tasks : ../tasks/t_mysql_create_admin.yaml diff --git a/playbooks.templates/mysql_create_db_and_user.yaml b/playbooks.templates/mysql_create_db_and_user.yaml new file mode 100644 index 0000000..035db8d --- /dev/null +++ b/playbooks.templates/mysql_create_db_and_user.yaml @@ -0,0 +1,14 @@ +--- +- name: Create MySQL database and user for a web application +# needed facts (variables) from commandline (e.g. -e db_name=webapp): +# db_name: name of the database +# db_host: the ip or name of the host hosting mysql. Normally: localhost +# db_user: name of the database user +# db_password: password of the database user + hosts: all + vars_files: + - ../vars/mysql_vault.yaml + - ../vars/mysql.yaml + tasks: + - name: Create the database adminstrator {{dba_name}} + import_tasks : ../tasks/t_mysql_db_and_user.yaml diff --git a/playbooks.templates/nginx_create_site.yaml b/playbooks.templates/nginx_create_site.yaml new file mode 100644 index 0000000..3eaa2c5 --- /dev/null +++ b/playbooks.templates/nginx_create_site.yaml @@ -0,0 +1,22 @@ +--- +- name: Creates the NGINX configuration file for a PHP site + # needed facts (variables) from commandline (e.g. -e domain=example.com -e force=true): + # domain: the site domain name + # optional facts: + # document_root: the document root of the site without path, e.g. example.com. Default: domain + # shortname: the short name of the site. Used for log file names + # php_version: the PHP version to use. Default: 8.3 + # force: if true, the site will be created even if it already exists. Default: false + hosts: all + vars_files: + - ../vars/common.yaml + - ../vars/ssl-certificate.yaml + tasks: + - name: Check pre-requisites + fail: msg="The variable 'domain' must be defined and not empty." + when: domain is not defined or domain == "" + - name: create certificate for {{ domain }} + import_tasks: ../tasks/t_ssl_create_certificate.yaml + - name: Create the NGINX configuration for {{domain}} + import_tasks: ../tasks/t_nginx_create_site.yaml + diff --git a/playbooks.templates/ssl_create_certificate.yaml b/playbooks.templates/ssl_create_certificate.yaml new file mode 100644 index 0000000..4571676 --- /dev/null +++ b/playbooks.templates/ssl_create_certificate.yaml @@ -0,0 +1,14 @@ +--- +- name: Creates a X509 certificate for a domain, needed for a letsencrypt certificate + # needed facts (variables) from the commandline: (e.g. ansible-playbook -e "domain=example.com") + # - domain: the domain name for the certificate + hosts: all + vars_files: + - ../vars/common.yaml + - ../vars/ssl-certificate.yaml + tasks: + - name: test pre-requisites + fail: msg="missing fact! domain. Use -e domain=..." + when: domain is not defined + - name: Creates a X509 certificate for {{domain}} + import_tasks : ../tasks/t_ssl_create_certificate.yaml diff --git a/playbooks.templates/webapp_backup.yaml b/playbooks.templates/webapp_backup.yaml new file mode 100644 index 0000000..e69de29 diff --git a/playbooks.templates/webapp_create.yaml b/playbooks.templates/webapp_create.yaml new file mode 100644 index 0000000..cd8253e --- /dev/null +++ b/playbooks.templates/webapp_create.yaml @@ -0,0 +1,20 @@ +--- +- name: Creates a database, a database user of a web application. + # Stores the data in configuration files (remote and local) + # needed facts (variables) from the command line: (e.g. -e webapp_name=webapp): + # - webapp_name: name of the web application + # - db_name: name of the database + # - db_host: the ip or name of the host hosting mysql. Normally: localhost + # - db_user: name of the databasded user + # - db_password: password of the database user + hosts: all + vars_files: + - ../vars/common.yaml + - ../vars/mysql_vault.yaml + - ../vars/mysql.yaml + tasks: + - name: test pre-requisites + fail: msg="missing facts! webapp_name, db_name, db_user, db_password. Use -e webapp_name=..." + when: webapp_name is not defined or db_name is not defined or db_user is not defined or db_password is not defined + - name: create the web application + import_tasks : ../tasks/t_webapp_create.yaml diff --git a/playbooks.templates/webapp_export.yaml b/playbooks.templates/webapp_export.yaml new file mode 100644 index 0000000..ebc5acc --- /dev/null +++ b/playbooks.templates/webapp_export.yaml @@ -0,0 +1,19 @@ +--- +- name: Dumps a database of a web application. + # needed facts (variables) from the command line: + # - webapp_name: name of the web application + # - target_file: name of the file where the database will be exported to. May be *.sql or *.sql.gz + hosts: all + vars: + fetch: false + vars_files: + - ../vars/common.yaml + - ../vars/mysql_vault.yaml + - ../vars/mysql.yaml + - ../vars/webapps.yaml + tasks: + - name: test pre-requisites + fail: msg="missing facts! webapp_name, target_file. Use -e webapp_name=..." + when: webapp_name is not defined or target_file is not defined + - name: create the web application + import_tasks : ../tasks/t_webapp_export.yaml diff --git a/playbooks.templates/webapp_import.yaml b/playbooks.templates/webapp_import.yaml new file mode 100644 index 0000000..0c7a424 --- /dev/null +++ b/playbooks.templates/webapp_import.yaml @@ -0,0 +1,22 @@ +--- +- name: Loads a database dump into the database of a web application. + # needed facts (variables) from the command line: (e.g. -e webapp_name=webapp) + # - webapp_name: name of the web application + # - target_file: name of the file where the database will be exported to. May be *.sql or *.sql.gz + # - webapps_list: the list defined in ../vars/webapps.yaml + hosts: all + vars: + backup: true + backup_file: "/tmp/{{ webapp_name }}.{{ now(fmt='%s') }}.sql.gz" + vars_files: + - ../vars/common.yaml + - ../vars/mysql_vault.yaml + - ../vars/mysql.yaml + - ../vars/webapps.yaml + tasks: + - name: test pre-requisites + fail: msg="missing facts! webapp_name, source_file. Use -e webapp_name=..." + when: webapp_name is not defined or source_file is not defined + + - name: create the web application + import_tasks : ../tasks/t_webapp_import.yaml diff --git a/playbooks/create_project.yaml b/playbooks/create_project.yaml new file mode 100644 index 0000000..4f0fa98 --- /dev/null +++ b/playbooks/create_project.yaml @@ -0,0 +1,136 @@ +--- +# Create a new project directory structure for an Ansible project. +# needed facts (variables) from the commandline: +# -e project=your_project_name +# -e postfix_mode=none|send_only|email_forwarding +# -e base_ansible=$(dirname $(realpath $(pwd))) +- hosts: localhost + become: true + tasks: + - name: check needed command line (variables + ansible.builtin.fail: + msg: "missing project or postfix_mode or base_ansible. + Example: -e project=your_project_name + Example: -e postfix_mode=none|send_only|email_forwarding + Example: -e base_ansible=$(dirname $(realpath $(pwd))) + Recommanded: -e base_ansible=$(dirname $(realpath $(pwd)))" + when: project is not defined or postfix_mode is not defined or base_ansible is not defined + - name: Check if project variable is defined + ansible.builtin.fail: + msg: "The 'project' variable is not defined. -e project=your_project_name" + when: project is not defined + - name: Prepare base directory for {{ project }} in {{ base_ansible }} + ansible.builtin.file: path={{ base_ansible }}/{{ project }} state=directory + - name: create sub directories for {{ project }} + ansible.builtin.file: path={{ base_ansible }}/{{ project }}/{{ item }} state=directory + with_items: [playbooks, docu, resources, roles, scripts, templates.local, tasks, vars] + - name: create the links into the resources directory + ansible.builtin.file: + src: "../../ansknife/resources/{{ item }}" + dest: "{{ base_ansible }}/{{ project }}/resources/{{ item }}" + force: yes + state: link + with_items: [needed.tgz, rsh.tgz] + - name: build link to the templates.fix directory + ansible.builtin.file: + src: "../ansknife/templates.fix" + dest: "{{ base_ansible }}/{{ project }}/templates.fix" + force: yes + state: link + delegate_to: localhost + - name: create the templates base directory + ansible.builtin.file: + path: "{{ base_ansible }}/{{ project }}/templates.local" + state: directory + - name: copy the filetree + ansible.builtin.shell: "cp -a {{ base_ansible }}/ansknife/templates.local/* {{ base_ansible }}/{{ project }}/templates.local/" + delegate_to: localhost + - name: remove superflous sub file tree antispam + ansible.builtin.file: + path: "{{ base_ansible }}/{{ project }}/templates.local/antispam" + state: absent + when: postfix_mode != 'email_forwarding' + delegate_to: localhost + - name: remove superflous sub file postfix/email_forwarding + ansible.builtin.file: + path: "{{ base_ansible }}/{{ project }}/templates.local/postfix/email_forwarding" + state: absent + when: postfix_mode != 'email_forwarding' + delegate_to: localhost + - name: prepare facts for templates + ansible.builtin.copy: + src: '../templates.vars/' + dest: "{{ base_ansible }}/{{ project }}/vars" + delegate_to: localhost + - name: Prepare facts for links in playbooks + ansible.builtin.set_fact: + src_dir: '../playbooks.templates' + src_relative: '../../ansknife/playbooks.templates' + dest_dir: "{{ base_ansible }}/{{ project }}/playbooks" + pattern: "*.yaml" + delegate_to: localhost + - name: Create links in playbooks + import_tasks: ../tasks.templates/t_link_wildcard.yaml + delegate_to: localhost + - name: Prepare facts for links in tasks + ansible.builtin.set_fact: + src_dir: '../tasks.templates' + src_relative: '../../ansknife/tasks.templates' + dest_dir: "{{ base_ansible }}/{{ project }}/tasks" + pattern: "*.yaml" + delegate_to: localhost + - name: Create links in tasks + ansible.builtin.import_tasks: ../tasks.templates/t_link_wildcard.yaml + - name: Prepare facts for links in scripts + set_fact: + src_dir: "../scripts.templates" + src_relative: '../../ansknife/scripts.templates' + dest_dir: "{{ base_ansible }}/{{ project }}/scripts" + pattern: "*" + delegate_to: localhost + - name: Create links in scripts + ansible.builtin.import_tasks: ../tasks.templates/t_link_wildcard.yaml + - name: create a README file + copy: + dest: "{{ base_ansible }}/{{ project }}/README.md" + content: | + Project {{ project }} + This is the README file for the {{ project }} project. + # Description + This project defines the software of the server + delegate_to: localhost + - name: create a CHANGELOG file + ansible.builtin.copy: + dest: "{{ base_ansible }}/{{ project }}/CHANGELOG.md" + content: | + # V0.1.0 + - Initial version + delegate_to: localhost + - name: create a the inventory (host definition) + ansible.builtin.copy: + dest: "{{ base_ansible }}/{{ project }}/inventory" + content: | + [hosts] + {{ project }}.example.com + [hosts:vars] + ansible_python_interpreter=/usr/bin/python3 + ansible_ssh_common_args=-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null + ansible_user=ansadm + ansible_become=yes + delegate_to: localhost + - name: create a the ansible configuration file + ansible.builtin.copy: + dest: "{{ base_ansible }}/{{ project }}/ansible.cfg" + content: | + [defaults] + inventory = ./inventory + delegate_to: localhost + - name: create project specific files + ansible.builtin.template: + src: "../templates.install/{{ item.src }}" + dest: "{{ base_ansible }}/{{ project }}/{{ item.path }}/{{ item.dest }}" + with_items: + - { src: project_ansible.md, dest: "{{ project }}_ansible", path: docu } + - { src: project_cron.yaml, dest: "{{ project }}_cron", path: playbooks } + - { src: t_firewalld_local.yaml, dest: "t_firewalld_local.yaml", path: tasks } + delegate_to: localhost diff --git a/playbooks/labor.yaml b/playbooks/labor.yaml new file mode 100644 index 0000000..5c3dbcd --- /dev/null +++ b/playbooks/labor.yaml @@ -0,0 +1,14 @@ +--- +# Create a new project directory structure for an Ansible project. +# needed facts (variables) from the commandline: +- hosts: localhost + become: true + tasks: + - name: prepare base target directory + ansible.builtin.file: + path: /tmp/ansknife + state: directory + - name: copy a file tree + ansible.builtin.copy: + src: ../templates.local/ + dest: /tmp/ansknife/ \ No newline at end of file diff --git a/resources/needed.tgz b/resources/needed.tgz new file mode 100644 index 0000000..a389986 Binary files /dev/null and b/resources/needed.tgz differ diff --git a/resources/rsh.tgz b/resources/rsh.tgz new file mode 100644 index 0000000..3ff6405 Binary files /dev/null and b/resources/rsh.tgz differ diff --git a/scripts.templates/AddPassword b/scripts.templates/AddPassword new file mode 100755 index 0000000..c5ad692 --- /dev/null +++ b/scripts.templates/AddPassword @@ -0,0 +1,23 @@ +#! /bin/bash +# Ansible controlled: do not change on remote server + +NAME=$1 +VALUE=$2 +PW_SAFE=resources/vaults.yaml +function usage() { + echo "Usage: $0 NAME VALUE" + echo "Add a password to the keyring" + echo " NAME: The name of the password" + echo " VALUE: The value of the password" + echo "Example: $0 dba_password mysecret" + echo "+++ $*" +} +if [ -z "$VALUE" ]; then + Usage "missing VALUE" +else + if [ ! -f "$PW_SAFE" ]; then + echo "= creating a password safe: $PW_SAFE" + ansible-vault create $PW_SAFE + fi + ansible-vault encrypt_string >$PW_SAFE "$VALUE" --name "$NAME" +fi diff --git a/scripts.templates/CreatePlaybook b/scripts.templates/CreatePlaybook new file mode 100755 index 0000000..e1d92eb --- /dev/null +++ b/scripts.templates/CreatePlaybook @@ -0,0 +1,17 @@ +#! /bin/bash +NAME=$1 +DIR_KNIFE=../ansknife/playbooks.templates +if [ -z "$NAME" ]; then + echo "Usage: CreatePlaybook NAME" + echo "Example: CreateTask db_create" + echo "+++ missing NAME" +else + touch $DIR_KNIFE/$NAME.yaml + cd playbooks + ln -s ../$DIR_KNIFE/$NAME.yaml . + cd .. + ls -ld $DIR_KNIFE/$NAME.yaml + sudo ./SetRights + cd ../ansknife/ + sudo ./SetRights +fi diff --git a/scripts.templates/CreateTask b/scripts.templates/CreateTask new file mode 100755 index 0000000..643fe8b --- /dev/null +++ b/scripts.templates/CreateTask @@ -0,0 +1,17 @@ +#! /bin/bash +NAME=$1 +DIR_KNIFE=../ansknife/tasks.templates +if [ -z "$NAME" ]; then + echo "Usage: CreateTask NAME" + echo "Example: CreateTask db_create" + echo "+++ missing NAME" +else + touch $DIR_KNIFE/$NAME.yaml + cd tasks + ln -s ../$DIR_KNIFE/$NAME.yaml . + cd .. + ls -ld $DIR_KNIFE/$NAME.yaml + sudo ./SetRights + cd ../ansknife/ + sudo ./SetRights +fi diff --git a/scripts.templates/SetRights b/scripts.templates/SetRights new file mode 100755 index 0000000..a0788ed --- /dev/null +++ b/scripts.templates/SetRights @@ -0,0 +1,9 @@ +#! /bin/bash +chown -R wk * +chmod -R g+rw * +if [ -d playbooks.templates ]; then + echo "protecting templates.local from changing" + chmod -R uog-w templates.local +fi + + diff --git a/scripts/build_dkim_text.py b/scripts/build_dkim_text.py new file mode 100755 index 0000000..33e271a --- /dev/null +++ b/scripts/build_dkim_text.py @@ -0,0 +1,17 @@ +#! /usr/bin/python3 +import sys +def main(args): + filename = args[0] + with open(filename, "r") as fp: + lines = fp.read().split('\n') + # 20250419._domainkey IN TXT ( "v=DKIM1; h=sha256; k=rsa; " + # "p=MIIBIjANBgkqh...QoSuyvjwoNEUNO" + # "dfsOuTKBzmchQ4Z...c2jwIDAQAB" ) ; ----- DKIM key 20250419 for f-r-e-i.de + info = lines[0].split('"')[1] + info += lines[1].split('"')[1] + info += lines[2].split('"')[1] + print(info) + +if __name__ == '__main__': + #main(["/tmp/example.txt"]) + main(sys.argv[1:]) diff --git a/tasks.templates/t_copy_wildcard.yaml b/tasks.templates/t_copy_wildcard.yaml new file mode 100644 index 0000000..846ccbe --- /dev/null +++ b/tasks.templates/t_copy_wildcard.yaml @@ -0,0 +1,13 @@ +--- +# Copys files specified by a wildcard pattern from the source directory to the destination directory. +# needed facts (variables): +# - src_dir: source directory +# - dest_dir: destination directory +# - pattern: wildcard pattern +- name: Copy files matching {{ pattern }} from {{ src_dir }} to {{dest_dir}} + ansible.builtin.copy: + src: "{{ item }}" + dest: "{{ dest_dir }}" + with_fileglob: + - "{{ src_dir }}/{{ pattern }}" + diff --git a/tasks.templates/t_dkim.yaml b/tasks.templates/t_dkim.yaml new file mode 100644 index 0000000..0c9aade --- /dev/null +++ b/tasks.templates/t_dkim.yaml @@ -0,0 +1,74 @@ +--- +# Configures DKIM +# needed facts (variables): +# dkim_domains: a list of domains handled by DKIM +# dkim_opendkim_config_dir: normally /etc/opendkim +# dkim_config_ansible.builtin.file: normally /etc/opendkim.conf +# dkim_selector: normally the date yyyymmdd +# dkim_user: the user running opendkim +# dkim_group: the user group running opendkim +# dkim_socket_port: port of the socket of opendkim + + +- name: add user to group postfix + ansible.builtin.user: + name: '{{ dkim_user }}' + groups: postfix + append: yes +- name: ensure existence of /var/spool/postfix/opendkim + ansible.builtin.file: + path: /var/spool/postfix/opendkim + state: directory + recurse: true + owner: "{{ dkim_user }}" + group: "{{ dkim_group }}" + +- name: opendkim directory present + ansible.builtin.file: + path: '{{ dkim_opendkim_config_dir }}/keys' + owner: "{{ dkim_user }}" + state: directory + group: "{{ dkim_group }}" + mode: 0750 + +- name: opendkim trusted.hosts present + ansible.builtin.template: + src: ../templates.local/antispam/trusted.hosts + dest: '{{ dkim_opendkim_config_dir }}/trusted.hosts' + notify: + - restart opendkim + +- name: configure opendkim in {{ dkim_config_file }} + ansible.builtin.copy: + src: ../templates.local/antispam/opendkim.conf + dest: "{{ dkim_config_file }}" + notify: + - restart opendkim + +- name: installing tables + ansible.builtin.template: + src: "../templates.fix/antispam/{{ item }}" + dest: '{{ dkim_opendkim_config_dir }}/{{item}}' + notify: + - restart opendkim + with_items: [key.table, signing.table] + +- name: generate opendkim keys + ansible.builtin.include_tasks: "../tasks/t_dkim_keys.yaml" + loop: "{{ dkim_domains }}" + loop_control: + loop_var: dkim_domain + +- name: ensure opendkim is running + service: + name: opendkim + state: started + enabled: yes +- name: display DNS staff and tests + ansible.builtin.include_tasks: "../tasks/t_dkim_dns.yaml" + loop: "{{ dkim_domains }}" + loop_control: + loop_var: dkim_domain + + +... diff --git a/tasks.templates/t_dkim_dns.yaml b/tasks.templates/t_dkim_dns.yaml new file mode 100644 index 0000000..8b7eb9f --- /dev/null +++ b/tasks.templates/t_dkim_dns.yaml @@ -0,0 +1,31 @@ +--- +# Show the DNS records to configure with dkim publik key +# needed facts (variables): +# dkim_domain: the domain to configure +# dkim_opendkim_config_dir: normally /etc/opendkim +# dkim_selector: normally the date yyyymmdd +- name: store hostname and name of the public key file + ansible.builtin.set_fact: + hostname: "{{ inventory_hostname }}" + public_key: "{{ dkim_opendkim_config_dir }}/keys/{{ dkim_domain }}/{{ dkim_selector }}.txt" +- name: fetch the remote key file to the (local) control host + fetch: + src: "{{ public_key }}" + dest: "/tmp" +- name: start the local script to find out the related info + script: "../scripts/build_dkim_text.py /tmp/{{ hostname }}{{ public_key }}" + register: dns_text + delegate_to: localhost +- name: display DKIM public key DNS record + debug: + msg: + - "#=============================================" + - "zone: {{ dkim_selector }}._domainkey.{{ dkim_domain }}" + - "type: TXT" + - "content: {{ dns_text.stdout | regex_replace('\n', '')}}" + - "#=============================================" + - "Test:" + - "host -t txt {{ dkim_selector }}._domainkey.{{ dkim_domain }}._domainkey" + - "opendkim-testkey -d {{ dkim_domain }} -s {{ dkim_selector }} -vvv" + - "#=============================================" +... diff --git a/tasks.templates/t_dkim_keys.yaml b/tasks.templates/t_dkim_keys.yaml new file mode 100644 index 0000000..ba35e97 --- /dev/null +++ b/tasks.templates/t_dkim_keys.yaml @@ -0,0 +1,34 @@ +--- +## DKIM: Generates a key for a given domain +# dkim_domain: the domain to configure +# dkim_opendkim_config_dir: normally /etc/opendkim +# dkim_selector: normally the date yyyymmdd +# dkim_user: the user running DKIM +# dkim_group: the group running DKIM +# dkim_rsa_keylen: the count of bits of the RSA key + +- name: creates domain's keys directory + file: + path: "{{ dkim_opendkim_config_dir }}/keys/{{ dkim_domain }}" + state: directory + recurse: true + +- name: ensure signing key is present + stat: + path: "{{ dkim_opendkim_config_dir }}/keys/{{ dkim_domain }}/{{ dkim_selector }}.private" + get_checksum: false + register: dkim_key + +- name: generate signing key + command: opendkim-genkey -b {{ dkim_rsa_keylen }} -s {{ dkim_selector }} -d {{ dkim_domain }} -D {{ dkim_opendkim_config_dir }}/keys/{{ dkim_domain }} + when: not dkim_key.stat.exists + notify: + - restart opendkim + +- name: ensure signing key owner + file: + path: "{{ dkim_opendkim_config_dir }}/keys/{{ dkim_domain }}/{{ dkim_selector }}.private" + owner: '{{ dkim_user }}' + group: '{{ dkim_group }}' + +... diff --git a/tasks.templates/t_dmarc.yaml b/tasks.templates/t_dmarc.yaml new file mode 100644 index 0000000..3f016eb --- /dev/null +++ b/tasks.templates/t_dmarc.yaml @@ -0,0 +1,54 @@ +--- +# Configures DMARC +# needed facts (variables): +# dkim_domains: a list of domains handled by DKIM +# dmarc_config_dir: normally /etc/opendmarc +# dmarc_config_file: normally /etc/opendmarc.conf +# dmarc_user: the user running opendmarc +# dmarc_group: the user group running opendmarc + +- name: add user to group postfix + ansible.builtin.user: + name: '{{ dmarc_user }}' + groups: postfix + append: yes +- name: ensure existence of /var/spool/postfix/opendmarc + file: + path: /var/spool/postfix/opendmarc + state: directory + recurse: true + owner: "{{ dmarc_user }}" + group: "{{ dmarc_group }}" + mode: "0750" +- name: configure opendmarc in {{ dmarc_config_file }} + ansible.builtin.copy: + src: ../templates.local/antispam/opendmarc.conf + dest: "{{ dmarc_config_file }}" + notify: + - restart opendmarc +- name: "create configuration directory {{ dmarc_config_dir }}" + ansible.builtin.file: + path: "{{ dmarc_config_dir }}" + state: directory + owner: "{{ dmarc_user }}" + group: "{{ dmarc_group }}" + mode: "0750" + +- name: create ignore.hosts + ansible.builtin.copy: + src: ../templates.local/antispam/ignore.hosts + dest: "{{ dmarc_config_dir }}/ignore.hosts" + notify: + - restart opendmarc +- name: download suffix list + get_url: + url: https://publicsuffix.org/list/public_suffix_list.dat + dest: "{{ dmarc_config_dir }}" + owner: "{{ dmarc_user }}" + group: "{{ dmarc_group }}" +- name: display DNS staff and tests + ansible.builtin.include_tasks: "../tasks/t_dmarc_dns.yaml" + loop: "{{ dkim_domains }}" + loop_control: + loop_var: dkim_domain + diff --git a/tasks.templates/t_dmarc_dns.yaml b/tasks.templates/t_dmarc_dns.yaml new file mode 100644 index 0000000..0bc19ec --- /dev/null +++ b/tasks.templates/t_dmarc_dns.yaml @@ -0,0 +1,17 @@ +--- +# Show the DNS records to configure DMARC +# needed facts (variables): +# dkim_domain: the domain to configure +# dmarc_email_report: receipient email addres for reports +- name: display DMARC public key DNS record + debug: + msg: + - "#=============================================" + - "zone: _dmarc.{{ dkim_domain }}" + - "type: TXT" + - "content: v=DMARC1; p=reje; sp=reje; adkim; aspf; pct=1; rua=mailto:{{ dmarc_email_report }}; ruf=mailto:{{ dmarc_email_report }}; fo=1;" + - "#=============================================" + - "Test:" + - "host -t txt _dmarc.{{ dkim_domain }}" + - "#=============================================" +... diff --git a/tasks.templates/t_firewalld_basic.yaml b/tasks.templates/t_firewalld_basic.yaml new file mode 100644 index 0000000..50a0621 --- /dev/null +++ b/tasks.templates/t_firewalld_basic.yaml @@ -0,0 +1,21 @@ +--- +# Allow HTTP and HTTPS +# needed facts (variables): +# +- name: allow http + ansible.posix.firewalld: + service: http + zone: public + permanent: true + state: enabled + immediate: yes + notify: firewall reload + +- name: allow https + ansible.posix.firewalld: + service: https + zone: public + permanent: true + state: enabled + immediate: yes + notify: firewall reload diff --git a/tasks.templates/t_firewalld_email.yaml b/tasks.templates/t_firewalld_email.yaml new file mode 100644 index 0000000..98fe999 --- /dev/null +++ b/tasks.templates/t_firewalld_email.yaml @@ -0,0 +1,21 @@ +--- +# Allow SMTP and IMAP +# needed facts (variables): +# +- name: Erlaube HTTP (Port 80) in der Zone public + ansible.posix.firewalld: + service: smtp + zone: public + permanent: true + state: enabled + immediate: yes + notify: firewall reload + +- name: allow imap + ansible.posix.firewalld: + service: imaps + zone: public + permanent: true + state: enabled + immediate: yes + notify: firewall reload diff --git a/tasks.templates/t_firewalld_http.yaml b/tasks.templates/t_firewalld_http.yaml new file mode 100644 index 0000000..50a0621 --- /dev/null +++ b/tasks.templates/t_firewalld_http.yaml @@ -0,0 +1,21 @@ +--- +# Allow HTTP and HTTPS +# needed facts (variables): +# +- name: allow http + ansible.posix.firewalld: + service: http + zone: public + permanent: true + state: enabled + immediate: yes + notify: firewall reload + +- name: allow https + ansible.posix.firewalld: + service: https + zone: public + permanent: true + state: enabled + immediate: yes + notify: firewall reload diff --git a/tasks.templates/t_firewalld_nfs.yaml b/tasks.templates/t_firewalld_nfs.yaml new file mode 100644 index 0000000..b7bdd7b --- /dev/null +++ b/tasks.templates/t_firewalld_nfs.yaml @@ -0,0 +1,19 @@ +# Allow NFS access only from known hosts +# needed facts (variables): +# firewall_nfs_hosts: a list of IPs of the allowed hosts +- name: Allow TCP port 111 from known hosts + ansible.posix.firewalld: + zone: public + rich_rule: 'rule family="ipv4" source address="{{ item }}" port protocol="tcp" port="111" accept' + permanent: true + state: enabled + with_items: "{{ firewall_nfs_hosts }}" + notify: firewall reload + +- name: Drop TCP port 111 from all other sources + ansible.posix.firewalld: + zone: public + rich_rule: 'rule family="ipv4" port protocol="tcp" port="111" drop' + permanent: true + state: enabled + notify: firewall reload diff --git a/tasks.templates/t_lets_create.yaml b/tasks.templates/t_lets_create.yaml new file mode 100644 index 0000000..1704b99 --- /dev/null +++ b/tasks.templates/t_lets_create.yaml @@ -0,0 +1,13 @@ +--- +# Creates a letsencrypt certificate for the given domain +# needed facts (variables): +# - domain: the domain to create the certificate for +# - webmaster_email: the email address to use for the certificate +- name: Check pre-requisites + fail: msg="The variable 'domain' must be defined and not empty." + when: domain is not defined or domain == "" +- name: create a letsencrypt certificate for {{ domain }} + shell: + cmd: "certbot certonly -a webroot --webroot-path=/srv/www/letsencrypt -d {{ domain }} --email {{ webmaster_email }} --agree-tos --non-interactive" + args: + creates: "/etc/letsencrypt/live/{{ domain }}" \ No newline at end of file diff --git a/tasks.templates/t_link_wildcard.yaml b/tasks.templates/t_link_wildcard.yaml new file mode 100644 index 0000000..818c9ed --- /dev/null +++ b/tasks.templates/t_link_wildcard.yaml @@ -0,0 +1,16 @@ +--- +# Copys files specified by a wildcard pattern from the source directory to the destination directory. +# needed facts (variables): +# - src_dir: source directory +# - src_relative: source directory relative link target +# - dest_dir: destination directory +# - pattern: wildcard pattern +- name: Link files matching {{ pattern }} from {{ src_dir }} to {{dest_dir}} + #command: ln -s {{ src_relative }}/{{ item | basename}} {{ dest_dir }}/{{ item | basename }} + ansible.builtin.file: + src: "{{ src_relative }}/{{ item | basename }}" + dest: "{{ dest_dir }}/{{ item | basename }}" + state: link + with_fileglob: + - "{{ src_dir }}/{{ pattern }}" + diff --git a/tasks.templates/t_mysql_create_admin.yaml b/tasks.templates/t_mysql_create_admin.yaml new file mode 100644 index 0000000..83d9cda --- /dev/null +++ b/tasks.templates/t_mysql_create_admin.yaml @@ -0,0 +1,10 @@ +# needed facts (variables): +# dba_name: name of the database user with all privileges +# dba_password: password of the database user with all privileges + +- name: Create the database adminstrator {{dba_name}} + community.mysql.mysql_user: + name: "{{ dba_name }}" + password: "{{ dba_password }}" + priv: "*.*:ALL,GRANT" + state: present diff --git a/tasks.templates/t_mysql_create_db_and_user.yaml b/tasks.templates/t_mysql_create_db_and_user.yaml new file mode 100644 index 0000000..888f64a --- /dev/null +++ b/tasks.templates/t_mysql_create_db_and_user.yaml @@ -0,0 +1,24 @@ +# needed facts: +# db_name: name of the database +# db_host: the ip or name of the host hosting mysql. Normally: localhost +# db_user: name of the database user +# db_password: password of the database user +# webapp_name: name of the web application +# dba_name: name of the database user with all privileges +# dba_password: password of the database user with all privileges +- name: Create the database {{db_name}} + community.mysql.mysql_db: + name: "{{ db_name }}" + login_host: "{{ db_host | default('localhost') }}" + login_user: "{{ dba_name }}" + login_password: "{{ dba_password }}" + state: present +- name: Create the database user {{db_user}} + community.mysql.mysql_user: + name: "{{ db_user }}" + host: "{{ db_host | default('localhost') }}" + password: "{{ db_password }}" + login_user: "{{ dba_name }}" + login_password: "{{ dba_password }}" + priv: "{{ db_name }}.*:ALL,GRANT" + state: present diff --git a/tasks.templates/t_nginx_create_site.yaml b/tasks.templates/t_nginx_create_site.yaml new file mode 100644 index 0000000..b64b337 --- /dev/null +++ b/tasks.templates/t_nginx_create_site.yaml @@ -0,0 +1,43 @@ +--- +# Creates the nginx site configuration file (sites-available) +# needed facts (variables): +# domain: the site domain name +# document_root: the document root of the site without path, e.g. example.com. Default: domain +# shortname: the short name of the site. Used for log file names +# force: if true, the site will be created even if it already exists. Default: false +- name: Set default values for document_root + ansible.builtin.set_fact: + document_root: "{{ domain }}" + when: document_root is not defined +- name: Set default values for shortname + ansible.builtin.set_fact: + shortname: "{{ domain }}" + when: name is not defined +- name: Set default values for php_version + ansible.builtin.set_fact: + php_version: "8.3" + when: php_version is not defined +- name: Set default values for force + ansible.builtin.set_fact: + force: + when: force is not defined +- name: check if configuration already exists + ansible.builtin.stat: + path: /etc/nginx/sites-available/{{ domain }} + register: site_config +- name: abort if configuration already exists + ansible.builtin.fail: + msg: "The nginx site configuration file /etc/nginx/sites-available/{{ domain }} already exists. Please remove it first." + when: not force and site_config.stat.exists +- name: Create nginx site configuration of {{ domain }} + ansible.builtin.template: + src: ../templates.fix/nginx/site.php + dest: /etc/nginx/sites-available/{{ domain }} + owner: root + group: root + mode: '0644' +- name: Create symlink to sites-enabled + ansible.builtin.file: + src: /etc/nginx/sites-available/{{ domain }} + dest: /etc/nginx/sites-enabled/{{ domain }} + state: link diff --git a/tasks.templates/t_spf.yaml b/tasks.templates/t_spf.yaml new file mode 100644 index 0000000..bf50d1e --- /dev/null +++ b/tasks.templates/t_spf.yaml @@ -0,0 +1,33 @@ +--- +# Installs the SPF staff for postfix +# needed facts (variables): +# dkim_domains: the list of email domains +- name: intro + debug: + msg: "domains: {{ dkim_domains }}" +- name: Adapt policyd-spf.conf + lineinfile: + dest: /etc/postfix-policyd-spf-python/policyd-spf.conf + regexp: "^{{ item }}\\s*=\\s*Fail" + line: "{{ item }} = False" + notify: + - restart postfix + with_items: [ HELO_reject, Mail_From_reject] +- name: Display DNS configuration + debug: + msg: + - "*************************************************************" + - "zone: {{ item }}" + - "type: TXT" + - "content: 'v=spf1 mx -all' deny other" + - "content: 'v=spf1 mx ~all' allow other" + - "*************************************************************" + with_items: "{{ dkim_domains }}" +- name: Display test command + debug: + msg: + - "*************************************************************" + - "Test it with:" + - "host -t txt {{ item }}" + - "*************************************************************" + with_items: "{{ dkim_domains }}" diff --git a/tasks.templates/t_ssl_create_certificate.yaml b/tasks.templates/t_ssl_create_certificate.yaml new file mode 100644 index 0000000..5d9550c --- /dev/null +++ b/tasks.templates/t_ssl_create_certificate.yaml @@ -0,0 +1,13 @@ +# Creates a X509 certificate for a domain, needed for a letsencrypt certificate. +# needed facts (variables): +# - domain: the domain name for the certificate +# - ssl_country: the country code for the certificate +# - ssl_state: the state for the certificate +# - ssl_locality: the locality for the certificate +# - ssl_organization: the organization for the certificate +# - ssl_lifetime: the lifetime of the certificate in days +# - ssl_rsa_key_size: the size of the RSA key for the certificate +- name: create a ssh-certificate + ansible.builtin.command: openssl req -x509 -nodes -days {{ssl_lifetime}} -newkey rsa:{{ssl_rsa_key_size}} -keyout /etc/ssl/private/{{domain}}.key -out /etc/ssl/certs/{{domain}}.pem -subj "/C={{ssl_country}}/ST={{ssl_state}}/L={{ssl_locality}}/O={{ssl_organization}}/CN={{domain}}" + args: + creates: /etc/ssl/private/{{domain}}.key diff --git a/tasks.templates/t_sysinfo_create.yaml b/tasks.templates/t_sysinfo_create.yaml new file mode 100644 index 0000000..0ad8391 --- /dev/null +++ b/tasks.templates/t_sysinfo_create.yaml @@ -0,0 +1,8 @@ +# Installs a script saving structure meta data and configuration and a cron job to start it every night +# needed facts (variables): +# hostname: the name of the remote server +- name: install the script + ansible.builtin.copy: + src: ../template.local/scripts/CreateSysInfo + dest: "/usr/local/bin/{{ hostname }}" + mode: "0755" diff --git a/tasks.templates/t_webapp_backup.yaml b/tasks.templates/t_webapp_backup.yaml new file mode 100644 index 0000000..91b0893 --- /dev/null +++ b/tasks.templates/t_webapp_backup.yaml @@ -0,0 +1,26 @@ +# Create a backup of a web application: +# - Dumps a database of a web application +# - Syncronizes the directory with the web application with an local directory +# needed facts: +# webapp_name: name of the web application +# webapp_backup_directory: a local directory which will be syncronized with the web application +# webapps_list: the list defined in ../vars/webapps.yaml +# fetch: true: the file will be fetched to the local machine +- ansible.builtin.set_fact: + db_name: "{{ webapps_list | selectattr('webapp_name', 'equalto', webapp_name) | map(attribute='db_name') | first }}" + db_host: "{{ webapps_list | selectattr('webapp_name', 'equalto', webapp_name) | map(attribute='db_host') | first }}" + db_user: "{{ webapps_list | selectattr('webapp_name', 'equalto', webapp_name) | map(attribute='db_user') | first }}" + db_password: "{{ webapps_list | selectattr('webapp_name', 'equalto', webapp_name) | map(attribute='db_password') | first }}" + document_root: "{{ webapps_list | selectattr('webapp_name', 'equalto', webapp_name) | map(attribute='directory') | first }}" + target_file: "{{ document_root }}/db/{{ webapp_name }}.sql.gz" +- name: Dump the database {{ db_host }}.{{ db_name }} into {{ document_root }} + ansible.builtin.import_tasks: t_webapp_export.yaml +- name: Synchronize the web application {{ webapp_name }} with {{ webapp_backup_directory }} + ansible.posix.synchronize: + mode: push + src: "{{ document_root }}" + dest: "{{ webapp_backup_directory }}/{{ webapp_name }}" + rsync_opts: + - "--delete" + - "--exclude=.git" + \ No newline at end of file diff --git a/tasks.templates/t_webapp_create.yaml b/tasks.templates/t_webapp_create.yaml new file mode 100644 index 0000000..21c8cac --- /dev/null +++ b/tasks.templates/t_webapp_create.yaml @@ -0,0 +1,47 @@ +# Create a web application: database, db user, db password, configuration file (remote and local) +# needed facts: +# db_name: name of the database +# db_host: ip or name fo the host hosting the database server: normally "localhost" +# db_user: name of the database user +# db_password: password of the database user +# webapp_name: name of the web application +# dba_name: name of the database user with all privileges +# dba_password: password of the database user with all privileges +# remote_webapps_directory: the access data will be stored there, e.g. /etc/ansknife/webapp.d +# local_webapps_directory: the access data will be stored there, e.g. ../webapp.d +# +# optional facts: +# db_host: ip or name fo the host hosting the database server: normally "localhost" + +- name: 'check if the webapp is already defined' + ansible.builtin.stat: + path: "{{remote_webapps_directory}}/{{ webapp_name }}.yaml" + register: webapp +- name: 'stop if the webapp already defined' + ansible.builtin.fail: msg="webapp {{ webapp_name }} is already defined" + when: webapp.stat.exists +- name: 'stop on missing facts' + ansible.builtin.fail: msg="missing facts! webapp_name, db_name, db_user, db_password. Use -e webapp_name=..., -e db_name=..., -e db_user=..., -e db_password=..." + when: webapp_name is not defined or db_name is not defined or db_user is not defined or db_password is not defined +- name: Create the database {{ db_host }}.{{ db_name }} and db user {{ db_user }} + ansible.builtin.import_tasks: ../tasks/t_mysql_create_db_and_user.yaml +- name: Store data in remote configuration file + ansible.builtin.copy: + content: | + db_name={{ db_name }} + db_type={{ db_type | default('mysql') }} + db_host={{ db_host | default('localhost') }} + db_user={{ db_user }} + db_password={{ db_password }} + directory=/srv/www/{{ webapp_name }} + exclude_dirs= + dest: "{{remote_webapps_directory}}/{{ webapp_name }}.conf" + mode: 0600 + owner: root + group: root +- name: append it to the webapps list + ansible.builtin.lineinfile: + path: ../vars/webapps.yaml + line: " - webapp_name: '{{ webapp_name }}'\n db_name: '{{ db_name }}'\n db_type: '{{ db_type | default('mysql') }}'\n db_host: '{{ db_host | default('localhost') }}'\n db_user: '{{ db_user }}'\n db_password: '{{ db_password }}'\n directory: '/srv/www/{{ webapp_name }}'" + create: yes + delegate_to: localhost diff --git a/tasks.templates/t_webapp_export.yaml b/tasks.templates/t_webapp_export.yaml new file mode 100644 index 0000000..c452c5c --- /dev/null +++ b/tasks.templates/t_webapp_export.yaml @@ -0,0 +1,34 @@ +# Dumps a database of a web application into a file +# needed facts: +# webapp_name: name of the web application +# target_file: the file where the database will be exported to. May be *.sql or *.sql.gz +# webapps_list: the list defined in ../vars/webapps.yaml +# fetch: true: the file will be fetched to the local machine +- set_fact: + db_name: "{{ webapps_list | selectattr('webapp_name', 'equalto', webapp_name) | map(attribute='db_name') | first }}" + db_type: "{{ webapps_list | selectattr('webapp_name', 'equalto', webapp_name) | map(attribute='db_type') | first }}" + db_host: "{{ webapps_list | selectattr('webapp_name', 'equalto', webapp_name) | map(attribute='db_host') | first }}" + db_user: "{{ webapps_list | selectattr('webapp_name', 'equalto', webapp_name) | map(attribute='db_user') | first }}" + db_password: "{{ webapps_list | selectattr('webapp_name', 'equalto', webapp_name) | map(attribute='db_password') | first }}" + compressed: "{{ target_file is regex('\\.sql\\.') }}" + remote_file: "{{ target_file }}" +- set_fact: + remote_file: "{{ '/tmp/' + (target_file | basename) }}" + when: fetch | bool +- name: Dump the database {{ db_host }}.{{ db_name }} to {{ target_file }} + community.mysql.mysql_db: + state: dump + name: "{{ db_name }}" + host: "{{ db_host }}" + login_user: "{{ db_user }}" + login_password: "{{ db_password }}" + target: "{{ remote_file }}" + single_transaction: true + pipefail: "{{ compressed }}" +- name: Fetch the database dump {{ target_file }} to local machine + ansible.builtin.fetch: + src: "{{ remote_file }}" + dest: "{{ target_file }}" + flat: true + when: fetch | bool + diff --git a/tasks.templates/t_webapp_import.yaml b/tasks.templates/t_webapp_import.yaml new file mode 100644 index 0000000..b0f6251 --- /dev/null +++ b/tasks.templates/t_webapp_import.yaml @@ -0,0 +1,34 @@ +# Imports a database dump into the database of a web application +# needed facts: +# webapp_name: name of the web application +# source_file: the database dump file. May be *.sql or *.sql.gz +# backup: if true, the database will be dumped before importing +# backup_file: the name of the backup file +# webapps_list: the list defined in ../vars/webapps.yaml +- set_fact: + db_name: "{{ webapps_list | selectattr('webapp_name', 'equalto', webapp_name) | map(attribute='db_name') | first }}" + db_type: "{{ webapps_list | selectattr('webapp_name', 'equalto', webapp_name) | map(attribute='db_type') | first }}" + db_host: "{{ webapps_list | selectattr('webapp_name', 'equalto', webapp_name) | map(attribute='db_host') | first }}" + db_user: "{{ webapps_list | selectattr('webapp_name', 'equalto', webapp_name) | map(attribute='db_user') | first }}" + db_password: "{{ webapps_list | selectattr('webapp_name', 'equalto', webapp_name) | map(attribute='db_password') | first }}" + compressed: "{{ source_file is regex('\\.sql\\.$') }}" +- name: Backup the current database {{db_name}} to {{ backup_file }} + community.mysql.mysql_db: + state: dump + name: "{{ db_name }}" + login_user: "{{ db_user }}" + login_password: "{{ db_password }}" + target: "{{ backup_file }}" + single_transaction: true + pipefail: "{{ compressed }}" + when: backup and db_type == 'mysql' | bool +- name: Import {{ source_file }} into {{db_name}} + community.mysql.mysql_db: + state: import + name: "{{ db_name }}" + host: "{{ 'db_host' }}" + login_user: "{{ db_user }}" + login_password: "{{ db_password }}" + target: "{{ source_file }}" + when: db_type == 'mysql' + diff --git a/templates.fix/antispam/key.table b/templates.fix/antispam/key.table new file mode 100644 index 0000000..58d41bc --- /dev/null +++ b/templates.fix/antispam/key.table @@ -0,0 +1,3 @@ +{% for domain in dkim_domains %} +{{ dkim_selector }}._domainkey.{{ domain }} {{ domain }}:{{ dkim_selector }}:/etc/opendkim/keys/{{ domain }}/{{ dkim_selector }}.private +{% endfor %} diff --git a/templates.fix/antispam/signing.table b/templates.fix/antispam/signing.table new file mode 100644 index 0000000..f7d5949 --- /dev/null +++ b/templates.fix/antispam/signing.table @@ -0,0 +1,3 @@ +{% for domain in dkim_domains %} +*@{{ domain }} {{ dkim_selector }}._domainkey.{{ domain }} +{% endfor %} diff --git a/templates.fix/nginx/hi1.txt b/templates.fix/nginx/hi1.txt new file mode 100644 index 0000000..877acc4 --- /dev/null +++ b/templates.fix/nginx/hi1.txt @@ -0,0 +1 @@ +Hi 1! diff --git a/templates.fix/nginx/hi2.txt b/templates.fix/nginx/hi2.txt new file mode 100644 index 0000000..e42aa2a --- /dev/null +++ b/templates.fix/nginx/hi2.txt @@ -0,0 +1 @@ +Hi 2! diff --git a/templates.fix/nginx/index.html b/templates.fix/nginx/index.html new file mode 100644 index 0000000..0e0cbca --- /dev/null +++ b/templates.fix/nginx/index.html @@ -0,0 +1,5 @@ + + +

Welcome to {{hostname}}!

+ \ No newline at end of file diff --git a/templates.fix/nginx/index.php b/templates.fix/nginx/index.php new file mode 100644 index 0000000..bfd863b --- /dev/null +++ b/templates.fix/nginx/index.php @@ -0,0 +1,2 @@ +>$LOG "$msg" +} + +Log "swapoff:" +info=$(swapoff -a) +Log "logoff result: $info" +info=$(swapon -a) +Log "logon result: $info" diff --git a/templates.fix/scripts/CreateSysInfo b/templates.fix/scripts/CreateSysInfo new file mode 100644 index 0000000..6dba4d4 --- /dev/null +++ b/templates.fix/scripts/CreateSysInfo @@ -0,0 +1,131 @@ +#! /bin/bash +# Ansible controlled: do not change on remote server +VERBOSE=-v +export PATH=/bin:/usr/bin:/sbin:/usr/sbin:/usr/local/bin +test "$1" = -q && VERBOSE= +. /usr/local/bin/local/CreateSysInfo.conf +function Packages(){ + test "$VERBOSE" = -v && echo "= packages" + dpkg -l | sort > $DIR_INFO/packets.installed.txt + apt-mark showmanual | sort >$DIR_INFO/packets.manually.txt + cp /var/log/apt/history.log $DIR_INFO/apt.history.txt +} +function Mount(){ + test "$VERBOSE" = -v && echo "= mount" + mount | grep /dev/ | egrep -v "devpts|hugetlbfs|tmpfs|mqueue" > $DIR_INFO/mount.txt +} +function Devices(){ + test "$VERBOSE" = -v && echo "= device info" + blkid > $DIR_INFO/blkid.txt + lsblk > $DIR_INFO/lsblk.txt + free > $DIR_INFO/free.txt + df -h | grep -v docker > $DIR_INFO/df.txt + test "$VERBOSE" = -v && echo "= btrfs" + rm -f $DIR_INFO/btrfs.subvols.txt + if [ -n "$BTRFS_FS" ]; then + for fs in $BTRFS_FS; do + if [ -d $fs ]; then + echo "= $fs: >>$DIR_INFO/btrfs.subvols.txt" + btrfs subvol list $fs | grep -v snapshot >> $DIR_INFO/btrfs.subvols.txt + fi + done + fi + test "$VERBOSE" = -v && echo "= fdisk" + fdisk -l > $DIR_INFO/fdisk.txt +} +function SoftwareRaid(){ + if [ "$HAS_SOFT_RAID" = yes ]; then + cat /proc/mdstat > $DIR_INFO/mdstat.txt + fi +} +function SmartMonitor(){ + local fn=$DIR_INFO/smartmon.txt + date >$fn + for dev in $SMART_MON_DEVS; do + echo "============ $dev" >>$fn + smartctl --all /dev/$dev >>$fn + done +} +function SystemD(){ + test "$VERBOSE" = -v && echo "= systemctl" + systemctl list-unit-files > $DIR_INFO/systemd.files.txt + systemctl list-units > $DIR_INFO/systemd.units.txt +} +function Trees(){ + test "$VERBOSE" = -v && echo "= directories" + for dir in etc usr/local/bin var/spool/cron/crontabs var/log boot; do + name=${dir//\//_} + cd /$dir + test -n "$VERBOSE" && echo "= $(pwd)" + opt= + trg=$DIR_INFO/$dir + mkdir -p $trg + #test $dir = var/log && opt=--newer-mtime=$(date --date='yesterday' +%Y%m%d) + #tar czf $DIR_INFO/$name.tgz . $opt + test $dir = var/log && opt="--exclude=journal --exclude=*.log.*gz" + test $dir = etc && opt="--exclude=.git --exclude=sysinit.target.wants --exclude=multi-user.target.wants" + rsync -au $VERBOSE --delete-excluded --delete $opt ./ $trg + test "$VERBOSE" = -v && echo "= chown $dir" + find $trg -uid 0 -exec chown bupsupply "{}" \; >/dev/null 2>&1 + done + if [ $MK_HOME_TAR = yes ]; then + test "$VERBOSE" = -v && echo "= home" + pushd /home >/dev/null 2>&1 + tar czf $DIR_INFO/home.tgz $EXCLUDE_HOME --exclude-tag-all=$TAG_FILE . + popd >/dev/null 2>&1 + test "$VERBOSE" = -v && ls -ld $DIR_INFO/home.tgz + fi +} +function Zfs(){ + if [ $USE_ZFS = yes ]; then + test "$VERBOSE" = -v && echo "= ZFS" + local fn=$DIR_INFO/zfs.info.txt + echo "== zfs list" >$fn + zfs list >>$fn + echo "== zpool list" >>$fn + zpool list >>$fn + echo "== zpool status" >>$fn + zpool status >>$fn + echo "== zfs list -t snapshot" >>$fn + zfs list -t snapshot >>$fn + fi +} +function DoIt(){ + local debug= + $debug Packages + $debug Mount + $debug Devices + $debug SystemD + $debug Trees + $debug Zfs + $debug SoftwareRaid + $debug SmartMonitor +} +function ShowConfig(){ + echo "= Example of /usr/local/bin/local/CreateSysInfo.conf:" + echo "# This file will be included into CreateSysInfo +DIR_INFO=/srv/www/sys.info/example.host +# a blank separated list of mount points for devices formatted with BTRFS, e.g. "/media/fs.cache /media/fs.system" +BTRFS_FS= +# a blank separated list of devices that should be controlled by smartmon, e.g. 'sda sdb' +SMART_MON_DEVS='nvme0n1' +# set to yes if mdadm manages software raid devices +HAS_SOFT_RAID=no +MK_HOME_TAR=yes +USE_ZFS=no +# a blank separated list of '--exclude=' entries. That listed subdirs will not be saved in the TAR archive +EXCLUDE_HOME='--exclude=jails' +# each subdirectory of /home containing that file will not be saved as TAR archive +TAG_FILE=.do.not.save.as.home +" +} +if [ ! -f /usr/local/bin/local/CreateSysInfo.conf ]; then + echo "+++ missing /usr/local/bin/local/CreateSysInfo.conf" + ShowConfig +elif [ -z "$DIR_INFO" -o ! -d "$DIR_INFO" ]; then + echo "+++ missing DIR_INFO" + ShowConfig +else + DoIt +fi + diff --git a/templates.fix/scripts/SaveDatabases b/templates.fix/scripts/SaveDatabases new file mode 100755 index 0000000..11e1720 --- /dev/null +++ b/templates.fix/scripts/SaveDatabases @@ -0,0 +1,26 @@ +#! /bin/bash +BASE_DIR=/srv/www +BASE_WEBAPPS=/etc/ansknife/webapps.d +VERBOSE=-v +test "$1" = -q && VERBOSE= +PATH=/bin:/usr/bin +function SaveOne(){ + local domain=$1 + if [ -e $BASE_WEBAPPS/$domain.conf ]; then + . $BASE_WEBAPPS/$domain.conf + if [ -n "$db_name" -a "$db_type" = 'mysql' ]; then + local sql=$BASE_DIR/$domain/db/$domain.sql.gz + mysqldump -h $db_host -u $db_user "-p$db_password" $db_name | gzip > $sql + test -n "$VERBOSE" && ls -ld $sql + fi + fi +} +function SaveAll(){ + cd $BASE_DIR + for domain in *; do + if [ -d $BASE_DIR/$domain/db ]; then + SaveOne $domain + fi + done +} +SaveAll \ No newline at end of file diff --git a/templates.fix/scripts/saveowner.py b/templates.fix/scripts/saveowner.py new file mode 100755 index 0000000..3ce0699 --- /dev/null +++ b/templates.fix/scripts/saveowner.py @@ -0,0 +1,225 @@ +#! /usr/bin/python3 +# -*- coding: utf-8 -*- + +import sys +import os +import os.path +import pwd +import grp +import time +import stat +import re + +class DirectoryInfo: + def __init__(self, owner: int, group: int): + self.owner = owner + self.group = group + self.nodes = {} + def addNode(self, node: str, owner: int, group: int): + self.nodes[node] = (owner, group) + + def hasNode(self, node: str): + rc = node in self.nodes + return rc + +class Traverser: + def __init__(self, directory: str, verbose: bool): + self.start = time.time() + self.verbose = verbose + self.startDirectory = directory + self.fileOwners = '.owner.list' + self.ownerList = [] + self.ownerOfId = {} + self.groupOfId = {} + self.linesOwnerList = None + self.directoryInfos = {} + self.first = True + + def adaptOneDirectory(self, directory: str, parentOwner: int, parentGroup: int): + dirs = [] + if self.first: + relativePath = '.' + self.first = False + else: + relativePath = self.startDirectory[len(directory):] + try: + statInfo = os.lstat(directory) + directoryInfo = self.directoryInfos[relativePath] if relativePath in self.directoryInfos else None + if directoryInfo is not None: + parentOwner = directoryInfo.owner + parentGroup = directoryInfo.group + if parentOwner != statInfo.st_uid or parentGroup != statInfo.st_gid: + os.chown(directory, parentOwner, parentGroup) + if self.verbose: + print(f'= {directory}') + nodes = os.listdir(directory) + for node in nodes: + fn = os.path.join(directory, node) + statInfo = os.lstat(fn) + if stat.S_ISDIR(statInfo.st_mode): + dirs.append(node) + if directoryInfo is None: + owner = parentOwner + group = parentGroup + elif node in directoryInfo.nodes: + owner, group = directoryInfo.nodes[node] + else: + owner = parentOwner + group = parentGroup + if owner != statInfo.st_uid or group != statInfo.st_gid: + os.chown(fn, owner, group) + #print(f' {owner} {group} {node}') + for node in dirs: + self.adaptOneDirectory(os.path.join(directory, node), parentOwner, parentGroup) + except OSError as e: + if self.verbose: + print(f'+++ Error: {e}') + + + def buildDirectoryInfos(self): + regexDir = re.compile(r'^= (\d+) (\d+) (.*)$') + regexNode = re.compile(r'^(\d+) (\d+) (.*)$') + currentDir = None + startDir = self.startDirectory + dirs = 0 + nodes = 0 + for line in self.linesOwnerList: + match = regexNode.match(line) + if match is not None: + node = match.group(3) + fn = os.path.join(startDir, node) + currentDir.addNode(node, int(match.group(1)), int(match.group(2))) + nodes += 1 + else: + match = re.match(regexDir, line) + if match is not None: + currentName = match.group(3) + currentDir = DirectoryInfo(int(match.group(1)), int(match.group(2))) + self.directoryInfos[currentName] = currentDir + #os.chown(currentDir, int(match.group(1)), int(match.group(2))) + dirs += 1 + elif line.startswith('u') or line.startswith('g') or line == '': + break + else: + print(f'+++ unknown line: {line}') + if self.verbose: + print(f'== directories: {dirs} nodes: {nodes}') + + def dumpOwnersGroups(self): + for owner in self.ownerOfId: + self.ownerList.append(f'u{owner} {self.ownerOfId[owner]}') + for group in self.groupOfId: + self.ownerList.append(f'g{group} {self.groupOfId[group]}') + + def extractOwnersAndGroups(self): + regexOwner = re.compile(r'^u(\d+) (\S+)') + regexGroup = re.compile(r'^g(\d+) (\S+)') + for ix in range(len(self.linesOwnerList) - 1, 0, -1): + line = self.linesOwnerList[ix] + match = regexOwner.match(line) + if match is not None: + self.ownerOfId[int(match.group(1))] = match.group(2) + else: + match = regexGroup.match(line) + if match is not None: + self.groupOfId[int(match.group(1))] = match.group(2) + elif line != '': + break + if self.verbose: + print(f'= {len(self.ownerOfId)} owners and {len(self.groupOfId)} groups found') + + def readOwnerList(self): + fn = os.path.join(self.startDirectory, self.fileOwners) + with open(fn, 'r') as fp: + self.linesOwnerList = fp.read().split('\n') + + def save(self): + fn = os.path.join(self.startDirectory, self.fileOwners) + with open(fn, 'w') as fp: + for line in self.ownerList: + fp.write(line + '\n') + if self.verbose: + print(f'== Saved {len(self.ownerList)} lines to {self.fileOwners}') + print(f'== Time taken: {time.time() - self.start:.2f} seconds') + + def storeOneDirectory(self, directory: str): + dirs = [] + try: + statInfo = os.lstat(directory) + parentOwner = statInfo.st_uid + parentGroup = statInfo.st_gid + nodes = os.listdir(directory) + firstChange = True + if self.first: + self.first = False + firstChange = False + msg = f'= {parentOwner} {parentGroup} .' + firstChange = False + if self.verbose: + print(msg) + self.ownerList.append(msg) + for node in nodes: + fn = os.path.join(directory, node) + statInfo = os.lstat(fn) + if stat.S_ISDIR(statInfo.st_mode): + dirs.append(node) + owner = statInfo.st_uid + group = statInfo.st_gid + if owner != parentOwner or group != parentGroup: + if firstChange: + ix = len(self.startDirectory) + relativePath = '.' if ix == len(directory) else directory[ix+1:] + msg = f'= {parentOwner} {parentGroup} {relativePath}' + firstChange = False + if self.verbose: + print(msg) + self.ownerList.append(msg) + self.ownerList.append(f'{owner} {group} {node}') + if owner not in self.ownerOfId: + self.ownerOfId[owner] = pwd.getpwuid(owner).pw_name + if group not in self.groupOfId: + self.groupOfId[group] = grp.getgrgid(group).gr_name + except OSError as e: + if self.verbose: + print(f'+++ Error: {e}') + for node in dirs: + self.storeOneDirectory(os.path.join(directory, node)) + +def usage(msg: str): + print(f'''Usage: saveowner.py [-q] [--restore] DIRECTORY + Save or restore the owner and group of all files in the filetree of DIRECTORY + Note: the stored data are only differences from the parent directory +Options: +-q Be quiet +--restore Restore the ownership of DIRECTORY by the data in DIRECTORY/.owner.list + If not given the ownership is stored in DIRECTORY/.owner.list ++++ {msg} +''') + sys.exit(9) +def main(args): + quiet = len(args) > 0 and args[0] == '-q' + if quiet: + args = args[1:] + doRestore = len(args) > 0 and args[0] == '--restore' + if doRestore: + args = args[1:] + if len(args) < 1: + usage('missing DIRECTORY') + else: + app = Traverser(args[0], not quiet) + if doRestore: + app.readOwnerList() + app.extractOwnersAndGroups() + app.buildDirectoryInfos() + app.adaptOneDirectory(app.startDirectory, None, None) + else: + app.storeOneDirectory(args[0]) + app.dumpOwnersGroups() + app.save() + +if __name__ == '__main__': + args = sys.argv[1:] + #args = sys.argv[1:] if len(sys.argv) > 1 else ['--restore', '/tmp/etc'] + #args = sys.argv[1:] if len(sys.argv) > 1 else ['/tmp/etc'] + main(args) + diff --git a/templates.install/project_ansible.md b/templates.install/project_ansible.md new file mode 100644 index 0000000..98218b7 --- /dev/null +++ b/templates.install/project_ansible.md @@ -0,0 +1,61 @@ +# Beschreibung der Ansible-Struktur von {{ project }} + +## Wartung +### Benutzer +* neuer Benutzer: Änderung in vars/users.yaml +``` +ansible-playbook playbooks/i_11_user.yaml +``` +### Webauftritte +Es wird eine neue Website xxx.example.net benötigt: +``` +ansible-playbook webapp_create.yaml -e domain=xxx.example.net -e db_name=dbexample -e db_user=dbexample -e db_password=xxx +``` +### Firewall-Änderung +Änderung in vars/firewalld.yaml eintragen. +``` +ansible-playbook playbooks/i_80_firewalld.yaml +``` + +### Email-Server-Änderung +- Alias-Änderung: Änderung in templates.local/postfix/alias.yaml eintragen +- Postfix-Änderung in main.cf: Änderung in templates.local/postfix/send_only/main.cf eintragen. +- Postfix-Änderung in master.cf: Änderung in templates.local/postfix/send_only/master.cf eintragen. +``` +ansible-playbook playbooks/i_60_postfix.yaml +``` + +## PHP-Konfiguration-Änderung +- Änderung in vars/php.yaml eintragen +``` +for VERS in 7.4 8.2 8.3; do + ansible-playbook playbooks/i_40_php8.2.yaml -e php_version=$VERS +done +``` + +## Neues Software-Paket +- Änderung in vars/packages.yaml +``` +ansible-playbook playbooks/i_15_server_packages.yaml +``` + +## Installation +- Am besten zuerst das Kapitel "Wartung" durchgehen, dort sind die Dateien aufgeführt, in denen konfiguriert wird. +- Nie in templates.fix konfigurieren: + - das wird vom Paket ansknife bereitgestellt und das sind nur symbolische Links + - die Dateien sind schreibgeschützt + +### Benutzte Playbooks +- i_10_basic.yaml +- i_11_user.yaml +- i_15_server.yaml +- i_17_configuration.yaml +- i_20_configuration.yaml +- i_30_mariadb.yaml +- i_40_php8.2.yaml + - i_40_php8.2.yaml -e php_vers=7.4 + - i_40_php8.2.yaml -e php_vers=8.3 +- i_60_postfix.yaml +- i_80_firewalld.yaml +- i_81_fail2ban.yaml +- mysql_create_admin.yaml diff --git a/templates.install/project_cron.yaml b/templates.install/project_cron.yaml new file mode 100644 index 0000000..b8e9d1a --- /dev/null +++ b/templates.install/project_cron.yaml @@ -0,0 +1,44 @@ +--- +# Defines the cron jobs +- hosts: all + tasks: + - name: Creates the script for clearing the swap devices + ansible.builtin.copy: + src: ../templates.fix/scripts/ClearSwap + dest: /usr/local/bin/ClearSwap + owner: root + group: root + mode: 0744 + - name: Cron job for clearing the swap devices + ansible.builtin.cron: + name: clear the swap files + #weekday: "2" + minute: "42" + hour: "23" + user: root + job: "/usr/local/bin/ClearSwap -q" + cron_file: ClearSwap + - name: Save the ownership of a file tree in the file .owner.list + ansible.builtin.cron: + name: store the ownership of a file tree + minute: "39" + hour: "23" + user: root + job: "/usr/local/bin/host.example.com/SaveOwner.sh -q" + cron_file: SaveOwner + - name: Build a backup of the /etc/letsencrypt directory + ansible.builtin.cron: + name: builds the backup + minute: "19" + hour: "0" + user: root + job: "/usr/local/bin/host.example.com/exampleBackup -q" + cron_file: exampleBackup + - name: Save the web applications (filetree and database) into a tar + ansible.builtin.cron: + name: Save the web applications + minute: "19" + hour: "0" + user: root + job: "/usr/local/bin/SaveWebapps -q" + cron_file: SaveWebapps diff --git a/templates.install/readme.txt b/templates.install/readme.txt new file mode 100644 index 0000000..e69de29 diff --git a/templates.install/t_firewalld_local.yaml b/templates.install/t_firewalld_local.yaml new file mode 100644 index 0000000..a4c87a5 --- /dev/null +++ b/templates.install/t_firewalld_local.yaml @@ -0,0 +1,12 @@ +--- +# Defines the specific firewall rules for example.com +# +- name: allow HTTP and HTTPS + ansible.builtin.include_tasks: "../tasks/t_firewalld_http.yaml" +- name: allow SMTP and IMAP + ansible.builtin.include_tasks: ../tasks/t_firewalld_email.yaml +- name: allow NFS + ansible.builtin.include_tasks: ../tasks/t_firewalld_nfs.yaml + vars: + # example1 example2 + firewall_nfs_hosts: ["1.2.3.4", "4.3.2.1"] diff --git a/templates.local/antispam/ignore.hosts b/templates.local/antispam/ignore.hosts new file mode 100644 index 0000000..d15cd1d --- /dev/null +++ b/templates.local/antispam/ignore.hosts @@ -0,0 +1,4 @@ +# Ansible controlled: do not change on server manually +127.0.0.1 +::1 +localhost \ No newline at end of file diff --git a/templates.local/antispam/opendkim.conf b/templates.local/antispam/opendkim.conf new file mode 100644 index 0000000..61329f8 --- /dev/null +++ b/templates.local/antispam/opendkim.conf @@ -0,0 +1,20 @@ +# normally no need for changes +UserID opendkim:opendkim +UMask 002 +PidFile /var/run/opendkim/opendkim.pid +SOCKET local:/var/spool/postfix/opendkim/opendkim.sock +Mode sv +Domain * +#Selector mail +Canonicalization relaxed/relaxed +SignatureAlgorithm rsa-sha256 +OversignHeaders From +AutoRestart yes +AutoRestartRate 10/1h +SigningTable refile:/etc/opendkim/signing.table +KeyTable /etc/opendkim/key.table +ExternalIgnoreList refile:/etc/opendkim/trusted.hosts +InternalHosts refile:/etc/opendkim/trusted.hosts +Syslog yes +SyslogSuccess yes +LogWhy yes diff --git a/templates.local/antispam/opendmarc.conf b/templates.local/antispam/opendmarc.conf new file mode 100644 index 0000000..130c98e --- /dev/null +++ b/templates.local/antispam/opendmarc.conf @@ -0,0 +1,20 @@ +# Ansible controlled: do not change on server manually +AuthservID "{{ hostname }}" +TrustedAuthservIDs "{{ hostname }}" +UMask 0002 +UserID opendmarc +AutoRestart true +Socket local:/var/spool/postfix/opendmarc/opendmarc.sock +RejectFailures true +IgnoreMailFrom "{{ domain }}" +IgnoreHosts /etc/opendmarc/ignore.hosts +PublicSuffixList /etc/opendmarc/public_suffix_list.dat +SoftwareHeader false +FailureReports true +FailureReportsSentBy "no-reply.dmarc.reports@{{ domain_email }}" +#FailureReportsBcc +BaseDirectory /var/run/opendmarc +PidFile /var/run/opendmarc/opendmarc.pid +HistoryFile /var/run/opendmarc/opendmarc.dat +Syslog true +SyslogFacility mail diff --git a/templates.local/antispam/trusted.hosts b/templates.local/antispam/trusted.hosts new file mode 100644 index 0000000..3f4908e --- /dev/null +++ b/templates.local/antispam/trusted.hosts @@ -0,0 +1,3 @@ +127.0.0.1 +::1 +localhost \ No newline at end of file diff --git a/templates.local/firewalld.yaml b/templates.local/firewalld.yaml new file mode 100644 index 0000000..f988c4b --- /dev/null +++ b/templates.local/firewalld.yaml @@ -0,0 +1,5 @@ +--- +# Facts (variables) of the firewall + +# execute "ip addr" on the remote server to findout the interface of the public zone: +firewall_iface_public: enp5s0 diff --git a/templates.local/nginx/http.conf b/templates.local/nginx/http.conf new file mode 100644 index 0000000..0eed601 --- /dev/null +++ b/templates.local/nginx/http.conf @@ -0,0 +1,9 @@ +client_max_body_size 512M; +## Detect when HTTPS is used +map $scheme $fastcgi_https { + default off; + https on; +} +fastcgi_read_timeout 3600s; +fastcgi_request_buffering off; +error_log /var/log/nginx/error.log; diff --git a/templates.local/nginx/sites/readme.txt b/templates.local/nginx/sites/readme.txt new file mode 100644 index 0000000..3d77b05 --- /dev/null +++ b/templates.local/nginx/sites/readme.txt @@ -0,0 +1,2 @@ +This directory contains all active NGINX configuration files from /etc/nginx/sites-available. +They can be found via vars/webapps.yaml. diff --git a/templates.local/postfix/aliases b/templates.local/postfix/aliases new file mode 100644 index 0000000..2ba0231 --- /dev/null +++ b/templates.local/postfix/aliases @@ -0,0 +1,9 @@ +# ansible controlled: do not change manually on remote server! +# +postmaster: root +devnull: /dev/null +mailer-daemon: root +webmaster: root +www: root +security: root +root: root@example.com diff --git a/templates.local/postfix/email_forwarding/main.cf b/templates.local/postfix/email_forwarding/main.cf new file mode 100644 index 0000000..c2e9cb2 --- /dev/null +++ b/templates.local/postfix/email_forwarding/main.cf @@ -0,0 +1,31 @@ +# Ansible controlled! Do not change on remote host +myorigin = /etc/mailname +smtpd_banner = $myhostname ESMTP $mail_name (Debian/GNU) +biff = no +append_dot_mydomain = no +#delay_warning_time = 4h +readme_directory = no +compatibility_level = 3.6 + + +#smtpd_tls_cert_file=/etc/letsencrypt/live/host.example.com/fullchain.pem +#smtpd_tls_key_file=/etc/letsencrypt/live/host.example.com/privkey.pem +smtpd_tls_cert_file=/etc/ssl/certs/ssl-cert-snakeoil.pem +smtpd_tls_key_file=/etc/ssl/private/ssl-cert-snakeoil.key +smtpd_tls_security_level=may + +smtp_tls_CApath=/etc/ssl/certs +smtp_tls_security_level=may +smtp_tls_session_cache_database = btree:${data_directory}/smtp_scache + +smtpd_relay_restrictions = permit_mynetworks permit_sasl_authenticated defer_unauth_destination +myhostname = host.example.com +alias_maps = hash:/etc/aliases +alias_database = hash:/etc/aliases +mydestination = $myhostname, localhost.example.com, localhost +relayhost = +mynetworks = 127.0.0.0/8 [::ffff:127.0.0.0]/104 [::1]/128 +mailbox_size_limit = 0 +recipient_delimiter = + +inet_interfaces = all +inet_protocols = all diff --git a/templates.local/postfix/email_forwarding/master.cf b/templates.local/postfix/email_forwarding/master.cf new file mode 100644 index 0000000..fd0d581 --- /dev/null +++ b/templates.local/postfix/email_forwarding/master.cf @@ -0,0 +1,137 @@ +# +# Postfix master process configuration file. For details on the format +# of the file, see the master(5) manual page (command: "man 5 master" or +# on-line: http://www.postfix.org/master.5.html). +# +# Do not forget to execute "postfix reload" after editing this file. +# +# ========================================================================== +# service type private unpriv chroot wakeup maxproc command + args +# (yes) (yes) (no) (never) (100) +# ========================================================================== +smtp inet n - y - - smtpd +#smtp inet n - y - 1 postscreen +#smtpd pass - - y - - smtpd +#dnsblog unix - - y - 0 dnsblog +#tlsproxy unix - - y - 0 tlsproxy +# Choose one: enable submission for loopback clients only, or for any client. +#127.0.0.1:submission inet n - y - - smtpd +#submission inet n - y - - smtpd +# -o syslog_name=postfix/submission +# -o smtpd_tls_security_level=encrypt +# -o smtpd_sasl_auth_enable=yes +# -o smtpd_tls_auth_only=yes +# -o smtpd_reject_unlisted_recipient=no +# Instead of specifying complex smtpd__restrictions here, +# specify "smtpd__restrictions=$mua__restrictions" +# here, and specify mua__restrictions in main.cf (where +# "" is "client", "helo", "sender", "relay", or "recipient"). +# -o smtpd_client_restrictions= +# -o smtpd_helo_restrictions= +# -o smtpd_sender_restrictions= +# -o smtpd_relay_restrictions= +# -o smtpd_recipient_restrictions=permit_sasl_authenticated,reject +# -o milter_macro_daemon_name=ORIGINATING +# Choose one: enable submissions for loopback clients only, or for any client. +#127.0.0.1:submissions inet n - y - - smtpd +#submissions inet n - y - - smtpd +# -o syslog_name=postfix/submissions +# -o smtpd_tls_wrappermode=yes +# -o smtpd_sasl_auth_enable=yes +# -o smtpd_reject_unlisted_recipient=no +# Instead of specifying complex smtpd__restrictions here, +# specify "smtpd__restrictions=$mua__restrictions" +# here, and specify mua__restrictions in main.cf (where +# "" is "client", "helo", "sender", "relay", or "recipient"). +# -o smtpd_client_restrictions= +# -o smtpd_helo_restrictions= +# -o smtpd_sender_restrictions= +# -o smtpd_relay_restrictions= +# -o smtpd_recipient_restrictions=permit_sasl_authenticated,reject +# -o milter_macro_daemon_name=ORIGINATING +#628 inet n - y - - qmqpd +pickup unix n - y 60 1 pickup +cleanup unix n - y - 0 cleanup +qmgr unix n - n 300 1 qmgr +#qmgr unix n - n 300 1 oqmgr +tlsmgr unix - - y 1000? 1 tlsmgr +rewrite unix - - y - - trivial-rewrite +bounce unix - - y - 0 bounce +defer unix - - y - 0 bounce +trace unix - - y - 0 bounce +verify unix - - y - 1 verify +flush unix n - y 1000? 0 flush +proxymap unix - - n - - proxymap +proxywrite unix - - n - 1 proxymap +smtp unix - - y - - smtp +relay unix - - y - - smtp + -o syslog_name=postfix/$service_name +# -o smtp_helo_timeout=5 -o smtp_connect_timeout=5 +showq unix n - y - - showq +error unix - - y - - error +retry unix - - y - - error +discard unix - - y - - discard +local unix - n n - - local +virtual unix - n n - - virtual +lmtp unix - - y - - lmtp +anvil unix - - y - 1 anvil +scache unix - - y - 1 scache +postlog unix-dgram n - n - 1 postlogd +# +# ==================================================================== +# Interfaces to non-Postfix software. Be sure to examine the manual +# pages of the non-Postfix software to find out what options it wants. +# +# Many of the following services use the Postfix pipe(8) delivery +# agent. See the pipe(8) man page for information about ${recipient} +# and other message envelope options. +# ==================================================================== +# +# maildrop. See the Postfix MAILDROP_README file for details. +# Also specify in main.cf: maildrop_destination_recipient_limit=1 +# +maildrop unix - n n - - pipe + flags=DRXhu user=vmail argv=/usr/bin/maildrop -d ${recipient} +# +# ==================================================================== +# +# Recent Cyrus versions can use the existing "lmtp" master.cf entry. +# +# Specify in cyrus.conf: +# lmtp cmd="lmtpd -a" listen="localhost:lmtp" proto=tcp4 +# +# Specify in main.cf one or more of the following: +# mailbox_transport = lmtp:inet:localhost +# virtual_transport = lmtp:inet:localhost +# +# ==================================================================== +# +# Cyrus 2.1.5 (Amos Gouaux) +# Also specify in main.cf: cyrus_destination_recipient_limit=1 +# +#cyrus unix - n n - - pipe +# flags=DRX user=cyrus argv=/cyrus/bin/deliver -e -r ${sender} -m ${extension} ${user} +# +# ==================================================================== +# Old example of delivery via Cyrus. +# +#old-cyrus unix - n n - - pipe +# flags=R user=cyrus argv=/cyrus/bin/deliver -e -m ${extension} ${user} +# +# ==================================================================== +# +# See the Postfix UUCP_README file for configuration details. +# +uucp unix - n n - - pipe + flags=Fqhu user=uucp argv=uux -r -n -z -a$sender - $nexthop!rmail ($recipient) +# +# Other external delivery methods. +# +ifmail unix - n n - - pipe + flags=F user=ftn argv=/usr/lib/ifmail/ifmail -r $nexthop ($recipient) +bsmtp unix - n n - - pipe + flags=Fq. user=bsmtp argv=/usr/lib/bsmtp/bsmtp -t$nexthop -f$sender $recipient +scalemail-backend unix - n n - 2 pipe + flags=R user=scalemail argv=/usr/lib/scalemail/bin/scalemail-store ${nexthop} ${user} ${extension} +mailman unix - n n - - pipe + flags=FRX user=list argv=/usr/lib/mailman/bin/postfix-to-mailman.py ${nexthop} ${user} diff --git a/templates.local/postfix/send_only/main.cf b/templates.local/postfix/send_only/main.cf new file mode 100644 index 0000000..039aa8a --- /dev/null +++ b/templates.local/postfix/send_only/main.cf @@ -0,0 +1,32 @@ +# Ansible controlled! Do not change on remote host +smtpd_banner = $myhostname ESMTP $mail_name (Debian/GNU) +biff = no +append_dot_mydomain = no +#delay_warning_time = 4h +readme_directory = no +compatibility_level = 3.6 + + +smtpd_tls_cert_file=/etc/letsencrypt/live/host.example.com/fullchain.pem +smtpd_tls_key_file=/etc/letsencrypt/live/host.example.com/privkey.pem +#smtpd_tls_cert_file=/etc/ssl/certs/ssl-cert-snakeoil.pem +#smtpd_tls_key_file=/etc/ssl/private/ssl-cert-snakeoil.key +smtpd_tls_security_level=may + +smtp_tls_CApath=/etc/ssl/certs +smtp_tls_security_level=may +smtp_tls_session_cache_database = btree:${data_directory}/smtp_scache + +smtpd_relay_restrictions = permit_mynetworks permit_sasl_authenticated defer_unauth_destination +myhostname = host.example.com +alias_maps = hash:/etc/aliases +alias_database = hash:/etc/aliases +myorigin = /etc/mailname +mydestination = $myhostname, localhost.example.com, localhost +relayhost = +mynetworks = 127.0.0.0/8 [::ffff:127.0.0.0]/104 [::1]/128 +mailbox_size_limit = 0 +recipient_delimiter = + +inet_interfaces = all +inet_protocols = all + diff --git a/templates.local/postfix/send_only/master.cf b/templates.local/postfix/send_only/master.cf new file mode 100644 index 0000000..248aa99 --- /dev/null +++ b/templates.local/postfix/send_only/master.cf @@ -0,0 +1,138 @@ +# Ansible controlled! Do not change on remote host +# +# Postfix master process configuration file. For details on the format +# of the file, see the master(5) manual page (command: "man 5 master" or +# on-line: http://www.postfix.org/master.5.html). +# +# Do not forget to execute "postfix reload" after editing this file. +# +# ========================================================================== +# service type private unpriv chroot wakeup maxproc command + args +# (yes) (yes) (no) (never) (100) +# ========================================================================== +smtp inet n - y - - smtpd +#smtp inet n - y - 1 postscreen +#smtpd pass - - y - - smtpd +#dnsblog unix - - y - 0 dnsblog +#tlsproxy unix - - y - 0 tlsproxy +# Choose one: enable submission for loopback clients only, or for any client. +#127.0.0.1:submission inet n - y - - smtpd +#submission inet n - y - - smtpd +# -o syslog_name=postfix/submission +# -o smtpd_tls_security_level=encrypt +# -o smtpd_sasl_auth_enable=yes +# -o smtpd_tls_auth_only=yes +# -o smtpd_reject_unlisted_recipient=no +# Instead of specifying complex smtpd__restrictions here, +# specify "smtpd__restrictions=$mua__restrictions" +# here, and specify mua__restrictions in main.cf (where +# "" is "client", "helo", "sender", "relay", or "recipient"). +# -o smtpd_client_restrictions= +# -o smtpd_helo_restrictions= +# -o smtpd_sender_restrictions= +# -o smtpd_relay_restrictions= +# -o smtpd_recipient_restrictions=permit_sasl_authenticated,reject +# -o milter_macro_daemon_name=ORIGINATING +# Choose one: enable submissions for loopback clients only, or for any client. +#127.0.0.1:submissions inet n - y - - smtpd +#submissions inet n - y - - smtpd +# -o syslog_name=postfix/submissions +# -o smtpd_tls_wrappermode=yes +# -o smtpd_sasl_auth_enable=yes +# -o smtpd_reject_unlisted_recipient=no +# Instead of specifying complex smtpd__restrictions here, +# specify "smtpd__restrictions=$mua__restrictions" +# here, and specify mua__restrictions in main.cf (where +# "" is "client", "helo", "sender", "relay", or "recipient"). +# -o smtpd_client_restrictions= +# -o smtpd_helo_restrictions= +# -o smtpd_sender_restrictions= +# -o smtpd_relay_restrictions= +# -o smtpd_recipient_restrictions=permit_sasl_authenticated,reject +# -o milter_macro_daemon_name=ORIGINATING +#628 inet n - y - - qmqpd +pickup unix n - y 60 1 pickup +cleanup unix n - y - 0 cleanup +qmgr unix n - n 300 1 qmgr +#qmgr unix n - n 300 1 oqmgr +tlsmgr unix - - y 1000? 1 tlsmgr +rewrite unix - - y - - trivial-rewrite +bounce unix - - y - 0 bounce +defer unix - - y - 0 bounce +trace unix - - y - 0 bounce +verify unix - - y - 1 verify +flush unix n - y 1000? 0 flush +proxymap unix - - n - - proxymap +proxywrite unix - - n - 1 proxymap +smtp unix - - y - - smtp +relay unix - - y - - smtp + -o syslog_name=postfix/$service_name +# -o smtp_helo_timeout=5 -o smtp_connect_timeout=5 +showq unix n - y - - showq +error unix - - y - - error +retry unix - - y - - error +discard unix - - y - - discard +local unix - n n - - local +virtual unix - n n - - virtual +lmtp unix - - y - - lmtp +anvil unix - - y - 1 anvil +scache unix - - y - 1 scache +postlog unix-dgram n - n - 1 postlogd +# +# ==================================================================== +# Interfaces to non-Postfix software. Be sure to examine the manual +# pages of the non-Postfix software to find out what options it wants. +# +# Many of the following services use the Postfix pipe(8) delivery +# agent. See the pipe(8) man page for information about ${recipient} +# and other message envelope options. +# ==================================================================== +# +# maildrop. See the Postfix MAILDROP_README file for details. +# Also specify in main.cf: maildrop_destination_recipient_limit=1 +# +maildrop unix - n n - - pipe + flags=DRXhu user=vmail argv=/usr/bin/maildrop -d ${recipient} +# +# ==================================================================== +# +# Recent Cyrus versions can use the existing "lmtp" master.cf entry. +# +# Specify in cyrus.conf: +# lmtp cmd="lmtpd -a" listen="localhost:lmtp" proto=tcp4 +# +# Specify in main.cf one or more of the following: +# mailbox_transport = lmtp:inet:localhost +# virtual_transport = lmtp:inet:localhost +# +# ==================================================================== +# +# Cyrus 2.1.5 (Amos Gouaux) +# Also specify in main.cf: cyrus_destination_recipient_limit=1 +# +#cyrus unix - n n - - pipe +# flags=DRX user=cyrus argv=/cyrus/bin/deliver -e -r ${sender} -m ${extension} ${user} +# +# ==================================================================== +# Old example of delivery via Cyrus. +# +#old-cyrus unix - n n - - pipe +# flags=R user=cyrus argv=/cyrus/bin/deliver -e -m ${extension} ${user} +# +# ==================================================================== +# +# See the Postfix UUCP_README file for configuration details. +# +uucp unix - n n - - pipe + flags=Fqhu user=uucp argv=uux -r -n -z -a$sender - $nexthop!rmail ($recipient) +# +# Other external delivery methods. +# +ifmail unix - n n - - pipe + flags=F user=ftn argv=/usr/lib/ifmail/ifmail -r $nexthop ($recipient) +bsmtp unix - n n - - pipe + flags=Fq. user=bsmtp argv=/usr/lib/bsmtp/bsmtp -t$nexthop -f$sender $recipient +scalemail-backend unix - n n - 2 pipe + flags=R user=scalemail argv=/usr/lib/scalemail/bin/scalemail-store ${nexthop} ${user} ${extension} +mailman unix - n n - - pipe + flags=FRX user=list argv=/usr/lib/mailman/bin/postfix-to-mailman.py ${nexthop} ${user} diff --git a/templates.local/postfix/virtual b/templates.local/postfix/virtual new file mode 100644 index 0000000..771d803 --- /dev/null +++ b/templates.local/postfix/virtual @@ -0,0 +1,6 @@ +root {{ postfix_receipient_email }} +webmaster {{ postfix_receipient_email }} +#ada ada@miller.com +#all@example.com jonny@miller.com ada@miller.com +#@example.com info@miller.com + diff --git a/templates.local/readme.txt b/templates.local/readme.txt new file mode 100644 index 0000000..bb20bd9 --- /dev/null +++ b/templates.local/readme.txt @@ -0,0 +1,2 @@ +Objective: This directory contains templates that are specific to the project and must be adapted for each project. +Therefore, the files may only be copied into the project, not linked. diff --git a/templates.local/scripts/CreateSysInfo.conf b/templates.local/scripts/CreateSysInfo.conf new file mode 100644 index 0000000..8b1c26f --- /dev/null +++ b/templates.local/scripts/CreateSysInfo.conf @@ -0,0 +1,14 @@ +# This file will be included into CreateSysInfo +DIR_INFO=/srv/www/sys.info/example.com +# a blank separated list of mount points formatted with BTRFS, e.g. "/media/fs.cache /media/fs.system" +BTRFS_FS="" +# a blank separated list of devices that should be controlled by smartmon, e.g. 'sda sdb' +SMART_MON_DEVS='nvme0n1' +# set to yes if mdadm manages software raid devices +HAS_SOFT_RAID=no +MK_HOME_TAR=yes +USE_ZFS=no +# a blank separated list of "--exclude=" entries. That listed subdirs will not be saved in the TAR archive +EXCLUDE_HOME="--exclude=jails" +# each subdirectory of /home containing that file will not be saved as TAR archive +TAG_FILE=.do.not.save.as.home diff --git a/templates.local/scripts/ExampleBackup b/templates.local/scripts/ExampleBackup new file mode 100644 index 0000000..2085655 --- /dev/null +++ b/templates.local/scripts/ExampleBackup @@ -0,0 +1,7 @@ +#! /bin/bash +VERBOSE=-v +QUIET= +test "$1" = -q && VERBOSE=-q && QUIET=-q +/usr/local/bin/CreateSysInfo $QUIET & +/usr/local/bin/SaveDatabases $QUIET & + diff --git a/templates.local/users.yaml b/templates.local/users.yaml new file mode 100644 index 0000000..e69de29 diff --git a/templates.vars/common.yaml b/templates.vars/common.yaml new file mode 100644 index 0000000..9079a81 --- /dev/null +++ b/templates.vars/common.yaml @@ -0,0 +1,12 @@ +configuration_directory: /etc/ansknife +remote_webapps_directory: "{{ configuration_directory }}/webapps.d" +local_webapps_directory: "../webapps" +remote_www_directory: "/home/www" +# the system log files have maximal that size +systemd_journal_system_max_use: 200M +systemd_journal_system_max_file_size: 50M +postfix_host: "example.infeos.it" +postfix_domain: "infeos.it" +postfix_mode: send_only +postfix_receipient_email: "mail@example.com" +webmaster_email: "web@example.com" diff --git a/templates.vars/dkim.yaml b/templates.vars/dkim.yaml new file mode 100644 index 0000000..c61e6f3 --- /dev/null +++ b/templates.vars/dkim.yaml @@ -0,0 +1,8 @@ +--- +dkim_domains: ["example.com"] +dkim_opendkim_config_dir: /etc/opendkim +dkim_config_file: /etc/opendkim.conf +dkim_selector: "email" +dkim_user: opendkim +dkim_group: opendkim + diff --git a/templates.vars/firewalld.yaml b/templates.vars/firewalld.yaml new file mode 100644 index 0000000..60af0e5 --- /dev/null +++ b/templates.vars/firewalld.yaml @@ -0,0 +1,4 @@ +--- +# Facts (variables) of the firewall +firewall_iface_public: enp5s0 + diff --git a/templates.vars/mysql.yaml b/templates.vars/mysql.yaml new file mode 100644 index 0000000..00573f9 --- /dev/null +++ b/templates.vars/mysql.yaml @@ -0,0 +1,8 @@ +--- +# Creation of the central password file: +# echo "top_secret_password" > resources/.vaults +# Creation of the encrypted vault file: +# ansible-vault encrypt_string --vault-password-file resources/.vault --name 'vault_dba_password' --stdin-name 'vault_dba_password' | tee vars/mysql_vault.yaml +# Find the password file: ANSIBLE_VAULT_PASSWORD_FILE=resources/.vault +dba_name: dba +dba_password: "{{ vault_dba_password }}" \ No newline at end of file diff --git a/templates.vars/mysql_vault.yaml b/templates.vars/mysql_vault.yaml new file mode 100644 index 0000000..5241a79 --- /dev/null +++ b/templates.vars/mysql_vault.yaml @@ -0,0 +1 @@ +vault_dba_password: TopSecret \ No newline at end of file diff --git a/templates.vars/packages.yaml b/templates.vars/packages.yaml new file mode 100644 index 0000000..360b9c6 --- /dev/null +++ b/templates.vars/packages.yaml @@ -0,0 +1,8 @@ +packages_list: + - certbot + - git + - unzip + - 7zip + - unrar-free + - htop + - smartmontools diff --git a/templates.vars/php.yaml b/templates.vars/php.yaml new file mode 100644 index 0000000..a9bcf1d --- /dev/null +++ b/templates.vars/php.yaml @@ -0,0 +1,44 @@ +--- +# php.yaml: +# Defines variables for the PHP role. +# This file is used to set up the PHP environment and configuration. +# needed variables: php_version + +php_packages: + - php{{php_version}}-common + - php{{php_version}}-curl + - php{{php_version}}-fpm + - php{{php_version}}-gd + - php{{php_version}}-igbinary + - php{{php_version}}-imagick + - php{{php_version}}-imap + - php{{php_version}}-intl + - php{{php_version}}-mbstring + - php{{php_version}}-memcached + - php{{php_version}}-msgpack + - php{{php_version}}-mysql + - php{{php_version}}-opcache + - php{{php_version}}-phpdbg + - php{{php_version}}-readline + - php{{php_version}}-redis + - php{{php_version}}-xdebug + - php{{php_version}}-xml + - php{{php_version}}-zip +php_additional_packages: + - redis-server + - imagemagick + +php_ini_settings: + - { section: "DEFAULT", option: "memory_limit", value: "512M" } + - { section: "DEFAULT", option: "upload_max_filesize", value: "512M" } + - { section: "DEFAULT", option: "max_file_uploads", value: 100 } + - { section: "DEFAULT", option: "post_max_size", value: "512M" } + - { section: "DEFAULT", option: "max_execution_time", value: 600 } + - { section: "DEFAULT", option: "max_input_time", value: 600 } + - { section: "DEFAULT", option: "default_socket_timeout", value: 600 } + - { section: "Session", option: "session.save_handler", value: "redis" } + - { section: "Session", option: "session.save_path", value: "tcp://127.0.0.1:6379" } + - { section: "opcache", option: "opcache.enable", value: 1 } + - { section: "opcache", option: "opcache.memory_consumption", value: 512 } + - { section: "opcache", option: "opcache.interned_strings_buffer", value: 256 } + diff --git a/templates.vars/ssl-certificate.yaml b/templates.vars/ssl-certificate.yaml new file mode 100644 index 0000000..7f42064 --- /dev/null +++ b/templates.vars/ssl-certificate.yaml @@ -0,0 +1,9 @@ +--- +# "/C=DE/ST=NRW/L=Bochum/O=IT/CN={{hostname}}" +ssl_country: DE +ssl_state: Bavaria +ssl_locality: Munich +ssl_organization: 'Example company Limited' +ssl_lifetime: 365 +ssl_rsa_key_size: 2048 + diff --git a/templates.vars/users.yaml b/templates.vars/users.yaml new file mode 100644 index 0000000..4d74e5d --- /dev/null +++ b/templates.vars/users.yaml @@ -0,0 +1,9 @@ +--- +# human users: use user ids > 1500 to avoid conflicts with ad hoc users (1000-1499) +user_humans: + jonny: 1000 + admin: 1501 +# the following users can use sudo to receive root rights +user_sudo_members: + - jonny + - admin diff --git a/templates.vars/webapps.yaml b/templates.vars/webapps.yaml new file mode 100644 index 0000000..4825c48 --- /dev/null +++ b/templates.vars/webapps.yaml @@ -0,0 +1,6 @@ +webapps_list: + - dummy + db_name: dbdummy5 + db_user: dummy5 + db_password: NeverKnown5 + directory: /srv/www/myapp5.example.com