Added page about my homelab

This commit is contained in:
Rolf Martin Glomsrud 2024-08-03 21:19:51 +02:00
parent 588660ac62
commit e85e882f48
23 changed files with 522 additions and 31 deletions

View file

@ -5,7 +5,7 @@ import '../styles/global.css';
interface Props { interface Props {
title: string; title: string;
description: string; description?: string;
image?: string; image?: string;
} }

View file

@ -9,7 +9,7 @@ const today = new Date();
<span class="sr-only">My Github?</span> <span class="sr-only">My Github?</span>
<svg viewBox="0 0 16 16" aria-hidden="true" width="32" height="32" astro-icon="social/github" <svg viewBox="0 0 16 16" aria-hidden="true" width="32" height="32" astro-icon="social/github"
><path ><path
fill="currentColor" fill="white"
d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.012 8.012 0 0 0 16 8c0-4.42-3.58-8-8-8z" d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.012 8.012 0 0 0 16 8c0-4.42-3.58-8-8-8z"
></path></svg ></path></svg
> >
@ -21,17 +21,21 @@ const today = new Date();
footer { footer {
padding: 2em 1em 6em 1em; padding: 2em 1em 6em 1em;
/* background: linear-gradient(var(--gray-gradient)) no-repeat;'*/ /* background: linear-gradient(var(--gray-gradient)) no-repeat;'*/
color: rgb(var(--gray)); color: white;
background-color: rgb(198, 60, 81);
text-align: center; text-align: center;
width: 80%;
margin-left: auto; margin-left: auto;
margin-right: auto; margin-right: auto;
margin-top: 0.5rem;
margin-bottom: 0.5rem;
border-radius: 10px;
width: 100%;
height: 0em;
} }
.social-links { .social-links {
display: flex; display: flex;
justify-content: center; justify-content: center;
gap: 1em; gap: 1em;
margin-top: 1em;
} }
.social-links a { .social-links a {
text-decoration: none; text-decoration: none;

View file

@ -10,6 +10,7 @@ import { SITE_TITLE } from '../consts';
<HeaderLink href="/">Home</HeaderLink> <HeaderLink href="/">Home</HeaderLink>
<HeaderLink href="/blog">Blog</HeaderLink> <HeaderLink href="/blog">Blog</HeaderLink>
<HeaderLink href="/about">About</HeaderLink> <HeaderLink href="/about">About</HeaderLink>
<HeaderLink href="/homelab">Homelab</HeaderLink>
</div> </div>
<div class="social-links"> <div class="social-links">
<a href="https://github.com/polsevev" target="_blank"> <a href="https://github.com/polsevev" target="_blank">
@ -28,12 +29,17 @@ import { SITE_TITLE } from '../consts';
header { header {
margin: 0; margin: 0;
padding: 0 1em; padding: 0 1em;
background: white; background-color: rgb(198, 60, 81);
box-shadow: 0 2px 8px rgba(var(--black), 5%); box-shadow: 0 2px 8px rgba(var(--black), 5%);
border-radius: 10px;
margin-top: 0.5rem;
color: white;
width: 100%;
} }
h2 { h2 {
margin: 0; margin: 0;
font-size: 1em; font-size: 1em;
color: white;
} }
h2 a, h2 a,

View file

@ -1,5 +1,24 @@
import { defineCollection, z } from 'astro:content'; import { defineCollection, z } from 'astro:content';
const homelab = defineCollection({
type: 'content',
schema: z.object({
title: z.string(),
description: z.string(),
// Transform string to Date object
pubDate: z.coerce.date(),
updatedDate: z.coerce.date().optional(),
})
})
const servers = defineCollection({
type: 'content',
schema: z.object({
title: z.string(),
lastUpdated: z.coerce.date()
})
})
const blog = defineCollection({ const blog = defineCollection({
type: 'content', type: 'content',
// Type-check frontmatter using a schema // Type-check frontmatter using a schema
@ -9,8 +28,7 @@ const blog = defineCollection({
// Transform string to Date object // Transform string to Date object
pubDate: z.coerce.date(), pubDate: z.coerce.date(),
updatedDate: z.coerce.date().optional(), updatedDate: z.coerce.date().optional(),
heroImage: z.string().optional(),
}), }),
}); });
export const collections = { blog }; export const collections = { blog, homelab, servers };

View file

@ -0,0 +1,7 @@
---
title: Woodpecker as CI
description: Setting up my own CI with Woodpecker!
pubDate: 08.03.2024
---
I am currently working on setting opp Woodpecker as my CI. Will write a post about this here :)

View file

@ -0,0 +1,34 @@
---
title: 'Oceanus'
lastUpdated: '03.08.2024'
---
## What is it?
Oceanus is my main VM server. It used to be an office PC, but has since been re-purposed as a tiny home server.
## Specs
- MODEL: HP Elitedesk 600 G3
- CPU: i7 6700
- RAM: 16 GB DDR4
- Storage:
- 256 GB NVME SSD
- 128 GB SATA SSD
## What is on it?
Currently this server has quite a bit of purpose, but i will probably move most of the stateless applications from this into my K3S cluster.
- lb-1
- HAproxy load-balancer
- nginx-public
- Temporary NGINX until i get K3S up and running
- wireguard
- Hosts a wireguard instance i use for VPN
- grafana
- ALL THE DASHBOARDS!
- woodpecker_worker
- This is a WIP of setting up my own CI
![](./images/oceanus.png)

View file

@ -0,0 +1,27 @@
---
title: 'Ares'
lastUpdated: '03.08.2024'
---
## What is it?
Ares is a node in my cluster of 5 mini PCs. It follows the exact same configuration as the other nodes for consistency. It used to be an office PC, but has since been re-purposed as a tiny home server.
## Specs
- MODEL: HP Elitedesk 705 G3 mini
- CPU: AMD A10-8770E
- RAM: 16 GB DDR4
- Storage
- 256 GB NVME SSD
- 128 GB SATA SSD
## What is on it?
This server is running proxmox at the moment, to allow for virtualization of all my services. Currently this server has 2 VMS.
- k3s_master
- Master in my K3s cluster
- k3s_worker
- Worker in my K3S cluster
![](./images/cluster.png)

View file

@ -0,0 +1,25 @@
---
title: 'Cronus'
lastUpdated: '03.08.2024'
---
## What is it?
Cronus was my first server, and i built it myself. This was my entry into homelabing and i am so thankful i spent the money on it, even though i was a struggling student.
## Specs
- CPU: 1x Xeon E5 2678 V3 12 core
- RAM: 24 GB DDR4
- MOTHERBOARD: MSI X99S SLI-PLUS
- Storage
- 7x 4 TB Ironwolf HDD
- 1x 1 TB NVME SSD
- GPU: Nvidia GTX 1050 ti
## What is on it?
This server is running [Unraid](https://unraid.net). I made this choice because i was new to servers when i built it, and wanted a simple entry. Turns out that was quite a wise choice, as it allowed me to build my skills over time without making it super difficult in the beginning.
Currently this server mostly only contains files and serves its purpose as a NAS. I do some video transcoding with ffmpeg on it as well to minimize the space my videos take. This is actually a breeze because of the GPU
![](./images/cronus.png)

View file

@ -0,0 +1,24 @@
---
title: 'Hades'
lastUpdated: '03.08.2024'
---
Hades is a node in my cluster of 5 mini PCs. It follows the exact same configuration as the other nodes for consistency. It used to be an office PC, but has since been re-purposed as a tiny home server.
- MODEL: HP Elitedesk 705 G3 mini
- CPU: AMD A10-8770E
- RAM: 16 GB DDR4
- Storage
- 256 GB NVME SSD
- 128 GB SATA SSD
## What is on it?
This server is running proxmox at the moment, to allow for virtualization of all my services. Currently this server has 2 VMS.
- k3s_master
- Master in my K3s cluster
- k3s_worker
- Worker in my K3S cluster
![](./images/cluster.png)

View file

@ -0,0 +1,24 @@
---
title: 'Hermes'
lastUpdated: '03.08.2024'
---
Hermes is a node in my cluster of 5 mini PCs. It follows the exact same configuration as the other nodes for consistency. It used to be an office PC, but has since been re-purposed as a tiny home server.
- MODEL: HP Elitedesk 705 G3 mini
- CPU: AMD A10-8770E
- RAM: 16 GB DDR4
- Storage
- 256 GB NVME SSD
- 128 GB SATA SSD
## What is on it?
This server is running proxmox at the moment, to allow for virtualization of all my services. Currently this server has 2 VMS.
- k3s_master
- Master in my K3s cluster
- k3s_worker
- Worker in my K3S cluster
![](./images/cluster.png)

Binary file not shown.

After

Width:  |  Height:  |  Size: 717 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 936 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 542 KiB

View file

@ -0,0 +1,24 @@
---
title: 'Poseidon'
lastUpdated: '03.08.2024'
---
Poseidon is a node in my cluster of 5 mini PCs. It follows the exact same configuration as the other nodes for consistency. It used to be an office PC, but has since been re-purposed as a tiny home server.
- MODEL: HP Elitedesk 705 G3 mini
- CPU: AMD A10-8770E
- RAM: 16 GB DDR4
- Storage
- 256 GB NVME SSD
- 128 GB SATA SSD
## What is on it?
This server is running proxmox at the moment, to allow for virtualization of all my services. Currently this server has 2 VMS.
- k3s_master
- Master in my K3s cluster
- k3s_worker
- Worker in my K3S cluster
![](./images/cluster.png)

View file

@ -0,0 +1,25 @@
---
title: 'Zeus'
lastUpdated: '03.08.2024'
---
## What is it?
Zeus is a node in my cluster of 5 mini PCs. It follows the exact same configuration as the other nodes for consistency. It used to be an office PC, but has since been re-purposed as a tiny home server.
## Specs
- MODEL: HP Elitedesk 705 G3 mini
- CPU: AMD A10-8770E
- RAM: 16 GB DDR4
- Storage
- 256 GB NVME SSD
- 128 GB SATA SSD
## What is on it?
This server is running proxmox at the moment, to allow for virtualization of all my services. Currently this server has 2 VMS.
- k3s_master
- Master in my K3s cluster
- k3s_worker
- Worker in my K3S cluster
![](./images/cluster.png)

View file

@ -7,7 +7,7 @@ import FormattedDate from '../components/FormattedDate.astro';
type Props = CollectionEntry<'blog'>['data']; type Props = CollectionEntry<'blog'>['data'];
const { title, description, pubDate, updatedDate, heroImage } = Astro.props; const { title, description, pubDate, updatedDate} = Astro.props;
--- ---
<html lang="en"> <html lang="en">
@ -19,21 +19,12 @@ const { title, description, pubDate, updatedDate, heroImage } = Astro.props;
max-width: 100%; max-width: 100%;
margin: 0; margin: 0;
} }
.hero-image {
width: 100%;
}
.hero-image img {
display: block;
margin: 0 auto;
border-radius: 12px;
box-shadow: var(--box-shadow);
}
.prose { .prose {
width: 720px; width: 720px;
max-width: calc(100% - 2em); max-width: calc(100% - 2em);
margin: auto; margin: auto;
padding: 1em; padding: 1em;
color: rgb(var(--gray-dark)); color: cyan;
} }
.title { .title {
margin-bottom: 1em; margin-bottom: 1em;
@ -58,9 +49,6 @@ const { title, description, pubDate, updatedDate, heroImage } = Astro.props;
<Header /> <Header />
<main> <main>
<article> <article>
<div class="hero-image">
{heroImage && <img width={1020} height={510} src={heroImage} alt="" />}
</div>
<div class="prose"> <div class="prose">
<div class="title"> <div class="title">
<div class="date"> <div class="date">

View file

@ -0,0 +1,67 @@
---
import type { CollectionEntry } from 'astro:content';
import BaseHead from '../components/BaseHead.astro';
import Header from '../components/Header.astro';
import Footer from '../components/Footer.astro';
import FormattedDate from '../components/FormattedDate.astro';
type Props = CollectionEntry<'servers'>['data'];
const { title, lastUpdated} = Astro.props;
---
<html lang="en">
<head>
<BaseHead title={title} />
<style>
main {
width: calc(100% - 2em);
max-width: 100%;
margin: 0;
}
.prose {
width: 720px;
max-width: calc(100% - 2em);
margin: auto;
padding: 1em;
color: cyan;
}
.title {
margin-bottom: 1em;
padding: 1em 0;
text-align: center;
line-height: 1;
}
.title h1 {
margin: 0 0 0.5em 0;
}
.date {
margin-bottom: 0.5em;
color: rgb(var(--gray));
}
img{
width: 100px;
}
</style>
</head>
<body>
<Header />
<main>
<article>
<div class="prose">
<div class="title">
<div class="date">
<FormattedDate date={lastUpdated} />
</div>
<h1>🖥️ {title}</h1>
<hr />
</div>
<slot />
</div>
</article>
</main>
<Footer />
</body>
</html>

View file

@ -21,7 +21,4 @@ import Layout from "../layouts/BlogPost.astro";
<p> <p>
I do love board games! Currently my favorite has to be Magic The Gathering, it eats up way too much of my time and salary, but i find the game so engaging and fun. I do love board games! Currently my favorite has to be Magic The Gathering, it eats up way too much of my time and salary, but i find the game so engaging and fun.
</p> </p>
<p>
I mostly spend my time hanging out with friends, playing magic, or tinkering in my homelab.
</p>
</Layout> </Layout>

View file

@ -94,7 +94,6 @@ const posts = (await getCollection('blog')).sort(
posts.map((post) => ( posts.map((post) => (
<li> <li>
<a href={`/blog/${post.slug}/`}> <a href={`/blog/${post.slug}/`}>
<img width={720} height={360} src={post.data.heroImage} alt="" />
<h4 class="title">{post.data.title}</h4> <h4 class="title">{post.data.title}</h4>
<p class="date"> <p class="date">
<FormattedDate date={post.data.pubDate} /> <FormattedDate date={post.data.pubDate} />

View file

@ -0,0 +1,23 @@
---
import { type CollectionEntry, getCollection } from 'astro:content';
import BlogPost from '../../layouts/BlogPost.astro';
export async function getStaticPaths() {
const posts = await getCollection('homelab');
return posts.map((post)=> ({
params: { slug: post.slug },
props: post,
}));
}
type Props = CollectionEntry<'homelab'>;
const post = Astro.props;
const { Content } = await post.render();
---
<BlogPost {...post.data}>
<Content />
</BlogPost>

View file

@ -0,0 +1,165 @@
---
import { getCollection } from "astro:content";
import BaseHead from "../../components/BaseHead.astro";
import Footer from "../../components/Footer.astro";
import FormattedDate from "../../components/FormattedDate.astro";
import Header from "../../components/Header.astro";
import { SITE_TITLE, SITE_DESCRIPTION } from "../../consts";
const posts = (await getCollection('homelab')).sort(
(a, b) => a.data.pubDate.valueOf() - b.data.pubDate.valueOf()
);
const servers = (await getCollection('servers'));
---
<!doctype html>
<html lang="en">
<head>
<BaseHead title={SITE_TITLE} description={SITE_DESCRIPTION} />
<style>
main {
width: 100%;
}
ul {
display: flex;
flex-wrap: wrap;
gap: 2rem;
list-style-type: none;
margin: 0;
padding: 0;
}
ul li {
width: 100%;
}
ul li * {
text-decoration: none;
transition: 0.2s ease;
}
ul li img {
margin-bottom: 0.5rem;
border-radius: 12px;
}
ul li a {
display: block;
}
.title {
margin: 0;
color: rgb(0,255,159);
line-height: 1;
}
.date {
margin: 0;
color: rgb(var(--gray));
}
ul li a:hover h4,
ul li a:hover .date {
color: rgb(var(--accent));
}
ul a:hover img {
box-shadow: var(--box-shadow);
}
@media (max-width: 720px) {
ul {
gap: 0.5em;
}
ul li {
width: 100%;
text-align: center;
}
}
site_header{
text-align: center;
}
left{
width: 75%;
text-align: center;
}
right{
width: 25%;
text-align: center;
}
#column{
float:left;
}
box{
display: flex;
}
box left,right{
border-color: white;
border-width: 0.2em;
border-style: solid;
border-radius: 1em;
margin: 0.2em;
padding: 0.5em;
}
</style>
</head>
<body>
<Header />
<main>
<site_header>
<h2>All bout the homelab!</h2>
</site_header>
<box>
<left class="column">
<h3>What is in my homelab now?</h3>
<p>
So my current homelab has gotten quite extensive, which honestly is a pain because electricity is expensive as hell.
</p>
<p>I have waaaaay too many servers, and they are not at all used to their full capabilites. But i am a tinkerer at heart.
Owning a proxmox cluster with 6 nodes, or a 5 node K3S cluster is just fun to me. It doesn't really matter that it costs money, it is a hobby. So without further ado,
let me explain each server, and what it is currently being used for.
</p>
<p>As you can probably tell, i use greek mythology for the naming scheme of physical machines :P</p>
<section>
<ul>
{
servers.map((s) => (
<li>
<a href={`/homelab/servers/${s.slug}/`}>
<h5 class="title">🖥️ {s.data.title}</h5>
</a>
</li>
))
}
</ul>
</section>
</left>
<right class="column">
<h3>Homelab blog:</h3>
<section>
<ul>
{
posts.length == 0 ? <p>Wow, such empty</p> :
posts.sort((a,b) => {
return b.data.pubDate.getTime() - a.data.pubDate.getTime()
}).map((post) => (
<li>
<a href={`/homelab/${post.slug}/`}>
<h5 class="title">{post.data.title}</h4>
<p class="date">
<FormattedDate date={post.data.pubDate} />
</p>
</a>
</li>
))
}
</ul>
</section>
</right>
</box>
</main>
<Footer />
</body>
</html>

View file

@ -0,0 +1,25 @@
---
import { type CollectionEntry, getCollection } from 'astro:content';
import BlogPost from '../../../layouts/BlogPost.astro';
import ServerDescription from '../../../layouts/ServerDescription.astro';
import type { ACTION_ERROR_CODES } from 'astro:actions';
export async function getStaticPaths() {
const servers = await getCollection('servers');
return servers.map((s)=> ({
params: { slug: s.slug },
props: s,
}));
}
type Props = CollectionEntry<'servers'>;
const servers = Astro.props;
const { Content } = await servers.render();
---
<ServerDescription {...servers.data}>
<Content />
</BlogPost>

View file

@ -14,6 +14,7 @@
--gray-gradient: rgba(var(--gray-light), 50%), #fff; --gray-gradient: rgba(var(--gray-light), 50%), #fff;
--box-shadow: 0 2px 6px rgba(var(--gray), 25%), 0 8px 24px rgba(var(--gray), 33%), --box-shadow: 0 2px 6px rgba(var(--gray), 25%), 0 8px 24px rgba(var(--gray), 33%),
0 16px 32px rgba(var(--gray), 33%); 0 16px 32px rgba(var(--gray), 33%);
} }
@font-face { @font-face {
font-family: 'Atkinson'; font-family: 'Atkinson';
@ -29,6 +30,12 @@
font-style: normal; font-style: normal;
font-display: swap; font-display: swap;
} }
html {
height: 100%;
}
footer{
height: 4em;
}
body { body {
font-family: 'Atkinson', sans-serif; font-family: 'Atkinson', sans-serif;
margin: 0; margin: 0;
@ -38,11 +45,11 @@ body {
margin-left: auto; margin-left: auto;
margin-right: auto; margin-right: auto;
text-align: left; text-align: left;
background: linear-gradient(var(--gray-gradient)) no-repeat; background: repeating-linear-gradient(to bottom, #522258 0%, #8c3061, #522258 100%);
background-size: 100% 600px;
word-wrap: break-word; word-wrap: break-word;
overflow-wrap: break-word; overflow-wrap: break-word;
color: rgb(var(--gray-dark)); color: cyan;
font-size: 20px; font-size: 20px;
line-height: 1.7; line-height: 1.7;
} }
@ -50,7 +57,9 @@ main {
width: 720px; width: 720px;
max-width: calc(100% - 2em); max-width: calc(100% - 2em);
margin: auto; margin: auto;
min-height: 100%;
padding: 3em 1em; padding: 3em 1em;
margin-bottom: -4em;
} }
h1, h1,
h2, h2,
@ -59,7 +68,7 @@ h4,
h5, h5,
h6 { h6 {
margin: 0 0 0.5rem 0; margin: 0 0 0.5rem 0;
color: rgb(var(--black)); color: rgb(0,255,159);
line-height: 1.2; line-height: 1.2;
} }
h1 { h1 {