Compare commits

..

76 commits
htmx ... master

Author SHA1 Message Date
63b4438463 Fix keyscan
All checks were successful
/ build (push) Successful in 28s
2025-03-29 19:40:04 +01:00
7989f98776 Move website to Nix server
Some checks failed
/ build (push) Has been cancelled
2025-03-29 19:36:26 +01:00
2608fd281d fix_date
All checks were successful
/ build (push) Successful in 27s
2025-02-10 19:32:28 +01:00
19e2605b03 1
All checks were successful
/ build (push) Successful in 30s
2025-01-30 18:19:35 +01:00
d12dfc6d91 forgejo logot o footer
All checks were successful
/ build (push) Successful in 24s
2024-11-03 21:13:15 +01:00
5a74fb5ef5 Fix path to forgejo logo
All checks were successful
/ build (push) Successful in 24s
2024-11-03 21:09:46 +01:00
10a550aea2 Merge branch 'master' of code.polsevev.dev:polsevev/polsevev.dev
All checks were successful
/ build (push) Successful in 25s
2024-11-03 21:08:19 +01:00
1d51866188 Move to forgejo 2024-11-03 21:08:10 +01:00
f969c3620d Upgrade astro
All checks were successful
/ build (push) Successful in 24s
2024-10-29 22:12:23 +01:00
5057118cc3 Merge branch 'master' of code.polsevev.dev:polsevev/polsevev.dev
All checks were successful
/ build (push) Successful in 27s
2024-10-26 12:41:35 +02:00
66f85f80f2 Edit a bit of the text on homepage and about page 2024-10-26 12:40:55 +02:00
76a9cbb157 Merge pull request 'workflow_test' (#1) from workflow_test into master
All checks were successful
/ build (push) Successful in 26s
Reviewed-on: #1
2024-10-26 10:30:32 +00:00
12ae79ecd8 Set run to only run on master 2024-10-26 12:29:46 +02:00
354c45a260 I am not root LOL
All checks were successful
/ build (push) Successful in 27s
2024-10-26 12:24:47 +02:00
186ff13f6c Finito
Some checks failed
/ build (push) Has been cancelled
2024-10-26 12:23:41 +02:00
84585ca242 Forgot to actually join to tailscale network
Some checks failed
/ build (push) Has been cancelled
2024-10-26 12:22:09 +02:00
ce129548d1 LOL
Some checks failed
/ build (push) Has been cancelled
2024-10-26 12:19:25 +02:00
4f07ca6526 Fix could not resolve hostname
Some checks failed
/ build (push) Failing after 27s
2024-10-26 12:17:51 +02:00
62166113fa Add sharp
Some checks failed
/ build (push) Failing after 28s
2024-10-26 12:16:49 +02:00
82774c08f4 Run it!
Some checks failed
/ build (push) Failing after 20s
2024-10-26 12:13:45 +02:00
5c642d0626 Checkout
All checks were successful
/ build (push) Successful in 17s
2024-10-26 12:09:09 +02:00
6a8a1a5871 Wrong runs-on
Some checks failed
/ build (push) Failing after 1s
2024-10-26 12:00:50 +02:00
0e156d5f61 Initial test of actions
Some checks are pending
/ build (push) Waiting to run
2024-10-26 11:59:56 +02:00
7c973ac48a Change tracking code 2024-10-22 21:11:01 +02:00
0c245cb055 Add self-hosted analytics 2024-10-20 18:59:43 +02:00
b5aabf34a3 Fix wrong date on recent blogpost 2024-10-19 17:55:59 +02:00
d2bb4cf502 Readme update 2024-10-19 17:53:15 +02:00
9e9aacfbcf New server! + Blogpost about homelab 2024-10-19 17:47:25 +02:00
af1cd7ed91 Fix sorting 2024-09-29 19:14:02 +02:00
4566c85c44 Remove placeholders lol 2024-09-29 19:07:18 +02:00
bd785f0fc0 WROOONG branch on push 2024-09-29 17:20:26 +02:00
7d46ceeae7 Correct date 2024-09-29 17:18:16 +02:00
c6891b9e1f Wrong branch :) 2024-09-29 16:52:36 +02:00
1f342fb6fd Styling stuffs 2024-09-29 16:26:08 +02:00
6382e55f70 styling 2024-09-29 16:12:28 +02:00
41f6a74b69 lol 2024-09-29 16:09:51 +02:00
2b17edc92b Terraform writeup 2024-09-29 16:08:48 +02:00
92507301ac Add navigation to homelab 2024-08-23 20:38:34 +02:00
8b72fb085f improve mobile experience 2024-08-23 20:36:08 +02:00
4fbb7f0321 Merge branch 'master' of github.com:polsevev/polsevev.dev 2024-08-08 20:16:42 +02:00
71dd878651 Added homelab blog to rss 2024-08-08 20:16:20 +02:00
Rolf Martin Glomsrud
8e245ce07b
Create LICENSE 2024-08-06 15:14:36 +02:00
429e7ff3b0 Reworked some of the navigation styling 2024-08-04 21:29:22 +02:00
2dc58f20ce Reworked the homepage 2024-08-04 21:19:54 +02:00
90bab656bf Header on blog site and correction on blog post 2024-08-04 21:07:57 +02:00
614a021b1e Added homelab blog post 2024-08-04 21:01:37 +02:00
88d816c144 Finally got my own ci/cd woooo 2024-08-04 01:30:07 +02:00
4a34d5d0c7 test 2024-08-04 01:03:20 +02:00
f7c69d649b Hopefully now 2024-08-04 00:59:36 +02:00
4abdde9ec9 Hopefully now 2024-08-04 00:58:54 +02:00
4e19138a89 Hopefully now 2024-08-04 00:57:22 +02:00
e00d1a023e Hopefully now 2024-08-04 00:56:39 +02:00
0f6b1b1b7b i forgot to mention secrets 2024-08-04 00:54:24 +02:00
6fc7af6488 i forgot to mention secrets 2024-08-04 00:50:27 +02:00
4ba483e420 test 2024-08-04 00:47:08 +02:00
0fb6a5ff36 test 2024-08-04 00:46:17 +02:00
e4c39ab2fb a 2024-08-04 00:44:51 +02:00
8fd2c0d180 a 2024-08-04 00:43:50 +02:00
de6ad54657 a 2024-08-04 00:42:14 +02:00
1b464c0f93 woodpecker lol 2024-08-04 00:39:55 +02:00
2278272c15 woodpecker #99 2024-08-04 00:38:56 +02:00
75491a3f7c woodpecker test 55 2024-08-04 00:32:30 +02:00
ebe9030bfb test 2024-08-03 23:44:54 +02:00
16d12bb39c test 2024-08-03 23:43:20 +02:00
48a635a68d bookworm test 3 2024-08-03 23:25:01 +02:00
b208dbcc9d woodpecker test 2 2024-08-03 23:18:04 +02:00
d57ed2986f testing woodpecker 2024-08-03 23:12:30 +02:00
995cd06420 forgot one file 2024-08-03 21:46:25 +02:00
20c91cb8dc fix navigation bug 2024-08-03 21:44:57 +02:00
35ce9ed2be Add navigation back on subpages 2024-08-03 21:38:16 +02:00
e85e882f48 Added page about my homelab 2024-08-03 21:19:51 +02:00
588660ac62 some initial stuff :) 2024-08-03 00:06:03 +02:00
b6a29a7f22 move 2024-08-02 23:46:08 +02:00
d2033cf715 change run title 2024-08-02 23:11:26 +02:00
e97083c325 trigger new run 2024-08-02 23:10:23 +02:00
4d980e399c Initial Astro commit 2024-08-02 22:52:09 +02:00
65 changed files with 8538 additions and 194 deletions

1
.env
View file

@ -1 +0,0 @@
DATABASE_URL=postgres://postgres:localdb@localhost:5432/postgres

View file

@ -0,0 +1,14 @@
on:
push:
branches:
- master
jobs:
build:
runs-on: ubuntu
steps:
- uses: actions/checkout@v4
- run: npm i
- run: npm run build
- run: ssh-keyscan hephaestus >> ~/.ssh/known_hosts
- run: /bin/rsync -avzh ./dist ansible@hephaestus:/var/www/polsevev/

View file

@ -1,29 +0,0 @@
name: build and deploy to prod!
on:
push:
branches:
- master
jobs:
build:
name: Build Frontend
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: 18
- run: |
cd polsevev.dev.frontend
npm install
npm run build
- name: Rsync to server
uses: burnett01/rsync-deployments@5.2
with:
switches: -avzr --delete
path: polsevev.dev.frontend/dist
remote_path: /home/beepsort/homepage
remote_host: ${{secrets.SERVER_IP}}
remote_user: beepsort
remote_key: ${{secrets.SSH_PRIVATE_KEY}}
remote_port: 6969

32
.gitignore vendored
View file

@ -1,14 +1,24 @@
# Generated by Cargo
# will have compiled files and executables
debug/
target/
# build output
dist/
# generated types
.astro/
# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html
Cargo.lock
# dependencies
node_modules/
# These are backup files generated by rustfmt
**/*.rs.bk
# logs
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
# MSVC Windows builds of rustc generate these, which store debugging information
*.pdb
# environment variables
.env
.env.production
# macOS-specific files
.DS_Store
# jetbrains setting folder
.idea/

4
.vscode/extensions.json vendored Normal file
View file

@ -0,0 +1,4 @@
{
"recommendations": ["astro-build.astro-vscode", "unifiedjs.vscode-mdx"],
"unwantedRecommendations": []
}

11
.vscode/launch.json vendored Normal file
View file

@ -0,0 +1,11 @@
{
"version": "0.2.0",
"configurations": [
{
"command": "./node_modules/.bin/astro dev",
"name": "Development server",
"request": "launch",
"type": "node-terminal"
}
]
}

View file

@ -1,7 +0,0 @@
# I made my own Kubernetes cluster!
I have for a looong time wanted to have my own kubernetes cluster for my homelab. Just because it really sounds fun and learning a new skill is something i love!
You wanna know something cool? This very website is actually hosted on this actual cluster!
Here it is in action!

View file

@ -1,7 +0,0 @@
# I made my own Kubernetes cluster!
I have for a looong time wanted to have my own kubernetes cluster for my homelab. Just because it really sounds fun and learning a new skill is something i love!
You wanna know something cool? This very website is actually hosted on this actual cluster!
Here it is in action!

View file

@ -1,12 +0,0 @@
[package]
name = "polsevev_dev_backend"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
poem = "3.0.1"
poem-openapi = { version = "5.0.2", features = ["swagger-ui", "static-files"] }
pulldown-cmark = "0.11.0"
tokio = { version = "1", features = ["macros", "rt-multi-thread"] }

24
LICENSE Normal file
View file

@ -0,0 +1,24 @@
This is free and unencumbered software released into the public domain.
Anyone is free to copy, modify, publish, use, compile, sell, or
distribute this software, either in source code form or as a compiled
binary, for any purpose, commercial or non-commercial, and by any
means.
In jurisdictions that recognize copyright laws, the author or authors
of this software dedicate any and all copyright interest in the
software to the public domain. We make this dedication for the benefit
of the public at large and to the detriment of our heirs and
successors. We intend this dedication to be an overt act of
relinquishment in perpetuity of all present and future rights to this
software under copyright law.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
For more information, please refer to <https://unlicense.org>

View file

@ -1 +1,5 @@
# HELLLO!
# Welcome!
This is a repo for my website [polsevev.dev](polsevev.dev)
You are welcome to look at, and use, the code. If you want submit a PR you can, but i prolly won't accept it, as this is just a place for mye to publish things on the interwebs while avoiding using social media.

10
astro.config.mjs Normal file
View file

@ -0,0 +1,10 @@
import { defineConfig } from 'astro/config';
import mdx from '@astrojs/mdx';
import sitemap from '@astrojs/sitemap';
// https://astro.build/config
export default defineConfig({
site: 'https://polsevev.dev',
integrations: [mdx(), sitemap()],
});

View file

@ -1,14 +0,0 @@
# Use postgres/example user/password credentials
version: '3.1'
services:
db:
image: postgres
restart: always
environment:
POSTGRES_PASSWORD: localdb
PGDATA: /data/postgres
ports:
- 5432:5432

6981
package-lock.json generated Normal file

File diff suppressed because it is too large Load diff

21
package.json Normal file
View file

@ -0,0 +1,21 @@
{
"name": "polsevev-dev",
"type": "module",
"version": "0.0.1",
"scripts": {
"dev": "astro dev",
"start": "astro dev",
"build": "astro check && astro build",
"preview": "astro preview",
"astro": "astro"
},
"dependencies": {
"@astrojs/check": "^0.9.4",
"@astrojs/mdx": "^3.1.8",
"@astrojs/rss": "^4.0.9",
"@astrojs/sitemap": "^3.2.1",
"astro": "^4.16.7",
"sharp": "^0.33.5",
"typescript": "^5.5.4"
}
}

Binary file not shown.

Binary file not shown.

BIN
public/images/self.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 79 KiB

View file

@ -0,0 +1,40 @@
<svg viewBox="0 0 212 212" xmlns="http://www.w3.org/2000/svg">
<metadata
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:dc="http://purl.org/dc/elements/1.1/"
>
<rdf:RDF>
<cc:Work rdf:about="https://codeberg.org/forgejo/meta/src/branch/readme/branding#logo">
<dc:title>Forgejo logo</dc:title>
<cc:creator rdf:resource="https://caesarschinas.com/"><cc:attributionName>Caesar Schinas</cc:attributionName></cc:creator>
<cc:license rdf:resource="http://creativecommons.org/licenses/by-sa/4.0/" />
</cc:Work>
</rdf:RDF>
</metadata>
<style type="text/css">
circle {
fill: none;
stroke: #000;
stroke-width: 15;
}
path {
fill: none;
stroke: #000;
stroke-width: 25;
}
.orange {
stroke:#ff6600;
}
.red {
stroke:#d40000;
}
</style>
<g transform="translate(6,6)">
<path d="M58 168 v-98 a50 50 0 0 1 50-50 h20" class="orange" />
<path d="M58 168 v-30 a50 50 0 0 1 50-50 h20" class="red" />
<circle cx="142" cy="20" r="18" class="orange" />
<circle cx="142" cy="88" r="18" class="red" />
<circle cx="58" cy="180" r="18" class="red" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

View file

@ -0,0 +1,49 @@
---
// Import the global.css file here so that it is included on
// all pages through the use of the <BaseHead /> component.
import '../styles/global.css';
interface Props {
title: string;
description?: string;
image?: string;
}
const canonicalURL = new URL(Astro.url.pathname, Astro.site);
const { title, description, image = '/self.png' } = Astro.props;
---
<!-- Global Metadata -->
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<link rel="icon" type="image/svg+xml" href="/self.png" />
<meta name="generator" content={Astro.generator} />
<!-- Font preloads -->
<link rel="preload" href="/fonts/atkinson-regular.woff" as="font" type="font/woff" crossorigin />
<link rel="preload" href="/fonts/atkinson-bold.woff" as="font" type="font/woff" crossorigin />
<!-- Canonical URL -->
<link rel="canonical" href={canonicalURL} />
<!-- Primary Meta Tags -->
<title>{title}</title>
<meta name="title" content={title} />
<meta name="description" content={description} />
<!-- Open Graph / Facebook -->
<meta property="og:type" content="website" />
<meta property="og:url" content={Astro.url} />
<meta property="og:title" content={title} />
<meta property="og:description" content={description} />
<meta property="og:image" content={new URL(image, Astro.url)} />
<!-- Twitter -->
<meta property="twitter:card" content="summary_large_image" />
<meta property="twitter:url" content={Astro.url} />
<meta property="twitter:title" content={title} />
<meta property="twitter:description" content={description} />
<meta property="twitter:image" content={new URL(image, Astro.url)} />
<script defer src="https://umami.polsevev.dev/script.js" data-website-id="84ead158-3295-4160-8b05-48d56cf7337b"></script>

View file

@ -0,0 +1,47 @@
---
const today = new Date();
---
<footer>
&copy; {today.getFullYear()} Rolf Martin Glomsrud. All rights reserved.
<div class="social-links">
<a href="https://code.polsevev.dev" target="_blank" >
<span class="sr-only">My Github?</span>
<img src="/svg/Forgejo_logo.svg"/>
<svg viewBox="0 0 16 16" aria-hidden="true" width="32" height="32" astro-icon="social/github" ></svg>
</a>
</a>
</div>
</footer>
<style>
footer {
padding: 1em 1em 1em 1em;
color: white;
background-color: rgb(198, 60, 81);
text-align: center;
margin-left: auto;
margin-right: auto;
margin-top: 2rem;
margin-bottom: 2rem;
border-radius: 10px;
width: 90%;
max-width: 1000px;
}
.social-links {
display: flex;
justify-content: center;
}
.social-links a {
text-decoration: none;
color: rgb(var(--gray));
}
.social-links a:hover {
color: rgb(var(--gray-dark));
}
img{
background-color: white;
padding: 5px;
}
</style>

View file

@ -0,0 +1,17 @@
---
interface Props {
date: Date;
}
const { date } = Astro.props;
---
<time datetime={date.toISOString()}>
{
date.toLocaleDateString('en-us', {
year: 'numeric',
month: 'short',
day: 'numeric',
})
}
</time>

View file

@ -0,0 +1,78 @@
---
import HeaderLink from './HeaderLink.astro';
import { SITE_TITLE } from '../consts';
---
<header>
<nav>
<h2><a href="/">{SITE_TITLE}</a></h2>
<div class="internal-links">
<HeaderLink href="/">~/</HeaderLink>
<HeaderLink href="/blog">~/blog</HeaderLink>
<HeaderLink href="/about">~/about</HeaderLink>
</div>
<div class="social-links">
<a href="https://code.polsevev.dev" target="_blank" >
<span class="sr-only">My Github?</span>
<img src="/svg/Forgejo_logo.svg"/>
<svg viewBox="0 0 16 16" aria-hidden="true" width="32" height="32" astro-icon="social/github" ></svg>
</a>
</div>
</nav>
</header>
<style>
header {
padding: 0.5em 0.5em 0.5em 0.5em;
background-color: rgb(198, 60, 81);
box-shadow: 0 2px 8px rgba(var(--black), 5%);
border-radius: 10px;
margin-left: auto;
margin-right: auto;
margin-top: 2rem;
margin-bottom: 2rem;
color: white;
width: 90%;
max-width: 1000px;
}
h2 {
margin: 0;
font-size: 1em;
color: white;
}
h2 a,
h2 a.active {
text-decoration: none;
}
nav {
display: flex;
align-items: center;
justify-content: space-between;
}
nav a {
padding: 0.5em 0.5em;
color: var(--black);
border-bottom: 4px solid transparent;
text-decoration: none;
}
img{
background-color: white;
padding: 5px;
}
nav a.active {
text-decoration: none;
border-bottom-color: cyan;
}
.social-links,
.social-links a {
display: flex;
}
@media (max-width: 720px) {
.social-links {
display: none;
}
nav a {
padding: 0.2em 0.2em;
}
}
</style>

View file

@ -0,0 +1,26 @@
---
import type { HTMLAttributes } from 'astro/types';
type Props = HTMLAttributes<'a'>;
const { href, class: className, ...props } = Astro.props;
const { pathname } = Astro.url;
const subpath = pathname.match(/[^\/]+/g);
const isActive = href === pathname || href === '/' + subpath?.[0];
---
<a href={href} class:list={[className, { active: isActive }]} {...props}>
<slot />
</a>
<style>
a {
display: inline-block;
text-decoration: none;
}
a.active {
font-weight: bolder;
text-decoreation: underline;
}
</style>

5
src/consts.ts Normal file
View file

@ -0,0 +1,5 @@
// Place any global data in this file.
// You can import this data from anywhere in your site by using the `import` keyword.
export const SITE_TITLE = 'polsevev.dev';
export const SITE_DESCRIPTION = 'I add stuff here sometimes';

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 MiB

View file

@ -0,0 +1,11 @@
---
title: I got a split keyboard!
description: I am now a superior developer
pubDate: 1.30.2025
---
As can be read in the title, i finally caved and bought one. I am currently writing this post using it and it is better than i expected.
I will do a full writeup of my experience learning a split keyboard sometime in the future, just wanted to share the excitement!
![](./images/keyboard.jpg)

34
src/content/config.ts Normal file
View file

@ -0,0 +1,34 @@
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({
type: 'content',
// Type-check frontmatter using a schema
schema: z.object({
title: z.string(),
description: z.string(),
// Transform string to Date object
pubDate: z.coerce.date(),
updatedDate: z.coerce.date().optional(),
}),
});
export const collections = { blog, homelab, servers };

View file

@ -0,0 +1,28 @@
---
title: Homelab update
description: I did some major renovations to my homelab this week
pubDate: 10.19.2024
---
## My homelab used to be on the floor
Yeah, you read that right, no rack here. While i don't have any pictures, because i actually was ashamed of it, my homelab used to be on the floor under my desk. With a ratsnest of wires behind all the "servers", and the heat eminating from it mimicking the scorching Norwegian summer (so not that hot lol). On a random wednesday i decided to do something about this.
### Well, what?
As i have seen time and time again on [r/homelab](https://reddit.com/r/homelab), people use proper server racks. However, i live in a 32 m² appartment (more an overgrown hotel room), so finding a small but still usable rack turned out to be a challenge. I did some research and looked at the options availible to me. Turns out getting a proper rack would be quite expensive where i live. This is for several reasons, i don't have easy access to a large van, so it had to be shipped, meaning i can't go for something used, I don't have any proper rack mount servers and getting shelves for a rack is also not cheap. Therefore, after browsing reddit for a bit, i decided to go with a Ikea [Ekenabben](https://www.ikea.com/no/no/p/ekenabben-apen-hylle-asp-hvit-10487816/).
### The pain, but in the end, glory!
I fucking hate cable managing. There is no fun in it in my opinion, it takes ages and makes it difficult to make changes. But the end result is so satisfying. I think the picture speaks for itself, cables are routed behind the legs of the rack (where applicable) and the power adapters for the mini pcs are hidden below the bottom shelf. I love this.
![](./images/homelab_revamp.jpg)
To give a super quick rundown. At the top i have my 3d printer and free space for random things (see the raspberry pi). On the shelf below there is a wireless keyboard and mouse (for debugging broken machines), a label maker (god i love that thing), 5 mini pcs used as proxmox hosts, a 24 port unmanaged gigabit switch (behind the pcs) as well as a wifi access point. On the bottom shelf i have the two most powerful proxmox hosts, and free space for future servers (i have a problem).
If you wish to read in a bit more detail about my homelab, check out [/homelab](/homelab)
### Fin
I just wanted to share the excitement i have now, as my homelab always used to be a mess. So i hope i can finally maintain it this time (probably not xD). Thanks for reading!
![](./images/cable_management.jpg)

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.9 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 226 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

View file

@ -0,0 +1,140 @@
---
title: Terraform + Proxmox = <3
description: Using Terraform to deploy Virtual Machines
pubDate: 09.29.2024
---
## I am tired of UIs
Up until now, every time i wanted to deploy a VM in my homelab i did it with the Proxmox GUI. Don't get me wrong, the GUI is nice, but i would like to not have to repeat the same mundane task every time i want a new VM.
There is an argument to be made that i probably shouldn't run that many VMs, and that for the number of VMs i need, this isn't worth it. However, being able to deploy a VM by just changing a few variables in a file and running `terraform apply` just tickles something in my nerd brain.
I also really like the repeatability of this, as i use these VM definitions to deploy K3S hosts, docker hosts and others where i want a "default" setup.
## Initial requirements
To use Terraform with Proxmox we use a privoder created by [Telmate](https://github.com/Telmate/terraform-provider-proxmox). We create the initial `provider.tf` file as so:
```json
terraform {
required_providers {
proxmox = {
source = "telmate/proxmox"
version = "3.0.1-rc3"
}
}
}
variable "proxmox_api_url" {
type = string
}
variable "proxmox_user" {
type = string
sensitive = true
}
variable "proxmox_password" {
type = string
sensitive = true
}
variable "ssh_public_key" {
type = string
}
provider "proxmox"{
pm_api_url = var.proxmox_api_url
pm_user = var.proxmox_user
pm_password = var.proxmox_password
pm_tls_insecure = true
pm_otp = ""
}
```
I have stored all secrets to access the proxmox API in custom variables located in a `.auto.tfvars` file that i do not track in git. Those variables are defined in the provider such that if they are not present Terraform will complain.
In this step i had some trouble, as you can see i use a release candidate version. There seems to be a bug in version `2.9.3`, and instead of trying to track it down i just switched to release candidate.
## Defining the virtual machine
Now as i mentioned previously, i already have templates created in my proxmox cluster (i made these with Ansible). Therefore i can use these as a base for the provisioning of a new VM.
Here is an example definition of a VM
```terraform
resource "proxmox_vm_qemu" "havneboks" {
name = "havneboks"
desc = "Docker master"
target_node = "poseidon"
agent = 1
onboot = true
clone = "VM 9001"
cores = 4
sockets = 1
cpu = "host"
memory = 3096
# Setup the disk
disks {
ide {
ide2 {
cloudinit {
storage = "basseng"
}
}
}
scsi {
scsi0 {
disk {
size = "10G"
storage = "basseng"
}
}
}
}
network {
bridge = "vmbr0"
model = "virtio"
}
scsihw = "virtio-scsi-pci"
os_type = "cloud-init"
ipconfig0 = "ip=192.168.1.51/24,gw=192.168.1.1"
nameserver = "192.168.1.69"
ciuser = "ansible"
sshkeys = var.ssh_public_key
}
```
A VM is defined using the resource type of `proxmox_vm_qemu` with a name. I really like to use the names of the service just translated to norwegian, so in this case, this VM is called `havneboks` (meaning docker box).
- name: The name of the VM (hostname)
- desc: A description of the VM
- target_node: Which node in the cluster should the VM be provisioned to, in this case i provision it to the node `poseidon`(hostname)
- agent: Just select 1
- onboot: Set the VM to start when the host boots
- clone: Which template to clone the VM from
- cores: How much horsepowa u want?
- sockets: I only got 1 cpu in each of my boxes
- memory: How much RAM u want?
Now, a really important part of this is the disk setup, as you have to mimic the setup of the template (sizes can be chosen freely). So in my case, i have the cloud-init disk on `ide2` and the OS disk on `scsi0` in the template. Therefore we create the same exact setup for this resource.
- network: Just mimic the cloud-init
- scsihw: Which hardware do you want the host system to use to provide scsi?
- os_type: I use cloud-init, so we select cloud-init
- ipconfig: Now this is quite interesting, you should set a static IP and gateway such that the VM starts with a proper IP adress (you can also use DHCP here)
- namesever: I use a custom dns on adress `.69`(nice) so i set that, but if this is not set it will use "same as host"
- ciuser: A user to be created by cloud-init for this VM
- sshkeys: Initial SSH public keys to allow access. This is stored in a variable in my case.
## Just run?
Bing bang bom. You can now deploy your VM fully automatically and about 43 seconds later access it via SSH. Now all i did was configure it with ansible, and i have a fully reproducible homelab setup.
If you want to have a look at my homelab infrastructure as code repo, you can find it at [polsevev/homelab](https://github.com/polsevev/homelab)
![](./images/i-have-homelab.webp)

View file

@ -0,0 +1,44 @@
---
title: Woodpecker as CI
description: Setting up my own CI with Woodpecker!
pubDate: 08.03.2024
---
## Self hosted CI/CD!
Yesterday, i decided that i was tired of using Github Actions to deploy my code from Github. So i decided to self host my own CI/CD. This is mostly because i am not comfortable having port 22 on my web server be publicly accessible on the internet.
To do this, i went down quite the rabbit hole. First i looked at [TeamCity](https://www.jetbrains.com/teamcity/) from JetBrains as this is what i use at work. It looked promising, however i decided it was too complex and had too many features i did not really care about.
Then i stumbled across [Drone](https://www.drone.io/), which seemed to fit my use-case. However it has since gotten very corporate when it was bought up, so i decided to go with the open source fork called [Woodpecker](https://woodpecker-ci.org/)
This turned out to be a bit of a bigger challenge than i first thought, but i somehow got it up and running in the end. Follow along!
### Installing
This was by far the easiest. As i have not gotten around to setting up my Kubernetes cluster yet, and i wanted to start of simple. I just used the docker compose provided by the Woodpecker team.
To provide integration with Github, i use a github Oauth2 App i made in the github UI, this is very well explained in the documentation of Woodpecker.
Now that i have configured the docker compose, i created a VM on [cronus](/homelab/servers/oceanus) and deployed the compose file using Ansible.
### Setting up a pipeline
Writing the pipeline was simple enough, as it has a very similar syntax to Github actions. However, i needed a docker image for the pipeline to run in. This actually turned out to be a challenge, as i currently do not have a docker registry in my homelab (i will be setting up Gitea in the future dw).
To solve the docker registry problem, i decided to simply build the image locally on the server. Ansible comes to the rescue again. I wrote a simple playbook that builds the docker image on the VM such that it is available to Woodpecker. Missing registry problem solved!
Now finally, i could make my own image that had all the dependencies for this website!
We were not out of the woods yet however, i still had to figure out how i wanted to transfer the files to the server they will be hosted on. Previously i had used rsync to transfer them into a directory on my web server, and i decided to do something similar this time.
However, since i did not want to put the SSH keys into the docker image.
So i ended up adding a private ssh key into the secret store of Woodpecker. This allows rsync to communicate with my web server.
This meant i have to distribute the public key to the web server beforehand, ansible saved the day again.
### Pipeline finished
Finally, i could deploy this very website, using woodpecker to my own web server, all without exposing port 22 on the web server to the internet.
![](./images/borat-borat-very-nice.gif)

View file

@ -0,0 +1,29 @@
---
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: 32 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.
- Opnsense
- This server currently runs my firewall, which i have behind the ISP router using DMZ to not have double NAT (even though is technically still is double NAT)
- Game servers
- I run Factorio and Feed the Beast servers on this machine
![](./images/elitedesk_800.jpg)

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.jpg)

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,25 @@
---
title: 'Hades'
lastUpdated: '03.08.2024'
---
## What is it?
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.
## 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.jpg)

View file

@ -0,0 +1,24 @@
---
title: 'Hermes'
lastUpdated: '03.08.2024'
---
## What is it?
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.
## 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.jpg)

View file

@ -0,0 +1,23 @@
---
title: 'Hyperion'
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: i5 6500
- RAM: 16 GB DDR4
- Storage:
- 256 GB NVME SSD
- 128 GB SATA SSD
## What is on it?
This is one of the more powerful nodes in my Proxmox cluster. Mostly used for decent CPU performance tasks.
![](./images/elitedesk_800.jpg)

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 936 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 MiB

View file

@ -0,0 +1,24 @@
---
title: 'Poseidon'
lastUpdated: '03.08.2024'
---
## What is it?
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.
## 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.jpg)

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.jpg)

2
src/env.d.ts vendored Normal file
View file

@ -0,0 +1,2 @@
/// <reference path="../.astro/types.d.ts" />
/// <reference types="astro/client" />

View file

@ -0,0 +1,76 @@
---
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<'blog'>['data'];
const { title, description, pubDate, updatedDate} = Astro.props;
---
<html lang="en">
<head>
<BaseHead title={title} description={description} />
<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));
}
.last-updated-on {
font-style: italic;
}
img{
align-self: center;
}
</style>
</head>
<body>
<Header />
<main>
<article>
<div class="prose">
<div class="title">
<div class="date">
<FormattedDate date={pubDate} />
{
updatedDate && (
<div class="last-updated-on">
Last updated on <FormattedDate date={updatedDate} />
</div>
)
}
</div>
<h1>{title}</h1>
<hr />
</div>
<slot />
</div>
</article>
</main>
<Footer />
</body>
</html>

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

@ -1,43 +0,0 @@
mod prepare;
use std::fs;
use poem::{endpoint::StaticFilesEndpoint, listener::TcpListener, Route};
use poem_openapi::{param::Query, payload::Html, payload::PlainText, OpenApi, OpenApiService};
use prepare::{prep, BlogPost};
struct Api {
blogPosts: Vec<BlogPost>,
}
#[OpenApi]
impl Api {
#[oai(path = "/posts", method = "get")]
async fn posts(&self) -> Html<String> {
Html(self.blogPosts.iter().fold(String::new(), |posts, post| {
posts + "<li>" + &post.name + "</li>"
}))
}
}
#[tokio::main]
async fn main() -> Result<(), std::io::Error> {
// Load files
let blog = prep("Blog".to_string());
let api_service = OpenApiService::new(Api { blogPosts: blog }, "Hello World", "1.0")
.server("http://localhost:3000/api");
let ui = api_service.swagger_ui();
let app = Route::new()
.nest(
"/",
StaticFilesEndpoint::new("static").index_file("index.html"),
)
.nest("/api", api_service)
.nest("/swagger", ui);
poem::Server::new(TcpListener::bind("127.0.0.1:3000"))
.run(app)
.await
}

28
src/pages/about.astro Normal file
View file

@ -0,0 +1,28 @@
---
import Layout from "../layouts/BlogPost.astro";
---
<Layout
title="About me"
description="Lorem ipsum dolor sit amet"
pubDate={new Date("08.02.2024")}
>
<p>Hi!</p>
<p>
My name is Rolf, i am 2024-2000 years old and live in Norway.
</p>
<p>
I currently work as a devops engineer / infrastructure guy for an insurance company. As you probably can tell from this website, front-end is not really my strong suit hehe.
</p>
<p>
I have a strong love for all things self-hosted. Due to this, i currently run my own "little" homelab from my living room, i use this lab as a learning platform and just an all around
way of tinkering with fun technologies. Perhaps i will introduce it on this page some day?
</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.
</p>
<p>
I also spend a lot of my time as a member of the student organization <a href="https://fribyte.no">friByte</a>. Even though i am no longer a student, it is hard to quit an orginization with so many dedicated
and nerdy members
</p>
</Layout>

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('blog');
return posts.map((post) => ({
params: { slug: post.slug },
props: post,
}));
}
type Props = CollectionEntry<'blog'>;
const post = Astro.props;
const { Content } = await post.render();
---
<BlogPost {...post.data}>
<a href="/blog">
<h5>&lt--</h5>
</a>
<Content />
</BlogPost>

134
src/pages/blog/index.astro Normal file
View file

@ -0,0 +1,134 @@
---
import BaseHead from '../../components/BaseHead.astro';
import Header from '../../components/Header.astro';
import Footer from '../../components/Footer.astro';
import { SITE_TITLE, SITE_DESCRIPTION } from '../../consts';
import { getCollection } from 'astro:content';
import FormattedDate from '../../components/FormattedDate.astro';
const posts = (await getCollection('blog')).sort(
(b, a) => a.data.pubDate.valueOf() - b.data.pubDate.valueOf()
);
const homelab_posts = (await getCollection('homelab')).sort(
(b, a) => a.data.pubDate.valueOf() - b.data.pubDate.valueOf()
);
---
<!doctype html>
<html lang="en">
<head>
<BaseHead title={SITE_TITLE} description={SITE_DESCRIPTION} />
<style>
main {
width: 960px;
}
ul {
display: flex;
flex-wrap: wrap;
gap: 2rem;
list-style-type: none;
margin: 0;
padding: 0;
}
ul li {
width: calc(50% - 1rem);
}
ul li * {
text-decoration: none;
transition: 0.2s ease;
}
ul li:first-child {
width: 100%;
margin-bottom: 1rem;
text-align: center;
}
ul li:first-child img {
width: 100%;
}
ul li:first-child .title {
font-size: 2.369rem;
}
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(white);
}
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;
}
ul li:first-child {
margin-bottom: 0;
}
ul li:first-child .title {
font-size: 1.563em;
}
}
blogheader {
text-align: center;
}
</style>
</head>
<body>
<Header />
<main>
<blogheader>
<h1>Blog</h1>
<p>
This page contains a blog about nothing and everything going on with me.
I will probably post mostly random stuff or opinion pieces. What it will be about i have no idea.
So stay tuned!
</p>
</blogheader>
<section>
<ul>
{
posts.map((post) => (
<li>
<a href={`/blog/${post.slug}/`}>
<h4 class="title">{post.data.title}</h4>
<p class="date">
<FormattedDate date={post.data.pubDate} />
</p>
</a>
</li>
))
}
{ homelab_posts.map((post) => (
<li>
<a href={`/homelab/${post.slug}/`}>
<h4 class="title">{post.data.title}</h4>
<p class="date">
<FormattedDate date={post.data.pubDate} />
</p>
</a>
</li>
))}
</ul>
</section>
</main>
<Footer />
</body>
</html>

View file

@ -0,0 +1,26 @@
---
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}>
<a href="/homelab">
<h5>&lt--</h5>
</a>
<Content />
</BlogPost>

View file

@ -0,0 +1,78 @@
---
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 servers = (await getCollection('servers'));
---
<!doctype html>
<html lang="en">
<head>
<BaseHead title={SITE_TITLE} description={SITE_DESCRIPTION} />
<style>
main {
width: 960px;
}
.title {
margin: 0;
color: rgb(0,255,159);
line-height: 1;
}
.date {
margin: 0;
color: rgb(var(--gray));
}
a {
margin-top: 0.5em;
margin-bottom: 0.5em;
padding: 5px;
}
site_header{
text-align: center;
}
box{
text-align: center;
}
</style>
</head>
<body>
<Header />
<main>
<site_header>
<h2>What is in my homelab now?</h2>
</site_header>
<box>
<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>
<box>
{
servers.map((s) => (
<a href={`/homelab/servers/${s.slug}/`}>
<h5 class="title">🖥️ {s.data.title}</h5>
</a>
))
}
</box>
</box>
</main>
<Footer />
</body>
</html>

View file

@ -0,0 +1,28 @@
---
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}>
<a href="/homelab">
<h5>&lt--</h5>
</a>
<Content />
</BlogPost>

64
src/pages/index.astro Normal file
View file

@ -0,0 +1,64 @@
---
import BaseHead from "../components/BaseHead.astro";
import Header from "../components/Header.astro";
import Footer from "../components/Footer.astro";
import { SITE_TITLE, SITE_DESCRIPTION } from "../consts";
---
<!doctype html>
<html lang="en">
<head>
<BaseHead title={SITE_TITLE} description={SITE_DESCRIPTION} />
<style>
body {
text-align: center;
}
main {
}
main left,
right {
margin: 0.2em;
padding: 0.5em;
}
left {
max-width: 600px;
text-align: center;
}
right {
max-width: 400px;
text-align: center;
}
box {
display: flex;
}
p{
text-align: left;
}
</style>
</head>
<body>
<Header />
<main>
<h1>Rolf Glomsrud</h1>
<img src="images/self.png" />
<h3>Hello!</h3>
<p>Welcome to my little corner of the interwebs, glad to have you!</p>
<p>This site is a little project of mine where is hope to share my adventures both on and off line.</p>
<p>Here are some useful links to my stuff:</p>
<ul>
<li>
<p>I self host my code online using Forgejo! <a href="https://code.polsevev.dev">code.polsevev.dev</a></p>
</li>
<li>
<p>I document my homelab! <a href="/homelab">/homelab</a></p>
</li>
</ul>
</main>
<Footer />
</body>
</html>

20
src/pages/rss.xml.js Normal file
View file

@ -0,0 +1,20 @@
import rss from '@astrojs/rss';
import { getCollection } from 'astro:content';
import { SITE_TITLE, SITE_DESCRIPTION } from '../consts';
export async function GET(context) {
const posts = await getCollection('blog');
const homelab = await getCollection('homelab');
return rss({
title: SITE_TITLE,
description: SITE_DESCRIPTION,
site: context.site,
items: posts.map((post) => ({
...post.data,
link: `/blog/${post.slug}/`,
})).concat(homelab.map((post) => ({
...post.data,
link: `/homelab/${post.slug}`
}))),
});
}

View file

@ -1,40 +0,0 @@
use std::fs;
use pulldown_cmark::{Options, Parser};
pub fn prep(blogPath: String) -> Vec<BlogPost>{
let blogPaths = fs::read_dir(&blogPath);
let parsed = match blogPaths {
Ok(paths) => paths
.into_iter()
.map(|x| x.unwrap().path().to_str().unwrap().to_string())
.collect::<Vec<String>>(),
Err(_) => panic!("Could not load directory of blog"),
};
dbg!(&parsed);
let mut options = Options::empty();
let compiled_html = parsed.into_iter().map(|mdFilePath| {
let fileContents = fs::read_to_string(&mdFilePath).unwrap();
let name = mdFilePath.strip_prefix(&format!("{}/", &blogPath)).unwrap().to_string();
let parser = Parser::new_ext(&fileContents, options);
let mut html = String::new();
pulldown_cmark::html::push_html(&mut html, parser);
BlogPost{
name,
html
}
}).collect::<Vec<_>>();
compiled_html
}
pub struct BlogPost {
pub name: String,
pub html: String,
}

174
src/styles/global.css Normal file
View file

@ -0,0 +1,174 @@
/*
The CSS in this style tag is based off of Bear Blog's default CSS.
https://github.com/HermanMartinus/bearblog/blob/297026a877bc2ab2b3bdfbd6b9f7961c350917dd/templates/styles/blog/default.css
License MIT: https://github.com/HermanMartinus/bearblog/blob/master/LICENSE.md
*/
:root {
--accent: #2337ff;
--accent-dark: #000d8a;
--black: 15, 18, 25;
--gray: 96, 115, 159;
--gray-light: 229, 233, 240;
--gray-dark: 34, 41, 57;
--gray-gradient: rgba(var(--gray-light), 50%), #fff;
--box-shadow: 0 2px 6px rgba(var(--gray), 25%), 0 8px 24px rgba(var(--gray), 33%),
0 16px 32px rgba(var(--gray), 33%);
}
@font-face {
font-family: 'Atkinson';
src: url('/fonts/atkinson-regular.woff') format('woff');
font-weight: 400;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: 'Atkinson';
src: url('/fonts/atkinson-bold.woff') format('woff');
font-weight: 700;
font-style: normal;
font-display: swap;
}
body {
font-family: 'Atkinson', sans-serif;
margin: 0;
padding: 0;
min-height: 100vh;
height: 100%;
margin-left: auto;
margin-right: auto;
text-align: left;
background: repeating-linear-gradient(to bottom, #522258 0%, #8c3061, #522258 100%);
display: flex;
flex-direction: column;
word-wrap: break-word;
overflow-wrap: break-word;
color: cyan;
font-size: 20px;
line-height: 1.7;
}
@media screen and (min-width:600px) {
body {
width: 95%;
}
}
@media screen and (max-width:600px){
body{
width: 95%;
}
}
main {
flex: 1;
width: 720px;
max-width: calc(100% - 2em);
margin: auto;
min-height: 100%;
padding: 1em 1em;
margin-bottom: -2em;
}
h1,
h2,
h3,
h4,
h5,
h6 {
margin: 0 0 0.5rem 0;
color: rgb(0,255,159);
line-height: 1.2;
}
h1 {
font-size: 3.052em;
}
h2 {
font-size: 2.441em;
}
h3 {
font-size: 1.953em;
}
h4 {
font-size: 1.563em;
}
h5 {
font-size: 1.25em;
}
strong,
b {
font-weight: 700;
}
a {
color: white;
}
a:hover {
color: black;
}
p {
margin-bottom: 1em;
}
.prose p {
margin-bottom: 2em;
}
textarea {
width: 100%;
font-size: 16px;
}
input {
font-size: 16px;
}
table {
width: 100%;
}
img {
max-width: 100%;
height: auto;
border-radius: 8px;
}
code {
padding: 2px 5px;
background-color: black;
border-radius: 10px;
}
pre {
padding: 1.5em;
border-radius: 8px;
}
pre > code {
all: unset;
}
blockquote {
border-left: 4px solid var(--accent);
padding: 0 0 0 20px;
margin: 0px;
font-size: 1.333em;
}
hr {
border: none;
border-top: 1px solid rgb(var(--gray-light));
}
@media (max-width: 720px) {
body {
font-size: 18px;
}
main {
padding: 1em;
}
}
.sr-only {
border: 0;
padding: 0;
margin: 0;
position: absolute !important;
height: 1px;
width: 1px;
overflow: hidden;
/* IE6, IE7 - a 0 height clip, off to the bottom right of the visible 1px box */
clip: rect(1px 1px 1px 1px);
/* maybe deprecated but we need to support legacy browsers */
clip: rect(1px, 1px, 1px, 1px);
/* modern browsers, clip-path works inwards from each corner */
clip-path: inset(50%);
/* added line to stop words getting smushed together (as they go onto separate lines and some screen readers do not understand line feeds as a space */
white-space: nowrap;
}

View file

@ -1,17 +0,0 @@
<!DOCTYPE html>
<head>
<link rel="stylesheet" href="style.css"/>
<script src="htmx.min.js"></script>
</head>
<body>
<div hx-confirm="Are you sure?">
<button hx-delete="/account">
Delete My Account
</button>
<button hx-put="/account">
Update My Account
</button>
</div>
</body>

1
static/htmx.min.js vendored

File diff suppressed because one or more lines are too long

View file

@ -1,11 +0,0 @@
<!DOCTYPE html>
<head>
<link rel="stylesheet" href="style.css"/>
<script src="htmx.min.js"></script>
</head>
<body>
<ul hx-get="/api/posts" hx-swap="innerHTML" hx-trigger="load">
This is a test
</ul>
</body>

View file

6
tsconfig.json Normal file
View file

@ -0,0 +1,6 @@
{
"extends": "astro/tsconfigs/strict",
"compilerOptions": {
"strictNullChecks": true
}
}