feat: initial mIRCcloud.com scaffold

- Express + TypeScript backend with IRC connection manager
- React + Vite frontend with mIRC-inspired theme
- Drizzle ORM + PostgreSQL schema (users, connections, channels, messages)
- Redis pub/sub for real-time WebSocket delivery across replicas
- IRC color code parser (16 + 99 extended palette)
- Kubernetes manifests for homecloud deployment
- Multi-stage Dockerfile with non-root user
- docker-compose for local development
This commit is contained in:
Jason Preston
2026-08-08 23:50:08 -06:00
commit c59ac7ad0f
52 changed files with 5723 additions and 0 deletions
+15
View File
@@ -0,0 +1,15 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: mirccloud-config
namespace: mirccloud
labels:
app.kubernetes.io/name: mirccloud
app.kubernetes.io/component: app
app.kubernetes.io/part-of: mirccloud
data:
PORT: "3000"
DATABASE_URL: "postgresql://mirccloud:$(POSTGRES_PASSWORD)@mirccloud-postgres:5432/mirccloud"
REDIS_URL: "redis://mirccloud-redis:6379"
CORS_ORIGIN: "https://mirccloud.com"
NODE_ENV: "production"
+82
View File
@@ -0,0 +1,82 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: mirccloud-app
namespace: mirccloud
labels:
app.kubernetes.io/name: mirccloud
app.kubernetes.io/component: app
app.kubernetes.io/part-of: mirccloud
spec:
replicas: 2
selector:
matchLabels:
app.kubernetes.io/name: mirccloud
app.kubernetes.io/component: app
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1
maxUnavailable: 0
template:
metadata:
labels:
app.kubernetes.io/name: mirccloud
app.kubernetes.io/component: app
app.kubernetes.io/part-of: mirccloud
spec:
serviceAccountName: default
securityContext:
runAsNonRoot: true
runAsUser: 1001
runAsGroup: 1001
fsGroup: 1001
containers:
- name: app
image: git.lab.fairings.org/jpreston/mirccloud/app:latest
imagePullPolicy: Always
ports:
- name: http
containerPort: 3000
protocol: TCP
envFrom:
- secretRef:
name: mirccloud-secrets
- configMapRef:
name: mirccloud-config
resources:
requests:
cpu: 200m
memory: 256Mi
limits:
cpu: 1000m
memory: 512Mi
livenessProbe:
httpGet:
path: /api/health
port: http
initialDelaySeconds: 15
periodSeconds: 20
timeoutSeconds: 5
failureThreshold: 3
readinessProbe:
httpGet:
path: /api/health
port: http
initialDelaySeconds: 5
periodSeconds: 10
timeoutSeconds: 3
failureThreshold: 3
volumeMounts:
- name: uploads
mountPath: /data/uploads
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: false
capabilities:
drop:
- ALL
volumes:
- name: uploads
persistentVolumeClaim:
claimName: mirccloud-uploads
+45
View File
@@ -0,0 +1,45 @@
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: mirccloud-ingress
namespace: mirccloud
labels:
app.kubernetes.io/name: mirccloud
app.kubernetes.io/component: ingress
app.kubernetes.io/part-of: mirccloud
annotations:
cert-manager.io/cluster-issuer: letsencrypt-prod
nginx.ingress.kubernetes.io/proxy-read-timeout: "3600"
nginx.ingress.kubernetes.io/proxy-send-timeout: "3600"
nginx.ingress.kubernetes.io/proxy-body-size: "50m"
nginx.ingress.kubernetes.io/websocket-services: mirccloud-app
nginx.ingress.kubernetes.io/proxy-http-version: "1.1"
nginx.ingress.kubernetes.io/upstream-hash-by: "$remote_addr"
spec:
ingressClassName: nginx-public
tls:
- hosts:
- mirccloud.com
- www.mirccloud.com
secretName: mirccloud-com-tls
rules:
- host: mirccloud.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: mirccloud-app
port:
number: 80
- host: www.mirccloud.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: mirccloud-app
port:
number: 80
+7
View File
@@ -0,0 +1,7 @@
apiVersion: v1
kind: Namespace
metadata:
name: mirccloud
labels:
app.kubernetes.io/name: mirccloud
app.kubernetes.io/part-of: mirccloud
+71
View File
@@ -0,0 +1,71 @@
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: mirccloud-app-ingress
namespace: mirccloud
labels:
app.kubernetes.io/name: mirccloud
app.kubernetes.io/part-of: mirccloud
spec:
podSelector:
matchLabels:
app.kubernetes.io/name: mirccloud
app.kubernetes.io/component: app
policyTypes:
- Ingress
ingress:
- from:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: ingress-nginx
ports:
- protocol: TCP
port: 3000
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: mirccloud-postgres-ingress
namespace: mirccloud
labels:
app.kubernetes.io/name: mirccloud
app.kubernetes.io/part-of: mirccloud
spec:
podSelector:
matchLabels:
app.kubernetes.io/name: mirccloud
app.kubernetes.io/component: postgres
policyTypes:
- Ingress
ingress:
- from:
- podSelector:
matchLabels:
app.kubernetes.io/name: mirccloud
ports:
- protocol: TCP
port: 5432
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: mirccloud-redis-ingress
namespace: mirccloud
labels:
app.kubernetes.io/name: mirccloud
app.kubernetes.io/part-of: mirccloud
spec:
podSelector:
matchLabels:
app.kubernetes.io/name: mirccloud
app.kubernetes.io/component: redis
policyTypes:
- Ingress
ingress:
- from:
- podSelector:
matchLabels:
app.kubernetes.io/name: mirccloud
ports:
- protocol: TCP
port: 6379
+113
View File
@@ -0,0 +1,113 @@
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: mirccloud-postgres
namespace: mirccloud
labels:
app.kubernetes.io/name: mirccloud
app.kubernetes.io/component: postgres
app.kubernetes.io/part-of: mirccloud
spec:
serviceName: mirccloud-postgres
replicas: 1
selector:
matchLabels:
app.kubernetes.io/name: mirccloud
app.kubernetes.io/component: postgres
template:
metadata:
labels:
app.kubernetes.io/name: mirccloud
app.kubernetes.io/component: postgres
app.kubernetes.io/part-of: mirccloud
spec:
securityContext:
runAsNonRoot: true
runAsUser: 1001
runAsGroup: 1001
fsGroup: 1001
containers:
- name: postgres
image: bitnami/postgresql:16
ports:
- name: tcp-postgres
containerPort: 5432
protocol: TCP
env:
- name: POSTGRESQL_USERNAME
value: mirccloud
- name: POSTGRESQL_DATABASE
value: mirccloud
- name: POSTGRESQL_PASSWORD
valueFrom:
secretKeyRef:
name: mirccloud-secrets
key: POSTGRES_PASSWORD
resources:
requests:
cpu: 250m
memory: 256Mi
limits:
cpu: 1000m
memory: 1Gi
volumeMounts:
- name: postgres-data
mountPath: /bitnami/postgresql
livenessProbe:
exec:
command:
- /bin/sh
- -c
- pg_isready -U mirccloud -d mirccloud
initialDelaySeconds: 30
periodSeconds: 10
timeoutSeconds: 5
failureThreshold: 6
readinessProbe:
exec:
command:
- /bin/sh
- -c
- pg_isready -U mirccloud -d mirccloud
initialDelaySeconds: 5
periodSeconds: 10
timeoutSeconds: 5
failureThreshold: 6
securityContext:
allowPrivilegeEscalation: false
capabilities:
drop:
- ALL
volumeClaimTemplates:
- metadata:
name: postgres-data
labels:
app.kubernetes.io/name: mirccloud
app.kubernetes.io/component: postgres
spec:
accessModes:
- ReadWriteOnce
storageClassName: nfs-synology
resources:
requests:
storage: 20Gi
---
apiVersion: v1
kind: Service
metadata:
name: mirccloud-postgres
namespace: mirccloud
labels:
app.kubernetes.io/name: mirccloud
app.kubernetes.io/component: postgres
app.kubernetes.io/part-of: mirccloud
spec:
type: ClusterIP
selector:
app.kubernetes.io/name: mirccloud
app.kubernetes.io/component: postgres
ports:
- name: tcp-postgres
port: 5432
targetPort: 5432
protocol: TCP
+16
View File
@@ -0,0 +1,16 @@
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: mirccloud-uploads
namespace: mirccloud
labels:
app.kubernetes.io/name: mirccloud
app.kubernetes.io/component: app
app.kubernetes.io/part-of: mirccloud
spec:
accessModes:
- ReadWriteMany
storageClassName: nfs-synology
resources:
requests:
storage: 5Gi
+117
View File
@@ -0,0 +1,117 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: mirccloud-redis
namespace: mirccloud
labels:
app.kubernetes.io/name: mirccloud
app.kubernetes.io/component: redis
app.kubernetes.io/part-of: mirccloud
spec:
replicas: 1
selector:
matchLabels:
app.kubernetes.io/name: mirccloud
app.kubernetes.io/component: redis
template:
metadata:
labels:
app.kubernetes.io/name: mirccloud
app.kubernetes.io/component: redis
app.kubernetes.io/part-of: mirccloud
spec:
securityContext:
runAsNonRoot: true
runAsUser: 999
runAsGroup: 999
fsGroup: 999
containers:
- name: redis
image: redis:7-alpine
command:
- redis-server
- --appendonly
- "yes"
- --maxmemory
- 100mb
- --maxmemory-policy
- allkeys-lru
ports:
- name: tcp-redis
containerPort: 6379
protocol: TCP
resources:
requests:
cpu: 50m
memory: 64Mi
limits:
cpu: 200m
memory: 128Mi
volumeMounts:
- name: redis-data
mountPath: /data
livenessProbe:
exec:
command:
- redis-cli
- ping
initialDelaySeconds: 10
periodSeconds: 10
timeoutSeconds: 3
failureThreshold: 3
readinessProbe:
exec:
command:
- redis-cli
- ping
initialDelaySeconds: 5
periodSeconds: 5
timeoutSeconds: 3
failureThreshold: 3
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: false
capabilities:
drop:
- ALL
volumes:
- name: redis-data
persistentVolumeClaim:
claimName: mirccloud-redis
---
apiVersion: v1
kind: Service
metadata:
name: mirccloud-redis
namespace: mirccloud
labels:
app.kubernetes.io/name: mirccloud
app.kubernetes.io/component: redis
app.kubernetes.io/part-of: mirccloud
spec:
type: ClusterIP
selector:
app.kubernetes.io/name: mirccloud
app.kubernetes.io/component: redis
ports:
- name: tcp-redis
port: 6379
targetPort: 6379
protocol: TCP
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: mirccloud-redis
namespace: mirccloud
labels:
app.kubernetes.io/name: mirccloud
app.kubernetes.io/component: redis
app.kubernetes.io/part-of: mirccloud
spec:
accessModes:
- ReadWriteOnce
storageClassName: nfs-synology
resources:
requests:
storage: 1Gi
+17
View File
@@ -0,0 +1,17 @@
# DO NOT commit real secrets to git!
# Copy this file to secrets.yaml and fill in real base64-encoded values.
#
# To encode a value: echo -n 'your-value' | base64
#
apiVersion: v1
kind: Secret
metadata:
name: mirccloud-secrets
namespace: mirccloud
labels:
app.kubernetes.io/name: mirccloud
app.kubernetes.io/part-of: mirccloud
type: Opaque
data:
JWT_SECRET: Y2hhbmdlLW1lLXRvLWEtcmVhbC1zZWNyZXQ=
POSTGRES_PASSWORD: Y2hhbmdlLW1lLXRvLWEtcmVhbC1wYXNzd29yZA==
+19
View File
@@ -0,0 +1,19 @@
apiVersion: v1
kind: Service
metadata:
name: mirccloud-app
namespace: mirccloud
labels:
app.kubernetes.io/name: mirccloud
app.kubernetes.io/component: app
app.kubernetes.io/part-of: mirccloud
spec:
type: ClusterIP
selector:
app.kubernetes.io/name: mirccloud
app.kubernetes.io/component: app
ports:
- name: http
port: 80
targetPort: 3000
protocol: TCP