15 Commits

Author SHA1 Message Date
d954c64e23 Add Podman deployment configuration 2025-08-07 00:24:58 -04:00
ccf6b10a0e Add GPG key and reorganize dockerbox configuration
- Add new primary GPG key in dev config for compose repos
- Slight reorganization of the dockerbox production playbook
- Remove group management in the docker role
- Move HSTS inside the location block
- Add git ignore entry for .ansible files
- Add X-Forwarded-Proto proxy header
2025-03-26 22:07:06 -04:00
bd8eca0466 Remove redundant group management for Docker users
- Minor formatting updated
2025-01-19 17:48:45 -05:00
56c3721a5e Add flexible home and group controls for users 2025-01-19 16:28:54 -05:00
77c9b12186 Add multi-domain support for Jellyfin
- Allow Jellyfin to operate on multiple domains via Host rule config
- Move Cloudflare API tokens from Ansible inventory to manual file
- Minor formatting
2024-12-29 02:22:46 -05:00
3102c621f0 Add optional IP restriction for nginx site configs 2024-10-19 21:08:15 -04:00
e3f03edf3f Use file-based preshared keys for WireGuard
- Include proxy role in standard Docker playbook
2024-10-13 22:27:27 -04:00
f481a965dd Update Samba and WireGuard configuration
- Adjust Samba config file permissions to 644
- Introduce PresharedKey option in WireGuard config template
2024-09-10 22:35:20 -04:00
a0aa289c05 Restrict GitHub Actions to a dedicated branch
- The Vagrant testing setup on macos-latest is broken
- Temporary measure until fixed or abandoned
2024-09-10 22:11:31 -04:00
324fe0b191 Upgrade Nextcloud setup to use compose files
- Integrated MariaDB role into Dockerbox configuration
- Moved proxy role to the end to avoid early endpoint activation
- Temporarily disabled select roles for future re-evaluation
- Introduced flush_handlers task for early MariaDB restart
- Moved a few Nextcloud tasks to handlers
- Configured Nextcloud to utilize the host's MariaDB instance
- Enhanced overall code linting quality
2024-04-21 22:27:48 -04:00
6fbd3c53bb Add Vagrant cache option for dhparams.pem 2024-03-26 21:51:39 -04:00
01e8e22c01 Prevent running 'vagrant ssh' as root
Resolve possible issues with 'vagrant ssh' when executed as root
2024-03-04 23:42:40 -05:00
a31bf233dc Slight message tweaks in forward-ssh.sh script 2023-12-09 13:16:46 -05:00
60fafed9cd Update forward-ssh.sh script for Swarm support
- Address limitations in Swarm with loopback binding
- Ensure compatibility with localhost DNS wildcard A record
- Enable port forwarding on 80 and 443 using VM IP for Swarm compatibility
- Retain 8443:localhost:8443 for non-Swarm setups
2023-12-09 13:04:07 -05:00
2c00858590 Update README.md 2023-11-18 17:37:27 -05:00
26 changed files with 374 additions and 219 deletions

View File

@@ -3,8 +3,9 @@ name: homelab-ci
on: on:
push: push:
branches: branches:
- main - github_actions
- testing # - main
# - testing
jobs: jobs:
homelab-ci: homelab-ci:

3
.gitignore vendored
View File

@@ -1,4 +1,5 @@
.ansible*
/environments/
.playbook .playbook
.vagrant* .vagrant*
.vscode .vscode
/environments/

View File

@@ -1,41 +1,76 @@
# Project Moxie # Homelab
Project Moxie is a personal IT homelab project written in Ansible and executed by Jenkins. It is a growing collection of infrastructure as code (IaC) I write out of curiosity and for reference purposes, keeping a handful of beneficial projects managed and secured. This project is my personal IT homelab initiative for self-hosting and
exploring Free and Open Source Software (FOSS) infrastructure. As a technology
enthusiast and professional, this project is primarily a practical tool for
hosting services. It serves as a playground for engaging with systems
technology in functional, intriguing, and gratifying ways. Self-hosting
empowers individuals to govern their digital space, ensuring that their online
environments reflect personal ethics rather than centralized entities' opaque
policies.
Built on Debian Stable, this project utilizes Ansible and Vagrant, providing
relatively easy-to-use reproducible ephemeral environments to test
infrastructure automation before pushing to live systems.
## Quick Start ## Quick Start
To configure a local virtual machine for testing, follow these simple steps. To configure a local virtual machine for testing, follow these simple steps.
### Prerequisites
Vagrant and VirtualBox are used to develop Project Moxie. You will need to install these before continuing.
### Installation ### Installation
1. Clone this repository 1. Clone this repository
``` ```
git clone https://github.com/krislamo/moxie git clone https://git.krislamo.org/kris/homelab
```
Optionally clone from the GitHub mirror instead:
```
git clone https://github.com/krislamo/homelab
``` ```
2. Set the `PLAYBOOK` environmental variable to a development playbook name in the `dev/` directory 2. Set the `PLAYBOOK` environmental variable to a development playbook name in the `dev/` directory
The following `PLAYBOOK` names are available: `dockerbox`, `hypervisor`, `minecraft`, `bitwarden`, `nextcloud`, `nginx` To list available options in the `dev/` directory and choose a suitable PLAYBOOK, run:
```
ls dev/*.yml | xargs -n 1 basename -s .yml
```
Export the `PLAYBOOK` variable
``` ```
export PLAYBOOK=dockerbox export PLAYBOOK=dockerbox
``` ```
3. Bring the Vagrant box up 3. Clean up any previous provision and build the VM
``` ```
vagrant up make clean && make
``` ```
#### Copyright and License ## Vagrant Settings
Copyright (C) 2020-2021 Kris Lamoureux The Vagrantfile configures the environment based on settings from `.vagrant.yml`,
with default values including:
- PLAYBOOK: `default`
- Runs a `default` playbook that does nothing.
- You can set this by an environmental variable with the same name.
- VAGRANT_BOX: `debian/bookworm64`
- Current Debian Stable codename
- VAGRANT_CPUS: `2`
- Threads or cores per node, depending on CPU architecture
- VAGRANT_MEM: `2048`
- Specifies the amount of memory (in MB) allocated
- SSH_FORWARD: `false`
- Enable this if you need to forward SSH agents to the Vagrant machine
## Copyright and License
Copyright (C) 2019-2023 Kris Lamoureux
[![License: GPL v3](https://img.shields.io/badge/License-GPLv3-blue.svg)](https://www.gnu.org/licenses/gpl-3.0) [![License: GPL v3](https://img.shields.io/badge/License-GPLv3-blue.svg)](https://www.gnu.org/licenses/gpl-3.0)
This program is free software: you can redistribute it and/or modify it under
the terms of the GNU General Public License as published by the Free Software
Foundation, version 3 of the License.
This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 3 of the License. This program is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
PARTICULAR PURPOSE. See the GNU General Public License for more details.
This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with
this program. If not, see <https://www.gnu.org/licenses/>.
You should have received a copy of the GNU General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.

View File

@@ -6,8 +6,7 @@
roles: roles:
- base - base
- docker - docker
- mariadb
- traefik - traefik
- nextcloud - nextcloud
- jenkins - proxy
- prometheus
- nginx

View File

@@ -2,44 +2,51 @@
allow_reboot: false allow_reboot: false
manage_network: false manage_network: false
# Import my GPG key for git signature verification
root_gpgkeys:
- name: kris@lamoureux.io
id: 42A3A92C5DA0F3E5F71A3710105B748C1362EB96
# Older key, but still in use
- name: kris@lamoureux.io
id: FBF673CEEC030F8AECA814E73EDA9C3441EDA925
server: keyserver.ubuntu.com
# proxy
proxy:
servers:
- domain: cloud.local.krislamo.org
proxy_pass: http://127.0.0.1:8000
# docker # docker
docker_official: true # docker's apt repos
docker_users: docker_users:
- vagrant - vagrant
docker_compose_env_nolog: false # dev only setting
docker_compose_deploy:
# Traefik
- name: traefik
url: https://github.com/krislamo/traefik
version: d62bd06b37ecf0993962b0449a9d708373f9e381
enabled: true
accept_newhostkey: true # Consider verifying manually instead
trusted_keys:
- FBF673CEEC030F8AECA814E73EDA9C3441EDA925
env:
DASHBOARD: true
# Nextcloud
- name: nextcloud
url: https://github.com/krislamo/nextcloud
version: fe6d349749f178e91ae7ff726d557f48ebf84356
env:
DATA: ./data
# traefik # traefik
traefik_version: latest traefik:
traefik_dashboard: true ENABLE: true
traefik_domain: traefik.local.krislamo.org
traefik_auth: admin:$apr1$T1l.BCFz$Jyg8msXYEAUi3LLH39I9d1 # admin:admin
traefik_web_entry: 0.0.0.0:80
traefik_websecure_entry: 0.0.0.0:443
#traefik_acme_email: realemail@example.com # Let's Encrypt settings
#traefik_production: true
#traefik_http_only: true # if behind reverse-proxy
# nextcloud # nextcloud
nextcloud_version: stable nextcloud:
nextcloud_admin: admin DOMAIN: cloud.local.krislamo.org
nextcloud_pass: password DB_PASSWD: password
nextcloud_domain: cloud.local.krislamo.org ADMIN_PASSWD: password
nextcloud_dbversion: latest
nextcloud_dbpass: password
# jenkins
jenkins_version: lts
jenkins_domain: jenkins.local.krislamo.org
# prometheus (includes grafana)
prom_version: latest
prom_domain: prom.local.krislamo.org
grafana_version: latest
grafana_domain: grafana.local.krislamo.org
prom_targets: "['10.0.2.15:9100']"
# nginx
nginx_domain: nginx.local.krislamo.org
nginx_name: staticsite
nginx_repo_url: https://git.krislamo.org/kris/example-website/
nginx_auth: admin:$apr1$T1l.BCFz$Jyg8msXYEAUi3LLH39I9d1 # admin:admin
nginx_version: latest

14
dev/host_vars/podman.yml Normal file
View File

@@ -0,0 +1,14 @@
# base
allow_reboot: false
manage_network: false
users:
kris:
uid: 1001
gid: 1001
home: true
# podman
user_namespaces:
- kris

8
dev/podman.yml Normal file
View File

@@ -0,0 +1,8 @@
- name: Install Podman server
hosts: all
become: true
vars_files:
- host_vars/podman.yml
roles:
- base
- podman

View File

@@ -22,7 +22,7 @@
# Root check # Root check
if [ "$EUID" -ne 0 ]; then if [ "$EUID" -ne 0 ]; then
echo "[ERROR]: Please run script as root" echo "[ERROR]: Please run this script as root"
exit 1 exit 1
fi fi
@@ -41,8 +41,8 @@ function ssh_connect {
printf "[INFO]: Starting new vagrant SSH tunnel on PID " printf "[INFO]: Starting new vagrant SSH tunnel on PID "
sudo -u "$USER" ssh -fNT -i "$PRIVATE_KEY" \ sudo -u "$USER" ssh -fNT -i "$PRIVATE_KEY" \
-L 22:localhost:22 \ -L 22:localhost:22 \
-L 80:localhost:80 \ -L 80:"$HOST_IP":80 \
-L 443:localhost:443 \ -L 443:"$HOST_IP":443 \
-L 8443:localhost:8443 \ -L 8443:localhost:8443 \
-o UserKnownHostsFile=/dev/null \ -o UserKnownHostsFile=/dev/null \
-o StrictHostKeyChecking=no \ -o StrictHostKeyChecking=no \
@@ -51,7 +51,7 @@ function ssh_connect {
pgrep -f "$MATCH_PATTERN" pgrep -f "$MATCH_PATTERN"
;; ;;
*) *)
echo "[INFO]: Delined to start a new vagrant SSH tunnel" echo "[INFO]: Declined to start a new vagrant SSH tunnel"
exit 0 exit 0
;; ;;
esac esac
@@ -64,7 +64,7 @@ PRIVATE_KEY="$(find .vagrant -name "private_key" 2>/dev/null | sort)"
if [ "$(echo "$PRIVATE_KEY" | wc -l)" -gt 1 ]; then if [ "$(echo "$PRIVATE_KEY" | wc -l)" -gt 1 ]; then
while IFS= read -r KEYFILE; do while IFS= read -r KEYFILE; do
if ! ssh-keygen -l -f "$KEYFILE" &>/dev/null; then if ! ssh-keygen -l -f "$KEYFILE" &>/dev/null; then
echo "[ERROR]: The SSH key '$KEYFILE' is not valid. Is your virtual machines running?" echo "[ERROR]: The SSH key '$KEYFILE' is not valid. Are your virtual machines running?"
exit 1 exit 1
fi fi
echo "[CHECK]: Valid key at $KEYFILE" echo "[CHECK]: Valid key at $KEYFILE"
@@ -78,7 +78,7 @@ else
fi fi
# Grab first IP or use whatever HOST_IP_FIELD is set to and check that the guest is up # Grab first IP or use whatever HOST_IP_FIELD is set to and check that the guest is up
HOST_IP="$(vagrant ssh -c "hostname -I | cut -d' ' -f${HOST_IP_FIELD:-1}" "${1:-default}" 2>/dev/null)" HOST_IP="$(sudo -u "$SUDO_USER" vagrant ssh -c "hostname -I | cut -d' ' -f${HOST_IP_FIELD:-1}" "${1:-default}" 2>/dev/null)"
if [ -z "$HOST_IP" ]; then if [ -z "$HOST_IP" ]; then
echo "[ERROR]: Failed to find ${1:-default}'s IP" echo "[ERROR]: Failed to find ${1:-default}'s IP"
exit 1 exit 1

View File

@@ -4,4 +4,5 @@
roles: roles:
- base - base
- jenkins - jenkins
- proxy
- docker - docker

View File

@@ -3,9 +3,9 @@
become: true become: true
roles: roles:
- base - base
- jenkins
- docker - docker
- mariadb
- traefik - traefik
- nextcloud - nextcloud
- jenkins - proxy
- prometheus
- nginx

View File

@@ -26,7 +26,7 @@
ansible.builtin.template: ansible.builtin.template:
src: smb.conf.j2 src: smb.conf.j2
dest: /etc/samba/smb.conf dest: /etc/samba/smb.conf
mode: "700" mode: "644"
notify: restart_samba notify: restart_samba
- name: Start smbd and enable on boot - name: Start smbd and enable on boot

View File

@@ -80,8 +80,10 @@
state: present state: present
uid: "{{ item.value.uid }}" uid: "{{ item.value.uid }}"
group: "{{ item.value.gid }}" group: "{{ item.value.gid }}"
groups: "{{ item.value.groups | default([]) }}"
shell: "{{ item.value.shell | default('/bin/bash') }}" shell: "{{ item.value.shell | default('/bin/bash') }}"
create_home: "{{ item.value.home | default(false) }}" create_home: "{{ item.value.home | default(false) }}"
home: "{{ item.value.homedir | default('/home/' + item.key) }}"
system: "{{ item.value.system | default(false) }}" system: "{{ item.value.system | default(false) }}"
loop: "{{ users | dict2items }}" loop: "{{ users | dict2items }}"
loop_control: loop_control:

View File

@@ -18,6 +18,28 @@
src: /etc/wireguard/privatekey src: /etc/wireguard/privatekey
register: wgkey register: wgkey
- name: Check if WireGuard preshared key file exists
ansible.builtin.stat:
path: /etc/wireguard/presharedkey-{{ item.name }}
loop: "{{ wireguard.peers }}"
loop_control:
label: "{{ item.name }}"
register: presharedkey_files
- name: Grab WireGuard preshared key for configuration
ansible.builtin.slurp:
src: /etc/wireguard/presharedkey-{{ item.item.name }}
register: wgshared
loop: "{{ presharedkey_files.results }}"
loop_control:
label: "{{ item.item.name }}"
when: item.stat.exists
- name: Grab WireGuard private key for configuration
ansible.builtin.slurp:
src: /etc/wireguard/privatekey
register: wgkey
- name: Install WireGuard configuration - name: Install WireGuard configuration
ansible.builtin.template: ansible.builtin.template:
src: wireguard.j2 src: wireguard.j2

View File

@@ -1,4 +1,6 @@
[Interface] # {{ ansible_managed }}
[Interface] # {{ ansible_hostname }}
PrivateKey = {{ wgkey['content'] | b64decode | trim }} PrivateKey = {{ wgkey['content'] | b64decode | trim }}
Address = {{ wireguard.address }} Address = {{ wireguard.address }}
{% if wireguard.listenport is defined %} {% if wireguard.listenport is defined %}
@@ -6,8 +8,26 @@ ListenPort = {{ wireguard.listenport }}
{% endif %} {% endif %}
{% for peer in wireguard.peers %} {% for peer in wireguard.peers %}
{% if peer.name is defined %}
[Peer] # {{ peer.name }}
{% else %}
[Peer] [Peer]
{% endif %}
PublicKey = {{ peer.publickey }} PublicKey = {{ peer.publickey }}
{% if peer.presharedkey is defined %}
PresharedKey = {{ peer.presharedkey }}
{% else %}
{% set preshared_key = (
wgshared.results
| selectattr('item.item.name', 'equalto', peer.name)
| first
).content
| default(none)
%}
{% if preshared_key is not none %}
PresharedKey = {{ preshared_key | b64decode | trim }}
{% endif %}
{% endif %}
{% if peer.endpoint is defined %} {% if peer.endpoint is defined %}
Endpoint = {{ peer.endpoint }} Endpoint = {{ peer.endpoint }}
{% endif %} {% endif %}

View File

@@ -24,15 +24,21 @@
- name: Install/uninstall Docker from Debian repositories - name: Install/uninstall Docker from Debian repositories
ansible.builtin.apt: ansible.builtin.apt:
name: ['docker.io', 'docker-compose', 'containerd', 'runc'] name: ["docker.io", "docker-compose", "containerd", "runc"]
state: "{{ 'absent' if docker_official else 'present' }}" state: "{{ 'absent' if docker_official else 'present' }}"
autoremove: true autoremove: true
update_cache: true update_cache: true
- name: Install/uninstall Docker from Docker repositories - name: Install/uninstall Docker from Docker repositories
ansible.builtin.apt: ansible.builtin.apt:
name: ['docker-ce', 'docker-ce-cli', 'containerd.io', name:
'docker-buildx-plugin', 'docker-compose-plugin'] [
"docker-ce",
"docker-ce-cli",
"containerd.io",
"docker-buildx-plugin",
"docker-compose-plugin",
]
state: "{{ 'present' if docker_official else 'absent' }}" state: "{{ 'present' if docker_official else 'absent' }}"
autoremove: true autoremove: true
update_cache: true update_cache: true
@@ -135,14 +141,6 @@
label: "{{ item.name }}" label: "{{ item.name }}"
when: docker_compose_deploy is defined and item.env is defined when: docker_compose_deploy is defined and item.env is defined
- name: Add users to docker group
ansible.builtin.user:
name: "{{ item }}"
groups: docker
append: true
loop: "{{ docker_users }}"
when: docker_users is defined
- name: Start Docker and enable on boot - name: Start Docker and enable on boot
ansible.builtin.service: ansible.builtin.service:
name: docker name: docker

View File

@@ -15,7 +15,7 @@ services:
networks: networks:
- traefik - traefik
labels: labels:
- "traefik.http.routers.{{ jellyfin_router }}.rule=Host(`{{ jellyfin_domain }}`)" - "traefik.http.routers.{{ jellyfin_router }}.rule=Host({{ jellyfin_domains }})"
{% if traefik_http_only %} {% if traefik_http_only %}
- "traefik.http.routers.{{ jellyfin_router }}.entrypoints=web" - "traefik.http.routers.{{ jellyfin_router }}.entrypoints=web"
{% else %} {% else %}

View File

@@ -16,6 +16,12 @@
regex: "^bind-address" regex: "^bind-address"
line: "bind-address = {{ ansible_facts.docker0.ipv4.address }}" line: "bind-address = {{ ansible_facts.docker0.ipv4.address }}"
notify: restart_mariadb notify: restart_mariadb
when: ansible_facts.docker0 is defined
- name: Flush handlers to ensure MariaDB restarts immediately
ansible.builtin.meta: flush_handlers
tags: restart_mariadb
when: ansible_facts.docker0 is defined
- name: Allow database connections from Docker - name: Allow database connections from Docker
community.general.ufw: community.general.ufw:

View File

@@ -1,11 +1 @@
# container names nextcloud_name: nextcloud
nextcloud_container: nextcloud
nextcloud_dbcontainer: "{{ nextcloud_container }}-db"
# database settings
nextcloud_dbname: "{{ nextcloud_container }}"
nextcloud_dbuser: "{{ nextcloud_dbname }}"
# host mounts
nextcloud_root: "/opt/{{ nextcloud_container }}/public_html"
nextcloud_dbroot: "/opt/{{ nextcloud_container }}/database"

View File

@@ -0,0 +1,25 @@
- name: Set Nextcloud's Trusted Proxy
ansible.builtin.command: >
docker exec --user www-data "{{ nextcloud_name }}"
php occ config:system:set trusted_proxies 0 --value="{{ traefik_name }}"
register: nextcloud_trusted_proxy
changed_when: "nextcloud_trusted_proxy.stdout == 'System config value trusted_proxies => 0 set to string ' ~ traefik_name"
listen: install_nextcloud
- name: Set Nextcloud's Trusted Domain
ansible.builtin.command: >
docker exec --user www-data "{{ nextcloud_name }}"
php occ config:system:set trusted_domains 0 --value="{{ nextcloud.DOMAIN }}"
register: nextcloud_trusted_domains
changed_when: "nextcloud_trusted_domains.stdout == 'System config value trusted_domains => 0 set to string ' ~ nextcloud.DOMAIN"
listen: install_nextcloud
- name: Preform Nextcloud database maintenance
ansible.builtin.command: >
docker exec --user www-data "{{ nextcloud_name }}" {{ item }}
loop:
- "php occ maintenance:mode --on"
- "php occ db:add-missing-indices"
- "php occ db:convert-filecache-bigint"
- "php occ maintenance:mode --off"
listen: install_nextcloud

View File

@@ -1,109 +1,62 @@
- name: Create Nextcloud network - name: Install MySQL module for Ansible
community.general.docker_network: ansible.builtin.apt:
name: "{{ nextcloud_container }}" name: python3-pymysql
state: present
- name: Start Nextcloud's database container - name: Create Nextcloud database
community.general.docker_container: community.mysql.mysql_db:
name: "{{ nextcloud_dbcontainer }}" name: "{{ nextcloud.DB_NAME | default('nextcloud') }}"
image: mariadb:{{ nextcloud_dbversion }} state: present
login_unix_socket: /var/run/mysqld/mysqld.sock
- name: Create Nextcloud database user
community.mysql.mysql_user:
name: "{{ nextcloud.DB_USER | default('nextcloud') }}"
password: "{{ nextcloud.DB_PASSWD }}"
host: '%'
state: present
priv: "{{ nextcloud.DB_NAME | default('nextcloud') }}.*:ALL"
login_unix_socket: /var/run/mysqld/mysqld.sock
- name: Start Nextcloud service and enable on boot
ansible.builtin.service:
name: "{{ docker_compose_service }}@{{ nextcloud_name }}"
state: started state: started
restart_policy: always enabled: true
volumes: "{{ nextcloud_dbroot }}:/var/lib/mysql" when: nextcloud.ENABLE | default('false')
networks_cli_compatible: true
networks:
- name: "{{ nextcloud_container }}"
env:
MYSQL_RANDOM_ROOT_PASSWORD: "true"
MYSQL_DATABASE: "{{ nextcloud_dbname }}"
MYSQL_USER: "{{ nextcloud_dbuser }}"
MYSQL_PASSWORD: "{{ nextcloud_dbpass }}"
- name: Start Nextcloud container
community.general.docker_container:
name: "{{ nextcloud_container }}"
image: nextcloud:{{ nextcloud_version }}
state: started
restart_policy: always
volumes: "{{ nextcloud_root }}:/var/www/html"
networks_cli_compatible: true
networks:
- name: "{{ nextcloud_container }}"
- name: traefik
env:
PHP_MEMORY_LIMIT: 1024M
labels:
traefik.http.routers.nextcloud.rule: "Host(`{{ nextcloud_domain }}`)"
traefik.http.routers.nextcloud.entrypoints: websecure
traefik.http.routers.nextcloud.tls.certresolver: letsencrypt
traefik.http.routers.nextcloud.middlewares: "securehttps@file,nextcloud-webdav"
traefik.http.middlewares.nextcloud-webdav.redirectregex.regex: "https://(.*)/.well-known/(card|cal)dav"
traefik.http.middlewares.nextcloud-webdav.redirectregex.replacement: "https://${1}/remote.php/dav/"
traefik.http.middlewares.nextcloud-webdav.redirectregex.permanent: "true"
traefik.docker.network: traefik
traefik.enable: "true"
- name: Grab Nextcloud database container information
community.general.docker_container_info:
name: "{{ nextcloud_dbcontainer }}"
register: nextcloud_dbinfo
- name: Grab Nextcloud container information - name: Grab Nextcloud container information
community.general.docker_container_info: community.general.docker_container_info:
name: "{{ nextcloud_container }}" name: "{{ nextcloud_name }}"
register: nextcloud_info register: nextcloud_info
- name: Wait for Nextcloud to become available - name: Wait for Nextcloud to become available
ansible.builtin.wait_for: ansible.builtin.wait_for:
host: "{{ nextcloud_info.container.NetworkSettings.Networks.traefik.IPAddress }}" host: "{{ nextcloud_info.container.NetworkSettings.Networks.traefik.IPAddress }}"
delay: 10
port: 80 port: 80
- name: Check Nextcloud status - name: Check Nextcloud status
ansible.builtin.command: "docker exec --user www-data {{ nextcloud_container }} ansible.builtin.command: >
php occ status" docker exec --user www-data "{{ nextcloud_name }}" php occ status
register: nextcloud_status register: nextcloud_status
args: changed_when: false
removes: "{{ nextcloud_root }}/config/CAN_INSTALL"
- name: Wait for Nextcloud database to become available
ansible.builtin.wait_for:
host: "{{ nextcloud_dbinfo.container.NetworkSettings.Networks.nextcloud.IPAddress }}"
port: 3306
- name: Install Nextcloud - name: Install Nextcloud
ansible.builtin.command: 'docker exec --user www-data {{ nextcloud_container }} ansible.builtin.command: >
php occ maintenance:install docker exec --user www-data {{ nextcloud_name }}
--database "mysql" php occ maintenance:install
--database-host "{{ nextcloud_dbcontainer }}" --database "mysql"
--database-name "{{ nextcloud_dbname }}" --database-host "{{ nextcloud.DB_HOST | default('host.docker.internal') }}"
--database-user "{{ nextcloud_dbuser }}" --database-name "{{ nextcloud.DB_NAME | default('nextcloud') }}"
--database-pass "{{ nextcloud_dbpass }}" --database-user "{{ nextcloud.DB_USER | default('nextcloud') }}"
--admin-user "{{ nextcloud_admin }}" --database-pass "{{ nextcloud.DB_PASSWD }}"
--admin-pass "{{ nextcloud_pass }}"' --admin-user "{{ nextcloud.ADMIN_USER | default('admin') }}"
--admin-pass "{{ nextcloud.ADMIN_PASSWD }}"
register: nextcloud_install register: nextcloud_install
when: when: nextcloud_status.stderr[:26] == "Nextcloud is not installed"
- nextcloud_status.stdout[:26] == "Nextcloud is not installed" changed_when: nextcloud_install.stdout == "Nextcloud was successfully installed"
- nextcloud_domain is defined notify: install_nextcloud
- name: Set Nextcloud's Trusted Proxy
ansible.builtin.command: 'docker exec --user www-data {{ nextcloud_container }}
php occ config:system:set trusted_proxies 0
--value="{{ traefik_name }}"'
when: nextcloud_install.changed
- name: Set Nextcloud's Trusted Domain
ansible.builtin.command: 'docker exec --user www-data {{ nextcloud_container }}
php occ config:system:set trusted_domains 0
--value="{{ nextcloud_domain }}"'
when: nextcloud_install.changed
- name: Preform Nextcloud database maintenance
ansible.builtin.command: "docker exec --user www-data {{ nextcloud_container }} {{ item }}"
loop:
- "php occ maintenance:mode --on"
- "php occ db:add-missing-indices"
- "php occ db:convert-filecache-bigint"
- "php occ maintenance:mode --off"
when: nextcloud_install.changed
- name: Install Nextcloud background jobs cron - name: Install Nextcloud background jobs cron
ansible.builtin.cron: ansible.builtin.cron:
@@ -111,8 +64,3 @@
minute: "*/5" minute: "*/5"
job: "/usr/bin/docker exec -u www-data nextcloud /usr/local/bin/php -f /var/www/html/cron.php" job: "/usr/bin/docker exec -u www-data nextcloud /usr/local/bin/php -f /var/www/html/cron.php"
user: root user: root
- name: Remove Nextcloud's CAN_INSTALL file
ansible.builtin.file:
path: "{{ nextcloud_root }}/config/CAN_INSTALL"
state: absent

View File

@@ -0,0 +1,62 @@
- name: Install Podman
ansible.builtin.apt:
name: ["podman", "podman-compose", "podman-docker"]
state: present
- name: Get user info for namespace users
ansible.builtin.getent:
database: passwd
key: "{{ item }}"
loop: "{{ user_namespaces }}"
register: user_info
- name: Configure /etc/subuid for rootless users
ansible.builtin.lineinfile:
path: "/etc/subuid"
line:
"{{ item.item }}:{{ 100000 +
((item.ansible_facts.getent_passwd[item.item][1] | int - 1000) * 65536)
}}:65536"
regexp: "^{{ item.item }}:"
create: true
backup: true
mode: "0644"
loop: "{{ user_info.results }}"
- name: Configure /etc/subgid for rootless users
ansible.builtin.lineinfile:
path: "/etc/subgid"
line:
"{{ item.item }}:{{ 100000 +
((item.ansible_facts.getent_passwd[item.item][1] | int - 1000) * 65536)
}}:65536"
regexp: "^{{ item.item }}:"
create: true
backup: true
mode: "0644"
loop: "{{ user_info.results }}"
- name: Create nodocker file to disable Docker CLI emulation message
ansible.builtin.file:
path: /etc/containers/nodocker
state: touch
owner: root
group: root
mode: "0644"
- name: Create global containers config directory
ansible.builtin.file:
path: /etc/containers
state: directory
mode: "0755"
- name: Configure global containers.conf for rootless
ansible.builtin.copy:
content: |
[engine]
cgroup_manager = "cgroupfs"
events_logger = "journald"
runtime = "crun"
dest: /etc/containers/containers.conf
mode: "0644"
backup: true

View File

@@ -0,0 +1 @@
cached_dhparams_pem: /vagrant/scratch/dhparams.pem

View File

@@ -10,6 +10,19 @@
state: started state: started
enabled: true enabled: true
- name: Check for cached dhparams.pem file
ansible.builtin.stat:
path: "{{ cached_dhparams_pem }}"
register: dhparams_file
- name: Copy cached dhparams.pem to /etc/ssl/
ansible.builtin.copy:
src: "{{ cached_dhparams_pem }}"
dest: /etc/ssl/dhparams.pem
mode: "600"
remote_src: true
when: dhparams_file.stat.exists
- name: Generate DH Parameters - name: Generate DH Parameters
community.crypto.openssl_dhparam: community.crypto.openssl_dhparam:
path: /etc/ssl/dhparams.pem path: /etc/ssl/dhparams.pem
@@ -32,10 +45,11 @@
register: nginx_sites register: nginx_sites
- name: Generate self-signed certificate - name: Generate self-signed certificate
ansible.builtin.command: 'openssl req -newkey rsa:4096 -x509 -sha256 -days 3650 -nodes \ ansible.builtin.command:
-subj "/C=US/ST=Local/L=Local/O=Org/OU=IT/CN=example.com" \ 'openssl req -newkey rsa:4096 -x509 -sha256 -days 3650 -nodes \
-keyout /etc/ssl/private/nginx-selfsigned.key \ -subj "/C=US/ST=Local/L=Local/O=Org/OU=IT/CN=example.com" \
-out /etc/ssl/certs/nginx-selfsigned.crt' -keyout /etc/ssl/private/nginx-selfsigned.key \
-out /etc/ssl/certs/nginx-selfsigned.crt'
args: args:
creates: /etc/ssl/certs/nginx-selfsigned.crt creates: /etc/ssl/certs/nginx-selfsigned.crt
when: proxy.production is not defined or not proxy.production when: proxy.production is not defined or not proxy.production
@@ -43,15 +57,22 @@
- name: Install LE's certbot - name: Install LE's certbot
ansible.builtin.apt: ansible.builtin.apt:
name: ['certbot', 'python3-certbot-dns-cloudflare'] name: ["certbot", "python3-certbot-dns-cloudflare"]
state: present state: present
when: proxy.production is defined and proxy.production when: proxy.production is defined and proxy.production
- name: Grab Cloudflare API token for configuration
ansible.builtin.slurp:
src: /root/.cloudflare-api
register: cfapi
when: proxy.production is defined and proxy.production and proxy.dns_cloudflare is defined
- name: Install Cloudflare API token - name: Install Cloudflare API token
ansible.builtin.template: ansible.builtin.template:
src: cloudflare.ini.j2 src: cloudflare.ini.j2
dest: /root/.cloudflare.ini dest: /root/.cloudflare.ini
mode: "400" mode: "400"
diff: false
when: proxy.production is defined and proxy.production and proxy.dns_cloudflare is defined when: proxy.production is defined and proxy.production and proxy.dns_cloudflare is defined
- name: Create nginx post renewal hook directory - name: Create nginx post renewal hook directory
@@ -65,19 +86,19 @@
ansible.builtin.copy: ansible.builtin.copy:
src: reload-nginx.sh src: reload-nginx.sh
dest: /etc/letsencrypt/renewal-hooks/post/reload-nginx.sh dest: /etc/letsencrypt/renewal-hooks/post/reload-nginx.sh
mode: '0755' mode: "0755"
when: proxy.production is defined and proxy.production when: proxy.production is defined and proxy.production
- name: Run Cloudflare DNS-01 challenges on wildcard domains - name: Run Cloudflare DNS-01 challenges on wildcard domains
ansible.builtin.shell: '/usr/bin/certbot certonly \ ansible.builtin.shell: '/usr/bin/certbot certonly \
--non-interactive \ --non-interactive \
--agree-tos \ --agree-tos \
--email "{{ proxy.dns_cloudflare.email }}" \ --email "{{ proxy.dns_cloudflare.email }}" \
--dns-cloudflare \ --dns-cloudflare \
--dns-cloudflare-credentials /root/.cloudflare.ini \ --dns-cloudflare-credentials /root/.cloudflare.ini \
-d "*.{{ item }}" \ -d "*.{{ item }}" \
-d "{{ item }}" \ -d "{{ item }}" \
{{ proxy.dns_cloudflare.opts | default("") }}' {{ proxy.dns_cloudflare.opts | default("") }}'
args: args:
creates: "/etc/letsencrypt/live/{{ item }}/fullchain.pem" creates: "/etc/letsencrypt/live/{{ item }}/fullchain.pem"
loop: "{{ proxy.dns_cloudflare.wildcard_domains }}" loop: "{{ proxy.dns_cloudflare.wildcard_domains }}"

View File

@@ -1,2 +1,2 @@
# Cloudflare API token used by Certbot # Cloudflare API token used by Certbot
dns_cloudflare_api_token = {{ proxy.dns_cloudflare.api_token }} dns_cloudflare_api_token = {{ cfapi['content'] | b64decode | trim }}

View File

@@ -28,14 +28,20 @@ server {
ssl_certificate /etc/ssl/certs/nginx-selfsigned.crt; ssl_certificate /etc/ssl/certs/nginx-selfsigned.crt;
ssl_certificate_key /etc/ssl/private/nginx-selfsigned.key; ssl_certificate_key /etc/ssl/private/nginx-selfsigned.key;
{% endif %} {% endif %}
{% if item.hsts is defined %}
add_header Strict-Transport-Security "max-age={{ item.hsts }}" always;
{% endif %}
{% if item.client_max_body_size is defined %} {% if item.client_max_body_size is defined %}
client_max_body_size {{ item.client_max_body_size }}; client_max_body_size {{ item.client_max_body_size }};
{% endif %} {% endif %}
location / { location / {
{% if item.restrict is defined and item.restrict %} {% if item.hsts is defined %}
add_header Strict-Transport-Security "max-age={{ item.hsts }}" always;
{% endif %}
{% if item.allowedips is defined %}
{% for ip in item.allowedips %}
allow {{ ip }};
{% endfor %}
deny all;
{% endif %}
{% if item.restrict is defined and item.restrict %}
auth_basic "{{ item.restrict_name | default('Restricted Access') }}"; auth_basic "{{ item.restrict_name | default('Restricted Access') }}";
auth_basic_user_file {{ item.restrict_file | default('/etc/nginx/.htpasswd') }}; auth_basic_user_file {{ item.restrict_file | default('/etc/nginx/.htpasswd') }};
proxy_set_header Authorization ""; proxy_set_header Authorization "";
@@ -43,6 +49,7 @@ server {
proxy_set_header Host $host; proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_pass {{ item.proxy_pass }}; proxy_pass {{ item.proxy_pass }};
{% if item.proxy_ssl_verify is defined and item.proxy_ssl_verify is false %} {% if item.proxy_ssl_verify is defined and item.proxy_ssl_verify is false %}
proxy_ssl_verify off; proxy_ssl_verify off;

View File

@@ -21,20 +21,6 @@
loop: "{{ traefik_external }}" loop: "{{ traefik_external }}"
when: traefik_external is defined when: traefik_external is defined
- name: Install Traefik's docker-compose file
ansible.builtin.template:
src: docker-compose.yml.j2
dest: "{{ traefik_root }}/docker-compose.yml"
mode: 0400
notify: restart_traefik
- name: Install Traefik's docker-compose variables
ansible.builtin.template:
src: compose-env.j2
dest: "{{ traefik_root }}/.env"
mode: 0400
notify: restart_traefik
- name: Install static Traefik configuration - name: Install static Traefik configuration
ansible.builtin.template: ansible.builtin.template:
src: traefik.yml.j2 src: traefik.yml.j2
@@ -42,8 +28,9 @@
mode: 0400 mode: 0400
notify: restart_traefik notify: restart_traefik
- name: Start and enable Traefik service - name: Start Traefik service and enable on boot
ansible.builtin.service: ansible.builtin.service:
name: "{{ docker_compose_service }}@{{ traefik_name }}" name: "{{ docker_compose_service }}@{{ traefik_name }}"
state: started state: started
enabled: true enabled: true
when: traefik.ENABLED | default('false')