diff --git a/.gitignore b/.gitignore index 5c199eb..2db929b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,2 @@ # ---> Ansible *.retry - diff --git a/ansible.cfg b/ansible.cfg new file mode 100644 index 0000000..ad5b82d --- /dev/null +++ b/ansible.cfg @@ -0,0 +1,3 @@ +[defaults] +remote_user = ubuntu +inventory = inventory diff --git a/group_vars/all/secrets.yml b/group_vars/all/secrets.yml new file mode 100644 index 0000000..af505c9 --- /dev/null +++ b/group_vars/all/secrets.yml @@ -0,0 +1,27 @@ +$ANSIBLE_VAULT;1.1;AES256 +65633564663165316234316164633133393062326534613461663237666537373861323162396136 +6665653130343264636466656566636132366439326330610a643735626235323335303937656333 +36326161666239323838373466373463383465396635393630663132356234353765653930643463 +3735383539303631300a633839373936663563363537656636633632323964393138333730303031 +61333535353132383562396136616138326265336235633665316164316234646533343232633938 +63343034363636343966363161376463383432643536633638663339323566396330306565356666 +33623936653635656265616364653831313866313931653662396463393430326662303164613838 +33306538366531656166306566353636366539636266346537353539643862663630663938663865 +62636531333566636438393831646466613465613537653162383030626564393464656132346661 +37383137633833366338383637333161303434363533363135376237636565373337396230663033 +38313838373131633035633761636134653263386430656535616339373336373538376364613563 +31323335343066346635393130623834313839303464313365633331616361393462373862306335 +31633266666630343637333936643633396463363336613332313736623466633733343164363631 +62303163616563393735633438633739333732656161653337343439313265656166613731356162 +66313433306338643533373265613637336232623732643734646233666266663666623565636631 +61336635326465333232616134666635363234396535386265373533363138363366303631616630 +61393837313139313531336631333734633039363034396165643733653132623136363137343232 +39633839303536613636313661363831343831303562383832313166316164346231626565323961 +31336334393965346466386564393961383734393663636139313964653163666235323538626362 +34373263383463376130323562386561376262666539663233346431623263376532643737633830 +62626136336632663030383136383364343332323732306539306663613161646535656531383561 +32353436386163626436356632386631653666343931663063373462613134613039386266636366 +36646538633166383830643466383936613565613031313936316539313434333839363764636438 +62616261613936663762373764656466623666373034303662306265636431333663376230393634 +64323032323439363265623938323237626538653534633364623730613836373336363862613334 +3566396264653531386637613639373638393633363639613566 diff --git a/inventory b/inventory new file mode 100644 index 0000000..e88be9a --- /dev/null +++ b/inventory @@ -0,0 +1,2 @@ +## Raspberry Pi 4B +pi.servers.sapti.me ansible_python_interface=/usr/bin/python3 diff --git a/playbook.yml b/playbook.yml new file mode 100644 index 0000000..e19bdb3 --- /dev/null +++ b/playbook.yml @@ -0,0 +1,10 @@ +--- +- name: Deploy homeserver + hosts: all + gather_facts: false + become: true + + tasks: + - name: Run Docker role + import_role: + name: docker diff --git a/roles/docker/defaults/main.yml b/roles/docker/defaults/main.yml new file mode 100644 index 0000000..6d5cdd0 --- /dev/null +++ b/roles/docker/defaults/main.yml @@ -0,0 +1,46 @@ +--- +base_domain: sapti.me +base_volume_folder: /opt/storage/apps + +services: + caddy: + file: caddy.yml + volume: "{{ base_volume_folder }}/caddy" + version: 2-alpine + + watchtower: + file: watchtower.yml + version: latest + + restic: + file: restic.yml + repo: /restic + version: 1.6 + + nextcloud: + file: nextcloud.yml + domain: "cloud.{{ base_domain }}" + volume: "{{ base_volume_folder }}/nextcloud" + version: 25-apache + mariadb_version: 10 + redis_version: 7-alpine + + emby: + file: emby.yml + domain: "watch.{{ base_domain }}" + volume: "{{ base_volume_folder }}/emby" + version: latest + + monerod: + file: monerod.yml + domain: "xmr.{{ base_domain }}" + version: latest + + wireguard: + file: wireguard.yml + domain: "wg01.vpn.{{ base_domain }}" + version: latest + + snowflake: + file: snowflake.yml + version: latest diff --git a/roles/docker/files/nextcloud/apache2/apache2.conf b/roles/docker/files/nextcloud/apache2/apache2.conf new file mode 100644 index 0000000..11639be --- /dev/null +++ b/roles/docker/files/nextcloud/apache2/apache2.conf @@ -0,0 +1,227 @@ +# This is the main Apache server configuration file. It contains the +# configuration directives that give the server its instructions. +# See http://httpd.apache.org/docs/2.4/ for detailed information about +# the directives and /usr/share/doc/apache2/README.Debian about Debian specific +# hints. +# +# +# Summary of how the Apache 2 configuration works in Debian: +# The Apache 2 web server configuration in Debian is quite different to +# upstream's suggested way to configure the web server. This is because Debian's +# default Apache2 installation attempts to make adding and removing modules, +# virtual hosts, and extra configuration directives as flexible as possible, in +# order to make automating the changes and administering the server as easy as +# possible. + +# It is split into several files forming the configuration hierarchy outlined +# below, all located in the /etc/apache2/ directory: +# +# /etc/apache2/ +# |-- apache2.conf +# | `-- ports.conf +# |-- mods-enabled +# | |-- *.load +# | `-- *.conf +# |-- conf-enabled +# | `-- *.conf +# `-- sites-enabled +# `-- *.conf +# +# +# * apache2.conf is the main configuration file (this file). It puts the pieces +# together by including all remaining configuration files when starting up the +# web server. +# +# * ports.conf is always included from the main configuration file. It is +# supposed to determine listening ports for incoming connections which can be +# customized anytime. +# +# * Configuration files in the mods-enabled/, conf-enabled/ and sites-enabled/ +# directories contain particular configuration snippets which manage modules, +# global configuration fragments, or virtual host configurations, +# respectively. +# +# They are activated by symlinking available configuration files from their +# respective *-available/ counterparts. These should be managed by using our +# helpers a2enmod/a2dismod, a2ensite/a2dissite and a2enconf/a2disconf. See +# their respective man pages for detailed information. +# +# * The binary is called apache2. Due to the use of environment variables, in +# the default configuration, apache2 needs to be started/stopped with +# /etc/init.d/apache2 or apache2ctl. Calling /usr/bin/apache2 directly will not +# work with the default configuration. + + +# Global configuration +# + +# +# ServerRoot: The top of the directory tree under which the server's +# configuration, error, and log files are kept. +# +# NOTE! If you intend to place this on an NFS (or otherwise network) +# mounted filesystem then please read the Mutex documentation (available +# at ); +# you will save yourself a lot of trouble. +# +# Do NOT add a slash at the end of the directory path. +# +#ServerRoot "/etc/apache2" + +# +# The accept serialization lock file MUST BE STORED ON A LOCAL DISK. +# +#Mutex file:${APACHE_LOCK_DIR} default + +# +# The directory where shm and other runtime files will be stored. +# + +DefaultRuntimeDir ${APACHE_RUN_DIR} + +# +# PidFile: The file in which the server should record its process +# identification number when it starts. +# This needs to be set in /etc/apache2/envvars +# +PidFile ${APACHE_PID_FILE} + +# +# Timeout: The number of seconds before receives and sends time out. +# +Timeout 300 + +# +# KeepAlive: Whether or not to allow persistent connections (more than +# one request per connection). Set to "Off" to deactivate. +# +KeepAlive On + +# +# MaxKeepAliveRequests: The maximum number of requests to allow +# during a persistent connection. Set to 0 to allow an unlimited amount. +# We recommend you leave this number high, for maximum performance. +# +MaxKeepAliveRequests 100 + +# +# KeepAliveTimeout: Number of seconds to wait for the next request from the +# same client on the same connection. +# +KeepAliveTimeout 5 + + +# These need to be set in /etc/apache2/envvars +User ${APACHE_RUN_USER} +Group ${APACHE_RUN_GROUP} + +# +# HostnameLookups: Log the names of clients or just their IP addresses +# e.g., www.apache.org (on) or 204.62.129.132 (off). +# The default is off because it'd be overall better for the net if people +# had to knowingly turn this feature on, since enabling it means that +# each client request will result in AT LEAST one lookup request to the +# nameserver. +# +HostnameLookups Off + +# ErrorLog: The location of the error log file. +# If you do not specify an ErrorLog directive within a +# container, error messages relating to that virtual host will be +# logged here. If you *do* define an error logfile for a +# container, that host's errors will be logged there and not here. +# +ErrorLog ${APACHE_LOG_DIR}/error.log + +# +# LogLevel: Control the severity of messages logged to the error_log. +# Available values: trace8, ..., trace1, debug, info, notice, warn, +# error, crit, alert, emerg. +# It is also possible to configure the log level for particular modules, e.g. +# "LogLevel info ssl:warn" +# +LogLevel warn + +# Include module configuration: +IncludeOptional mods-enabled/*.load +IncludeOptional mods-enabled/*.conf + +# Include list of ports to listen on +Include ports.conf + + +# Sets the default security model of the Apache2 HTTPD server. It does +# not allow access to the root filesystem outside of /usr/share and /var/www. +# The former is used by web applications packaged in Debian, +# the latter may be used for local directories served by the web server. If +# your system is serving content from a sub-directory in /srv you must allow +# access here, or in any related virtual host. + + Options FollowSymLinks + AllowOverride None + Require all denied + + + + AllowOverride None + Require all granted + + + + Options Indexes FollowSymLinks + AllowOverride None + Require all granted + + +# +# Options Indexes FollowSymLinks +# AllowOverride None +# Require all granted +# + + + + +# AccessFileName: The name of the file to look for in each directory +# for additional configuration directives. See also the AllowOverride +# directive. +# +AccessFileName .htaccess + +# +# The following lines prevent .htaccess and .htpasswd files from being +# viewed by Web clients. +# + + Require all denied + + + +# +# The following directives define some format nicknames for use with +# a CustomLog directive. +# +# These deviate from the Common Log Format definitions in that they use %O +# (the actual bytes sent including headers) instead of %b (the size of the +# requested file), because the latter makes it impossible to detect partial +# requests. +# +# Note that the use of %{X-Forwarded-For}i instead of %h is not recommended. +# Use mod_remoteip instead. +# +LogFormat "%v:%p %a %l %u %t \"%r\" %>s %O \"%{Referer}i\" \"%{User-Agent}i\"" vhost_combined +LogFormat "%a %l %u %t \"%r\" %>s %O \"%{Referer}i\" \"%{User-Agent}i\"" combined +LogFormat "%a %l %u %t \"%r\" %>s %O" common +LogFormat "%{Referer}i -> %U" referer +LogFormat "%{User-agent}i" agent + +# Include of directories ignores editors' and dpkg's backup files, +# see README.Debian for details. + +# Include generic snippets of statements +IncludeOptional conf-enabled/*.conf + +# Include the virtual host configurations: +IncludeOptional sites-enabled/*.conf + +# vim: syntax=apache ts=4 sw=4 sts=4 sr noet diff --git a/roles/docker/files/nextcloud/apache2/remoteip.conf b/roles/docker/files/nextcloud/apache2/remoteip.conf new file mode 100644 index 0000000..ae41079 --- /dev/null +++ b/roles/docker/files/nextcloud/apache2/remoteip.conf @@ -0,0 +1,2 @@ +RemoteIPHeader X-Forwarded-For +RemoteIPInternalProxy 172.17.0.0/16 diff --git a/roles/docker/tasks/main.yml b/roles/docker/tasks/main.yml new file mode 100644 index 0000000..355e883 --- /dev/null +++ b/roles/docker/tasks/main.yml @@ -0,0 +1,33 @@ +--- +- name: Add Docker PGP key + apt_key: + keyserver: keys.openpgp.org + id: 0x8D81803C0EBFCD88 + state: present + +- name: Add Docker apt repository + apt_repository: + repo: 'deb [arch=arm64] https://download.docker.com/linux/ubuntu focal stable' + state: present + update_cache: yes + +- name: Install Docker + apt: + name: docker-ce + state: present + +- name: Install Python bindings for Docker Compose + pip: + executable: pip3 + name: docker-compose + state: present + +- name: Create base directory for Docker volumes + file: + name: "{{ base_volume_folder }}" + state: directory + +- name: Set up Docker services + import_tasks: services.yml + tags: + - services diff --git a/roles/docker/tasks/services.yml b/roles/docker/tasks/services.yml new file mode 100644 index 0000000..1ddb854 --- /dev/null +++ b/roles/docker/tasks/services.yml @@ -0,0 +1,16 @@ +--- +- name: Create Docker network for services + docker_network: + name: services + +- name: Deploy services + include_tasks: "services/{{ item.service.file }}" + loop: "{{ services | dict2items(value_name='service') }}" + when: single_service is not defined and + item.service.file is defined + +- name: Deploy single service + include_tasks: "services/{{ services[single_service].file }}" + when: single_service is defined and + single_service in services and + services[single_service].file is defined diff --git a/roles/docker/tasks/services/nextcloud.yml b/roles/docker/tasks/services/nextcloud.yml new file mode 100644 index 0000000..6eec0a1 --- /dev/null +++ b/roles/docker/tasks/services/nextcloud.yml @@ -0,0 +1,84 @@ +--- +- name: Create Nextcloud volume directories + file: + name: "{{ services.nextcloud.volume }}/{{ name }}" + state: directory + loop: + - data + - db + - apache2 + loop_control: + loop_var: name + +- name: Copy Apache2 config files + copy: + src: "nextcloud/apache2/{{ file }}" + dest: "{{ services.nextcloud.volume }}/apache2/{{ file }}" + loop: + - apache2.conf + - remoteip.conf + loop_control: + loop_var: file + +- name: Deploy Nextcloud with Docker Compose + docker_compose: + project_name: nextcloud + pull: true + definition: + version: '3.8' + + services: + mysql: + image: "mariadb:{{ services.nextcloud.mariadb_version }}" + restart: unless-stopped + command: + - --transaction-isolation=READ-COMMITTED + - --log-bin + - --binlog-format=ROW + - --innodb_read_only_compressed=OFF + environment: + MYSQL_DATABASE: "{{ secrets.nextcloud.mysql.db }}" + MYSQL_USER: "{{ secrets.nextcloud.mysql.user }}" + MYSQL_PASSWORD: "{{ secrets.nextcloud.mysql.pw }}" + MYSQL_ROOT_PASSWORD: "{{ secrets.nextcloud.mysql.pw }}" + volumes: + - "{{ services.nextcloud.volume }}/db:/var/lib/mysql" + + redis: + image: "redis:{{ services.nextcloud.redis_version }}" + restart: unless-stopped + command: "redis-server --requirepass={{ secrets.nextcloud.redis.pw }}" + tmpfs: + - /var/lib/redis + + cron: + image: "nextcloud:{{ services.nextcloud.version }}" + restart: unless-stopped + entrypoint: /cron.sh + volumes: + - "{{ services.nextcloud.volume }}/data:/var/www/html" + depends_on: + - mysql + - redis + + app: + image: "nextcloud:{{ services.nextcloud.version }}" + restart: unless-stopped + environment: + MYSQL_HOST: mysql + MYSQL_DATABASE: nextcloud + MYSQL_USER: nextcloud + MYSQL_PASSWORD: "{{ secrets.nextcloud.mysql.pw }}" + REDIS_HOST: redis + REDIS_HOST_PASSWORD: "{{ secrets.nextcloud.redis.pw }}" + PHP_MEMORY_LIMIT: 2G + PHP_UPLOAD_LIMIT: 16G + volumes: + - "{{ services.nextcloud.volume }}/data:/var/www/html" + - "{{ services.nextcloud.volume }}/apache2/apache2.conf:/etc/apache2/apache2.conf:ro" + - "{{ services.nextcloud.volume }}/apache2/remoteip.conf:/etc/apache2/conf-enabled/remoteip.conf:ro" + ports: + - "127.0.0.1:8080:80" + depends_on: + - mysql + - redis diff --git a/roles/docker/tasks/services/restic.yml b/roles/docker/tasks/services/restic.yml new file mode 100644 index 0000000..f0beeb8 --- /dev/null +++ b/roles/docker/tasks/services/restic.yml @@ -0,0 +1,65 @@ +--- +- name: Deploy Restic with Docker Compose + docker_compose: + project_name: restic + pull: true + definition: + version: '3.8' + + services: + restic_backup: + image: "mazzolino/restic:{{ services.restic.version }}" + restart: unless-stopped + environment: + RUN_ON_STARTUP: false + BACKUP_CRON: '0 30 3 * * *' + RESTIC_REPOSITORY: "b2:{{ secrets.restic.b2.bucket }}:{{ services.restic.repo }}" + RESTIC_PASSWORD: "{{ secrets.restic.pw }}" + RESTIC_BACKUP_SOURCES: /mnt/volumes + RESTIC_BACKUP_ARGS: >- + --tag docker-volumes + --exclude '*.tmp' + --verbose + RESTIC_FORGET_ARGS: >- + --keep-last 10 + --keep-daily 7 + --keep-weekly 5 + --keep-monthly 12 + B2_ACCOUNT_ID: "{{ secrets.restic.b2.id }}" + B2_ACCOUNT_KEY: "{{ secrets.restic.b2.key }}" + TZ: Europe/Copenhagen + volumes: + - ./caddy:/mnt/volumes/caddy:ro + - ./nextcloud:/mnt/volumes/nextcloud:ro + networks: + restic: + + restic_prune: + image: "mazzolino/restic:{{ services.restic.version }}" + restart: unless-stopped + environment: + RUN_ON_STARTUP: false + PRUNE_CRON: '0 0 4 * * *' + RESTIC_REPOSITORY: "b2:{{ secrets.restic.b2.bucket }}:{{ services.restic.repo }}" + RESTIC_PASSWORD: "{{ secrets.restic.pw }}" + RESTIC_PRUNE_ARGS: >- + --verbose + B2_ACCOUNT_ID: "{{ secrets.restic.b2.id }}" + B2_ACCOUNT_KEY: "{{ secrets.restic.b2.key }}" + TZ: Europe/Copenhagen + networks: + restic: + + restic_check: + image: "mazzolino/restic:{{ services.restic.version }}" + restart: unless-stopped + environment: + RUN_ON_STARTUP: false + CHECK_CRON: '0 15 5 * * *' + RESTIC_REPOSITORY: "b2:{{ secrets.restic.b2.bucket }}:{{ services.restic.repo }}" + RESTIC_PASSWORD: "{{ secrets.restic.pw }}" + RESTIC_CHECK_ARGS: >- + --verbose + B2_ACCOUNT_ID: "{{ secrets.restic.b2.id }}" + B2_ACCOUNT_KEY: "{{ secrets.restic.b2.key }}" + TZ: Europe/Copenhagen diff --git a/roles/docker/templates/Caddyfile.j2 b/roles/docker/templates/Caddyfile.j2 new file mode 100644 index 0000000..fcb6b7c --- /dev/null +++ b/roles/docker/templates/Caddyfile.j2 @@ -0,0 +1,51 @@ +{ + admin off +} + +{{ services.nextcloud.domain }} { + tls {{ secrets.tls.email }} + + rewrite /.well-known/caldav /remote.php/dav + rewrite /.well-known/carddav /remote.php/dav + + header { + Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" + -Server + } + + log { + output discard + } + + reverse_proxy localhost:8080 +} + +{{ services.emby.domain }} { + tls {{ secrets.tls.email }} + + header { + Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" + -Server + } + + log { + output discard + } + + reverse_proxy localhost:8096 +} + +{{ services.monerod.domain }}:18089 { + tls {{ secrets.tls.email }} + + header { + Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" + -Server + } + + log { + output discard + } + + reverse_proxy localhost:18081 +}