442 lines
9.6 KiB
HCL
442 lines
9.6 KiB
HCL
job "unifi" {
|
|
datacenters = ["dc1"]
|
|
region = "global"
|
|
|
|
|
|
|
|
|
|
group "unifi" {
|
|
|
|
network {
|
|
mode = "bridge"
|
|
port "stun" {
|
|
to = 3478
|
|
}
|
|
}
|
|
|
|
service {
|
|
name = "unifi"
|
|
port = 8888
|
|
|
|
connect {
|
|
sidecar_service {
|
|
}
|
|
sidecar_task {
|
|
config {
|
|
args = [
|
|
"-c",
|
|
"${NOMAD_SECRETS_DIR}/envoy_bootstrap.json",
|
|
"-l",
|
|
"${meta.connect.log_level}",
|
|
"--concurrency",
|
|
"${meta.connect.proxy_concurrency}",
|
|
"--disable-hot-restart"
|
|
]
|
|
}
|
|
|
|
resources {
|
|
cpu = 50
|
|
memory = 64
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
|
|
check {
|
|
type = "http"
|
|
path = "/status"
|
|
expose = true
|
|
interval = "10s"
|
|
timeout = "5s"
|
|
check_restart {
|
|
limit = 12
|
|
grace = "2m"
|
|
}
|
|
}
|
|
|
|
tags = [
|
|
|
|
"traefik.enable=true",
|
|
"traefik.http.routers.unifi.entrypoints=https",
|
|
"traefik.http.routers.unifi.rule=Host(`unifi.example.org`)",
|
|
"traefik.http.middlewares.csp-unifi.headers.contentsecuritypolicy=default-src 'self';font-src 'self' data:;img-src 'self' data:;script-src 'self' 'unsafe-inline' 'unsafe-eval';style-src 'self' 'unsafe-inline';",
|
|
"traefik.http.middlewares.unifi-inflight.inflightreq.amount=300",
|
|
"traefik.http.middlewares.unifi-rate-limit.ratelimit.average=100",
|
|
"traefik.http.middlewares.unifi-rate-limit.ratelimit.burst=200",
|
|
"traefik.http.routers.unifi.middlewares=security-headers@file,unifi-rate-limit,forward-proto@file,inflight-std@file,unifi-inflight,hsts@file,compression@file,csp-unifi",
|
|
|
|
|
|
"traefik.enable=true",
|
|
"traefik.http.routers.unifi-inform.entrypoints=unifi-inform",
|
|
"traefik.http.routers.unifi-inform.rule=(Path(`/inform`) && Method(`POST`)) || (PathPrefix(`/dl/firmware-cached`) && (Method(`GET`) || Method(`HEAD`)))",
|
|
"traefik.http.routers.unifi-inform.middlewares=rate-limit-std@file,inflight-std@file",
|
|
|
|
|
|
"traefik.enable=true",
|
|
"traefik.http.routers.unifi-portal.entrypoints=unifi-portal",
|
|
"traefik.http.routers.unifi-portal.rule=Host(`unifi-portal.example.org`) && PathPrefix(`/guest`)",
|
|
"traefik.http.middlewares.csp-unifi-portal.headers.contentsecuritypolicy=default-src 'self';font-src 'self' data:;img-src 'self' data:;script-src 'self' 'unsafe-inline' 'unsafe-eval';style-src 'self' 'unsafe-inline';",
|
|
"traefik.http.routers.unifi-portal.middlewares=security-headers@file,rate-limit-std@file,forward-proto@file,inflight-std@file,hsts@file,compression@file,csp-unifi-portal",
|
|
|
|
]
|
|
}
|
|
|
|
service {
|
|
name = "unifi-stun"
|
|
port = "stun"
|
|
|
|
tags = [
|
|
|
|
"traefik.enable=true",
|
|
"traefik.consulcatalog.connect=false",
|
|
"traefik.udp.routers.unifi-stun.entrypoints=unifi-stun",
|
|
|
|
]
|
|
}
|
|
service {
|
|
name = "unifi-mongo"
|
|
port = 27017
|
|
|
|
check {
|
|
type = "script"
|
|
command = "sh"
|
|
args = ["-c", "mongo --quiet --eval 'db.runCommand(\"ping\").ok'"]
|
|
interval = "30s"
|
|
timeout = "5s"
|
|
task = "mongo"
|
|
|
|
check_restart {
|
|
limit = 4
|
|
grace = "3m"
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
volume "mongo" {
|
|
source = "unifi-mongo"
|
|
type = "csi"
|
|
access_mode = "single-node-writer"
|
|
attachment_mode = "file-system"
|
|
}
|
|
|
|
|
|
|
|
volume "data" {
|
|
source = "unifi-data"
|
|
type = "csi"
|
|
access_mode = "single-node-writer"
|
|
attachment_mode = "file-system"
|
|
}
|
|
|
|
# wait for required services tp be ready before starting the main task
|
|
task "wait-for" {
|
|
|
|
driver = "docker"
|
|
user = 1053
|
|
|
|
config {
|
|
image = "danielberteaud/wait-for:24.3-1"
|
|
readonly_rootfs = true
|
|
pids_limit = 20
|
|
}
|
|
|
|
lifecycle {
|
|
hook = "prestart"
|
|
}
|
|
|
|
env {
|
|
SERVICE_0 = "unifi-mongo.service.consul"
|
|
}
|
|
|
|
resources {
|
|
cpu = 10
|
|
memory = 10
|
|
memory_max = 30
|
|
}
|
|
}
|
|
|
|
|
|
|
|
task "nginx" {
|
|
|
|
driver = "docker"
|
|
user = 8306
|
|
|
|
lifecycle {
|
|
hook = "poststart"
|
|
sidecar = "true"
|
|
}
|
|
|
|
config {
|
|
image = "nginxinc/nginx-unprivileged:alpine"
|
|
volumes = ["local/nginx.conf:/etc/nginx/conf.d/default.conf"]
|
|
readonly_rootfs = true
|
|
pids_limit = 20
|
|
mount {
|
|
type = "tmpfs"
|
|
target = "/tmp"
|
|
tmpfs_options {
|
|
size = 3000000
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
template {
|
|
data = <<_EOF
|
|
map $http_upgrade $connection_upgrade {
|
|
default upgrade;
|
|
'' close;
|
|
}
|
|
|
|
server {
|
|
listen 127.0.0.1:8888;
|
|
server_name _;
|
|
server_tokens off;
|
|
root /usr/share/html;
|
|
proxy_set_header Origin "";
|
|
proxy_set_header Authorization "";
|
|
|
|
proxy_http_version 1.1;
|
|
proxy_set_header Upgrade $http_upgrade;
|
|
proxy_set_header Connection $connection_upgrade;
|
|
proxy_set_header Host $host;
|
|
proxy_socket_keepalive on;
|
|
client_max_body_size 100m;
|
|
|
|
set_real_ip_from 127.0.0.1;
|
|
real_ip_header X-Forwarded-For;
|
|
real_ip_recursive on;
|
|
|
|
# Inform endpoint
|
|
location ~ ^/(inform|dl/firmware-cached).* {
|
|
if ($request_method !~ ^(GET|HEAD|POST)$ ) {
|
|
return 405;
|
|
}
|
|
proxy_pass http://localhost:8080;
|
|
}
|
|
|
|
# Guest portal
|
|
location /guest/ {
|
|
if ($request_method !~ ^(GET|HEAD|POST)$ ) {
|
|
return 405;
|
|
}
|
|
proxy_pass https://localhost:8843;
|
|
}
|
|
|
|
# Main console
|
|
location / {
|
|
if ($request_method !~ ^(GET|HEAD|POST|PUT|DELETE)$ ) {
|
|
return 405;
|
|
}
|
|
proxy_pass https://localhost:8443;
|
|
}
|
|
}
|
|
|
|
|
|
_EOF
|
|
destination = "local/nginx.conf"
|
|
}
|
|
|
|
|
|
resources {
|
|
cpu = 10
|
|
memory = 15
|
|
}
|
|
|
|
}
|
|
|
|
task "controller" {
|
|
|
|
|
|
|
|
leader = true
|
|
driver = "docker"
|
|
|
|
config {
|
|
image = "danielberteaud/unifi:8.1.113-1"
|
|
volumes = [
|
|
"local/init-system.properties.sh:/entrypoint.d/10-init-system.properties.sh"
|
|
]
|
|
readonly_rootfs = true
|
|
pids_limit = 200
|
|
mount {
|
|
type = "tmpfs"
|
|
target = "/opt/unifi/run"
|
|
tmpfs_options {
|
|
size = 3000000
|
|
}
|
|
}
|
|
|
|
mount {
|
|
type = "tmpfs"
|
|
target = "/tmp"
|
|
tmpfs_options {
|
|
size = 3000000
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
|
|
vault {
|
|
policies = ["unifi"]
|
|
env = false
|
|
disable_file = true
|
|
change_mode = "noop"
|
|
}
|
|
|
|
|
|
env {
|
|
|
|
|
|
TMPDIR = "/local/tmp"
|
|
}
|
|
|
|
|
|
|
|
# Use a template block instead of env {} so we can fetch values from vault
|
|
template {
|
|
data = <<_EOT
|
|
LANG=fr_FR.utf8
|
|
TZ=Europe/Paris
|
|
_EOT
|
|
destination = "secrets/.env"
|
|
perms = 400
|
|
env = true
|
|
}
|
|
|
|
|
|
template {
|
|
data = <<_EOF
|
|
unifi.http.port=8080
|
|
unifi.https.port=8443
|
|
portal.http.port=8880
|
|
portal.https.port=8843
|
|
unifi.stun.port={{ env "NOMAD_PORT_stun" }}
|
|
unifi.db.nojournal=true
|
|
db.mongo.local=false
|
|
db.mongo.uri=mongodb://127.0.0.1:27017/unifi?
|
|
statdb.mongo.uri=mongodb://127.0.0.1:27017/unifi_stats?
|
|
debug.device=info
|
|
debug.mgmt=info
|
|
debug.system=info
|
|
debug.sdn=warn
|
|
autobackup.dir=/data/backup
|
|
|
|
_EOF
|
|
destination = "secrets/system.properties"
|
|
}
|
|
|
|
template {
|
|
data = <<_EOF
|
|
#!/bin/sh
|
|
|
|
# vim: syntax=sh
|
|
set -euo pipefail
|
|
|
|
mkdir -p /data/unifi
|
|
mkdir -p /data/logs
|
|
|
|
if [ \! -f "/opt/unifi/data/system.properties" ]; then
|
|
echo "System initialization, copy the default system.properties"
|
|
cp /secrets/system.properties /opt/unifi/data/system.properties
|
|
else
|
|
for PROP in $(grep -vE '^(\s*$|#)' /secrets/system.properties); do
|
|
KEY=$(echo ${PROP} | cut -d= -f1)
|
|
VALUE=$(echo ${PROP} | cut -d= -f2)
|
|
if grep -q -E "^${KEY}=" /opt/unifi/data/system.properties; then
|
|
if grep -q -E "^${PROP}" /opt/unifi/data/system.properties; then
|
|
echo "${PROP} already set in system.properties"
|
|
else
|
|
echo "Updating ${PROP} in system.properties"
|
|
sed -i -E "s|^${KEY}=.*|${PROP}|" /opt/unifi/data/system.properties
|
|
fi
|
|
else
|
|
echo "Adding ${PROP} in system.properties"
|
|
echo ${PROP} >> /opt/unifi/data/system.properties
|
|
fi
|
|
done
|
|
fi
|
|
|
|
_EOF
|
|
destination = "local/init-system.properties.sh"
|
|
perms = "755"
|
|
}
|
|
|
|
volume_mount {
|
|
volume = "data"
|
|
destination = "/data"
|
|
}
|
|
|
|
|
|
resources {
|
|
cpu = 200
|
|
memory = 1024
|
|
}
|
|
|
|
|
|
}
|
|
|
|
task "mongo" {
|
|
|
|
driver = "docker"
|
|
|
|
lifecycle {
|
|
hook = "prestart"
|
|
sidecar = true
|
|
}
|
|
|
|
config {
|
|
image = "danielberteaud/mongo:5.0.24.3-1"
|
|
command = "mongod"
|
|
readonly_rootfs = true
|
|
pids_limit = 200
|
|
args = ["--config", "/local/mongod.conf"]
|
|
mount {
|
|
type = "tmpfs"
|
|
target = "/tmp"
|
|
tmpfs_options {
|
|
size = 3000000
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
template {
|
|
data = <<_EOF
|
|
net:
|
|
bindIp: 127.0.0.1
|
|
storage:
|
|
dbPath: /data/db
|
|
directoryPerDB: true
|
|
wiredTiger:
|
|
engineConfig:
|
|
directoryForIndexes: true
|
|
journalCompressor: snappy
|
|
collectionConfig:
|
|
blockCompressor: snappy
|
|
|
|
|
|
_EOF
|
|
destination = "local/mongod.conf"
|
|
}
|
|
|
|
volume_mount {
|
|
volume = "mongo"
|
|
destination = "/data/db"
|
|
}
|
|
|
|
|
|
resources {
|
|
cpu = 100
|
|
memory = 256
|
|
}
|
|
|
|
|
|
}
|
|
}
|
|
}
|