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

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