diff --git a/docs/architecture.md b/docs/architecture.md
deleted file mode 100644
index 1395fb4..0000000
--- a/docs/architecture.md
+++ /dev/null
@@ -1,33 +0,0 @@
-```mermaid
-architecture-beta
- group cloud(cloud)[Hetzner]
-
- group network(cloud)[network] in cloud
-
- service disk1(mdi:disk)[Storage] in cloud
- service disk2(mdi:disk)[Storage] in cloud
-
- service dev(mdi:server)[dev] in network
-
- service prod(mdi:server)[prod] in network
- service prodfirewall(mdi:firewall)[firewall] in cloud
-
- service vpn(mdi:server)[vpn] in cloud
- service vpnfirewall(mdi:firewall)[firewall] in cloud
-
-
-
- service gateway(mdi:web)[gateway] in cloud
-
- dev:L -- R:prod
- disk1:B -- T:prod
- disk2:B -- T:dev
-
-
- prod:B -- T:prodfirewall
- vpn:B -- T:vpnfirewall
-
- prodfirewall: L -- R: gateway
- vpnfirewall: B -- T: gateway
-
-```
\ No newline at end of file
diff --git a/infra/firewalls.tf b/infra/firewalls.tf
index 807f33e..55528ad 100644
--- a/infra/firewalls.tf
+++ b/infra/firewalls.tf
@@ -72,13 +72,6 @@ resource "hcloud_firewall" "prod" {
source_ips = ["0.0.0.0/0", "::/0"]
}
- rule { # mongo via Traefik tcp entrypoint (Database)
- direction = "in"
- protocol = "tcp"
- port = "27017"
- source_ips = ["0.0.0.0/0", "::/0"]
- }
-
rule { # rustdesk hbbs
direction = "in"
protocol = "tcp"
diff --git a/readme.md b/readme.md
index 00ffcbc..0a21744 100644
--- a/readme.md
+++ b/readme.md
@@ -1,48 +1,206 @@
-# Server Repository
+# Server
-This repository contains various scripts and configurations for managing Docker containers and services.
+Infrastructure-as-code and service definitions for luke-else.co.uk's self-hosted server estate: three [Hetzner Cloud](https://www.hetzner.com/cloud/) VPS instances provisioned with [OpenTofu](https://opentofu.org/), each running a set of Docker Compose stacks behind [Traefik](https://traefik.io/traefik/).
-## Scripts
+## Contents
-- `cicd-spindown.sh`: Spins down all CICD relevant Docker containers.
-- `cicd-spinup.sh`: Spins up all CICD relevant Docker containers.
-- `main-spindown.sh`: Spins down all Docker containers.
-- `main-spinup.sh`: Spins up all Docker containers.
+- [Architecture](#architecture)
+- [Repository layout](#repository-layout)
+- [Prerequisites](#prerequisites)
+- [Provisioning the infrastructure](#provisioning-the-infrastructure-infra)
+- [Deploying the services](#deploying-the-services-services)
+- [Service inventory](#service-inventory)
+- [First-time setup](#first-time-setup)
+- [Development container](#development-container)
+- [Security notes](#security-notes)
-## Services
+## Architecture
-### Bitwarden
+Three servers, one shared private network:
-Configuration for Bitwarden is located in [Bitwarden/docker-compose.yml](Bitwarden/docker-compose.yml).
+```mermaid
+architecture-beta
+ group cloud(cloud)[Hetzner]
-### Database
+ group network(cloud)[network] in cloud
-Configuration for MongoDB is located in [Database/docker-compose.yml](Database/docker-compose.yml).
+ service disk1(mdi:disk)[Storage] in cloud
+ service disk2(mdi:disk)[Storage] in cloud
-### Misc
+ service dev(mdi:server)[dev] in network
-Configuration for miscellaneous services is located in [Misc/docker-compose.yml](Misc/docker-compose.yml).
+ service prod(mdi:server)[prod] in network
+ service prodfirewall(mdi:firewall)[firewall] in cloud
-### Tracking
+ service vpn(mdi:server)[vpn] in cloud
+ service vpnfirewall(mdi:firewall)[firewall] in cloud
-Configuration for tracking services is located in [Tracking/docker-compose.yml](Tracking/docker-compose.yml).
+ service gateway(mdi:web)[gateway] in cloud
-### Traefik
+ dev:L -- R:prod
+ disk1:B -- T:prod
+ disk2:B -- T:dev
-Configuration for Traefik is located in [Traefik/docker-compose.yml](Traefik/docker-compose.yml).
+ prod:B -- T:prodfirewall
+ vpn:B -- T:vpnfirewall
-### TraefikRunner
+ prodfirewall: L -- R: gateway
+ vpnfirewall: B -- T: gateway
+```
-Configuration for TraefikRunner is located in [TraefikRunner/docker-compose.yml](TraefikRunner/docker-compose.yml).
+`gateway` represents the public internet, not a provisioned resource.
-### Websites
+| Server | Purpose | Network | Volume |
+|---|---|---|---|
+| `dev` | Gitea, CI runner, dev-facing Traefik | Private network only (no public firewall exposure beyond CI/CD) | `dev-storage` |
+| `prod` | Public-facing websites, Bitwarden, RustDesk, status page, prod Traefik | Private network + public firewall | `prod-storage` |
+| `vpn` | OpenVPN + its own Traefik | **Not** attached to the private network — kept isolated so a compromised VPN endpoint can't pivot to `dev`/`prod` | none |
-Configuration for websites is located in [Websites/docker-compose.yml](Websites/docker-compose.yml).
+`dev` and `prod` share a private Hetzner network (`10.0.1.0/24` by default) so CI/CD on `dev` can reach deployment targets on `prod` without exposing that traffic publicly. `vpn` is deliberately kept off this network. Each server has its own Hetzner Cloud Firewall (see `infra/firewalls.tf`) that only opens the ports actually used by the compose stacks running on it, plus SSH restricted to `var.allowed_ssh_source_ips`.
-## ToDo
+## Repository layout
-See [todo.md](todo.md) for a list of tasks and configurations that need to be completed.
\ No newline at end of file
+```
+.
+├── infra/ # OpenTofu (Terraform-compatible) config — provisions the 3 servers, network, firewalls, volumes
+│ ├── servers.tf
+│ ├── network.tf
+│ ├── firewalls.tf
+│ ├── volumes.tf
+│ ├── ssh.tf
+│ ├── variables.tf
+│ ├── outputs.tf
+│ ├── versions.tf
+│ └── terraform.tfvars.example
+├── services/ # Docker Compose stacks, grouped by which server they run on
+│ ├── dev/ # Gitea + CI runner + Traefik (git.luke-else.co.uk, cicd.luke-else.co.uk)
+│ ├── prod/ # Websites, Bitwarden, RustDesk, status page + Traefik
+│ ├── vpn/ # OpenVPN + Traefik
+│ └── todo.md # Outstanding manual setup/hardening tasks
+├── docs/
+│ └── architecture.md # Source of the architecture diagram above
+├── .devcontainer/ # Git submodule: shared devcontainer for working on this repo (OpenTofu tooling)
+└── assets/
+```
+
+Each of `services/dev`, `services/prod`, `services/vpn` follows the same convention: one `*-docker-compose.yml` (or subdirectory, e.g. `Runners/`) per logical service, plus a `spinup.sh` / `spindown.sh` pair that brings up or tears down every stack on that host in the right order.
+
+## Prerequisites
+
+- A [Hetzner Cloud](https://console.hetzner.cloud/) project and API token
+- An SSH key uploaded to that project (Console → Security → SSH Keys)
+- [OpenTofu](https://opentofu.org/docs/intro/install/) `>= 1.6.0`
+- Docker + the Compose plugin on each target server
+- DNS records for the domains in [Service inventory](#service-inventory) pointed at the relevant server's public IP
+
+## Provisioning the infrastructure (`infra/`)
+
+```sh
+cd infra
+export HCLOUD_TOKEN=your-hetzner-api-token # never commit this
+cp terraform.tfvars.example terraform.tfvars
+$EDITOR terraform.tfvars # set ssh_key_name at minimum
+
+tofu init
+tofu plan
+tofu apply
+```
+
+This creates:
+- `hcloud_server.dev`, `hcloud_server.prod`, `hcloud_server.vpn`
+- `hcloud_network.main` + subnet, shared by `dev` and `prod`
+- `hcloud_firewall.dev` / `.prod` / `.vpn`, scoped to the ports each host actually uses
+- `hcloud_volume.dev_storage` / `.prod_storage`
+
+Useful outputs: `tofu output dev_ipv4`, `tofu output prod_ipv4`, `tofu output vpn_ipv4`.
+
+`terraform.tfvars` and any `*.tfvars` file are gitignored — never commit real values there. Defaults for server sizes, locations, and IP ranges live in `infra/variables.tf` and can be overridden per-environment via `terraform.tfvars`.
+
+## Deploying the services (`services/`)
+
+Once a server exists, copy the relevant `services//` directory to it (e.g. `scp -r services/prod user@:~/services`) and run the matching script from inside that directory:
+
+```sh
+# on dev
+./spinup.sh # Traefik → Gitea Runner → Watchtower
+./spindown.sh # reverse order, then prunes images/volumes
+
+# on prod
+./spinup.sh # Traefik, then (after a 20s cert/registry settle) Watchtower, status, websites, Bitwarden, RustDesk
+./spindown.sh
+
+# on vpn
+./spinup.sh # Traefik → OpenVPN → Watchtower
+./spindown.sh
+```
+
+Each stack can also be managed individually with plain Compose, e.g.:
+
+```sh
+cd services/prod
+docker compose -f bitwarden-docker-compose.yml up -d
+docker compose -f bitwarden-docker-compose.yml down
+```
+
+All three hosts run [Watchtower](https://containrrr.dev/watchtower/) polling every 60s with cleanup enabled, so images are kept current automatically once deployed — the spinup scripts only need to be re-run after adding/removing a service or changing compose files.
+
+Every public-facing service is fronted by its host's own Traefik instance, terminating TLS via Let's Encrypt (`tlschallenge`, port 80/443). Each stack joins an external `proxy` Docker network and opts in via `traefik.enable=true` labels rather than publishing ports directly (RustDesk and the Gitea SSH port are the deliberate exceptions, since they aren't HTTP).
+
+## Service inventory
+
+### `dev`
+
+| Service | Compose file | Domain(s) |
+|---|---|---|
+| Traefik | `traefik-docker-compose.yml` | `traefik.cicd.luke-else.co.uk` |
+| Gitea | `gitea-docker-compose.yml` | `git.luke-else.co.uk` (HTTP), SSH on `222` |
+| Gitea Actions runner | `Runners/docker-compose.yml` | `cicd.luke-else.co.uk` |
+| Watchtower | `watchtower-docker-compose.yml` | — |
+
+### `prod`
+
+| Service | Compose file | Domain(s) |
+|---|---|---|
+| Traefik | `traefik-docker-compose.yml` | `traefik.luke-else.co.uk` |
+| Websites | `web-docker-compose.yml` | `luke-else.co.uk`, `dev.luke-else.co.uk`, `metarius.luke-else.co.uk`, `www.divine-couture.co.uk`, `snexo.co.uk` |
+| Status page (Uptime Kuma) | `status-docker-compose.yml` | `status.luke-else.co.uk` |
+| Bitwarden (Vaultwarden) | `bitwarden-docker-compose.yml` | `bitwarden.luke-else.co.uk` |
+| RustDesk relay (hbbs/hbbr) | `rd-docker-compose.yml` | `rd.luke-else.co.uk`, ports `21115-21119` |
+| Watchtower | `watchtower-docker-compose.yml` | — |
+
+### `vpn`
+
+| Service | Compose file | Domain(s) |
+|---|---|---|
+| Traefik | `traefik-docker-compose.yml` | `traefik.vpn.luke-else.co.uk` |
+| OpenVPN (Dockovpn) | `vpn-docker-compose.yml` | `vpn.luke-else.co.uk`, UDP `1194` |
+| Watchtower | `watchtower-docker-compose.yml` | — |
+
+## First-time setup
+
+A few things need manual attention before a stack is fully live — tracked in [`services/todo.md`](services/todo.md), summarized here:
+
+- **Gitea Actions runner**: set a real `GITEA_RUNNER_REGISTRATION_TOKEN` in `services/dev/Runners/docker-compose.yml` (generate one from the Gitea admin UI) before starting the runner.
+- **Traefik dashboard auth**: the committed basic-auth hash is a placeholder. Generate your own with `echo $(htpasswd -nb user password) | sed -e 's/\$/\$\$/g'` and replace the `traefik-auth` middleware value in each `traefik-docker-compose.yml`.
+- **General host hardening**: non-root user, UFW, unattended-upgrades, Docker install — see `services/todo.md`.
+
+## Development container
+
+`.devcontainer` is a git submodule providing a ready-to-use OpenTofu development environment (VS Code + OpenTofu/Docker/Mermaid extensions). After cloning:
+
+```sh
+git submodule update --init --recursive
+```
+
+Then reopen the repo in VS Code with the Dev Containers extension.
+
+## Security notes
+
+- Real secrets (`HCLOUD_TOKEN`, `*.tfvars`, `.env` files) must never be committed — see `.gitignore`.
+- SSH is restricted to `var.allowed_ssh_source_ips` on every host; narrow this from the default `0.0.0.0/0` once you know your own egress IP(s).
+- `vpn` is intentionally excluded from the private network so that a compromised VPN endpoint cannot reach `dev` or `prod` directly.
+- Firewalls are allowlists scoped per host in `infra/firewalls.tf` — only ports actually used by that host's compose stacks (plus the cross-host private network range) are open.
diff --git a/services/Database/docker-compose.yml b/services/Database/docker-compose.yml
deleted file mode 100644
index 3d1a9a3..0000000
--- a/services/Database/docker-compose.yml
+++ /dev/null
@@ -1,24 +0,0 @@
-services:
-
-#MongoDB (27017)
- mongodb:
- image: "mongo:latest"
- container_name: mongoDB
- volumes:
- - ./mongo/:/data/db
- networks:
- - proxy
- labels:
- - "traefik.enable=true"
- - "traefik.tcp.routers.mongodb.rule=HostSNI(`*`)"
- - "traefik.tcp.routers.mongodb.entrypoints=mongo"
- - "traefik.tcp.routers.mongodb.service=mongodb"
- - "traefik.tcp.services.mongodb.loadbalancer.server.port=27017"
- environment:
- MONGO_INITDB_ROOT_USERNAME: root
- MONGO_INITDB_ROOT_PASSWORD: rootpassword
- restart: unless-stopped
-
-networks:
- proxy:
- external: true
\ No newline at end of file
diff --git a/services/Misc/docker-compose.yml b/services/Misc/docker-compose.yml
deleted file mode 100644
index 7914b79..0000000
--- a/services/Misc/docker-compose.yml
+++ /dev/null
@@ -1,51 +0,0 @@
-services:
- status:
- image: louislam/uptime-kuma:latest
- container_name: status
- volumes:
- - ./uptime-kuma/data:/app/data
- networks:
- - proxy
- labels:
- ## Expose uptime-kuma Through Trefik ##
- - "traefik.enable=true" # <== Enable traefik to proxy this container
-
- - "traefik.http.middlewares.redirect-web-secure.redirectscheme.scheme=https"
- - "traefik.http.routers.status-insecure.middlewares=redirect-web-secure"
- - "traefik.http.routers.status-insecure.rule=Host(`status.luke-else.co.uk`)"
- - "traefik.http.routers.status-insecure.entrypoints=web"
-
- - "traefik.http.routers.status.rule=Host(`status.luke-else.co.uk`)"
- - "traefik.http.routers.status.entrypoints=websecure"
- - "traefik.http.routers.status.tls.certresolver=myresolver"
- restart: unless-stopped
-
- portainer:
- image: portainer/portainer-ce:latest
- container_name: portainer
- security_opt:
- - no-new-privileges:true
- volumes:
- - /etc/localtime:/etc/localtime:ro
- - /var/run/docker.sock:/var/run/docker.sock:ro
- - ./portainer-data:/data
- networks:
- - proxy
- labels:
- ## Expose portainer Through Trefik ##
- - "traefik.enable=true" # <== Enable traefik to proxy this container
-
- - "traefik.http.middlewares.redirect-web-secure.redirectscheme.scheme=https"
- - "traefik.http.routers.portainer-insecure.middlewares=redirect-web-secure"
- - "traefik.http.routers.portainer-insecure.rule=Host(`portainer.luke-else.co.uk`)"
- - "traefik.http.routers.portainer-insecure.entrypoints=web"
-
- - "traefik.http.routers.portainer.rule=Host(`portainer.luke-else.co.uk`)"
- - "traefik.http.services.portainer.loadbalancer.server.port=9000"
- - "traefik.http.routers.portainer.entrypoints=websecure"
- - "traefik.http.routers.portainer.tls.certresolver=myresolver"
- restart: unless-stopped
-
-networks:
- proxy:
- external: true
\ No newline at end of file
diff --git a/services/Tracking/docker-compose.yml b/services/Tracking/docker-compose.yml
deleted file mode 100644
index 6fe612a..0000000
--- a/services/Tracking/docker-compose.yml
+++ /dev/null
@@ -1,52 +0,0 @@
-services:
- ackee:
- image: electerious/ackee
- container_name: ackee
- environment:
- - WAIT_HOSTS=mongo:27017
- - ACKEE_MONGODB=mongodb://mongo-ackee:27017/ackee
- env_file:
- - .env
- expose:
- - 3000
- networks:
- - tracking
- - proxy
- labels:
- ## Expose Ackee Through Trefik ##
- - "traefik.enable=true" # <== Enable traefik to proxy this container
-
- # Enable CORS headers
- - "traefik.http.middlewares.cors-tracking.headers.accesscontrolallowmethods=*"
- - "traefik.http.middlewares.cors-tracking.headers.accesscontrolalloworiginlist=https://luke-else.co.uk"
- - "traefik.http.middlewares.cors-tracking.headers.accesscontrolallowcredentials=true"
- - "traefik.http.middlewares.cors-tracking.headers.accesscontrolallowheaders=Content-Type,Authorization"
- - "traefik.http.middlewares.cors-tracking.headers.addvaryheader=true"
- - "traefik.http.middlewares.cors-tracking.headers.accesscontrolmaxage=100"
-
- - "traefik.http.middlewares.redirect-web-secure.redirectscheme.scheme=https"
- - "traefik.http.routers.ackee-insecure.middlewares=redirect-web-secure"
- - "traefik.http.routers.ackee-insecure.rule=Host(`tracking.luke-else.co.uk`)"
- - "traefik.http.routers.ackee-insecure.entrypoints=web"
-
- - "traefik.http.routers.ackee.rule=Host(`tracking.luke-else.co.uk`)"
- - "traefik.http.routers.ackee.entrypoints=websecure"
- - "traefik.http.routers.ackee.tls.certresolver=myresolver"
- - "traefik.http.routers.ackee.middlewares=cors-tracking"
- depends_on:
- - mongo
- restart: unless-stopped
-
- mongo:
- image: mongo
- container_name: mongo-ackee
- volumes:
- - ./data:/data/db
- networks:
- - tracking
- restart: unless-stopped
-
-networks:
- proxy:
- external: true
- tracking:
\ No newline at end of file
diff --git a/services/cicd-spindown.sh b/services/cicd-spindown.sh
deleted file mode 100755
index 1e3d49c..0000000
--- a/services/cicd-spindown.sh
+++ /dev/null
@@ -1,16 +0,0 @@
-#Script file for spinning down all CICD relevant docker-containers
- cd ./Development/Runners/
- docker compose down
- cd ../..
-
-cd ./TraefikRunner/
-docker compose down
-cd ..
-
-cd ./Watchtower/
-docker compose down
-cd ..
-
-docker rmi $(docker images -q)
-docker system prune -f -a
-docker volume prune -f -a
\ No newline at end of file
diff --git a/services/cicd-spinup.sh b/services/cicd-spinup.sh
deleted file mode 100755
index 6a3b139..0000000
--- a/services/cicd-spinup.sh
+++ /dev/null
@@ -1,13 +0,0 @@
-#Script file for spinning up all docker-containers
-
-cd ./TraefikRunner/
-docker compose pull && docker compose up -d
-cd ..
-
- cd ./Development/Runners/
- docker compose pull && docker compose up -d
- cd ../..
-
-cd ./Watchtower/
-docker compose pull && docker compose up -d
-cd ..
\ No newline at end of file
diff --git a/services/Development/Runners/config.yaml b/services/dev/Runners/config.yaml
similarity index 100%
rename from services/Development/Runners/config.yaml
rename to services/dev/Runners/config.yaml
diff --git a/services/Development/Runners/docker-compose.yml b/services/dev/Runners/docker-compose.yml
similarity index 95%
rename from services/Development/Runners/docker-compose.yml
rename to services/dev/Runners/docker-compose.yml
index f9f5acd..0e1bb51 100644
--- a/services/Development/Runners/docker-compose.yml
+++ b/services/dev/Runners/docker-compose.yml
@@ -13,7 +13,7 @@ services:
CONFIG_FILE: /config.yaml
GITEA_INSTANCE_URL: "https://git.luke-else.co.uk"
GITEA_RUNNER_REGISTRATION_TOKEN: "INSERT REGISTRATION TOKEN"
- GITEA_RUNNER_NAME: "CICD"
+ GITEA_RUNNER_NAME: "CICD#1"
labels:
## Expose cicd Through Trefik ##
- "traefik.enable=true" # <== Enable traefik to proxy this container
@@ -30,4 +30,4 @@ services:
networks:
proxy:
- external: true
\ No newline at end of file
+ external: true
diff --git a/services/Development/Gitea/docker-compose.yml b/services/dev/gitea-docker-compose.yml
similarity index 100%
rename from services/Development/Gitea/docker-compose.yml
rename to services/dev/gitea-docker-compose.yml
diff --git a/services/dev/spindown.sh b/services/dev/spindown.sh
new file mode 100755
index 0000000..5e71bd2
--- /dev/null
+++ b/services/dev/spindown.sh
@@ -0,0 +1,14 @@
+#!/bin/bash
+# Script file for spinning down all dev docker-containers
+cd "$(dirname "$0")"
+
+cd Runners
+docker compose down
+cd ..
+
+docker compose -f traefik-docker-compose.yml down
+docker compose -f watchtower-docker-compose.yml down
+
+docker rmi $(docker images -q)
+docker system prune -f -a
+docker volume prune -f -a
diff --git a/services/dev/spinup.sh b/services/dev/spinup.sh
new file mode 100755
index 0000000..6d9bfcc
--- /dev/null
+++ b/services/dev/spinup.sh
@@ -0,0 +1,12 @@
+#!/bin/bash
+# Script file for spinning up all dev docker-containers
+set -e
+cd "$(dirname "$0")"
+
+docker compose -f traefik-docker-compose.yml pull && docker compose -f traefik-docker-compose.yml up -d
+
+cd Runners
+docker compose pull && docker compose up -d
+cd ..
+
+docker compose -f watchtower-docker-compose.yml pull && docker compose -f watchtower-docker-compose.yml up -d
diff --git a/services/TraefikRunner/docker-compose.yml b/services/dev/traefik-docker-compose.yml
similarity index 100%
rename from services/TraefikRunner/docker-compose.yml
rename to services/dev/traefik-docker-compose.yml
diff --git a/services/Watchtower/docker-compose.yml b/services/dev/watchtower-docker-compose.yml
similarity index 100%
rename from services/Watchtower/docker-compose.yml
rename to services/dev/watchtower-docker-compose.yml
diff --git a/services/main-spindown.sh b/services/main-spindown.sh
deleted file mode 100755
index 1012835..0000000
--- a/services/main-spindown.sh
+++ /dev/null
@@ -1,42 +0,0 @@
-#Script file for spinning down all docker-containers
-
-cd ./Websites/
-docker compose down
-cd ..
-
-cd ./Tracking/
-docker compose down
-cd ..
-
-
- cd ./Development/Gitea/
- docker compose down
- cd ../..
-
-cd ./Database/
-docker compose down
-cd ..
-
-cd ./Bitwarden/
-docker compose down
-cd ..
-
-cd ./Rustdesk/
-docker compose down
-cd ..
-
-cd ./Misc/
-docker compose down
-cd ..
-
-cd ./Watchtower/
-docker compose down
-cd ..
-
-cd ./Traefik/
-docker compose down
-cd ..
-
-docker rmi $(docker images -q)
-docker system prune -f -a
-docker volume prune -f -a
\ No newline at end of file
diff --git a/services/main-spinup.sh b/services/main-spinup.sh
deleted file mode 100755
index d8f8a03..0000000
--- a/services/main-spinup.sh
+++ /dev/null
@@ -1,39 +0,0 @@
-#Script file for spinning up all docker-containers
-
-cd ./Traefik/
-docker compose pull && docker compose up -d
-cd ..
-
- cd ./Development/Gitea/
- docker compose pull && docker compose up -d
- cd ../..
-
-sleep 20 # Allow Gitea + registry to start up before starting the rest of the services
-
-cd ./Watchtower/
-docker compose pull && docker compose up -d
-cd ..
-
-cd ./Tracking/
-docker compose pull && docker compose up -d
-cd ..
-
-cd ./Websites/
-docker compose pull && docker compose up -d
-cd ..
-
-cd ./Database/
-docker compose pull && docker compose up -d
-cd ..
-
-cd ./Bitwarden/
-docker compose pull && docker compose up -d
-cd ..
-
-cd ./Rustdesk/
-docker compose pull && docker compose up -d
-cd ..
-
-cd ./Misc/
-docker compose pull && docker compose up -d
-cd ..
\ No newline at end of file
diff --git a/services/Bitwarden/docker-compose.yml b/services/prod/bitwarden-docker-compose.yml
similarity index 100%
rename from services/Bitwarden/docker-compose.yml
rename to services/prod/bitwarden-docker-compose.yml
diff --git a/services/Rustdesk/docker-compose.yml b/services/prod/rd-docker-compose.yml
similarity index 100%
rename from services/Rustdesk/docker-compose.yml
rename to services/prod/rd-docker-compose.yml
diff --git a/services/prod/spindown.sh b/services/prod/spindown.sh
new file mode 100755
index 0000000..665ab2b
--- /dev/null
+++ b/services/prod/spindown.sh
@@ -0,0 +1,14 @@
+#!/bin/bash
+# Script file for spinning down all prod docker-containers
+cd "$(dirname "$0")"
+
+docker compose -f web-docker-compose.yml down
+docker compose -f status-docker-compose.yml down
+docker compose -f bitwarden-docker-compose.yml down
+docker compose -f rd-docker-compose.yml down
+docker compose -f watchtower-docker-compose.yml down
+docker compose -f traefik-docker-compose.yml down
+
+docker rmi $(docker images -q)
+docker system prune -f -a
+docker volume prune -f -a
diff --git a/services/prod/spinup.sh b/services/prod/spinup.sh
new file mode 100755
index 0000000..8fa967e
--- /dev/null
+++ b/services/prod/spinup.sh
@@ -0,0 +1,14 @@
+#!/bin/bash
+# Script file for spinning up all prod docker-containers
+set -e
+cd "$(dirname "$0")"
+
+docker compose -f traefik-docker-compose.yml pull && docker compose -f traefik-docker-compose.yml up -d
+
+sleep 20 # Allow Traefik + registry auth to settle before starting the rest of the services
+
+docker compose -f watchtower-docker-compose.yml pull && docker compose -f watchtower-docker-compose.yml up -d
+docker compose -f status-docker-compose.yml pull && docker compose -f status-docker-compose.yml up -d
+docker compose -f web-docker-compose.yml pull && docker compose -f web-docker-compose.yml up -d
+docker compose -f bitwarden-docker-compose.yml pull && docker compose -f bitwarden-docker-compose.yml up -d
+docker compose -f rd-docker-compose.yml pull && docker compose -f rd-docker-compose.yml up -d
diff --git a/services/prod/status-docker-compose.yml b/services/prod/status-docker-compose.yml
new file mode 100644
index 0000000..0e833c2
--- /dev/null
+++ b/services/prod/status-docker-compose.yml
@@ -0,0 +1,25 @@
+services:
+ status:
+ image: louislam/uptime-kuma:latest
+ container_name: status
+ volumes:
+ - ./uptime-kuma/data:/app/data
+ networks:
+ - proxy
+ labels:
+ ## Expose uptime-kuma Through Trefik ##
+ - "traefik.enable=true" # <== Enable traefik to proxy this container
+
+ - "traefik.http.middlewares.redirect-web-secure.redirectscheme.scheme=https"
+ - "traefik.http.routers.status-insecure.middlewares=redirect-web-secure"
+ - "traefik.http.routers.status-insecure.rule=Host(`status.luke-else.co.uk`)"
+ - "traefik.http.routers.status-insecure.entrypoints=web"
+
+ - "traefik.http.routers.status.rule=Host(`status.luke-else.co.uk`)"
+ - "traefik.http.routers.status.entrypoints=websecure"
+ - "traefik.http.routers.status.tls.certresolver=myresolver"
+ restart: unless-stopped
+
+networks:
+ proxy:
+ external: true
diff --git a/services/Traefik/docker-compose.yml b/services/prod/traefik-docker-compose.yml
similarity index 96%
rename from services/Traefik/docker-compose.yml
rename to services/prod/traefik-docker-compose.yml
index c2b613a..53e89ac 100644
--- a/services/Traefik/docker-compose.yml
+++ b/services/prod/traefik-docker-compose.yml
@@ -8,7 +8,6 @@ services:
- "--providers.docker.exposedbydefault=false"
- "--entrypoints.web.address=:80"
- "--entrypoints.websecure.address=:443"
- - "--entrypoints.mongo.address=:27017"
- "--entrypoints.web.transport.respondingTimeouts.readTimeout=120s"
- "--entrypoints.websecure.transport.respondingTimeouts.readTimeout=120s"
- "--entrypoints.web.transport.respondingTimeouts.writeTimeout=120s"
@@ -36,11 +35,11 @@ services:
- "traefik.http.routers.traefik.rule=Host(`traefik.luke-else.co.uk`)"
- "traefik.http.routers.traefik.entrypoints=websecure"
- "traefik.http.routers.traefik.service=api@internal"
- - "traefik.http.routers.traefik.tls.certresolver=myresolver"
+ - "traefik.http.routers.traefik.tls.certresolver=myresolver"
- "traefik.http.routers.traefik.middlewares=traefik-auth"
- "traefik.http.middlewares.traefik-auth.basicauth.users=user:$$2y$$05$$s/vPphFtSO2fWJR7SYkEb.90UwPDRM3aOKqgOF/rme/3fUQ5tvpTS"
restart: unless-stopped
networks:
proxy:
- name: proxy
\ No newline at end of file
+ name: proxy
diff --git a/services/prod/watchtower-docker-compose.yml b/services/prod/watchtower-docker-compose.yml
new file mode 100644
index 0000000..ee735d3
--- /dev/null
+++ b/services/prod/watchtower-docker-compose.yml
@@ -0,0 +1,8 @@
+services:
+ watchtower:
+ image: nickfedor/watchtower
+ volumes:
+ - /var/run/docker.sock:/var/run/docker.sock
+ environment:
+ - WATCHTOWER_CLEANUP=true
+ - WATCHTOWER_POLL_INTERVAL=60
diff --git a/services/Websites/docker-compose.yml b/services/prod/web-docker-compose.yml
similarity index 100%
rename from services/Websites/docker-compose.yml
rename to services/prod/web-docker-compose.yml
diff --git a/services/todo.md b/services/todo.md
index a53d118..eccd34c 100644
--- a/services/todo.md
+++ b/services/todo.md
@@ -3,13 +3,6 @@
## General
- Setup non root user
-- UFW should be setup to keep VPS secure and only allow for:
-
- - https
- - http
- - ssh
- - ftp
- - 27017
- "21115:21115/tcp"
- "21116:21116/tcp"
@@ -17,8 +10,6 @@
- "21117:21117/tcp"
- "21119:21119/tcp"
-- Install SSH keys
-- Setup unattended upgrades
- Install docker, docker-compose and apache utils.
## Traefik + TraefikRunner + Traefik VPN
@@ -34,15 +25,6 @@
- Ensure that a registration token has been setup before continuing
-## Tracking
-
-Create a .env file with the following content:
-
-```sh
-ACKEE_USERNAME=luke-else
-ACKEE_PASSWORD=XXX
-```
-
## Websites
- Ensure website files are copied over
@@ -52,13 +34,3 @@ ACKEE_PASSWORD=XXX
- Ensure that all data is fully encrypted during transfer.
- Ensure that ports are assigned correctly for the system
-
-## Rustdesk
-
-- No additional setup required bar the ports
-
-## Database
-
-- Ensure that mysql root password, user and default database are updated.
-- Ensure that mongo root password, and user are updated.
-- Ensure database ports are correctly assigned and do not have to pass through traefik.
diff --git a/services/vpn-spindown.sh b/services/vpn-spindown.sh
deleted file mode 100755
index 7cd9ef1..0000000
--- a/services/vpn-spindown.sh
+++ /dev/null
@@ -1,17 +0,0 @@
-#Script file for spinning down all CICD relevant docker-containers
-
-cd ./VPN/
-docker compose down
-cd ..
-
-cd ./TraefikVPN/
-docker compose down
-cd ..
-
-cd ./Watchtower/
-docker compose down
-cd ..
-
-docker rmi $(docker images -q)
-docker system prune -f -a
-docker volume prune -f -a
\ No newline at end of file
diff --git a/services/vpn-spinup.sh b/services/vpn-spinup.sh
deleted file mode 100755
index 4cc5159..0000000
--- a/services/vpn-spinup.sh
+++ /dev/null
@@ -1,13 +0,0 @@
-#Script file for spinning up all docker-containers
-
-cd ./TraefikRunner/
-docker compose pull && docker compose up -d
-cd ..
-
-cd ./VPN/
-docker compose pull && docker compose up -d
-cd ..
-
-cd ./Watchtower/
-docker compose pull && docker compose up -d
-cd ..
\ No newline at end of file
diff --git a/services/vpn/spindown.sh b/services/vpn/spindown.sh
new file mode 100755
index 0000000..c30233f
--- /dev/null
+++ b/services/vpn/spindown.sh
@@ -0,0 +1,11 @@
+#!/bin/bash
+# Script file for spinning down all vpn docker-containers
+cd "$(dirname "$0")"
+
+docker compose -f vpn-docker-compose.yml down
+docker compose -f traefik-docker-compose.yml down
+docker compose -f watchtower-docker-compose.yml down
+
+docker rmi $(docker images -q)
+docker system prune -f -a
+docker volume prune -f -a
diff --git a/services/vpn/spinup.sh b/services/vpn/spinup.sh
new file mode 100755
index 0000000..14250a8
--- /dev/null
+++ b/services/vpn/spinup.sh
@@ -0,0 +1,8 @@
+#!/bin/bash
+# Script file for spinning up all vpn docker-containers
+set -e
+cd "$(dirname "$0")"
+
+docker compose -f traefik-docker-compose.yml pull && docker compose -f traefik-docker-compose.yml up -d
+docker compose -f vpn-docker-compose.yml pull && docker compose -f vpn-docker-compose.yml up -d
+docker compose -f watchtower-docker-compose.yml pull && docker compose -f watchtower-docker-compose.yml up -d
diff --git a/services/TraefikVPN/docker-compose.yml b/services/vpn/traefik-docker-compose.yml
similarity index 100%
rename from services/TraefikVPN/docker-compose.yml
rename to services/vpn/traefik-docker-compose.yml
diff --git a/services/VPN/docker-compose.yml b/services/vpn/vpn-docker-compose.yml
similarity index 100%
rename from services/VPN/docker-compose.yml
rename to services/vpn/vpn-docker-compose.yml
diff --git a/services/vpn/watchtower-docker-compose.yml b/services/vpn/watchtower-docker-compose.yml
new file mode 100644
index 0000000..ee735d3
--- /dev/null
+++ b/services/vpn/watchtower-docker-compose.yml
@@ -0,0 +1,8 @@
+services:
+ watchtower:
+ image: nickfedor/watchtower
+ volumes:
+ - /var/run/docker.sock:/var/run/docker.sock
+ environment:
+ - WATCHTOWER_CLEANUP=true
+ - WATCHTOWER_POLL_INTERVAL=60