Initialize project with general functionality

This commit is contained in:
thiloho
2025-04-26 09:13:54 +02:00
commit 69be9d8ab7
42 changed files with 6368 additions and 0 deletions

24
.gitignore vendored Normal file
View File

@@ -0,0 +1,24 @@
# build output
dist/
# generated types
.astro/
# dependencies
node_modules/
# logs
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
# environment variables
.env
.env.production
# macOS-specific files
.DS_Store
# jetbrains setting folder
.idea/

11
.prettierrc Normal file
View File

@@ -0,0 +1,11 @@
{
"plugins": ["prettier-plugin-astro"],
"overrides": [
{
"files": "*.astro",
"options": {
"parser": "astro"
}
}
]
}

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

@@ -0,0 +1,4 @@
{
"recommendations": ["astro-build.astro-vscode"],
"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"
}
]
}

47
README.md Normal file
View File

@@ -0,0 +1,47 @@
# Astro Starter Kit: Minimal
```sh
npm create astro@latest -- --template minimal
```
[![Open in StackBlitz](https://developer.stackblitz.com/img/open_in_stackblitz.svg)](https://stackblitz.com/github/withastro/astro/tree/latest/examples/minimal)
[![Open with CodeSandbox](https://assets.codesandbox.io/github/button-edit-lime.svg)](https://codesandbox.io/p/sandbox/github/withastro/astro/tree/latest/examples/minimal)
[![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/withastro/astro?devcontainer_path=.devcontainer/minimal/devcontainer.json)
> 🧑‍🚀 **Seasoned astronaut?** Delete this file. Have fun!
## 🚀 Project Structure
Inside of your Astro project, you'll see the following folders and files:
```text
/
├── public/
├── src/
│ └── pages/
│ └── index.astro
└── package.json
```
Astro looks for `.astro` or `.md` files in the `src/pages/` directory. Each page is exposed as a route based on its file name.
There's nothing special about `src/components/`, but that's where we like to put any Astro/React/Vue/Svelte/Preact components.
Any static assets, like images, can be placed in the `public/` directory.
## 🧞 Commands
All commands are run from the root of the project, from a terminal:
| Command | Action |
| :------------------------ | :----------------------------------------------- |
| `npm install` | Installs dependencies |
| `npm run dev` | Starts local dev server at `localhost:4321` |
| `npm run build` | Build your production site to `./dist/` |
| `npm run preview` | Preview your build locally, before deploying |
| `npm run astro ...` | Run CLI commands like `astro add`, `astro check` |
| `npm run astro -- --help` | Get help using the Astro CLI |
## 👀 Want to learn more?
Feel free to check [our documentation](https://docs.astro.build) or jump into our [Discord server](https://astro.build/chat).

20
astro.config.mjs Normal file
View File

@@ -0,0 +1,20 @@
import { defineConfig } from "astro/config";
import tailwindcss from "@tailwindcss/vite";
import { remarkModifiedTime } from "./remark-modified-time.mjs";
export default defineConfig({
prefetch: {
prefetchAll: true,
},
vite: {
plugins: [tailwindcss()],
},
markdown: {
shikiConfig: {
theme: "github-dark",
},
remarkPlugins: [remarkModifiedTime],
},
});

27
flake.lock generated Normal file
View File

@@ -0,0 +1,27 @@
{
"nodes": {
"nixpkgs": {
"locked": {
"lastModified": 1745391562,
"narHash": "sha256-sPwcCYuiEopaafePqlG826tBhctuJsLx/mhKKM5Fmjo=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "8a2f738d9d1f1d986b5a4cd2fd2061a7127237d7",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixos-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"root": {
"inputs": {
"nixpkgs": "nixpkgs"
}
}
},
"root": "root",
"version": 7
}

41
flake.nix Normal file
View File

@@ -0,0 +1,41 @@
{
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
};
outputs =
{ self, nixpkgs, ... }:
let
allSystems = [
"x86_64-linux"
"aarch64-linux"
"x86_64-darwin"
"aarch64-darwin"
];
forAllSystems = nixpkgs.lib.genAttrs allSystems;
in
{
devShells = forAllSystems (
system:
let
pkgs = nixpkgs.legacyPackages.${system};
in
{
default = pkgs.mkShell {
packages = with pkgs; [
nodejs
];
};
}
);
formatter = forAllSystems (
system:
let
pkgs = nixpkgs.legacyPackages.${system};
in
pkgs.nixfmt-rfc-style
);
};
}

5486
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

22
package.json Normal file
View File

@@ -0,0 +1,22 @@
{
"name": "thiloho-github-io",
"type": "module",
"version": "0.0.1",
"scripts": {
"dev": "astro dev",
"build": "astro build",
"preview": "astro preview",
"astro": "astro",
"format": "prettier . --write"
},
"dependencies": {
"@tailwindcss/vite": "4.1.4",
"astro": "5.7.5",
"tailwindcss": "4.1.4"
},
"devDependencies": {
"@tailwindcss/typography": "0.5.16",
"prettier": "3.5.3",
"prettier-plugin-astro": "0.14.1"
}
}

BIN
public/apple-touch-icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

BIN
public/favicon-96x96.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

BIN
public/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

10
public/favicon.svg Normal file
View File

@@ -0,0 +1,10 @@
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:svgjs="http://svgjs.dev/svgjs" width="174.8" height="131.4"><svg viewBox="0 0 174.8 131.4" xmlns="http://www.w3.org/2000/svg">
<rect x="0" y="0" width="174.8" height="131.4" fill="#e5e5e5" stroke="none" rx="20" ry="20"></rect>
<g transform="translate(20, 30)">
<g id="SvgjsG1082" stroke-linecap="round" fill-rule="evenodd" font-size="9pt" stroke="#525252" stroke-width="0.125mm" fill="#a1a1a1" style="stroke:#525252;stroke-width:0.125mm;fill:#a1a1a1">
<path d="M 92.2 71.4 L 63.1 71.4 L 63.1 67.2 L 64.4 67.2 Q 66.7 67.2 68.55 66.7 Q 70.4 66.2 71.5 64.65 A 5.045 5.045 0 0 0 72.17 63.27 Q 72.587 61.979 72.6 60.121 A 17.219 17.219 0 0 0 72.6 60 L 72.6 11 A 12.54 12.54 0 0 0 72.509 9.435 Q 72.285 7.659 71.509 6.627 A 3.715 3.715 0 0 0 71.45 6.55 Q 70.3 5.1 68.45 4.65 A 15.785 15.785 0 0 0 66.001 4.263 A 19.693 19.693 0 0 0 64.4 4.2 L 63.1 4.2 L 63.1 0 L 92.2 0 L 92.2 4.2 L 90.9 4.2 A 17.148 17.148 0 0 0 88.267 4.395 A 14.51 14.51 0 0 0 86.8 4.7 A 5.523 5.523 0 0 0 84.882 5.604 A 5.171 5.171 0 0 0 83.8 6.7 Q 82.771 8.104 82.705 10.996 A 17.684 17.684 0 0 0 82.7 11.4 L 82.7 31.6 L 115.2 31.6 L 115.2 11.4 A 14.799 14.799 0 0 0 115.113 9.734 Q 114.92 8.032 114.296 6.995 A 3.873 3.873 0 0 0 114.1 6.7 Q 113 5.2 111.15 4.7 A 14.115 14.115 0 0 0 108.926 4.296 A 18.631 18.631 0 0 0 107 4.2 L 105.7 4.2 L 105.7 0 L 134.8 0 L 134.8 4.2 L 133.5 4.2 A 17.148 17.148 0 0 0 130.867 4.395 A 14.51 14.51 0 0 0 129.4 4.7 A 5.523 5.523 0 0 0 127.482 5.604 A 5.171 5.171 0 0 0 126.4 6.7 Q 125.371 8.104 125.305 10.996 A 17.684 17.684 0 0 0 125.3 11.4 L 125.3 60.5 A 11.673 11.673 0 0 0 125.401 62.094 Q 125.512 62.894 125.743 63.543 A 4.544 4.544 0 0 0 126.45 64.85 Q 127.6 66.3 129.45 66.75 A 15.785 15.785 0 0 0 131.899 67.137 A 19.693 19.693 0 0 0 133.5 67.2 L 134.8 67.2 L 134.8 71.4 L 105.7 71.4 L 105.7 67.2 L 107 67.2 Q 109.3 67.2 111.15 66.7 Q 113 66.2 114.1 64.65 A 5.045 5.045 0 0 0 114.77 63.27 Q 115.187 61.979 115.2 60.121 A 17.219 17.219 0 0 0 115.2 60 L 115.2 36.6 L 82.7 36.6 L 82.7 60 A 14.094 14.094 0 0 0 82.787 61.621 Q 82.998 63.435 83.718 64.53 A 4.105 4.105 0 0 0 83.8 64.65 Q 84.9 66.2 86.8 66.7 A 15.233 15.233 0 0 0 89.554 67.151 A 18.189 18.189 0 0 0 90.9 67.2 L 92.2 67.2 L 92.2 71.4 Z M 44.1 71.4 L 13 71.4 L 13 67.2 L 15.3 67.2 A 18.863 18.863 0 0 0 17.664 67.058 A 15.182 15.182 0 0 0 19.35 66.75 Q 21.2 66.3 22.35 64.85 Q 23.179 63.805 23.41 62.007 A 11.832 11.832 0 0 0 23.5 60.5 L 23.5 5 L 13.9 5 Q 11.343 5 9.698 5.828 A 4.991 4.991 0 0 0 7.8 7.45 Q 6.1 9.9 5.7 13.2 L 5.2 17.5 L 0 17.5 L 0.5 0 L 56.8 0 L 57.3 17.5 L 52.1 17.5 L 51.6 13.2 A 13.62 13.62 0 0 0 50.739 9.756 A 11.607 11.607 0 0 0 49.5 7.45 Q 48.152 5.507 45.043 5.105 A 13.611 13.611 0 0 0 43.3 5 L 33.6 5 L 33.6 60 A 14.094 14.094 0 0 0 33.687 61.621 Q 33.898 63.435 34.618 64.53 A 4.105 4.105 0 0 0 34.7 64.65 Q 35.8 66.2 37.7 66.7 A 15.233 15.233 0 0 0 40.454 67.151 A 18.189 18.189 0 0 0 41.8 67.2 L 44.1 67.2 L 44.1 71.4 Z" vector-effect="non-scaling-stroke"></path>
</g>
</g>
</svg><style>@media (prefers-color-scheme: light) { :root { filter: none; } }
@media (prefers-color-scheme: dark) { :root { filter: none; } }
</style></svg>

After

Width:  |  Height:  |  Size: 3.2 KiB

2
public/robots.txt Normal file
View File

@@ -0,0 +1,2 @@
User-agent: *
Allow: /

21
public/site.webmanifest Normal file
View File

@@ -0,0 +1,21 @@
{
"name": "Thilo Hohlt",
"short_name": "THohlt",
"icons": [
{
"src": "/web-app-manifest-192x192.png",
"sizes": "192x192",
"type": "image/png",
"purpose": "maskable"
},
{
"src": "/web-app-manifest-512x512.png",
"sizes": "512x512",
"type": "image/png",
"purpose": "maskable"
}
],
"theme_color": "#ffffff",
"background_color": "#ffffff",
"display": "standalone"
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

9
remark-modified-time.mjs Normal file
View File

@@ -0,0 +1,9 @@
import { execSync } from "child_process";
export const remarkModifiedTime = () => {
return (tree, file) => {
const filepath = file.history[0];
const result = execSync(`git log -1 --pretty="format:%cI" "${filepath}"`);
file.data.astro.frontmatter.lastModified = result.toString();
};
};

17
src/components/Date.astro Normal file
View File

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

View File

@@ -0,0 +1,9 @@
<footer
class="flex flex-col items-center p-4 bg-neutral-100 dark:bg-neutral-900 prose prose-neutral dark:prose-invert max-w-none prose-a:text-blue-800 prose-a:dark:text-blue-300 prose-a:hover:no-underline"
>
<p class="mb-2">&copy; 2025 Thilo Hohlt</p>
<div class="flex gap-4">
<a href="/legal-disclosure">Legal Disclosure</a>
<a href="https://github.com/thiloho">GitHub</a>
</div>
</footer>

49
src/components/Head.astro Normal file
View File

@@ -0,0 +1,49 @@
---
import { ClientRouter } from "astro:transitions";
import "../styles/global.css";
interface Props {
title: string;
metaDescription: string;
}
const { title, metaDescription } = Astro.props;
---
<head>
<meta charset="utf-8" />
<link rel="icon" type="image/png" href="/favicon-96x96.png" sizes="96x96" />
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<link rel="shortcut icon" href="/favicon.ico" />
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png" />
<meta name="apple-mobile-web-app-title" content="THohlt" />
<link rel="manifest" href="/site.webmanifest" />
<meta name="viewport" content="width=device-width" />
<meta name="generator" content={Astro.generator} />
<title>{title}</title>
<meta name="description" content={metaDescription} />
<ClientRouter />
<script is:inline>
const setTheme = () => {
let theme = "light";
if (localStorage.getItem("theme")) {
theme = localStorage.getItem("theme");
} else if (window.matchMedia("(prefers-color-scheme: dark)").matches) {
theme = "dark";
}
if (theme === "light") {
document.documentElement.classList.remove("dark");
} else {
document.documentElement.classList.add("dark");
}
window.localStorage.setItem("theme", theme);
};
setTheme();
document.addEventListener("astro:after-swap", setTheme);
</script>
</head>

View File

@@ -0,0 +1,33 @@
---
import Date from "./Date.astro";
interface Props {
title: string;
pubDate?: Date;
modDate?: Date;
}
const { title, pubDate, modDate } = Astro.props;
---
<header class="bg-white dark:bg-neutral-800">
<div
class="prose prose-neutral dark:prose-invert mx-auto px-4 py-8 border-b border-neutral-200 dark:border-neutral-700 prose-h1:font-bold"
>
{
pubDate ? (
<hgroup>
<h1 class="mb-2">{title}</h1>
<p>
Published: <Date date={pubDate} />
<br />
Last modified:{" "}
{modDate ? <Date date={modDate} /> : <span>No changes yet</span>}
</p>
</hgroup>
) : (
<h1>{title}</h1>
)
}
</div>
</header>

77
src/components/Nav.astro Normal file
View File

@@ -0,0 +1,77 @@
---
import Logo from "../img/TH.svg";
const routes = ["blog"];
---
<nav class="max-w-none bg-neutral-100 dark:bg-neutral-900 sticky top-0 z-10">
<div
class="dark:text-neutral-300 flex items-center justify-between max-w-screen-lg mx-auto ps-4 pe-2"
>
<a href="/" title="Home">
<Logo width={42} height={42} />
</a>
<div class="flex">
{
routes.map((route) => (
<a
class="inline-block p-2 border-b-2 border-transparent hover:bg-neutral-200 hover:border-neutral-400 hover:dark:bg-neutral-700 hover:dark:border-neutral-600"
href={`/${route}`}
>
{route
.split(" ")
.map((word) => word.charAt(0).toUpperCase() + word.slice(1))
.join(" ")}
</a>
))
}
<button
class="theme-toggle p-2 cursor-pointer border-b-2 border-transparent hover:bg-neutral-200 hover:border-neutral-400 hover:dark:bg-neutral-700 hover:dark:border-neutral-600"
title="Toggle dark mode"
>
<!-- Moon -->
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20"
fill="currentColor"
class="size-5 dark:hidden"
>
<path
fill-rule="evenodd"
d="M7.455 2.004a.75.75 0 0 1 .26.77 7 7 0 0 0 9.958 7.967.75.75 0 0 1 1.067.853A8.5 8.5 0 1 1 6.647 1.921a.75.75 0 0 1 .808.083Z"
clip-rule="evenodd"></path>
</svg>
<!-- Sun -->
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20"
fill="currentColor"
class="size-5 hidden dark:block"
>
<path
d="M10 2a.75.75 0 0 1 .75.75v1.5a.75.75 0 0 1-1.5 0v-1.5A.75.75 0 0 1 10 2ZM10 15a.75.75 0 0 1 .75.75v1.5a.75.75 0 0 1-1.5 0v-1.5A.75.75 0 0 1 10 15ZM10 7a3 3 0 1 0 0 6 3 3 0 0 0 0-6ZM15.657 5.404a.75.75 0 1 0-1.06-1.06l-1.061 1.06a.75.75 0 0 0 1.06 1.06l1.06-1.06ZM6.464 14.596a.75.75 0 1 0-1.06-1.06l-1.06 1.06a.75.75 0 0 0 1.06 1.06l1.06-1.06ZM18 10a.75.75 0 0 1-.75.75h-1.5a.75.75 0 0 1 0-1.5h1.5A.75.75 0 0 1 18 10ZM5 10a.75.75 0 0 1-.75.75h-1.5a.75.75 0 0 1 0-1.5h1.5A.75.75 0 0 1 5 10ZM14.596 15.657a.75.75 0 0 0 1.06-1.06l-1.06-1.061a.75.75 0 1 0-1.06 1.06l1.06 1.06ZM5.404 6.464a.75.75 0 0 0 1.06-1.06l-1.06-1.06a.75.75 0 1 0-1.061 1.06l1.06 1.06Z"
></path>
</svg>
</button>
</div>
</div>
</nav>
<script>
const setToggleListener = () => {
const toggleBtn = document.querySelector(".theme-toggle");
toggleBtn?.addEventListener("click", () => {
const element = document.documentElement;
element.classList.toggle("dark");
const isDark = element.classList.contains("dark");
localStorage.setItem("theme", isDark ? "dark" : "light");
});
};
setToggleListener();
document.addEventListener("astro:after-swap", setToggleListener);
</script>

23
src/content.config.ts Normal file
View File

@@ -0,0 +1,23 @@
import { defineCollection, z } from "astro:content";
import { glob } from "astro/loaders";
const index = defineCollection({
loader: glob({ pattern: "**/*.md", base: "./src/content/index" }),
});
const blog = defineCollection({
loader: glob({ pattern: "**/*.md", base: "./src/content/blog" }),
schema: z.object({
id: z.number().positive(),
title: z.string(),
description: z.string(),
pubDate: z.coerce.date(),
modDate: z.coerce.date().optional(),
}),
});
const legal = defineCollection({
loader: glob({ pattern: "**/*.md", base: "./src/content/legal" }),
});
export const collections = { index, blog, legal };

Binary file not shown.

After

Width:  |  Height:  |  Size: 184 KiB

View File

@@ -0,0 +1,136 @@
---
id: 1
title: "Steps to install NixOS on a system with ext4 and LUKS"
description: "A guide to installing NixOS with full disk encryption using LUKS and LVM, showing the complete process from disk partitioning to system configuration"
pubDate: "2025-01-04"
---
## Disk layout
```sh
NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINT
sda 8:0 0 233.8G 0 disk
├─sda1 8:1 0 500M 0 part /boot # Unencrypted EFI partition
└─sda2 8:2 0 233.3G 0 part # Encrypted partition
└─cryptroot 254:0 0 233.3G 0 crypt # LUKS container
├─vg-swap 254:1 0 8G 0 lvm [SWAP] # LVM swap volume
└─vg-root 254:2 0 225.3G 0 lvm / # LVM root volume
```
## Partitioning
```
parted /dev/sda -- mklabel gpt
parted /dev/sda -- mkpart ESP fat32 1MB 512MB
parted /dev/sda -- mkpart primary 512MB 100%
parted /dev/sda -- set 1 esp on
```
## Setting up Encryption
```
cryptsetup luksFormat /dev/sda2
cryptsetup luksOpen /dev/sda2 cryptroot
```
## Setting up LVM
```
pvcreate /dev/mapper/cryptroot
vgcreate vg /dev/mapper/cryptroot
lvcreate -L 8G vg -n swap
lvcreate -l 100%FREE vg -n root
```
## Creating Filesystems
```
mkfs.fat -F 32 -n boot /dev/sda1
mkfs.ext4 -L root /dev/vg/root
mkswap -L swap /dev/vg/swap
```
## Mounting Filesystems
```
mount /dev/vg/root /mnt
mkdir -p /mnt/boot
mount -o umask=077 /dev/sda1 /mnt/boot
swapon /dev/vg/swap
```
## NixOS configuration
```sh
nixos-generate-config --root /mnt
# Get UUID of encrypted partition (needed for configuration)
blkid -s UUID /dev/sda2
```
Edit `/mnt/etc/nixos/configuration.nix`:
```nix
{ config, lib, pkgs, ... }:
{
boot = {
loader = {
systemd-boot.enable = true;
efi.canTouchEfiVariables = true;
};
# Encryption configuration
initrd = {
luks.devices = {
cryptroot = {
device = "/dev/disk/by-uuid/UUID-OF-SDA2"; # Replace with your UUID
};
};
};
};
# ...
}
```
## Installation
```
nixos-install
reboot
```
## How it works
1. **UEFI Phase**
- The UEFI firmware loads systemd-boot from the unencrypted /boot partition
- systemd-boot loads the NixOS kernel and initrd
2. **Early boot**
- Kernel starts and loads initrd
- initrd asks for LUKS passphrase
- after entering correct passphrase, /dev/sda2 will be decrypted
3. **LVM setup**
- LVM volumes are available after decryption
- System can now access root and swap volumes
4. **System start**
- Root file system is mounted
- Control handed over to systemd
- regular boot process continues
## Change of encryption password
To make this step as easy as possible, I recommend using [GNOME Disks](https://apps.gnome.org/DiskUtility).
![Linux disk management window showing a 2TB Samsung SSD with partition details and management options](./change-of-encryption-password.png)

View File

@@ -0,0 +1,39 @@
---
id: 2
title: "Privacy-focused operating systems"
description: "Good choices for privacy-focused operating systems for desktop and mobile phones."
pubDate: "2025-01-16"
---
## Introduction
Privacy on the Internet is a fundamental right and there are many steps you can take to protect your personal information. Various companies and services have developed sophisticated strategies to collect as much information from you as possible. A fundamental aspect of many people's daily lives is the operating system they run on both their desktop computer and their mobile phone, and while the most popular ones offer great usability and features, they are usually not in favor of your privacy.
Below are the options that, after a few years of testing, I have found to be the most usable and at the same time the most privacy and security oriented.
## Desktop
I would recommend using one of the more popular GNU/Linux distributions here, as they have many contributors and are well maintained. Some options that I find great are:
- NixOS
- ArchLinux
- Debian
- Fedora
You should stick with one of them and use it for at least a few months or years, or commit to it completely. Most of them are the same anyway, and with Flatpak there is already a universally usable packaging system that contains most of the relevant software you might need if it is not included in the distribution's package repository.
There are other great options, such as [FreeBSD](https://www.freebsd.org) and [OpenBSD](https://www.openbsd.org), but these may not be as easy to use as GNU/Linux, and there may be problems with hardware compatibility and software availability.
## Mobile
Your main choice here should probably be [GrapheneOS](https://grapheneos.org).
> GrapheneOS is an open source, privacy and security-focused Android operating system that runs on selected Google Pixel devices, including smartphones, tablets and foldables.
As mentioned in the quote, note that you need a [supported Google Pixel device](https://grapheneos.org/faq#supported-devices) to use GrapheneOS and I would not recommend using any other privacy focused or hardened mobile operating system as they do not come close to its usability while maintaining these aspects.
## Conclusion
Aside from operating systems, there are of course many different components that are needed to meet one's digital needs. If you are interested in digital privacy, I would advise you to check out the [Privacy Guide's Recommendations](https://www.privacyguides.org/en/tools), there are probably several things you can replace with more private alternatives without much effort. Having a VPN connection that is always enabled on all of your devices is also a good utility to have, and it is not that expensive.
Some services require personal information for verification or payment purposes, but just know that you can greatly limit the information you disclose about yourself by making the right choices.

View File

@@ -0,0 +1,25 @@
## About
I am a software developer from Germany who is passionate about building high quality websites and web applications. I value privacy on the Internet and prefer to use free/libre open source software whenever possible. I switched to GNU/Linux for desktop use on all my machines a few years ago and it has been my daily driver ever since.
## Build
| Component | Selection |
| ------------ | ----------------------------------------------------------------------------- |
| CPU | AMD Ryzen 7 7700X 4.5 GHz 8-Core Processor |
| CPU Cooler | Noctua NH-U12A chromax.black 60.09 CFM CPU Cooler |
| Motherboard | ASRock B650M-HDV/M.2 Micro ATX AM5 Motherboard |
| Memory | G.Skill Trident Z5 Neo 32 GB (2 x 16 GB) DDR5-6000 CL30 Memory |
| Storage | Samsung 980 Pro 2 TB M.2-2280 PCIe 4.0 X4 NVME Solid State Drive |
| Video Card | XFX Speedster MERC 319 Radeon RX 6950 XT 16 GB Video Card |
| Case | Fractal Design Pop Mini Air MicroATX Mid Tower Case |
| Power Supply | SeaSonic FOCUS PX 850 W 80+ Platinum Certified Fully Modular ATX Power Supply |
## Software
- [NixOS](https://nixos.org)
- [GNOME](https://www.gnome.org)
- [VSCodium](https://vscodium.com)
- [Firefox Developer Edition](https://www.mozilla.org/en-US/firefox/developer)
- [Tuta Mail](https://tuta.com)
- [Mullvad VPN](https://mullvad.net)

View File

@@ -0,0 +1,12 @@
Information according to [&sect; 5 DDG](https://gesetz-digitale-dienste.de/5-ddg):
Thilo Hohlt
c/o IP-Management #3723 \
Ludwig-Erhard-Str. 18 \
20459 Hamburg
Contact:
E-Mail: [contact@thilohohlt.com](mailto:contact@thilohohlt.com) \
Phone: [+49 171 7599950](tel:+491717599950)

1
src/img/TH.svg Normal file
View File

@@ -0,0 +1 @@
<svg width="134.8" height="71.4" viewBox="0 0 134.8 71.4" xmlns="http://www.w3.org/2000/svg"><g id="svgGroup" stroke-linecap="round" fill-rule="evenodd" font-size="9pt" stroke="#525252" stroke-width="0.125mm" fill="#a1a1a1" style="stroke:#525252;stroke-width:0.125mm;fill:#a1a1a1"><path d="M 92.2 71.4 L 63.1 71.4 L 63.1 67.2 L 64.4 67.2 Q 66.7 67.2 68.55 66.7 Q 70.4 66.2 71.5 64.65 A 5.045 5.045 0 0 0 72.17 63.27 Q 72.587 61.979 72.6 60.121 A 17.219 17.219 0 0 0 72.6 60 L 72.6 11 A 12.54 12.54 0 0 0 72.509 9.435 Q 72.285 7.659 71.509 6.627 A 3.715 3.715 0 0 0 71.45 6.55 Q 70.3 5.1 68.45 4.65 A 15.785 15.785 0 0 0 66.001 4.263 A 19.693 19.693 0 0 0 64.4 4.2 L 63.1 4.2 L 63.1 0 L 92.2 0 L 92.2 4.2 L 90.9 4.2 A 17.148 17.148 0 0 0 88.267 4.395 A 14.51 14.51 0 0 0 86.8 4.7 A 5.523 5.523 0 0 0 84.882 5.604 A 5.171 5.171 0 0 0 83.8 6.7 Q 82.771 8.104 82.705 10.996 A 17.684 17.684 0 0 0 82.7 11.4 L 82.7 31.6 L 115.2 31.6 L 115.2 11.4 A 14.799 14.799 0 0 0 115.113 9.734 Q 114.92 8.032 114.296 6.995 A 3.873 3.873 0 0 0 114.1 6.7 Q 113 5.2 111.15 4.7 A 14.115 14.115 0 0 0 108.926 4.296 A 18.631 18.631 0 0 0 107 4.2 L 105.7 4.2 L 105.7 0 L 134.8 0 L 134.8 4.2 L 133.5 4.2 A 17.148 17.148 0 0 0 130.867 4.395 A 14.51 14.51 0 0 0 129.4 4.7 A 5.523 5.523 0 0 0 127.482 5.604 A 5.171 5.171 0 0 0 126.4 6.7 Q 125.371 8.104 125.305 10.996 A 17.684 17.684 0 0 0 125.3 11.4 L 125.3 60.5 A 11.673 11.673 0 0 0 125.401 62.094 Q 125.512 62.894 125.743 63.543 A 4.544 4.544 0 0 0 126.45 64.85 Q 127.6 66.3 129.45 66.75 A 15.785 15.785 0 0 0 131.899 67.137 A 19.693 19.693 0 0 0 133.5 67.2 L 134.8 67.2 L 134.8 71.4 L 105.7 71.4 L 105.7 67.2 L 107 67.2 Q 109.3 67.2 111.15 66.7 Q 113 66.2 114.1 64.65 A 5.045 5.045 0 0 0 114.77 63.27 Q 115.187 61.979 115.2 60.121 A 17.219 17.219 0 0 0 115.2 60 L 115.2 36.6 L 82.7 36.6 L 82.7 60 A 14.094 14.094 0 0 0 82.787 61.621 Q 82.998 63.435 83.718 64.53 A 4.105 4.105 0 0 0 83.8 64.65 Q 84.9 66.2 86.8 66.7 A 15.233 15.233 0 0 0 89.554 67.151 A 18.189 18.189 0 0 0 90.9 67.2 L 92.2 67.2 L 92.2 71.4 Z M 44.1 71.4 L 13 71.4 L 13 67.2 L 15.3 67.2 A 18.863 18.863 0 0 0 17.664 67.058 A 15.182 15.182 0 0 0 19.35 66.75 Q 21.2 66.3 22.35 64.85 Q 23.179 63.805 23.41 62.007 A 11.832 11.832 0 0 0 23.5 60.5 L 23.5 5 L 13.9 5 Q 11.343 5 9.698 5.828 A 4.991 4.991 0 0 0 7.8 7.45 Q 6.1 9.9 5.7 13.2 L 5.2 17.5 L 0 17.5 L 0.5 0 L 56.8 0 L 57.3 17.5 L 52.1 17.5 L 51.6 13.2 A 13.62 13.62 0 0 0 50.739 9.756 A 11.607 11.607 0 0 0 49.5 7.45 Q 48.152 5.507 45.043 5.105 A 13.611 13.611 0 0 0 43.3 5 L 33.6 5 L 33.6 60 A 14.094 14.094 0 0 0 33.687 61.621 Q 33.898 63.435 34.618 64.53 A 4.105 4.105 0 0 0 34.7 64.65 Q 35.8 66.2 37.7 66.7 A 15.233 15.233 0 0 0 40.454 67.151 A 18.189 18.189 0 0 0 41.8 67.2 L 44.1 67.2 L 44.1 71.4 Z" vector-effect="non-scaling-stroke"/></g></svg>

After

Width:  |  Height:  |  Size: 2.7 KiB

View File

@@ -0,0 +1,3 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M2.25 4.5C2.25 4.08579 2.58579 3.75 3 3.75H17.25C17.6642 3.75 18 4.08579 18 4.5C18 4.91421 17.6642 5.25 17.25 5.25H3C2.58579 5.25 2.25 4.91421 2.25 4.5ZM2.25 9C2.25 8.58579 2.58579 8.25 3 8.25H12.75C13.1642 8.25 13.5 8.58579 13.5 9C13.5 9.41421 13.1642 9.75 12.75 9.75H3C2.58579 9.75 2.25 9.41421 2.25 9ZM17.25 8.25C17.6642 8.25 18 8.58579 18 9V19.1893L20.4697 16.7197C20.7626 16.4268 21.2374 16.4268 21.5303 16.7197C21.8232 17.0126 21.8232 17.4874 21.5303 17.7803L17.7803 21.5303C17.4874 21.8232 17.0126 21.8232 16.7197 21.5303L12.9697 17.7803C12.6768 17.4874 12.6768 17.0126 12.9697 16.7197C13.2626 16.4268 13.7374 16.4268 14.0303 16.7197L16.5 19.1893V9C16.5 8.58579 16.8358 8.25 17.25 8.25ZM2.25 13.5C2.25 13.0858 2.58579 12.75 3 12.75H12.75C13.1642 12.75 13.5 13.0858 13.5 13.5C13.5 13.9142 13.1642 14.25 12.75 14.25H3C2.58579 14.25 2.25 13.9142 2.25 13.5Z" fill="#0F172A"/>
</svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

3
src/img/icons/moon.svg Normal file
View File

@@ -0,0 +1,3 @@
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M7.45519 2.00395C7.68518 2.18765 7.78646 2.4889 7.71414 2.77423C7.57443 3.32547 7.5 3.90348 7.5 4.49996C7.5 8.36595 10.634 11.5 14.5 11.5C15.6435 11.5 16.721 11.2263 17.6724 10.7417C17.9347 10.608 18.2509 10.6402 18.4809 10.8239C18.7109 11.0076 18.8122 11.3089 18.7399 11.5942C17.8069 15.2755 14.4725 18 10.5 18C5.80558 18 2 14.1944 2 9.49996C2 6.19139 3.89048 3.32567 6.64671 1.92168C6.909 1.78807 7.22519 1.82025 7.45519 2.00395Z" fill="#0F172A"/>
</svg>

After

Width:  |  Height:  |  Size: 602 B

11
src/img/icons/sun.svg Normal file
View File

@@ -0,0 +1,11 @@
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M10 2C10.4142 2 10.75 2.33579 10.75 2.75V4.25C10.75 4.66421 10.4142 5 10 5C9.58579 5 9.25 4.66421 9.25 4.25V2.75C9.25 2.33579 9.58579 2 10 2Z" fill="#0F172A"/>
<path d="M10 15C10.4142 15 10.75 15.3358 10.75 15.75V17.25C10.75 17.6642 10.4142 18 10 18C9.58579 18 9.25 17.6642 9.25 17.25V15.75C9.25 15.3358 9.58579 15 10 15Z" fill="#0F172A"/>
<path d="M10 7C8.34315 7 7 8.34315 7 10C7 11.6569 8.34315 13 10 13C11.6569 13 13 11.6569 13 10C13 8.34315 11.6569 7 10 7Z" fill="#0F172A"/>
<path d="M15.6568 5.40386C15.9497 5.11096 15.9497 4.63609 15.6568 4.3432C15.3639 4.0503 14.889 4.0503 14.5961 4.3432L13.5355 5.40386C13.2426 5.69675 13.2426 6.17162 13.5355 6.46452C13.8284 6.75741 14.3032 6.75741 14.5961 6.46452L15.6568 5.40386Z" fill="#0F172A"/>
<path d="M6.46441 14.5962C6.7573 14.3034 6.7573 13.8285 6.46441 13.5356C6.17151 13.2427 5.69664 13.2427 5.40375 13.5356L4.34309 14.5962C4.05019 14.8891 4.05019 15.364 4.34309 15.6569C4.63598 15.9498 5.11085 15.9498 5.40375 15.6569L6.46441 14.5962Z" fill="#0F172A"/>
<path d="M18 10C18 10.4142 17.6642 10.75 17.25 10.75H15.75C15.3358 10.75 15 10.4142 15 10C15 9.58579 15.3358 9.25 15.75 9.25H17.25C17.6642 9.25 18 9.58579 18 10Z" fill="#0F172A"/>
<path d="M5 10C5 10.4142 4.66421 10.75 4.25 10.75H2.75C2.33579 10.75 2 10.4142 2 10C2 9.58579 2.33579 9.25 2.75 9.25H4.25C4.66421 9.25 5 9.58579 5 10Z" fill="#0F172A"/>
<path d="M14.596 15.6568C14.8889 15.9497 15.3638 15.9497 15.6567 15.6568C15.9496 15.3639 15.9496 14.889 15.6567 14.5961L14.596 13.5355C14.3031 13.2426 13.8283 13.2426 13.5354 13.5355C13.2425 13.8284 13.2425 14.3032 13.5354 14.5961L14.596 15.6568Z" fill="#0F172A"/>
<path d="M5.40363 6.46441C5.69653 6.7573 6.1714 6.7573 6.46429 6.46441C6.75719 6.17151 6.75719 5.69664 6.46429 5.40375L5.40363 4.34309C5.11074 4.05019 4.63587 4.05019 4.34297 4.34309C4.05008 4.63598 4.05008 5.11085 4.34297 5.40375L5.40363 6.46441Z" fill="#0F172A"/>
</svg>

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

@@ -0,0 +1,31 @@
---
import Head from "../components/Head.astro";
import Nav from "../components/Nav.astro";
import Header from "../components/Header.astro";
import Footer from "../components/Footer.astro";
interface Props {
title: string;
metaDescription: string;
pubDate?: Date;
modDate?: Date;
}
const { title, metaDescription, pubDate, modDate } = Astro.props;
---
<html lang="en" class="light">
<Head {title} {metaDescription} />
<body class="min-h-screen flex flex-col">
<Nav />
<Header {title} {pubDate} {modDate} />
<main class="flex-1 bg-white dark:bg-neutral-800">
<div
class={`relative prose prose-neutral dark:prose-invert mx-auto px-4 ${pubDate ? "pt-0" : "pt-8"} pb-16 prose-headings:scroll-mt-16 prose-h1:font-bold prose-pre:!bg-neutral-700 prose-a:text-blue-800 prose-a:dark:text-blue-300 prose-a:hover:no-underline`}
>
<slot />
</div>
</main>
<Footer />
</body>
</html>

View File

@@ -0,0 +1,87 @@
---
import PageLayout from "../../layouts/PageLayout.astro";
import { getCollection, getEntry, render } from "astro:content";
export const getStaticPaths = async () => {
const allArticles = await getCollection("blog");
return allArticles.map((article) => ({
params: { slug: article.id },
props: { article },
}));
};
const { slug } = Astro.params;
const article = await getEntry("blog", slug);
if (!article) {
throw new Error();
}
const { Content, headings, remarkPluginFrontmatter } = await render(article);
---
<PageLayout
title={article.data.title}
metaDescription="Blog"
pubDate={article.data.pubDate}
modDate={remarkPluginFrontmatter.lastModified}
>
<details class="toc sticky top-0 z-20">
<summary
title="Table of contents"
class="flex mx-auto w-fit cursor-pointer list-none p-2 border-b-2 border-transparent hover:bg-neutral-200 hover:border-neutral-400 hover:dark:bg-neutral-700 hover:dark:border-neutral-600"
>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="currentColor"
class="size-6"
>
<path
fill-rule="evenodd"
d="M2.25 4.5A.75.75 0 0 1 3 3.75h14.25a.75.75 0 0 1 0 1.5H3a.75.75 0 0 1-.75-.75Zm0 4.5A.75.75 0 0 1 3 8.25h9.75a.75.75 0 0 1 0 1.5H3A.75.75 0 0 1 2.25 9Zm15-.75A.75.75 0 0 1 18 9v10.19l2.47-2.47a.75.75 0 1 1 1.06 1.06l-3.75 3.75a.75.75 0 0 1-1.06 0l-3.75-3.75a.75.75 0 1 1 1.06-1.06l2.47 2.47V9a.75.75 0 0 1 .75-.75Zm-15 5.25a.75.75 0 0 1 .75-.75h9.75a.75.75 0 0 1 0 1.5H3a.75.75 0 0 1-.75-.75Z"
clip-rule="evenodd"></path>
</svg>
</summary>
<div
class="not-prose border border-neutral-400 dark:border-neutral-600 bg-white dark:bg-neutral-800 p-2 max-h-[calc(100vh-4rem)] overflow-y-scroll"
>
<p class="text-center">
<strong class="text-sm">Table of Contents</strong>
</p>
<ul>
{
headings
.filter(({ depth }) => depth === 2)
.map((heading) => (
<li>
<a
class="text-center text-blue-800 dark:text-blue-300 block py-1 px-2 hover:underline"
href={`#${heading.slug}`}
aria-labelledby={`Section: ${heading.slug}`}
>
{heading.text}
</a>
</li>
))
}
</ul>
</div>
</details>
<Content />
</PageLayout>
<script>
const setAnchorListener = () => {
const tocLinks = document.querySelectorAll(".toc a");
for (const link of tocLinks) {
link.addEventListener("click", () => {
document.querySelector(".toc")?.removeAttribute("open");
});
}
};
setAnchorListener();
document.addEventListener("astro:after-swap", setAnchorListener);
</script>

View File

@@ -0,0 +1,29 @@
---
import PageLayout from "../../layouts/PageLayout.astro";
import { getCollection } from "astro:content";
import Date from "../../components/Date.astro";
const allArticles = await getCollection("blog");
const sortedArticles = allArticles.sort((a, b) => {
return b.data.pubDate.valueOf() - a.data.pubDate.valueOf();
});
---
<PageLayout title="Blog" metaDescription="Blog">
<ul>
{
sortedArticles.map((article) => (
<li class="gap-1">
<Date date={article.data.pubDate} />
<span>&raquo;</span>
<a
class="text-blue-800 hover:no-underline"
href={`/blog/${article.id}`}
>
{article.data.title}
</a>
</li>
))
}
</ul>
</PageLayout>

7
src/pages/contact.astro Normal file
View File

@@ -0,0 +1,7 @@
---
import PageLayout from "../layouts/PageLayout.astro";
---
<PageLayout title="Contact" metaDescription="Contact">
<p>Example content</p>
</PageLayout>

16
src/pages/index.astro Normal file
View File

@@ -0,0 +1,16 @@
---
import PageLayout from "../layouts/PageLayout.astro";
import { getEntry, render } from "astro:content";
const indexContent = await getEntry("index", "index");
if (!indexContent) {
throw new Error();
}
const { Content } = await render(indexContent);
---
<PageLayout title="Thilo Hohlt" metaDescription="Thilo Hohlt">
<Content />
</PageLayout>

View File

@@ -0,0 +1,16 @@
---
import PageLayout from "../layouts/PageLayout.astro";
import { getEntry, render } from "astro:content";
const legalContent = await getEntry("legal", "legal");
if (!legalContent) {
throw new Error();
}
const { Content } = await render(legalContent);
---
<PageLayout title="Legal Disclosure" metaDescription="Legal Disclosure">
<Content />
</PageLayout>

4
src/styles/global.css Normal file
View File

@@ -0,0 +1,4 @@
@import "tailwindcss";
@plugin "@tailwindcss/typography";
@custom-variant dark (&:where(.dark, .dark *));

5
tsconfig.json Normal file
View File

@@ -0,0 +1,5 @@
{
"extends": "astro/tsconfigs/strict",
"include": [".astro/types.d.ts", "**/*"],
"exclude": ["dist"]
}