Compare commits
1 commit
master
...
forwardedf
Author | SHA1 | Date | |
---|---|---|---|
34885e6231 |
37 changed files with 141 additions and 664 deletions
1
.envrc
1
.envrc
|
@ -1 +0,0 @@
|
||||||
use flake
|
|
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -1 +0,0 @@
|
||||||
.direnv
|
|
|
@ -1,6 +1,4 @@
|
||||||
# shoarma
|
# shoarma
|
||||||
|
|
||||||
⚠️ Code in this repository has been assimilated by the [home/nixos-servers](https://git.kun.is/home/nixos-servers/src/branch/master/legacy) repository.
|
|
||||||
|
|
||||||
Docker Swarm for our home servers.
|
Docker Swarm for our home servers.
|
||||||
Includes both Terraform and Ansible code to provision and configure the swarm.
|
Includes both Terraform and Ansible code to provision and configure the swarm.
|
||||||
|
|
|
@ -3,7 +3,7 @@ roles_path=~/.ansible/roles:/usr/share/ansible/roles:/etc/ansible/roles:roles
|
||||||
inventory=inventory
|
inventory=inventory
|
||||||
interpreter_python=/usr/bin/python3
|
interpreter_python=/usr/bin/python3
|
||||||
remote_user = root
|
remote_user = root
|
||||||
vault_password_file=$HOME/.config/home/ansible-vault-secret
|
vault_password_file=util/secret-service-client.sh
|
||||||
|
|
||||||
[diff]
|
[diff]
|
||||||
always = True
|
always = True
|
||||||
|
|
|
@ -1,8 +1,5 @@
|
||||||
data_directory_base: /mnt/data
|
data_directory_base: /mnt/data
|
||||||
git_ssh_port: 56287
|
git_ssh_port: 56287
|
||||||
elasticsearch_port: 14653
|
|
||||||
fluent_forward_port: 24224
|
|
||||||
concourse_public_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIBSVLcr617iJt+hqLFSsOQy1JeueLIAj1eRfuI+KeZAu pim@x260"
|
|
||||||
|
|
||||||
nfs_shares:
|
nfs_shares:
|
||||||
- name: nextcloud_data
|
- name: nextcloud_data
|
||||||
|
@ -37,22 +34,6 @@ nfs_shares:
|
||||||
path: /mnt/data/overleaf/redis
|
path: /mnt/data/overleaf/redis
|
||||||
- name: overleaf_mongodb
|
- name: overleaf_mongodb
|
||||||
path: /mnt/data/overleaf/mongodb
|
path: /mnt/data/overleaf/mongodb
|
||||||
- name: prometheus_data
|
|
||||||
path: /mnt/data/prometheus/data
|
|
||||||
- name: elasticsearch_certs
|
|
||||||
path: /mnt/data/elasticsearch/certs
|
|
||||||
- name: elasticsearch_data
|
|
||||||
path: /mnt/data/elasticsearch/data
|
|
||||||
- name: grafana_data
|
|
||||||
path: /mnt/data/grafana/data
|
|
||||||
- name: kitchenowl_data
|
|
||||||
path: /mnt/data/kitchenowl/data
|
|
||||||
- name: ampache_mysql
|
|
||||||
path: /mnt/data/ampache/mysql
|
|
||||||
- name: ampache_config
|
|
||||||
path: /mnt/data/ampache/config
|
|
||||||
- name: music
|
|
||||||
path: /mnt/data/nextcloud/data/data/pim/files/Music
|
|
||||||
|
|
||||||
database_passwords:
|
database_passwords:
|
||||||
nextcloud: !vault |
|
nextcloud: !vault |
|
||||||
|
|
25
ansible/inventory/host_vars/manager.yml
Normal file
25
ansible/inventory/host_vars/manager.yml
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
docker_node_labels:
|
||||||
|
- hostname: maestro
|
||||||
|
labels: {}
|
||||||
|
- hostname: swarmpub1
|
||||||
|
labels:
|
||||||
|
public: "true"
|
||||||
|
mastodon: "true"
|
||||||
|
- hostname: swarmpub2
|
||||||
|
labels:
|
||||||
|
public: "true"
|
||||||
|
jitsi: "true"
|
||||||
|
- hostname: swarmpriv1
|
||||||
|
labels:
|
||||||
|
private: "true"
|
||||||
|
overleaf: "true"
|
||||||
|
syncthing: "true"
|
||||||
|
hedgedoc: "true"
|
||||||
|
radicale: "true"
|
||||||
|
- hostname: swarmpriv2
|
||||||
|
labels:
|
||||||
|
private: "true"
|
||||||
|
seafile: "true"
|
||||||
|
freshrss: "true"
|
||||||
|
pihole: "true"
|
||||||
|
discourse: "true"
|
|
@ -7,7 +7,11 @@ all:
|
||||||
children:
|
children:
|
||||||
workers:
|
workers:
|
||||||
hosts:
|
hosts:
|
||||||
bancomart:
|
swarmpub1:
|
||||||
ansible_host: bancomart.dmz
|
ansible_host: swarmpub1.dmz
|
||||||
vpay:
|
swarmpub2:
|
||||||
ansible_host: vpay.dmz
|
ansible_host: swarmpub2.dmz
|
||||||
|
swarmpriv1:
|
||||||
|
ansible_host: swarmpriv1.dmz
|
||||||
|
swarmpriv2:
|
||||||
|
ansible_host: swarmpriv2.dmz
|
||||||
|
|
|
@ -1,9 +0,0 @@
|
||||||
---
|
|
||||||
- name: Remove a Docker swarm stack
|
|
||||||
hosts: manager
|
|
||||||
|
|
||||||
tasks:
|
|
||||||
- name: Remove the stack
|
|
||||||
docker_stack:
|
|
||||||
name: "{{ stack }}"
|
|
||||||
state: absent
|
|
|
@ -17,17 +17,7 @@
|
||||||
|
|
||||||
roles:
|
roles:
|
||||||
- setup_apt
|
- setup_apt
|
||||||
|
- docker
|
||||||
post_tasks:
|
|
||||||
- name: Install Docker
|
|
||||||
include_role:
|
|
||||||
name: docker
|
|
||||||
vars:
|
|
||||||
docker_daemon_config: {}
|
|
||||||
# log-driver: fluentd
|
|
||||||
# log-opts:
|
|
||||||
# fluentd-address: "localhost:22222"
|
|
||||||
# tag: "docker.{{ '{{' }}.Name{{ '}}' }}"
|
|
||||||
|
|
||||||
- name: Setup Docker Swarm manager
|
- name: Setup Docker Swarm manager
|
||||||
hosts: manager
|
hosts: manager
|
||||||
|
@ -59,14 +49,9 @@
|
||||||
|
|
||||||
- hosts: manager
|
- hosts: manager
|
||||||
tasks:
|
tasks:
|
||||||
- name: Add concourse to authorized keys
|
- name: Add labels to Docker Swarm
|
||||||
authorized_key:
|
docker_node:
|
||||||
user: root
|
hostname: "{{ item.hostname }}"
|
||||||
key: "{{ concourse_public_key }}"
|
labels: "{{ item.labels }}"
|
||||||
|
labels_state: replace
|
||||||
- hosts: manager, workers
|
loop: "{{ docker_node_labels }}"
|
||||||
tasks:
|
|
||||||
- name: Increase vm.max_map_count
|
|
||||||
sysctl:
|
|
||||||
name: vm.max_map_count
|
|
||||||
value: 262144
|
|
||||||
|
|
|
@ -9,15 +9,13 @@
|
||||||
- {role: mastodon, tags: mastodon}
|
- {role: mastodon, tags: mastodon}
|
||||||
- {role: freshrss, tags: freshrss}
|
- {role: freshrss, tags: freshrss}
|
||||||
- {role: hedgedoc, tags: hedgedoc}
|
- {role: hedgedoc, tags: hedgedoc}
|
||||||
# - {role: overleaf, tags: overleaf}
|
- {role: overleaf, tags: overleaf}
|
||||||
- {role: cyberchef, tags: cyberchef}
|
- {role: cyberchef, tags: cyberchef}
|
||||||
- {role: inbucket, tags: inbucket}
|
- {role: inbucket, tags: inbucket}
|
||||||
- {role: kms, tags: kms}
|
- {role: kms, tags: kms}
|
||||||
- {role: swarm_dashboard, tags: swarm_dashboard}
|
- {role: swarm_dashboard, tags: swarm_dashboard}
|
||||||
- {role: shephard, tags: shephard}
|
- {role: shephard, tags: shephard}
|
||||||
|
# - {role: jitsi, tags: jitsi}
|
||||||
- {role: pihole, tags: pihole}
|
- {role: pihole, tags: pihole}
|
||||||
- {role: nextcloud, tags: nextcloud}
|
- {role: nextcloud, tags: nextcloud}
|
||||||
- {role: syncthing, tags: syncthing}
|
- {role: syncthing, tags: syncthing}
|
||||||
- {role: monitoring, tags: monitoring}
|
|
||||||
- {role: kitchenowl, tags: kitchenowl}
|
|
||||||
- {role: ampache, tags: ampache}
|
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
---
|
|
||||||
roles:
|
|
||||||
- name: setup_apt
|
- name: setup_apt
|
||||||
src: https://github.com/sunscrapers/ansible-role-apt.git
|
src: https://github.com/sunscrapers/ansible-role-apt.git
|
||||||
scm: git
|
scm: git
|
||||||
|
|
|
@ -1,56 +0,0 @@
|
||||||
# vi: ft=yaml
|
|
||||||
version: '3.7'
|
|
||||||
|
|
||||||
networks:
|
|
||||||
traefik:
|
|
||||||
external: true
|
|
||||||
|
|
||||||
volumes:
|
|
||||||
ampache_mysql:
|
|
||||||
driver_opts:
|
|
||||||
type: "nfs"
|
|
||||||
o: "addr=192.168.30.10,nolock,soft,rw"
|
|
||||||
device: ":/mnt/data/ampache/mysql"
|
|
||||||
ampache_config:
|
|
||||||
driver_opts:
|
|
||||||
type: "nfs"
|
|
||||||
o: "addr=192.168.30.10,nolock,soft,rw"
|
|
||||||
device: ":/mnt/data/ampache/config"
|
|
||||||
music:
|
|
||||||
driver_opts:
|
|
||||||
type: "nfs"
|
|
||||||
o: "addr=192.168.30.10,nolock,soft,rw"
|
|
||||||
device: ":/mnt/data/nextcloud/data/data/pim/files/Music"
|
|
||||||
|
|
||||||
services:
|
|
||||||
ampache:
|
|
||||||
image: ampache/ampache:6
|
|
||||||
volumes:
|
|
||||||
- type: volume
|
|
||||||
source: ampache_mysql
|
|
||||||
target: /var/lib/mysql
|
|
||||||
volume:
|
|
||||||
nocopy: true
|
|
||||||
- type: volume
|
|
||||||
source: ampache_config
|
|
||||||
target: /var/www/config
|
|
||||||
volume:
|
|
||||||
nocopy: true
|
|
||||||
- type: volume
|
|
||||||
source: music
|
|
||||||
target: /media
|
|
||||||
read_only: true
|
|
||||||
volume:
|
|
||||||
nocopy: true
|
|
||||||
networks:
|
|
||||||
- traefik
|
|
||||||
deploy:
|
|
||||||
labels:
|
|
||||||
- traefik.enable=true
|
|
||||||
- traefik.http.routers.ampache.entrypoints=websecure
|
|
||||||
- traefik.http.routers.ampache.rule=Host(`music.kun.is`)
|
|
||||||
- traefik.http.routers.ampache.tls=true
|
|
||||||
- traefik.http.routers.ampache.tls.certresolver=letsencrypt
|
|
||||||
- traefik.http.routers.ampache.service=ampache
|
|
||||||
- traefik.http.services.ampache.loadbalancer.server.port=80
|
|
||||||
- traefik.docker.network=traefik
|
|
|
@ -1,5 +0,0 @@
|
||||||
- name: Deploy Docker stack
|
|
||||||
docker_stack:
|
|
||||||
name: ampache
|
|
||||||
compose:
|
|
||||||
- "{{ lookup('template', '{{ role_path }}/docker-stack.yml.j2') | from_yaml }}"
|
|
|
@ -1,7 +1,6 @@
|
||||||
APP_NAME = Forgejo: Beyond coding. We forge.
|
APP_NAME = Forgejo: Beyond coding. We forge.
|
||||||
RUN_MODE = prod
|
RUN_MODE = prod
|
||||||
RUN_USER = git
|
RUN_USER = git
|
||||||
WORK_PATH=/data/gitea
|
|
||||||
|
|
||||||
[repository]
|
[repository]
|
||||||
ROOT = /data/git/repositories
|
ROOT = /data/git/repositories
|
||||||
|
@ -57,9 +56,8 @@ PATH = /data/gitea/attachments
|
||||||
[log]
|
[log]
|
||||||
MODE = console
|
MODE = console
|
||||||
LEVEL = info
|
LEVEL = info
|
||||||
logger.router.MODE = console
|
ROUTER = console
|
||||||
ROOT_PATH = /data/gitea/log
|
ROOT_PATH = /data/gitea/log
|
||||||
logger.access.MODE=console
|
|
||||||
|
|
||||||
[security]
|
[security]
|
||||||
INSTALL_LOCK = true
|
INSTALL_LOCK = true
|
||||||
|
@ -104,6 +102,3 @@ DEFAULT_TRUST_MODEL = committer
|
||||||
|
|
||||||
[ui]
|
[ui]
|
||||||
DEFAULT_THEME = forgejo-light
|
DEFAULT_THEME = forgejo-light
|
||||||
|
|
||||||
[oauth2]
|
|
||||||
ENABLE=false
|
|
||||||
|
|
|
@ -18,8 +18,8 @@ volumes:
|
||||||
device: ":/mnt/data/forgejo"
|
device: ":/mnt/data/forgejo"
|
||||||
|
|
||||||
services:
|
services:
|
||||||
forgejo:
|
server:
|
||||||
image: codeberg.org/forgejo/forgejo:1.20
|
image: codeberg.org/forgejo/forgejo:1.18
|
||||||
environment:
|
environment:
|
||||||
- USER_UID=1000
|
- USER_UID=1000
|
||||||
- USER_GID=1000
|
- USER_GID=1000
|
||||||
|
@ -49,8 +49,6 @@ services:
|
||||||
- traefik.http.routers.forgejo.service=forgejo
|
- traefik.http.routers.forgejo.service=forgejo
|
||||||
- traefik.http.services.forgejo.loadbalancer.server.port=3000
|
- traefik.http.services.forgejo.loadbalancer.server.port=3000
|
||||||
- traefik.docker.network=traefik
|
- traefik.docker.network=traefik
|
||||||
- traefik.http.middlewares.set-forwarded-for.headers.hostsProxyHeaders=X-Forwarded-For
|
|
||||||
- traefik.http.routers.forgejo.middlewares=set-forwarded-for
|
|
||||||
configs:
|
configs:
|
||||||
- source: config
|
- source: config
|
||||||
target: /data/gitea/conf/app.ini
|
target: /data/gitea/conf/app.ini
|
||||||
|
|
|
@ -13,7 +13,7 @@ volumes:
|
||||||
device: ":/mnt/data/hedgedoc/uploads"
|
device: ":/mnt/data/hedgedoc/uploads"
|
||||||
|
|
||||||
services:
|
services:
|
||||||
hedgedoc:
|
hedgedoc-app:
|
||||||
image: quay.io/hedgedoc/hedgedoc:1.9.7
|
image: quay.io/hedgedoc/hedgedoc:1.9.7
|
||||||
environment:
|
environment:
|
||||||
- CMD_DB_URL=postgres://hedgedoc:{{ database_passwords.hedgedoc }}@192.168.30.10:5432/hedgedoc
|
- CMD_DB_URL=postgres://hedgedoc:{{ database_passwords.hedgedoc }}@192.168.30.10:5432/hedgedoc
|
||||||
|
|
|
@ -6,7 +6,7 @@ networks:
|
||||||
external: true
|
external: true
|
||||||
|
|
||||||
services:
|
services:
|
||||||
inbucket:
|
kms-server:
|
||||||
image: inbucket/inbucket
|
image: inbucket/inbucket
|
||||||
networks:
|
networks:
|
||||||
- traefik
|
- traefik
|
||||||
|
|
|
@ -1,45 +0,0 @@
|
||||||
# vi: ft=yaml
|
|
||||||
version: '3.7'
|
|
||||||
|
|
||||||
networks:
|
|
||||||
traefik:
|
|
||||||
external: true
|
|
||||||
kitchenowl:
|
|
||||||
|
|
||||||
volumes:
|
|
||||||
data:
|
|
||||||
driver_opts:
|
|
||||||
type: "nfs"
|
|
||||||
o: "addr=192.168.30.10,nolock,soft,rw"
|
|
||||||
device: ":/mnt/data/kitchenowl/data"
|
|
||||||
|
|
||||||
services:
|
|
||||||
front:
|
|
||||||
image: tombursch/kitchenowl-web:v0.4.17
|
|
||||||
depends_on:
|
|
||||||
- back
|
|
||||||
networks:
|
|
||||||
- traefik
|
|
||||||
- kitchenowl
|
|
||||||
deploy:
|
|
||||||
labels:
|
|
||||||
- traefik.enable=true
|
|
||||||
- traefik.http.routers.kitchenowl.entrypoints=websecure
|
|
||||||
- traefik.http.routers.kitchenowl.rule=Host(`boodschappen.kun.is`)
|
|
||||||
- traefik.http.routers.kitchenowl.tls=true
|
|
||||||
- traefik.http.routers.kitchenowl.tls.certresolver=letsencrypt
|
|
||||||
- traefik.http.routers.kitchenowl.service=kitchenowl
|
|
||||||
- traefik.http.services.kitchenowl.loadbalancer.server.port=80
|
|
||||||
- traefik.docker.network=traefik
|
|
||||||
back:
|
|
||||||
image: tombursch/kitchenowl:v88
|
|
||||||
networks:
|
|
||||||
- kitchenowl
|
|
||||||
environment:
|
|
||||||
- JWT_SECRET_KEY={{ jwt_secret_key }}
|
|
||||||
volumes:
|
|
||||||
- type: volume
|
|
||||||
source: data
|
|
||||||
target: /data
|
|
||||||
volume:
|
|
||||||
nocopy: true
|
|
|
@ -1,5 +0,0 @@
|
||||||
- name: Deploy Docker stack
|
|
||||||
docker_stack:
|
|
||||||
name: kitchenowl
|
|
||||||
compose:
|
|
||||||
- "{{ lookup('template', '{{ role_path }}/docker-stack.yml.j2') | from_yaml }}"
|
|
|
@ -1,7 +0,0 @@
|
||||||
jwt_secret_key: !vault |
|
|
||||||
$ANSIBLE_VAULT;1.1;AES256
|
|
||||||
37376338663532376135613331303737626633666138643132316336306164393134633639303865
|
|
||||||
3134613830323335663466373262316262353464323535300a636163633439323035643033623363
|
|
||||||
36316361656133663235333834343233363134313938656664356538366166653336656562623664
|
|
||||||
3332393330616636630a646139393937313932373963623764346134323635336539346562346635
|
|
||||||
36613637396133383664323561666464346336386233363434653765356334633831
|
|
|
@ -2,7 +2,7 @@
|
||||||
version: '3.7'
|
version: '3.7'
|
||||||
|
|
||||||
services:
|
services:
|
||||||
kms:
|
kms-server:
|
||||||
image: teddysun/kms
|
image: teddysun/kms
|
||||||
ports:
|
ports:
|
||||||
- 1688:1688
|
- 1688:1688
|
||||||
|
|
|
@ -1,132 +0,0 @@
|
||||||
# vi: ft=yaml
|
|
||||||
version: "3.8"
|
|
||||||
|
|
||||||
networks:
|
|
||||||
traefik:
|
|
||||||
external: true
|
|
||||||
grafana:
|
|
||||||
|
|
||||||
configs:
|
|
||||||
esdatasource:
|
|
||||||
external: true
|
|
||||||
name: "{{ esdatasource.config_name }}"
|
|
||||||
fluentconf:
|
|
||||||
external: true
|
|
||||||
name: "{{ fluentconf.config_name }}"
|
|
||||||
|
|
||||||
volumes:
|
|
||||||
escerts:
|
|
||||||
driver_opts:
|
|
||||||
type: "nfs"
|
|
||||||
o: "addr=192.168.30.10,nolock,soft,rw"
|
|
||||||
device: ":/mnt/data/elasticsearch/certs"
|
|
||||||
esdata:
|
|
||||||
driver_opts:
|
|
||||||
type: "nfs"
|
|
||||||
o: "addr=192.168.30.10,nolock,soft,rw"
|
|
||||||
device: ":/mnt/data/elasticsearch/data"
|
|
||||||
grafanadata:
|
|
||||||
driver_opts:
|
|
||||||
type: "nfs"
|
|
||||||
o: "addr=192.168.30.10,nolock,soft,rw"
|
|
||||||
device: ":/mnt/data/grafana/data"
|
|
||||||
|
|
||||||
services:
|
|
||||||
elasticsearch:
|
|
||||||
image: docker.elastic.co/elasticsearch/elasticsearch:8.8.1
|
|
||||||
volumes:
|
|
||||||
- type: volume
|
|
||||||
source: escerts
|
|
||||||
target: /usr/share/elasticsearch/config/certs
|
|
||||||
volume:
|
|
||||||
nocopy: true
|
|
||||||
- type: volume
|
|
||||||
source: esdata
|
|
||||||
target: /usr/share/elasticsearch/data
|
|
||||||
volume:
|
|
||||||
nocopy: true
|
|
||||||
ports:
|
|
||||||
- {{ elasticsearch_port }}:9200
|
|
||||||
environment:
|
|
||||||
- node.name=es01
|
|
||||||
- cluster.name=shoarma
|
|
||||||
- discovery.type=single-node
|
|
||||||
- bootstrap.memory_lock=true
|
|
||||||
- xpack.security.enabled=false
|
|
||||||
- xpack.security.http.ssl.enabled=false
|
|
||||||
- xpack.security.http.ssl.key=certs/es01/es01.key
|
|
||||||
- xpack.security.http.ssl.certificate=certs/es01/es01.crt
|
|
||||||
- xpack.security.http.ssl.certificate_authorities=certs/ca/ca.crt
|
|
||||||
- xpack.security.transport.ssl.enabled=false
|
|
||||||
- xpack.security.transport.ssl.key=certs/es01/es01.key
|
|
||||||
- xpack.security.transport.ssl.certificate=certs/es01/es01.crt
|
|
||||||
- xpack.security.transport.ssl.certificate_authorities=certs/ca/ca.crt
|
|
||||||
- xpack.security.transport.ssl.verification_mode=certificate
|
|
||||||
- xpack.license.self_generated.type=basic
|
|
||||||
ulimits:
|
|
||||||
memlock:
|
|
||||||
soft: -1
|
|
||||||
hard: -1
|
|
||||||
healthcheck:
|
|
||||||
test:
|
|
||||||
[
|
|
||||||
"CMD-SHELL",
|
|
||||||
"curl http://localhost:9200 | grep -q 'You Know, for Search'",
|
|
||||||
]
|
|
||||||
interval: 10s
|
|
||||||
timeout: 10s
|
|
||||||
retries: 120
|
|
||||||
|
|
||||||
grafana:
|
|
||||||
image: grafana/grafana-oss
|
|
||||||
depends_on:
|
|
||||||
- elasticsearch
|
|
||||||
networks:
|
|
||||||
- traefik
|
|
||||||
- grafana
|
|
||||||
deploy:
|
|
||||||
labels:
|
|
||||||
- traefik.enable=true
|
|
||||||
- traefik.http.routers.grafana.entrypoints=localsecure
|
|
||||||
- traefik.http.routers.grafana.rule=Host(`grafana.kun.is`)
|
|
||||||
- traefik.http.routers.grafana.tls=true
|
|
||||||
- traefik.http.routers.grafana.tls.certresolver=letsencrypt
|
|
||||||
- traefik.http.routers.grafana.service=grafana
|
|
||||||
- traefik.http.services.grafana.loadbalancer.server.port=3000
|
|
||||||
- traefik.docker.network=traefik
|
|
||||||
volumes:
|
|
||||||
- type: volume
|
|
||||||
source: grafanadata
|
|
||||||
target: /var/lib/grafana
|
|
||||||
volume:
|
|
||||||
nocopy: true
|
|
||||||
configs:
|
|
||||||
- source: esdatasource
|
|
||||||
target: /etc/grafana/provisioning/datasources/elasticsearch.yaml
|
|
||||||
|
|
||||||
grafana-ntfy:
|
|
||||||
image: kittyandrew/grafana-to-ntfy:master
|
|
||||||
depends_on:
|
|
||||||
- grafana
|
|
||||||
ports:
|
|
||||||
- 8080:8080
|
|
||||||
networks:
|
|
||||||
grafana:
|
|
||||||
aliases:
|
|
||||||
- grafana-ntfy
|
|
||||||
environment:
|
|
||||||
- NTFY_URL=https://ntfy.kun.is/alerts
|
|
||||||
- NTFY_BAUTH_USER=pim
|
|
||||||
- NTFY_BAUTH_PASS={{ ntfy_password }}
|
|
||||||
- BAUTH_USER=admin
|
|
||||||
- BAUTH_PASS=test
|
|
||||||
|
|
||||||
fluentd:
|
|
||||||
image: git.kun.is/pim/fluentd:1.0.3
|
|
||||||
depends_on:
|
|
||||||
- elasticsearch
|
|
||||||
ports:
|
|
||||||
- {{ fluent_forward_port }}:24224
|
|
||||||
configs:
|
|
||||||
- source: fluentconf
|
|
||||||
target: /fluentd/etc/fluent.conf
|
|
|
@ -1,35 +0,0 @@
|
||||||
# vi: ft=yaml
|
|
||||||
apiVersion: 1
|
|
||||||
|
|
||||||
datasources:
|
|
||||||
- name: cpu
|
|
||||||
type: elasticsearch
|
|
||||||
access: proxy
|
|
||||||
url: http://maestro.dmz:{{ elasticsearch_port }}
|
|
||||||
jsonData:
|
|
||||||
index: 'fluentd.cpu-*'
|
|
||||||
timeField: '@timestamp'
|
|
||||||
|
|
||||||
- name: memory
|
|
||||||
type: elasticsearch
|
|
||||||
access: proxy
|
|
||||||
url: http://maestro.dmz:{{ elasticsearch_port }}
|
|
||||||
jsonData:
|
|
||||||
index: 'fluentd.memory-*'
|
|
||||||
timeField: '@timestamp'
|
|
||||||
|
|
||||||
- name: diskfree
|
|
||||||
type: elasticsearch
|
|
||||||
access: proxy
|
|
||||||
url: http://maestro.dmz:{{ elasticsearch_port }}
|
|
||||||
jsonData:
|
|
||||||
index: 'fluentd.diskfree-*'
|
|
||||||
timeField: '@timestamp'
|
|
||||||
|
|
||||||
- name: traefik_access
|
|
||||||
type: elasticsearch
|
|
||||||
access: proxy
|
|
||||||
url: http://maestro.dmz:{{ elasticsearch_port }}
|
|
||||||
jsonData:
|
|
||||||
index: 'fluentd.access.traefik-*'
|
|
||||||
timeField: '@timestamp'
|
|
|
@ -1,35 +0,0 @@
|
||||||
# vi: ft=yaml
|
|
||||||
# Receive events from 24224/tcp
|
|
||||||
# This is used by log forwarding and the fluent-cat command
|
|
||||||
<source>
|
|
||||||
@type forward
|
|
||||||
port {{ fluent_forward_port }}
|
|
||||||
</source>
|
|
||||||
|
|
||||||
<filter access.**>
|
|
||||||
@type geoip
|
|
||||||
geoip_lookup_keys host
|
|
||||||
backend_library geoip2_c
|
|
||||||
<record>
|
|
||||||
latitude ${location.latitude["host"]}
|
|
||||||
longitude ${location.longitude["host"]}
|
|
||||||
</record>
|
|
||||||
skip_adding_null_record true
|
|
||||||
</filter>
|
|
||||||
|
|
||||||
<match cpu memory diskfree access.**>
|
|
||||||
@type elasticsearch
|
|
||||||
host maestro.dmz
|
|
||||||
port {{ elasticsearch_port }}
|
|
||||||
include_timestamp true
|
|
||||||
logstash_format true
|
|
||||||
logstash_prefix fluentd.${tag}
|
|
||||||
</match>
|
|
||||||
|
|
||||||
<match **>
|
|
||||||
@type null
|
|
||||||
</match>
|
|
||||||
|
|
||||||
<system>
|
|
||||||
log_level info
|
|
||||||
</system>
|
|
|
@ -1,21 +0,0 @@
|
||||||
- name: Create fluentd config
|
|
||||||
docker_config:
|
|
||||||
name: fluentconf
|
|
||||||
data: "{{ lookup('template', '{{ role_path }}/fluent.conf.j2') }}"
|
|
||||||
use_ssh_client: true
|
|
||||||
rolling_versions: true
|
|
||||||
register: fluentconf
|
|
||||||
|
|
||||||
- name: Create elasticsearch data source config
|
|
||||||
docker_config:
|
|
||||||
name: esdatasource
|
|
||||||
data: "{{ lookup('template', '{{ role_path }}/elasticsearch.yml.j2') }}"
|
|
||||||
use_ssh_client: true
|
|
||||||
rolling_versions: true
|
|
||||||
register: esdatasource
|
|
||||||
|
|
||||||
- name: Deploy Docker stack
|
|
||||||
docker_stack:
|
|
||||||
name: monitoring
|
|
||||||
compose:
|
|
||||||
- "{{ lookup('template', '{{ role_path }}/docker-stack.yml.j2') | from_yaml }}"
|
|
|
@ -1,8 +0,0 @@
|
||||||
ntfy_password: !vault |
|
|
||||||
$ANSIBLE_VAULT;1.1;AES256
|
|
||||||
36333232393635383732336630626463633038353862333430396437333733376239343531663339
|
|
||||||
6364643930636566326463393963316263323061613032350a383930376537373437633333623639
|
|
||||||
66613439636531393761366534333134383231303637643063633537393535356536636530666665
|
|
||||||
6537653731666130610a346135373562333931646237396233613065353165623336373935386137
|
|
||||||
36313830623931313238333430346238626562353661616465333736346230396162386137363435
|
|
||||||
3362636565336639643832626165613236643466633537633236
|
|
|
@ -13,7 +13,7 @@ volumes:
|
||||||
device: ":/mnt/data/nextcloud/data"
|
device: ":/mnt/data/nextcloud/data"
|
||||||
|
|
||||||
services:
|
services:
|
||||||
nextcloud:
|
app:
|
||||||
image: nextcloud:27
|
image: nextcloud:27
|
||||||
volumes:
|
volumes:
|
||||||
- type: volume
|
- type: volume
|
||||||
|
|
|
@ -51,7 +51,6 @@ services:
|
||||||
- traefik.http.routers.pihole.tls.certresolver=letsencrypt
|
- traefik.http.routers.pihole.tls.certresolver=letsencrypt
|
||||||
- traefik.http.routers.pihole.service=pihole
|
- traefik.http.routers.pihole.service=pihole
|
||||||
- traefik.http.services.pihole.loadbalancer.server.port=80
|
- traefik.http.services.pihole.loadbalancer.server.port=80
|
||||||
|
- traefik.http.middlewares.set-forwarded-for.headers.hostsProxyHeaders=X-Forwarded-For
|
||||||
|
- traefik.http.routers.pihole.middlewares=set-forwarded-for
|
||||||
- traefik.docker.network=traefik
|
- traefik.docker.network=traefik
|
||||||
placement:
|
|
||||||
constraints:
|
|
||||||
- node.role == manager
|
|
||||||
|
|
|
@ -6,7 +6,7 @@ networks:
|
||||||
external: true
|
external: true
|
||||||
|
|
||||||
services:
|
services:
|
||||||
swarm-dashboard:
|
dashboard:
|
||||||
image: charypar/swarm-dashboard
|
image: charypar/swarm-dashboard
|
||||||
volumes:
|
volumes:
|
||||||
- type: bind
|
- type: bind
|
||||||
|
|
|
@ -23,18 +23,9 @@ services:
|
||||||
networks:
|
networks:
|
||||||
- traefik
|
- traefik
|
||||||
ports:
|
ports:
|
||||||
- mode: host
|
- 443:443
|
||||||
protocol: tcp
|
- 80:80
|
||||||
published: 443
|
- 444:444
|
||||||
target: 443
|
|
||||||
- mode: host
|
|
||||||
protocol: tcp
|
|
||||||
published: 80
|
|
||||||
target: 80
|
|
||||||
- mode: host
|
|
||||||
protocol: tcp
|
|
||||||
published: 444
|
|
||||||
target: 444
|
|
||||||
deploy:
|
deploy:
|
||||||
placement:
|
placement:
|
||||||
constraints:
|
constraints:
|
||||||
|
@ -125,11 +116,3 @@ services:
|
||||||
- --certificatesresolvers.letsencrypt.acme.httpchallenge.entrypoint=web
|
- --certificatesresolvers.letsencrypt.acme.httpchallenge.entrypoint=web
|
||||||
|
|
||||||
- --serversTransport.insecureSkipVerify=true
|
- --serversTransport.insecureSkipVerify=true
|
||||||
|
|
||||||
- --accesslog=true
|
|
||||||
- --accesslog.fields.defaultmode=keep
|
|
||||||
- --accesslog.fields.names.ClientUsername=drop
|
|
||||||
- --accesslog.fields.headers.defaultmode=keep
|
|
||||||
- --accesslog.fields.headers.names.User-Agent=keep
|
|
||||||
- --accesslog.fields.headers.names.Authorization=drop
|
|
||||||
- --accesslog.fields.headers.names.Content-Type=keep
|
|
||||||
|
|
9
ansible/util/secret-service-client.sh
Executable file
9
ansible/util/secret-service-client.sh
Executable file
|
@ -0,0 +1,9 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
pass=`secret-tool lookup ansible_vault shoarma`
|
||||||
|
retval=$?
|
||||||
|
|
||||||
|
if [ $retval -ne 0 ]; then
|
||||||
|
read -s pass
|
||||||
|
fi
|
||||||
|
echo $pass
|
61
flake.lock
61
flake.lock
|
@ -1,61 +0,0 @@
|
||||||
{
|
|
||||||
"nodes": {
|
|
||||||
"flake-utils": {
|
|
||||||
"inputs": {
|
|
||||||
"systems": "systems"
|
|
||||||
},
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1694529238,
|
|
||||||
"narHash": "sha256-zsNZZGTGnMOf9YpHKJqMSsa0dXbfmxeoJ7xHlrt+xmY=",
|
|
||||||
"owner": "numtide",
|
|
||||||
"repo": "flake-utils",
|
|
||||||
"rev": "ff7b65b44d01cf9ba6a71320833626af21126384",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "numtide",
|
|
||||||
"repo": "flake-utils",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"nixpkgs": {
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1698266953,
|
|
||||||
"narHash": "sha256-jf72t7pC8+8h8fUslUYbWTX5rKsRwOzRMX8jJsGqDXA=",
|
|
||||||
"owner": "NixOS",
|
|
||||||
"repo": "nixpkgs",
|
|
||||||
"rev": "75a52265bda7fd25e06e3a67dee3f0354e73243c",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "NixOS",
|
|
||||||
"ref": "nixpkgs-unstable",
|
|
||||||
"repo": "nixpkgs",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"root": {
|
|
||||||
"inputs": {
|
|
||||||
"flake-utils": "flake-utils",
|
|
||||||
"nixpkgs": "nixpkgs"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"systems": {
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1681028828,
|
|
||||||
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
|
||||||
"owner": "nix-systems",
|
|
||||||
"repo": "default",
|
|
||||||
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "nix-systems",
|
|
||||||
"repo": "default",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"root": "root",
|
|
||||||
"version": 7
|
|
||||||
}
|
|
20
flake.nix
20
flake.nix
|
@ -1,20 +0,0 @@
|
||||||
{
|
|
||||||
description = "A basic flake with a shell";
|
|
||||||
inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
|
|
||||||
inputs.flake-utils.url = "github:numtide/flake-utils";
|
|
||||||
|
|
||||||
outputs = { self, nixpkgs, flake-utils }:
|
|
||||||
flake-utils.lib.eachDefaultSystem (system: let
|
|
||||||
pkgs = nixpkgs.legacyPackages.${system};
|
|
||||||
in {
|
|
||||||
devShells.default = pkgs.mkShell {
|
|
||||||
packages = with pkgs; [
|
|
||||||
bashInteractive
|
|
||||||
opentofu
|
|
||||||
jq
|
|
||||||
cdrtools
|
|
||||||
ansible
|
|
||||||
];
|
|
||||||
};
|
|
||||||
});
|
|
||||||
}
|
|
|
@ -1,5 +1,5 @@
|
||||||
data "external" "secrets" {
|
data "external" "secrets" {
|
||||||
program = ["cat", pathexpand("~/.config/home/powerdns-api-key.json")]
|
program = ["cat", pathexpand("~/.tfvars.json")]
|
||||||
}
|
}
|
||||||
|
|
||||||
provider "powerdns" {
|
provider "powerdns" {
|
||||||
|
@ -7,6 +7,23 @@ provider "powerdns" {
|
||||||
api_key = data.external.secrets.result.powerdns_api_key
|
api_key = data.external.secrets.result.powerdns_api_key
|
||||||
}
|
}
|
||||||
|
|
||||||
|
resource "powerdns_record" "subdomain_pim" {
|
||||||
|
for_each = toset(["dav", "git", "meet", "rss", "latex", "md", "swarm", "traefik", "syncthing", "cloud", "pihole", "ntfy", "apprise", "uptime", "concourse", "discourse"])
|
||||||
|
zone = "pim.kunis.nl."
|
||||||
|
name = "${each.key}.pim.kunis.nl."
|
||||||
|
type = "CNAME"
|
||||||
|
records = ["www.pim.kunis.nl."]
|
||||||
|
ttl = 60
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "powerdns_record" "social_pim_kunis_nl_a" {
|
||||||
|
zone = "pim.kunis.nl."
|
||||||
|
name = "social.pim.kunis.nl."
|
||||||
|
type = "A"
|
||||||
|
records = ["84.245.14.149"]
|
||||||
|
ttl = 60
|
||||||
|
}
|
||||||
|
|
||||||
resource "powerdns_record" "kms_geokunis2_nl_a" {
|
resource "powerdns_record" "kms_geokunis2_nl_a" {
|
||||||
zone = "geokunis2.nl."
|
zone = "geokunis2.nl."
|
||||||
name = "kms.geokunis2.nl."
|
name = "kms.geokunis2.nl."
|
||||||
|
@ -54,27 +71,3 @@ resource "powerdns_record" "inbucket_geokunis2_nl_a" {
|
||||||
records = ["84.245.14.149"]
|
records = ["84.245.14.149"]
|
||||||
ttl = 60
|
ttl = 60
|
||||||
}
|
}
|
||||||
|
|
||||||
resource "powerdns_record" "smtp2go_1_geokunis2_nl_cname" {
|
|
||||||
zone = "geokunis2.nl."
|
|
||||||
name = "em670271.geokunis2.nl."
|
|
||||||
type = "CNAME"
|
|
||||||
records = ["return.smtp2go.net."]
|
|
||||||
ttl = 60
|
|
||||||
}
|
|
||||||
|
|
||||||
resource "powerdns_record" "smtp2go_2_geokunis2_nl_cname" {
|
|
||||||
zone = "geokunis2.nl."
|
|
||||||
name = "s670271._domainkey.geokunis2.nl."
|
|
||||||
type = "CNAME"
|
|
||||||
records = ["dkim.smtp2go.net."]
|
|
||||||
ttl = 60
|
|
||||||
}
|
|
||||||
|
|
||||||
resource "powerdns_record" "smtp2go_3_geokunis2_nl_cname" {
|
|
||||||
zone = "geokunis2.nl."
|
|
||||||
name = "link.geokunis2.nl."
|
|
||||||
type = "CNAME"
|
|
||||||
records = ["track.smtp2go.net."]
|
|
||||||
ttl = 60
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,62 +0,0 @@
|
||||||
terraform {
|
|
||||||
backend "pg" {
|
|
||||||
schema_name = "shoarma-elasticsearch"
|
|
||||||
}
|
|
||||||
|
|
||||||
required_providers {
|
|
||||||
elasticstack = {
|
|
||||||
source = "elastic/elasticstack"
|
|
||||||
version = "0.6.2"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
provider "elasticstack" {
|
|
||||||
elasticsearch {
|
|
||||||
endpoints = ["http://maestro.dmz:14653"]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
resource "elasticstack_elasticsearch_index_lifecycle" "metrics_ilm" {
|
|
||||||
name = "metrics_ilm"
|
|
||||||
|
|
||||||
delete {
|
|
||||||
min_age = "7d"
|
|
||||||
delete {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
resource "elasticstack_elasticsearch_index_template" "metrics_template" {
|
|
||||||
name = "metrics_template"
|
|
||||||
|
|
||||||
priority = 42
|
|
||||||
index_patterns = ["fluentd.cpu-*", "fluentd.memory-*", "fluentd.diskfree-*"]
|
|
||||||
|
|
||||||
template {
|
|
||||||
settings = jsonencode({
|
|
||||||
"index.lifecycle.name" = elasticstack_elasticsearch_index_lifecycle.metrics_ilm.name
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
resource "elasticstack_elasticsearch_index_lifecycle" "logs_ilm" {
|
|
||||||
name = "logs_ilm"
|
|
||||||
|
|
||||||
delete {
|
|
||||||
min_age = "2d"
|
|
||||||
delete {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
resource "elasticstack_elasticsearch_index_template" "logs_template" {
|
|
||||||
name = "logs_template"
|
|
||||||
|
|
||||||
priority = 42
|
|
||||||
index_patterns = ["fluentd.access.**"]
|
|
||||||
|
|
||||||
template {
|
|
||||||
settings = jsonencode({
|
|
||||||
"index.lifecycle.name" = elasticstack_elasticsearch_index_lifecycle.logs_ilm.name
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,12 +1,12 @@
|
||||||
terraform {
|
terraform {
|
||||||
backend "pg" {
|
backend "pg" {
|
||||||
schema_name = "shoarma"
|
schema_name = "shoarma"
|
||||||
|
conn_str = "postgres://terraform@10.42.0.1/terraform_state"
|
||||||
}
|
}
|
||||||
|
|
||||||
required_providers {
|
required_providers {
|
||||||
libvirt = {
|
libvirt = {
|
||||||
source = "dmacvicar/libvirt"
|
source = "dmacvicar/libvirt"
|
||||||
version = "0.7.1" # https://github.com/dmacvicar/terraform-provider-libvirt/issues/1040
|
|
||||||
}
|
}
|
||||||
|
|
||||||
powerdns = {
|
powerdns = {
|
||||||
|
@ -17,52 +17,66 @@ terraform {
|
||||||
}
|
}
|
||||||
|
|
||||||
provider "libvirt" {
|
provider "libvirt" {
|
||||||
# https://libvirt.org/uri.html#libssh-and-libssh2-transport
|
uri = "qemu+ssh://root@atlas.hyp/system"
|
||||||
uri = "qemu+ssh://root@atlas.hyp/system?known_hosts=/etc/ssh/ssh_known_hosts"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
provider "libvirt" {
|
provider "libvirt" {
|
||||||
alias = "jefke"
|
alias = "jefke"
|
||||||
uri = "qemu+ssh://root@jefke.hyp/system?known_hosts=/etc/ssh/ssh_known_hosts"
|
uri = "qemu+ssh://root@jefke.hyp/system"
|
||||||
}
|
}
|
||||||
|
|
||||||
provider "libvirt" {
|
module "manager" {
|
||||||
alias = "lewis"
|
|
||||||
uri = "qemu+ssh://root@lewis.hyp/system?known_hosts=/etc/ssh/ssh_known_hosts"
|
|
||||||
}
|
|
||||||
|
|
||||||
module "maestro" {
|
|
||||||
source = "git::https://git.kun.is/home/tf-modules.git//debian"
|
source = "git::https://git.kun.is/home/tf-modules.git//debian"
|
||||||
name = "maestro"
|
name = "maestro"
|
||||||
domain_name = "tf-maestro"
|
domain_name = "tf-maestro"
|
||||||
memory = 10240
|
memory = 1024
|
||||||
mac = "CA:FE:C0:FF:EE:08"
|
mac = "CA:FE:C0:FF:EE:08"
|
||||||
|
hypervisor_host = "atlas.hyp"
|
||||||
providers = {
|
providers = {
|
||||||
libvirt = libvirt
|
libvirt = libvirt
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module "bancomart" {
|
module "swarmpub1" {
|
||||||
source = "git::https://git.kun.is/home/tf-modules.git//debian"
|
source = "git::https://git.kun.is/home/tf-modules.git//debian"
|
||||||
name = "bancomart"
|
name = "swarmpub1"
|
||||||
domain_name = "tf-bancomart"
|
domain_name = "tf-swarmpub1"
|
||||||
memory = 10240
|
memory = 1024 * 5
|
||||||
disk_pool = "disks"
|
hypervisor_host = "atlas.hyp"
|
||||||
cloudinit_pool = "cloudinit"
|
providers = {
|
||||||
disk_base_pool = "images"
|
libvirt = libvirt
|
||||||
bridge_name = "bridgedmz"
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module "swarmpriv1" {
|
||||||
|
source = "git::https://git.kun.is/home/tf-modules.git//debian"
|
||||||
|
name = "swarmpriv1"
|
||||||
|
domain_name = "tf-swarmpriv1"
|
||||||
|
memory = 1024 * 5
|
||||||
|
hypervisor_host = "atlas.hyp"
|
||||||
|
providers = {
|
||||||
|
libvirt = libvirt
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module "swarmpub2" {
|
||||||
|
source = "git::https://git.kun.is/home/tf-modules.git//debian"
|
||||||
|
name = "swarmpub2"
|
||||||
|
domain_name = "tf-swarmpub2"
|
||||||
|
memory = 1024 * 3
|
||||||
|
hypervisor_host = "jefke.hyp"
|
||||||
providers = {
|
providers = {
|
||||||
libvirt = libvirt.jefke
|
libvirt = libvirt.jefke
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module "vpay" {
|
module "swarmpriv2" {
|
||||||
source = "git::https://git.kun.is/home/tf-modules.git//debian"
|
source = "git::https://git.kun.is/home/tf-modules.git//debian"
|
||||||
name = "vpay"
|
name = "swarmpriv2"
|
||||||
domain_name = "tf-vpay"
|
domain_name = "tf-swarmpriv2"
|
||||||
memory = 3 * 1024
|
memory = 1024 * 3
|
||||||
|
hypervisor_host = "jefke.hyp"
|
||||||
providers = {
|
providers = {
|
||||||
libvirt = libvirt.lewis
|
libvirt = libvirt.jefke
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Reference in a new issue