Compare commits

...
32 Commits
Author SHA1 Message Date
V 9a4f1a87de Added some fluxcd notes
Publish new notes / build-quartz (push) Successful in 1m0s
Publish new notes / deploy (push) Successful in 7s
2025-12-17 19:00:12 +00:00
V bde604e75b fixin
Publish new notes / build-quartz (push) Successful in 57s
Publish new notes / deploy (push) Successful in 7s
2025-12-14 22:57:07 +00:00
V 7cc0be2724 fixed quartz build - missing content dir
Publish new notes / build-quartz (push) Failing after 41s
Publish new notes / deploy (push) Has been skipped
2025-12-14 22:53:17 +00:00
V 1a001af9d7 fixed binary arch mismatch...ooops
Publish new notes / build-quartz (push) Successful in 59s
Publish new notes / deploy (push) Successful in 17s
2025-12-14 21:51:50 +00:00
V ab21705170 fixin workflow paths
Publish new notes / build-quartz (push) Successful in 1m2s
Publish new notes / deploy (push) Failing after 47s
2025-12-14 21:48:24 +00:00
V 6361a78052 added missing dep
Publish new notes / build-quartz (push) Failing after 1m2s
Publish new notes / deploy (push) Has been skipped
2025-12-14 21:46:01 +00:00
V 8ce2b8f834 changed to regular image
Publish new notes / build-quartz (push) Failing after 52s
Publish new notes / deploy (push) Has been skipped
2025-12-14 21:44:10 +00:00
Vlad 8b8ead003c switched to bun:
Publish new notes / build-quartz (push) Failing after 1m2s
Publish new notes / deploy (push) Has been skipped
2025-12-14 18:03:42 +00:00
V 9b55e1521a added some PVC migration notes
Publish new notes / build-quartz (push) Successful in 3m41s
Publish new notes / deploy (push) Successful in 4m6s
2025-12-14 14:16:02 +00:00
V 2325431cb6 Sync
Publish new notes / build-quartz (push) Successful in 7m31s
Publish new notes / deploy (push) Successful in 1m4s
2025-11-18 09:54:24 +00:00
VR dfab1fa9dd Fixin
Publish new notes / build-quartz (push) Successful in 1m46s
Publish new notes / deploy (push) Successful in 27s
2025-06-28 14:34:54 +01:00
VR 942334035c fixin
Publish new notes / build-quartz (push) Failing after 3m28s
Publish new notes / deploy (push) Has been skipped
2025-06-28 14:26:27 +01:00
VR bfa3f0720d fixin
Publish new notes / build-quartz (push) Successful in 1m36s
Publish new notes / deploy (push) Successful in 3m50s
2025-06-28 14:06:16 +01:00
VR 67adaaed92 Figured out why images were not loading...
Publish new notes / build-quartz (push) Failing after 19s
Publish new notes / deploy (push) Has been skipped
2025-06-28 14:02:41 +01:00
VR 1d8a8c25fd Fixed date
Publish new notes / build-quartz (push) Successful in 4m52s
Publish new notes / deploy (push) Successful in 3m36s
2025-06-28 12:11:19 +01:00
VR 2d1a7495cc fixin
Publish new notes / build-quartz (push) Successful in 1m39s
Publish new notes / deploy (push) Successful in 23s
2025-06-23 22:47:45 +01:00
VR abe5f110f6 Testin
Publish new notes / build-quartz (push) Successful in 1m54s
Publish new notes / deploy (push) Successful in 25s
2025-06-23 22:31:50 +01:00
Vlad 79b1494658 Fixed image links
Publish new notes / build-quartz (push) Failing after 1m51s
Publish new notes / deploy (push) Has been skipped
2025-06-23 12:48:08 +01:00
Vlad a12aec37b9 Updated nginx conf
Publish new notes / build-quartz (push) Successful in 1m36s
Publish new notes / deploy (push) Successful in 23s
2025-06-23 12:20:19 +01:00
VR 78a5a2b180 1st published item - fixes
Publish new notes / build-quartz (push) Successful in 1m55s
Publish new notes / deploy (push) Successful in 30s
2025-06-22 23:17:26 +01:00
VR adbce7ceb6 1st published item
Publish new notes / build-quartz (push) Successful in 2m0s
Publish new notes / deploy (push) Successful in 28s
2025-06-22 23:09:03 +01:00
VR e3b8ba7140 New notes - WIP
Gitea Actions Demo / build-quartz (push) Successful in 1m36s
Gitea Actions Demo / deploy (push) Successful in 24s
2025-06-22 22:33:29 +01:00
VR 7712bd713d Testin'
Gitea Actions Demo / build-quartz (push) Successful in 1m57s
Gitea Actions Demo / deploy (push) Successful in 23s
2025-06-22 16:52:06 +01:00
VR 481605e2c0 Testin' 2025-06-22 16:47:29 +01:00
VR 636af566e3 Testin' 2025-06-22 16:44:11 +01:00
VR f7e96733ce fixin 2025-06-22 11:12:19 +01:00
VR 465ef0a158 fixin 2025-06-22 11:08:52 +01:00
VR 161afa0a4c fixin 2025-06-22 11:01:05 +01:00
VR 1db6850468 fixin 2025-06-22 10:51:27 +01:00
VR 1eee92d24b fixin 2025-06-22 10:46:37 +01:00
VR b1edeeca3c fixin 2025-06-22 10:33:12 +01:00
VR 9a8ebb67aa fixin 2025-06-22 10:16:02 +01:00
16 changed files with 913 additions and 28 deletions
+19 -13
View File
@@ -1,26 +1,30 @@
name: Gitea Actions Demo
run-name: ${{ gitea.actor }} is testing out Gitea Actions 🚀
name: Publish new notes
run-name: Build in Quartz and push to Nginx
on: [push]
jobs:
build-quartz:
runs-on: ubuntu-latest
container:
image: node:24.2
image: oven/bun:latest
steps:
- name: Install Git
run: |
apt-get update
apt-get install -y git
- name: Grab local files
uses: actions/checkout@v4
- name: Clone local copy of Quartz
run: git clone https://k3gtpi.jumpingcrab.com/vlad/quartz-clone.git
- name: Clone Quartz
run: git clone https://github.com/jackyzha0/quartz.git
- name: Copy notes to content directory
run: cp ./notes/* quartz-clone/content
run: mkdir -p quartz/content && cp -r ./vlads-notes/* quartz/content
- name: Build Quartz
run: cd quartz-clone && npm i && npx quartz create && npx quartz build
run: cd quartz && bun install && bun run quartz create && bun run quartz build
- name: Upload artifact
uses: actions/upload-artifact@v3
with:
name: content
path: quartz-clone/public
path: quartz/public
deploy:
runs-on: ubuntu-latest
needs: build-quartz
@@ -31,10 +35,12 @@ jobs:
name: content
path: ./content
- name: Install kubectl
run: curl -LO https://dl.k8s.io/release/v1.33.0/bin/linux/arm64/kubectl && chmod +x kubectl && mv kubectl /usr/bin
run: curl -LO https://dl.k8s.io/release/v1.33.0/bin/linux/amd64/kubectl && chmod +x kubectl && mv kubectl /usr/bin
- name: Set up cluster access
run: ls -al ~ && mkdir ~/.kube && echo "${{ secrets.K8S_CONF }}" > ~/.kube/config && cat ~/.kube/config
run: mkdir ~/.kube && echo "${{ secrets.K8S_CONF }}" > ~/.kube/config
- name: Get target pods's name
run: echo "TARGET_POD=$(kubectl get pods -n my-stuff -l app=digital-garden -o json | jq -r .items[0].metadata.name)" >> "$GITHBU_ENV"
- name: Copy contents to PVC
run: kubectl cp content my-stuff/$TARGET_POD:/www/data/
run: echo "TARGET_POD=$(kubectl get pods -n my-stuff -l app=digital-garden -o json | jq -r .items[0].metadata.name)" >> "$GITHUB_ENV"
- name: Copy contents to pod temp folder (due to permission issues)
run: kubectl cp content my-stuff/$TARGET_POD:/tmp
- name: Change permissions and move files to WWW directory
run: kubectl exec -i -n my-stuff $TARGET_POD -- bash -c "chown -R 1000:1000 /tmp/content && rm -rf /www/data/* && mv /tmp/content/* /www/data"
+1
View File
@@ -0,0 +1 @@
.obsidian/
+1
View File
@@ -0,0 +1 @@
This repo stores all the markdown and image files used to build the Quartz site.
-10
View File
@@ -1,10 +0,0 @@
---
title: Example Title
draft: false
tags:
- example-tag
---
1st note
The rest of your content lives here. You can use **Markdown** here :)
-5
View File
@@ -1,5 +0,0 @@
---
title: 1st note
---
The **1st** note!
+5
View File
@@ -0,0 +1,5 @@
{
"newLinkFormat": "absolute",
"useMarkdownLinks": true,
"alwaysUpdateLinks": true
}
+1
View File
@@ -0,0 +1 @@
{}
+31
View File
@@ -0,0 +1,31 @@
{
"file-explorer": true,
"global-search": true,
"switcher": true,
"graph": true,
"backlink": true,
"canvas": true,
"outgoing-link": true,
"tag-pane": true,
"properties": false,
"page-preview": true,
"daily-notes": true,
"templates": true,
"note-composer": true,
"command-palette": true,
"slash-command": false,
"editor-status": true,
"bookmarks": true,
"markdown-importer": false,
"zk-prefixer": false,
"random-note": false,
"outline": true,
"word-count": true,
"slides": false,
"audio-recorder": false,
"workspaces": false,
"file-recovery": true,
"publish": false,
"sync": true,
"webviewer": false
}
+181
View File
@@ -0,0 +1,181 @@
{
"main": {
"id": "69b48d20eddd5d27",
"type": "split",
"children": [
{
"id": "b3cbadb65948797d",
"type": "tabs",
"children": [
{
"id": "17a11d1621382a65",
"type": "leaf",
"state": {
"type": "markdown",
"state": {
"file": "0000-how-this-was-built.md",
"mode": "source",
"source": false
},
"icon": "lucide-file",
"title": "0000-how-this-was-built"
}
}
]
}
],
"direction": "vertical"
},
"left": {
"id": "371e529f0bd09bef",
"type": "split",
"children": [
{
"id": "aa6ce42d2ba4c6d9",
"type": "tabs",
"children": [
{
"id": "24693a4da486c556",
"type": "leaf",
"state": {
"type": "file-explorer",
"state": {
"sortOrder": "alphabetical",
"autoReveal": false
},
"icon": "lucide-folder-closed",
"title": "Files"
}
},
{
"id": "0de12dda217872ec",
"type": "leaf",
"state": {
"type": "search",
"state": {
"query": "",
"matchingCase": false,
"explainSearch": false,
"collapseAll": false,
"extraContext": false,
"sortOrder": "alphabetical"
},
"icon": "lucide-search",
"title": "Search"
}
},
{
"id": "761be25a753c2b9f",
"type": "leaf",
"state": {
"type": "bookmarks",
"state": {},
"icon": "lucide-bookmark",
"title": "Bookmarks"
}
}
]
}
],
"direction": "horizontal",
"width": 300
},
"right": {
"id": "0b7622c040c7528e",
"type": "split",
"children": [
{
"id": "03c6b66733d3f1bf",
"type": "tabs",
"children": [
{
"id": "98ba5c9bbb9fcca1",
"type": "leaf",
"state": {
"type": "backlink",
"state": {
"file": "notes/index.md",
"collapseAll": false,
"extraContext": false,
"sortOrder": "alphabetical",
"showSearch": false,
"searchQuery": "",
"backlinkCollapsed": false,
"unlinkedCollapsed": true
},
"icon": "links-coming-in",
"title": "Backlinks for index"
}
},
{
"id": "14a7943d9d272e96",
"type": "leaf",
"state": {
"type": "outgoing-link",
"state": {
"file": "notes/index.md",
"linksCollapsed": false,
"unlinkedCollapsed": true
},
"icon": "links-going-out",
"title": "Outgoing links from index"
}
},
{
"id": "c93bb8473637a3d3",
"type": "leaf",
"state": {
"type": "tag",
"state": {
"sortOrder": "frequency",
"useHierarchy": true,
"showSearch": false,
"searchQuery": ""
},
"icon": "lucide-tags",
"title": "Tags"
}
},
{
"id": "50ded0e5f80093cd",
"type": "leaf",
"state": {
"type": "outline",
"state": {
"file": "notes/index.md",
"followCursor": false,
"showSearch": false,
"searchQuery": ""
},
"icon": "lucide-list",
"title": "Outline of index"
}
}
]
}
],
"direction": "horizontal",
"width": 300,
"collapsed": true
},
"left-ribbon": {
"hiddenItems": {
"switcher:Open quick switcher": false,
"graph:Open graph view": false,
"canvas:Create new canvas": false,
"daily-notes:Open today's daily note": false,
"templates:Insert template": false,
"command-palette:Open command palette": false
}
},
"active": "17a11d1621382a65",
"lastOpenFiles": [
"index.md",
"0000-how-this-was-built.md",
"images/0000",
"images/0000/gitea_secret_setup.png",
"images",
"Pasted image 20250622224303.png",
"here.md"
]
}
+301
View File
@@ -0,0 +1,301 @@
---
title: Building this digital garden
tags:
- how-to
- cicd
- quartz
- github-actions
draft: false
date: 2025-06-22
---
### Why?
Well, in my work I deal with networks, systems, servers, services, tools, etc... every time I got stuck with an issue or a broken config or anything really, a quick Google search led me to some random person's web page which either gave me a solution or at least pointed me in the right direction.
So as a thank you to all those who spend their time and energy sharing their experience and knowledge, I decided to do the same and set up a place where I can write about what I do, learn, discover, break (and hopefully fix) and deal with.
### How?
I found out recently about this class of tools called [static site generators (SSGs)](https://en.wikipedia.org/wiki/Static_site_generator) - they can take text written in various formats and use it to generate static websites. Since I wanted for some time to have a place to share my notes online, I felt SSGs were just the thing I needed so I started looking around. There are many great options but out of all of them [Quartz](https://quartz.jzhao.xyz/) felt like the one - built with Typescript, super lightweight and comes with a clean and simple interface.
At the same time, I've been messing with K8s in my home lab for a few years and I also had to get familiar with Github Actions for a work project (I've mostly used Gitlab CICD and custom scripts for CI stuff) so it all combined nicely into an opportunity to learn something new!
Anyway, let's get into it - the plan was to serve the site with Nginx, running as a pod in K8s. The content would be written in Markdown (edited with Obsidian - more on that later) and pushed to my Gitea local instance where a Github Actions workflow would build the site files with Quartz and upload them to the Nginx pod's PVC.
Summarised, I had to:
- [ ] Set up a Nginx pod (PVC, cert, service, ingress, configmap and deployment)
- [ ] Create a notes repo and the Github Actions workflow for auto deployment
- [ ] Troubleshoot the whole things until it works (always the fun bit)
##### Nginx pod
Nothing fancy, manifests below and explanations after.
PVC:
```
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
    namespace: my-stuff
    name: digital-garden-data-pvc
spec:
    storageClassName: nfs-client
    accessModes:
        - ReadWriteOnce
    resources:
        requests:
            storage: 10Gi
```
Certificate:
```
---
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: digital-garden-prod-tls
namespace: my-stuff
spec:
secretName: digital-garden-prod-tls
# 90d
duration: 2160h
# 15d
renewBefore: 360h
subject:
organizations:
- RPI
commonName: vlads-notes.jumpingcrab.com
isCA: false
privateKey:
algorithm: RSA
encoding: PKCS1
size: 2048
usages:
- server auth
- client auth
dnsNames:
- vlads-notes.jumpingcrab.com
issuerRef:
name: letsencrypt-prod
kind: ClusterIssuer
group: cert-manager.io
```
Service:
```
---
apiVersion: v1
kind: Service
metadata:
labels:
app: digital-garden
name: digital-garden-svc
namespace: my-stuff
spec:
type: ClusterIP
ports:
- port: 443
protocol: TCP
targetPort: 80
selector:
app: digital-garden
```
Ingress:
```
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
namespace: my-stuff
name: digital-garden-ingress
annotations:
cert-manager.io/cluster-issuer: letsencrypt-prod
nginx.ingress.kubernetes.io/proxy-body-size: 512m
spec:
ingressClassName: nginx
tls:
- hosts:
- vlads-notes.jumpingcrab.com
# Name of the certifciate (see kubectl get certificate -A)
secretName: digital-garden-prod-tls
rules:
- host: vlads-notes.jumpingcrab.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
# Mapping to the service (see kubectl get services -n nextcloud)
name: digital-garden-svc
port:
number: 443
```
ConfigMap:
```
---
apiVersion: v1
kind: ConfigMap
metadata:
name: digital-garden-nginx-conf
namespace: my-stuff
data:
nginx.conf: |
user nginx;
worker_processes 3;
error_log /var/log/nginx/error.log;
events {
worker_connections 10240;
}
http {
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
access_log /var/log/nginx/access.log main;
limit_req_zone $binary_remote_addr zone=req_limit_per_ip:1m rate=1r/s;
server {
listen 80;
server_name vlads-notes.jumpingcrab.com;
root /www/data;
index index.html;
error_page 404 /404.html;
server_tokens off;
include mime.types;
add_header X-Content-Type-Options nosniff;
add_header X-Frame-Options DENY;
add_header X-XSS-Protection "1; mode=block";
add_header Referrer-Policy "no-referrer-when-downgrade";
location ~ /\. {
deny all;
access_log off;
log_not_found off;
}
location / {
limit_req zone=req_limit_per_ip burst=5 nodelay;
try_files $uri $uri.html $uri/ =404;
}
}
}
```
Deployment:
```
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: digital-garden
namespace: my-stuff
labels:
app: digital-garden
spec:
replicas: 1
selector:
matchLabels:
app: digital-garden
template:
metadata:
labels:
app: digital-garden
name: digital-garden
spec:
containers:
- name: digital-garden
image: nginx:1.28.0
imagePullPolicy: IfNotPresent
ports:
- containerPort: 80
volumeMounts:
- name: digital-garden-volume
mountPath: /www/data
- name: nginx-config
mountPath: /etc/nginx/nginx.conf
subPath: nginx.conf
resources: {}
volumes:
- name: digital-garden-volume
persistentVolumeClaim:
claimName: digital-garden-data-pvc
- name: nginx-config
configMap:
name: digital-garden-nginx-conf
```
I needed a PVC to persist the data if the pod/node crashed - this one is hosted on an SSD attached to an NFS server that is exposed via a StorageClass to the cluster. The certificate is managed via [CertManager](https://cert-manager.io/) and is issued by Let'sEncrypt - always good to use TLS! The service simply ties the pod to the ingress, not much to say here. The ingress uses the Nginx admission controller and is configured with the Let'sEncrypt cert to enable TLS. The config map has a minimal Nginx config file that is mounted to the pod under "/etc/nginx/nginx.conf". Lastly, the deployment which ties it all together - not much to say, it's just one Nginx replica.
Good practice says that I should add some resource limits and requests, but I'll leave that for later with the rest of the tech debt...
#### Notes repo and Github Actions workflow
I'm running a local Gitea instance, so I just created a new [repo](https://k3gtpi.jumpingcrab.com/vlad/vlads-notes) and saved the K8s cluster config as a repo secret called "K8S_CONF" (use secrets instead of plain variables as the latter can be exposed in the action's logs)
![gitea_secret_setup](images/0000/gitea_secret_setup.png)
I then created the ".gitea/workflows" directories and placed the workflow YAML file (publish.yaml) within. The workflow is split into two jobs:
- 1st job
- checks out the files from the repo
- clones Quartz (from a repo clone hosted locally) to the working directory
- copies notes from "./notes" directory to the "./quartz-clone" and triggers the Quartz build
- uploads the files created by Quartz as artifacts for the next job
- 2nd job
- copies these artifacts locally
- installs the kubectl client
- copies the K8s config from the secret into the required path
- gets the Nginx's pod name
- deletes old files from the Nginx root directory and copies the new files built by Quartz
Contents:
```
name: Publish new notes
run-name: Build in Quartz and push to Nginx
on: [push]
jobs:
build-quartz:
runs-on: ubuntu-latest
container:
image: node:24.2
steps:
- name: Grab local files
uses: actions/checkout@v4
- name: Clone local copy of Quartz
run: git clone https://k3gtpi.jumpingcrab.com/vlad/quartz-clone.git
- name: Copy notes to content directory
run: cp ./notes/* quartz-clone/content
- name: Build Quartz
run: cd quartz-clone && npm i && npx quartz create && npx quartz build
- name: Upload artifact
uses: actions/upload-artifact@v3
with:
name: content
path: quartz-clone/public
deploy:
runs-on: ubuntu-latest
needs: build-quartz
steps:
- name: Get artifacts
uses: actions/download-artifact@v3
with:
name: content
path: ./content
- name: Install kubectl
run: curl -LO https://dl.k8s.io/release/v1.33.0/bin/linux/arm64/kubectl && chmod +x kubectl && mv kubectl /usr/bin
- name: Set up cluster access
run: mkdir ~/.kube && echo "${{ secrets.K8S_CONF }}" > ~/.kube/config
- name: Get target pods's name
run: echo "TARGET_POD=$(kubectl get pods -n my-stuff -l app=digital-garden -o json | jq -r .items[0].metadata.name)" >> "$GITHUB_ENV"
- name: Copy contents to pod temp folder (due to permission issues)
run: kubectl cp content my-stuff/$TARGET_POD:/tmp
- name: Change permissions and move files to WWW directory
run: kubectl exec -i -n my-stuff $TARGET_POD -- bash -c "chown -R 1000:1000 /tmp/content && rm -rf /www/data/* && mv /tmp/content/* /www/data"
```
#### Troubleshooting
Compared to Gitlab CI, I think Actions is simpler to use but it has its quirks - the main difficulty I found was around sharing artifacts between jobs. The latest (v4) upload-artifact and download-artifact actions are not supported for some reason, so I had to rely on the deprecated v3 version.
Aside from that I encountered some issues with "kubectl cp" command as it could not preserve the original file permissions when copying the Quartz files into the PVC - I had to copy them to a temp location and change their ownership to UID 1000 and GID 1000 as the NFS PVC did not allow files owned by root (UID 0, GID 0).
@@ -0,0 +1,51 @@
---
title: Working with K8s config
tags:
- learning-notes
- k8s
draft: true
date: 2025-11-15
---
### Intro
In Kubernetes, users and other applications interact with clusters via the API provided by the **kube-apiserver** component. During the bootstrap process, a config file is generated for the user which stores the connectivity details and credentials needed to interact with the cluster via its API.
In order to use the **kubectl** utility to interact with the cluster, the users must make this generated config file available in "~/.kube/config" so that the binary can authenticate to the cluster.
By default, the config file generated during the cluster bootstrapping contains credentials for accessing the cluster with full admin privileges. Good practice dictates that this level of access should not be granted to users that do not explicitly require it, hence giving this file to everybody who needs access to the cluster is a bad idea!
It is therefor important to understand the structure of this file and how to create versions of it for other users with the right level of access.
Let's look at how to set up new users with their own TLS credentials and the appropriate levels of access
References:
- https://kubernetes-tutorial.schoolofdevops.com/configuring_authentication_and_authorization/#exercise
- https://kubernetes.io/docs/reference/access-authn-authz/rbac/
### Prerequisites
You will need access to a K8s cluster and a Linux host with the **kubectl** client available and configured with full admin cluster access (using the default cluster config generated during bootstrapping).
### Setting up a new user for the cluster
Each user needs their own TLS certificate and key with which to authenticate to the cluster API. You can use **openssl** to create them:
```
cd ~/.kube
mkdir users
cd users
# User 1 will be a cluster admin
openssl genrsa -out user1.key 2048
openssl req -new -key user1.key -out user1.csr -subj "/CN=user1/O=admins/O=learning.lab"
# User 2 will only have access to one namespace
openssl genrsa -out user2.key 2048
openssl req -new -key user2.key -out user2.csr -subj "/CN=user2/O=namespace/O=learning.lab"
```
Copy the cluster's TLS client CA certificate and key from the master node to the local directory. For vanilla K8s they can be found in "/etc/kubernetes/pki"; for K3s they are in "/var/lib/rancher/k3s/server/tls",
```
cd /var/lib/rancher/k3s/server/tls
cp client-ca.crt client-ca.crt ~/.kube/users
```
+57
View File
@@ -0,0 +1,57 @@
---
title: Command references
tags:
- learning-notes
- linux
draft: false
date: 2025-11-15
---
## Debian (bash)
### OpenSSL
Inspect SSL certificates:
```
# Display all info
openssl x509 -text -in certificate.crt
# Display only subject and validity info
openssl x509 -subject -dates -in certificate.crt
```
Generate SSL key and certificate request:
```
# Generate a key
openssl genrsa -out user.key 2048
# Generate a CSR with a minimal config
openssl req -new -key user.key -out user.csr -subj "/CN=user/O=admins/O=learning.lab"
# Generate a CSR with config from a file
openssl req -new -key user.key -out user.csr -config ssl_conf.conf
```
### VIM
Search and replace:
```
# All instance in current line
:s/original/replaced/g
# All instances in all lines
:%s/original/replaced/g
# All instances in all lines, asks for confirmation
:%s/original/replaced/gc
```
## K8s utilities
### Kubeseal
Retrieve the public certificate of the controller:
```
kubeseal --controler-namespace <NAMESPACE> --controller-name sealed-secrets --fetch-cert >mycert.pem
```
+215
View File
@@ -0,0 +1,215 @@
---
title: Various K8s ops to remember
tags:
- k8s
- learning-notes
- guides
draft: false
date: 2025-12-14
---
### Enabling Talos system extensions
I wanted to deploy Longhorn on a Talos cluster and I didn't realise that at the time I didn't include the required extensions:
- siderolabs/iscsi-tools
- siderolabs/util-linux-tools
After a bit of struggling, I found the fix on Reddit lol... create a patch file:
```
# patch.yaml
customization:
systemExtensions:
officialExtensions:
- siderolabs/iscsi-tools
- siderolabs/util-linux-tools
```
Use this patch file in a POST request to the Talos image builder to get the schematic ID for an image with these extensions:
```
curl -X POST --data-binary @patch.yaml https://factory.talos.dev/schematics
# OUTPUT:
{"id":"613e1592b2da41ae5e265e8789429f22e121aab91cb4deb6bc3c0b6262961245"}
```
Then upgrade the Talos cluster using this new schematic (make sure to match the version of Talos software that is currently running - in my case it was 1.11.5):
```
talosctl upgrade --preserve --image factory.talos.dev/installer/613e1592b2da41ae5e265e8789429f22e121aab91cb4deb6bc3c0b6262961245:v1.11.5 --nodes=192.168.0.5,192.168.0.6,192.168.0.7,192.168.0.8
# OUTPUT:
watching nodes: [192.168.0.5 192.168.0.6 192.168.0.7 192.168.0.8]
* 192.168.0.5: post check passed
* 192.168.0.6: post check passed
* 192.168.0.7: post check passed
* 192.168.0.8: post check passed
talosctl get extensions -n 192.168.0.5
# OUTPUT:
NODE NAMESPACE TYPE ID VERSION NAME VERSION
192.168.0.5 runtime ExtensionStatus 0 1 iscsi-tools v0.2.0
192.168.0.5 runtime ExtensionStatus 1 1 util-linux-tools 2.41.1
192.168.0.5 runtime ExtensionStatus 2 1 schematic 613e1592b2da41ae5e265e8789429f22e121aab91cb4deb6bc3c0b6262961245
```
### Migrating data between PVCs
Let's say I have a pod using a PVC that relies on a PV that uses NFS as a storage backend. At some point I want to make this pod use a quicker backend like Longhorn, without losing any data in the process. One option to achieve this is to us a temporary Pod to replicate the data into the new PVC. Here are the steps along with an example of migrating the storage of a Gitea Actions runner:
1. create a new PV using the new storage backend + a new PVC that binds to it
The storage backend of the new PVC is Longhorn:
```
# pvc.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: gitea-run-1-pvc
namespace: dev-stuff
spec:
storageClassName: longhorn-static
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 5Gi
```
Apply the manifest and check that the PVC is is "bound" state:
```
kubectl apply -f pvc.yaml
# OUTPUT:
persistentvolumeclaim/gitea-run-1-pvc created
kubectl get pvc -n dev-stuff
# OUTPUT:
gitea-run-1-pvc Bound pvc-17af2514-7307-4d74-b343-33c31607ad12 5Gi RWO longhorn-static <unset> 56s
```
2. scale down the Deployment to 0 replicas
```
kubectl scale deployment -n dev-stuff gitea-runner-1 --replicas=0
# OUTPUT:
deployment.apps/gitea-runner-1 scaled
```
3. create a temporary Pod that attaches both the old and the new PVC
The image of choice is "busybox", but any other image that has the basic linux utilities available will do:
```
# temp.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: data-mover
name: data-mover
namespace: dev-stuff
spec:
replicas: 1
selector:
matchLabels:
app: data-mover
template:
metadata:
labels:
app: data-mover
spec:
containers:
- args:
- -c
- while true; do ping localhost; sleep 60;done
command:
- /bin/sh
image: busybox:latest
name: data-mover
volumeMounts:
- mountPath: /source
name: source
- mountPath: /destination
name: destination
restartPolicy: Always
volumes:
- name: source
persistentVolumeClaim:
claimName: gitea-runner-1-pvc
- name: destination
persistentVolumeClaim:
claimName: gitea-run-1-pvc
```
Apply the manifest and check that the pod is running:
```
kubectl get pods -n dev-stuff
# OUTPUT:
NAME READY STATUS RESTARTS AGE
data-mover-5ff6cfcbfc-9cd8f 1/1 Running 0 31s
```
3. copy all the data across
Exec into the newly created pod and copy the contents of "/source" into "/destination":
```
kubectl exec -it -n dev-stuff data-mover-5ff6cfcbfc-9cd8f -- sh
copy -r source/* destination/*
exit
```
4. remove the "data-mover" deployment
```
kuebctl delete -f temp.yaml
```
5. modify the Deployment to mount the new PVC
```
kubectl edit deployment -n dev-stuff gitea-runner-1
# Change the line that references the PVC to use the new one:
volumes: - name: runner-data persistentVolumeClaim: claimName: gitea-run-1-pvc # This was previously "gitea-runner-1-pvc"
# Then save and close. The manifest should be applied with the new values
# OUTPUT:
deployment.apps/gitea-runner-1 edited
```
5. scale up the Deployment and check that everything works as expected
```
kubectl scale deployment -n dev-stuff gitea-runner-1 --replicas=1
# OUTPUT:
deployment.apps/gitea-runner-1 scaled
kubectl get pods -n dev-stuff
# OUTPUT:
NAME READY STATUS RESTARTS AGE
gitea-runner-1-754f74b9c4-vlqrf 1/1 Running 0 90s
```
+42
View File
@@ -0,0 +1,42 @@
---
title: FluxCD notes
tags:
- cicd
- fluxcd
- k8s
- learning-notes
date: 2025-12-17
---
## Testing Kustomization rendering before pushing changes
This got me so many times... making some changes to the manifests and pushing them without a worry only to then see error when checking the Kustomization status!
Anyway, the "kubectl" client can render any Kustomization and alert you of any YAML shenanigans or if any dependencies are missing in advance. In a repo directory with this structure:
```
.
├── base
│ ├── dashboards
│ │ ├── cluster.json
│ │ └── control-plane.json
│ ├── kustomization.yaml
│ └── podmonitor.yaml
├── dev
│ └── kustomization.yaml
└── prod
└── kustomization.yaml
```
, you can run from the root of the directory:
```
kubectl kustomize ./prod
# Or
kubectl kustomize ./dev
```
Which will render the manifests to stdout. In my case, the "base/dashboards" directory was misspelled (it was "dashboard") which led to the FluxCD Kustomization failing.
Ideally these changes would go via a feature branch and they'd be validated in CICD, but for now this will do.
Binary file not shown.

After

Width:  |  Height:  |  Size: 166 KiB

+8
View File
@@ -0,0 +1,8 @@
---
title: Hi! Welcome to my notes! Or "digital garden"?
draft: false
tags:
- home-page
date: 2025-06-21
---
Bear with me, things are still getting sorted in the background. Have a look [[0000-how-this-was-built|here]] for a brief description of how this digital garden was built!