diff --git a/group_vars/all/vars.yml b/group_vars/all/vars.yml index 2940dfc..2bc1d7f 100644 --- a/group_vars/all/vars.yml +++ b/group_vars/all/vars.yml @@ -17,6 +17,9 @@ escalation_method: doas sshd_port: 2500 +# Username for the priviledged user +admin_username: maestro + # Username for unpriviledged user username: dogeystamp @@ -129,6 +132,9 @@ enable_nameserver: yes # SSL ACME (Let's Encrypt) enable_ssl: yes +# nginx: necessary for gitea, synapse, wiki, and website. +enable_webserver: yes + # Git server enable_gitea: yes diff --git a/hosts.example b/hosts.example new file mode 100644 index 0000000..e3a9c4d --- /dev/null +++ b/hosts.example @@ -0,0 +1,7 @@ +# Copy this to hosts. + +# fallback_host would be the DHCP address given to the host. +# It will be changed to local_ip during execution of the connection role. + +[sv] +your_hostname fallback_host=192.168.0.109 ansible_port=2500 ansible_connection=ssh ansible_ssh_private_key_file=~/.ssh/keys/patria diff --git a/roles/system/templates/bash_profile.j2 b/roles/dotfiles/templates/bash_profile.j2 similarity index 100% rename from roles/system/templates/bash_profile.j2 rename to roles/dotfiles/templates/bash_profile.j2 diff --git a/roles/networking/connection/tasks/main.yml b/roles/networking/connection/tasks/main.yml index 74ff3ff..80ba822 100644 --- a/roles/networking/connection/tasks/main.yml +++ b/roles/networking/connection/tasks/main.yml @@ -9,13 +9,36 @@ enabled: yes state: started -- name: Disable existing eth0 connection - community.general.nmcli: - conn_name: eth0 - state: absent - register: networkmanager_config +- name: Send script to remove externally managed eth0 connection + template: + src: rmconn.sh + dest: /root/rmconn.sh + mode: 700 + register: rmconn -- name: Set static IP address +- name: Run rmconn every 30 minutes + cron: + name: "Ensure eth0 is not externally managed" + minute: "*/30" + job: "/root/rmconn.sh" + state: present + +- name: Run rmconn every minute (temporarily) + cron: + name: "Ensure eth0 is not externally managed (temporary)" + minute: "*" + job: "/root/rmconn.sh" + state: present + when: rmconn.changed + +- name: Run rmconn at boot + cron: + name: "Ensure eth0 is not externally managed (at reboot)" + special_time: reboot + job: "/root/rmconn.sh" + state: present + +- name: Create NetworkManager connection community.general.nmcli: dns4: "{{ dns_forward }}" dns4_ignore_auto: yes @@ -25,11 +48,21 @@ conn_name: wired ifname: "{{ interface }}" type: ethernet - when: networkmanager_config.changed + register: nmcli_conf + poll: 0 + async: 1000 -- name: Cronjob to remove externally managed eth0 connection +- name: Set ansible_host to static IP + set_fact: + ansible_host: "{{ local_ip }}" + +- name: Reconnect to new IP + wait_for_connection: + timeout: 240 + + when: nmcli_conf.changed + +- name: Remove rmconn task at every minute cron: - name: "Ensure eth0 is not externally managed" - minute: "*/10" - job: "/usr/bin/nmcli connection down eth0 > /dev/null 2>&1; /usr/bin/nmcli connection down wired > /dev/null 2>&1; /usr/bin/nmcli connection up wired > /dev/null" - state: present + name: "Ensure eth0 is not externally managed (temporary)" + state: absent diff --git a/roles/networking/connection/templates/rmconn.sh b/roles/networking/connection/templates/rmconn.sh new file mode 100644 index 0000000..9408e8b --- /dev/null +++ b/roles/networking/connection/templates/rmconn.sh @@ -0,0 +1,8 @@ +#!/bin/sh + +sleep 30 + +/usr/bin/nmcli connection down {{ interface }} > /dev/null 2>&1 +/usr/bin/nmcli connection down "Wired connection 1" > /dev/null 2>&1 +/usr/bin/nmcli connection down wired > /dev/null 2>&1 +/usr/bin/nmcli connection up wired > /dev/null diff --git a/roles/networking/ssl/tasks/main.yml b/roles/networking/ssl/tasks/main.yml index 614af4b..1dd4193 100644 --- a/roles/networking/ssl/tasks/main.yml +++ b/roles/networking/ssl/tasks/main.yml @@ -1,6 +1,10 @@ -- name: Ensure nginx is installed +- name: Ensure relevant packages are installed community.general.pacman: - name: nginx + name: + - nginx + - certbot + - certbot-nginx + state: present - name: Create directories for ACME @@ -37,7 +41,7 @@ register: result when: cert_file.stat.exists -- name: Determine whethe certificate should be regenerated +- name: Determine whether the certificate should be regenerated set_fact: cert_regen: yes when: not cert_file.stat.exists or result.expired | bool diff --git a/roles/services/gitea/meta/main.yml b/roles/services/gitea/meta/main.yml deleted file mode 100644 index 96c394a..0000000 --- a/roles/services/gitea/meta/main.yml +++ /dev/null @@ -1,2 +0,0 @@ -dependencies: - - role: webserver diff --git a/roles/services/gitea/tasks/main.yml b/roles/services/gitea/tasks/main.yml index a7646d3..8745697 100644 --- a/roles/services/gitea/tasks/main.yml +++ b/roles/services/gitea/tasks/main.yml @@ -18,6 +18,20 @@ state: present notify: Restart gitea +- name: Find owner of data folder + stat: + path: "{{ dataroot }}/gitea/" + register: data_folder + +- name: Ensure data folder is under correct owner + file: + path: "{{ dataroot }}/gitea/" + state: directory + recurse: yes + owner: gitea + group: gitea + when: data_folder.stat.pw_name != "gitea" + - name: Ensure gitea is stopped service: name: gitea diff --git a/roles/services/synapse/handlers/main.yml b/roles/services/synapse/handlers/main.yml new file mode 100644 index 0000000..02632db --- /dev/null +++ b/roles/services/synapse/handlers/main.yml @@ -0,0 +1,4 @@ +- name: Restart synapse + service: + name: synapse + state: restarted diff --git a/roles/services/synapse/meta/main.yml b/roles/services/synapse/meta/main.yml deleted file mode 100644 index 96c394a..0000000 --- a/roles/services/synapse/meta/main.yml +++ /dev/null @@ -1,2 +0,0 @@ -dependencies: - - role: webserver diff --git a/roles/services/synapse/tasks/main.yml b/roles/services/synapse/tasks/main.yml index 511d310..7a9d282 100644 --- a/roles/services/synapse/tasks/main.yml +++ b/roles/services/synapse/tasks/main.yml @@ -7,16 +7,19 @@ copy: src: signing.key.secret dest: /etc/synapse/signing.key + notify: Restart synapse - name: Deploy matrix homeserver configuration template: src: homeserver.yaml.j2 dest: /etc/synapse/homeserver.yaml + notify: Restart synapse - name: Deploy matrix logging configuration template: src: log.config.j2 dest: /etc/synapse/log.config + notify: Restart synapse - name: Change systemd unit file to allow access to dataroot lineinfile: @@ -25,6 +28,22 @@ regexp: "^ReadWritePaths.*" line: "ReadWritePaths={{ dataroot }}/synapse/" state: present + notify: Restart synapse + +- name: Find owner of data folder + stat: + path: "{{ dataroot }}/synapse/" + register: data_folder + +- name: Ensure data folder is under correct owner + file: + path: "{{ dataroot }}/synapse/" + state: directory + recurse: yes + owner: synapse + group: synapse + when: data_folder.stat.pw_name != "synapse" + notify: Restart synapse - name: Enable matrix service service: diff --git a/roles/services/website/meta/main.yml b/roles/services/website/meta/main.yml deleted file mode 100644 index 96c394a..0000000 --- a/roles/services/website/meta/main.yml +++ /dev/null @@ -1,2 +0,0 @@ -dependencies: - - role: webserver diff --git a/roles/services/wiki/meta/main.yml b/roles/services/wiki/meta/main.yml deleted file mode 100644 index 96c394a..0000000 --- a/roles/services/wiki/meta/main.yml +++ /dev/null @@ -1,2 +0,0 @@ -dependencies: - - role: webserver diff --git a/roles/system/handlers/main.yml b/roles/system/handlers/main.yml deleted file mode 100644 index 784cf59..0000000 --- a/roles/system/handlers/main.yml +++ /dev/null @@ -1,4 +0,0 @@ -- name: Restart sshd - service: - name: sshd - state: restarted diff --git a/roles/system/tasks/essential.yml b/roles/system/tasks/essential.yml index 764fd63..7abe458 100644 --- a/roles/system/tasks/essential.yml +++ b/roles/system/tasks/essential.yml @@ -12,6 +12,16 @@ update_cache: yes upgrade: yes +- name: Determine if reboot for kernel update is needed + shell: + cmd: "if [[ $(pacman -Q linux | cut -d \" \" -f 2) > $(uname -r) ]]; then echo reboot; fi" + register: reboot_check + +- name: Reboot for kernel update + reboot: + when: + reboot_check.stdout == "reboot" + - name: Install utility packages community.general.pacman: name: "{{ util_pack }}" @@ -30,7 +40,7 @@ ansible.posix.authorized_key: user: "{{ username }}" state: present - key: "{{ lookup('file', '~/.ssh/keys/{{ ansible_hostname }}.pub')}}" + key: "{{ lookup('file', '~/.ssh/keys/{{ inventory_hostname }}.pub')}}" - name: Enable cron daemon service: diff --git a/roles/system/tasks/initial.yml b/roles/system/tasks/initial.yml new file mode 100644 index 0000000..053dd3d --- /dev/null +++ b/roles/system/tasks/initial.yml @@ -0,0 +1,56 @@ +- name: Determine whether initial setup is needed + set_fact: + initial_setup: yes + when: + ansible_user != admin_username + +- name: Fallback to su + set_fact: + ansible_become_method: "su" + ansible_become_user: "root" + ansible_become_password: "root" + when: + initial_setup is defined + +- setup: + +- name: Install opendoas + community.general.pacman: + name: opendoas + state: present + +- name: Configure doas + template: + src: doas.conf.j2 + dest: /etc/doas.conf + +- name: Create priviledged user + user: + name: "{{ admin_username }}" + groups: wheel + +- name: Deploy SSH key to admin user + ansible.posix.authorized_key: + user: "{{ admin_username }}" + state: present + key: "{{ lookup('file', '~/.ssh/keys/{{ inventory_hostname }}.pub')}}" + +- name: Reset variables to before fallback + set_fact: + ansible_become_method: "{{ escalation_method }}" + ansible_user: "{{ admin_username }}" + ansible_ssh_password: "" + when: + initial_setup is defined + +- name: Reconnect as new administrator + wait_for_connection: + timeout: 10 + when: + initial_setup is defined + +- name: Delete initial user + user: + name: "alarm" + force: yes + state: absent diff --git a/roles/system/tasks/main.yml b/roles/system/tasks/main.yml index f00961a..37f3c43 100644 --- a/roles/system/tasks/main.yml +++ b/roles/system/tasks/main.yml @@ -1,2 +1,3 @@ +- include_tasks: initial.yml - include_tasks: essential.yml - include_tasks: sshd.yml diff --git a/roles/system/tasks/sshd.yml b/roles/system/tasks/sshd.yml index ae7824b..16a2e6c 100644 --- a/roles/system/tasks/sshd.yml +++ b/roles/system/tasks/sshd.yml @@ -3,11 +3,27 @@ dest: /etc/ssh/sshd_config regexp: "^#PasswordAuthentication yes" line: "PasswordAuthentication no" - notify: Restart sshd + register: sshd_config - name: Change SSH port lineinfile: dest: /etc/ssh/sshd_config regexp: "^#Port 22" line: "Port {{ sshd_port }}" - notify: Restart sshd + register: sshd_config + +- name: Restart SSHD + service: + name: sshd + state: restarted + when: sshd_config.changed + +- name: Reset ansible_ssh_port + set_fact: + ansible_ssh_port: "{{ sshd_port }}" + when: sshd_config.changed + +- name: Reconnect under new port + wait_for_connection: + timeout: 10 + when: sshd_config.changed diff --git a/roles/system/templates/doas.conf.j2 b/roles/system/templates/doas.conf.j2 new file mode 100644 index 0000000..d2423e7 --- /dev/null +++ b/roles/system/templates/doas.conf.j2 @@ -0,0 +1 @@ +permit nopass :wheel diff --git a/run.yml b/run.yml index f74330c..fe6d595 100644 --- a/run.yml +++ b/run.yml @@ -2,27 +2,42 @@ - hosts: sv become: yes + gather_facts: no + + pre_tasks: + - import_tasks: tasks/set_host.yml + - import_tasks: tasks/ssh_port.yml + roles: - role: system - - - role: dotfiles - when: enable_dotfiles - + - role: filesystems when: enable_filesystems - role: networking/connection when: enable_connection + - role: firewall + when: enable_firewall + - role: networking/ddclient when: enable_ddclient - role: networking/nameserver when: enable_nameserver + - role: services/sftp + when: enable_sftpr + + - role: services/mail + when: enable_mail + - role: networking/ssl when: enable_ssl + - role: services/webserver + when: enable_webserver + - role: services/gitea when: enable_gitea @@ -35,8 +50,5 @@ - role: services/website when: enable_website - - role: services/sftp - when: enable_sftpr - - - role: services/mail - when: enable_mail + - role: dotfiles + when: enable_dotfiles diff --git a/tasks/set_host.yml b/tasks/set_host.yml new file mode 100644 index 0000000..69991ee --- /dev/null +++ b/tasks/set_host.yml @@ -0,0 +1,15 @@ +- name: Determine if host is up at static IP + local_action: "command ping -c 1 {{ local_ip }}" + become: no + ignore_errors: true + register: up_static + +- name: Switch ansible_host to fallback IP + set_fact: + ansible_host: "{{ fallback_host }}" + when: up_static.rc != 0 + +- name: Switch ansible_host to static IP + set_fact: + ansible_host: "{{ local_ip }}" + when: up_static.rc == 0 diff --git a/tasks/ssh_port.yml b/tasks/ssh_port.yml new file mode 100644 index 0000000..c3abe39 --- /dev/null +++ b/tasks/ssh_port.yml @@ -0,0 +1,42 @@ +- name: Set default user for SSH + set_fact: + ansible_user: "{{ admin_username }}" + +- name: Attempt ssh connection + become: no + wait_for_connection: + timeout: 10 + ignore_errors: true + register: ssh_result_default + +- name: Set ssh to fallback port + set_fact: + ansible_ssh_port: 22 + when: + - ssh_result_default is failed + +- name: Attempt ssh connection + become: no + wait_for_connection: + timeout: 10 + ignore_errors: true + register: ssh_result_port + when: + - ssh_result_default is failed + +- name: Set ssh to fallback port and default Arch Linux ARM credentials + set_fact: + ansible_ssh_port: "22" + ansible_user: "alarm" + ansible_ssh_password: "alarm" + when: + - ssh_result_default is failed + - ssh_result_port is failed + +- name: Attempt ssh connection + become: no + wait_for_connection: + timeout: 10 + when: + - ssh_result_default is failed + - ssh_result_port is failed