Initial commit: mushroom, zetikettes

With basic config to have TLS certs with ACME.
It works!

TODO:
- gitea
- bar-jupyter
This commit is contained in:
2026-03-02 06:48:46 +00:00
commit 0cf7c9b908
20 changed files with 756 additions and 0 deletions

22
jenova/ingress.yaml Normal file
View File

@@ -0,0 +1,22 @@
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: jenova.ponteilla.net
annotations:
traefik.ingress.kubernetes.io/router.tls.certresolver: "default"
spec:
rules:
- host: jenova.ponteilla.net
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: jenova
port:
number: 80
tls:
- hosts:
- jenova.ponteilla.net
secretName: jenova-tls

13
jenova/nginx-config.yaml Normal file
View File

@@ -0,0 +1,13 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: nginx-config
data:
default.conf: |
server {
listen 80;
root /usr/share/nginx/html;
location /zetikettes {
return 301 https://zetikettes.jenova.ponteilla.net;
}
}

81
jenova/web.yaml Normal file
View File

@@ -0,0 +1,81 @@
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: web
name: web
spec:
replicas: 1
selector:
matchLabels:
app: web
strategy: {}
template:
metadata:
labels:
app: web
spec:
containers:
- image: nginx
name: nginx
ports:
- containerPort: 80
volumeMounts:
- name: index
mountPath: /usr/share/nginx/html/index.html
subPath: index.html
- name: nginx-config
mountPath: /etc/nginx/conf.d/default.conf
subPath: default.conf
volumes:
- name: index
configMap:
name: jenova-index-html
- name: nginx-config
configMap:
name: nginx-config
---
apiVersion: v1
kind: ConfigMap
metadata:
name: jenova-index-html
data:
index.html: |
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>P</title>
</head>
<body>
<div align="center">
<div>
<pre>
.----------------.
| .--------------. |
| | ______ | |
| | |_ __ \ | |
| | | |__) | | |
| | | ___/ | |
| | _| |_ | |
| | |_____| | |
| | | |
| '--------------' |
'----------------'
</pre>
</div>
</div>
</body>
</html>
---
apiVersion: v1
kind: Service
metadata:
name: jenova
spec:
selector:
app: web
ports:
- port: 80

13
mushroom/Makefile Normal file
View File

@@ -0,0 +1,13 @@
.PHONY: lint
lint: ## Lint the helm chart
helm lint helm
.PHONY: deploy
deploy: ## Deploy the helm chart on a running cluster
helm upgrade --install mushroom ./helm
.PHONY: help
help: ## Show this help
@echo Noteworthy targets:
@egrep '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-20s\033[0m %s\n", $$1, $$2}'
.DEFAULT_GOAL := help

23
mushroom/helm/.helmignore Normal file
View File

@@ -0,0 +1,23 @@
# Patterns to ignore when building packages.
# This supports shell glob matching, relative path matching, and
# negation (prefixed with !). Only one pattern per line.
.DS_Store
# Common VCS dirs
.git/
.gitignore
.bzr/
.bzrignore
.hg/
.hgignore
.svn/
# Common backup files
*.swp
*.bak
*.tmp
*.orig
*~
# Various IDEs
.project
.idea/
*.tmproj
.vscode/

6
mushroom/helm/Chart.yaml Normal file
View File

@@ -0,0 +1,6 @@
apiVersion: v2
name: mushroom
description: mushroom server and web front-end
type: application
version: 0.1.0
appVersion: "0.3"

211
mushroom/helm/index.html Normal file
View File

@@ -0,0 +1,211 @@
<html>
<head>
<title>A simple netcat client</title>
<link rel="icon">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
<style>
body {
background-color: rgb(4, 18, 0);
color: rgb(199, 224, 192);
}
.container {
padding: 32px;
width: 800px;
}
@media only screen and (max-width: 480px) {
body {
font-size: 100%;
}
.container {
width: 100%;
padding: 0px;
}
}
.console {
height: 480px;
border: solid;
border-radius: 8px;
border-color: rgb(76, 75, 83);
padding: 10px;
background-color: rgb(8, 36, 0);
margin-bottom: 10px;
margin-top: 10px;
}
.console-text {
overflow: auto;
max-height: calc(100% - 20px);
white-space: pre-wrap;
}
.echo {
font-size: 80%;
color: rgb(133, 199, 170);
}
.online-status {
font-weight: bold;
color: greenyellow;
font-size: larger;
display: none;
text-align: center;
width: 120px;
}
</style>
<script src="https://code.jquery.com/jquery-3.7.0.slim.min.js"></script>
<script>
$(() => {
let ws;
const history = [];
let history_cursor;
function colorize(input) {
var mapObj = {
30: '#000000',
31: '#cc0000',
32: '#4e9a06',
33: '#c4a000',
34: '#729fcf',
35: '#75507b',
36: '#06989a',
37: '#d3d7cf',
90: '#555753',
91: '#ef2929',
92: '#8ae234',
93: '#fce94f',
94: '#32afff',
95: '#ad7fa8',
96: '#34e2e2',
97: '#ffffff',
};
const colored = input.replace(/\033\[(?:0;)?(\d+)m/g, function (_, color) {
return `</span><span style="color:${mapObj[color]}">`;
}).replace(/\033\[0m/g, '</span><span>');
return `<span>${colored}</span>`;
}
document.addEventListener("visibilitychange", () => {
if (!document.hidden) {
$('link')[0].href = document.createElement('canvas').toDataURL();
}
});
function notifyActivity() {
if (document.hasFocus()) {
return;
}
let c = document.createElement('canvas');
c.width = 16;
c.height = 16;
let ctx = c.getContext('2d');
ctx.fillStyle = "red";
ctx.beginPath();
ctx.arc(7, 7, 3, 0, 2*Math.PI);
ctx.fill();
$('link')[0].href = c.toDataURL();
}
$('.input').keydown(e => {
if (e.key == "ArrowUp") {
if (history_cursor === undefined) {
history_cursor = history.length;
} else if (history_cursor === 0) {
return true;
}
history_cursor--;
$('.input').val(history[history_cursor]);
return false;
} else if (e.key == "ArrowDown" && history_cursor !== undefined) {
if (history_cursor >= history.length - 1) {
history_cursor = undefined;
$('.input').val('');
return false;
}
history_cursor++;
$('.input').val(history[history_cursor]);
return false;
}
});
$('.input').keypress(e => {
if (e.key == "Enter") {
if (ws === undefined || ws.readyState !== 1) {
return true;
}
const sh = $('.console-text').get(0).scrollHeight;
const line = $('.input').val()
$('.console-text').append('<br /> <span class="echo">&gt; ' + line + '</span><br />').scrollTop(sh);
$('.input').val('');
history.push(line);
history_cursor = undefined;
ws.send(`${line}\n`);
return false; //<---- Add this line
}
});
function connect() {
ws = new WebSocket($('input[name=wshost]').val());
ws.onerror = console.log;
ws.onclose = e => {
$('.online-status').hide();
$('input[type=button]').prop('value', 'Connect');
};
ws.onopen = e => {
$('.online-status').show();
$('.input').focus();
};
ws.onmessage = m => {
m.data.text().then(text => {
text = text
.replaceAll('&', '&amp;')
.replaceAll('<', '&lt;')
.replaceAll('>', '&gt;');
const sh = $('.console-text').get(0).scrollHeight;
$('.console-text').append(colorize(text)).scrollTop(sh);
});
notifyActivity();
};
$('input[type=button]').prop('value', 'Disconnect');
}
function disconnect() {
ws.close();
}
$('input[type=button]').click(() => {
if (ws && ws.readyState !== WebSocket.CLOSED) {
disconnect();
} else {
connect();
}
});
});
</script>
</head>
<body>
<div class="container">
<input name="wshost" value="wss://mushroom.jenova.ponteilla.net/ws"><input type="button" value="Connect">
<span class="online-status">online</span>
<div class="console">
<pre class="console-text"></pre>
</div>
<input class="input" style="width:100%">
</div>
</body>
</html>

View File

@@ -0,0 +1,24 @@
server {
listen 80;
server_name localhost;
location / {
root /usr/share/nginx/html;
index index.html index.htm;
}
location /ws {
proxy_pass http://127.0.0.1:1338;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "Upgrade";
proxy_set_header Host $host;
proxy_read_timeout 10h;
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /usr/share/nginx/html;
}
}

View File

@@ -0,0 +1,51 @@
{{/*
Expand the name of the chart.
*/}}
{{- define "mushroom.name" -}}
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
{{- end }}
{{/*
Create a default fully qualified app name.
We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec).
If release name contains chart name it will be used as a full name.
*/}}
{{- define "mushroom.fullname" -}}
{{- if .Values.fullnameOverride }}
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- $name := default .Chart.Name .Values.nameOverride }}
{{- if contains $name .Release.Name }}
{{- .Release.Name | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }}
{{- end }}
{{- end }}
{{- end }}
{{/*
Create chart name and version as used by the chart label.
*/}}
{{- define "mushroom.chart" -}}
{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }}
{{- end }}
{{/*
Common labels
*/}}
{{- define "mushroom.labels" -}}
helm.sh/chart: {{ include "mushroom.chart" . }}
{{ include "mushroom.selectorLabels" . }}
{{- if .Chart.AppVersion }}
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
{{- end }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
{{- end }}
{{/*
Selector labels
*/}}
{{- define "mushroom.selectorLabels" -}}
app.kubernetes.io/name: {{ include "mushroom.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
{{- end }}

View File

@@ -0,0 +1,36 @@
{{- if .Values.ingress.enabled -}}
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: {{ include "mushroom.fullname" . }}
labels:
{{- include "mushroom.labels" . | nindent 4 }}
{{- with .Values.ingress.annotations }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}
spec:
{{- with .Values.ingress.className }}
ingressClassName: {{ . }}
{{- end }}
{{- if .Values.ingress.tls }}
tls:
- hosts:
{{- range .Values.ingress.hosts }}
- {{ . | quote }}
{{- end }}
{{- end }}
rules:
{{- range .Values.ingress.hosts }}
- host: {{ . | quote }}
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: {{ include "mushroom.fullname" $ }}
port:
number: {{ $.Values.service.port }}
{{- end }}
{{- end }}

View File

@@ -0,0 +1,15 @@
apiVersion: v1
kind: Service
metadata:
name: {{ include "mushroom.fullname" . }}
labels:
{{- include "mushroom.labels" . | nindent 4 }}
spec:
type: {{ .Values.service.type }}
ports:
- port: {{ .Values.service.port }}
targetPort: http
protocol: TCP
name: http
selector:
{{- include "mushroom.selectorLabels" . | nindent 4 }}

View File

@@ -0,0 +1,70 @@
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: {{ include "mushroom.fullname" . }}
labels:
{{- include "mushroom.labels" . | nindent 4 }}
spec:
selector:
matchLabels:
{{- include "mushroom.selectorLabels" . | nindent 6 }}
serviceName: {{ include "mushroom.fullname" . }}
template:
metadata:
{{- with .Values.podAnnotations }}
annotations:
{{- toYaml . | nindent 8 }}
{{- end }}
labels:
{{- include "mushroom.labels" . | nindent 8 }}
{{- with .Values.podLabels }}
{{- toYaml . | nindent 8 }}
{{- end }}
spec:
containers:
- name: mushroomd
image: "{{ .Values.images.server.repository }}:{{ .Values.images.server.tag | default .Chart.AppVersion }}"
ports:
- containerPort: 1337
volumeMounts:
- name: data
mountPath: /data
- name: webproxy
image: "{{ .Values.images.proxy.repository }}:{{ .Values.images.proxy.tag | default .Chart.AppVersion }}"
ports:
- containerPort: 1338
- name: nginx
image: "{{ .Values.images.nginx.repository }}:{{ .Values.images.nginx.tag | default "1.29.5" }}"
ports:
- name: http
containerPort: {{ .Values.service.port }}
protocol: TCP
volumeMounts:
- name: config
mountPath: /usr/share/nginx/html/index.html
subPath: index.html
- name: config
mountPath: /etc/nginx/conf.d/default.conf
subPath: default.conf
volumes:
- name: config
configMap:
name: {{ .Chart.Name }}-nginx
volumeClaimTemplates:
- metadata:
name: data
spec:
accessModes: [ "ReadWriteOnce" ]
resources:
requests:
storage: 32Mi
---
apiVersion: v1
kind: ConfigMap
metadata:
name: {{ .Chart.Name }}-nginx
data:
index.html: |
{{ .Files.Get "index.html" | indent 4 }}
default.conf: |
{{ .Files.Get "nginx-default.conf" | indent 4 }}

View File

@@ -0,0 +1,15 @@
apiVersion: v1
kind: Pod
metadata:
name: "{{ include "mushroom.fullname" . }}-test-connection"
labels:
{{- include "mushroom.labels" . | nindent 4 }}
annotations:
"helm.sh/hook": test
spec:
containers:
- name: wget
image: busybox
command: ['wget']
args: ['{{ include "mushroom.fullname" . }}:{{ .Values.service.port }}']
restartPolicy: Never

31
mushroom/helm/values.yaml Normal file
View File

@@ -0,0 +1,31 @@
# Default values for mushroom.
# This is a YAML-formatted file.
# Declare variables to be passed into your templates.
images:
server:
repository: mushroom
# tag: latest
proxy:
repository: mushroom-proxy
# tag: latest
nginx:
repository: nginx
tag: 1.29.5
# This is for setting up a service more information can be found here: https://kubernetes.io/docs/concepts/services-networking/service/
service:
# This sets the service type more information can be found here: https://kubernetes.io/docs/concepts/services-networking/service/#publishing-services-service-types
type: ClusterIP
# This sets the ports more information can be found here: https://kubernetes.io/docs/concepts/services-networking/service/#field-spec-ports
port: 80
# This block is for setting up the ingress for more information can be found here: https://kubernetes.io/docs/concepts/services-networking/ingress/
ingress:
enabled: true
className: "traefik"
annotations:
traefik.ingress.kubernetes.io/router.tls.certresolver: "default"
hosts:
- mushroom.jenova.ponteilla.net
tls: true

View File

@@ -0,0 +1,19 @@
apiVersion: helm.cattle.io/v1
kind: HelmChartConfig
metadata:
name: traefik
namespace: kube-system
spec:
failurePolicy: reinstall
valuesContent: |-
additionalArguments:
- "--certificatesresolvers.default.acme.email=paul@ponteilla.net"
- "--certificatesresolvers.default.acme.storage=/data/acme.json"
- "--certificatesresolvers.default.acme.httpchallenge.entrypoint=web"
ports:
web:
redirections:
entryPoint:
to: websecure
scheme: https
permanent: true

15
zetikettes/Makefile Normal file
View File

@@ -0,0 +1,15 @@
namespace := zetikettes
.PHONY: deploy
deploy: ## Deploy the service on a running cluster
kubectl -n $(namespace) apply \
-f app.yaml \
-f ingress.yaml \
-f nginx-config.yaml \
-f service.yaml
.PHONY: help
help: ## Show this help
@echo Noteworthy targets:
@egrep '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-20s\033[0m %s\n", $$1, $$2}'
.DEFAULT_GOAL := help

52
zetikettes/app.yaml Normal file
View File

@@ -0,0 +1,52 @@
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: zetikettes-app
spec:
serviceName: "zetikettes"
replicas: 1
selector:
matchLabels:
app: zetikettes
template:
metadata:
labels:
app: zetikettes
spec:
containers:
# --- The Backend (Stateful) ---
- name: backend
image: pol/zetikettes:latest
imagePullPolicy: Never
ports:
- containerPort: 8000
volumeMounts:
- name: var-lib-zetikettes
mountPath: /data
subPath: data
env:
- name: CSRF_TRUSTED_ORIGINS
value: https://zetikettes.jenova.ponteilla.net
- name: STATIC_URL
value: /zetikettes/srv/static/
# --- The Frontend (Nginx Sidecar) ---
- name: nginx-sidecar
image: nginx:alpine
ports:
- containerPort: 80
volumeMounts:
- name: var-lib-zetikettes
mountPath: /var/lib/zetikettes
- name: config-volume
mountPath: /etc/nginx/conf.d/default.conf
subPath: default.conf
volumes:
- name: config-volume
configMap:
name: nginx-config
- name: var-lib-zetikettes
hostPath:
path: /var/lib/zetikettes
type: DirectoryOrCreate

21
zetikettes/ingress.yaml Normal file
View File

@@ -0,0 +1,21 @@
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: zetikettes
annotations:
traefik.ingress.kubernetes.io/router.tls.certresolver: default
spec:
tls:
- hosts:
- zetikettes.jenova.ponteilla.net
rules:
- host: zetikettes.jenova.ponteilla.net
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: zetikettes
port:
number: 80

View File

@@ -0,0 +1,26 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: nginx-config
data:
default.conf: |
server {
listen 80;
root /var/lib/zetikettes/frontend;
location /zetikettes/srv/static {
alias /var/lib/zetikettes/www_static;
}
location /zetikettes/srv/data {
alias /var/lib/zetikettes/data;
}
location ^~ /zetikettes/srv {
proxy_pass http://127.0.0.1:8000;
proxy_set_header SCRIPT_NAME /zetikettes/srv;
proxy_set_header Host $http_host;
# generating stuff takes time
proxy_read_timeout 10m;
client_max_body_size 10M;
}
}

12
zetikettes/service.yaml Normal file
View File

@@ -0,0 +1,12 @@
apiVersion: v1
kind: Service
metadata:
name: zetikettes
spec:
selector:
app: zetikettes
ports:
- protocol: TCP
port: 80
targetPort: 80
type: ClusterIP