1
0

Compare commits

..

13 Commits

Author SHA1 Message Date
8c9da1d33f Fix openQA Helm Chart installation
All checks were successful
Build / build (push) Successful in 19s
2025-06-19 14:39:36 +02:00
c311b67b04 Installing openQA on Kubernetes with Helm Charts
All checks were successful
Build / build (push) Successful in 19s
2025-06-04 13:40:32 +02:00
93c77ebd48 Improve Gitea Action example
All checks were successful
Build / build (push) Successful in 20s
2025-06-03 15:01:46 +02:00
ad3ba54726 Enable automatic build and deployment
All checks were successful
Build / build (push) Successful in 14s
2025-06-03 13:43:13 +02:00
3085297dbe Fix minor typo 2025-06-03 12:51:03 +02:00
9c15c4d4d4 Obfuscate e-mail address 2025-05-19 17:07:54 +02:00
e3dcd4c6b0 Enable smooth scrolling 2025-05-15 21:07:31 +02:00
0f6cc9b1fc Fix indentation 2025-05-15 21:03:52 +02:00
e7d373f886 Display TOC in sidebar 2025-05-15 21:02:19 +02:00
b851a127c7 Improve search
This new approach makes sure the input field is hidden on browsers which
ignore `display:none`, such as w3m or lynx.
2025-05-14 14:51:49 +02:00
a2b355477f Add profile picture 2025-05-06 11:33:53 +02:00
6b245f540d Better mobile nav 2025-05-05 19:02:42 +02:00
85f0e72676 Remove mobile-specific navigation options 2025-05-05 18:53:50 +02:00
14 changed files with 289 additions and 57 deletions

View File

@ -0,0 +1,38 @@
name: Build
on:
push:
branches:
- master
env:
ZOLA_VERSION: ${{ vars.ZOLA_VERSION }}
HOST: ${{ vars.HOST }}
HOST_DIR: ${{ vars.HOST_DIR }}
SSH_USERNAME: ${{ secrets.SSH_USERNAME }}
SSH_PRIVATE_KEY: ${{ secrets.SSH_PRIVATE_KEY }}
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Install Zola
run: |
wget https://github.com/getzola/zola/releases/download/v${ZOLA_VERSION}/zola-v${ZOLA_VERSION}-x86_64-unknown-linux-gnu.tar.gz
tar -xvzf *.tar.gz
- name: Build
run: ./zola build
- name: Deploy
run: |
apt update -y && apt-get install -y --no-install-recommends rsync
eval "$(ssh-agent -s)"
ssh-add - <<< "${SSH_PRIVATE_KEY}"
mkdir -p ~/.ssh/
ssh-keyscan -H ${HOST} >> ~/.ssh/known_hosts
rsync -r --delete-after public/* "${SSH_USERNAME}@${HOST}:${HOST_DIR}"

View File

@ -21,7 +21,6 @@ nav = [
{title = "Index", path = "/"},
{title = "About", path = "/about/"},
{title = "Search", path = "/posts/"},
{title = "Categories", path = "/categories/", mobile_only = true}
]
latest_posts_count = 3
repository_url = "https://git.0x45.cz/em/em.0x45.cz"

View File

@ -4,13 +4,15 @@ title = "About"
notoc = true
+++
This is my personal website mainly used for technical articles about problems and projects I am working on.
<img src="profile-picture.jpg" alt="Profile Picture" class="profile-picture">
This is my personal website mainly used for technical articles about problems and projects I&nbsp;am working on.
It is generated to static HTML via [Zola](https://www.getzola.org/).
## Contact me
- [em@0x45.cz](mailto:em@0x45.cz)
- [&#101;&#109;&#64;&#48;&#120;&#52;&#53;&#46;&#99;&#122;](&#109;&#97;&#105;&#108;&#116;&#111;&#58;&#101;&#109;&#64;&#48;&#120;&#52;&#53;&#46;&#99;&#122;)
- [@irungentoo](https://t.me/irungentoo) on Telegram
- PGP: [0x453A7AE1754BFED2](https://keys.openpgp.org/vks/v1/by-fingerprint/3B08B7B5F00CCB0370EE3E71453A7AE1754BFED2)
@ -34,7 +36,7 @@ Master's thesis focuses on implementing a custom ESP32 development board expanda
## Projects
Some of my project are published directly at this website or in Git:
Some of my projects are published directly at this website or in Git:
- [git.0x45.cz/em](https://git.0x45.cz/em)
- [git.microlab.space/em](https://git.microlab.space/em)

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

View File

@ -0,0 +1,127 @@
+++
title = "Installing openQA on Kubernetes with Helm Charts"
date = 2025-06-04
[taxonomies]
categories = ["Linux"]
[extra]
author = "Emil Miler"
+++
Recently, I experimented with Kubernetes by installing [openQA](https://open.qa/) using Helm Charts. This article is a simple guide on how to do the same locally on your computer, with minimal effort.
<!-- more -->
You need a working cluster. There are several single-node management tools that let you run a Kubernetes cluster locally on your machine, such as [k3d](https://k3d.io/), [kind](https://kind.sigs.k8s.io/), or [minikube](https://minikube.sigs.k8s.io/). They provide a cluster entirely isolated within a container.
I will be using minikube.
## Installation of Tools
Install minikube -- or any other management tool -- along with *helm*. For example, on Void Linux:
```
xbps-install -S minikube kubernetes-helm
```
You also have to have a running container engine, such as [Podman](https://podman.io/) or [Docker](https://www.docker.com/). Configuring a container engine is outside the scope of this article.
## Preparing the Cluster
Since I am using minikube, I can simply start it like so:
```
minikube start
```
This might take a while during the initial setup. If you want, you can also run a web-based dashboard for better visualization and debugging.
```
minikube dashboard
```
The dashboard will open in your default browser.
## Downloading openQA Charts
The official charts are available in the [openQA source code repository](https://github.com/os-autoinst/openQA), which can be cloned with the following command:
```
git clone https://github.com/os-autoinst/openQA.git
```
If you have GitHub setup properly with SSH, you might use this instead:
```
git clone git@github.com:os-autoinst/openQA.git
```
The charts themselves are stored in `container/helm`. You can change your working directory to that location:
```
cd openQA/container/helm
```
### Chart Structure
The `charts` folder contains several different charts:
- `openqa` -- Parent chart.
- `webui` -- Web interface and API for openQA.
- `worker` -- openQA worker that performs the actual test execution.
We will be working with the parent chart `openqa`. Since it references the other charts, they will be handled automatically by the parent.
## Installing openQA
We then have to update dependencies and install openQA:
```
$ helm dependency update charts/openqa/
$ helm install openqa charts/openqa/
```
We can also list installed releases to confirm that openQA was deployed:
```
$ helm list
NAME NAMESPACE REVISION UPDATED STATUS CHART APP VERSION
openqa default 1 2025-06-19 12:39:08.150021755 +0200 CEST deployed openqa-0.1.0
```
The minikube dashboard also lists all running pods:
![Minikube Dashboard, Pods](minikube-dashboard-pods.png)
### Accessing openQA WebUI
Since openQA is confined to its own network within minikube, it is not directly accessible from the host system. Below are the services running inside minikube:
```
$ kubectl get services
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
db ClusterIP 10.103.192.20 <none> 5432/TCP 6m34s
db-hl ClusterIP None <none> 5432/TCP 6m34s
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 174m
openqa-webui ClusterIP 10.104.24.71 <none> 9526/TCP,9527/TCP,9528/TCP,9529/TCP 6m34s
```
First, we need to create a proxy within the minikube network:
```
minikube tunnel
```
The openQA WebUI is running at `10.104.24.71` on port `9526` and is now accessible directly from the browser.
![openQA Dashboard](openqa-dashboard.png)

Binary file not shown.

After

Width:  |  Height:  |  Size: 102 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

View File

@ -23,6 +23,8 @@ The Act Runner reads the private key from a secret and uses it for SSH authentic
Zola is also installed by pulling a built release tar and extracting the binary. I have tried using the official Zola container, but it just would not work properly.
Here is an example from this very website [deployment action](https://git.0x45.cz/em/em.0x45.cz/src/branch/master/.gitea/workflows/deploy.yaml):
```yaml
name: Build
@ -32,9 +34,9 @@ on:
- master
env:
ZOLA_VERSION: "0.18.0"
HOST: ${{ secrets.SSH_HOSTNAME }}
HOST_DIR: ${{ secrets.SSH_TARGET_DIR }}
ZOLA_VERSION: ${{ vars.ZOLA_VERSION }}
HOST: ${{ vars.HOST }}
HOST_DIR: ${{ vars.HOST_DIR }}
SSH_USERNAME: ${{ secrets.SSH_USERNAME }}
SSH_PRIVATE_KEY: ${{ secrets.SSH_PRIVATE_KEY }}
@ -64,6 +66,8 @@ jobs:
rsync -r --delete-after public/* "${SSH_USERNAME}@${HOST}:${HOST_DIR}"
```
The example cinfiguration uses both secrets and vars from within Gitea. These have to be set trough the web UI under *Settings > Actions*.
## Webserver configuration
The server needs a new user with write access to the website root directory. I still call it drone for the sake of not having to redo my server configuration.
@ -88,7 +92,13 @@ Public key has to be added to `~/.ssh/authorized_keys` of the "drone" user.
| | |
|-------------------|------------------------|
| `SSH_HOSTNAME` | Server hostname |
| `SSH_TARGET_DIR` | Website root directory |
| `SSH_USERNAME` | In our case "drone" |
| `SSH_PRIVATE_KEY` | Plaintext private key |
## Vars
| | |
|-------------------|------------------------|
| `ZOLA_VERSION` | Version of Zola |
| `HOST` | Server hostname |
| `TARGET_DIR` | Website root directory |

View File

@ -28,6 +28,8 @@
color: #000;
}
html { scroll-behavior: smooth }
.wrap {
max-width: 55rem;
margin: 0 auto;
@ -81,6 +83,9 @@ nav {
align-self: start;
top: 2em;
margin-bottom: 4rem;
max-height: calc(100vh - 2em);
box-sizing: border-box;
overflow-y: auto;
span.title {
display: block;
@ -94,7 +99,8 @@ nav {
li a {
color: #000;
display: block;
padding: 0 .5em;
line-height: 1;
padding: .25em .5em;
&:hover {
color: #fff;
@ -102,7 +108,35 @@ nav {
text-decoration: none;
}
}
li.mobile-only { display: none }
}
ul.table-of-contents li {
a {
color: #e1140a;
position: relative;
&::before, &::after {
content: "";
background-color: #bbb;
position: absolute;
left: 0;
top: 0;
}
&::before {
height: 2px;
width: .5em;
}
&::after {
width: 2px;
height: .5em;
}
&:hover { color: #fff }
&:hover::before, &:hover::after { display: none }
}
li {
padding: 0 .5em;
&>a::before, &>a::after { display: none }
}
}
}
@ -189,7 +223,6 @@ main {
}
input.search {
display: none; // Hide for non-js browsers
box-sizing: border-box;
width: 100%;
border: 1px solid #ccc;
@ -212,23 +245,36 @@ a {
&:hover { text-decoration: underline }
}
.profile-picture {
float: right;
width: 15rem;
margin-left: 3em;
}
@media screen and (max-width: 50rem) {
.grid { grid-template-columns: auto }
header { margin-bottom: 0 }
nav {
position: initial;
margin-bottom: 1rem;
ul { display: none }
ul.main {
display: flex;
justify-content: center;
flex-wrap: wrap;
border: 1px solid #43494c;
padding: .25rem 0;
}
ul li.mobile-only { display: list-item }
span.title { display: none }
}
header, nav { margin-bottom: 3rem }
.profile-picture {
width: 10rem;
margin-left: 1em;
}
}
@media (prefers-color-scheme: dark) {
@ -242,14 +288,19 @@ a {
color: inherit;
}
nav ul li a {
nav ul li a, nav ul.table-of-contents li a {
color: inherit;
&:hover {
color: #131516;
background-color: #cdcdcd;
}
}
nav ul.table-of-contents li a {
color: #ff6e67;
&::before, &::after {
background-color: #383838;
}
}
main {
.info, input.search { border-color: #383838 }

View File

@ -1,15 +1,23 @@
function filter_title(str)
function filter_title(query, articles)
{
if (str.length == 0) {
articles.forEach(article => article.style.display = "block");
} else {
articles.forEach(article => article.style.display = article.dataset.title.indexOf(str.toLowerCase()) === -1 ? "none" : "block");
const search = query.trim().toLowerCase();
if (search.length === 0) {
articles.forEach(article => article.style.display = 'block');
return;
}
articles.forEach(article => {
const title = article.dataset.title || '';
article.style.display = title.includes(search) ? 'block' : 'none';
});
}
const search = document.querySelector("input[type='search']");
const form = document.querySelector("form");
let articles = Array.from(document.querySelectorAll("article"));
window.addEventListener('DOMContentLoaded', () => {
document.querySelector('.search-wrapper').innerHTML =
'<input class="search" type="search" placeholder="Search">';
search.addEventListener("input", () => filter_title(search.value));
window.addEventListener("load", () => search.style.display = "inline-block");
const search = document.querySelector("input[type='search']");
const articles = Array.from(document.querySelectorAll('article'));
search.addEventListener('input', () => filter_title(search.value, articles));
});

View File

@ -26,13 +26,13 @@
<span class="title">Navigation</span>
<ul class="main">
{% for item in config.extra.nav %}
<li
{% if item.mobile_only %}
class="mobile-only"
{% endif %}
><a href="{{ get_url(path=item.path, trailing_slash=true) }}">{{ item.title }}</a></li>
<li><a href="{{ get_url(path=item.path, trailing_slash=true) }}">{{ item.title }}</a></li>
{% endfor %}
</ul>
{% if page.toc and not page.extra.notoc %}
<span class="title">Table of Contents</span>
{{ macros::toc() }}
{% endif %}
<span class="title">Categories</span>
{{ macros::list_taxonomy(kind="categories") }}
</nav>

View File

@ -48,8 +48,7 @@
{% endmacro %}
{% macro toc() %}
{% if page.toc and not page.extra.notoc %}
<ul>
<ul class="table-of-contents">
{% for h1 in page.toc %}
<li>
<a href="{{ h1.permalink | safe }}">{{ h1.title }}</a>
@ -65,7 +64,6 @@
</li>
{% endfor %}
</ul>
{% endif %}
{% endmacro %}
{% macro list_taxonomy(kind, page=false, prepend="") %}

View File

@ -6,7 +6,6 @@
{% block content %}
<h1>{{ page.title }}</h1>
{{ macros::toc() }}
{{ page.content | safe }}
{{ macros::page_info(page=page) }}
{{ macros::page_updates(page=page) }}

View File

@ -6,7 +6,7 @@
{% block content %}
<h1>{{ section.title }}</h1>
<input class="search" type="search" placeholder="Search">
<div class="search-wrapper"></div>
{{ macros::list_posts() }}
{% endblock content %}