Initial Commit
This commit is contained in:
commit
dddb37e979
21
LICENSE
Normal file
21
LICENSE
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2022 Arsen Musayelyan
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
17
README.md
Normal file
17
README.md
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
# Nomad
|
||||||
|
|
||||||
|
This repository contains my [Nomad](https://github.com/hashicorp/nomad) and [Consul](https://github.com/hashicorp/consul) configurations that I am using for my servers.
|
||||||
|
|
||||||
|
Nomad is an orchestrator. It orchestrates a cluster of multiple computers to perform a certain task. Consul is a service discovery solution as well as a key/value store. Nomad seamlessly integrates with Consul to provide a cohesive solution that works really well.
|
||||||
|
|
||||||
|
I have a cluster of 8 [Raspberry Pi 4s](https://www.raspberrypi.com/products/raspberry-pi-4-model-b/) (4x 1GB, 4x 2GB RAM), 2 2GB [Pine H64s](https://www.pine64.org/pine-h64-ver-b/), 1 2GB [RockPro64](https://www.pine64.org/rockpro64/), and a 2012 Mac Mini with an i7 that I've upgraded with a 1TB SSD and 16GB of RAM. I've also pre-ordered a 16GB [Radxa Rock 5 Model B](https://wiki.radxa.com/Rock5/5b).
|
||||||
|
|
||||||
|
I'd originally planned to use Kubernetes on this cluster, but due to the low power and especially low RAM of many of my nodes, Kubernetes would either take up nearly all the resources or freeze the nodes entirely, forcing a hard reboot. Recently, I found Nomad, which is far more lightweight, while providing more features. In my opinion, the only downside is the fact that Kubernetes has a much bigger community.
|
||||||
|
|
||||||
|
Using Nomad is great, and much better than my previous strategy of "stick everything somewhere and hope you remember where". All I do is create one of these nomad files, using the very good documentation for Nomad, and then submit it to Nomad. Nomad then automatically decides where to put the service, installs it, configures it, runs it, and publishes it to Consul, from where Traefik picks it up and reconfigures itself automatically to point to the newly-created service. If the service is stateless, like a few of mine are, then Nomad can seamlessly migrate the service to another node, and Traefik will reconfigure itself automatically to point to the new node.
|
||||||
|
|
||||||
|
The way I have my cluster set up is: My mac mini acts as the only Nomad and Consul server in the cluster because it has the most resources. Each node in the cluster has a Nomad client running on it, as well as a Consul client. They are simply services installed by the package manager and run by systemd.
|
||||||
|
|
||||||
|
A couple of my services are running outside of Nomad because they have a complex config, or because they're Home Assistant, which has a supervisor docker container that launches other docker containers, etc. and I don't know how to express that in a nomad job file. For these, I use a simple systemd service that runs `consul register` with the service's information. This causes Traefik to pick them up and automatically reconfigure itself the same as it does for Nomad servces.
|
||||||
|
|
||||||
|
Before using any files in this repo, read through them and make sure to replace anything that is mentioned on the individual READMEs, as well as things like domain names. My domain is `arsenm.dev`, so change anything that has that to your domain instead,
|
11
authelia/README.md
Normal file
11
authelia/README.md
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
# Authelia
|
||||||
|
|
||||||
|
This job file runs [Authelia](https://www.authelia.com/), which allows users to protect services that prpvide no authentication mechanism with a password and 2fa. It can also be used to log in to other self-hosted services, similar to how "Log in to Google" buttons work.
|
||||||
|
|
||||||
|
IMPORTANT NOTE: Some services in this repo are protected by Authelia. You can find out which by doing Ctrl+F in their nomad files and searching for "authelia". If you are not running Authelia, those services will be available WITHOUT A PASSWORD, so do not expose them to the internet without Authelia protecting them.
|
||||||
|
|
||||||
|
This job requires one volume for Authelia's config and secrets. This volume should be named `authelia-config`, and be read/write, not readonly.
|
||||||
|
|
||||||
|
There is one thing you will want to change in this file. In the `env` stanza, the timezone, or `TZ`, is set to "CHANGE ME". Set this to your timezone as it is used when generating TOTP codes for 2fa.
|
||||||
|
|
||||||
|
Since configuration for Authelia is complex and heavily dependent on your environment and what you want it to do, I will leave my authelia config out and instead direct you to the documentation: https://www.authelia.com/configuration/prologue/introduction/, and this blog post that I used when configuring authelia: https://spad.uk/configure-authelia-to-work-with-traefik/.
|
54
authelia/authelia.nomad
Normal file
54
authelia/authelia.nomad
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
job "authelia" {
|
||||||
|
region = "global"
|
||||||
|
datacenters = ["dc1"]
|
||||||
|
type = "service"
|
||||||
|
|
||||||
|
group "authelia" {
|
||||||
|
count = 1
|
||||||
|
|
||||||
|
volume "authelia-config" {
|
||||||
|
type = "host"
|
||||||
|
source = "authelia-config"
|
||||||
|
read_only = false
|
||||||
|
}
|
||||||
|
|
||||||
|
network {
|
||||||
|
port "authelia" {
|
||||||
|
to = 9091
|
||||||
|
static = 9091
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
task "authelia" {
|
||||||
|
driver = "docker"
|
||||||
|
|
||||||
|
volume_mount {
|
||||||
|
volume = "authelia-config"
|
||||||
|
destination = "/config"
|
||||||
|
read_only = false
|
||||||
|
}
|
||||||
|
|
||||||
|
env {
|
||||||
|
TZ = "CHANGE ME"
|
||||||
|
AUTHELIA_JWT_SECRET_FILE = "/config/secrets/jwt"
|
||||||
|
AUTHELIA_SESSION_SECRET_FILE = "/config/secrets/session"
|
||||||
|
}
|
||||||
|
|
||||||
|
config {
|
||||||
|
image = "authelia/authelia:latest"
|
||||||
|
ports = ["authelia"]
|
||||||
|
}
|
||||||
|
|
||||||
|
service {
|
||||||
|
name = "authelia"
|
||||||
|
port = "authelia"
|
||||||
|
|
||||||
|
tags = [
|
||||||
|
"traefik.enable=true",
|
||||||
|
"traefik.http.routers.authelia.rule=Host(`auth.arsenm.dev`)",
|
||||||
|
"traefik.http.routers.authelia.tls.certResolver=letsencrypt",
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
7
gitea/README.md
Normal file
7
gitea/README.md
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
# Gitea
|
||||||
|
|
||||||
|
This is the nomad file for [Gitea](https://github.com/go-gitea/gitea), a git web interface.
|
||||||
|
|
||||||
|
This job requires one volume for Gitea's data. This volume should be called `gitea-data` and should be read/write, not readonly. Set the owner of the directory bound to the volume as `1002`. The command that would be used for this is `sudo chown 1002:1002 <volume directory>`.
|
||||||
|
|
||||||
|
In the `env` stanza, there is an SSH domain. Set that to the local IP of your server so that you can use it to upload to git via SSH.
|
74
gitea/gitea.nomad
Normal file
74
gitea/gitea.nomad
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
job "gitea" {
|
||||||
|
region = "global"
|
||||||
|
datacenters = [
|
||||||
|
"dc1",
|
||||||
|
]
|
||||||
|
type = "service"
|
||||||
|
|
||||||
|
group "gitea" {
|
||||||
|
count = 1
|
||||||
|
|
||||||
|
network {
|
||||||
|
port "http" {
|
||||||
|
to = 3000
|
||||||
|
}
|
||||||
|
|
||||||
|
port "ssh" {
|
||||||
|
static = 2222
|
||||||
|
to = 22
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
volume "gitea-data" {
|
||||||
|
type = "host"
|
||||||
|
source = "gitea-data"
|
||||||
|
read_only = false
|
||||||
|
}
|
||||||
|
|
||||||
|
restart {
|
||||||
|
attempts = 5
|
||||||
|
delay = "30s"
|
||||||
|
}
|
||||||
|
|
||||||
|
task "app" {
|
||||||
|
driver = "docker"
|
||||||
|
volume_mount {
|
||||||
|
volume = "gitea-data"
|
||||||
|
destination = "/data"
|
||||||
|
read_only = false
|
||||||
|
}
|
||||||
|
|
||||||
|
config {
|
||||||
|
image = "gitea/gitea:latest"
|
||||||
|
ports = ["ssh", "http"]
|
||||||
|
}
|
||||||
|
|
||||||
|
env {
|
||||||
|
APP_NAME = "Gitea: Git with a cup of tea"
|
||||||
|
RUN_MODE = "prod"
|
||||||
|
SSH_DOMAIN = "CHANGE ME"
|
||||||
|
SSH_PORT = "$NOMAD_PORT_ssh"
|
||||||
|
ROOT_URL = "https://gitea.arsenm.dev/"
|
||||||
|
USER_UID = "1002"
|
||||||
|
USER_GID = "1002"
|
||||||
|
GITEA__server__START_SSH_SERVER = "true"
|
||||||
|
}
|
||||||
|
|
||||||
|
resources {
|
||||||
|
cpu = 200
|
||||||
|
memory = 512
|
||||||
|
}
|
||||||
|
|
||||||
|
service {
|
||||||
|
name = "gitea"
|
||||||
|
port = "http"
|
||||||
|
|
||||||
|
tags = [
|
||||||
|
"traefik.enable=true",
|
||||||
|
"traefik.http.routers.gitea.rule=Host(`gitea.arsenm.dev`)",
|
||||||
|
"traefik.http.routers.gitea.tls.certResolver=letsencrypt"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
7
go-import-redirector/README.md
Normal file
7
go-import-redirector/README.md
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
# Go Import Redirector
|
||||||
|
|
||||||
|
This is the nomad job file I use for rsc's [go-import-redirector](https://github.com/rsc/go-import-redirector), which redirects the Go programming language tool to the proper github repo. For example, if someone tries to install one of my projects, rather than having to type out `go install gitea.arsenm.dev/Arsen6331/itd@latest`, they can just use `go install go.arsenm.dev/itd@latest` and this service will redirect them.
|
||||||
|
|
||||||
|
This job file downloads an archive containing `go-import-redirector` executables for `x86_64` and `aarch64`, as well as a script that runs the correct one, from my minio instance.
|
||||||
|
|
||||||
|
I am running two instances of this, automatically load-balanced by Traefik. Since the configuration is done entirely in command-line arguments, this workload is completely stateless, and can therefore be moved around freely by Nomad.
|
46
go-import-redirector/go-import-redirector.nomad
Normal file
46
go-import-redirector/go-import-redirector.nomad
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
job "go-import-redirector" {
|
||||||
|
region = "global"
|
||||||
|
datacenters = ["dc1"]
|
||||||
|
type = "service"
|
||||||
|
|
||||||
|
group "go-import-redirector" {
|
||||||
|
count = 2
|
||||||
|
|
||||||
|
network {
|
||||||
|
port "http" {}
|
||||||
|
}
|
||||||
|
|
||||||
|
task "go-import-redirector" {
|
||||||
|
driver = "exec"
|
||||||
|
|
||||||
|
config {
|
||||||
|
command = "local/exec.sh"
|
||||||
|
args = [
|
||||||
|
"-addr", ":${NOMAD_PORT_http}",
|
||||||
|
"go.arsenm.dev/*",
|
||||||
|
"https://gitea.arsenm.dev/Arsen6331/*"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
artifact {
|
||||||
|
source = "https://api.minio.arsenm.dev/adl/go-import-redirector.tar.gz"
|
||||||
|
}
|
||||||
|
|
||||||
|
resources {
|
||||||
|
cpu = 100
|
||||||
|
memory = 20
|
||||||
|
}
|
||||||
|
|
||||||
|
service {
|
||||||
|
name = "go-import-redirector"
|
||||||
|
port = "http"
|
||||||
|
|
||||||
|
tags = [
|
||||||
|
"traefik.enable=true",
|
||||||
|
"traefik.http.routers.go-import-redirector.rule=Host(`go.arsenm.dev`)",
|
||||||
|
"traefik.http.routers.go-import-redirector.tls.certResolver=letsencrypt"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
5
homer/README.md
Normal file
5
homer/README.md
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
# Homer
|
||||||
|
|
||||||
|
This is the nomad job file I use for the [Homer](https://github.com/bastienwirtz/homer/) dashboard. It takes its config file from the Consul Key/Value store. This means that whenever the config is edited, Nomad automatically installs the new file and restarts the container. It also means that Homer is completely stateless, and therefore can be freely moved around by Nomad.
|
||||||
|
|
||||||
|
This directory also contains the config I use for my dashboard at https://dashboard.arsenm.dev. This is placed into the Consul K/V store as `homer/config.yml`.
|
84
homer/config.yml
Normal file
84
homer/config.yml
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
---
|
||||||
|
# Homepage configuration
|
||||||
|
# See https://fontawesome.com/v5/search for icons options
|
||||||
|
|
||||||
|
# externalConfig: https://example.com/server-luci/config.yaml
|
||||||
|
|
||||||
|
title: "Dashboard"
|
||||||
|
subtitle: "Home"
|
||||||
|
documentTitle: "Dashboard"
|
||||||
|
|
||||||
|
header: true
|
||||||
|
footer: false
|
||||||
|
|
||||||
|
columns: "auto"
|
||||||
|
connectivityCheck: true
|
||||||
|
|
||||||
|
defaults:
|
||||||
|
layout: list
|
||||||
|
colorTheme: dark
|
||||||
|
|
||||||
|
theme: default
|
||||||
|
|
||||||
|
colors:
|
||||||
|
dark:
|
||||||
|
highlight-primary: "#00aa0b"
|
||||||
|
highlight-secondary: "#00b918"
|
||||||
|
highlight-hover: "#47c447"
|
||||||
|
|
||||||
|
links: []
|
||||||
|
|
||||||
|
services:
|
||||||
|
- name: "Self-hosted Services"
|
||||||
|
icon: "fa-solid fa-user"
|
||||||
|
items:
|
||||||
|
- name: "SearXNG"
|
||||||
|
icon: "fa-solid fa-magnifying-glass"
|
||||||
|
subtitle: "Metasearch engine"
|
||||||
|
url: "https://search.arsenm.dev/"
|
||||||
|
- name: "Nextcloud"
|
||||||
|
icon: "fa-solid fa-cloud"
|
||||||
|
subtitle: "Cloud file storage"
|
||||||
|
url: "https://nextcloud.arsenm.dev/"
|
||||||
|
- name: "Home Assistant"
|
||||||
|
icon: "fa-solid fa-house"
|
||||||
|
subtitle: "Home automation hub"
|
||||||
|
url: "https://hass.arsenm.dev/"
|
||||||
|
- name: "Gitea"
|
||||||
|
icon: "fa-solid fa-code-branch"
|
||||||
|
subtitle: "Web interface for Git"
|
||||||
|
url: "https://gitea.arsenm.dev/"
|
||||||
|
- name: "Minio"
|
||||||
|
icon: "fa-solid fa-box"
|
||||||
|
subtitle: "Open source S3 implementation"
|
||||||
|
url: "https://minio.arsenm.dev/"
|
||||||
|
- name: "LMS"
|
||||||
|
icon: "fa-solid fa-music"
|
||||||
|
subtitle: "Lightweight music server"
|
||||||
|
url: "https://music.arsenm.dev/"
|
||||||
|
- name: "Piracy"
|
||||||
|
icon: "fa-solid fa-skull-crossbones"
|
||||||
|
items:
|
||||||
|
- name: "LibGen"
|
||||||
|
icon: "fa-solid fa-book"
|
||||||
|
subtitle: "Database of pirated books"
|
||||||
|
url: "https://whereislibgen.now.sh/go"
|
||||||
|
- name: "Sci-Hub"
|
||||||
|
icon: "fa-solid fa-flask"
|
||||||
|
subtitle: "Database of pirated scientific papers"
|
||||||
|
url: "https://sci-hub.now.sh/"
|
||||||
|
- name: "Administration"
|
||||||
|
icon: "fa-solid fa-wrench"
|
||||||
|
items:
|
||||||
|
- name: "Traefik"
|
||||||
|
icon: "fa fa-globe"
|
||||||
|
subtitle: "Reverse proxy"
|
||||||
|
url: "https://traefik.arsenm.dev/"
|
||||||
|
- name: "Nomad"
|
||||||
|
icon: "fa-solid fa-circle-nodes"
|
||||||
|
subtitle: "Cluster orchestrator"
|
||||||
|
url: "https://nomad.arsenm.dev/"
|
||||||
|
- name: "Consul"
|
||||||
|
icon: "fa-solid fa-database"
|
||||||
|
subtitle: "Service discovery and K/V store"
|
||||||
|
url: "https://consul.arsenm.dev/"
|
46
homer/homer.nomad
Normal file
46
homer/homer.nomad
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
job "homer" {
|
||||||
|
region = "global"
|
||||||
|
datacenters = ["dc1"]
|
||||||
|
type = "service"
|
||||||
|
|
||||||
|
group "homer" {
|
||||||
|
count = 1
|
||||||
|
|
||||||
|
network {
|
||||||
|
port "homer" {}
|
||||||
|
}
|
||||||
|
|
||||||
|
task "homer" {
|
||||||
|
driver = "docker"
|
||||||
|
|
||||||
|
env {
|
||||||
|
PORT = "${NOMAD_PORT_homer}"
|
||||||
|
}
|
||||||
|
|
||||||
|
config {
|
||||||
|
image = "b4bz/homer:latest"
|
||||||
|
ports = ["homer"]
|
||||||
|
|
||||||
|
volumes = [
|
||||||
|
"local/config.yml:/www/assets/config.yml"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
template {
|
||||||
|
data = "{{ key `homer/config.yml` }}"
|
||||||
|
destination = "local/config.yml"
|
||||||
|
}
|
||||||
|
|
||||||
|
service {
|
||||||
|
name = "homer"
|
||||||
|
port = "homer"
|
||||||
|
|
||||||
|
tags = [
|
||||||
|
"traefik.enable=true",
|
||||||
|
"traefik.http.routers.homer.rule=Host(`dashboard.arsenm.dev`)",
|
||||||
|
"traefik.http.routers.homer.tls.certResolver=letsencrypt",
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
7
lms/README.md
Normal file
7
lms/README.md
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
# LMS
|
||||||
|
|
||||||
|
This is the nomad job file for the [Lightweight Music Server](https://github.com/epoupon/lms).
|
||||||
|
|
||||||
|
This job requires two volumes. One for data and one for media. These volumes should be named `lms-data` and `lms-media` respectively. The `lms-data` volume should be read/write, not readonly, while the `lms-media` volume can be readonly.
|
||||||
|
|
||||||
|
LMS' user will not have permissions to write to the data directory. There are multiple ways to solve this. The easy way is to set the permissions of the directory to `777`. Since the directory won't contain any user data and the service will not be exposed directly to the internet, but rather go through a reverse proxy, this should be fine, but I still wouldn't recommend it. The more secure option is to enter the container's shell and chown the directory so that it is owned by the same user that's running LMS inside the container.
|
59
lms/lms.nomad
Normal file
59
lms/lms.nomad
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
job "lms" {
|
||||||
|
region = "global"
|
||||||
|
datacenters = ["dc1"]
|
||||||
|
type = "service"
|
||||||
|
|
||||||
|
group "lms" {
|
||||||
|
count = 1
|
||||||
|
|
||||||
|
volume "lms-media" {
|
||||||
|
type = "host"
|
||||||
|
source = "lms-media"
|
||||||
|
read_only = true
|
||||||
|
}
|
||||||
|
|
||||||
|
volume "lms-data" {
|
||||||
|
type = "host"
|
||||||
|
source = "lms-data"
|
||||||
|
read_only = false
|
||||||
|
}
|
||||||
|
|
||||||
|
network {
|
||||||
|
port "lms" {
|
||||||
|
to = 5082
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
task "lms" {
|
||||||
|
driver = "docker"
|
||||||
|
|
||||||
|
volume_mount {
|
||||||
|
volume = "lms-media"
|
||||||
|
destination = "/media"
|
||||||
|
read_only = true
|
||||||
|
}
|
||||||
|
|
||||||
|
volume_mount {
|
||||||
|
volume = "lms-data"
|
||||||
|
destination = "/var/lms"
|
||||||
|
read_only = false
|
||||||
|
}
|
||||||
|
|
||||||
|
config {
|
||||||
|
image = "epoupon/lms:latest"
|
||||||
|
ports = ["lms"]
|
||||||
|
}
|
||||||
|
|
||||||
|
service {
|
||||||
|
name = "lms"
|
||||||
|
port = "lms"
|
||||||
|
|
||||||
|
tags = [
|
||||||
|
"traefik.enable=true",
|
||||||
|
"traefik.http.routers.lms.rule=Host(`music.arsenm.dev`)",
|
||||||
|
"traefik.http.routers.lms.tls.certResolver=letsencrypt",
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
5
minio/README.md
Normal file
5
minio/README.md
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
# Minio
|
||||||
|
|
||||||
|
This is a nomad file for [Minio](https://min.io/), which is an object storage service with an Amazon S3-compatible API. This nomad file exposes both the console and the API of minio. If you would like to not expose the API, remove the `service` stanza with the name `minio-api`.
|
||||||
|
|
||||||
|
In the `env` stanza, there is a username and password set to "CHANGE ME". Set these to the username/password you want to login with.
|
75
minio/minio.nomad
Normal file
75
minio/minio.nomad
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
job "minio" {
|
||||||
|
datacenters = ["dc1"]
|
||||||
|
|
||||||
|
group "minio" {
|
||||||
|
|
||||||
|
network {
|
||||||
|
port "minio" {}
|
||||||
|
port "minio-api" {}
|
||||||
|
}
|
||||||
|
|
||||||
|
volume "minio-data" {
|
||||||
|
type = "host"
|
||||||
|
source = "minio-data"
|
||||||
|
read_only = false
|
||||||
|
}
|
||||||
|
|
||||||
|
task "minio" {
|
||||||
|
driver = "docker"
|
||||||
|
|
||||||
|
volume_mount {
|
||||||
|
volume = "minio-data"
|
||||||
|
destination = "/data"
|
||||||
|
read_only = false
|
||||||
|
}
|
||||||
|
|
||||||
|
config {
|
||||||
|
image = "minio/minio"
|
||||||
|
|
||||||
|
ports = ["minio", "minio-api"]
|
||||||
|
|
||||||
|
args = [
|
||||||
|
"server",
|
||||||
|
"/data",
|
||||||
|
"--address", "0.0.0.0:${NOMAD_PORT_minio_api}",
|
||||||
|
"--console-address", "0.0.0.0:${NOMAD_PORT_minio}"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
env {
|
||||||
|
MINIO_ROOT_USER = "CHANGE ME"
|
||||||
|
MINIO_ROOT_PASSWORD = "CHANGE ME"
|
||||||
|
}
|
||||||
|
|
||||||
|
service {
|
||||||
|
name = "minio"
|
||||||
|
tags = [
|
||||||
|
"traefik.enable=true",
|
||||||
|
"traefik.http.routers.minio.rule=Host(`minio.arsenm.dev`)",
|
||||||
|
"traefik.http.routers.minio.tls.certResolver=letsencrypt"
|
||||||
|
]
|
||||||
|
|
||||||
|
port = "minio"
|
||||||
|
|
||||||
|
check {
|
||||||
|
type = "http"
|
||||||
|
path = "/minio/login"
|
||||||
|
port = "minio"
|
||||||
|
interval = "10s"
|
||||||
|
timeout = "2s"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
service {
|
||||||
|
name = "minio-api"
|
||||||
|
port = "minio-api"
|
||||||
|
|
||||||
|
tags = [
|
||||||
|
"traefik.enable=true",
|
||||||
|
"traefik.http.routers.minio-api.rule=Host(`api.minio.arsenm.dev`)",
|
||||||
|
"traefik.http.routers.minio-api.tls.certResolver=letsencrypt"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
15
nextcloud-onlyoffice/README.md
Normal file
15
nextcloud-onlyoffice/README.md
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
# Nextcloud with OnlyOffice
|
||||||
|
|
||||||
|
This nomad job file is used to run [Nextcloud](https://github.com/nextcloud/server) with [OnlyOffice](https://github.com/ONLYOFFICE/DocumentServer). Since OnlyOffice requires a lot of RAM, this job file reserves 2GB of RAM, 1.5GB for OnlyOffice, and 0.5GB for Nextcloud.
|
||||||
|
|
||||||
|
There are two things in this file you will want to change. In the `env` stanza of the `nextcloud` task, the admin username and password is set to "CHANGE ME". Set these to whatever username/password you prefer.
|
||||||
|
|
||||||
|
This job requires two volumes. One for Nextcloud's `/var/www/html` directory, which contains all of Nextcloud's files, and one for OnlyOffice's data. These should be called `nextcloud-html` and `onlyoffice-data` respectively, and they should both be read/write, not readonly.
|
||||||
|
|
||||||
|
Once Nextcloud is up and running, if you're using https, go to the `nextcloud-html` volume you created and edit `config/config.php`. There, you will want to add
|
||||||
|
|
||||||
|
```php
|
||||||
|
'overwriteprotocol' => 'https',
|
||||||
|
```
|
||||||
|
|
||||||
|
right above the `trusted_domains`. This will tell Nextcloud to use https for its URLs. Once this is done, you can install the OnlyOffice connector app, and set the OnlyOffice URL in the settings to whatever domain you used for OnlyOffice.
|
106
nextcloud-onlyoffice/nextcloud-onlyoffice.nomad
Normal file
106
nextcloud-onlyoffice/nextcloud-onlyoffice.nomad
Normal file
@ -0,0 +1,106 @@
|
|||||||
|
job "nextcloud" {
|
||||||
|
region = "global"
|
||||||
|
datacenters = ["dc1"]
|
||||||
|
type = "service"
|
||||||
|
|
||||||
|
group "nextcloud" {
|
||||||
|
|
||||||
|
network {
|
||||||
|
port "http" {
|
||||||
|
to = 80
|
||||||
|
}
|
||||||
|
|
||||||
|
port "onlyoffice-http" {
|
||||||
|
to = 8000
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
volume "nextcloud-html" {
|
||||||
|
type = "host"
|
||||||
|
source = "nextcloud-html"
|
||||||
|
read_only = false
|
||||||
|
}
|
||||||
|
|
||||||
|
volume "onlyoffice-data" {
|
||||||
|
type = "host"
|
||||||
|
source = "onlyoffice-data"
|
||||||
|
read_only = false
|
||||||
|
}
|
||||||
|
|
||||||
|
task "nextcloud" {
|
||||||
|
driver = "docker"
|
||||||
|
|
||||||
|
env {
|
||||||
|
NEXTCLOUD_ADMIN_USER = "CHANGE ME"
|
||||||
|
NEXTCLOUD_ADMIN_PASSWORD = "CHANGE ME"
|
||||||
|
NEXTCLOUD_TRUSTED_DOMAINS = "nextcloud.arsenm.dev"
|
||||||
|
}
|
||||||
|
|
||||||
|
volume_mount {
|
||||||
|
volume = "nextcloud-html"
|
||||||
|
destination = "/var/www/html"
|
||||||
|
read_only = false
|
||||||
|
}
|
||||||
|
|
||||||
|
config {
|
||||||
|
image = "nextcloud"
|
||||||
|
ports = ["http"]
|
||||||
|
}
|
||||||
|
|
||||||
|
service {
|
||||||
|
name = "nextcloud"
|
||||||
|
port = "http"
|
||||||
|
tags = [
|
||||||
|
"traefik.enable=true",
|
||||||
|
"traefik.http.routers.nextcloud.rule=Host(`nextcloud.arsenm.dev`)",
|
||||||
|
"traefik.http.routers.nextcloud.tls.certresolver=letsencrypt"
|
||||||
|
]
|
||||||
|
|
||||||
|
check {
|
||||||
|
type = "tcp"
|
||||||
|
port = "http"
|
||||||
|
interval = "30s"
|
||||||
|
timeout = "2s"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
resources {
|
||||||
|
cpu = 500
|
||||||
|
memory = 512
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
task "onlyoffice" {
|
||||||
|
driver = "docker"
|
||||||
|
|
||||||
|
volume_mount {
|
||||||
|
volume = "onlyoffice-data"
|
||||||
|
destination = "/var/www/onlyoffice/Data"
|
||||||
|
read_only = false
|
||||||
|
}
|
||||||
|
|
||||||
|
config {
|
||||||
|
image = "onlyoffice/documentserver:latest"
|
||||||
|
ports = ["onlyoffice-http"]
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
service {
|
||||||
|
name = "onlyoffice"
|
||||||
|
port = "onlyoffice-http"
|
||||||
|
tags = [
|
||||||
|
"traefik.enable=true",
|
||||||
|
"traefik.http.routers.onlyoffice.rule=Host(`onlyoffice.arsenm.dev`)",
|
||||||
|
"traefik.http.routers.onlyoffice.tls.certresolver=letsencrypt",
|
||||||
|
"traefik.http.routers.onlyoffice.middlewares=onlyoffice-headers",
|
||||||
|
"traefik.http.middlewares.onlyoffice-headers.headers.customrequestheaders.X-Forwarded-Proto=https"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
resources {
|
||||||
|
cpu = 500
|
||||||
|
memory = 1536
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
7
searxng/README.md
Normal file
7
searxng/README.md
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
# SearXNG
|
||||||
|
|
||||||
|
This nomad file runs the [SearXNG](https://docs.searxng.org/) metasearch engine. SearXNG is a search engine that takes its results from other search engines and aggregates all of them into one, also known as a metasearch engine.
|
||||||
|
|
||||||
|
There is one thing you will want to change in this config. In the `env` stanza, there is a secret set to "CHANGE ME". Set this to a random string. If you're on Linux, most linux distros can generate a suitable string using `openssl rand -base64 25`.
|
||||||
|
|
||||||
|
This job is stateless, so it can be moved around freely by Nomad.
|
50
searxng/searxng.nomad
Normal file
50
searxng/searxng.nomad
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
job "searxng" {
|
||||||
|
region = "global"
|
||||||
|
datacenters = ["dc1"]
|
||||||
|
type = "service"
|
||||||
|
|
||||||
|
group "searxng" {
|
||||||
|
count = 1
|
||||||
|
|
||||||
|
network {
|
||||||
|
port "searx" {}
|
||||||
|
}
|
||||||
|
|
||||||
|
task "searxng" {
|
||||||
|
driver = "docker"
|
||||||
|
|
||||||
|
env {
|
||||||
|
BIND_ADDRESS = "0.0.0.0:${NOMAD_PORT_searx}"
|
||||||
|
SEARXNG_BASE_URL = "https://search.arsenm.dev"
|
||||||
|
SEARXNG_SECRET = "CHANGE ME"
|
||||||
|
}
|
||||||
|
|
||||||
|
config {
|
||||||
|
image = "searxng/searxng:latest"
|
||||||
|
|
||||||
|
ports = ["searx"]
|
||||||
|
|
||||||
|
cap_drop = ["all"]
|
||||||
|
cap_add = [
|
||||||
|
"chown",
|
||||||
|
"setgid",
|
||||||
|
"setuid",
|
||||||
|
"dac_override"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
service {
|
||||||
|
name = "searxng"
|
||||||
|
port = "searx"
|
||||||
|
|
||||||
|
tags = [
|
||||||
|
"traefik.enable=true",
|
||||||
|
"traefik.http.routers.searxng.rule=Host(`search.arsenm.dev`)",
|
||||||
|
"traefik.http.routers.searxng.tls.certResolver=letsencrypt",
|
||||||
|
"traefik.http.routers.searxng.middlewares=searxng-headers",
|
||||||
|
"traefik.http.middlewares.searxng-headers.headers.customrequestheaders.X-Robots-Tag=noindex, noarchive, nofollow"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
21
traefik/README.md
Normal file
21
traefik/README.md
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
# Traefik
|
||||||
|
|
||||||
|
This job file is for the [Traefik](https://github.com/traefik/traefik) reverse proxy, which serves as the entry point for all requests to the cluster, and is the only way to access any services on it from outside the local network.
|
||||||
|
|
||||||
|
This nomad job file also contains Traefik's own config embedded within it as part of a template stanza.
|
||||||
|
|
||||||
|
In Traefik's config file is a variable called `email`. Set this to your email for Let's Encrypt to use when creating new TLS certificates for your domain. In the `tags` variable, there is a URL. In it, replace `<authelia address>` with the address of your Authelia server if you're running one.
|
||||||
|
|
||||||
|
If you would like to constrain the reverse proxy to only run on a specific node in order to ensure that its IP stays constant, you can place the following inside of `task "traefik"`:
|
||||||
|
|
||||||
|
```hcl
|
||||||
|
constraint {
|
||||||
|
attribute = "${attr.unique.hostname}"
|
||||||
|
operator = "=="
|
||||||
|
value = "<hostname>"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
replacing `<hostname>` with the hostname of the node you want this to run on.
|
||||||
|
|
||||||
|
In the `service` stanza, there is a `tags` variable. Inside it, there is a section configuring Authelia. If not using Authelia, remove this section and the one under it. There should be comments above them stating the same thing.
|
109
traefik/traefik.nomad
Normal file
109
traefik/traefik.nomad
Normal file
@ -0,0 +1,109 @@
|
|||||||
|
job "traefik" {
|
||||||
|
region = "global"
|
||||||
|
datacenters = ["dc1"]
|
||||||
|
type = "service"
|
||||||
|
|
||||||
|
group "traefik" {
|
||||||
|
count = 1
|
||||||
|
|
||||||
|
network {
|
||||||
|
port "http" {
|
||||||
|
static = 80
|
||||||
|
}
|
||||||
|
|
||||||
|
port "https" {
|
||||||
|
static = 443
|
||||||
|
}
|
||||||
|
|
||||||
|
port "api" {
|
||||||
|
static = 8081
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
service {
|
||||||
|
name = "traefik"
|
||||||
|
port = "api"
|
||||||
|
|
||||||
|
check {
|
||||||
|
name = "alive"
|
||||||
|
type = "tcp"
|
||||||
|
port = "http"
|
||||||
|
interval = "10s"
|
||||||
|
timeout = "2s"
|
||||||
|
}
|
||||||
|
|
||||||
|
tags = [
|
||||||
|
"traefik.enable=true",
|
||||||
|
|
||||||
|
// Redirect all http requests to HTTPS
|
||||||
|
"traefik.http.middlewares.https-redirect.redirectscheme.permanent=true",
|
||||||
|
"traefik.http.middlewares.https-redirect.redirectscheme.scheme=https",
|
||||||
|
"traefik.http.routers.http-catchall.entrypoints=http",
|
||||||
|
"traefik.http.routers.http-catchall.rule=HostRegexp(`{any:.+}`)",
|
||||||
|
"traefik.http.routers.http-catchall.middlewares=https-redirect",
|
||||||
|
|
||||||
|
// Forward requests to protected services to Authelia. Remove this if not running Authelia.
|
||||||
|
"traefik.http.middlewares.authelia.forwardauth.address=http://<authelia address>/api/verify?rd=https://auth.arsenm.dev/",
|
||||||
|
"traefik.http.middlewares.authelia.forwardauth.trustforwardheader=true",
|
||||||
|
"traefik.http.middlewares.authelia.forwardauth.authresponseheaders=Remote-User, Remote-Groups",
|
||||||
|
|
||||||
|
// Expose Traefik API with authentication. Remove this if not running Authelia.
|
||||||
|
"traefik.http.routers.traefik.rule=Host(`traefik.arsenm.dev`)",
|
||||||
|
"traefik.http.routers.traefik.tls.certResolver=letsencrypt",
|
||||||
|
"traefik.http.routers.traefik.middlewares=authelia",
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
task "traefik" {
|
||||||
|
driver = "docker"
|
||||||
|
|
||||||
|
config {
|
||||||
|
image = "traefik:v2.2"
|
||||||
|
network_mode = "host"
|
||||||
|
|
||||||
|
volumes = [
|
||||||
|
"/opt/traefik/acme.json:/acme.json",
|
||||||
|
"local/traefik.toml:/etc/traefik/traefik.toml",
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
template {
|
||||||
|
data = <<EOF
|
||||||
|
[entryPoints]
|
||||||
|
[entryPoints.http]
|
||||||
|
address = ":80"
|
||||||
|
[entryPoints.https]
|
||||||
|
address = ":443"
|
||||||
|
[entryPoints.traefik]
|
||||||
|
address = ":8081"
|
||||||
|
|
||||||
|
[certificatesResolvers.letsencrypt.acme]
|
||||||
|
email = "you@example.com"
|
||||||
|
storage = "acme.json"
|
||||||
|
[certificatesResolvers.letsencrypt.acme.httpChallenge]
|
||||||
|
entryPoint = "http"
|
||||||
|
|
||||||
|
[api]
|
||||||
|
dashboard = true
|
||||||
|
insecure = true
|
||||||
|
|
||||||
|
# Enable Consul Catalog configuration backend.
|
||||||
|
[providers.consulCatalog]
|
||||||
|
prefix = "traefik"
|
||||||
|
exposedByDefault = false
|
||||||
|
|
||||||
|
[providers.consulCatalog.endpoint]
|
||||||
|
address = "127.0.0.1:8500"
|
||||||
|
scheme = "http"
|
||||||
|
EOF
|
||||||
|
|
||||||
|
destination = "local/traefik.toml"
|
||||||
|
}
|
||||||
|
|
||||||
|
resources {
|
||||||
|
cpu = 100
|
||||||
|
memory = 128
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user