Add homepage

This commit is contained in:
Jake Runyan 2024-08-07 18:10:38 -07:00
parent 1323f4fe8f
commit 99f394f414
21 changed files with 511 additions and 10 deletions

View File

@ -1,7 +1,7 @@
# Olomana - All in One # Olomana - All in One
This is the one-dockerfile version of olomana containing the stable "production" containers. 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. 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 everything with a simple `docker-compose down && docker system prune && docker-compose up -d`
Run one thing with a simple `docker-compose up serviceName` Run one thing with a simple `docker-compose up serviceName`
## Setup ## Setup
@ -13,7 +13,7 @@ Run one thing with a simple `docker-compose up serviceName`
##### Files ##### Files
Create/Fill in the following files in a `traefik/` directory under this one using the provided templates: Create/Fill in the following files in a `traefik/` directory under this one using the provided templates:
- `traefik.toml` - `traefik.toml`
- `traefik-dynamic.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. 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.
@ -22,7 +22,7 @@ In addition to the above files, make sure the docker socket is mounted:
- `/var/run/docker.sock:/var/run/docker.sock:ro` - `/var/run/docker.sock:/var/run/docker.sock:ro`
#### References #### References
https://doc.traefik.io/traefik/getting-started/quick-start/ https://doc.traefik.io/traefik/getting-started/quick-start/
https://doc.traefik.io/traefik/user-guides/docker-compose/basic-example/ https://doc.traefik.io/traefik/user-guides/docker-compose/basic-example/
### Code-Server ### Code-Server
@ -34,12 +34,12 @@ Mount the persistant storage somewhere.
- `/pwspool/software/code-server/config:/config` - `/pwspool/software/code-server/config:/config`
##### Metadata ##### Metadata
Re-roll hashed passwords. Re-roll hashed passwords.
./olomana.ini:/etc/grafana/grafana.ini ./olomana.ini:/etc/grafana/grafana.ini
#### References #### References
https://docs.linuxserver.io/images/docker-code-server/ https://docs.linuxserver.io/images/docker-code-server/
https://coder.com/docs/code-server/latest/install#docker https://coder.com/docs/code-server/latest/install#docker
https://hub.docker.com/r/linuxserver/code-server 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 https://github.com/coder/code-server/blob/main/docs/FAQ.md#can-i-store-my-password-hashed
### Grafana ### Grafana
@ -63,5 +63,9 @@ To set up the data source in grafana to point to prometheus, you would refer to
#### References #### References
https://grafana.com/docs/grafana/latest/setup-grafana/installation/docker/ https://grafana.com/docs/grafana/latest/setup-grafana/installation/docker/
### Homepage
#### Instructions
##### Build
Test build with `docker-compose build homepage`.

View File

@ -57,7 +57,7 @@ services:
depends_on: depends_on:
- node_exporter - node_exporter
- prometheus - prometheus
networks: networks:
- grafana - grafana
- traefik - traefik
user: "1003" user: "1003"
@ -104,3 +104,15 @@ services:
labels: labels:
- traefik.enable=false - traefik.enable=false
homepage:
image: homepage
container_name: homepage
build: homepage/
restart: unless-stopped
networks:
- traefik
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=80

View File

@ -0,0 +1,9 @@
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

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

View File

@ -0,0 +1,47 @@
<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

@ -0,0 +1,50 @@
<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

@ -0,0 +1,81 @@
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

@ -0,0 +1,105 @@
<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.

After

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 293 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 262 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

View File

@ -0,0 +1,178 @@
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;
}