Don't need monolith

This commit is contained in:
Jake Runyan 2024-08-11 17:32:28 -07:00
parent 30039bf91b
commit a54e32a54b
110 changed files with 130 additions and 1153 deletions

19
code-server/README.md Normal file
View File

@ -0,0 +1,19 @@
# Visual Studio
A self-hosted version of Visual Studio Code, as an online notebook.
## Instructions
### Volumes
Mount the persistant storage somewhere.
- `/pwspool/software/code-server/config:/config`
### Metadata
Re-roll hashed passwords.
`./olomana.ini:/etc/grafana/grafana.ini`
## References
https://docs.linuxserver.io/images/docker-code-server/
https://coder.com/docs/code-server/latest/install#docker
https://hub.docker.com/r/linuxserver/code-server
https://github.com/coder/code-server/blob/main/docs/FAQ.md#can-i-store-my-password-hashed

View File

@ -3,18 +3,16 @@ version: '3.7'
networks: networks:
[TRAEFIK_NETWORK]: [TRAEFIK_NETWORK]:
external: true external: true
services: services:
code-server: code-server:
image: lscr.io/linuxserver/code-server:latest image: lscr.io/linuxserver/code-server:latest
container_name: code-server container_name: code-server
restart: unless-stopped restart: unless-stopped
networks: networks:
- [TRAEFIK_NETWORK] - traefik
volumes: volumes:
- /pwspool/software/code-server/config:/config - /pwspool/software/code-server/config:/config
ports:
- 8443:8443
environment: environment:
- PUID=1000 - PUID=1000
- PGID=1000 - PGID=1000
@ -30,4 +28,3 @@ services:
- traefik.http.routers.code.tls=true - traefik.http.routers.code.tls=true
- traefik.http.routers.code.tls.certresolver=lets-encrypt - traefik.http.routers.code.tls.certresolver=lets-encrypt
- traefik.http.services.code.loadbalancer.server.port=8443 - traefik.http.services.code.loadbalancer.server.port=8443

View File

@ -1,7 +0,0 @@
# Visual Studio
A self-hosted version of Visual Studio Code, as an online notebook.
https://coder.com/docs/code-server/latest/install#docker
https://hub.docker.com/r/linuxserver/code-server

View File

@ -1,31 +0,0 @@
# Whitney Grafana
Resource: https://grafana.com/docs/grafana/latest/setup-grafana/installation/docker/
Note: Create a `grafana` user and add its UID to the docker-compose file so that created files will be given the correct ownership for later. Then, when running the container for the first time, use sudo so we can create the folder hierarchy for grafana.
Grafana instance that produces system metrics. Creates a Node-Exporter container that reads system metrics. A bridge network is created to allow Prometheus to query Node Exporter for metrics. Grafana connects to Prometheus via its bridge network to gather aggregated metrics.
</br>
Custom configuration held in grafana.ini in the container. Container expects to copy in a file called "olomana.ini", which is not checked into git.
</br>
After starting this image, we still need to create a datasource. Select Prometheus as the type, and `prometheus_whitney:9090` is the address. Everything else can be default. The networks need to be set up correctly to be able to refer to the container like this. If not, specifying it over the local network works fine.
</br>
We also need to create a dashboard if starting from scratch. A good default one for node-exporter is `1860`.
Setting up the data source is kind of dumb. you need to both indicate you want to communicate over http and to the container by name. So the correct thing to put when making a prometheus data source is `http://prometheus:9090` if the container name is "prometheus".
You can check the contents of Prometheus with `curl localhost:9090/metrics`.
The repo contains `grafana.ini`, the config file for grafana. By default this defines the admin account to be `admin:admin`. Change that and save the new file as `olomana.ini`, which is what will be copied into the container.
### Start with Docker-Compose (USING SUDO, see ownership issues above.)
`sudo docker-compose up -d`
I usually had to manually give ownership to the right user to the /pwspool/grafana folder hierarchy before grafana would work.
Also, if using traefik, to expose this container, you probably need to restart traefik after this container goes up.
#### Customization Notes
Images can be hosted by imgur and added in html to a Text Panel.

View File

@ -1,8 +0,0 @@
# PWS
This is the official landing webpage for Olomana.
### Run with Docker
`docker-compose down && docker system prune && docker-compose build && docker-compose up -d`

View File

@ -1,19 +0,0 @@
# NordVPN
Some containers go through a nordvpn container.
Uses the open source `https://github.com/bubuntux/nordvpn` repo to create a NordVPN container that other containers can reference when starting up.
Changes were made to the docker-compose following each container's README instructions on the various repos.
```
Volumes:
- /data/persistent/qb/appdata/config:/config
- /data/persistent/qb/downloads:/downloads
```
```
FIREWALL=Enable
```
Note: If conflicts on qb port, can change it by setting the env var for WEBUI_PORT=xxxx.

View File

@ -1,179 +0,0 @@
# Traefik
I got recommended Traefik over Nginx for its ease of use and service discovery.
For setup I followed this official Traefik tutorial: https://doc.traefik.io/traefik/getting-started/quick-start/
`https://doc.traefik.io/traefik/user-guides/docker-compose/basic-example/` is also a good reference.
GENERAL NOTE: Don't restart the container that often because it issues Let'sEncrypt challenges that can get you rate limited quickly. (1hr cooldown)
## GENERAL REMINDERS
There is some funkiness with Traefik's acme.json file. The container is supposed to create it and manage it but due to docker linking of files and folders this gets mixed up.
Reading the logs can help you understand, need to look backward from the cert resolver that i've called lets-encrypt.
To make Traefik correctly populate the acme.json do the following:
1. create acme.json yourself.
2. Make sure the mount in docker works with the definition of the certresolver in traefik.toml.
I found that we needed to have a full path (or at least a path that contained a folder, so that traefik didn't get confused and think that acme.json was a folder.
3. Give the acme.json file specific permissions (600) e.g. `chmod 600 acme.json`
4. Run docker-compose, does not require you to be root.
### Baby Steps Setup
##### Step 1
1. Run `step1-docker-compose.yml`
`docker-compose -f step1-docker-compose.yml up -d`
2. Test by curl
`curl http://localhost:8080/api/rawdata`
3. Stop all and prune
`docker stop step1-traefik`
`docker system prune`
##### Step 2
1. Run `step2-docker-compose.yml`
`docker-compose -f step2-docker-compose.yml up -d`
2. Test by curl
`curl -H Host:whoami.docker.localhost http://127.0.0.1`
3. Stop all and prune
`docker stop step2-traefik step2-whoami`
`docker system prune`
##### Step 3
1. Run `step3-docker-compose.yml`
`docker-compose -f step3-docker-compose.yml up -d`
2. Test by curl
`curl http://whoami.whitney.rip`
3. Stop all and prune
`docker stop step3-traefik step3-whoami`
`docker system prune`
### Actual Traefik Setup
##### Step 1: Basic Traefik
Starting with the basic example: `https://doc.traefik.io/traefik/user-guides/docker-compose/basic-example/`
1. Change basic things like names of containers, routing host for the example service.
2. Add a traefik-specific network for the containers to communicate over. Other containers outside of this config file can reference it so that Traefik can discover and send requests to them internally.
3. Run, and test that the whoami container gives its response back correctly.
4. Then, configure other containers to be discovered off of Traefik by adding the basic labels: `traefik.enable`, `traefik.http.routers.container_name.rule`, and `traefik.http.routers.container_name.entrypoints`.
##### Step 2: Proper Traefik Config
1. Convert the traefik related command line flags into traefik static and dynamic config. I am using toml files. These are `step5-traefik.toml` and `step5-traefik-dynamic.toml`. The static should reference a folder that the dynamic is copied into in the docker-compose.
Note: These pages are good examples of syntax and options. `https://doc.traefik.io/traefik/reference/static-configuration/file/` and `https://doc.traefik.io/traefik/reference/dynamic-configuration/file/`
Note: The Dynamic config is empty, and this is for good reason. See `https://doc.traefik.io/traefik/routing/providers/docker/`
`If a label defines a router (e.g. through a router Rule) and a label defines a service (e.g. implicitly through a loadbalancer server port value), but the router does not specify any service, then that service is automatically assigned to the router.`
Because we define the router as labels in docker-compose, we don't have to define them in the dynamic conf, which is where we'd normally have to do it.
From this point, when we add another service, all that has to be done is the following:
> 1. Add the `traefik_traefik-network` as an external network, so that the container can communicate with Traefik internally.
> 2. Add the external network to the container's definition.
> 3. Add labels defining a router for this container. This should at least include the `traefik.enable`, `traefik.http.routers.my_router_name.rule`, and `traefik.http.routers.my_router_name.entrypoints` labels.
### Some old notes from when I tried TLS stuff.
Followed this tutorial: `https://doc.traefik.io/traefik/user-guides/docker-compose/acme-dns/`.
Providers list for Traefik: `https://doc.traefik.io/traefik/https/acme/#providers`.
1. Update `docker-compose-BLANKED.yml` with the correct values and copy it to `docker-compose.yml`.
`EMAIL@ME.COM`
`CLOUDFLARE@EMAIL.COM`
`API_KEY`
`MYDOMAIN.COM`
2. Run `docker-compose.yml`
`docker-compose up -d`
3. Test by curl, then on browser.
`curl -vvv https://whitney.rip`
If certificate is not right, can probably debug from the curl response.
Try `https://whitney.rip` in browser, should see the curl response in html.
BEFORE running the container, create acme.json with permission code 600. Otherwise the container will create a folder instead.
Running the container will generate the following structure:
`letsencrypt/`
|--> `acme.json`
4. Check that the api is working.
Visit `http://YOUR_IP_HERE:8080/dashboard`
### Additional Steps
1. Convert to a static Traefik configuration (seems better)
Create `traefik.toml` and edit the `docker-compose.yml` to ensure that it's copied to one of the searched directories (I chose `/etc/traefik/`)
The conversion from env vars to `traefik.toml` is really easy - each one of them is basically describing one attribute of the yaml, so basically just build the yaml from those attributes.
2. Start to secure the Traefik Dashboard.
Generate a password with `htpasswd` (`https://doc.traefik.io/traefik/middlewares/http/basicauth/`)
Example using BCrypt: `https://unix.stackexchange.com/questions/307994/compute-bcrypt-hash-from-command-line`. See this thread about Traefik not liking the `$` character: `https://github.com/DeviaVir/zenbot/issues/2663`
# Some notes about making this a cleaner experience.
I have created blanked files for the traefik.toml and traefik-dynamic.toml since they need important info that will vary by deployment.
When putting in the user and pw to the toml, you generate the password with htpasswd, but that generates a string with escaped dollar sign values. Since we're putting it into the toml we can replace the double dollar signs with just single dollar signs.
Also. the acme.json is generated by letsencrypt, so that is not included in the repo as it's a secret.

View File

@ -1,4 +0,0 @@
# Transmission
https://github.com/haugene/docker-transmission-openvpn

23
grafana/README.md Normal file
View File

@ -0,0 +1,23 @@
# Whitney Grafana
## Instructions
### Files
Create/Fill in the following files in a `grafana` directory under this one using the templates.
- `grafana.ini`
- `prometheus.yml`
### Volumes
Make sure that in additionto mounting the 2 files above to their respective containers, grafana container has the following mount:
- `/pwspool/software/grafana:/var/lib/grafana`
Also make sure that the correct user has access to the folder on the host machine, sometimes grafana won't start up otherwise.
### Grafana Setup
To set up the data source in grafana to point to prometheus, you would refer to `http://prometheus:9090`.
## Notes
Some images are hosted on Imgur and linked to via url.
## References
https://grafana.com/docs/grafana/latest/setup-grafana/installation/docker/

View File

@ -3,9 +3,9 @@ version: "3"
networks: networks:
grafana-network: grafana-network:
driver: bridge driver: bridge
[TRAEFIK_NETWORK]: traefik:
external: true external: true
[MINECRAFT_NETWORK]: minecraft:
external: true external: true
volumes: volumes:
@ -19,12 +19,12 @@ services:
restart: unless-stopped restart: unless-stopped
networks: networks:
- grafana-network - grafana-network
- [TRAEFIK_NETWORK] - traefik
volumes: volumes:
- ./olomana.ini:/etc/grafana/grafana.ini - ./olomana.ini:/etc/grafana/grafana.ini
- /pwspool/software/grafana:/var/lib/grafana - /pwspool/software/grafana:/var/lib/grafana
labels: labels:
- traefik.http.routers.grafana.rule=Host(`[SUBDOMAIN_URL]`) - traefik.http.routers.grafana.rule=Host(`SITE_URL`)
- traefik.http.routers.grafana.tls=true - traefik.http.routers.grafana.tls=true
- traefik.http.routers.grafana.tls.certresolver=lets-encrypt - traefik.http.routers.grafana.tls.certresolver=lets-encrypt
- traefik.http.services.grafana.loadbalancer.server.port=3000 - traefik.http.services.grafana.loadbalancer.server.port=3000
@ -53,7 +53,7 @@ services:
restart: always restart: always
networks: networks:
- grafana-network - grafana-network
- [MINECRAFT_NETWORK] - minecraft
volumes: volumes:
- "./prometheus.yml:/etc/prometheus.yml" - "./prometheus.yml:/etc/prometheus.yml"
- prometheus_data:/prometheus - prometheus_data:/prometheus
@ -62,4 +62,3 @@ services:
- "--storage.tsdb.path=/prometheus" - "--storage.tsdb.path=/prometheus"
- "--web.console.libraries=/usr/share/prometheus/console_libraries" - "--web.console.libraries=/usr/share/prometheus/console_libraries"
- "--web.console.templates=/usr/share/prometheus/consoles" - "--web.console.templates=/usr/share/prometheus/consoles"

12
homepage/README.md Normal file
View File

@ -0,0 +1,12 @@
# Homepage
This is the official landing webpage for Olomana/PWS.
## Instructions
### Build
Test build with `docker compose build homepage`.
### Run with Docker
`docker-compose down && docker system prune && docker-compose build && docker-compose up -d`

View File

@ -1,19 +1,21 @@
version: '3' version: '3'
networks: networks:
[TRAEFIK_NETWORK]: traefik:
external: true external: true
services: services:
web: homepage:
image: homepage image: homepage
container_name: homepage
build: .
restart: unless-stopped restart: unless-stopped
networks: networks:
- [TRAEFIK_NETWORK] - traefik
build: . ports:
- "81:80"
labels: labels:
- traefik.http.routers.homepage.rule=Host(`[SUBDOMAIN_URL]`) - traefik.http.routers.homepage.rule=Host(`SITE_URL`)
- traefik.http.routers.homepage.tls=true - traefik.http.routers.homepage.tls=true
- traefik.http.routers.homepage.tls.certresolver=lets-encrypt - traefik.http.routers.homepage.tls.certresolver=lets-encrypt
- traefik.http.services.homepage.loadbalancer.server.port=[[NGINX_INTERNAL_PORT]] - traefik.http.services.homepage.loadbalancer.server.port=81

View File

Before

Width:  |  Height:  |  Size: 1.3 MiB

After

Width:  |  Height:  |  Size: 1.3 MiB

View File

Before

Width:  |  Height:  |  Size: 5.5 MiB

After

Width:  |  Height:  |  Size: 5.5 MiB

View File

Before

Width:  |  Height:  |  Size: 1.6 MiB

After

Width:  |  Height:  |  Size: 1.6 MiB

View File

Before

Width:  |  Height:  |  Size: 8.7 KiB

After

Width:  |  Height:  |  Size: 8.7 KiB

View File

Before

Width:  |  Height:  |  Size: 293 KiB

After

Width:  |  Height:  |  Size: 293 KiB

View File

Before

Width:  |  Height:  |  Size: 66 KiB

After

Width:  |  Height:  |  Size: 66 KiB

View File

Before

Width:  |  Height:  |  Size: 32 KiB

After

Width:  |  Height:  |  Size: 32 KiB

View File

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 20 KiB

View File

Before

Width:  |  Height:  |  Size: 5.4 MiB

After

Width:  |  Height:  |  Size: 5.4 MiB

View File

Before

Width:  |  Height:  |  Size: 1.7 MiB

After

Width:  |  Height:  |  Size: 1.7 MiB

View File

Before

Width:  |  Height:  |  Size: 262 KiB

After

Width:  |  Height:  |  Size: 262 KiB

View File

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 28 KiB

View File

@ -1,11 +1,11 @@
version: "3" version: "3"
networks: networks:
[MINECRAFT_NETWORK]: minecraft:
driver: bridge driver: bridge
[GRAFANA_NETWORK]: grafana:
external: true external: true
[TRAEFIK_NETWORK]: traefik:
external: true external: true
services: services:
@ -14,8 +14,8 @@ services:
container_name: minecraft_creative container_name: minecraft_creative
restart: unless-stopped restart: unless-stopped
networks: networks:
- [GRAFANA_NETWORK] - grafana
- [MINECRAFT_NETWORK] - minecraft
ports: ports:
- "[SERVER_PORT_1]:25565" - "[SERVER_PORT_1]:25565"
- "[PROMETHEUS_PORT_1]:9225" - "[PROMETHEUS_PORT_1]:9225"
@ -39,14 +39,14 @@ services:
- ALLOW_FLIGHT=true - ALLOW_FLIGHT=true
labels: labels:
- traefik.enable=false - traefik.enable=false
minecraft_2023: minecraft_2023:
image: itzg/minecraft-server:latest image: itzg/minecraft-server:latest
container_name: minecraft_2023 container_name: minecraft_2023
networks: networks:
- [TRAEFIK_NETWORK] - traefik
- [GRAFANA_NETWORK] - grafana
- [MINECRAFT_NETWORK] - minecraft
ports: ports:
- "[SERVER_PORT_2]:25565" - "[SERVER_PORT_2]:25565"
- "[PROMETHEUS_PORT_2]:9225" - "[PROMETHEUS_PORT_2]:9225"
@ -78,7 +78,7 @@ services:
image: itzg/minecraft-server:latest image: itzg/minecraft-server:latest
container_name: minecraft_skyblock container_name: minecraft_skyblock
networks: networks:
- minecraft_network - minecraft
ports: ports:
- "[SERVER_PORT_3]:25565" - "[SERVER_PORT_3]:25565"
volumes: volumes:
@ -101,7 +101,7 @@ services:
image: joshi425/minecraft_exporter:latest image: joshi425/minecraft_exporter:latest
container_name: minecraft_2023_metrics container_name: minecraft_2023_metrics
networks: networks:
- minecraft_network - minecraft
ports: ports:
- [SEVER_PORT_4]:2565 - [SEVER_PORT_4]:2565
volumes: volumes:
@ -115,6 +115,3 @@ services:
- DYNMAP_ENABLED="True" - DYNMAP_ENABLED="True"
labels: labels:
- traefik.enable=false - traefik.enable=false

View File

@ -1,85 +0,0 @@
# Olomana - All in One
This is the one-dockerfile version of olomana containing the stable "production" containers.
Better than going module by module but don't let that stop you.
Run everything with a simple `docker compose down && docker system prune && docker compose up -d`
Run one thing with a simple `docker compose up serviceName`
## Setup
### Traefik
#### Instructions
##### Files
Create/Fill in the following files in a `traefik/` directory under this one using the provided templates:
- `traefik.toml`
- `traefik-dynamic.toml`.
The file `traefik/acme.json` will be generated on first run. Make sure it eventually gets permission code 600. You might need to create a blank file before the first run.
##### Volumes
In addition to the above files, make sure the docker socket is mounted:
- `/var/run/docker.sock:/var/run/docker.sock:ro`
#### References
https://doc.traefik.io/traefik/getting-started/quick-start/
https://doc.traefik.io/traefik/user-guides/docker compose/basic-example/
### Code-Server
#### Instructions
##### Volumes
Mount the persistant storage somewhere.
- `/pwspool/software/code-server/config:/config`
##### Metadata
Re-roll hashed passwords.
./olomana.ini:/etc/grafana/grafana.ini
#### References
https://docs.linuxserver.io/images/docker-code-server/
https://coder.com/docs/code-server/latest/install#docker
https://hub.docker.com/r/linuxserver/code-server
https://github.com/coder/code-server/blob/main/docs/FAQ.md#can-i-store-my-password-hashed
### Grafana
#### Instructions
##### Files
Create/Fill in the following files in a `grafana` directory under this one using the templates.
- `grafana.ini`
- `prometheus.yml`
##### Volumes
Make sure that in additionto mounting the 2 files above to their respective containers, grafana container has the following mount:
- `/pwspool/software/grafana:/var/lib/grafana`
Also make sure that the correct user has access to the folder on the host machine, sometimes grafana won't start up otherwise.
##### Grafana Setup
To set up the data source in grafana to point to prometheus, you would refer to `http://prometheus:9090`.
#### References
https://grafana.com/docs/grafana/latest/setup-grafana/installation/docker/
### Homepage
#### Instructions
##### Build
Test build with `docker compose build homepage`.
### NordVPN
#### Instructions
##### Files
Create `nordvpn/token.txt` with NordVPN token which is generated from NordVPN website.
##### Notes
Can use either nordlynx or nordvpn in nordlynx configuration.
#### References
https://github.com/bubuntux/nordvpn
https://github.com/bubuntux/nordlynx

View File

@ -1,160 +0,0 @@
networks:
traefik:
driver: bridge
name: traefik
grafana:
driver: bridge
name: grafana
volumes:
prometheus_data: {}
services:
traefik:
image: traefik:latest
container_name: traefik
restart: unless-stopped
networks:
- traefik
ports:
- "80:80"
- "8080:8080"
- "443:443"
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
- ./traefik/traefik.toml:/etc/traefik/traefik.toml
- ./traefik/traefik-dynamic.toml:/etc/traefik/dynamic/traefik-dynamic.toml
- ./traefik/acme.json:/etc/acme.json
code-server:
image: lscr.io/linuxserver/code-server:latest
container_name: code-server
restart: unless-stopped
depends_on:
- traefik
networks:
- traefik
volumes:
- /pwspool/software/code-server/config:/config
environment:
- PUID=1000
- PGID=1000
- TZ=Etc/UTC
- HASHED_PASSWORD=$$argon2i$$v=19$$m=4096,t=3,p=1$$o70PqzdDrUvzijIN+Nd+uw$$8wsBelBomYLsaKFelBAf+v8KqpS7TMsfFvmouarbehg
- SUDO_PASSWORD_HASH=$$argon2i$$v=19$$m=4096,t=3,p=1$$o70PqzdDrUvzijIN+Nd+uw$$8wsBelBomYLsaKFelBAf+v8KqpS7TMsfFvmouarbehg
- PROXY_DOMAIN=code.whitney.rip
- DEFAULT_WORKSPACE=/config/workspace
labels:
- traefik.http.routers.code.rule=Host(`code.whitney.rip`)
- traefik.http.routers.code.tls=true
- traefik.http.routers.code.tls.certresolver=lets-encrypt
- traefik.http.services.code.loadbalancer.server.port=8443
grafana:
image: grafana/grafana
container_name: grafana
restart: unless-stopped
depends_on:
- traefik
- node_exporter
- prometheus
networks:
- grafana
- traefik
user: "1003"
volumes:
- ./grafana/grafana.ini:/etc/grafana/grafana.ini
- /pwspool/software/grafana:/var/lib/grafana
labels:
- traefik.http.routers.grafana.rule=Host(`grafana.whitney.rip`)
- traefik.http.routers.grafana.tls=true
- traefik.http.routers.grafana.tls.certresolver=lets-encrypt
- traefik.http.services.grafana.loadbalancer.server.port=3000
node_exporter:
image: quay.io/prometheus/node-exporter:latest
container_name: node_exporter
restart: unless-stopped
depends_on:
- prometheus
networks:
- grafana
pid: host
user: "1003:1005"
command:
- "--path.rootfs=/host"
volumes:
- "/:/host:ro,rslave"
labels:
- traefik.enable=false
prometheus:
image: prom/prometheus:latest
container_name: prometheus
restart: unless-stopped
networks:
- grafana
volumes:
- "./grafana/prometheus.yml:/etc/prometheus.yml"
- prometheus_data:/prometheus
command:
- "--config.file=/etc/prometheus.yml"
- "--storage.tsdb.path=/prometheus"
- "--web.console.libraries=/usr/share/prometheus/console_libraries"
- "--web.console.templates=/usr/share/prometheus/consoles"
labels:
- traefik.enable=false
homepage:
image: homepage
container_name: homepage
build: homepage/
restart: unless-stopped
depends_on:
- traefik
networks:
- traefik
ports:
- "81:80"
labels:
- traefik.http.routers.homepage.rule=Host(`www.whitney.rip`)
- traefik.http.routers.homepage.tls=true
- traefik.http.routers.homepage.tls.certresolver=lets-encrypt
- traefik.http.services.homepage.loadbalancer.server.port=81
nordlynx:
image: ghcr.io/bubuntux/nordlynx
container_name: nordlynx
restart: unless-stopped
network_mode: bridge
cap_add:
- NET_ADMIN
- NET_RAW
environment:
- PRIVATE_KEY=mDnVa4EMbnlSF8Sg/i657hf+NRyWQxQjKTwkImD/HWE=
- NET_LOCAL=192.168.1.0/24
- TZ=America/Los_Angeles
- QUERY=filters\[country_id\]=202
ports:
- "8888:8888"
- 6881:6881
- 6881:6881/udp
labels:
- traefik.enable=false
qb:
image: ghcr.io/linuxserver/qbittorrent:latest
container_name: qb
restart: unless-stopped
depends_on:
- nordlynx
network_mode: service:nordlynx
environment:
- PUID=1001
- PGID=1001
- WEBUI_PORT=8888
volumes:
- /data/write/qb/appdata/config:/config
- /data/write/qb/downloads:/downloads
labels:
- traefik.enable=false

View File

@ -1,2 +0,0 @@
grafana.ini
prometheus.yml

View File

@ -1,62 +0,0 @@
[paths]
[server]
[database]
[datasources]
[remote_cache]
[dataproxy]
[analytics]
[security]
admin_user = admin
admin_password = adminpassword
[snapshots]
[dashboards]
[users]
default_theme = dark
[auth]
signout_redirect_url = www.example.com
[auth.anonymous]
enabled = true
[auth.github]
[auth.gitlab]
[auth.google]
[auth.grafana_com]
[auth.azuread]
[auth.okta]
[auth.generic_oauth]
[auth.basic]
[auth.proxy]
[auth.jwt]
[auth.ldap]
[aws]
[smtp]
[emails]
[log]
[log.console]
[log.file]
[log.syslog]
[log.frontend]
[quota]
[alerting]
[annotations]
[annotations.dashboard]
[annotations.api]
[explore]
[metrics]
[metrics.environment_info]
[metrics.graphite]
[grafana_com]
[tracing.jaeger]
[external_image_storage]
[external_image_storage.s3]
[external_image_storage.webdav]
[external_image_storage.gcs]
[external_image_storage.azure_blob]
[external_image_storage.local]
[rendering]
[panels]
[plugins]
[plugin.grafana-image-renderer]
[enterprise]
[feature_toggles]
[date_formats]
[expressions]

View File

@ -1,14 +0,0 @@
global:
scrape_interval: 15s
external_labels:
monitor: 'codelab-monitor'
scrape_configs:
- job_name: 'prometheus'
scrape_interval: 5s
static_configs:
- targets: ['localhost:9090']
- job_name: 'node_exporter'
scrape_interval: 5s
static_configs:
- targets: ['node_exporter:9100']

View File

@ -1,9 +0,0 @@
FROM nginx:alpine
COPY nginx/nginx.conf /etc/nginx/nginx.conf
COPY website /usr/share/nginx/html
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]

View File

@ -1,15 +0,0 @@
events {
worker_connections 1024;
}
http {
server {
listen 81;
server_name localhost;
location / {
root /usr/share/nginx/html;
index index.html;
}
}
}

View File

@ -1,47 +0,0 @@
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>About | PWS</title>
<link href="https://fonts.googleapis.com/css2?family=Montserrat:wght@600&family=Roboto&display=swap" rel="stylesheet">
<link rel="stylesheet" href="styles.css">
</head>
<body>
<header>
<div class="container">
<div class="logo">
<img src="static/favicon.ico" alt="Logo">
PWS
</div>
<nav>
<a href="index.html" class="nav-button">Home</a>
</nav>
</div>
</header>
<section id="home" class="hero">
<div class="container-with-footer-padding">
<h1>About</h1>
<p>PWS is a homelabbing project that has taught me much about Linux, hardware, networking, and security practices. I use it to host a variety of self-created projects, open source software, and anything that piques my interest.</p>
<p>The v1 implementation of PWS was named Whitney, after the mountain in California. The original hardware was sourced from my old desktop PC, which was made completely of hand-me-down parts, and was housed in an old server case that was e-wasted by my college. This initial build was definitely on the "janky" side, featuring an unmounted power supply in the optical bay, secured only by some green yarn. (Fire hazard, anyone?)</p>
<img class="blog-image" src="static/about/whitney.jpg" alt="Whitney">
<p>I ran a lot of services from this box - my personal website/online resume, side projects, a Covid-19 data tracker, game servers, and a lot of other projects that taught me lessons in DNS config, networking, maintaining persistent storage and others.</p>
<p>But eventually I started running up against the limits of the box. The machine's CPU was released in 2008, which was indicative of the age of most of its hardware. After spending a lot of work on the original Whitney config in the first repo, I decided that I had learned enough to warrant an upgrade.</p>
<p>The v2 upgrade to PWS was given the nickname of "Olomana", a second step in this pattern of mountainous server names. <a href="https://en.wikipedia.org/wiki/Olomana_(mountain)">Mount Olomana</a> is a mountain on the windward side of Oahu. It has 3 peaks which are are a popular, albeit difficult and dangerous hike. While visiting family in Kailua, I hiked the Ko'olau range and snapped this picture of the rarely seen backside of Mount Olomana.</p>
<img class="blog-image" src="static/about/mount-olomana.jpg" alt="Mount Olomana">
<p>Olomana, the web server, is a significant upgrade over its predecessor. I've built it as a 4U rack-mounted machine with modern components. It's now mounted in a 16U rack, with a UPS and mesh network node close by. Critical resources like RAM and CPU cores are now a little more abundant, and I've set up the ZFS filesystem to provide redundancy against the power outages that occasionally would corrupt drive on the original PWS.</p>
<img class="blog-image" src="static/about/olomana.jpg" alt="Olomana">
<p>I'm super happy with the rebuild, and would encourage anyone with similar interests to jump in and give homelabbing a try for themselves.</p>
</div>
</section>
<script src="script.js"></script>
</body>
<footer>
<div class="container">
<p>&copy; 2024 PWS. All rights reserved.</p>
<div class="social-media">
<a href="https://github.com/whitney-server">Github</a>
</div>
</div>
</footer>
</html>

View File

@ -1,50 +0,0 @@
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>PWS | Pinner Web Services</title>
<link href="https://fonts.googleapis.com/css2?family=Montserrat:wght@600&family=Roboto&display=swap" rel="stylesheet">
<link rel="stylesheet" href="styles.css">
<link rel="icon" href="/static/favicon.ico" type="image/x-icon">
</head>
<body>
<header>
<div class="container">
<div class="logo">
<img src="static/favicon.ico" alt="Logo">
PWS
</div>
<nav>
<a href="about.html" class="nav-button">About</a>
<a href="services.html" class="nav-button">Services</a>
</nav>
</div>
</header>
<section id="home" class="hero">
<div class="container">
<h1>PWS | Pinner Web Services</h1>
<p>Experience the ultimate web service tailored just for you.</p>
<a href="about.html" class="cta-button">Learn More</a>
</div>
</section>
<section id="quote-of-the-day" class="sub-section">
<div class="container">
<h2>Quote of the Day</h2>
<div class="quote" id="quote-content">
<!-- Quote content delived dynamically by script.js -->
</div>
</div>
</section>
<script src="script.js"></script>
</body>
<footer>
<div class="container">
<p>&copy; 2024 PWS. All rights reserved.</p>
<div class="social-media">
<a href="https://github.com/whitney-server">Github</a>
</div>
</div>
</footer>
</html>

View File

@ -1,81 +0,0 @@
const quotes = [
{
text: "The only way to do great work is to love what you do.",
author: "Steve Jobs",
link: "https://en.wikipedia.org/wiki/Steve_Jobs"
},
{
text: "Success is not final, failure is not fatal: It is the courage to continue that counts.",
author: "Winston Churchill",
link: "https://en.wikipedia.org/wiki/Winston_Churchill"
},
{
text: "In the end, it's not the years in your life that count. It's the life in your years.",
author: "Abraham Lincoln",
link: "https://en.wikipedia.org/wiki/Abraham_Lincoln"
},
{
text: "Don't believe everything you read on the internet.",
author: "Abraham Lincoln",
link: "https://www.goodreads.com/quotes/4472530-the-problem-with-internet-quotes-is-that-you-cannot-always"
},
{
text: "I should be on in a few minutes.",
author: "Jake, in the moments before baiting",
link: "https://www.youtube.com/channel/UCZctf3QSXXk9a5KzrAJ2bmw"
},
{
text: "It's good news!",
author: "Sujay",
link: "https://anditsgood.news/"
},
{
text: "It's bad news.",
author: "Sujay",
link: "https://anditsgood.news/"
},
{
text: "Meow",
author: "Tobee",
link: "https://gallery.whitney.rip/library/albums/as3wif72i18kzs9q/tobee-and-maymay"
},
{
text: "Meow",
author: "MayMay",
link: "https://gallery.whitney.rip/library/albums/as3wif72i18kzs9q/tobee-and-maymay"
},
{
text: "It's your internet. Take it back.",
author: "DWS",
link: "https://dws.rip"
},
];
const failureMessageQOTD = 'Failed to fetch the quote of the day. Please try again later.';
async function getQOTD() {
var daysSinceEpoch = new Date().getTime() / (1000 * 60 * 60 * 24);
const idx = Math.floor(daysSinceEpoch % quotes.length);
return quotes[idx];
}
async function renderQOTD() {
const quoteData = await getQOTD();
const quoteElement = document.getElementById('quote-content');
console.log(`Rendering Today's Quote: "${quoteData.text}" - ${quoteData.author}`);
try {
if(quoteData) {
const html = `<p>"${quoteData.text}" - <a href="${quoteData.link}">${quoteData.author}</a></p>`;
quoteElement.innerHTML = html;
} else {
quoteSection.innerHTML = `<p>${failureMessageQOTD}</p>`;
}
} catch (error) {
console.error('Error rendering Quote of the Day:', error);
quoteSection.innerHTML = `<p>${failureMessageQOTD}</p>`;
}
}
console.log("Thanks for checking out PWS!");
document.addEventListener('DOMContentLoaded', renderQOTD);

View File

@ -1,105 +0,0 @@
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Services | PWS</title>
<link href="https://fonts.googleapis.com/css2?family=Montserrat:wght@600&family=Roboto&display=swap" rel="stylesheet">
<link rel="stylesheet" href="styles.css">
</head>
<body>
<header>
<div class="container">
<div class="logo">
<img src="static/favicon.ico" alt="Logo">
PWS
</div>
<nav>
<a href="index.html" class="nav-button">Home</a>
</nav>
</div>
</header>
<section id="home" class="hero">
<div class="container">
<h1>PWS Services</h1>
<p>Check out some of the PWS services!</p>
</div>
</section>
<section id="services" class="sub-section">
<div class="container">
<div class="service-entry">
<img class="blog-image" src="static/services/gitea.png" alt="Gitea">
<div class="service-text">
<h2>Gitea <a class="service-private-status">Private</a></h2>
<p>I usually host code on Github, but for personal projects or my own IP, I self host. Some repos are mirrored to Github.</p>
</div>
</div>
<div class="service-entry">
<img class="blog-image" src="static/services/code-server.png" alt="Code Server">
<div class="service-text">
<h2>Code Server <a class="service-private-status">Private</a></h2>
<p>Personal notebook hosting stuff like my grocery list and anything I need to keep track of in the moment. UI is not too bad on both mobile and on desktop.</p>
</div>
</div>
<div class="service-entry">
<img class="blog-image" src="static/services/covid-tracker.png" alt="Covid Tracker">
<div class="service-text">
<h2>Covid Tracker <a class="service-public-status">Public</a></h2>
<p>This was a coding project that my dad and I worked on during covid. He wanted to do some data analysis to fact check what was reported on about the covid rates. I wanted some practice with hosting web applications on PWS v1, and hosted the <a href="https://covid.whitney.rip">covid tracker website</a> for him on PWS.</p>
<p>The website renders various charts, tables, and dashboards by interest. It additionally has a few "Checker" utilities to compare cases across cities and availability within hospitals.</p>
<p>Unfortunately, as of 2024, the US government website we scraped data for has stopped sharing information, and unfortunately this marks the end for this project. However, we still run it on PWS with the most recent batch of data.</p>
<p>Some stretch goals for this project include caching with <a href="https://nginx.org/en/">Nginx</a>, or converting it to a different framework like <a href="https://observablehq.com/">Observable</a>.</p>
</div>
</div>
<div class="service-entry">
<img class="blog-image" src="static/services/photoprism.png" alt="Photoprism">
<div class="service-text">
<h2>Photoprism <a class="service-public-status">Public</a></h2>
<p>I host a personal <a href="https://gallery.whitney.rip">photo gallery</a> of memories that I'd like to remember. <a href="https://www.photoprism.app/">Photoprism</a> makes a great open source(!) solution for doing this that is better than most other comparable projects. Its features list is rather long, and can give you a pretty decent Google Photos impression.</p>
</div>
</div>
<div class="service-entry">
<img class="blog-image" src="static/services/recipes.png" alt="Websites">
<div class="service-text">
<h2>Websites <a class="service-public-status">Public</a></h2>
<p>There are a few one-of websites that I host off of PWS. One is my <a href="https://recipes.whitney.rip">Recipes Website</a>, a ad-less, bloat-less recipes website that actually tells you how to cook something instead of trying to sell you on a cookbook or something. This was written around the time that ChatGPT was becoming a household name, and this was a great project to start to learn some of the capabilities on.</p>
<p>Some other sites include my personal website (where I blog on some of these projects), and, of course, this PWS website!</p>
</div>
</div>
<div class="service-entry">
<img class="blog-image" src="static/services/plex.png" alt="Plex">
<div class="service-text">
<h2>Plex <a class="service-private-status">Private</a></h2>
<p>I have a few YouTube channels whose original video files I like to keep an archive of. It's perfectly fine to just host the files and play them in a media player, but self-hosting <a href="https://www.plex.tv/">Plex</a> allows me to slap on a UI for essentially free.</p>
</div>
</div>
<div class="service-entry">
<img class="blog-image" src="static/services/discord.png" alt="Discord Bots">
<div class="service-text">
<h2>Discord Bots <a class="service-public-status">Public</a></h2>
<p>At one point, when creating discord servers for special interest groups I was a part of, I found that moderation and automation were a problem with the default discord server owner experience.</p>
<p>I tried using a paid discord bot to solve some of these problems, and eventually liked it enough to purchase a lifetime subscription. However, that bot's owners started to lock features away under additional tiers and I became unhappy.</p>
<p>Just unhappy enought to sit down and learn how to use the Discord Api. With the help of the Discord API for Python, and ChatGPT by my side, I have started a <a href="https://github.com/runyanjake/discord">repository</a> containing some of the bots and features I have written for them. It's a nice, fun way to add functionality to a service that some of my friends choose to congregate on.</p>
</div>
</div>
<div class="service-entry">
<img class="blog-image" src="static/services/minecraft.png" alt="Minecraft">
<div class="service-text">
<h2>Minecraft <a class="service-private-status">Private</a></h2>
<p>Every few winters, people get stuck inside due to rain or snow and want to play some games. In the past, PWS has hosted a minecraft server to scratch that itch. Third party plugins like Dynmap allowed us to elevate the experience by hosting a live-updating <a href="https://minemap.whitney.rip">server map</a>, which is where the screenshot comes from.</p>
</div>
</div>
</div>
</section>
<script src="script.js"></script>
</body>
<footer>
<div class="container">
<p>&copy; 2024 PWS. All rights reserved.</p>
<div class="social-media">
<a href="https://github.com/whitney-server">Github</a>
</div>
</div>
</footer>
</html>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.5 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 293 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.4 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 262 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

View File

@ -1,178 +0,0 @@
body {
height: 100%;
font-family: 'Roboto', sans-serif;
margin: 0;
padding-bottom: 5%;
color: #333333;
line-height: 1.6;
}
.container {
width: 80%;
margin: 0 auto;
}
.container-with-footer-padding {
width: 80%;
margin: 0 auto;
padding-bottom: 60px;
}
header {
background-color: #333333;
color: #FFFFFF;
padding: 20px 0;
}
header .container {
display: flex;
justify-content: space-between;
align-items: center;
}
header .logo {
font-family: 'Montserrat', sans-serif;
font-size: 1.5em;
font-weight: 600;
display: flex;
align-items: center;
}
header nav ul {
list-style: none;
display: flex;
gap: 20px;
}
header nav ul li {
display: inline;
}
header nav ul li a {
color: #FFFFFF;
text-decoration: none;
font-weight: 500;
}
.blog-image {
width: 50%;
height: auto;
border: 2px solid black;
border-radius: 5px;
}
.nav-button {
display: inline-block;
padding: 10px 20px;
background-color: #F5844C;
color: #240f0f;
text-decoration: none;
font-weight: 600;
border-radius: 5px;
transition: background-color 0.3s ease;
}
.service-entry {
padding-bottom: 60px;
}
.service-public-status {
display: inline;
padding: 10px 20px;
background-color: #1daa0a;
color: #240f0f;
text-decoration: none;
font-weight: 600;
border-radius: 5px;
transition: background-color 0.3s ease;
}
.service-private-status {
display: inline;
padding: 10px 20px;
background-color: #c72424;
color: #240f0f;
text-decoration: none;
font-weight: 600;
border-radius: 5px;
transition: background-color 0.3s ease;
}
.nav-button:hover {
background-color: #cf5416;
}
.hero {
background: #f4f4f4;
text-align: center;
padding: 60px 0;
}
.hero h1 {
font-family: 'Montserrat', sans-serif;
font-size: 2.5em;
margin: 0;
}
.hero p {
font-size: 1.2em;
margin: 20px 0;
}
.sub-section {
background: #f4f4f4;
text-align: center;
padding: 60px 0;
padding-bottom: 60px;
}
.sub-section h1 {
font-family: 'Montserrat', sans-serif;
font-size: 2.5em;
margin: 0;
}
.sub-section p {
font-size: 1.2em;
margin: 20px 0;
}
.cta-button {
display: inline-block;
padding: 10px 20px;
background-color: #F5844C;
color: #333333;
text-decoration: none;
font-weight: 600;
border-radius: 5px;
transition: background-color 0.3s ease;
}
.cta-button:hover {
background-color: #e0a806;
}
footer {
position: fixed;
bottom: 0;
width: 100%;
height: 5%;
background: #333333;
color: #ffffff;
text-align: center;
padding: 20px 0;
}
footer .social-media a {
color: #ffffff;
text-decoration: none;
margin: 0 10px;
}
footer .social-media a:hover {
text-decoration: underline;
}
footer p {
margin: 0;
}

View File

@ -1,3 +0,0 @@
acme.json
traefik.toml
traefik-dynamic.toml

View File

@ -1,18 +0,0 @@
# Whitney Traefik Dynamic Config
[http.middlewares]
[http.middlewares.redirect-to-www.redirectRegex]
regex = "^https?://example.com(.*)"
replacement = "http://www.example.com$${1}"
permanent = true
[http.middlewares.simpleAuth.basicAuth]
users = ["olomana:HASHED_PASSWORD_HERE"]
[http.routers.api]
rule = "Host(`monitor.example.rip`)"
entrypoints = "websecure"
middlewares = ["simpleAuth"]
service = "api@internal"
[http.routers.api.tls]
certResolver = "lets-encrypt"

View File

@ -1,41 +0,0 @@
# Whitney Traefik Static Config
[global]
checkNewVersion = true
sendAnonymousUsage = false
[entryPoints]
[entryPoints.web]
address = ":80"
[entryPoints.web.http.redirections.entryPoint]
to = "websecure"
scheme = "https"
[entryPoints.websecure]
address = ":443"
[api]
dashboard = true
[file]
watch = true
[certificatesResolvers.lets-encrypt.acme]
email = "your-email@example.com"
storage = "/etc/acme.json"
# Use LetsEncrypt Staging Server
# caServer: "https://acme-staging-v02.api.letsencrypt.org/directory"
# Use LetsEncrypt Prod Server
caServer = "https://acme-v02.api.letsencrypt.org/directory"
[certificatesResolvers.lets-encrypt.acme.tlsChallenge]
[providers]
[providers.docker]
watch = true
network = "traefik"
[providers.file]
directory = "/etc/traefik/dynamic/"
filename = "traefik-dynamic.toml"
watch = true
[log]
level = "info"

13
nordvpn/README.md Normal file
View File

@ -0,0 +1,13 @@
# NordVPN
## Instructions
### Files
Create `nordvpn/token.txt` with NordVPN token which is generated from NordVPN website.
### Notes
Can use either nordlynx or nordvpn in nordlynx configuration.
## References
https://github.com/bubuntux/nordvpn
https://github.com/bubuntux/nordlynx

25
traefik/README.md Normal file
View File

@ -0,0 +1,25 @@
# Traefik
Traefik is my load balancer.
`https://doc.traefik.io/traefik/getting-started/quick-start/`
## Setup
### Files
Create/Fill in the following files in a `traefik/` directory under this one using the provided templates:
- `traefik.toml`
- `traefik-dynamic.toml`.
The file `traefik/acme.json` will be generated on first run. Make sure it eventually gets permission code 600. You might need to create a blank file before the first run.
### Volumes
In addition to the above files, make sure the docker socket is mounted:
- `/var/run/docker.sock:/var/run/docker.sock:ro`
## Reminders
The file `acme.json` can be weird when it comes to permissions. It will be generated on first run.
Ensure it is permission code 600.
## References
https://doc.traefik.io/traefik/getting-started/quick-start/
https://doc.traefik.io/traefik/user-guides/docker compose/basic-example/

View File

@ -2,14 +2,16 @@ version: "3"
networks: networks:
traefik-network: traefik-network:
driver: bridge
name: traefik
services: services:
traefik: traefik:
image: traefik:v2.9 image: traefik:latest
container_name: traefik container_name: traefik
restart: unless-stopped restart: unless-stopped
networks: networks:
- traefik-network - traefik
ports: ports:
- "80:80" - "80:80"
- "8080:8080" - "8080:8080"
@ -19,4 +21,3 @@ services:
- ./traefik.toml:/etc/traefik/traefik.toml - ./traefik.toml:/etc/traefik/traefik.toml
- ./traefik-dynamic.toml:/etc/traefik/dynamic/traefik-dynamic.toml - ./traefik-dynamic.toml:/etc/traefik/dynamic/traefik-dynamic.toml
- ./acme.json:/etc/acme.json - ./acme.json:/etc/acme.json

Some files were not shown because too many files have changed in this diff Show More