Don't need monolith
19
code-server/README.md
Normal 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
|
@ -3,18 +3,16 @@ version: '3.7'
|
||||
networks:
|
||||
[TRAEFIK_NETWORK]:
|
||||
external: true
|
||||
|
||||
|
||||
services:
|
||||
code-server:
|
||||
image: lscr.io/linuxserver/code-server:latest
|
||||
container_name: code-server
|
||||
restart: unless-stopped
|
||||
networks:
|
||||
- [TRAEFIK_NETWORK]
|
||||
- traefik
|
||||
volumes:
|
||||
- /pwspool/software/code-server/config:/config
|
||||
ports:
|
||||
- 8443:8443
|
||||
environment:
|
||||
- PUID=1000
|
||||
- PGID=1000
|
||||
@ -30,4 +28,3 @@ services:
|
||||
- traefik.http.routers.code.tls=true
|
||||
- traefik.http.routers.code.tls.certresolver=lets-encrypt
|
||||
- traefik.http.services.code.loadbalancer.server.port=8443
|
||||
|
@ -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
|
||||
|
@ -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.
|
||||
|
@ -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`
|
||||
|
@ -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.
|
@ -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.
|
||||
|
@ -1,4 +0,0 @@
|
||||
# Transmission
|
||||
|
||||
https://github.com/haugene/docker-transmission-openvpn
|
||||
|
23
grafana/README.md
Normal 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/
|
@ -3,9 +3,9 @@ version: "3"
|
||||
networks:
|
||||
grafana-network:
|
||||
driver: bridge
|
||||
[TRAEFIK_NETWORK]:
|
||||
traefik:
|
||||
external: true
|
||||
[MINECRAFT_NETWORK]:
|
||||
minecraft:
|
||||
external: true
|
||||
|
||||
volumes:
|
||||
@ -19,12 +19,12 @@ services:
|
||||
restart: unless-stopped
|
||||
networks:
|
||||
- grafana-network
|
||||
- [TRAEFIK_NETWORK]
|
||||
- traefik
|
||||
volumes:
|
||||
- ./olomana.ini:/etc/grafana/grafana.ini
|
||||
- /pwspool/software/grafana:/var/lib/grafana
|
||||
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.certresolver=lets-encrypt
|
||||
- traefik.http.services.grafana.loadbalancer.server.port=3000
|
||||
@ -53,7 +53,7 @@ services:
|
||||
restart: always
|
||||
networks:
|
||||
- grafana-network
|
||||
- [MINECRAFT_NETWORK]
|
||||
- minecraft
|
||||
volumes:
|
||||
- "./prometheus.yml:/etc/prometheus.yml"
|
||||
- prometheus_data:/prometheus
|
||||
@ -62,4 +62,3 @@ services:
|
||||
- "--storage.tsdb.path=/prometheus"
|
||||
- "--web.console.libraries=/usr/share/prometheus/console_libraries"
|
||||
- "--web.console.templates=/usr/share/prometheus/consoles"
|
||||
|
12
homepage/README.md
Normal 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`
|
@ -1,19 +1,21 @@
|
||||
version: '3'
|
||||
|
||||
networks:
|
||||
[TRAEFIK_NETWORK]:
|
||||
traefik:
|
||||
external: true
|
||||
|
||||
services:
|
||||
web:
|
||||
homepage:
|
||||
image: homepage
|
||||
container_name: homepage
|
||||
build: .
|
||||
restart: unless-stopped
|
||||
networks:
|
||||
- [TRAEFIK_NETWORK]
|
||||
build: .
|
||||
- traefik
|
||||
ports:
|
||||
- "81:80"
|
||||
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.certresolver=lets-encrypt
|
||||
- traefik.http.services.homepage.loadbalancer.server.port=[[NGINX_INTERNAL_PORT]]
|
||||
|
||||
- traefik.http.services.homepage.loadbalancer.server.port=81
|
Before Width: | Height: | Size: 1.3 MiB After Width: | Height: | Size: 1.3 MiB |
Before Width: | Height: | Size: 5.5 MiB After Width: | Height: | Size: 5.5 MiB |
Before Width: | Height: | Size: 1.6 MiB After Width: | Height: | Size: 1.6 MiB |
Before Width: | Height: | Size: 8.7 KiB After Width: | Height: | Size: 8.7 KiB |
Before Width: | Height: | Size: 293 KiB After Width: | Height: | Size: 293 KiB |
Before Width: | Height: | Size: 66 KiB After Width: | Height: | Size: 66 KiB |
Before Width: | Height: | Size: 32 KiB After Width: | Height: | Size: 32 KiB |
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 20 KiB |
Before Width: | Height: | Size: 5.4 MiB After Width: | Height: | Size: 5.4 MiB |
Before Width: | Height: | Size: 1.7 MiB After Width: | Height: | Size: 1.7 MiB |
Before Width: | Height: | Size: 262 KiB After Width: | Height: | Size: 262 KiB |
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 28 KiB |
@ -1,11 +1,11 @@
|
||||
version: "3"
|
||||
|
||||
networks:
|
||||
[MINECRAFT_NETWORK]:
|
||||
minecraft:
|
||||
driver: bridge
|
||||
[GRAFANA_NETWORK]:
|
||||
grafana:
|
||||
external: true
|
||||
[TRAEFIK_NETWORK]:
|
||||
traefik:
|
||||
external: true
|
||||
|
||||
services:
|
||||
@ -14,8 +14,8 @@ services:
|
||||
container_name: minecraft_creative
|
||||
restart: unless-stopped
|
||||
networks:
|
||||
- [GRAFANA_NETWORK]
|
||||
- [MINECRAFT_NETWORK]
|
||||
- grafana
|
||||
- minecraft
|
||||
ports:
|
||||
- "[SERVER_PORT_1]:25565"
|
||||
- "[PROMETHEUS_PORT_1]:9225"
|
||||
@ -39,14 +39,14 @@ services:
|
||||
- ALLOW_FLIGHT=true
|
||||
labels:
|
||||
- traefik.enable=false
|
||||
|
||||
|
||||
minecraft_2023:
|
||||
image: itzg/minecraft-server:latest
|
||||
container_name: minecraft_2023
|
||||
networks:
|
||||
- [TRAEFIK_NETWORK]
|
||||
- [GRAFANA_NETWORK]
|
||||
- [MINECRAFT_NETWORK]
|
||||
- traefik
|
||||
- grafana
|
||||
- minecraft
|
||||
ports:
|
||||
- "[SERVER_PORT_2]:25565"
|
||||
- "[PROMETHEUS_PORT_2]:9225"
|
||||
@ -78,7 +78,7 @@ services:
|
||||
image: itzg/minecraft-server:latest
|
||||
container_name: minecraft_skyblock
|
||||
networks:
|
||||
- minecraft_network
|
||||
- minecraft
|
||||
ports:
|
||||
- "[SERVER_PORT_3]:25565"
|
||||
volumes:
|
||||
@ -101,7 +101,7 @@ services:
|
||||
image: joshi425/minecraft_exporter:latest
|
||||
container_name: minecraft_2023_metrics
|
||||
networks:
|
||||
- minecraft_network
|
||||
- minecraft
|
||||
ports:
|
||||
- [SEVER_PORT_4]:2565
|
||||
volumes:
|
||||
@ -115,6 +115,3 @@ services:
|
||||
- DYNMAP_ENABLED="True"
|
||||
labels:
|
||||
- traefik.enable=false
|
||||
|
||||
|
||||
|
@ -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
|
@ -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
|
2
monolith/grafana/.gitignore
vendored
@ -1,2 +0,0 @@
|
||||
grafana.ini
|
||||
prometheus.yml
|
@ -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]
|
@ -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']
|
||||
|
@ -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;"]
|
||||
|
@ -1,15 +0,0 @@
|
||||
events {
|
||||
worker_connections 1024;
|
||||
}
|
||||
|
||||
http {
|
||||
server {
|
||||
listen 81;
|
||||
server_name localhost;
|
||||
|
||||
location / {
|
||||
root /usr/share/nginx/html;
|
||||
index index.html;
|
||||
}
|
||||
}
|
||||
}
|
@ -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>© 2024 PWS. All rights reserved.</p>
|
||||
<div class="social-media">
|
||||
<a href="https://github.com/whitney-server">Github</a>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
</html>
|
@ -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>© 2024 PWS. All rights reserved.</p>
|
||||
<div class="social-media">
|
||||
<a href="https://github.com/whitney-server">Github</a>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
</html>
|
@ -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);
|
@ -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>© 2024 PWS. All rights reserved.</p>
|
||||
<div class="social-media">
|
||||
<a href="https://github.com/whitney-server">Github</a>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
</html>
|
Before Width: | Height: | Size: 1.3 MiB |
Before Width: | Height: | Size: 5.5 MiB |
Before Width: | Height: | Size: 1.6 MiB |
Before Width: | Height: | Size: 8.7 KiB |
Before Width: | Height: | Size: 293 KiB |
Before Width: | Height: | Size: 66 KiB |
Before Width: | Height: | Size: 32 KiB |
Before Width: | Height: | Size: 20 KiB |
Before Width: | Height: | Size: 5.4 MiB |
Before Width: | Height: | Size: 1.7 MiB |
Before Width: | Height: | Size: 262 KiB |
Before Width: | Height: | Size: 28 KiB |
@ -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;
|
||||
}
|
3
monolith/traefik/.gitignore
vendored
@ -1,3 +0,0 @@
|
||||
acme.json
|
||||
traefik.toml
|
||||
traefik-dynamic.toml
|
@ -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"
|
||||
|
@ -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
@ -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
@ -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/
|
@ -2,14 +2,16 @@ version: "3"
|
||||
|
||||
networks:
|
||||
traefik-network:
|
||||
driver: bridge
|
||||
name: traefik
|
||||
|
||||
services:
|
||||
traefik:
|
||||
image: traefik:v2.9
|
||||
image: traefik:latest
|
||||
container_name: traefik
|
||||
restart: unless-stopped
|
||||
networks:
|
||||
- traefik-network
|
||||
- traefik
|
||||
ports:
|
||||
- "80:80"
|
||||
- "8080:8080"
|
||||
@ -19,4 +21,3 @@ services:
|
||||
- ./traefik.toml:/etc/traefik/traefik.toml
|
||||
- ./traefik-dynamic.toml:/etc/traefik/dynamic/traefik-dynamic.toml
|
||||
- ./acme.json:/etc/acme.json
|
||||
|