Initial commit

This commit is contained in:
Nathaniel Landau
2022-02-05 16:22:33 -05:00
parent 43e9f4fc59
commit 84958e0ef8
103 changed files with 10138 additions and 23 deletions

View File

@@ -0,0 +1,22 @@
{
"nodeID": "{{ inventory_hostname }}",
"nodeIP": "{{ ansible_host }}",
"nodePort": "{{ tdarr_node_port }}",
"serverIP": "{% for h in groups['lan'] if hostvars[h].is_tdarr_server == true %}{{ hostvars[h].ansible_host }}{% endfor %}",
"serverPort": "{{ tdarr_server_port }}",
{% if ansible_os_family == 'Darwin' and ansible_architecture == 'arm64' -%}
"handbrakePath": "/opt/homebrew/bin/HandBrakeCLI",
"ffmpegPath": "/opt/homebrew/bin/ffmpeg",
{% else %}
"handbrakePath": "/usr/local/bin/HandBrakeCLI",
"ffmpegPath": "/usr/local/bin/ffmpeg",
{%- endif %}
"mkvpropeditPath": "",
"pathTranslators": [
{
"server": "",
"node": ""
}
]
}

View File

@@ -0,0 +1,13 @@
{
"serverPort": "{{ tdarr_server_port }}",
"webUIPort": "{{ tdarr_webui_port }}",
"serverIP": "{% for h in groups['lan'] if hostvars[h].is_tdarr_server == true %}{{ hostvars[h].ansible_host }}{% endfor %}",
{% if ansible_os_family == 'Darwin' and ansible_architecture == 'arm64' -%}
"handbrakePath": "/opt/homebrew/bin/HandBrakeCLI",
"ffmpegPath": "/opt/homebrew/bin/ffmpeg",
{% else %}
"handbrakePath": "/usr/local/bin/HandBrakeCLI",
"ffmpegPath": "/usr/local/bin/ffmpeg"
{%- endif %}
}

128
templates/consul.hcl.j2 Normal file
View File

@@ -0,0 +1,128 @@
# ----------------------------------------- General Info
"datacenter" = "{{ datacenter_name }}" # NOTE: changing the datacenter requires generating new certificates
"node_name" = "{{ inventory_hostname }}"
"domain" = "consul"
{% if is_consul_server %}
"server" = true
"ui_config" = {
"enabled" = true
}
{% else %}
"ui_config" = {
"enabled" = false
}
{% endif %}
# ----------------------------------------- Files and Logs
{% if 'synology' in inventory_hostname %}
"data_dir" = "/consul/data"
"log_file" = "/consul/data/logs/consul.log"
{% else %}
"data_dir" = "{{ consul_opt_dir }}"
"log_file" = "{{ consul_opt_dir }}/logs/consul.log"
{% endif %}
"log_level" = "warn"
"log_rotate_max_files" = 5
"enable_syslog" = false
# ----------------------------------------- Networking
"addresses" = {
"dns" = "0.0.0.0"
"grpc" = "0.0.0.0"
"http" = "0.0.0.0"
"https" = "0.0.0.0"
}
"ports" = {
"dns" = 8600
"http" = 8500
"server" = 8300
}
{% if 'linode' in group_names %}
"advertise_addr" = "{{ linode_private_ip }}"
"bind_addr" = "{{ linode_private_ip }}"
"client_addr" = "{{ linode_private_ip }} {{ '{{' }} GetInterfaceIP \"docker0\" {{ '}}' }}"
{% elif 'synology' in inventory_hostname %}
"advertise_addr" = "{{ synology_second_ip }}"
"bind_addr" = "{{ synology_second_ip }}"
"client_addr" = "{{ synology_second_ip }} {{ '{{' }} GetInterfaceIP \"docker0\" {{ '}}' }}"
{% else %}
"advertise_addr" = "{{ ansible_default_ipv4.address }}"
"bind_addr" = "{{ ansible_default_ipv4.address }}"
"client_addr" = "{{ ansible_default_ipv4.address }} {{ '{{' }} GetInterfaceIP \"docker0\" {{ '}}' }}"
{% endif %}
"retry_interval" = "30s"
"retry_interval_wan" = "30s"
{% if 'linode' in group_names %}
"retry_join" = [{% for h in groups['linode-cluster'] if hostvars[h].is_consul_server == true %}"{{ hostvars[h].linode_private_ip }}"{% if not loop.last %}, {% endif %}{% endfor %}]
{% else %}
"retry_join" = [{% for h in groups['lan'] if hostvars[h].is_consul_server == true %}"{{ hostvars[h].ansible_host }}"{% if not loop.last %}, {% endif %}{% endfor %}]
{% if is_consul_server %}
{% if 'linode' in group_names %}
"join_wan" = [{% for h in groups['linode-cluster'] if hostvars[h].is_consul_server == true %}"{{ hostvars[h].ansible_host }}"{% if not loop.last %}, {% endif %}{% endfor %}]
{% endif %}
{% endif %}
{% endif %}
# ----------------------------------------- Security
"encrypt" = "{{ consul_encryprion_key }}"
{% if is_consul_server %} {# Consul Servers #}
"verify_incoming" = true
"verify_outgoing" = true
"verify_server_hostname" = true
{% if 'synology' in inventory_hostname %} {# necessary, since running in docker container #}
"ca_file" = "/consul/data/certs/consul-agent-ca.pem"
"cert_file" = "/consul/data/certs/{{ datacenter_name }}-server-consul-0.pem"
"key_file" = "/consul/data/certs/{{ datacenter_name }}-server-consul-0-key.pem"
{% else %}
"ca_file" = "{{ consul_opt_dir }}/certs/consul-agent-ca.pem"
"cert_file" = "{{ consul_opt_dir }}/certs/{{ datacenter_name }}-server-consul-0.pem"
"key_file" = "{{ consul_opt_dir }}/certs/{{ datacenter_name }}-server-consul-0-key.pem"
{% endif %}
"auto_encrypt" = {
"allow_tls" = true
}
{% else %} {# Consul Clients #}
"verify_incoming" = false
"verify_outgoing" = true
"verify_server_hostname" = true
{% if 'synology' in inventory_hostname %} {# necessary, since running in docker container #}
"ca_file" = "/consul/data/certs/consul-agent-ca.pem"
{% else %}
"ca_file" = "{{ consul_opt_dir }}/certs/consul-agent-ca.pem"
{% endif %}
"auto_encrypt" = {
"tls" = true
}
{% endif %}
"acl" = {
enabled = false
default_policy = "allow"
enable_token_persistence = true
}
# ----------------------------------------- Cluster Operations
{% if is_cluster_leader is defined %}
{% if is_cluster_leader %}
"bootstrap" = true
{% endif %}
{% endif %}
"disable_update_check" = false
"enable_local_script_checks" = false
"enable_script_checks" = false
"skip_leave_on_interrupt" = true
"leave_on_terminate" = false
"primary_datacenter" = "{{ datacenter_name }}"
"performance" = {
"leave_drain_time" = "5s"
"raft_multiplier" = 1
"rpc_hold_timeout" = "7s"
}
{# telemetry = {
"dogstatsd_addr" = "localhost:8125"
"disable_hostname" = true
"disable_compat_1.9" = true
} #}

View File

@@ -0,0 +1,32 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>EnvironmentVariables</key>
<dict>
<key>PATH</key>
<string>/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/opt/X11/bin:/usr/local/sbin</string>
</dict>
<key>KeepAlive</key>
<dict>
<key>PathState</key>
<dict>
<key>{{ mac_keep_alive_file }}</key>
<true/>
</dict>
<key>SuccessfulExit</key>
<true/>
</dict>
<key>Label</key>
<string>com.{{ my_username }}.consul</string>
<key>ProgramArguments</key>
<array>
<string>/usr/local/bin/consul</string>
<string>agent</string>
<string>-config-dir</string>
<string>{{ interpolated_consul_configuration_dir }}</string>
</array>
<key>RunAtLoad</key>
<true/>
</dict>
</plist>

View File

@@ -0,0 +1,21 @@
[Unit]
Description="HashiCorp Consul - A service mesh solution"
Documentation=https://www.consul.io/
Requires=network-online.target
After=network-online.target
After=docker.service
Requires=docker.service
ConditionFileNotEmpty={{ interpolated_consul_configuration_dir }}/consul.hcl
[Service]
Type=notify
User=consul
Group=consul
ExecStart=/usr/local/bin/consul agent -config-dir={{ interpolated_consul_configuration_dir }}
ExecReload=/usr/local/bin/consul reload
KillMode=process
Restart=on-failure
LimitNOFILE=65536
[Install]
WantedBy=multi-user.target

View File

@@ -0,0 +1,67 @@
{
"services": [{
"name": "sabnzbd",
"id": "sabnzbd",
"tags": [
"traefik.enable=true",
"traefik.http.services.sabnzbd.loadbalancer.server.port=8080",
"traefik.http.routers.sabnzbd.rule=Host(`sab.{{ homelab_domain_name }}`)",
"traefik.http.routers.sabnzbd.entryPoints=web,websecure",
"traefik.http.routers.sabnzbd.service=sabnzbd",
"traefik.http.routers.sabnzbd.tls=true",
"traefik.http.routers.sabnzbd.tls.certresolver=cloudflare",
"traefik.http.routers.sabnzbd.middlewares=authelia@file"
],
"checks": [{
"id": "sabnzbd-http-check",
"http": "http://{{ synology_second_ip }}:8080",
"interval": "30s",
"timeout": "5s",
"success_before_passing": 3,
"failures_before_critical": 3
}]
},
{
"name": "synology",
"id": "synology",
"tags": [
"traefik.enable=true",
"traefik.http.services.synology.loadbalancer.server.port=5000",
"traefik.http.routers.synology.rule=Host(`nas.{{ homelab_domain_name }}`)",
"traefik.http.routers.synology.entryPoints=web,websecure",
"traefik.http.routers.synology.service=synology",
"traefik.http.routers.synology.tls=true",
"traefik.http.routers.synology.tls.certresolver=cloudflare"
],
"checks": [{
"id": "synology-http-check",
"http": "http://{{ synology_second_ip }}:5000",
"interval": "30s",
"timeout": "5s",
"success_before_passing": 3,
"failures_before_critical": 3
}]
},
{
"name": "asntoip",
"id": "asntoip",
"tags": [
"traefik.enable=true",
"traefik.http.services.asntoip.loadbalancer.server.port=5151",
"traefik.http.routers.asntoip.rule=Host(`asntoip.{{ homelab_domain_name }}`)",
"traefik.http.routers.asntoip.entryPoints=web,websecure",
"traefik.http.routers.asntoip.service=asntoip",
"traefik.http.routers.asntoip.tls=true",
"traefik.http.routers.asntoip.tls.certresolver=cloudflare"
],
"checks": [{
"id": "asntoip-http-check",
"http": "http://{{ synology_second_ip }}:5151",
"interval": "30s",
"timeout": "5s",
"success_before_passing": 3,
"failures_before_critical": 3
}]
}
]
}

View File

@@ -0,0 +1,25 @@
{
"services": [{
"name": "tdarr",
"id": "tdarr",
"tags": [
"traefik.enable=true",
"traefik.http.services.tdarr.loadbalancer.server.port={{ tdarr_webui_port }}",
"traefik.http.routers.tdarr.rule=Host(`tdarr.{{ homelab_domain_name }}`)",
"traefik.http.routers.tdarr.entryPoints=web,websecure",
"traefik.http.routers.tdarr.service=tdarr",
"traefik.http.routers.tdarr.tls=true",
"traefik.http.routers.tdarr.tls.certresolver=cloudflare",
"traefik.http.routers.tdarr.middlewares=authelia@file"
],
"checks": [{
"id": "tdarr-http-check",
"http": "http://{{ ansible_host }}:{{ tdarr_webui_port }}",
"interval": "30s",
"timeout": "30s",
"success_before_passing": 3,
"failures_before_critical": 3
}]
}
]
}

View File

@@ -0,0 +1,11 @@
version: '3.9'
services:
asn-to-ip:
image: ddimick/asn-to-ip:latest
hostname: asn-to-ip
container_name: asn-to-ip
network_mode: "bridge"
ports:
- 5151:5000
restart: unless-stopped

View File

@@ -0,0 +1,13 @@
version: '3.9'
services:
consul:
image: consul:latest
hostname: consul
container_name: consul
network_mode: "host"
volumes:
- /volume1/docker/consul/data:/consul/data
- /volume1/docker/consul/config:/consul/config
command: consul agent -config-dir=/consul/config
restart: unless-stopped

View File

@@ -0,0 +1,18 @@
version: "3.9"
services:
diun:
image: ghcr.io/crazy-max/diun
hostname: diun
container_name: diun
network_mode: "bridge"
environment:
- "TZ=America/New_York"
- "DIUN_WATCH_SCHEDULE=26 */48 * * *"
- "DIUN_PROVIDERS_DOCKER_WATCHBYDEFAULT=true"
- "DIUN_NOTIF_PUSHOVER_TOKEN={{ pushover_token }}"
- "DIUN_NOTIF_PUSHOVER_RECIPIENT={{ pushover_recipient }}"
- "DIUN_WATCH_FIRSTCHECKNOTIF=false"
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
restart: unless-stopped

View File

@@ -0,0 +1,17 @@
version: '3.9'
services:
plex:
image: ghcr.io/linuxserver/plex:latest
hostname: plex
container_name: plex
network_mode: "host"
environment:
- "TZ=America/New_York"
- "PGID=101"
- "PUID={{ ansible_user_uid }}"
- "VERSION=docker"
volumes:
- /volume1/media/media:/data/media
- /volume1/docker/plex:/config
restart: unless-stopped

View File

@@ -0,0 +1,14 @@
version: '3.9'
services:
promtail:
image: grafana/promtail
hostname: promtail
container_name: promtail
ports:
- 9080:9080
network_mode: "bridge"
volumes:
- /volume1/docker/promtail/config.yml:/etc/promtail/config.yml
- /var/log:/var/log:ro
restart: unless-stopped

View File

@@ -0,0 +1,23 @@
version: '3.9'
services:
sabnzbd:
image: ghcr.io/linuxserver/sabnzbd
hostname: sabnzbd
container_name: sabnzbd
network_mode: "bridge"
environment:
- "TZ=America/New_York"
- "PGID=101"
- "PUID={{ ansible_user_uid }}"
volumes:
- /var/services/homes/{{ my_username }}:/{{ my_username }}
- /volume1/nate:/nate
- /volume1/media/downloads/nzb:/nzbd
- /volume1/media/downloads/temp:/incomplete-downloads
- /volume1/media/downloads/complete:/downloads
- /volume1/docker/sabnzbd:/config
ports:
- 8080:8080
- 9090:9090
restart: unless-stopped

View File

@@ -0,0 +1,29 @@
---
version: '3.9'
services:
tdarr_node:
image: haveagitgat/tdarr_node:latest
hostname: tdarr_node
container_name: tdarr_node
network_mode: "bridge"
environment:
- "nodeID={{ inventory_hostname }}"
- "nodeIP={{ ansible_host }}"
- "nodePort={{ tdarr_node_port }}"
- "serverIP={% for h in groups['lan'] if hostvars[h].is_tdarr_server == true %}{{ hostvars[h].ansible_host }}{% endfor %}"
- "serverPort={{ tdarr_server_port }}"
- "TZ=America/New_York"
- "PGID=101"
- "PUID={{ ansible_user_uid }}"
volumes:
- /volume1/docker/tdarr_node:/app/configs
- /volume1/media/media/movies:/movies
- /volume1/media/tdarr_tmp:/tdarr_tmp
- /volume1/media/tdarr_complete:/tdarr_complete
ports:
- {{ tdarr_node_port }}:{{ tdarr_node_port }}
devices:
- /dev/dri:/dev/dri
privileged: true
restart: unless-stopped

View File

@@ -0,0 +1,14 @@
version: '3.9'
services:
telegraf:
image: nuntz/telegraf-snmp:latest
hostname: telegraf
container_name: nuntz-telegraf-snmp
network_mode: "host"
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
- /volume1/docker/telegraf/mibs:/usr/share/snmp/mibs
- /volume1/docker/telegraf/logs:/var/logs/telegraf
- /volume1/docker/telegraf/config:/etc/telegraf
restart: unless-stopped

217
templates/nomad.hcl.j2 Normal file
View File

@@ -0,0 +1,217 @@
# ----------------------------------------- General Info
name = "{{ inventory_hostname }}"
region = "global"
datacenter = "{{ datacenter_name }}"
# ----------------------------------------- Files and Logs
data_dir = "{{ nomad_opt_dir_location }}"
plugin_dir = "{{ nomad_opt_dir_location }}/plugins"
log_level = "warn"
log_file = "{{ nomad_opt_dir_location }}/logs/nomad.log"
log_rotate_max_files = 5
enable_syslog = false
# ----------------------------------------- Networking
bind_addr = "0.0.0.0" # the default
advertise {
{% if 'linode' in group_names %}
http = "{{ linode_private_ip }}:4646"
rpc = "{{ linode_private_ip }}:4647"
serf = "{{ linode_private_ip }}:4648" # non-default ports may be specified
{% elif 'synology' in group_names %}
http = "{{ synology_second_ip }}:4646"
rpc = "{{ synology_second_ip }}:4647"
serf = "{{ synology_second_ip }}:4648" # non-default ports may be specified
{% else %}
http = "{{ ansible_host }}:4646"
rpc = "{{ ansible_host }}:4647"
serf = "{{ ansible_host }}:4648" # non-default ports may be specified
{% endif %}
}
# ----------------------------------------- Consul Integration
consul {
{% if 'linode' in group_names %}
address = "{{ linode_private_ip }}:8500"
{% elif 'synology' in group_names %}
address = "{{ synology_second_ip }}:8500"
{% else %}
address = "{{ ansible_host }}:8500"
{% endif %}
server_service_name = "nomad-servers"
client_service_name = "nomad-clients"
auto_advertise = true
server_auto_join = true
client_auto_join = true
{% if is_nomad_server %}
tags = [
"traefik.enable=true",
"traefik.http.routers.nomad-server.entryPoints=web,websecure",
"traefik.http.routers.nomad-server.service=nomad-server",
"traefik.http.routers.nomad-server.rule=Host(`nomad.{{ homelab_domain_name }}`)",
"traefik.http.routers.nomad-server.tls=true",
"traefik.http.routers.nomad-server.middlewares=authelia@file,redirectScheme@file",
"traefik.http.services.nomad-server.loadbalancer.server.port=4646"
]
{% endif %}
}
# ----------------------------------------- CLient Config
client {
enabled = true
{% if 'pis' in group_names %}
node_class = "rpi"
{% elif 'macs' in group_names %}
node_class = "mac"
{% elif 'synology' in group_names %}
node_class = "synology"
{% endif %}
reserved {
cpu = 250
memory = 100
reserved_ports = "22"
}
{% if not is_nomad_server %}
{% if 'linode' in group_names %}
server_join {
retry_join = [{% for h in groups['linode'] if hostvars[h].is_nomad_server == true %}"{{ hostvars[h].ansible_host }}"{% if not loop.last %}, {% endif %}{% endfor %}]
retry_max = 3
retry_interval = "15s"
}
{% else %}
server_join {
retry_join = [{% for h in groups['lan'] if hostvars[h].is_nomad_server == true %}"{{ hostvars[h].ansible_host }}"{% if not loop.last %}, {% endif %}{% endfor %}]
retry_max = 3
retry_interval = "15s"
}
{% endif %}
{% endif %}
meta {
# These are variables that can be used in Nomad job files
PUID = "{{ ansible_user_uid }}"
PGID = "{{ ansible_user_gid }}"
nfsStorageRoot = "{{ interpolated_nfs_service_storage }}"
localStorageRoot = "{{ interpolated_localfs_service_storage }}"
{% if 'macs' in group_names %}
restoreCommand = "/usr/local/bin/service_restore"
restoreCommand1 = "--verbose"
restoreCommand2 = "--job"
restoreCommand3 = ""
backupCommand = "/usr/local/bin/service_backups"
backupCommandArg1 = "--verbose"
backupCommandArg2 = "--loglevel=INFO"
backupCommandArg3 = ""
backupAllocArg1 = "--verbose"
backupAllocArg2 = "--loglevel=INFO"
backupAllocArg3 = "--allocation"
backupAllocArg4 = "--delete"
backupAllocArg5 = "--job"
backupAllocArg6 = ""
{% else %}
restoreCommand = "sudo"
restoreCommand1 = "/usr/local/bin/service_restore"
restoreCommand2 = "--job"
restoreCommand3 = "--verbose"
backupCommand = "sudo"
backupCommandArg1 = "/usr/local/bin/service_backups"
backupCommandArg2 = "--verbose"
backupCommandArg3 = "--loglevel=INFO"
backupAllocArg1 = "/usr/local/bin/service_backups"
backupAllocArg2 = "--verbose"
backupAllocArg3 = "--loglevel=INFO"
backupAllocArg4 = "--allocation"
backupAllocArg5 = "--job"
backupAllocArg6 = "--delete"
{% endif %}
}
} # /client
{% if is_nomad_server %}
# ----------------------------------------- Server Config
server {
enabled = true
encrypt = "{{ nomad_encryption_key }}"
{% if 'linode' in group_names %}
bootstrap_expect = 1
{% else %}
bootstrap_expect = 3
{% endif %}
node_gc_threshold = "15m"
job_gc_interval = "15m"
job_gc_threshold = "6h"
heartbeat_grace = "60s"
min_heartbeat_ttl = "20s"
raft_protocol = "3"
server_join {
retry_join = [{% for h in groups['lan'] if hostvars[h].is_nomad_server == true %}"{{ hostvars[h].ansible_host }}"{% if not loop.last %}, {% endif %}{% endfor %}]
retry_max = 3
retry_interval = "15s"
}
}
autopilot {
cleanup_dead_servers = true
last_contact_threshold = "200ms"
max_trailing_logs = 250
server_stabilization_time = "10s"
enable_redundancy_zones = false
disable_upgrade_migration = false
enable_custom_upgrades = false
}
{% endif %}
{% if is_nomad_server and is_nomad_client %}
client {
enabled = true
}
{% endif %}
# ----------------------------------------- Telemety
telemetry = {
publish_allocation_metrics = true
publish_node_metrics = true
collection_interval = "10s"
filter_default = false
datadog_address = "localhost:8125"
prefix_filter = [
"+nomad.client.allocations.running",
"+nomad.client.allocations.terminal",
"+nomad.client.allocs.cpu.allocated",
"+nomad.client.allocs.cpu.total_percent",
"+nomad.client.allocs.memory.allocated",
"+nomad.client.allocs.memory.swap",
"+nomad.client.allocs.memory.usage",
"+nomad.nomad.job_status.dead",
"+nomad.nomad.job_status.running",
"+nomad.nomad.job_status.pending",
"+nomad.nomad.job_summary.running",
"+nomad.nomad.job_summary.complete",
"+nomad.nomad.job_summary.lost",
"+nomad.nomad.job_summary.failed"]
}
# ----------------------------------------- Plugins
plugin "raw_exec" {
config {
enabled = true
}
}
plugin "docker" {
config {
allow_caps = [ "ALL" ]
allow_privileged = true
volumes {
enabled = true
}
}
}

View File

@@ -0,0 +1,36 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>EnvironmentVariables</key>
<dict>
<key>PATH</key>
<string>/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/opt/X11/bin:/usr/local/sbin</string>
</dict>
<key>KeepAlive</key>
<dict>
<key>PathState</key>
<dict>
<key>{{ mac_keep_alive_file }}</key>
<true/>
</dict>
<key>SuccessfulExit</key>
<true/>
</dict>
<key>Label</key>
<string>com.{{ my_username }}.nomad</string>
<key>ProgramArguments</key>
<array>
<string>/usr/local/bin/nomad</string>
<string>agent</string>
<string>-config</string>
<string>{{ nomad_configuration_dir }}</string>
</array>
<key>RunAtLoad</key>
<true/>
<key>StandardErrorPath</key>
<string>/usr/local/var/log/nomad.log</string>
<key>StandardOutPath</key>
<string>/usr/local/var/log/nomad.log</string>
</dict>
</plist>

View File

@@ -0,0 +1,25 @@
[Unit]
Description=Nomad
Documentation=https://nomadproject.io/docs/
Wants=network-online.target
After=network-online.target
ConditionFileNotEmpty={{ nomad_configuration_dir }}/nomad.hcl
[Service]
{# {% if 'linode' in group_names %} #}
User=nomad
Group=nomad
{# {% endif %} #}
ExecReload=/bin/kill -HUP $MAINPID
ExecStart=/usr/local/bin/nomad agent -config {{ nomad_configuration_dir }}
KillMode=process
KillSignal=SIGINT
LimitNOFILE=infinity
LimitNPROC=infinity
Restart=on-failure
RestartSec=2
StartLimitBurst=3
TasksMax=infinity
[Install]
WantedBy=multi-user.target

View File

@@ -0,0 +1,21 @@
job "backup_local_filesystems" {
region = "global"
datacenters = ["{{ datacenter_name }}"]
type = "sysbatch"
periodic {
cron = "0 */8 * * * *"
prohibit_overlap = true
time_zone = "America/New_York"
}
task "do_backups" {
driver = "raw_exec"
config {
# When running a binary that exists on the host, the path must be absolute
command = "${meta.backupCommand}"
args = ["${meta.backupCommandArg1}", "${meta.backupCommandArg2}", "${meta.backupCommandArg3}"]
}
} // /task do_backups
} //job

View File

@@ -0,0 +1,88 @@
job "changedetection" {
region = "global"
datacenters = ["{{ datacenter_name }}"]
type = "service"
// constraint {
// attribute = "${node.unique.name}"
// operator = "regexp"
// value = "rpi(1|2|3)"
// }
update {
max_parallel = 1
health_check = "checks"
min_healthy_time = "10s"
healthy_deadline = "5m"
progress_deadline = "10m"
auto_revert = true
canary = 0
stagger = "30s"
}
group "changedetection" {
count = 1
restart {
attempts = 0
delay = "30s"
}
network {
port "webUI" {
to = "5000"
}
}
task "changedetection" {
env {
PUID = "${meta.PUID}"
PGID = "${meta.PGID}"
BASE_URL = "https://changes.{{ homelab_domain_name }}"
}
driver = "docker"
config {
image = "dgtlmoon/changedetection.io:latest"
hostname = "${NOMAD_JOB_NAME}"
volumes = [
"${meta.nfsStorageRoot}/pi-cluster/changedetection:/datastore"
]
ports = ["webUI"]
} // docker config
service {
port = "webUI"
name = "${NOMAD_JOB_NAME}"
tags = [
"traefik.enable=true",
"traefik.http.routers.${NOMAD_JOB_NAME}.rule=Host(`changes.{{ homelab_domain_name }}`)",
"traefik.http.routers.${NOMAD_JOB_NAME}.entryPoints=web,websecure",
"traefik.http.routers.${NOMAD_JOB_NAME}.service=${NOMAD_JOB_NAME}",
"traefik.http.routers.${NOMAD_JOB_NAME}.tls=true",
"traefik.http.routers.${NOMAD_JOB_NAME}.tls.certresolver=cloudflare"
]
check {
type = "http"
path = "/"
interval = "30s"
timeout = "4s"
}
check_restart {
limit = 0
grace = "1m"
ignore_warnings = true
}
} // service
resources {
cpu = 100 # MHz
memory = 150 # MB
} // resources
} // task changedetection
} // group
} // job

View File

@@ -0,0 +1,109 @@
job "chronograf" {
region = "global"
datacenters = ["{{ datacenter_name }}"]
type = "service"
// constraint {
// attribute = "${node.unique.name}"
// operator = "regexp"
// value = "rpi(1|2|3)"
// }
update {
max_parallel = 1
health_check = "checks"
min_healthy_time = "10s"
healthy_deadline = "5m"
progress_deadline = "10m"
auto_revert = true
canary = 0
stagger = "30s"
}
group "chronograf" {
restart {
attempts = 0
delay = "30s"
}
network {
port "chronografPort" {
to = "8888"
}
}
task "await-influxdb" {
driver = "docker"
config {
image = "busybox:latest"
command = "sh"
args = [
"-c",
"echo -n 'Waiting for influxdb.service.consul to come alive'; until nslookup influxdb.service.consul 2>&1 >/dev/null; do echo '.'; sleep 2; done"
]
network_mode = "host"
}
resources {
cpu = 200
memory = 128
}
lifecycle {
hook = "prestart"
sidecar = false
}
} // /task
task "chronograf" {
// env {
// KEY = "VALUE"
// }
driver = "docker"
config {
image = "chronograf:latest"
hostname = "${NOMAD_JOB_NAME}"
ports = ["chronografPort"]
} // docker config
service {
port = "chronografPort"
name = "${NOMAD_JOB_NAME}"
tags = [
"traefik.enable=true",
"traefik.http.routers.${NOMAD_JOB_NAME}.rule=Host(`${NOMAD_JOB_NAME}.{{ homelab_domain_name }}`)",
"traefik.http.routers.${NOMAD_JOB_NAME}.entryPoints=web,websecure",
"traefik.http.routers.${NOMAD_JOB_NAME}.service=${NOMAD_JOB_NAME}",
"traefik.http.routers.${NOMAD_JOB_NAME}.tls=true",
"traefik.http.routers.${NOMAD_JOB_NAME}.tls.certresolver=cloudflare"
]
check {
type = "tcp"
port = "chronografPort"
interval = "30s"
timeout = "4s"
}
check_restart {
limit = 0
grace = "1m"
ignore_warnings = true
}
} // service
// resources {
// cpu = 40 # MHz
// memory = 10 # MB
// } // resources
} // task
} // group
} // job

View File

@@ -0,0 +1,100 @@
job "code" {
region = "global"
datacenters = ["{{ datacenter_name }}"]
type = "service"
// constraint {
// attribute = "${node.unique.name}"
// operator = "regexp"
// value = "rpi(1|2|3)"
// }
update {
max_parallel = 1
health_check = "checks"
min_healthy_time = "10s"
healthy_deadline = "5m"
progress_deadline = "10m"
auto_revert = true
canary = 0
stagger = "30s"
}
group "code" {
count = 1
restart {
attempts = 0
delay = "30s"
}
network {
port "port1" {
// static = "80"
to = "3000"
}
}
task "code" {
env {
PUID = "${meta.PUID}"
PGID = "${meta.PGID}"
TZ = "America/New_York"
SUDO_PASSWORD = "{{ simple_web_password }}"
PROXY_DOMAIN = "code.{{ homelab_domain_name }}"
CONNECTION_TOKEN = "1234"
DOCKER_MODS = "linuxserver/mods:code-server-python3|linuxserver/mods:code-server-shellcheck|linuxserver/mods:universal-git|linuxserver/mods:code-server-zsh"
// CONNECTION_TOKEN = supersecrettoken
// CONNECTION_SECRET = supersecrettoken
}
driver = "docker"
config {
image = "lscr.io/linuxserver/openvscode-server"
hostname = "${NOMAD_JOB_NAME}"
volumes = [
"${meta.nfsStorageRoot}/pi-cluster/${NOMAD_JOB_NAME}:/config"
]
ports = ["port1"]
} // docker config
service {
port = "port1"
name = "${NOMAD_JOB_NAME}"
tags = [
"traefik.enable=true",
"traefik.http.routers.${NOMAD_JOB_NAME}.rule=Host(`${NOMAD_JOB_NAME}.{{ homelab_domain_name }}`)",
"traefik.http.routers.${NOMAD_JOB_NAME}.entryPoints=web,websecure",
"traefik.http.routers.${NOMAD_JOB_NAME}.service=${NOMAD_JOB_NAME}",
"traefik.http.routers.${NOMAD_JOB_NAME}.tls=true",
"traefik.http.routers.${NOMAD_JOB_NAME}.tls.certresolver=cloudflare",
"traefik.http.routers.${NOMAD_JOB_NAME}.middlewares=authelia@file,redirectScheme@file"
]
check {
type = "tcp"
port = "port1"
interval = "30s"
timeout = "4s"
}
check_restart {
limit = 0
grace = "1m"
ignore_warnings = true
}
} // service
resources {
cpu = 1500 # MHz
memory = 300 # MB
} // resources
} // task
} // group
} // job

View File

@@ -0,0 +1,64 @@
job "diagnostics" {
region = "global"
datacenters = ["{{ datacenter_name }}"]
type = "service"
// constraint {
// attribute = "${node.unique.name}"
// operator = "regexp"
// value = "rpi(1|2|3)"
// }
group "diagnostics" {
count = 1
restart {
attempts = 0
delay = "30s"
}
task "diagnostics" {
// env {
// KEY = "VALUE"
// }
driver = "docker"
config {
image = "alpine:latest"
hostname = "${NOMAD_JOB_NAME}"
args = [
"/bin/sh",
"-c",
"chmod 755 /local/bootstrap.sh && /local/bootstrap.sh"
]
volumes = [
"${meta.nfsStorageRoot}/pi-cluster/backups/config_backups:/backups",
"${meta.localStorageRoot}:/docker"
]
} // docker config
template {
destination = "local/bootstrap.sh"
data = <<EOH
#!/bin/sh
apk update
apk add --no-cache bash
apk add --no-cache bind-tools
apk add --no-cache curl
apk add --no-cache git
apk add --no-cache jq
apk add --no-cache openssl
apk add --no-cache iperf3
apk add --no-cache nano
apk add --no-cache wget
tail -f /dev/null # Keep container running
EOH
}
} // tasks
} // group
} // job

View File

@@ -0,0 +1,41 @@
job "diun" {
region = "global"
datacenters = ["{{ datacenter_name }}"]
type = "system"
group "diun" {
restart {
attempts = 0
delay = "30s"
}
task "diun" {
env {
// DIUN_PROVIDERS_DOCKER_ENDPOINT = "unix:///var/run/docker.sock"
DIUN_NOTIF_PUSHOVER_RECIPIENT = "{{ pushover_recipient }}"
DIUN_NOTIF_PUSHOVER_TOKEN = "{{ pushover_token }}"
DIUN_PROVIDERS_DOCKER_WATCHBYDEFAULT = "true"
DIUN_WATCH_FIRSTCHECKNOTIF = "false"
DIUN_WATCH_SCHEDULE = "26 */48 * * *"
TZ = "America/New_York"
}
driver = "docker"
config {
image = "crazymax/diun:latest"
hostname = "${NOMAD_JOB_NAME}"
volumes = [
"/var/run/docker.sock:/var/run/docker.sock"
]
} // docker config
// resources {
// cpu = 100 # MHz
// memory = 300 # MB
// } // resources
} // task diun
} // group
} // job

View File

@@ -0,0 +1,120 @@
job "grafana" {
region = "global"
datacenters = ["{{ datacenter_name }}"]
type = "service"
update {
max_parallel = 1
health_check = "checks"
min_healthy_time = "10s"
healthy_deadline = "5m"
progress_deadline = "10m"
auto_revert = true
canary = 0
stagger = "30s"
}
group "grafana" {
count = 1
restart {
attempts = 0
delay = "30s"
}
network {
port "http" {}
}
task "grafana" {
env {
GF_PATHS_CONFIG = "/local/grafana.ini"
}
driver = "docker"
config {
image = "grafana/grafana:latest"
hostname = "${NOMAD_JOB_NAME}"
ports = ["http"]
volumes = ["${meta.nfsStorageRoot}/pi-cluster/grafana:/var/lib/grafana"]
} // docker config
template {
destination = "local/grafana.ini"
data = <<EOH
[server]
domain = grafana.{{ homelab_domain_name }}
{% raw %}http_port = {{ env "NOMAD_PORT_http" }}{% endraw +%}
[analytics]
reporting_enabled = false
[security]
admin_user = {{ my_username }}
admin_password = {{ grafana_admin_password }}
cookie_secure = true
[users]
allow_sign_up = false
allow_org_create = false
[smtp]
enabled = true
host = {{ email_smtp_host }}:{{ email_smtp_port}}
user = {{ email_smtp_account }}
password = {{ grafana_smtp_password }}
skip_verify = true
from_address = {{ my_email_address }}
from_name = Grafana
[log.file]
level = info
[date_formats]
default_timezone = America/New_York
[auth.proxy]
enabled = true
header_name = Remote-User
header_property = username
auto_sign_up = false
sync_ttl = 60
EOH
}
service {
port = "http"
name = "${NOMAD_JOB_NAME}"
tags = [
"traefik.enable=true",
"traefik.http.routers.${NOMAD_JOB_NAME}.rule=Host(`${NOMAD_JOB_NAME}.{{ homelab_domain_name }}`)",
"traefik.http.routers.${NOMAD_JOB_NAME}.entryPoints=web,websecure",
"traefik.http.routers.${NOMAD_JOB_NAME}.service=${NOMAD_JOB_NAME}",
"traefik.http.routers.${NOMAD_JOB_NAME}.tls=true",
"traefik.http.routers.${NOMAD_JOB_NAME}.tls.certresolver=cloudflare",
"traefik.http.middlewares.${NOMAD_JOB_NAME}_logout_redirect.redirectregex.regex=${NOMAD_JOB_NAME}\\.{{ homelab_domain_name }}/logout$",
"traefik.http.middlewares.${NOMAD_JOB_NAME}_logout_redirect.redirectregex.replacement=authelia.{{ homelab_domain_name }}/logout",
"traefik.http.routers.${NOMAD_JOB_NAME}.middlewares=authelia@file,${NOMAD_JOB_NAME}_logout_redirect"
]
check {
type = "http"
port = "http"
path = "/"
interval = "90s"
timeout = "15s"
}
check_restart {
limit = 0
grace = "1m"
ignore_warnings = true
}
} // service
resources {
cpu = 200 # MHz
memory = 60 # MB
} // resources
} // task grafana
} // group
} // job

View File

@@ -0,0 +1,88 @@
job "headless-chrome" {
region = "global"
datacenters = ["{{ datacenter_name }}"]
type = "service"
constraint {
attribute = "${attr.cpu.arch}"
value = "amd64"
}
update {
max_parallel = 1
health_check = "checks"
min_healthy_time = "10s"
healthy_deadline = "5m"
progress_deadline = "10m"
auto_revert = true
canary = 0
stagger = "30s"
}
group "headless-chrome" {
count = 1
restart {
attempts = 0
delay = "30s"
}
network {
port "port1" {
static = "9222"
to = "9222"
}
}
task "headless-chrome" {
// env {
// PUID = "${meta.PUID}"
// PGID = "${meta.PGID}"
// }
driver = "docker"
config {
image = "alpeware/chrome-headless-trunk:latest"
hostname = "${NOMAD_JOB_NAME}"
ports = ["port1"]
} // docker config
service {
port = "port1"
name = "${NOMAD_JOB_NAME}"
tags = [
"traefik.enable=true",
"traefik.http.routers.${NOMAD_JOB_NAME}.rule=Host(`chrome.{{ homelab_domain_name }}`)",
"traefik.http.routers.${NOMAD_JOB_NAME}.entryPoints=web,websecure",
"traefik.http.routers.${NOMAD_JOB_NAME}.service=${NOMAD_JOB_NAME}",
"traefik.http.routers.${NOMAD_JOB_NAME}.tls=true",
"traefik.http.routers.${NOMAD_JOB_NAME}.tls.certresolver=cloudflare"
]
check {
type = "tcp"
port = "port1"
interval = "30s"
timeout = "4s"
}
check_restart {
limit = 0
grace = "1m"
ignore_warnings = true
}
} // service
// resources {
// cpu = 100 # MHz
// memory = 300 # MB
// } // resources
} // task
} // group
} // job

View File

@@ -0,0 +1,113 @@
job "influxdb" {
region = "global"
datacenters = ["{{ datacenter_name }}"]
type = "service"
// constraint {
// attribute = "${node.unique.name}"
// operator = "regexp"
// value = "rpi"
// }
update {
max_parallel = 1
health_check = "checks"
min_healthy_time = "10s"
healthy_deadline = "5m"
progress_deadline = "10m"
auto_revert = true
canary = 0
stagger = "30s"
}
group "influxdbGroup" {
count = 1
network {
port "httpAPI" {
static = "{{ influxdb_port }}"
to = "8086"
}
}
restart {
attempts = 0
delay = "30s"
}
task "create_filesystem" {
// Copy the most recent backup into place on the local computer. sonarr will not work with
// its database in an NFS share
driver = "raw_exec"
config {
# When running a binary that exists on the host, the path must be absolute
command = "${meta.restoreCommand}"
args = ["${meta.restoreCommand1}", "${meta.restoreCommand2}", "${NOMAD_JOB_NAME}", "${meta.restoreCommand3}"]
}
lifecycle {
hook = "prestart"
sidecar = false
}
} // /task create_filesystem
task "influxdb" {
env {
PUID = "${meta.PUID}"
PGID = "${meta.PGID}"
TZ = "America/New_York"
}
driver = "docker"
config {
image = "influxdb:{{ influxdb_version }}"
hostname = "${NOMAD_JOB_NAME}"
ports = ["httpAPI"]
volumes = [
"${meta.localStorageRoot}/influxdb:/var/lib/influxdb"
]
} // docker config
service {
port = "httpAPI"
name = "${NOMAD_JOB_NAME}"
check {
type = "tcp"
port = "httpAPI"
interval = "30s"
timeout = "4s"
}
check_restart {
limit = 0
grace = "1m"
ignore_warnings = true
}
} // service
resources {
cpu = 1000 # MHz
memory = 400 # MB
} // resources
} // /task influxdb
task "save_configuration" {
driver = "raw_exec"
config {
# When running a binary that exists on the host, the path must be absolute
command = "${meta.backupCommand}"
args = ["${meta.backupAllocArg1}", "${meta.backupAllocArg2}", "${meta.backupAllocArg3}", "${meta.backupAllocArg4}", "${meta.backupAllocArg5}", "${NOMAD_JOB_NAME}", "${meta.backupAllocArg6}"]
}
lifecycle {
hook = "poststop"
sidecar = false
}
} // /task save_configuration
} // group
} // job

View File

@@ -0,0 +1,126 @@
job "lidarr" {
region = "global"
datacenters = ["{{ datacenter_name }}"]
type = "service"
// constraint {
// attribute = "${node.unique.name}"
// operator = "regexp"
// value = "rpi"
// }
update {
max_parallel = 1
health_check = "checks"
min_healthy_time = "10s"
healthy_deadline = "10m"
progress_deadline = "15m"
auto_revert = true
canary = 0
stagger = "30s"
}
group "lidarrGroup" {
count = 1
restart {
attempts = 0
delay = "10m"
}
network {
port "lidarr" {
to = "8686"
}
}
task "create_filesystem" {
// Copy the most recent backup into place on the local computer. sonarr will not work with
// its database in an NFS share
driver = "raw_exec"
config {
# When running a binary that exists on the host, the path must be absolute
command = "${meta.restoreCommand}"
args = ["${meta.restoreCommand1}", "${meta.restoreCommand2}", "${NOMAD_JOB_NAME}", "${meta.restoreCommand3}"]
}
lifecycle {
hook = "prestart"
sidecar = false
}
} // /task create_filesystem
task "lidarr" {
env {
PUID = "${meta.PUID}"
PGID = "${meta.PGID}"
TZ = "America/New_York"
//DOCKER_MODS = "linuxserver/mods:universal-cron|linuxserver/mods:universal-mod2"
//UMASK_SET = 022 #optional
}
driver = "docker"
config {
image = "linuxserver/lidarr:latest"
hostname = "${NOMAD_JOB_NAME}"
ports = ["lidarr"]
volumes = [
"${meta.localStorageRoot}/lidarr:/config",
"${meta.nfsStorageRoot}/media:/media"
]
} // docker config
service {
port = "lidarr"
name = "${NOMAD_JOB_NAME}"
tags = [
"traefik.enable=true",
"traefik.http.routers.${NOMAD_JOB_NAME}.rule=Host(`${NOMAD_JOB_NAME}.{{ homelab_domain_name }}`)",
"traefik.http.routers.${NOMAD_JOB_NAME}.entryPoints=web,websecure",
"traefik.http.routers.${NOMAD_JOB_NAME}.service=${NOMAD_JOB_NAME}",
"traefik.http.routers.${NOMAD_JOB_NAME}.tls=true",
"traefik.http.routers.${NOMAD_JOB_NAME}.tls.certresolver=cloudflare"
]
check {
type = "tcp"
port = "lidarr"
interval = "30s"
timeout = "4s"
}
check_restart {
limit = 0
grace = "10m"
ignore_warnings = true
}
} // service
resources {
cpu = 2000 # MHz
memory = 400 # MB
} // resources
} // /task lidarr main task
task "save_configuration" {
driver = "raw_exec"
config {
# When running a binary that exists on the host, the path must be absolute
command = "${meta.backupCommand}"
args = ["${meta.backupAllocArg1}", "${meta.backupAllocArg2}", "${meta.backupAllocArg3}", "${meta.backupAllocArg4}", "${meta.backupAllocArg5}", "${NOMAD_JOB_NAME}", "${meta.backupAllocArg6}"]
}
lifecycle {
hook = "poststop"
sidecar = false
}
} // /task save_configuration
} // group
} // job

View File

@@ -0,0 +1,157 @@
job "loki" {
region = "global"
datacenters = ["{{ datacenter_name }}"]
type = "service"
update {
max_parallel = 1
health_check = "checks"
min_healthy_time = "10s"
healthy_deadline = "5m"
progress_deadline = "10m"
auto_revert = true
canary = 0
stagger = "30s"
}
group "loki" {
count = 1
restart {
attempts = 0
delay = "1m"
}
network {
port "loki_port" {
static = "3100"
to = "3100"
}
}
task "loki" {
driver = "docker"
config {
image = "grafana/loki:latest"
hostname = "${NOMAD_JOB_NAME}"
volumes = [
"local/loki/local-config.yaml:/etc/loki/local-config.yaml",
"${meta.nfsStorageRoot}/pi-cluster/loki:/loki"
]
ports = ["loki_port"]
} // docker config
service {
port = "loki_port"
name = "${NOMAD_JOB_NAME}"
tags = [
"traefik.enable=true",
"traefik.http.routers.${NOMAD_JOB_NAME}.rule=Host(`${NOMAD_JOB_NAME}.{{ homelab_domain_name }}`)",
"traefik.http.routers.${NOMAD_JOB_NAME}.entryPoints=web,websecure",
"traefik.http.routers.${NOMAD_JOB_NAME}.service=${NOMAD_JOB_NAME}",
"traefik.http.routers.${NOMAD_JOB_NAME}.tls=true",
"traefik.http.routers.${NOMAD_JOB_NAME}.tls.certresolver=cloudflare"
]
check {
type = "http"
path = "/metrics"
interval = "30s"
timeout = "10s"
}
check_restart {
limit = 0
grace = "1m"
ignore_warnings = true
}
} // service
template {
destination = "local/loki/local-config.yaml"
env = false
change_mode = "noop"
data = <<-EOH
---
auth_enabled: false
server:
http_listen_port: 3100
grpc_listen_port: 9096
ingester:
wal:
enabled: true
dir: /tmp/wal
lifecycler:
address: 127.0.0.1
ring:
kvstore:
store: inmemory
replication_factor: 1
final_sleep: 0s
chunk_idle_period: 1h # Any chunk not receiving new logs in this time will be flushed
max_chunk_age: 1h # All chunks will be flushed when they hit this age. Def: 1h
chunk_target_size: 1048576 # Loki will attempt to build chunks up to 1.5MB, flushing first if chunk_idle_period or max_chunk_age is reached first
chunk_retain_period: 30s # Must be greater than index read cache TTL if using an index cache (Default index read cache TTL is 5m)
max_transfer_retries: 0 # Chunk transfers disabled
schema_config:
configs:
- from: 2020-10-24
store: boltdb-shipper
object_store: filesystem
schema: v11
index:
prefix: index_
period: 24h
storage_config:
boltdb_shipper:
active_index_directory: /loki/boltdb-shipper-active
cache_location: /loki/boltdb-shipper-cache
cache_ttl: 24h # Can be increased for faster performance over longer query periods, uses more disk space
shared_store: filesystem
filesystem:
directory: /loki/chunks
compactor:
working_directory: /loki/boltdb-shipper-compactor
shared_store: filesystem
limits_config:
reject_old_samples: true
reject_old_samples_max_age: 168h
chunk_store_config:
max_look_back_period: 0s
table_manager:
retention_deletes_enabled: false
retention_period: 0s
ruler:
storage:
type: local
local:
directory: /loki/rules
rule_path: /loki/rules-temp
alertmanager_url: http://localhost:9093
ring:
kvstore:
store: inmemory
enable_api: true
EOH
} // template
// resources {
// cpu = 100 # MHz
// memory = 300 # MB
// } // resources
} // task loki
} // group
} // job

View File

@@ -0,0 +1,93 @@
job "nginx" {
region = "global"
datacenters = ["{{ datacenter_name }}"]
type = "service"
constraint {
attribute = "${node.unique.name}"
operator = "regexp"
value = "rpi"
}
update {
max_parallel = 1
health_check = "checks"
min_healthy_time = "10s"
healthy_deadline = "5m"
progress_deadline = "10m"
auto_revert = true
canary = 0
stagger = "30s"
}
group "nginx" {
restart {
attempts = 0
delay = "30s"
}
network {
port "web" {
to = "80"
}
// port "websecure" {
// to = "443"
// }
}
task "nginx" {
env {
PUID = "${meta.PUID}"
PGID = "${meta.PGID}"
TZ = "America/New_York"
}
driver = "docker"
config {
image = "ghcr.io/linuxserver/nginx"
hostname = "${NOMAD_JOB_NAME}"
volumes = [
"/mnt/usbDrive/nginx:/config"
]
ports = ["web"]
} // docker config
service {
port = "web"
name = "${NOMAD_JOB_NAME}"
tags = [
"traefik.enable=true",
"traefik.http.routers.${NOMAD_JOB_NAME}.rule=Host(`${NOMAD_JOB_NAME}.{{ homelab_domain_name }}`)",
"traefik.http.routers.${NOMAD_JOB_NAME}.entryPoints=web,websecure",
"traefik.http.routers.${NOMAD_JOB_NAME}.service=${NOMAD_JOB_NAME}",
"traefik.http.routers.${NOMAD_JOB_NAME}.tls=true",
"traefik.http.routers.${NOMAD_JOB_NAME}.tls.certresolver=cloudflare"
]
check {
type = "tcp"
port = "web"
interval = "30s"
timeout = "4s"
}
check_restart {
limit = 0
grace = "1m"
ignore_warnings = true
}
} // service
resources {
cpu = 100 # MHz
memory = 300 # MB
} // resources
} // task
} // group
} // job

View File

@@ -0,0 +1,91 @@
job "nzbhydra" {
region = "global"
datacenters = ["{{ datacenter_name }}"]
type = "service"
// constraint {
// attribute = "${node.unique.name}"
// operator = "regexp"
// value = "rpi"
// }
update {
max_parallel = 1
health_check = "checks"
min_healthy_time = "10s"
healthy_deadline = "5m"
progress_deadline = "10m"
auto_revert = true
canary = 0
stagger = "30s"
}
group "nzbhydra" {
restart {
attempts = 0
delay = "30s"
}
network {
port "hydra_port" {
to = "5076"
}
}
task "nzbhydra" {
env {
PUID = "${meta.PUID}"
PGID = "${meta.PGID}"
TZ = "America/New_York"
//DOCKER_MODS = "linuxserver/mods:universal-cron|linuxserver/mods:universal-mod2"
}
driver = "docker"
config {
image = "ghcr.io/linuxserver/nzbhydra2:latest"
hostname = "${NOMAD_JOB_NAME}"
ports = ["hydra_port"]
volumes = [
"${meta.nfsStorageRoot}/pi-cluster/nzbhydra:/config"
]
} // docker config
service {
port = "hydra_port"
name = "${NOMAD_JOB_NAME}"
tags = [
"traefik.enable=true",
"traefik.http.routers.${NOMAD_JOB_NAME}.rule=Host(`hydra.{{ homelab_domain_name }}`)",
"traefik.http.routers.${NOMAD_JOB_NAME}.entryPoints=web,websecure",
"traefik.http.routers.${NOMAD_JOB_NAME}.service=${NOMAD_JOB_NAME}",
"traefik.http.routers.${NOMAD_JOB_NAME}.tls=true",
"traefik.http.routers.${NOMAD_JOB_NAME}.tls.certresolver=cloudflare"
]
check {
type = "http"
path = "/"
interval = "30s"
timeout = "10s"
}
check_restart {
limit = 0
grace = "1m"
ignore_warnings = true
}
} // service
resources {
cpu = 600 # MHz
memory = 400 # MB
} // resources
} // task
} // group
} // job

View File

@@ -0,0 +1,94 @@
job "overseerr" {
region = "global"
datacenters = ["{{ datacenter_name }}"]
type = "service"
// constraint {
// attribute = "${node.unique.name}"
// operator = "regexp"
// value = "rpi"
// }
update {
max_parallel = 1
health_check = "checks"
min_healthy_time = "10s"
healthy_deadline = "5m"
progress_deadline = "10m"
auto_revert = true
canary = 0
stagger = "30s"
}
group "overseerr" {
count = 1
restart {
attempts = 0
delay = "30s"
}
network {
port "overseerr" {
to = "5055"
}
}
task "overseerr" {
env {
PUID = "${meta.PUID}"
PGID = "${meta.PGID}"
TZ = "America/New_York"
}
driver = "docker"
config {
image = "ghcr.io/linuxserver/overseerr"
hostname = "${NOMAD_JOB_NAME}"
ports = ["overseerr"]
volumes = [
"${meta.nfsStorageRoot}/pi-cluster/overseerr:/config"
]
} // docker config
service {
port = "overseerr"
name = "${NOMAD_JOB_NAME}"
tags = [
"traefik.enable=true",
"traefik.http.routers.${NOMAD_JOB_NAME}.rule=Host(`${NOMAD_JOB_NAME}.{{ homelab_domain_name }}`)",
"traefik.http.routers.${NOMAD_JOB_NAME}.entryPoints=web,websecure",
"traefik.http.routers.${NOMAD_JOB_NAME}.service=overseerr",
"traefik.http.routers.${NOMAD_JOB_NAME}.tls=true",
"traefik.http.routers.${NOMAD_JOB_NAME}.tls.certresolver=cloudflare",
"traefik.http.routers.${NOMAD_JOB_NAME}.middlewares=authelia@file"
]
check {
type = "tcp"
port = "overseerr"
interval = "30s"
timeout = "4s"
}
check_restart {
limit = 0
grace = "1m"
ignore_warnings = true
}
} // service
resources {
cpu = 1600 # MHz
memory = 300 # MB
} // resources
} // task
} // group
} // job

View File

@@ -0,0 +1,155 @@
job "pihole" {
region = "global"
datacenters = ["{{ datacenter_name }}"]
type = "service"
constraint {
attribute = "${node.unique.name}"
operator = "regexp"
value = "rpi(2|3)"
}
update {
max_parallel = 1
health_check = "checks"
min_healthy_time = "10s"
healthy_deadline = "5m"
progress_deadline = "10m"
auto_revert = true
canary = 0
stagger = "30s"
}
group "pihole-group" {
network {
port "web" {
static = "80"
to = "80"
}
port "dns" {
static = "53"
to = "53"
}
// port "dhcp" {
// static = "67"
// to = "67"
// }
}
task "await_filesytem" {
driver = "docker"
config {
image = "busybox:latest"
command = "sh"
network_mode = "host"
args = [
"-c",
"echo -n 'Waiting for /mnt/pi-cluster/pihole5 to be mounted'; until [ -f /etc/pihole/gravity.db ]; do echo '.'; sleep 2; done",
]
volumes = [
"/mnt/pi-cluster/pihole5:/etc/pihole/"
]
}
lifecycle {
hook = "prestart"
sidecar = false
}
} // /await-filesystem
task "pihole" {
env {
// REV_SERVER_DOMAIN = ""
ADMIN_EMAIL = "{{ my_email_address }}"
DHCP_ACTIVE = "false"
DNS_BOGUS_PRIV = "false"
DNS_FQDN_REQUIRED = "false"
DNSSEC = "false"
FTLCONF_REPLY_ADDR4 = "${attr.unique.network.ip-address}"
IPv6 = "false"
PIHOLE_DNS_ = "10.0.30.1#53"
QUERY_LOGGING = "true"
REV_SERVER = "true"
REV_SERVER_CIDR = "10.0.0.0/16"
REV_SERVER_TARGET = "10.0.30.1"
TEMPERATUREUNIT = "f"
TZ = "America/New_York"
WEBTHEME = "default-light"
WEBUIBOXEDLAYOUT = "traditional"
}
driver = "docker"
config {
image = "pihole/pihole:latest"
hostname = "${NOMAD_JOB_NAME}"
dns_servers = [
"127.0.0.1",
"1.1.1.1"
]
extra_hosts = [
"laptopVPN:10.0.90.2",
"FiddleStixPhoneVPN:10.0.90.3"
]
volumes = [
"${meta.nfsStorageRoot}/pi-cluster/pihole5:/etc/pihole/",
"${meta.nfsStorageRoot}/pi-cluster/pihole5/dnsmasq.d:/etc/dnsmasq.d/"
// "${meta.nfsStorageRoot}/pi-cluster/pihole5/logs/pihole.log:/var/log/pihole.log",
// "${meta.nfsStorageRoot}/pi-cluster/pihole5/logs/pihole-FTL.log:/var/log/pihole-FTL.log"
]
ports = ["web", "dns"]
}
resources {
cpu = 400 # MHz
memory = 80 # MB
}
service {
name = "${NOMAD_JOB_NAME}"
port = "web"
tags = [
"traefik.enable=true",
"traefik.http.routers.${NOMAD_JOB_NAME}.rule=Host(`p.{{ homelab_domain_name }}`)",
"traefik.http.routers.${NOMAD_JOB_NAME}.entryPoints=web,websecure",
"traefik.http.routers.${NOMAD_JOB_NAME}.service=${NOMAD_JOB_NAME}",
"traefik.http.routers.${NOMAD_JOB_NAME}.tls=true",
"traefik.http.routers.${NOMAD_JOB_NAME}.tls.certresolver=cloudflare",
"traefik.http.middlewares.piholeRedirect.redirectregex.regex=^(https?://p\\.{{ homelab_domain_name }})/?$",
"traefik.http.middlewares.piholeRedirect.redirectregex.replacement=$${1}/admin/",
"traefik.http.routers.${NOMAD_JOB_NAME}.middlewares=authelia@file,piholeRedirect"
]
check {
type = "http"
path = "/admin/"
port = "web"
interval = "30s"
timeout = "2s"
}
check_restart {
limit = 3
grace = "10m"
ignore_warnings = false
}
}
service {
name = "piholeDNStcp"
port = "dns"
check {
type = "tcp"
port = "dns"
interval = "30s"
timeout = "2s"
}
check_restart {
limit = 3
grace = "60s"
ignore_warnings = false
}
}
}
} // group
}

View File

@@ -0,0 +1,88 @@
job "promtail-syslogs" {
region = "global"
datacenters = ["{{ datacenter_name }}"]
type = "system"
update {
max_parallel = 1
health_check = "checks"
min_healthy_time = "10s"
healthy_deadline = "5m"
progress_deadline = "10m"
auto_revert = true
canary = 0
stagger = "30s"
}
group "promtail-syslogs" {
restart {
attempts = 0
delay = "30s"
}
task "promtail-syslogs" {
driver = "docker"
config {
image = "grafana/promtail"
hostname = "${NOMAD_JOB_NAME}"
volumes = [
"/var/log:/var/log"
]
args = [
"-config.file",
"/local/promtail-config.yaml",
"-print-config-stderr"
]
} // docker config
template {
destination = "local/promtail-config.yaml"
env = false
data = <<EOH
server:
http_listen_port: 9080
grpc_listen_port: 0
positions:
filename: /tmp/positions.yaml
{% raw -%}
clients:
- url: http://{{ range service "loki" }}{{ .Address }}:{{ .Port }}{{ end }}/loki/api/v1/push
{% endraw %}
scrape_configs:
- job_name: system
static_configs:
- targets:
- localhost
labels:
job: syslog
{% raw %}host: {{ env "node.unique.name" }}{% endraw +%}
__path__: /var/log/syslog
- targets:
- localhost
labels:
job: authlog
{% raw %}host: {{ env "node.unique.name" }}{% endraw +%}
__path__: /var/log/auth.log
EOH
} // template
resources {
cpu = 30 # MHz
memory = 30 # MB
} // resources
} // task
} // group
} // job

View File

@@ -0,0 +1,129 @@
job "prowlarr" {
region = "global"
datacenters = ["{{ datacenter_name }}"]
type = "service"
// constraint {
// attribute = "${node.unique.name}"
// operator = "regexp"
// value = "rpi4"
// }
update {
max_parallel = 1
health_check = "checks"
min_healthy_time = "10s"
healthy_deadline = "5m"
progress_deadline = "10m"
auto_revert = true
canary = 0
stagger = "30s"
}
group "prowlarrGroup" {
count = 1
restart {
attempts = 0
delay = "10m"
}
network {
port "prowlarr" {
to = "9696"
}
}
task "create_filesystem" {
// Copy the most recent backup into place on the local computer. sonarr will not work with
// its database in an NFS share
driver = "raw_exec"
config {
# When running a binary that exists on the host, the path must be absolute
command = "${meta.restoreCommand}"
args = ["${meta.restoreCommand1}", "${meta.restoreCommand2}", "${NOMAD_JOB_NAME}", "${meta.restoreCommand3}"]
}
lifecycle {
hook = "prestart"
sidecar = false
}
} // /task create_filesystem
task "prowlarr" {
env {
PUID = "${meta.PUID}"
PGID = "${meta.PGID}"
TZ = "America/New_York"
//DOCKER_MODS = "linuxserver/mods:universal-cron|linuxserver/mods:universal-mod2"
//UMASK_SET = 022 #optional
}
driver = "docker"
config {
image = "ghcr.io/linuxserver/prowlarr:develop"
force_pull = true
hostname = "${NOMAD_JOB_NAME}"
ports = ["prowlarr"]
volumes = [
"${meta.localStorageRoot}/prowlarr:/config"
]
} // docker config
service {
port = "prowlarr"
name = "${NOMAD_JOB_NAME}"
tags = [
"traefik.enable=true",
"traefik.http.routers.${NOMAD_JOB_NAME}.rule=Host(`${NOMAD_JOB_NAME}.{{ homelab_domain_name }}`)",
"traefik.http.routers.${NOMAD_JOB_NAME}.entryPoints=web,websecure",
"traefik.http.routers.${NOMAD_JOB_NAME}.service=${NOMAD_JOB_NAME}",
"traefik.http.routers.${NOMAD_JOB_NAME}.tls=true",
"traefik.http.routers.${NOMAD_JOB_NAME}.tls.certresolver=cloudflare"
]
check {
type = "tcp"
port = "prowlarr"
interval = "30s"
timeout = "4s"
}
check_restart {
limit = 0
grace = "1m"
ignore_warnings = true
}
} // service
resources {
cpu = 1000 # MHz
memory = 400 # MB
} // resources
} // /task prowlarr
task "save_configuration" {
driver = "raw_exec"
config {
# When running a binary that exists on the host, the path must be absolute
command = "${meta.backupCommand}"
args = ["${meta.backupAllocArg1}", "${meta.backupAllocArg2}", "${meta.backupAllocArg3}", "${meta.backupAllocArg4}", "${meta.backupAllocArg5}", "${NOMAD_JOB_NAME}", "${meta.backupAllocArg6}"]
}
lifecycle {
hook = "poststop"
sidecar = false
}
} // /task save_configuration
} // group
} // job

View File

@@ -0,0 +1,123 @@
job "radarr" {
region = "global"
datacenters = ["{{ datacenter_name }}"]
type = "service"
// constraint {
// attribute = "${node.unique.name}"
// operator = "regexp"
// value = "rpi"
// }
update {
max_parallel = 1
health_check = "checks"
min_healthy_time = "10s"
healthy_deadline = "5m"
progress_deadline = "10m"
auto_revert = true
canary = 0
stagger = "30s"
}
group "radarrGroup" {
restart {
attempts = 0
delay = "10m"
}
network {
port "radarr" {
to = "7878"
}
}
task "create_filesystem" {
// Copy the most recent backup into place on the local computer. sonarr will not work with
// its database in an NFS share
driver = "raw_exec"
config {
# When running a binary that exists on the host, the path must be absolute
command = "${meta.restoreCommand}"
args = ["${meta.restoreCommand1}", "${meta.restoreCommand2}", "${NOMAD_JOB_NAME}", "${meta.restoreCommand3}"]
}
lifecycle {
hook = "prestart"
sidecar = false
}
} // /task create_filesystem
task "radarr" {
env {
PUID = "${meta.PUID}"
PGID = "${meta.PGID}"
TZ = "America/New_York"
//DOCKER_MODS = "linuxserver/mods:universal-cron|linuxserver/mods:universal-mod2"
//UMASK_SET = 022 #optional
}
driver = "docker"
config {
image = "ghcr.io/linuxserver/radarr:develop"
hostname = "${NOMAD_JOB_NAME}"
force_pull = true
ports = ["radarr"]
volumes = [
"${meta.localStorageRoot}/${NOMAD_JOB_NAME}:/config",
"${meta.nfsStorageRoot}/media:/media"
]
} // docker config
service {
port = "radarr"
name = "${NOMAD_JOB_NAME}"
tags = [
"traefik.enable=true",
"traefik.http.routers.${NOMAD_JOB_NAME}.rule=Host(`${NOMAD_JOB_NAME}.{{ homelab_domain_name }}`)",
"traefik.http.routers.${NOMAD_JOB_NAME}.entryPoints=web,websecure",
"traefik.http.routers.${NOMAD_JOB_NAME}.service=${NOMAD_JOB_NAME}",
"traefik.http.routers.${NOMAD_JOB_NAME}.tls=true",
"traefik.http.routers.${NOMAD_JOB_NAME}.tls.certresolver=cloudflare"
]
check {
type = "tcp"
port = "radarr"
interval = "30s"
timeout = "4s"
}
check_restart {
limit = 0
grace = "1m"
ignore_warnings = true
}
} // service
resources {
cpu = 2000 # MHz
memory = 400 # MB
} // resources
} // /task radarr
task "save_configuration" {
driver = "raw_exec"
config {
# When running a binary that exists on the host, the path must be absolute
command = "${meta.backupCommand}"
args = ["${meta.backupAllocArg1}", "${meta.backupAllocArg2}", "${meta.backupAllocArg3}", "${meta.backupAllocArg4}", "${meta.backupAllocArg5}", "${NOMAD_JOB_NAME}", "${meta.backupAllocArg6}"]
}
lifecycle {
hook = "poststop"
sidecar = false
}
} // /task save_configuration
} // group
} // job

View File

@@ -0,0 +1,468 @@
job "reverse-proxy" {
region = "global"
datacenters = ["{{ datacenter_name }}"]
type = "service"
constraint {
attribute = "${node.unique.name}"
value = "rpi1"
}
update {
max_parallel = 1
health_check = "checks"
min_healthy_time = "10s"
healthy_deadline = "5m"
progress_deadline = "10m"
auto_revert = true
canary = 0
stagger = "30s"
}
group "reverse-proxy-group" {
restart {
attempts = 0
delay = "30s"
}
network {
port "authelia-port" {
static = { { authelia_port } }
to = 9091
}
port "whoami" {
to = 80
}
port "dashboard" {
static = 8080
to = 8080
}
port "web" {
static = 80
to = 80
}
port "websecure" {
static = 443
to = 443
}
port "externalwebsecure" {
static = 4430
to = 4430
}
}
task "authelia" {
env {
TZ = "America/New_York"
PUID = "${meta.PUID}"
PGID = "${meta.PGID}"
}
driver = "docker"
config {
image = "authelia/authelia"
hostname = "authelia"
ports = ["authelia-port"]
volumes = [
"${meta.nfsStorageRoot}/pi-cluster/authelia:/config"
]
args = [
"--config",
"/local/authelia/config.yaml"
]
} // docker config
template {
destination = "local/authelia/config.yaml"
env = false
change_mode = "noop"
perms = "644"
data = <<-EOH
---
## The theme to display: light, dark, grey, auto.
theme: auto
jwt_secret: {{ authelia_jwt_secret}}
default_redirection_url: https://authelia.{{ homelab_domain_name}}
server:
host: 0.0.0.0
port: 9091
path: ""
read_buffer_size: 4096
write_buffer_size: 4096
enable_pprof: false
enable_expvars: false
disable_healthcheck: false
log:
level: info
format: text
# file_path: "/config/log.txt"
keep_stdout: false
totp:
issuer: authelia.com
authentication_backend:
disable_reset_password: false
file:
path: /config/users.yml
password:
algorithm: argon2id
iterations: 1
salt_length: 16
parallelism: 8
memory: 64
access_control:
default_policy: deny
networks:
- name: internal
networks:
- 10.0.0.0/16
#- 172.16.0.0/12
#- 192.168.0.0/18
rules:
# Rules applied to everyone
- domain: "*.{{ homelab_domain_name }}"
policy: two_factor
networks:
- internal
session:
name: authelia_session
domain: {{ homelab_domain_name }}
same_site: lax
secret: {{ authelia_session_secret }}
expiration: 1h
inactivity: 15m
remember_me_duration: 1w
regulation:
max_retries: 5
find_time: 10m
ban_time: 15m
storage:
encryption_key: {{ authelia_sqlite_encryption_key}}
local:
path: /config/db.sqlite3
notifier:
smtp:
username: {{ email_smtp_account }}
password: {{ authelia_smtp_password }}
host: {{ email_smtp_host }}
port: {{ email_smtp_port }}
sender: "Authelia <{{ my_email_address }}>"
subject: "[Authelia] {title}"
startup_check_address: {{ my_email_address }}
EOH
}
service {
port = "authelia-port"
name = "${NOMAD_TASK_NAME}"
tags = [
"traefik.enable=true",
"traefik.http.routers.${NOMAD_TASK_NAME}.rule=Host(`authelia.{{ homelab_domain_name }}`)",
"traefik.http.routers.${NOMAD_TASK_NAME}.entryPoints=web,websecure",
"traefik.http.routers.${NOMAD_TASK_NAME}.service=${NOMAD_TASK_NAME}",
"traefik.http.routers.${NOMAD_TASK_NAME}.tls=true",
"traefik.http.routers.${NOMAD_TASK_NAME}.tls.certresolver=cloudflare",
"traefik.http.middlewares.authelia-headers.headers.customResponseHeaders.Cache-Control=no-store",
"traefik.http.middlewares.authelia-headers.headers.customResponseHeaders.Pragma=no-cache",
"traefik.http.routers.authelia.middlewares=authelia-headers"
]
check {
type = "tcp"
port = "authelia-port"
interval = "30s"
timeout = "4s"
}
check_restart {
limit = 0
grace = "1m"
ignore_warnings = true
}
} // service
resources {
cpu = 200 # MHz
memory = 110 # MB
}
} // task authelia
task "whoami" {
driver = "docker"
config {
image = "containous/whoami:latest"
hostname = "${NOMAD_TASK_NAME}"
ports = ["whoami"]
} // /docker config
service {
port = "whoami"
name = "${NOMAD_TASK_NAME}"
tags = [
"traefik.enable=true",
"traefik.http.routers.${NOMAD_TASK_NAME}.rule=Host(`${NOMAD_TASK_NAME}.{{ homelab_domain_name }}`)",
"traefik.http.routers.${NOMAD_TASK_NAME}.entryPoints=web,websecure",
"traefik.http.routers.${NOMAD_TASK_NAME}.service=${NOMAD_TASK_NAME}",
"traefik.http.routers.${NOMAD_TASK_NAME}.tls=true",
"traefik.http.routers.${NOMAD_TASK_NAME}.tls.certresolver=cloudflare",
"traefik.http.routers.${NOMAD_TASK_NAME}.middlewares=authelia@file"
]
check {
type = "http"
path = "/"
interval = "90s"
timeout = "15s"
}
check_restart {
limit = 2
grace = "1m"
ignore_warnings = true
}
}
resources {
cpu = 25 # MHz
memory = 10 # MB
}
} // /task whoami
task "traefik" {
env {
PUID = "${meta.PUID}"
PGID = "${meta.PGID}"
TZ = "America/New_York"
CF_API_EMAIL = "{{ my_email_address }}"
CF_DNS_API_TOKEN = "{{ traefik_cf_api_token }}"
}
driver = "docker"
config {
image = "traefik:{{ traefik_version }}"
hostname = "traefik"
ports = ["dashboard", "web", "websecure", "externalwebsecure"]
volumes = ["${meta.nfsStorageRoot}/pi-cluster/traefik/acme:/acme"]
args = [
"--global.sendAnonymousUsage=false",
"--global.checkNewVersion=false",
"--entryPoints.web.address=:80",
"--entryPoints.websecure.address=:443",
"--entryPoints.externalwebsecure.address=:4430",
"--entrypoints.web.http.redirections.entryPoint.to=websecure",
"--entrypoints.web.http.redirections.entryPoint.scheme=https",
"--entrypoints.web.http.redirections.entryPoint.permanent=true",
"--providers.file.filename=/local/traefik/siteconfigs.toml",
"--providers.file.watch=true",
"--providers.consulcatalog=true",
"--providers.consulcatalog.endpoint.address=http://consul.service.consul:8500",
"--providers.consulcatalog.prefix=traefik",
"--providers.consulcatalog.exposedbydefault=false",
"--metrics=true",
"--metrics.influxdb=true",
"--metrics.influxdb.address=influxdb.service.consul:{{ influxdb_port }}",
"--metrics.influxdb.protocol=http",
"--metrics.influxdb.pushinterval=10s",
"--metrics.influxdb.database=homelab",
"--metrics.influxdb.retentionpolicy=2day",
"--metrics.influxdb.addentrypointslabels=true",
"--metrics.influxdb.addserviceslabels=true",
"--accesslog=true",
"--log=true",
"--log.level=ERROR",
"--api=true",
"--api.dashboard=true",
"--api.insecure=true",
"--certificatesresolvers.cloudflare.acme.email={{ my_email_address }}",
"--certificatesresolvers.cloudflare.acme.storage=/acme/acme-${node.unique.name}.json",
"--certificatesresolvers.cloudflare.acme.dnschallenge=true",
"--certificatesresolvers.cloudflare.acme.dnschallenge.provider=cloudflare",
"--certificatesresolvers.cloudflare.acme.dnschallenge.delaybeforecheck=10",
"--certificatesresolvers.cloudflare.acme.dnschallenge.resolvers=1.1.1.1:53,8.8.8.8:53"
]
} // docker config
template {
destination = "local/traefik/httpasswd"
env = false
change_mode = "noop"
data = <<-EOH
{{ my_username }}:{{ traefik_http_pass_me }}
family:{{ traefik_http_pass_family }}
EOH
}
template {
destination = "local/traefik/httpasswdFamily"
env = false
change_mode = "noop"
data = <<-EOH
{{ my_username }}:{{ traefik_http_pass_me }}
family:{{ traefik_http_pass_family }}
EOH
}
template {
destination = "local/traefik/siteconfigs.toml"
env = false
change_mode = "noop"
data = <<-EOH
[http]
[http.middlewares]
[http.middlewares.compress.compress]
[http.middlewares.localIPOnly.ipWhiteList]
sourceRange = ["10.0.0.0/16"]
[http.middlewares.redirectScheme.redirectScheme]
scheme = "https"
permanent = true
[http.middlewares.authelia.forwardAuth]
address = "http://authelia.service.consul:{{ authelia_port }}/api/verify?rd=https://authelia.{{ homelab_domain_name }}"
trustForwardHeader = true
authResponseHeaders = ["Remote-User", "Remote-Groups", "Remote-Name", "Remote-Email"]
[http.middlewares.basicauth.basicauth]
usersfile = "/local/traefik/httpasswd"
removeHeader = true
[http.middlewares.basicauth-family.basicauth]
usersfile = "/local/traefik/httpasswdFamily"
removeHeader = true
[http.middlewares.allowFrame.headers]
customFrameOptionsValue = "allow-from https://home.{{ homelab_domain_name }}"
[http.routers]
[http.routers.consul]
rule = "Host(`consul.{{ homelab_domain_name }}`)"
service = "consul"
entrypoints = ["web","websecure"]
[http.routers.consul.tls]
certResolver = "cloudflare" # From static configuration
[http.services]
[http.services.consul]
[http.services.consul.loadBalancer]
passHostHeader = true
[[http.services.consul.loadBalancer.servers]]
url = "http://consul.service.consul:8500"
EOH
}
service {
port = "dashboard"
name = "${NOMAD_TASK_NAME}"
tags = [
"traefik.enable=true",
"traefik.http.routers.${NOMAD_TASK_NAME}.rule=Host(`${NOMAD_TASK_NAME}.{{ homelab_domain_name }}`)",
"traefik.http.routers.${NOMAD_TASK_NAME}.entryPoints=web,websecure",
"traefik.http.routers.${NOMAD_TASK_NAME}.service=${NOMAD_TASK_NAME}",
"traefik.http.routers.${NOMAD_TASK_NAME}.tls=true",
"traefik.http.routers.${NOMAD_TASK_NAME}.tls.certresolver=cloudflare",
"traefik.http.routers.${NOMAD_TASK_NAME}.middlewares=authelia@file,redirectScheme@file"
]
check {
type = "tcp"
port = "dashboard"
interval = "30s"
timeout = "4s"
}
check_restart {
limit = 0
grace = "1m"
ignore_warnings = true
}
} // service
resources {
//cpu = 40 # MHz
memory = 64 # MB
} // resources
} // task traefik
// task "promtail-traefik" {
// driver = "docker"
// config {
// image = "grafana/promtail"
// hostname = "promtail-traefik"
// volumes = [
// "/mnt/pi-cluster/logs:/traefik"
// ]
// args = [
// "-config.file",
// "/local/promtail-config.yaml",
// "-print-config-stderr",
// ]
// } // docker config
// template {
// destination = "local/promtail-config.yaml"
// env = false
// data = <<-EOH
// server:
// http_listen_port: 9080
// grpc_listen_port: 0
// positions:
// filename: /alloc/positions.yaml
// {% raw -%}
// clients:
// - url: http://{{ range service "loki" }}{{ .Address }}:{{ .Port }}{{ end }}/loki/api/v1/push
// {% endraw %}
// scrape_configs:
// - job_name: traefik
// static_configs:
// - targets:
// - localhost
// labels:
// job: traefik_access
// {% raw %}host: {{ env "node.unique.name" }}{% endraw +%}
// __path__: "/alloc/logs/traefik.std*.0"
// pipeline_stages:
// - regex:
// expression: '^(?P<remote_addr>[\w\.]+) - (?P<remote_user>[^ ]*) \[(?P<time_local>.*)\] "(?P<method>[^ ]*) (?P<request>[^ ]*) (?P<protocol>[^ ]*)" (?P<status>[\d]+) (?P<body_bytes_sent>[\d]+) "(?P<http_referer>[^"]*)" "(?P<http_user_agent>[^"]*)" (?P<request_number>[^ ]+) "(?P<router>[^ ]+)" "(?P<server_URL>[^ ]+)" (?P<response_time_ms>[^ ]+)ms$'
// - labels:
// method:
// status:
// router:
// response_time_ms:
// EOH
// } // template
// lifecycle {
// hook = "poststart"
// sidecar = true
// }
// resources {
// cpu = 30 # MHz
// memory = 30 # MB
// } // resources
// } // promtail sidecar task
} // reverse-proxy-group
}

View File

@@ -0,0 +1,139 @@
job "sonarr" {
region = "global"
datacenters = ["{{ datacenter_name }}"]
type = "service"
// constraint {
// attribute = "${node.unique.name}"
// operator = "regexp"
// value = "macmini"
// }
update {
max_parallel = 1
health_check = "checks"
min_healthy_time = "10s"
healthy_deadline = "5m"
progress_deadline = "10m"
auto_revert = true
canary = 0
stagger = "30s"
}
group "sonarrGroup" {
count = 1
restart {
attempts = 0
delay = "10m"
}
network {
port "sonarr" {
to = "8989"
}
}
task "create_filesystem" {
// Copy the most recent backup into place on the local computer. sonarr will not work with
// its database in an NFS share
driver = "raw_exec"
config {
# When running a binary that exists on the host, the path must be absolute
command = "${meta.restoreCommand}"
args = [
"${meta.restoreCommand1}",
"${meta.restoreCommand2}",
"${NOMAD_JOB_NAME}",
"${meta.restoreCommand3}"
]
}
lifecycle {
hook = "prestart"
sidecar = false
}
} // /task create_filesystem
task "sonarr" {
env {
PUID = "${meta.PUID}"
PGID = "${meta.PGID}"
TZ = "America/New_York"
//DOCKER_MODS = "linuxserver/mods:universal-cron|linuxserver/mods:universal-mod2"
//UMASK_SET = 022 #optional
}
driver = "docker"
config {
image = "linuxserver/sonarr:latest"
hostname = "${NOMAD_JOB_NAME}"
ports = ["sonarr"]
volumes = [
"${meta.localStorageRoot}/${NOMAD_JOB_NAME}:/config",
"${meta.nfsStorageRoot}/media:/media"
]
} // docker config
service {
port = "sonarr"
name = "${NOMAD_JOB_NAME}"
tags = [
"traefik.enable=true",
"traefik.http.routers.${NOMAD_JOB_NAME}.rule=Host(`${NOMAD_JOB_NAME}.{{ homelab_domain_name }}`)",
"traefik.http.routers.${NOMAD_JOB_NAME}.entryPoints=web,websecure",
"traefik.http.routers.${NOMAD_JOB_NAME}.service=sonarr",
"traefik.http.routers.${NOMAD_JOB_NAME}.tls=true",
"traefik.http.routers.${NOMAD_JOB_NAME}.tls.certresolver=cloudflare"
]
check {
type = "tcp"
port = "sonarr"
interval = "30s"
timeout = "4s"
}
check_restart {
limit = 0
grace = "1m"
ignore_warnings = true
}
} // service
resources {
cpu = 1000 # MHz
memory = 400 # MB
} // resources
} // /task sonarr
task "save_configuration" {
driver = "raw_exec"
config {
# When running a binary that exists on the host, the path must be absolute
command = "${meta.backupCommand}"
args = [
"${meta.backupAllocArg1}",
"${meta.backupAllocArg2}",
"${meta.backupAllocArg3}",
"${meta.backupAllocArg4}",
"${meta.backupAllocArg5}",
"${NOMAD_JOB_NAME}",
"${meta.backupAllocArg6}"
]
}
lifecycle {
hook = "poststop"
sidecar = false
}
} // /task save_configuration
} // group
} // job

View File

@@ -0,0 +1,103 @@
job "stash" {
region = "global"
datacenters = ["{{ datacenter_name }}"]
type = "service"
constraint {
attribute = "${node.unique.name}"
operator = "regexp"
value = "macmini"
}
update {
max_parallel = 1
health_check = "checks"
min_healthy_time = "10s"
healthy_deadline = "5m"
progress_deadline = "10m"
auto_revert = true
canary = 0
stagger = "30s"
}
group "stashGroup" {
count = 1
restart {
attempts = 0
delay = "30s"
}
network {
port "port1" {
to = "9999"
}
}
task "stash" {
env {
PUID = "${meta.PUID}"
PGID = "${meta.PGID}"
STASH_STASH = "/data/"
STASH_GENERATED = "/generated/"
STASH_METADATA = "/metadata/"
STASH_CACHE = "/cache/"
STASH_PORT = "9999"
STASH_EXTERNAL_HOST = "https://${NOMAD_JOB_NAME}.{{ homelab_domain_name }}"
}
driver = "docker"
config {
image = "stashapp/stash:latest"
hostname = "${NOMAD_JOB_NAME}"
volumes = [
"${meta.nfsStorageRoot}/nate/.stash/cache:/cache",
"${meta.nfsStorageRoot}/nate/.stash/config:/root/.stash",
"${meta.nfsStorageRoot}/nate/.stash/generated:/generated",
"${meta.nfsStorageRoot}/nate/.stash/media:/data",
"${meta.nfsStorageRoot}/nate/.stash/metadata:/metadata",
"/etc/timezone:/etc/timezone:ro"
]
ports = ["port1"]
} // docker config
service {
port = "port1"
name = "${NOMAD_JOB_NAME}"
tags = [
"traefik.enable=true",
"traefik.http.routers.${NOMAD_JOB_NAME}.rule=Host(`${NOMAD_JOB_NAME}.{{ homelab_domain_name }}`)",
"traefik.http.routers.${NOMAD_JOB_NAME}.entryPoints=web,websecure",
"traefik.http.routers.${NOMAD_JOB_NAME}.service=${NOMAD_JOB_NAME}",
"traefik.http.routers.${NOMAD_JOB_NAME}.tls=true",
"traefik.http.routers.${NOMAD_JOB_NAME}.tls.certresolver=cloudflare",
"traefik.http.routers.${NOMAD_JOB_NAME}.middlewares=authelia@file"
]
check {
type = "tcp"
port = "port1"
interval = "30s"
timeout = "4s"
}
check_restart {
limit = 0
grace = "1m"
ignore_warnings = true
}
} // service
resources {
cpu = 4500 # MHz
memory = 400 # MB
} // resources
} // task
} // group
} // job

View File

@@ -0,0 +1,100 @@
job "syncthing" {
region = "global"
datacenters = ["{{ datacenter_name }}"]
type = "service"
// constraint {
// attribute = "${node.unique.name}"
// operator = "regexp"
// value = "rpi(1|2|3)"
// }
update {
max_parallel = 1
health_check = "checks"
min_healthy_time = "10s"
healthy_deadline = "5m"
progress_deadline = "10m"
auto_revert = true
canary = 0
stagger = "30s"
}
group "syncthing" {
restart {
attempts = 0
delay = "30s"
}
network {
port "webGUI" {
to = "8384"
}
port "listen_tcp_udp" {
static = "22000"
to = "22000"
}
port "udp_proto_discovery" {
static = "21027"
to = "21027"
}
}
task "syncthing" {
env {
PUID = "${meta.PUID}"
PGID = "${meta.PGID}"
TZ = "America/New_York"
}
driver = "docker"
config {
image = "ghcr.io/linuxserver/syncthing"
hostname = "${NOMAD_JOB_NAME}"
volumes = [
"${meta.nfsStorageRoot}/pi-cluster/${NOMAD_JOB_NAME}:/config",
"${meta.nfsStorageRoot}/${NOMAD_JOB_NAME}:/Sync"
]
ports = ["webGUI","listen_tcp_udp","udp_proto_discovery"]
} // docker config
service {
port = "webGUI"
name = "${NOMAD_JOB_NAME}"
tags = [
"traefik.enable=true",
"traefik.http.routers.${NOMAD_JOB_NAME}.rule=Host(`${NOMAD_JOB_NAME}.{{ homelab_domain_name }}`)",
"traefik.http.routers.${NOMAD_JOB_NAME}.entryPoints=web,websecure",
"traefik.http.routers.${NOMAD_JOB_NAME}.service=syncthing",
"traefik.http.routers.${NOMAD_JOB_NAME}.tls=true",
"traefik.http.routers.${NOMAD_JOB_NAME}.tls.certresolver=cloudflare",
"traefik.http.routers.${NOMAD_JOB_NAME}.middlewares=authelia@file"
]
check {
type = "tcp"
port = "webGUI"
interval = "30s"
timeout = "4s"
}
check_restart {
limit = 0
grace = "1m"
ignore_warnings = true
}
} // service
resources {
cpu = 1200 # MHz
memory = 300 # MB
} // resources
} // task
} // group
} // job

View File

@@ -0,0 +1,191 @@
job "TEMPLATE" {
region = "global"
datacenters = ["{{ datacenter_name }}"]
type = "service"
update {
max_parallel = 1
health_check = "checks"
min_healthy_time = "10s"
healthy_deadline = "5m"
progress_deadline = "10m"
auto_revert = true
canary = 0
stagger = "30s"
}
group "TEMPLATE-db-group" {
count = 1
restart {
attempts = 0
delay = "30s"
}
network {
port "port1" {
static = "80"
to = "80"
}
}
task "TEMPLATE-db" {
// constraint {
// attribute = "${node.unique.name}"
// operator = "regexp"
// value = "rpi(1|2|3)"
// }
env {
// PUID = "${meta.PUID}"
// PGID = "${meta.PGID}"
// TZ = "America/New_York"
}
driver = "docker"
config {
image = ""
hostname = "${NOMAD_JOB_NAME}1"
volumes = [
"${meta.nfsStorageRoot}/pi-cluster/${NOMAD_JOB_NAME}1:/data",
"/etc/timezone:/etc/timezone:ro",
"/etc/localtime:/etc/localtime:ro"
]
ports = ["port1"]
} // docker config
service {
port = "port1"
name = "${NOMAD_JOB_NAME}1"
tags = [
"traefik.enable=true",
"traefik.http.routers.${NOMAD_JOB_NAME}1.rule=Host(`${NOMAD_JOB_NAME}1.{{ homelab_domain_name }}`)",
"traefik.http.routers.${NOMAD_JOB_NAME}1.entryPoints=web,websecure",
"traefik.http.routers.${NOMAD_JOB_NAME}1.service=${NOMAD_JOB_NAME}1",
"traefik.http.routers.${NOMAD_JOB_NAME}1.tls=true",,
"traefik.http.routers.${NOMAD_JOB_NAME}1.tls.certresolver=cloudflare",
"traefik.http.routers.${NOMAD_JOB_NAME}1.middlewares=authelia@file"
]
check {
type = "tcp"
port = "port1"
interval = "30s"
timeout = "4s"
}
check_restart {
limit = 0
grace = "1m"
ignore_warnings = true
}
} // service
// resources {
// cpu = 40 # MHz
// memory = 10 # MB
// }
} // resources
} // task
} // group
group "TEMPLATE-app-group" {
restart {
attempts = 1
delay = "30s"
}
network {
port "port2" {
static = "443"
to = "443"
}
}
task "await-TEMPLATEdb" {
driver = "docker"
config {
image = "busybox:latest"
command = "sh"
args = ["-c", "echo -n 'Waiting for service'; until nslookup ${NOMAD_JOB_NAME}1.service.consul 2>&1 >/dev/null; do echo '.'; sleep 2; done"]
network_mode = "host"
}
resources {
cpu = 200
memory = 128
}
lifecycle {
hook = "prestart"
sidecar = false
}
} // /task
task "TEMPLATE" {
// constraint {
// attribute = "${node.unique.name}"
// operator = "regexp"
// value = "rpi(1|2|3)"
// }
// env {
// PUID = "${meta.PUID}"
// PGID = "${meta.PGID}"
// TZ = "America/New_York"
// }
driver = "docker"
config {
image = ""
hostname = "${NOMAD_TASK_NAME}"
volumes = [
"${meta.nfsStorageRoot}/pi-cluster/${NOMAD_TASK_NAME}:/data",
"/etc/timezone:/etc/timezone:ro",
"/etc/localtime:/etc/localtime:ro"
]
ports = ["port2"]
}
service {
name = "${NOMAD_TASK_NAME}"
port = "port2"
tags = [
"traefik.enable=true",
"traefik.http.routers.${NOMAD_TASK_NAME}.rule=Host(`${NOMAD_TASK_NAME}.{{ homelab_domain_name }}`)",
"traefik.http.routers.${NOMAD_TASK_NAME}.entryPoints=web,websecure",
"traefik.http.routers.${NOMAD_TASK_NAME}.service=${NOMAD_TASK_NAME}",
"traefik.http.routers.${NOMAD_TASK_NAME}.tls=true",,
"traefik.http.routers.${NOMAD_TASK_NAME}.tls.certresolver=cloudflare",
"traefik.http.routers.${NOMAD_TASK_NAME}.middlewares=authelia@file"
"traefik.http.routers.${NOMAD_TASK_NAME}.priority=1"
]
check {
type = "http"
port = "port2"
path = "/"
interval = "5m"
timeout = "1m"
}
check_restart {
limit = 3
grace = "1m"
ignore_warnings = true
}
} // service
// resources {
// cpu = 100 # MHz
// memory = 300 # MB
// }
} // TASK
} // close group
} // job

View File

@@ -0,0 +1,95 @@
job "TEMPLATE" {
region = "global"
datacenters = ["{{ datacenter_name }}"]
type = "service"
// constraint {
// attribute = "${node.unique.name}"
// operator = "regexp"
// value = "rpi(1|2|3)"
// }
update {
max_parallel = 1
health_check = "checks"
min_healthy_time = "10s"
healthy_deadline = "5m"
progress_deadline = "10m"
auto_revert = true
canary = 0
stagger = "30s"
}
group "TEMPLATE" {
count = 1
restart {
attempts = 0
delay = "30s"
}
network {
port "port1" {
static = "80"
to = "80"
}
}
task "TEMPLATE" {
// env {
// PUID = "${meta.PUID}"
// PGID = "${meta.PGID}"
// }
driver = "docker"
config {
image = ""
hostname = "${NOMAD_TASK_NAME}"
volumes = [
"${meta.nfsStorageRoot}/pi-cluster/${NOMAD_TASK_NAME}:/etc/TEMPLATE/",
"/etc/timezone:/etc/timezone:ro",
"/etc/localtime:/etc/localtime:ro"
]
ports = ["port1"]
} // docker config
service {
port = "port1"
name = "${NOMAD_TASK_NAME}"
tags = [
"traefik.enable=true",
"traefik.http.routers.${NOMAD_TASK_NAME}.rule=Host(`${NOMAD_JOB_NAME}.{{ homelab_domain_name }}`)",
"traefik.http.routers.${NOMAD_TASK_NAME}.entryPoints=web,websecure",
"traefik.http.routers.${NOMAD_TASK_NAME}.service=${NOMAD_TASK_NAME}",
"traefik.http.routers.${NOMAD_TASK_NAME}.tls=true",
"traefik.http.routers.${NOMAD_TASK_NAME}.tls.certresolver=cloudflare",
"traefik.http.routers.${NOMAD_TASK_NAME}.middlewares=authelia@file"
]
check {
type = "tcp"
port = "port1"
interval = "30s"
timeout = "4s"
}
check_restart {
limit = 0
grace = "1m"
ignore_warnings = true
}
} // service
// resources {
// cpu = 100 # MHz
// memory = 300 # MB
// } // resources
} // task
} // group
} // job

View File

@@ -0,0 +1,128 @@
job "TEMPLATE" {
region = "global"
datacenters = ["{{ datacenter_name }}"]
type = "service"
// constraint {
// attribute = "${node.unique.name}"
// operator = "regexp"
// value = "rpi4"
// }
update {
max_parallel = 1
health_check = "checks"
min_healthy_time = "10s"
healthy_deadline = "5m"
progress_deadline = "10m"
auto_revert = true
canary = 0
stagger = "30s"
}
group "TEMPLATE-group" {
count = 1
restart {
attempts = 0
delay = "10m"
}
network {
port "port1" {
static = ""
to = ""
}
}
task "create_filesystem" {
// Copy the most recent backup into place on the local computer. sonarr will not work with
// its database in an NFS share
driver = "raw_exec"
config {
# When running a binary that exists on the host, the path must be absolute
command = "${meta.restoreCommand}"
args = ["${meta.restoreCommand1}", "${meta.restoreCommand2}", "${NOMAD_JOB_NAME}", "${meta.restoreCommand3}"]
}
lifecycle {
hook = "prestart"
sidecar = false
}
} // /task create_filesystem
task "TEMPLATE" {
env {
PUID = "${meta.PUID}"
PGID = "${meta.PGID}"
TZ = "America/New_York"
}
driver = "docker"
config {
image = ""
hostname = "${NOMAD_TASK_NAME}"
ports = ["port1"]
volumes = [
"${meta.localStorageRoot}/${NOMAD_TASK_NAME}:/config"
]
} // docker config
service {
port = "port1"
name = "${NOMAD_TASK_NAME}"
tags = [
"traefik.enable=true",
"traefik.http.routers.${NOMAD_TASK_NAME}.rule=Host(`${NOMAD_JOB_NAME}.{{ homelab_domain_name }}`)",
"traefik.http.routers.${NOMAD_TASK_NAME}.entryPoints=web,websecure",
"traefik.http.routers.${NOMAD_TASK_NAME}.service=${NOMAD_TASK_NAME}",
"traefik.http.routers.${NOMAD_TASK_NAME}.tls=true",
"traefik.http.routers.${NOMAD_TASK_NAME}.tls.certresolver=cloudflare",
"traefik.http.routers.${NOMAD_TASK_NAME}.middlewares=authelia@file"
]
check {
type = "tcp"
port = "port1"
interval = "30s"
timeout = "4s"
}
check_restart {
limit = 0
grace = "1m"
ignore_warnings = true
}
} // service
resources {
cpu = 1000 # MHz
memory = 400 # MB
} // resources
} // /task ${NOMAD_JOB_NAME}
task "save_configuration" {
driver = "raw_exec"
config {
# When running a binary that exists on the host, the path must be absolute
command = "${meta.backupCommand}"
args = ["${meta.backupAllocArg1}", "${meta.backupAllocArg2}", "${meta.backupAllocArg3}", "${meta.backupAllocArg4}", "${meta.backupAllocArg5}", "${NOMAD_JOB_NAME}", "${meta.backupAllocArg6}"]
}
lifecycle {
hook = "poststop"
sidecar = false
}
} // /task save_configuration
} // group
} // job

View File

@@ -0,0 +1,27 @@
job "execTest" {
region = "global"
datacenters = ["{{ datacenter_name }}"]
type = "batch"
constraint {
attribute = "${node.unique.name}"
operator = "regexp"
value = "rpi3"
}
group "testing" {
task "execTest" {
driver = "raw_exec"
config {
command = "/usr/local/bin/backup_configs"
args = ["--verbose","--job","sonarr"]
}
resources {
cpu = 500
memory = 256
}
}
}
}

View File

@@ -0,0 +1,110 @@
job "uptimekuma" {
region = "global"
datacenters = ["{{ datacenter_name }}"]
type = "service"
update {
max_parallel = 1
health_check = "checks"
min_healthy_time = "10s"
healthy_deadline = "5m"
progress_deadline = "10m"
auto_revert = true
canary = 0
stagger = "30s"
}
group "uptimekumaGroup" {
count = 1
restart {
attempts = 0
delay = "30s"
}
network {
port "web" {
to = "3001"
}
}
task "create_filesystem" {
// Copy the most recent backup into place on the local computer. sonarr will not work with
// its database in an NFS share
driver = "raw_exec"
config {
# When running a binary that exists on the host, the path must be absolute
command = "${meta.restoreCommand}"
args = ["${meta.restoreCommand1}", "${meta.restoreCommand2}", "${NOMAD_JOB_NAME}", "${meta.restoreCommand3}"]
}
lifecycle {
hook = "prestart"
sidecar = false
}
} // /task create_filesystem
task "uptimekuma" {
// env {
// PUID = "${meta.PUID}"
// PGID = "${meta.PGID}"
// }
driver = "docker"
config {
image = "louislam/uptime-kuma:latest"
hostname = "${NOMAD_JOB_NAME}"
volumes = [ "${meta.localStorageRoot}/uptimekuma:/app/data" ]
ports = ["web"]
} // docker config
service {
port = "web"
name = "${NOMAD_JOB_NAME}"
tags = [
"traefik.enable=true",
"traefik.http.routers.${NOMAD_JOB_NAME}.rule=Host(`uptime.{{ homelab_domain_name }}`)",
"traefik.http.routers.${NOMAD_JOB_NAME}.entryPoints=web,websecure",
"traefik.http.routers.${NOMAD_JOB_NAME}.service=${NOMAD_JOB_NAME}",
"traefik.http.routers.${NOMAD_JOB_NAME}.tls=true",
"traefik.http.routers.${NOMAD_JOB_NAME}.tls.certresolver=cloudflare"
]
check {
type = "tcp"
port = "web"
interval = "30s"
timeout = "4s"
}
check_restart {
limit = 0
grace = "1m"
ignore_warnings = true
}
} // service
resources {
cpu = 400 # MHz
memory = 100 # MB
} // resources
} // task
task "save_configuration" {
driver = "raw_exec"
config {
# When running a binary that exists on the host, the path must be absolute
command = "${meta.backupCommand}"
args = ["${meta.backupAllocArg1}", "${meta.backupAllocArg2}", "${meta.backupAllocArg3}", "${meta.backupAllocArg4}", "${meta.backupAllocArg5}", "${NOMAD_JOB_NAME}", "${meta.backupAllocArg6}"]
}
lifecycle {
hook = "poststop"
sidecar = false
}
} // /task save_configuration
} // group
} // job

View File

@@ -0,0 +1,95 @@
job "whoogle" {
region = "global"
datacenters = ["{{ datacenter_name }}"]
type = "service"
// constraint {
// attribute = "${node.unique.name}"
// operator = "regexp"
// value = "rpi(1|2|3)"
// }
update {
max_parallel = 1
health_check = "checks"
min_healthy_time = "10s"
healthy_deadline = "5m"
progress_deadline = "10m"
auto_revert = true
canary = 0
stagger = "30s"
}
group "whoogle" {
restart {
attempts = 0
delay = "30s"
}
network {
port "whoogle" {
to = "5000"
}
}
task "whoogle" {
env {
WHOOGLE_CONFIG_BLOCK = "pinterest.com"
WHOOGLE_CONFIG_DISABLE = "1"
WHOOGLE_CONFIG_GET_ONLY = "1"
WHOOGLE_CONFIG_LANGUAGE = "lang_en"
WHOOGLE_CONFIG_NEW_TAB = "0"
WHOOGLE_CONFIG_SEARCH_LANGUAGE = "lang_en"
WHOOGLE_CONFIG_THEME = "light"
WHOOGLE_CONFIG_URL = "https://${NOMAD_JOB_NAME}.{{ homelab_domain_name }}"
WHOOGLE_CONFIG_VIEW_IMAGE = "1"
WHOOGLE_RESULTS_PER_PAGE = "20"
}
driver = "docker"
config {
image = "benbusby/whoogle-search:latest"
hostname = "${NOMAD_JOB_NAME}"
ports = ["whoogle"]
} // docker config
service {
port = "whoogle"
name = "${NOMAD_JOB_NAME}"
tags = [
"traefik.enable=true",
"traefik.http.routers.${NOMAD_JOB_NAME}.rule=Host(`${NOMAD_JOB_NAME}.{{ homelab_domain_name }}`)",
"traefik.http.routers.${NOMAD_JOB_NAME}.entryPoints=web,websecure",
"traefik.http.routers.${NOMAD_JOB_NAME}.service=${NOMAD_JOB_NAME}",
"traefik.http.routers.${NOMAD_JOB_NAME}.tls=true",
"traefik.http.routers.${NOMAD_JOB_NAME}.tls.certresolver=cloudflare"
]
check {
type = "http"
path = "/"
interval = "30s"
timeout = "4s"
}
check_restart {
limit = 0
grace = "1m"
ignore_warnings = true
}
} // service
// resources {
// cpu = 100 # MHz
// memory = 300 # MB
// } // resources
} // task
} // group
} // job

View File

@@ -0,0 +1,257 @@
job "wikijs" {
region = "global"
datacenters = ["{{ datacenter_name }}"]
type = "service"
update {
max_parallel = 1
health_check = "checks"
min_healthy_time = "10s"
healthy_deadline = "5m"
progress_deadline = "10m"
auto_revert = true
canary = 0
stagger = "30s"
}
group "wikijs_db_group" {
restart {
attempts = 1
delay = "30s"
}
network {
port "db" {
static = "5434"
to = "5432"
}
}
task "await_db_filesytem" {
constraint {
attribute = "${node.unique.name}"
value = "macmini"
}
driver = "docker"
config {
image = "busybox:latest"
command = "sh"
args = [
"-c",
"echo -n 'Waiting for /etc/postgresql/postgresql.conf to be available'; until [ -f /etc/postgresql/my-postgres.conf ]; do echo '.'; sleep 2; done",
]
network_mode = "host"
volumes = [
"/Users/{{ my_username }}/cluster/wikidb:/etc/postgresql"
]
}
lifecycle {
hook = "prestart"
sidecar = false
}
} // /task
task "await_backup_filesytem" {
constraint {
attribute = "${node.unique.name}"
value = "macmini"
}
driver = "docker"
config {
image = "busybox:latest"
command = "sh"
args = [
"-c",
"echo -n 'Waiting for /backups to be available'; until [ -f /backups/dbBackup.log ]; do echo '.'; sleep 2; done",
]
network_mode = "host"
volumes = [
"${meta.nfsStorageRoot}/pi-cluster/backups/wikijsdb:/backups"
]
}
lifecycle {
hook = "prestart"
sidecar = false
}
} // /task
task "wikijs_db" {
constraint {
attribute = "${node.unique.name}"
value = "macmini"
}
env {
PUID = "${meta.PUID}"
PGID = "${meta.PGID}"
TZ = "America/New_York"
POSTGRES_USER = "wikijs"
POSTGRES_PASSWORD = "wikijs"
POSTGRES_DB = "wikijs"
PGDATA = "/var/lib/postgresql/data/pgdata"
}
driver = "docker"
config {
image = "postgres:9.6.17"
hostname = "wikijs_db"
volumes = [
"/Users/{{ my_username }}/cluster/wikidb/pgdata:/var/lib/postgresql/data",
"/Users/{{ my_username }}/cluster/wikidb/my-postgres.conf:/etc/postgresql/postgresql.conf",
"/Users/{{ my_username }}/cluster/wikidb/entrypoint:/docker-entrypoint-initdb.d",
"${meta.nfsStorageRoot}/pi-cluster/backups/wikijsdb:/backups"
]
ports = ["db"]
}
artifact {
source = "git::https://github.com/{{ my_username }}/db_scripts.git"
destination = "local/scripts"
}
service {
port = "db"
name = "wikijsdb"
check {
type = "tcp"
port = "db"
interval = "30s"
timeout = "4s"
}
check_restart {
limit = 2
grace = "1m"
ignore_warnings = true
}
}
resources {
cpu = 55 # MHz
memory = 60 # MB
}
} // /task
} // /group
group "wikijs_app_group" {
restart {
attempts = 1
delay = "30s"
}
network {
port "http" {
to = "3000"
}
}
task "await_database" {
driver = "docker"
config {
image = "busybox:latest"
command = "sh"
args = [
"-c",
"echo -n 'Waiting for wikijsdb.service.consul to come alive'; until nslookup wikijsdb.service.consul 2>&1 >/dev/null; do echo '.'; sleep 2; done"
]
network_mode = "host"
}
resources {
cpu = 200
memory = 128
}
lifecycle {
hook = "prestart"
sidecar = false
}
} // /task
task "await_filesytem" {
driver = "docker"
config {
image = "busybox:latest"
command = "sh"
args = [
"-c",
"echo -n 'Waiting for ${meta.nfsStorageRoot}/pi-cluster/wikijs/ to be mounted'; until less -E /wiki/config.yml | grep 'wikijsdb.service.consul' 2>&1 >/dev/null; do echo '.'; sleep 2; done",
]
network_mode = "host"
volumes = [
"${meta.nfsStorageRoot}/pi-cluster/wikijs/config/config.yml:/wiki/config.yml"
]
}
lifecycle {
hook = "prestart"
sidecar = false
}
} // /task
task "wikijs_app" {
env {
PUID = "${meta.PUID}"
PGID = "${meta.PGID}"
TZ = "America/New_York"
}
driver = "docker"
config {
image = "linuxserver/wikijs:version-2.5.170"
hostname = "wikijs-app"
volumes = [
"${meta.nfsStorageRoot}/pi-cluster/wikijs/config/config.yml:/wiki/config.yml",
"${meta.nfsStorageRoot}/pi-cluster/wikijs/config:/config",
"${meta.nfsStorageRoot}/pi-cluster/wikijs/data/:/data"
]
ports = ["http"]
} // /config
service {
port = "http"
name = "wikijs"
tags = [
"traefik.enable=true",
"traefik.http.routers.wikijs.rule=Host(`wiki.{{ homelab_domain_name }}`)",
"traefik.http.routers.wikijs.entryPoints=web,websecure",
"traefik.http.routers.wikijs.service=wikijs",
"traefik.http.routers.wikijs.tls=true"
]
check {
type = "http"
path = "/"
interval = "90s"
timeout = "15s"
}
check_restart {
limit = 3
grace = "30s"
ignore_warnings = true
}
} // /service
resources {
// cpu = 100 # MHz
// memory = 60 # MB
}
} // /task
} // /group
} // job

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,948 @@
#!/usr/bin/env bash
_mainScript_() {
_setPATH_ "/bin" "/usr/bin" "/usr/local/bin"
debug "whoami: $(whoami)"
if ! _rootAvailable_; then fatal "This script must be run as root"; fi
if [ -z ${JOB:-} ]; then
error "Service name is not set"
_safeExit_ 1
fi
JOB_DIR="{{ interpolated_localfs_service_storage }}/${JOB}"
debug "JOB_DIR: ${JOB_DIR}" ${LINENO}
if [ -z ${JOB:-} ] || [ ! -d "${JOB_DIR}" ]; then
error "Can not find job directory: ${JOB_DIR}" "${LINENO}"
_safeExit_ 1
fi
if [ ! -d "${BACKUP_DIR}" ]; then
error "Can not find backup directory: ${BACKUP_DIR}" "${LINENO}"
_safeExit_ 1
fi
# Identify the latest backup
# shellcheck disable=SC2010
MOST_RECENT_BACKUP="$(ls "${BACKUP_DIR}" | grep --color=never "${JOB}" | sort -n -t _ -k 2 | tail -1)"
if [ -f "${BACKUP_DIR}/${MOST_RECENT_BACKUP}" ]; then
debug "Most recent backup: ${MOST_RECENT_BACKUP}"
else
error "Most recent backup does not exist" "${LINENO}"
fi
# Don't run as root on macOS
if [[ $(_detectOS_) == mac ]]; then
# Ensure destination directory is clean
_execute_ "command rm -rf \"${JOB_DIR:?}\""
_execute_ "mkdir \"${JOB_DIR:?}\""
# Extract the backup
if [ ${DRYRUN} == true ]; then
dryrun "tar zxvf \"${BACKUP_DIR}/${MOST_RECENT_BACKUP}\" -C \"${JOB_DIR}\""
elif tar zxvf "${BACKUP_DIR}/${MOST_RECENT_BACKUP}" -C "${JOB_DIR}"; then
info "Restore successful"
else
error "Restore failed"
_safeExit_ 1
fi
# Ensure permissions are correct
_execute_ "chown -R {{ ansible_user_uid }}:{{ ansible_user_gid }} \"${JOB_DIR}\""
else
# Ensure destination directory is clean
_execute_ "_runAsRoot_ command rm -rf \"${JOB_DIR:?}\""
_execute_ "_runAsRoot_ mkdir \"${JOB_DIR:?}\""
# Extract the backup
if [ ${DRYRUN} == true ]; then
dryrun "_runAsRoot_ tar zxvf \"${BACKUP_DIR}/${MOST_RECENT_BACKUP}\" -C \"${JOB_DIR}\""
elif _runAsRoot_ tar zxvf "${BACKUP_DIR}/${MOST_RECENT_BACKUP}" -C "${JOB_DIR}"; then
info "Restore successful"
else
error "Restore failed"
_safeExit_ 1
fi
# Ensure permissions are correct
_execute_ "_runAsRoot_ chown -R {{ ansible_user_uid }}:{{ ansible_user_gid }} \"${JOB_DIR}\""
fi
notice "Restored: ${MOST_RECENT_BACKUP}"
}
# end _mainScript_
# ################################## Flags and defaults
# Required variables
QUIET=false
LOGLEVEL=NOTICE
VERBOSE=false
FORCE=false
DRYRUN=false
declare -a ARGS=()
# Script specific
MULTIHOST=false
BACKUP_DIR="{{ interpolated_nfs_service_storage }}/pi-cluster/backups/config_backups"
LOGFILE="{{ interpolated_nfs_service_storage }}/pi-cluster/logs/$(basename "$0").log"
# ################################## Custom utility functions (Pasted from repository)
_execute_() {
# DESC:
# Executes commands while respecting global DRYRUN, VERBOSE, LOGGING, and QUIET flags
# ARGS:
# $1 (Required) - The command to be executed. Quotation marks MUST be escaped.
# $2 (Optional) - String to display after command is executed
# OPTS:
# -v Always print output from the execute function to STDOUT
# -n Use NOTICE level alerting (default is INFO)
# -p Pass a failed command with 'return 0'. This effectively bypasses set -e.
# -e Bypass _alert_ functions and use 'echo RESULT'
# -s Use '_alert_ success' for successful output. (default is 'info')
# -q Do not print output (QUIET mode)
# OUTS:
# stdout: Configurable output
# USE :
# _execute_ "cp -R \"~/dir/somefile.txt\" \"someNewFile.txt\"" "Optional message"
# _execute_ -sv "mkdir \"some/dir\""
# NOTE:
# If $DRYRUN=true, no commands are executed and the command that would have been executed
# is printed to STDOUT using dryrun level alerting
# If $VERBOSE=true, the command's native output is printed to stdout. This can be forced
# with '_execute_ -v'
local _localVerbose=false
local _passFailures=false
local _echoResult=false
local _echoSuccessResult=false
local _quietMode=false
local _echoNoticeResult=false
local opt
local OPTIND=1
while getopts ":vVpPeEsSqQnN" opt; do
case $opt in
v | V) _localVerbose=true ;;
p | P) _passFailures=true ;;
e | E) _echoResult=true ;;
s | S) _echoSuccessResult=true ;;
q | Q) _quietMode=true ;;
n | N) _echoNoticeResult=true ;;
*)
{
error "Unrecognized option '$1' passed to _execute_. Exiting."
_safeExit_
}
;;
esac
done
shift $((OPTIND - 1))
[[ $# == 0 ]] && fatal "Missing required argument to ${FUNCNAME[0]}"
local _command="${1}"
local _executeMessage="${2:-$1}"
local _saveVerbose=${VERBOSE}
if "${_localVerbose}"; then
VERBOSE=true
fi
if "${DRYRUN}"; then
if "${_quietMode}"; then
VERBOSE=${_saveVerbose}
return 0
fi
if [ -n "${2:-}" ]; then
dryrun "${1} (${2})" "$(caller)"
else
dryrun "${1}" "$(caller)"
fi
elif ${VERBOSE}; then
if eval "${_command}"; then
if "${_quietMode}"; then
VERBOSE=${_saveVerbose}
elif "${_echoResult}"; then
printf "%s\n" "${_executeMessage}"
elif "${_echoSuccessResult}"; then
success "${_executeMessage}"
elif "${_echoNoticeResult}"; then
notice "${_executeMessage}"
else
info "${_executeMessage}"
fi
else
if "${_quietMode}"; then
VERBOSE=${_saveVerbose}
elif "${_echoResult}"; then
printf "%s\n" "warning: ${_executeMessage}"
else
warning "${_executeMessage}"
fi
VERBOSE=${_saveVerbose}
"${_passFailures}" && return 0 || return 1
fi
else
if eval "${_command}" >/dev/null 2>&1; then
if "${_quietMode}"; then
VERBOSE=${_saveVerbose}
elif "${_echoResult}"; then
printf "%s\n" "${_executeMessage}"
elif "${_echoSuccessResult}"; then
success "${_executeMessage}"
elif "${_echoNoticeResult}"; then
notice "${_executeMessage}"
else
info "${_executeMessage}"
fi
else
if "${_quietMode}"; then
VERBOSE=$_saveVerbose
elif "${_echoResult}"; then
printf "%s\n" "error: ${_executeMessage}"
else
warning "${_executeMessage}"
fi
VERBOSE=${_saveVerbose}
"${_passFailures}" && return 0 || return 1
fi
fi
VERBOSE=${_saveVerbose}
return 0
}
_runAsRoot_() {
# DESC:
# Run the requested command as root (via sudo if requested)
# ARGS:
# $1 (optional): Set to zero to not attempt execution via sudo
# $@ (required): Passed through for execution as root user
# OUTS:
# Runs the requested command as root
# CREDIT:
# https://github.com/ralish/bash-script-template
[[ $# == 0 ]] && fatal "Missing required argument to ${FUNCNAME[0]}"
local _skip_sudo=false
if [[ ${1} =~ ^0$ ]]; then
_skip_sudo=true
shift
fi
if [[ ${EUID} -eq 0 ]]; then
"$@"
elif [[ -z ${_skip_sudo} ]]; then
sudo -H -- "$@"
else
fatal "Unable to run requested command as root: $*"
fi
}
_rootAvailable_() {
# DESC:
# Validate we have superuser access as root (via sudo if requested)
# ARGS:
# $1 (optional): Set to any value to not attempt root access via sudo
# OUTS:
# 0 if true
# 1 if false
# CREDIT:
# https://github.com/ralish/bash-script-template
local _superuser
local _testEUID
if [[ ${EUID} -eq 0 ]]; then
_superuser=true
elif [[ -z ${1:-} ]]; then
debug 'Sudo: Updating cached credentials ...'
if sudo -v; then
if [[ $(sudo -H -- "$BASH" -c 'printf "%s" "$EUID"') -eq 0 ]]; then
_superuser=true
else
_superuser=false
fi
else
_superuser=false
fi
fi
if [[ ${_superuser} == true ]]; then
debug 'Successfully acquired superuser credentials.'
return 0
else
debug 'Unable to acquire superuser credentials.'
return 1
fi
}
_detectOS_() {
# DESC:
# Identify the OS the script is run on
# ARGS:
# None
# OUTS:
# 0 - Success
# 1 - Failed to detect OS
# stdout: One of 'mac', 'linux', 'windows'
# USAGE:
# _detectOS_
# CREDIT:
# https://github.com/labbots/bash-utility
local _uname
local _os
if _uname=$(command -v uname); then
case $("${_uname}" | tr '[:upper:]' '[:lower:]') in
linux*)
_os="linux"
;;
darwin*)
_os="mac"
;;
msys* | cygwin* | mingw* | nt | win*)
# or possible 'bash on windows'
_os="windows"
;;
*)
return 1
;;
esac
else
return 1
fi
printf "%s" "${_os}"
}
# ################################## Functions required for this template to work
# Functions for providing alerts to the user and printing them to the log
_setColors_() {
# DESC:
# Sets colors use for alerts.
# ARGS:
# None
# OUTS:
# None
# USAGE:
# echo "${blue}Some text${reset}"
if tput setaf 1 >/dev/null 2>&1; then
bold=$(tput bold)
underline=$(tput smul)
reverse=$(tput rev)
reset=$(tput sgr0)
if [[ $(tput colors) -ge 256 ]] >/dev/null 2>&1; then
white=$(tput setaf 231)
blue=$(tput setaf 38)
yellow=$(tput setaf 11)
tan=$(tput setaf 3)
green=$(tput setaf 82)
red=$(tput setaf 1)
purple=$(tput setaf 171)
gray=$(tput setaf 250)
else
white=$(tput setaf 7)
blue=$(tput setaf 38)
yellow=$(tput setaf 3)
tan=$(tput setaf 3)
green=$(tput setaf 2)
red=$(tput setaf 1)
purple=$(tput setaf 13)
gray=$(tput setaf 7)
fi
else
bold="\033[4;37m"
reset="\033[0m"
underline="\033[4;37m"
reverse=""
white="\033[0;37m"
blue="\033[0;34m"
yellow="\033[0;33m"
tan="\033[0;33m"
green="\033[1;32m"
red="\033[0;31m"
purple="\033[0;35m"
gray="\033[0;37m"
fi
}
_alert_() {
# DESC:
# Controls all printing of messages to log files and stdout.
# ARGS:
# $1 (required) - The type of alert to print
# (success, header, notice, dryrun, debug, warning, error,
# fatal, info, input)
# $2 (required) - The message to be printed to stdout and/or a log file
# $3 (optional) - Pass '${LINENO}' to print the line number where the _alert_ was triggered
# OUTS:
# stdout: The message is printed to stdout
# log file: The message is printed to a log file
# USAGE:
# [_alertType] "[MESSAGE]" "${LINENO}"
# NOTES:
# - The colors of each alert type are set in this function
# - For specified alert types, the funcstac will be printed
local _color
local _alertType="${1}"
local _message="${2}"
local _line="${3:-}" # Optional line number
[[ $# -lt 2 ]] && fatal 'Missing required argument to _alert_'
if [[ -n ${_line} && ${_alertType} =~ ^(fatal|error) && ${FUNCNAME[2]} != "_trapCleanup_" ]]; then
_message="${_message} ${gray}(line: ${_line}) $(_printFuncStack_)"
elif [[ -n ${_line} && ${FUNCNAME[2]} != "_trapCleanup_" ]]; then
_message="${_message} ${gray}(line: ${_line})"
elif [[ -z ${_line} && ${_alertType} =~ ^(fatal|error) && ${FUNCNAME[2]} != "_trapCleanup_" ]]; then
_message="${_message} ${gray}$(_printFuncStack_)"
fi
if [[ ${_alertType} =~ ^(error|fatal) ]]; then
_color="${bold}${red}"
elif [ "${_alertType}" == "info" ]; then
_color="${gray}"
elif [ "${_alertType}" == "warning" ]; then
_color="${red}"
elif [ "${_alertType}" == "success" ]; then
_color="${green}"
elif [ "${_alertType}" == "debug" ]; then
_color="${purple}"
elif [ "${_alertType}" == "header" ]; then
_color="${bold}${white}${underline}"
elif [ ${_alertType} == "notice" ]; then
_color="${bold}"
elif [ ${_alertType} == "input" ]; then
_color="${bold}${underline}"
elif [ "${_alertType}" = "dryrun" ]; then
_color="${blue}"
else
_color=""
fi
_writeToScreen_() {
("${QUIET}") && return 0 # Print to console when script is not 'quiet'
[[ ${VERBOSE} == false && ${_alertType} =~ ^(debug|verbose) ]] && return 0
if ! [[ -t 1 || -z ${TERM:-} ]]; then # Don't use colors on non-recognized terminals
_color=""
reset=""
fi
if [[ ${_alertType} == header ]]; then
printf "${_color}%s${reset}\n" "${_message}"
else
printf "${_color}[%7s] %s${reset}\n" "${_alertType}" "${_message}"
fi
}
_writeToScreen_
_writeToLog_() {
[[ ${_alertType} == "input" ]] && return 0
[[ ${LOGLEVEL} =~ (off|OFF|Off) ]] && return 0
if [ -z "${LOGFILE:-}" ]; then
LOGFILE="$(pwd)/$(basename "$0").log"
fi
[ ! -d "$(dirname "${LOGFILE}")" ] && mkdir -p "$(dirname "${LOGFILE}")"
[[ ! -f ${LOGFILE} ]] && touch "${LOGFILE}"
# Don't use colors in logs
local cleanmessage="$(echo "${_message}" | sed -E 's/(\x1b)?\[(([0-9]{1,2})(;[0-9]{1,3}){0,2})?[mGK]//g')"
# Print message to log file
printf "%s [%7s] %s %s\n" "$(date +"%b %d %R:%S")" "${_alertType}" "[$(/bin/hostname)]" "${cleanmessage}" >>"${LOGFILE}"
}
# Write specified log level data to logfile
case "${LOGLEVEL:-ERROR}" in
ALL | all | All)
_writeToLog_
;;
DEBUG | debug | Debug)
_writeToLog_
;;
INFO | info | Info)
if [[ ${_alertType} =~ ^(error|fatal|warning|info|notice|success) ]]; then
_writeToLog_
fi
;;
NOTICE | notice | Notice)
if [[ ${_alertType} =~ ^(error|fatal|warning|notice|success) ]]; then
_writeToLog_
fi
;;
WARN | warn | Warn)
if [[ ${_alertType} =~ ^(error|fatal|warning) ]]; then
_writeToLog_
fi
;;
ERROR | error | Error)
if [[ ${_alertType} =~ ^(error|fatal) ]]; then
_writeToLog_
fi
;;
FATAL | fatal | Fatal)
if [[ ${_alertType} =~ ^fatal ]]; then
_writeToLog_
fi
;;
OFF | off)
return 0
;;
*)
if [[ ${_alertType} =~ ^(error|fatal) ]]; then
_writeToLog_
fi
;;
esac
} # /_alert_
error() { _alert_ error "${1}" "${2:-}"; }
warning() { _alert_ warning "${1}" "${2:-}"; }
notice() { _alert_ notice "${1}" "${2:-}"; }
info() { _alert_ info "${1}" "${2:-}"; }
success() { _alert_ success "${1}" "${2:-}"; }
dryrun() { _alert_ dryrun "${1}" "${2:-}"; }
input() { _alert_ input "${1}" "${2:-}"; }
header() { _alert_ header "${1}" "${2:-}"; }
debug() { _alert_ debug "${1}" "${2:-}"; }
fatal() {
_alert_ fatal "${1}" "${2:-}"
_safeExit_ "1"
}
# shellcheck disable=SC1009,SC1054,SC1056,SC1072,SC1073,SC1083
{% raw %}
_printFuncStack_() {
# DESC:
# Prints the function stack in use. Used for debugging, and error reporting.
# ARGS:
# None
# OUTS:
# stdout: Prints [function]:[file]:[line]
# NOTE:
# Does not print functions from the alert class
local _i
_funcStackResponse=()
for ((_i = 1; _i < ${#BASH_SOURCE[@]}; _i++)); do
case "${FUNCNAME[$_i]}" in "_alert_" | "_trapCleanup_" | fatal | error | warning | notice | info | debug | dryrun | header | success) continue ;; esac
_funcStackResponse+=("${FUNCNAME[$_i]}:$(basename ${BASH_SOURCE[$_i]}):${BASH_LINENO[_i - 1]}")
done
printf "( "
printf %s "${_funcStackResponse[0]}"
printf ' < %s' "${_funcStackResponse[@]:1}"
printf ' )\n'
}
{% endraw %}
_safeExit_() {
# DESC:
# Cleanup and exit from a script
# ARGS:
# $1 (optional) - Exit code (defaults to 0)
# OUTS:
# None
if [[ -d ${SCRIPT_LOCK:-} ]]; then
if command rm -rf "${SCRIPT_LOCK}"; then
debug "Removing script lock"
else
warning "Script lock could not be removed. Try manually deleting ${tan}'${LOCK_DIR}'"
fi
fi
if [[ -n ${TMP_DIR:-} && -d ${TMP_DIR:-} ]]; then
if [[ ${1:-} == 1 && -n "$(ls "${TMP_DIR}")" ]]; then
command rm -r "${TMP_DIR}"
else
command rm -r "${TMP_DIR}"
debug "Removing temp directory"
fi
fi
trap - INT TERM EXIT
exit ${1:-0}
}
_trapCleanup_() {
# DESC:
# Log errors and cleanup from script when an error is trapped. Called by 'trap'
# ARGS:
# $1: Line number where error was trapped
# $2: Line number in function
# $3: Command executing at the time of the trap
# $4: Names of all shell functions currently in the execution call stack
# $5: Scriptname
# $6: $BASH_SOURCE
# USAGE:
# trap '_trapCleanup_ ${LINENO} ${BASH_LINENO} "${BASH_COMMAND}" "${FUNCNAME[*]}" "${0}" "${BASH_SOURCE[0]}"' EXIT INT TERM SIGINT SIGQUIT SIGTERM
# OUTS:
# Exits script with error code 1
local _line=${1:-} # LINENO
local _linecallfunc=${2:-}
local _command="${3:-}"
local _funcstack="${4:-}"
local _script="${5:-}"
local _sourced="${6:-}"
if [[ "$(declare -f "fatal")" && "$(declare -f "_printFuncStack_")" ]]; then
_funcstack="'$(echo "${_funcstack}" | sed -E 's/ / < /g')'"
if [[ ${_script##*/} == "${_sourced##*/}" ]]; then
fatal "${7:-} command: '${_command}' (line: ${_line}) [func: $(_printFuncStack_)]"
else
fatal "${7:-} command: '${_command}' (func: ${_funcstack} called at line ${_linecallfunc} of '${_script##*/}') (line: ${_line} of '${_sourced##*/}') "
fi
else
printf "%s\n" "Fatal error trapped. Exiting..."
fi
if [ "$(declare -f "_safeExit_")" ]; then
_safeExit_ 1
else
exit 1
fi
}
_makeTempDir_() {
# DESC:
# Creates a temp directory to house temporary files
# ARGS:
# $1 (Optional) - First characters/word of directory name
# OUTS:
# Sets $TMP_DIR variable to the path of the temp directory
# USAGE:
# _makeTempDir_ "$(basename "$0")"
[ -d "${TMP_DIR:-}" ] && return 0
if [ -n "${1:-}" ]; then
TMP_DIR="${TMPDIR:-/tmp/}${1}.${RANDOM}.${RANDOM}.$$"
else
TMP_DIR="${TMPDIR:-/tmp/}$(basename "$0").${RANDOM}.${RANDOM}.${RANDOM}.$$"
fi
(umask 077 && mkdir "${TMP_DIR}") || {
fatal "Could not create temporary directory! Exiting."
}
debug "\$TMP_DIR=${TMP_DIR}"
}
_acquireScriptLock_() {
# DESC:
# Acquire script lock to prevent running the same script a second time before the
# first instance exits
# ARGS:
# $1 (optional) - Scope of script execution lock (system or user)
# OUTS:
# exports $SCRIPT_LOCK - Path to the directory indicating we have the script lock
# Exits script if lock cannot be acquired
# NOTE:
# If the lock was acquired it's automatically released in _safeExit_()
local _lockDir
if [[ ${1:-} == 'system' ]]; then
_lockDir="${TMPDIR:-/tmp/}$(basename "$0").lock"
else
_lockDir="${TMPDIR:-/tmp/}$(basename "$0").$UID.lock"
fi
if command mkdir "${LOCK_DIR}" 2>/dev/null; then
readonly SCRIPT_LOCK="${_lockDir}"
debug "Acquired script lock: ${yellow}${SCRIPT_LOCK}${purple}"
else
if [ "$(declare -f "_safeExit_")" ]; then
error "Unable to acquire script lock: ${tan}${LOCK_DIR}${red}"
fatal "If you trust the script isn't running, delete the lock dir"
else
printf "%s\n" "ERROR: Could not acquire script lock. If you trust the script isn't running, delete: ${LOCK_DIR}"
exit 1
fi
fi
}
_setPATH_() {
# DESC:
# Add directories to $PATH so script can find executables
# ARGS:
# $@ - One or more paths
# OUTS: Adds items to $PATH
# USAGE:
# _setPATH_ "/usr/local/bin" "${HOME}/bin" "$(npm bin)"
[[ $# == 0 ]] && fatal "Missing required argument to ${FUNCNAME[0]}"
local _newPath
for _newPath in "$@"; do
if [ -d "${_newPath}" ]; then
if ! echo "${PATH}" | grep -Eq "(^|:)${_newPath}($|:)"; then
if PATH="${_newPath}:${PATH}"; then
debug "Added '${_newPath}' to PATH"
else
return 1
fi
else
debug "_setPATH_: '${_newPath}' already exists in PATH"
fi
else
debug "_setPATH_: can not find: ${_newPath}"
return 0
fi
done
return 0
}
_useGNUutils_() {
# DESC:
# Add GNU utilities to PATH to allow consistent use of sed/grep/tar/etc. on MacOS
# ARGS:
# None
# OUTS:
# 0 if successful
# 1 if unsuccessful
# PATH: Adds GNU utilities to the path
# USAGE:
# # if ! _useGNUUtils_; then exit 1; fi
# NOTES:
# GNU utilities can be added to MacOS using Homebrew
! declare -f "_setPATH_" &>/dev/null && fatal "${FUNCNAME[0]} needs function _setPATH_"
if _setPATH_ \
"/usr/local/opt/gnu-tar/libexec/gnubin" \
"/usr/local/opt/coreutils/libexec/gnubin" \
"/usr/local/opt/gnu-sed/libexec/gnubin" \
"/usr/local/opt/grep/libexec/gnubin" \
"/usr/local/opt/findutils/libexec/gnubin" \
"/opt/homebrew/opt/findutils/libexec/gnubin" \
"/opt/homebrew/opt/gnu-sed/libexec/gnubin" \
"/opt/homebrew/opt/grep/libexec/gnubin" \
"/opt/homebrew/opt/coreutils/libexec/gnubin" \
"/opt/homebrew/opt/gnu-tar/libexec/gnubin"; then
return 0
else
return 1
fi
}
_homebrewPath_() {
# DESC:
# Add homebrew bin dir to PATH
# ARGS:
# None
# OUTS:
# 0 if successful
# 1 if unsuccessful
# PATH: Adds homebrew bin directory to PATH
# USAGE:
# # if ! _homebrewPath_; then exit 1; fi
! declare -f "_setPATH_" &>/dev/null && fatal "${FUNCNAME[0]} needs function _setPATH_"
if _uname=$(command -v uname); then
if "${_uname}" | tr '[:upper:]' '[:lower:]' | grep -q 'darwin'; then
if _setPATH_ "/usr/local/bin" "/opt/homebrew/bin"; then
return 0
else
return 1
fi
fi
else
if _setPATH_ "/usr/local/bin" "/opt/homebrew/bin"; then
return 0
else
return 1
fi
fi
}
{% raw %}
_parseOptions_() {
# DESC:
# Iterates through options passed to script and sets variables. Will break -ab into -a -b
# when needed and --foo=bar into --foo bar
# ARGS:
# $@ from command line
# OUTS:
# Sets array 'ARGS' containing all arguments passed to script that were not parsed as options
# USAGE:
# _parseOptions_ "$@"
# Iterate over options
local _optstring=h
declare -a _options
local _c
local i
while (($#)); do
case $1 in
# If option is of type -ab
-[!-]?*)
# Loop over each character starting with the second
for ((i = 1; i < ${#1}; i++)); do
_c=${1:i:1}
_options+=("-${_c}") # Add current char to options
# If option takes a required argument, and it's not the last char make
# the rest of the string its argument
if [[ ${_optstring} == *"${_c}:"* && ${1:i+1} ]]; then
_options+=("${1:i+1}")
break
fi
done
;;
# If option is of type --foo=bar
--?*=*) _options+=("${1%%=*}" "${1#*=}") ;;
# add --endopts for --
--) _options+=(--endopts) ;;
# Otherwise, nothing special
*) _options+=("$1") ;;
esac
shift
done
set -- "${_options[@]:-}"
unset _options
# Read the options and set stuff
while [[ ${1:-} == -?* ]]; do
case $1 in
# Custom options
--job)
shift
JOB="$1"
;;
# Common options
-h | --help)
_usage_
_safeExit_
;;
--loglevel)
shift
LOGLEVEL=${1}
;;
--logfile)
shift
LOGFILE="${1}"
;;
-n | --dryrun) DRYRUN=true ;;
-v | --verbose) VERBOSE=true ;;
-q | --quiet) QUIET=true ;;
--force) FORCE=true ;;
--endopts)
shift
break
;;
*)
if [ "$(declare -f "_safeExit_")" ]; then
fatal "invalid option: $1"
else
printf "%s\n" "Invalid option: $1"
exit 1
fi
;;
esac
shift
done
if [[ -z ${*} || ${*} == null ]]; then
ARGS=()
else
ARGS+=("$@") # Store the remaining user input as arguments.
fi
}
{% endraw %}
_usage_() {
cat <<USAGE_TEXT
Restores the most recent backup of a service to a target directory on the host. It is assumed
that the destination directory is already mounted on the host and available at: {{ interpolated_nfs_service_storage }}/[service name]
${bold}Options:${reset}
--job [service name] Name of the service to restore
-h, --help Display this help and exit
--loglevel [LEVEL] One of: FATAL, ERROR, WARN, INFO, NOTICE, DEBUG, ALL, OFF
(Default is 'ERROR')
--logfile [FILE] Full PATH to logfile. (Default is '${HOME}/logs/$(basename "$0").log')
-n, --dryrun Non-destructive. Makes no permanent changes.
-q, --quiet Quiet (no output)
-v, --verbose Output more information. (Items echoed to 'verbose')
--force Skip all user interaction. Implied 'Yes' to all actions.
${bold}Example Usage:${reset}
${gray}# Run the script and specify log level and log file.${reset}
$(basename "$0") -vn --logfile "/path/to/file.log" --loglevel 'WARN'
USAGE_TEXT
}
# ################################## INITIALIZE AND RUN THE SCRIPT
# (Comment or uncomment the lines below to customize script behavior)
trap '_trapCleanup_ ${LINENO} ${BASH_LINENO} "${BASH_COMMAND}" "${FUNCNAME[*]}" "${0}" "${BASH_SOURCE[0]}"' EXIT INT TERM SIGINT SIGQUIT SIGTERM
# Trap errors in subshells and functions
set -o errtrace
# Exit on error. Append '||true' if you expect an error
set -o errexit
# Use last non-zero exit code in a pipeline
set -o pipefail
# Confirm we have BASH greater than v4
[ "${BASH_VERSINFO:-0}" -ge 4 ] || {
printf "%s\n" "ERROR: BASH_VERSINFO is '${BASH_VERSINFO:-0}'. This script requires BASH v4 or greater."
exit 1
}
# Make `for f in *.txt` work when `*.txt` matches zero files
shopt -s nullglob globstar
# Set IFS to preferred implementation
IFS=$' \n\t'
# Run in debug mode
# set -o xtrace
# Initialize color constants
_setColors_
# Disallow expansion of unset variables
set -o nounset
# Force arguments when invoking the script
# [[ $# -eq 0 ]] && _parseOptions_ "-h"
# Parse arguments passed to script
_parseOptions_ "$@"
# Create a temp directory '$TMP_DIR'
# _makeTempDir_ "$(basename "$0")"
# Acquire script lock
# _acquireScriptLock_
# Add Homebrew bin directory to PATH (MacOS)
_homebrewPath_
# Source GNU utilities from Homebrew (MacOS)
_useGNUutils_
# Run the main logic script
_mainScript_
# Exit cleanly
_safeExit_

View File

@@ -0,0 +1,96 @@
# Telegraf Configuration
#
# Telegraf is entirely plugin driven. All metrics are gathered from the
# declared inputs, and sent to the declared outputs.
#
# Plugins must be declared in here to be active.
# To deactivate a plugin, comment out the name and any variables.
#
# Use 'telegraf -config telegraf.conf -test' to see what metrics a config
# file would generate.
#
# Environment variables can be used anywhere in this config file, simply surround
# them with ${}. For strings the variable must be within quotes (ie, "${STR_VAR}"),
# for numbers and booleans they should be plain (ie, ${INT_VAR}, ${BOOL_VAR})
# Global tags can be specified here in key="value" format.
[global_tags]
dc = "{{ datacenter_name }}"
ip = "{{ ansible_host }}"
# Configuration for telegraf agent
[agent]
interval = "10s" ## Default data collection interval for all inputs
round_interval = true ## ie, if interval="10s" then always collect on :00, :10, :20, etc.
metric_batch_size = 1000 ## Controls the size of writes that Telegraf sends to output plugins
metric_buffer_limit = 10000 ## Maximum number of unwritten metrics per output.
collection_jitter = "5s" ## Jitter the collection by a random amount.
flush_interval = "10s" ## Default flushing interval for all outputs.
flush_jitter = "5s" ## Jitter the flush interval by a random amount
precision = ""
debug = false ## Log at debug level.
# quiet = false ## Log only error level messages.
{% if 'pis' in group_names %}
logtarget = "file" ## destination logs can be one of "file" or "stderr"
logfile = "/var/log/telegraf/telegraf.log"
logfile_rotation_interval = "1d"
# logfile_rotation_max_size = "0MB"
logfile_rotation_max_archives = 2
{% elif 'macs' in group_names %}
logtarget = "stderr" ## destination logs can be one of "file" or "stderr"
{% endif %}
hostname = "{{ inventory_hostname }}" ## Override default hostname, if empty use os.Hostname()
omit_hostname = false ## If set to true, do no set the "host" tag in the telegraf agent.
###############################################################################
# OUTPUT PLUGINS #
###############################################################################
[[outputs.influxdb]]
urls = ["http://influxdb.service.consul:{{ influxdb_port }}"]
database = "homelab"
retention_policy = "2day"
timeout = "5s"
###############################################################################
# INPUT PLUGINS #
###############################################################################
[[inputs.cpu]] # Read metrics about cpu usage
percpu = true ## Whether to report per-cpu stats or not
totalcpu = true ## Whether to report total system cpu stats or not
collect_cpu_time = false ## If true, collect raw CPU time metrics.
report_active = false ## If true, compute and report the sum of all non-idle CPU states.
[[inputs.disk]] # Read metrics about disk usage by mount point
#mount_points = ["/mnt/usbDrive","/boot"] # Restrict the stats to only the specified mount points.
ignore_fs = ["tmpfs", "devtmpfs", "devfs", "iso9660", "overlay", "aufs", "squashfs", "nfsd", "nfs4", "smbfs"]
[[inputs.diskio]] # Read metrics about disk IO by device
[[inputs.internal]] # Collect telegraf memory stats.
collect_memstats = true
[[inputs.mem]] # Read metrics about memory usage
[[inputs.processes]] # Get the number of processes and group them by status
[[inputs.swap]] # Read metrics about swap memory usage
[[inputs.system]] # Read metrics about system load & uptime
[[inputs.net]] # Gather metrics about network interfaces
#[[inputs.netstat]] # Collect TCP connections state and UDP socket counts
{% if 'macs' not in group_names %}
[[inputs.nstat]] # Collects network metrics
{% endif %}
{% if 'pis' in group_names%}
[[inputs.ntpq]]
dns_lookup = false ## If false, add -n for ntpq command. Can reduce metric gather times.
{% endif %}
{% if 'opnsense' in group_names %}
[[inputs.ntpq]]
dns_lookup = false ## If false, add -n for ntpq command. Can reduce metric gather times.
[[inputs.wireguard]]
devices = ["wg0","wg1"]
[[inputs.pf]]
{% endif %}

View File

@@ -0,0 +1,156 @@
##############################################################################
# PROCESSOR PLUGINS #
##############################################################################
[[processors.regex]]
namepass = ["docker_container_mem"]
# Tag and field conversions defined in a separate sub-tables
[[processors.regex.tags]]
## Tag to change
key = "container_name"
## Regular expression to match on a tag value
pattern = "^([a-zA-Z0-9_]+)-\\w{8}-\\w{4}-\\w{4}-\\w{4}-\\w{12}$"
## Matches of the pattern will be replaced with this string. Use ${1}
## notation to use the text of the first submatch.
replacement = "${1}"
[[processors.regex]]
namepass = ["docker_container_net"]
# Tag and field conversions defined in a separate sub-tables
[[processors.regex.tags]]
## Tag to change
key = "container_name"
## Regular expression to match on a tag value
pattern = "^([a-zA-Z0-9_]+)-\\w{8}-\\w{4}-\\w{4}-\\w{4}-\\w{12}$"
## Matches of the pattern will be replaced with this string. Use ${1}
## notation to use the text of the first submatch.
replacement = "${1}"
[[processors.regex]]
namepass = ["docker_container_cpu"]
# Tag and field conversions defined in a separate sub-tables
[[processors.regex.tags]]
## Tag to change
key = "container_name"
## Regular expression to match on a tag value
pattern = "^([a-zA-Z0-9_]+)-\\w{8}-\\w{4}-\\w{4}-\\w{4}-\\w{12}$"
## Matches of the pattern will be replaced with this string. Use ${1}
## notation to use the text of the first submatch.
replacement = "${1}"
[[processors.regex]]
namepass = ["docker_container_blkio"]
# Tag and field conversions defined in a separate sub-tables
[[processors.regex.tags]]
## Tag to change
key = "container_name"
## Regular expression to match on a tag value
pattern = "^([a-zA-Z0-9_]+)-\\w{8}-\\w{4}-\\w{4}-\\w{4}-\\w{12}$"
## Matches of the pattern will be replaced with this string. Use ${1}
## notation to use the text of the first submatch.
replacement = "${1}"
[[processors.regex]]
namepass = ["docker_container_health"]
# Tag and field conversions defined in a separate sub-tables
[[processors.regex.tags]]
## Tag to change
key = "container_name"
## Regular expression to match on a tag value
pattern = "^([a-zA-Z0-9_]+)-\\w{8}-\\w{4}-\\w{4}-\\w{4}-\\w{12}$"
## Matches of the pattern will be replaced with this string. Use ${1}
## notation to use the text of the first submatch.
replacement = "${1}"
[[processors.regex]]
namepass = ["docker_container_status"]
# Tag and field conversions defined in a separate sub-tables
[[processors.regex.tags]]
## Tag to change
key = "container_name"
## Regular expression to match on a tag value
pattern = "^([a-zA-Z0-9_]+)-\\w{8}-\\w{4}-\\w{4}-\\w{4}-\\w{12}$"
## Matches of the pattern will be replaced with this string. Use ${1}
## notation to use the text of the first submatch.
replacement = "${1}"
###############################################################################
# INPUT PLUGINS #
###############################################################################
[[inputs.docker]]
## Docker Endpoint
## To use TCP, set endpoint = "tcp://[ip]:[port]"
## To use environment variables (ie, docker-machine), set endpoint = "ENV"
endpoint = "unix:///var/run/docker.sock"
## Set to true to collect Swarm metrics(desired_replicas, running_replicas)
## Note: configure this in one of the manager nodes in a Swarm cluster.
## configuring in multiple Swarm managers results in duplication of metrics.
gather_services = false
## Only collect metrics for these containers. Values will be appended to
## container_name_include.
## Deprecated (1.4.0), use container_name_include
container_names = []
## Set the source tag for the metrics to the container ID hostname, eg first 12 chars
source_tag = false
## Containers to include and exclude. Collect all if empty. Globs accepted.
container_name_include = []
container_name_exclude = []
## Container states to include and exclude. Globs accepted.
## When empty only containers in the "running" state will be captured.
## example: container_state_include = ["created", "restarting", "running", "removing", "paused", "exited", "dead"]
## example: container_state_exclude = ["created", "restarting", "running", "removing", "paused", "exited", "dead"]
# container_state_include = []
# container_state_exclude = []
## Timeout for docker list, info, and stats commands
timeout = "5s"
## Whether to report for each container per-device blkio (8:0, 8:1...),
## network (eth0, eth1, ...) and cpu (cpu0, cpu1, ...) stats or not.
## Usage of this setting is discouraged since it will be deprecated in favor of 'perdevice_include'.
## Default value is 'true' for backwards compatibility, please set it to 'false' so that 'perdevice_include' setting
## is honored.
perdevice = true
## Specifies for which classes a per-device metric should be issued
## Possible values are 'cpu' (cpu0, cpu1, ...), 'blkio' (8:0, 8:1, ...) and 'network' (eth0, eth1, ...)
## Please note that this setting has no effect if 'perdevice' is set to 'true'
# perdevice_include = ["cpu"]
## Whether to report for each container total blkio and network stats or not.
## Usage of this setting is discouraged since it will be deprecated in favor of 'total_include'.
## Default value is 'false' for backwards compatibility, please set it to 'true' so that 'total_include' setting
## is honored.
total = false
## Specifies for which classes a total metric should be issued. Total is an aggregated of the 'perdevice' values.
## Possible values are 'cpu', 'blkio' and 'network'
## Total 'cpu' is reported directly by Docker daemon, and 'network' and 'blkio' totals are aggregated by this plugin.
## Please note that this setting has no effect if 'total' is set to 'false'
# total_include = ["cpu", "blkio", "network"]
## docker labels to include and exclude as tags. Globs accepted.
## Note that an empty array for both will include all labels as tags
docker_label_include = []
docker_label_exclude = ["traefik.*"] # Do not report on Traefik tags
## Which environment variables should we use as a tag
tag_env = ["JAVA_HOME", "HEAP_SIZE"]
## Optional TLS Config
# tls_ca = "/etc/telegraf/ca.pem"
# tls_cert = "/etc/telegraf/cert.pem"
# tls_key = "/etc/telegraf/key.pem"
## Use TLS but skip chain & host verification
# insecure_skip_verify = false

View File

@@ -0,0 +1,22 @@
[[processors.regex]]
namepass = ["consul_health_checks"]
# Tag and field conversions defined in a separate sub-tables
[[processors.regex.tags]]
## Tag to change
key = "check_name"
## Regular expression to match on a tag value
pattern = "^service: \\W(\\w+)\\W check$"
## Matches of the pattern will be replaced with this string. Use ${1}
## notation to use the text of the first submatch.
replacement = "${1}"
[[inputs.consul]]
address = "consul.service.consul:8500"
scheme = "http"
insecure_skip_verify = true
metric_version = 2
namedrop = ["traefik.http*","traefik.enable*","traefik.tcp*"]
tagexclude = ["traefik.http*","traefik.enable*", "traefik.tcp*"]
[inputs.consul.tagdrop]
check_name = [ "Nomad Client*", "Nomad Server*", "Serf Health Status" ]

View File

@@ -0,0 +1,15 @@
[[inputs.statsd]]
protocol = "udp" # Protocol, must be "tcp", "udp4", "udp6" or "udp" (default=udp)
service_address = "127.0.0.1:8125" # Address and port to host UDP listener on
delete_gauges = true # Reset gauges every interval (default=true)
delete_counters = true # Reset counters every interval (default=true)
delete_sets = true # Reset sets every interval (default=true)
delete_timings = true # Reset timings & histograms every interval (default=true)
percentiles = [90.0] # Percentiles to calculate for timing & histogram stats
metric_separator = "_"
datadog_extensions = true # Parses tags in the datadog statsd format
allowed_pending_messages = 10000
percentile_limit = 1000
[inputs.statsd.tagdrop]
task = [ "await-*","run-*","await_*","run_*","create_*","create-*" ]
task_group = [ "await-*","run-*","await_*","run_*","create_*","create-*" ]

View File

@@ -0,0 +1,88 @@
{# Ping internal servers #}
[[processors.enum]]
[[processors.enum.mapping]]
## Name of the field to map
#field = "url"
## Name of the tag to map
tag = "url"
## Destination tag or field to be used for the mapped value. By default the
## source tag or field is used, overwriting the original value.
dest = "host"
## Default value to be used for all values not contained in the mapping
## table. When unset and no match is found, the original field will remain
## unmodified and the destination tag or field will not be created.
# default = 0
## Table of mappings
[processors.enum.mapping.value_mappings]
"10.0.30.6" = "synology"
{% for i in groups['pis'] %}
"{{ hostvars[i].ansible_host }}" = "{{ hostvars[i].inventory_hostname }}"
{% endfor %}
[[inputs.ping]]
## Hosts to send ping packets to.
# https://github.com/influxdata/telegraf/blob/release-1.13/plugins/inputs/ping/README.md
urls = [{% for i in groups['pis'] %}'{{ hostvars[i].ansible_host }}'{% if not loop.last %}, {% endif %}{% endfor %},
'10.0.30.6',
'core1.bos1.he.net',
'core1.lax1.he.net',
'core1.nyc4.he.net',
'core1.oma1.he.net',
'core1.chi1.he.net',
'core1.dal1.he.net',
'core1.den1.he.net',
'core1.mia1.he.net',
'core1.bna1.he.net',
'core1.phx1.he.net',
'core1.sea1.he.net',
'core1.blp1.he.net',
'core1.ams1.he.net',
'core1.dxb1.he.net',
'core1.jnb1.he.net',
'core1.man1.he.net',
'core1.rom1.he.net',
'core1.tyo1.he.net',
'core1.zrh3.he.net',
'core2.sao1.he.net',
'core1.sin1.he.net',
'core1.kpb1.he.net',
'core1.nbo1.he.net',
'core1.tpe1.he.net',
'core1.sto1.he.net',
'core1.ymq1.he.net',
'core2.syd1.he.net'
]
## Method used for sending pings, can be either "exec" or "native". When set
## to "exec" the systems ping command will be executed. When set to "native"
## the plugin will send pings directly.
##
## While the default is "exec" for backwards compatibility, new deployments
## are encouraged to use the "native" method for improved compatibility and
## performance.
method = "exec"
## Number of ping packets to send per interval. Corresponds to the "-c"
## option of the ping command.
count = 1
## Time to wait between sending ping packets in seconds. Operates like the
## "-i" option of the ping command.
ping_interval = 1.0
fielddrop = ["packets_received", "packets_transmitted", "ttl", "standard_deviation_ms"]
interval = "1m" ## Interval to send pings
## Specify the ping executable binary.
{% if 'pis' in group_names %}
binary = "/usr/bin/ping"
{% elif 'macs' in group_names %}
binary = "/sbin/ping"
{% else %}
binary = "/bin/ping"
{% endif %}

View File

@@ -0,0 +1,234 @@
# Telegraf Configuration
#
# Telegraf is entirely plugin driven. All metrics are gathered from the
# declared inputs, and sent to the declared outputs.
#
# Plugins must be declared in here to be active.
# To deactivate a plugin, comment out the name and any variables.
#
# Use 'telegraf -config telegraf.conf -test' to see what metrics a config
# file would generate.
#
# Environment variables can be used anywhere in this config file, simply surround
# them with ${}. For strings the variable must be within quotes (ie, "${STR_VAR}"),
# for numbers and booleans they should be plain (ie, ${INT_VAR}, ${BOOL_VAR})
# Global tags can be specified here in key="value" format.
[global_tags]
dc = "{{ datacenter_name }}"
ip = "{{ ansible_host }}"
# Configuration for telegraf agent
[agent]
interval = "10s" ## Default data collection interval for all inputs
round_interval = true ## ie, if interval="10s" then always collect on :00, :10, :20, etc.
metric_batch_size = 1000 ## Controls the size of writes that Telegraf sends to output plugins
metric_buffer_limit = 10000 ## Maximum number of unwritten metrics per output.
collection_jitter = "5s" ## Jitter the collection by a random amount.
flush_interval = "10s" ## Default flushing interval for all outputs.
flush_jitter = "5s" ## Jitter the flush interval by a random amount
precision = ""
debug = false ## Log at debug level.
# quiet = false ## Log only error level messages.
{% if 'pis' in group_names %}
logtarget = "file" ## destination logs can be one of "file" or "stderr"
logfile = "/var/log/telegraf/telegraf.log"
logfile_rotation_interval = "1d"
# logfile_rotation_max_size = "0MB"
logfile_rotation_max_archives = 2
{% elif 'macs' in group_names %}
logtarget = "stderr" ## destination logs can be one of "file" or "stderr"
{% endif %}
hostname = "{{ inventory_hostname }}" ## Override default hostname, if empty use os.Hostname()
omit_hostname = false ## If set to true, do no set the "host" tag in the telegraf agent.
###############################################################################
# OUTPUT PLUGINS #
###############################################################################
[[outputs.influxdb]]
urls = ["http://influxdb.service.consul:{{ influxdb_port }}"]
database = "homelab"
retention_policy = "2day"
timeout = "5s"
###############################################################################
# INPUT PLUGINS #
###############################################################################
[[inputs.cpu]] # Read metrics about cpu usage
percpu = true ## Whether to report per-cpu stats or not
totalcpu = true ## Whether to report total system cpu stats or not
collect_cpu_time = false ## If true, collect raw CPU time metrics.
report_active = false ## If true, compute and report the sum of all non-idle CPU states.
[[inputs.disk]] # Read metrics about disk usage by mount point
#mount_points = ["/mnt/usbDrive","/boot"] # Restrict the stats to only the specified mount points.
ignore_fs = ["tmpfs", "devtmpfs", "devfs", "iso9660", "overlay", "aufs", "squashfs", "nfsd", "nfs4", "smbfs"]
[[inputs.diskio]] # Read metrics about disk IO by device
[[inputs.internal]] # Collect telegraf memory stats.
collect_memstats = true
[[inputs.mem]] # Read metrics about memory usage
[[inputs.processes]] # Get the number of processes and group them by status
[[inputs.swap]] # Read metrics about swap memory usage
[[inputs.system]] # Read metrics about system load & uptime
[[inputs.net]] # Gather metrics about network interfaces
###############################################################################
# PROCESSOR PLUGINS #
###############################################################################
[[processors.regex]]
namepass = ["docker_container_mem"]
# Tag and field conversions defined in a separate sub-tables
[[processors.regex.tags]]
## Tag to change
key = "container_name"
## Regular expression to match on a tag value
pattern = "^([a-zA-Z0-9_]+)-\\w{8}-\\w{4}-\\w{4}-\\w{4}-\\w{12}$"
## Matches of the pattern will be replaced with this string. Use ${1}
## notation to use the text of the first submatch.
replacement = "${1}"
[[processors.regex]]
namepass = ["docker_container_net"]
# Tag and field conversions defined in a separate sub-tables
[[processors.regex.tags]]
## Tag to change
key = "container_name"
## Regular expression to match on a tag value
pattern = "^([a-zA-Z0-9_]+)-\\w{8}-\\w{4}-\\w{4}-\\w{4}-\\w{12}$"
## Matches of the pattern will be replaced with this string. Use ${1}
## notation to use the text of the first submatch.
replacement = "${1}"
[[processors.regex]]
namepass = ["docker_container_cpu"]
# Tag and field conversions defined in a separate sub-tables
[[processors.regex.tags]]
## Tag to change
key = "container_name"
## Regular expression to match on a tag value
pattern = "^([a-zA-Z0-9_]+)-\\w{8}-\\w{4}-\\w{4}-\\w{4}-\\w{12}$"
## Matches of the pattern will be replaced with this string. Use ${1}
## notation to use the text of the first submatch.
replacement = "${1}"
[[processors.regex]]
namepass = ["docker_container_blkio"]
# Tag and field conversions defined in a separate sub-tables
[[processors.regex.tags]]
## Tag to change
key = "container_name"
## Regular expression to match on a tag value
pattern = "^([a-zA-Z0-9_]+)-\\w{8}-\\w{4}-\\w{4}-\\w{4}-\\w{12}$"
## Matches of the pattern will be replaced with this string. Use ${1}
## notation to use the text of the first submatch.
replacement = "${1}"
[[processors.regex]]
namepass = ["docker_container_health"]
# Tag and field conversions defined in a separate sub-tables
[[processors.regex.tags]]
## Tag to change
key = "container_name"
## Regular expression to match on a tag value
pattern = "^([a-zA-Z0-9_]+)-\\w{8}-\\w{4}-\\w{4}-\\w{4}-\\w{12}$"
## Matches of the pattern will be replaced with this string. Use ${1}
## notation to use the text of the first submatch.
replacement = "${1}"
[[processors.regex]]
namepass = ["docker_container_status"]
# Tag and field conversions defined in a separate sub-tables
[[processors.regex.tags]]
## Tag to change
key = "container_name"
## Regular expression to match on a tag value
pattern = "^([a-zA-Z0-9_]+)-\\w{8}-\\w{4}-\\w{4}-\\w{4}-\\w{12}$"
## Matches of the pattern will be replaced with this string. Use ${1}
## notation to use the text of the first submatch.
replacement = "${1}"
###############################################################################
# INPUT PLUGINS #
###############################################################################
[[inputs.docker]]
## Docker Endpoint
## To use TCP, set endpoint = "tcp://[ip]:[port]"
## To use environment variables (ie, docker-machine), set endpoint = "ENV"
endpoint = "unix:///var/run/docker.sock"
## Set to true to collect Swarm metrics(desired_replicas, running_replicas)
## Note: configure this in one of the manager nodes in a Swarm cluster.
## configuring in multiple Swarm managers results in duplication of metrics.
gather_services = false
## Only collect metrics for these containers. Values will be appended to
## container_name_include.
## Deprecated (1.4.0), use container_name_include
container_names = []
## Set the source tag for the metrics to the container ID hostname, eg first 12 chars
source_tag = false
## Containers to include and exclude. Collect all if empty. Globs accepted.
container_name_include = []
container_name_exclude = []
## Container states to include and exclude. Globs accepted.
## When empty only containers in the "running" state will be captured.
## example: container_state_include = ["created", "restarting", "running", "removing", "paused", "exited", "dead"]
## example: container_state_exclude = ["created", "restarting", "running", "removing", "paused", "exited", "dead"]
# container_state_include = []
# container_state_exclude = []
## Timeout for docker list, info, and stats commands
timeout = "5s"
## Whether to report for each container per-device blkio (8:0, 8:1...),
## network (eth0, eth1, ...) and cpu (cpu0, cpu1, ...) stats or not.
## Usage of this setting is discouraged since it will be deprecated in favor of 'perdevice_include'.
## Default value is 'true' for backwards compatibility, please set it to 'false' so that 'perdevice_include' setting
## is honored.
perdevice = true
## Specifies for which classes a per-device metric should be issued
## Possible values are 'cpu' (cpu0, cpu1, ...), 'blkio' (8:0, 8:1, ...) and 'network' (eth0, eth1, ...)
## Please note that this setting has no effect if 'perdevice' is set to 'true'
# perdevice_include = ["cpu"]
## Whether to report for each container total blkio and network stats or not.
## Usage of this setting is discouraged since it will be deprecated in favor of 'total_include'.
## Default value is 'false' for backwards compatibility, please set it to 'true' so that 'total_include' setting
## is honored.
total = false
## Specifies for which classes a total metric should be issued. Total is an aggregated of the 'perdevice' values.
## Possible values are 'cpu', 'blkio' and 'network'
## Total 'cpu' is reported directly by Docker daemon, and 'network' and 'blkio' totals are aggregated by this plugin.
## Please note that this setting has no effect if 'total' is set to 'false'
# total_include = ["cpu", "blkio", "network"]
## docker labels to include and exclude as tags. Globs accepted.
## Note that an empty array for both will include all labels as tags
docker_label_include = []
docker_label_exclude = ["traefik.*"] # Do not report on Traefik tags
## Which environment variables should we use as a tag
tag_env = ["JAVA_HOME", "HEAP_SIZE"]
## Optional TLS Config
# tls_ca = "/etc/telegraf/ca.pem"
# tls_cert = "/etc/telegraf/cert.pem"
# tls_key = "/etc/telegraf/key.pem"
## Use TLS but skip chain & host verification
# insecure_skip_verify = false

View File

@@ -0,0 +1,10 @@
[[inputs.exec]]
{% if 'pis' in group_names %}
commands = ["cat /sys/class/thermal/thermal_zone0/temp"]
{% elif 'macs' in group_names %}
commands = ["${HOME}/dotfiles-private/config/telegraf/cputemp.sh"]
{% endif %}
timeout = "5s"
name_suffix = "_cpu_temp"
data_format = "value"
data_type = "integer"