From cb2001357f7df06d55afd0d32f5b4e797a98db46 Mon Sep 17 00:00:00 2001 From: Bastian de Byl Date: Sat, 30 Apr 2022 03:44:55 -0400 Subject: [PATCH] moved ddns, partkeepr, hass to podman, selinux --- .gitignore | 1 + Makefile | 7 +- ansible.cfg | 2 +- ansible/deploy_home.yml | 9 +- ansible/inventories/home/hosts.yml | 5 + ansible/roles/common/defaults/main.yml | 17 +++- .../common/files/fail2ban/jails/nginx.local | 14 +-- ansible/roles/common/tasks/deps.yml | 2 +- ansible/roles/common/tasks/main.yml | 15 +++ ansible/roles/ddns/tasks/main.yml | 2 - ansible/roles/drone/handlers/main.yml | 4 + ansible/roles/drone/tasks/drone.yml | 4 +- ansible/roles/drone/tasks/main.yml | 1 + ansible/roles/drone/tasks/selinux.yml | 9 ++ ansible/roles/graylog/tasks/graylog.yml | 9 +- ansible/roles/hass/files/automations.yaml | Bin 935 -> 0 bytes ansible/roles/hass/files/configuration.yaml | Bin 460 -> 0 bytes ansible/roles/hass/meta/main.yml | 4 - ansible/roles/hass/tasks/hass.yml | 44 -------- ansible/roles/hass/tasks/main.yml | 2 - ansible/roles/http/defaults/main.yml | 2 - ansible/roles/http/handlers/main.yml | 13 ++- ansible/roles/http/tasks/deps.yml | 2 +- ansible/roles/http/tasks/firewall.yml | 12 +++ ansible/roles/http/tasks/http.yml | 16 +-- ansible/roles/http/tasks/logrotate.yml | 10 -- ansible/roles/http/tasks/main.yml | 4 +- ansible/roles/http/tasks/nginx.yml | 33 ++++++ ansible/roles/http/tasks/security.yml | 14 --- .../roles/http/templates/nginx/nginx.conf.j2 | 2 +- .../nginx/sites/ci.bdebyl.net.http.conf.j2 | 1 - .../nginx/sites/ci.bdebyl.net.https.conf.j2 | 3 +- .../nginx/sites/home.bdebyl.net.conf.j2 | 2 +- .../nginx/sites/logs.bdebyl.net.conf.j2 | 1 + .../nginx/sites/parts.bdebyl.net.conf.j2 | 1 - .../sites/parts.bdebyl.net.https.conf.j2 | 3 +- .../nginx/sites/pi.bdebyl.net.conf.j2 | 1 - ansible/roles/motion/defaults/main.yml | 2 - ansible/roles/motion/handlers/main.yml | 6 -- ansible/roles/motion/meta/main.yml | 4 - ansible/roles/motion/tasks/deps.yml | 9 -- ansible/roles/motion/tasks/main.yml | 3 - ansible/roles/motion/tasks/motion.yml | 55 ---------- ansible/roles/partkeepr/meta/main.yml | 4 - ansible/roles/partkeepr/tasks/main.yml | 87 ---------------- ansible/roles/pihole/tasks/deps.yml | 2 +- ansible/roles/podman/defaults/main.yml | 3 + ansible/roles/podman/files/automations.yaml | 41 ++++++++ ansible/roles/podman/files/configuration.yaml | 19 ++++ ansible/roles/podman/handlers/main.yml | 8 ++ ansible/roles/{ddns => podman}/meta/main.yml | 0 .../tasks/container-awsddns.yml} | 17 +++- ansible/roles/podman/tasks/container-hass.yml | 57 +++++++++++ .../podman/tasks/container-partkeepr.yml | 95 ++++++++++++++++++ ansible/roles/podman/tasks/main.yml | 5 + ansible/roles/podman/tasks/podman.yml | 58 +++++++++++ .../roles/podman/tasks/systemd-generate.yml | 17 ++++ ansible/roles/ssl/defaults/main.yml | 2 + ansible/roles/ssl/tasks/certbot.yml | 16 ++- ansible/roles/ssl/tasks/deps.yml | 7 ++ ansible/vars/vault.yml | Bin 4394 -> 5496 bytes 61 files changed, 481 insertions(+), 307 deletions(-) delete mode 100644 ansible/roles/ddns/tasks/main.yml create mode 100644 ansible/roles/drone/handlers/main.yml create mode 100644 ansible/roles/drone/tasks/selinux.yml delete mode 100644 ansible/roles/hass/files/automations.yaml delete mode 100644 ansible/roles/hass/files/configuration.yaml delete mode 100644 ansible/roles/hass/meta/main.yml delete mode 100644 ansible/roles/hass/tasks/hass.yml delete mode 100644 ansible/roles/hass/tasks/main.yml create mode 100644 ansible/roles/http/tasks/firewall.yml delete mode 100644 ansible/roles/http/tasks/logrotate.yml create mode 100644 ansible/roles/http/tasks/nginx.yml delete mode 100644 ansible/roles/http/tasks/security.yml delete mode 100644 ansible/roles/motion/defaults/main.yml delete mode 100644 ansible/roles/motion/handlers/main.yml delete mode 100644 ansible/roles/motion/meta/main.yml delete mode 100644 ansible/roles/motion/tasks/deps.yml delete mode 100644 ansible/roles/motion/tasks/main.yml delete mode 100644 ansible/roles/motion/tasks/motion.yml delete mode 100644 ansible/roles/partkeepr/meta/main.yml delete mode 100644 ansible/roles/partkeepr/tasks/main.yml create mode 100644 ansible/roles/podman/defaults/main.yml create mode 100644 ansible/roles/podman/files/automations.yaml create mode 100644 ansible/roles/podman/files/configuration.yaml create mode 100644 ansible/roles/podman/handlers/main.yml rename ansible/roles/{ddns => podman}/meta/main.yml (100%) rename ansible/roles/{ddns/tasks/awsddns.yml => podman/tasks/container-awsddns.yml} (56%) create mode 100644 ansible/roles/podman/tasks/container-hass.yml create mode 100644 ansible/roles/podman/tasks/container-partkeepr.yml create mode 100644 ansible/roles/podman/tasks/main.yml create mode 100644 ansible/roles/podman/tasks/podman.yml create mode 100644 ansible/roles/podman/tasks/systemd-generate.yml create mode 100644 ansible/roles/ssl/defaults/main.yml create mode 100644 ansible/roles/ssl/tasks/deps.yml diff --git a/.gitignore b/.gitignore index 0218305..201651b 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ .ansible-vaultpass +.venv/* diff --git a/Makefile b/Makefile index e54fac2..feca4c8 100644 --- a/Makefile +++ b/Makefile @@ -26,10 +26,11 @@ SSH_KEY=${HOME}/.ssh/id_rsa_home_ansible # Default to all ansible tags to run (passed via 'make deploy TAGS=sometag') TAGS?=all +SKIP_TAGS?=none TARGET?=all ${VENV}: - virtualenv -p python3 ${VENV} + python3 -m venv ${VENV} ${PIP}: ${VENV} ${ANSIBLE} ${ANSIBLE_VAULT} ${LINT_YAML} ${LINT_ANSIBLE}: ${VENV} requirements.txt @@ -51,7 +52,7 @@ SKIP_FILE=./.lint-vars.sh # Targets deploy: ${ANSIBLE} ${VAULT_FILE} - ${ANSIBLE} --diff --private-key ${SSH_KEY} -t ${TAGS} -i ${ANSIBLE_INVENTORY} -l ${TARGET} --vault-password-file ${VAULT_PASS_FILE} ansible/deploy.yml + ${ANSIBLE} --diff --private-key ${SSH_KEY} -t ${TAGS} --skip-tags ${SKIP_TAGS} -i ${ANSIBLE_INVENTORY} -l ${TARGET} --vault-password-file ${VAULT_PASS_FILE} ansible/deploy.yml list-tags: ${ANSIBLE} ${VAULT_FILE} ${ANSIBLE} --list-tags -i ${ANSIBLE_INVENTORY} -l ${TARGET} --vault-password-file ${VAULT_PASS_FILE} ansible/deploy.yml @@ -60,7 +61,7 @@ list-tasks: ${ANSIBLE} ${VAULT_FILE} ${ANSIBLE} --list-tasks -i ${ANSIBLE_INVENTORY} -l ${TARGET} --vault-password-file ${VAULT_PASS_FILE} ansible/deploy.yml check: ${ANSIBLE} ${VAULT_FILE} - ${ANSIBLE} --check --diff --private-key ${SSH_KEY} -t ${TAGS} -i ${ANSIBLE_INVENTORY} -l ${TARGET} --vault-password-file ${VAULT_PASS_FILE} ansible/deploy.yml + ${ANSIBLE} --check --diff --private-key ${SSH_KEY} -t ${TAGS} --skip-tags ${SKIP_TAGS} -i ${ANSIBLE_INVENTORY} -l ${TARGET} --vault-password-file ${VAULT_PASS_FILE} ansible/deploy.yml vault: ${ANSIBLE_VAULT} ${VAULT_FILE} ${ANSIBLE_VAULT} edit --vault-password-file ${VAULT_PASS_FILE} ${VAULT_FILE} diff --git a/ansible.cfg b/ansible.cfg index 82a20a5..a3a4a47 100644 --- a/ansible.cfg +++ b/ansible.cfg @@ -2,7 +2,7 @@ callback_enabled = profile_tasks # Do not gather facts by default -gathering = explicit +#gathering = explicit # Hide warnings about discovered Python interpreter interpreter_python = auto_silent diff --git a/ansible/deploy_home.yml b/ansible/deploy_home.yml index 13739ac..dbb9a1e 100644 --- a/ansible/deploy_home.yml +++ b/ansible/deploy_home.yml @@ -5,12 +5,9 @@ roles: - role: common - role: git - - role: ddns + - role: podman - role: ssl - - role: pihole - - role: http + #- role: pihole - role: drone - - role: hass - - role: motion - - role: partkeepr - role: graylog + - role: http diff --git a/ansible/inventories/home/hosts.yml b/ansible/inventories/home/hosts.yml index 64a403e..d9ed16b 100644 --- a/ansible/inventories/home/hosts.yml +++ b/ansible/inventories/home/hosts.yml @@ -3,3 +3,8 @@ all: hosts: home.bdebyl.net: ansible_user: ansible + children: + newhome: + hosts: + galactica.lan: + ansible_user: fedora diff --git a/ansible/roles/common/defaults/main.yml b/ansible/roles/common/defaults/main.yml index b5057ad..e4cb465 100644 --- a/ansible/roles/common/defaults/main.yml +++ b/ansible/roles/common/defaults/main.yml @@ -1,12 +1,21 @@ --- -deps: [cronie, docker, fail2ban, git, logrotate, python-docker, tmux, weechat] +deps: + [ + cockpit-podman, + cronie, + docker, + fail2ban, + fail2ban-selinux, + git, + logrotate, + podman, + python-docker, + ] fail2ban_jails: [sshd.local, nginx.local] services: - - cronie + - crond - docker - fail2ban - - iptables - - nginx - systemd-timesyncd diff --git a/ansible/roles/common/files/fail2ban/jails/nginx.local b/ansible/roles/common/files/fail2ban/jails/nginx.local index 2826281..f637405 100644 --- a/ansible/roles/common/files/fail2ban/jails/nginx.local +++ b/ansible/roles/common/files/fail2ban/jails/nginx.local @@ -7,13 +7,13 @@ bantime = 1w maxretry = 8 ignoreip = 127.0.0.1/32 192.168.1.0/24 -[nginx-http-auth] -enabled = true -port = http,https -logpath = %(nginx_error_log)s -bantime = 2w -maxretry = 5 -ignoreip = 127.0.0.1/32 192.168.1.0/24 +#[nginx-http-auth] +#enabled = true +#port = http,https +#logpath = %(nginx_error_log)s +#bantime = 2w +#maxretry = 5 +#ignoreip = 127.0.0.1/32 192.168.1.0/24 [nginx-botsearch] enabled = true diff --git a/ansible/roles/common/tasks/deps.yml b/ansible/roles/common/tasks/deps.yml index ccbf5e3..43834c2 100644 --- a/ansible/roles/common/tasks/deps.yml +++ b/ansible/roles/common/tasks/deps.yml @@ -1,7 +1,7 @@ --- - name: install common dependencies become: true - community.general.pacman: + ansible.builtin.package: name: "{{ deps }}" state: present tags: deps diff --git a/ansible/roles/common/tasks/main.yml b/ansible/roles/common/tasks/main.yml index 73a2b03..8aca606 100644 --- a/ansible/roles/common/tasks/main.yml +++ b/ansible/roles/common/tasks/main.yml @@ -2,3 +2,18 @@ - import_tasks: deps.yml - import_tasks: security.yml - import_tasks: service.yml + +- name: create the docker group + become: true + ansible.builtin.group: + name: docker + state: present + tags: common + +- name: add default user to docker group + become: true + ansible.builtin.user: + name: "{{ ansible_user }}" + groups: docker + append: true + tags: common diff --git a/ansible/roles/ddns/tasks/main.yml b/ansible/roles/ddns/tasks/main.yml deleted file mode 100644 index 4857114..0000000 --- a/ansible/roles/ddns/tasks/main.yml +++ /dev/null @@ -1,2 +0,0 @@ ---- -- import_tasks: awsddns.yml diff --git a/ansible/roles/drone/handlers/main.yml b/ansible/roles/drone/handlers/main.yml new file mode 100644 index 0000000..2de51a2 --- /dev/null +++ b/ansible/roles/drone/handlers/main.yml @@ -0,0 +1,4 @@ +--- +- name: restorecon drone + become: true + ansible.builtin.command: sh -c 'restorecon -Firv /var/lib/drone' diff --git a/ansible/roles/drone/tasks/drone.yml b/ansible/roles/drone/tasks/drone.yml index eaaf7c8..314cb46 100644 --- a/ansible/roles/drone/tasks/drone.yml +++ b/ansible/roles/drone/tasks/drone.yml @@ -4,7 +4,7 @@ community.general.docker_container: name: drone image: drone/drone:latest - recreate: true + recreate: false restart: true restart_policy: on-failure restart_retries: 3 @@ -32,7 +32,7 @@ community.general.docker_container: name: drone-runner image: drone/drone-runner-docker:latest - recreate: true + recreate: false restart: true restart_policy: on-failure restart_retries: 3 diff --git a/ansible/roles/drone/tasks/main.yml b/ansible/roles/drone/tasks/main.yml index 479cac1..c44b8ef 100644 --- a/ansible/roles/drone/tasks/main.yml +++ b/ansible/roles/drone/tasks/main.yml @@ -1,2 +1,3 @@ --- - import_tasks: drone.yml +- import_tasks: selinux.yml diff --git a/ansible/roles/drone/tasks/selinux.yml b/ansible/roles/drone/tasks/selinux.yml new file mode 100644 index 0000000..433b50d --- /dev/null +++ b/ansible/roles/drone/tasks/selinux.yml @@ -0,0 +1,9 @@ +--- +- name: selinux context for drone directory + become: true + community.general.sefcontext: + target: "/var/lib/drone(/.*)?" + setype: svirt_sandbox_file_t + state: present + notify: restorecon drone + tags: selinux diff --git a/ansible/roles/graylog/tasks/graylog.yml b/ansible/roles/graylog/tasks/graylog.yml index 73eecae..bb9ef30 100644 --- a/ansible/roles/graylog/tasks/graylog.yml +++ b/ansible/roles/graylog/tasks/graylog.yml @@ -44,9 +44,10 @@ transport.host: "localhost" network.host: "0.0.0.0" cluster.name: "graylog" - ES_JAVA_OPTS: "-Dlog4j2.formatMsgNoLookups=true -Xms512m -Xmx512m" + ES_JAVA_OPTS: "-Dlog4j2.formatMsgNoLookups=true -Xms512m -Xmx2048m" ulimits: - "memlock:-1:-1" + - "nofile:64000:64000" memory: 1G tags: graylog @@ -58,14 +59,18 @@ restart: true restart_policy: on-failure restart_retries: 3 + sysctls: + net.ipv6.conf.all.disable_ipv6: 1 + net.ipv6.conf.default.disable_ipv6: 1 networks: - name: "graylog" volumes: - graylog-conf:/usr/share/graylog/data/config + - /var/lib/docker/shared/graylog:/usr/share/graylog/bin:z env: GRAYLOG_PASSWORD_SECRET: "{{ graylog_secret }}" GRAYLOG_ROOT_PASSWORD_SHA2: "{{ graylog_root_pass_sha2 }}" - GRAYLOG_HTTP_EXTERNAL_URI: http://192.168.1.12:9000/ + GRAYLOG_HTTP_EXTERNAL_URI: http://192.168.1.10:9000/ GRAYLOG_HTTP_BIND_ADDRESS: 0.0.0.0:9000 GRAYLOG_MONGODB_URI: mongodb://graylog-mongo/graylog GRAYLOG_ELASTICSEARCH_HOSTS: http://graylog-elastic:9200 diff --git a/ansible/roles/hass/files/automations.yaml b/ansible/roles/hass/files/automations.yaml deleted file mode 100644 index 64014c12bdc89f5c6f7f7fa41d5102a0cf5d0214..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 935 zcmV;Y16ce3M@dveQdv+`0BR>cl&V>&CskN3hQ>Qh7`^#8Ndx}3!ptPQ4morPp}6({ zl~iu5hoSKpZJ;ha({hBr=1Fb)`>eLB?LO|DA%O>}4-j{!|NRzS#o%9L#&K-F#XMVl z(?8@!Ct@7ib|B8}XH1~6kR+RzPpU?ZMQlMHl0`amfhv|1&HXABzXPOjM2AwP?*T+1 zrfb};VX?hWlZ!%7r&5Fm8C_NXvBThl=pFDfXK+kb_W)yT?DS41ae|th$HrOFFA}BA z;CUn!{GGi_kJ4JvW#aUc4CLSd8GPWY8Rc}OK9!KYEJpgJf2x`?sj{F~o_Z^l@X@PP z#P-^;oPe=q2?@%5*jUGTU=2|7md4}0SVKb`Swa~aJ zNoG~7dvv?$+RfQeVoskfZNzLS?g&Fzx;VzbHN;$lr$bW%l3g8>LG9g^Adj3Ff zSGbEI)onKsf!YB0KTYtRQLA)KU3x73B$oP1FuEr#aEN~btO z?tfw5asYU4U3sqY2Z@u}neBF;!e-h@m(7UUsAM0Wkc>N;ZPJ zj*I=x-#cjWaZGY(;&NG&H{zd!To~gD0n;@^G3)kd9y$X`$$yA0z@+wMat7c%fwcyV zF3OK)!PKS$Gh4R5#qs^_Xu=={hxB9Q + bash -c "crontab /etc/cron.d/partkeepr && cron -f" + recreate: false + restart: true + restart_policy: on-failure + log_driver: journald + network: + - partkeepr + tags: partkeepr + +- name: create systemd startup job for partkeepr-cron + include_tasks: systemd-generate.yml + vars: + container_name: partkeepr-cron + tags: partkeepr diff --git a/ansible/roles/podman/tasks/main.yml b/ansible/roles/podman/tasks/main.yml new file mode 100644 index 0000000..1f0468a --- /dev/null +++ b/ansible/roles/podman/tasks/main.yml @@ -0,0 +1,5 @@ +--- +- import_tasks: podman.yml +- import_tasks: container-awsddns.yml +- import_tasks: container-partkeepr.yml +- import_tasks: container-hass.yml diff --git a/ansible/roles/podman/tasks/podman.yml b/ansible/roles/podman/tasks/podman.yml new file mode 100644 index 0000000..85a76d7 --- /dev/null +++ b/ansible/roles/podman/tasks/podman.yml @@ -0,0 +1,58 @@ +--- +- name: create podman user + become: true + ansible.builtin.user: + name: "{{ podman_user }}" + comment: Rootless podman user + shell: /sbin/nologin + home: "{{ podman_home }}" + tags: podman + +- name: check if podman user lingering enabled + become: true + ansible.builtin.stat: + path: "/var/lib/systemd/linger/{{ podman_user }}" + register: user_lingering + tags: podman + +- name: enable podman user lingering + become: true + become_user: "{{ podman_user }}" + ansible.builtin.command: | + loginctl enable-linger {{ podman_user }} + when: + - not user_lingering.stat.exists + tags: podman + +- name: selinux context for podman directories + become: true + community.general.sefcontext: + target: "{{ item.target }}(/.*)?" + setype: "{{ item.setype }}" + state: present + notify: restorecon podman + with_items: + - { target: "{{ podman_home }}", setype: "user_home_dir_t" } + - { target: "{{ podman_path }}", setype: "container_file_t" } + tags: + - podman + - selinux + +- name: create podman system directories + become: true + become_user: "{{ podman_user }}" + ansible.builtin.file: + path: "{{ podman_home }}/{{ item }}" + state: directory + owner: "{{ podman_user }}" + group: "{{ podman_user }}" + mode: 0755 + notify: restorecon podman + with_items: + - ".config/systemd/user" + - "{{ podman_containers }}" + - "{{ podman_volumes }}" + tags: podman + +- meta: flush_handlers + tags: podman diff --git a/ansible/roles/podman/tasks/systemd-generate.yml b/ansible/roles/podman/tasks/systemd-generate.yml new file mode 100644 index 0000000..f86c062 --- /dev/null +++ b/ansible/roles/podman/tasks/systemd-generate.yml @@ -0,0 +1,17 @@ +--- +- name: create systemd startup job for {{ container_name }} + become: true + become_user: "{{ podman_user }}" + ansible.builtin.shell: | + podman generate systemd {{ container_name }} > {{ podman_home}}/.config/systemd/user/{{ container_name }}.service + tags: systemd + +- name: enable systemd startup job for {{ container_name }} + become: true + become_user: "{{ podman_user }}" + ansible.builtin.systemd: + name: "{{ container_name }}.service" + daemon_reload: true + enabled: true + scope: user + tags: systemd diff --git a/ansible/roles/ssl/defaults/main.yml b/ansible/roles/ssl/defaults/main.yml new file mode 100644 index 0000000..6b91523 --- /dev/null +++ b/ansible/roles/ssl/defaults/main.yml @@ -0,0 +1,2 @@ +--- +deps: [certbot] diff --git a/ansible/roles/ssl/tasks/certbot.yml b/ansible/roles/ssl/tasks/certbot.yml index ebda748..02ae53d 100644 --- a/ansible/roles/ssl/tasks/certbot.yml +++ b/ansible/roles/ssl/tasks/certbot.yml @@ -1,18 +1,28 @@ --- +- name: create nginx ssl directory + become: true + ansible.builtin.file: + path: /etc/nginx/ssl + owner: root + group: root + mode: 0644 + state: directory + tags: ssl + - name: stat dhparam become: true ansible.builtin.stat: - path: /etc/ssl/certs/dhparam.pem + path: /etc/nginx/ssl/dhparam.pem register: dhparam tags: ssl - name: generate openssl dhparam for nginx become: true ansible.builtin.command: | - openssl dhparam -out /etc/ssl/certs/dhparam.pem 2048 + openssl dhparam -out /etc/nginx/ssl/dhparam.pem 2048 when: not dhparam.stat.exists args: - creates: /etc/ssl/certs/dhparam.pem + creates: /etc/nginx/ssl/dhparam.pem tags: ssl - name: create ssl certificate for ci server diff --git a/ansible/roles/ssl/tasks/deps.yml b/ansible/roles/ssl/tasks/deps.yml new file mode 100644 index 0000000..d712c92 --- /dev/null +++ b/ansible/roles/ssl/tasks/deps.yml @@ -0,0 +1,7 @@ +--- +- name: install ssl dependencies + become: true + ansible.builtin.package: + name: "{{ deps }}" + state: present + tags: deps diff --git a/ansible/vars/vault.yml b/ansible/vars/vault.yml index be66e12e6e18ab0ddc8fc255ddd93f07167bd897..036950b1d01ecda779e9b0aa2446e030b55c910d 100644 GIT binary patch literal 5496 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 z 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 literal 4394 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 kAzDraenbV4B%z&