Files
bluejay-infra/apps/irc/irc.yaml
2026-04-16 19:29:43 -05:00

831 lines
18 KiB
YAML

# UnrealIRCd + Anope IRC Services + The Lounge web client
# ArgoCD managed - BlueJay Lab
# Credentials: 1Password → OnePasswordItem → K8s Secret → initContainer sed injection
---
apiVersion: v1
kind: Namespace
metadata:
name: irc
labels:
app.kubernetes.io/part-of: bluejay-infra
---
# 1Password → K8s Secret sync
apiVersion: onepassword.com/v1
kind: OnePasswordItem
metadata:
name: irc-credentials
namespace: irc
spec:
itemPath: "vaults/IAmWorkin/items/IRC UnrealIRCd"
---
# TLS Certificate for IRC
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: irc-tls
namespace: irc
spec:
secretName: irc-tls
issuerRef:
name: step-ca-acme
kind: ClusterIssuer
dnsNames:
- irc.iamworkin.lan
---
# TLS Certificate for The Lounge web IRC
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: webirc-tls
namespace: irc
spec:
secretName: webirc-tls
issuerRef:
name: step-ca-acme
kind: ClusterIssuer
dnsNames:
- webirc.iamworkin.lan
---
# The Lounge configuration
apiVersion: v1
kind: ConfigMap
metadata:
name: thelounge-config
namespace: irc
data:
config.js: |
"use strict";
module.exports = {
public: true,
host: "0.0.0.0",
port: 9000,
reverseProxy: true,
maxHistory: 2500,
theme: "default",
prefetch: false,
disableMediaPreview: true,
fileUpload: {
enable: false
},
defaults: {
name: "BlueJayIRC",
host: "unrealircd.irc.svc.cluster.local",
port: 6667,
password: "",
tls: false,
rejectUnauthorized: true,
nick: "BlueJayWeb%%",
username: "bluejayweb",
realname: "BlueJay Web IRC",
join: "#general"
},
lockNetwork: true,
leaveMessage: "BlueJay Web IRC"
};
---
# UnrealIRCd configuration template (passwords replaced by placeholders)
apiVersion: v1
kind: ConfigMap
metadata:
name: unrealircd-config-template
namespace: irc
data:
unrealircd.conf: |
/* BlueJay Lab IRC - UnrealIRCd 6.x config */
/* Managed by ArgoCD */
/* Credentials injected from 1Password at pod startup */
include "modules.default.conf";
include "help/help.conf";
include "operclass.default.conf";
include "snomasks.default.conf";
loadmodule "cloak_sha256";
me {
name "irc.iamworkin.lan";
info "BlueJay Lab IRC Server";
sid 001;
}
admin {
"BlueJay Lab IRC";
"admin@iamwork.in";
}
class clients {
pingfreq 90;
maxclients 500;
sendq 200k;
recvq 8000;
}
class opers {
pingfreq 90;
maxclients 50;
sendq 1M;
recvq 8000;
}
class servers {
pingfreq 60;
connfreq 15;
maxclients 10;
sendq 20M;
}
allow {
mask *;
class clients;
maxperip 5;
}
listen {
ip *;
port 6667;
}
listen {
ip *;
port 6697;
options { tls; }
tls-options {
certificate "/app/conf/tls/server.cert.pem";
key "/app/conf/tls/server.key.pem";
}
}
listen {
ip *;
port 8067;
}
oper bluejay {
mask *;
password "__OPER_PASSWORD__";
operclass netadmin-with-override;
class opers;
}
drpass {
restart "__OPER_PASSWORD__";
die "__OPER_PASSWORD__";
}
link services.iamworkin.lan {
incoming {
mask *;
}
password "__LINK_PASSWORD__";
class servers;
}
ulines {
services.iamworkin.lan;
}
log {
source {
all;
\\!debug;
}
destination {
channel "#ops";
}
}
set {
network-name "BlueJayIRC";
default-server "irc.iamworkin.lan";
services-server "services.iamworkin.lan";
stats-server "stats.iamworkin.lan";
help-channel "#general";
cloak-keys {
"ZWKeb8YevNiL45Xdh2p5u4tv2xksWgb8YPQSvmerBmNObyGbTDGnU4PNomZaLbZ1D9M2Cy6njM1XLJUkJhAx1oY3coBdZoPykEo7";
"KqRaLeA6ijOnWDdCqYtJ6rb1VgR8lYnU9Sey7cbRhi3PsGzD5gZONJXyUdbJ7bD26QKCuiDydBsccUVKC3lYN0HJ9sGTlOYR3c2m";
"2I4oopLDY79Fr4Mucy63EVOfkelVV23nESPWoqMnP1pUc8Yg0D4RK1mVtxyEhdTPpLFyKgG4fRlb6R33eHoQe7yi7moOu4W1Waw6";
}
kline-address "admin@iamwork.in";
maxchannelsperuser 25;
anti-flood {
everyone {
connect-flood 3:60;
}
}
options {
hide-ulines;
show-connect-info;
}
/* TLS config */
tls {
certificate "/app/conf/tls/server.cert.pem";
key "/app/conf/tls/server.key.pem";
trusted-ca-file "/etc/ssl/certs/ca-certificates.crt";
}
/* Allow plaintext for server-to-server links (Anope is internal) */
plaintext-policy {
server allow;
}
}
ircd.motd: |
- BlueJay IRC -
Welcome to BlueJayIRC on iamworkin.lan.
Web IRC: https://webirc.iamworkin.lan
Channels: #general, #ops, #alerts
Keep it keyboard-first, practical, and kind.
Managed from bluejay-infra via ArgoCD.
---
# Anope configuration template (passwords replaced by placeholders)
apiVersion: v1
kind: ConfigMap
metadata:
name: anope-config-template
namespace: irc
data:
services.conf: |
define
{
name = "services.host"
value = "services.iamworkin.lan"
}
uplink
{
host = "unrealircd.irc.svc.cluster.local"
port = 8067
password = "__LINK_PASSWORD__"
}
serverinfo
{
name = "services.iamworkin.lan"
description = "BlueJay IRC Services"
pid = "/anope/data/services.pid"
motd = "/anope/data/services.motd"
}
module { name = "unreal4" }
networkinfo
{
networkname = "BlueJayIRC"
nicklen = 31
userlen = 10
hostlen = 64
chanlen = 32
}
options
{
casemap = "ascii"
strictpasswords = yes
readtimeout = 5s
warningtimeout = 4h
}
module { name = "enc_sha256" }
/* Service pseudo-client definitions */
service
{
nick = "NickServ"
user = "services"
host = "services.host"
gecos = "Nickname Registration Service"
}
service
{
nick = "ChanServ"
user = "services"
host = "services.host"
gecos = "Channel Registration Service"
}
service
{
nick = "OperServ"
user = "services"
host = "services.host"
gecos = "Operator Service"
}
service
{
nick = "BotServ"
user = "services"
host = "services.host"
gecos = "Bot Service"
}
service
{
nick = "HostServ"
user = "services"
host = "services.host"
gecos = "vHost Service"
}
service
{
nick = "MemoServ"
user = "services"
host = "services.host"
gecos = "Memo Service"
}
service
{
nick = "Global"
user = "services"
host = "services.host"
gecos = "Global Noticer"
}
/* Module configurations */
module
{
name = "nickserv"
client = "NickServ"
defaults = "kill_quick ns_secure ns_private hide_email"
registration = "none"
expire = 90d
}
module { name = "ns_identify" }
module { name = "ns_register" }
module { name = "ns_set" }
module { name = "ns_drop" }
module { name = "ns_recover" }
module { name = "ns_info" }
module { name = "ns_list" }
module { name = "ns_access" }
module { name = "ns_group" }
module
{
name = "chanserv"
client = "ChanServ"
defaults = "keeptopic peace cs_secure"
expire = 14d
}
module { name = "cs_register" }
module { name = "cs_set" }
module { name = "cs_access" }
module { name = "cs_ban" }
module { name = "cs_kick" }
module { name = "cs_mode" }
module { name = "cs_topic" }
module { name = "cs_info" }
module { name = "cs_list" }
module { name = "cs_drop" }
module
{
name = "operserv"
client = "OperServ"
}
module { name = "os_akill" }
module { name = "os_mode" }
module { name = "os_kick" }
module { name = "os_kill" }
module { name = "os_list" }
module { name = "os_stats" }
module { name = "os_reload" }
module { name = "os_shutdown" }
module
{
name = "botserv"
client = "BotServ"
defaults = "dontkickops fantasy greet"
}
module { name = "bs_bot" }
module { name = "bs_assign" }
module
{
name = "hostserv"
client = "HostServ"
}
module { name = "hs_set" }
module { name = "hs_request" }
module
{
name = "memoserv"
client = "MemoServ"
maxmemos = 20
}
module { name = "ms_send" }
module { name = "ms_read" }
module { name = "ms_del" }
module { name = "ms_list" }
module
{
name = "global"
client = "Global"
}
module { name = "gl_global" }
opertype
{
name = "Services Root"
commands = "*"
privs = "*"
}
oper
{
name = "bluejay"
type = "Services Root"
}
module
{
name = "db_flatfile"
database = "anope.db"
fork = no
}
log
{
target = "services.log"
admin = "*"
override = "chanserv/* nickserv/* operserv/*"
commands = "chanserv/* nickserv/* operserv/*"
servers = "*"
channels = "*"
users = "connect disconnect"
}
---
# UnrealIRCd PVC
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: unrealircd-data
namespace: irc
spec:
accessModes: [ReadWriteOnce]
resources:
requests:
storage: 1Gi
---
# Anope PVC
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: anope-data
namespace: irc
spec:
accessModes: [ReadWriteOnce]
resources:
requests:
storage: 1Gi
---
# UnrealIRCd Deployment
apiVersion: apps/v1
kind: Deployment
metadata:
name: unrealircd
namespace: irc
labels:
app: unrealircd
spec:
replicas: 1
selector:
matchLabels:
app: unrealircd
template:
metadata:
labels:
app: unrealircd
spec:
initContainers:
- name: inject-credentials
image: busybox:1.36
command: ["sh", "-c"]
args:
- |
OPER_PW=$(cat /secrets/password)
LINK_PW=$(cat /secrets/Link-Password)
sed -e "s|__OPER_PASSWORD__|${OPER_PW}|g" \
-e "s|__LINK_PASSWORD__|${LINK_PW}|g" \
/config-template/unrealircd.conf > /injected-config/unrealircd.conf
echo "Credentials injected into unrealircd.conf"
volumeMounts:
- name: irc-credentials
mountPath: /secrets
readOnly: true
- name: unrealircd-config-template
mountPath: /config-template
readOnly: true
- name: injected-config
mountPath: /injected-config
- name: copy-tls
image: busybox:1.36
command: ["sh", "-c"]
args:
- |
cp /tls-secret/tls.crt /tls/server.cert.pem
cp /tls-secret/tls.key /tls/server.key.pem
chmod 644 /tls/server.cert.pem
chmod 644 /tls/server.key.pem
chown 1000:1000 /tls/server.cert.pem /tls/server.key.pem 2>/dev/null || true
chmod 777 /data
volumeMounts:
- name: irc-tls-secret
mountPath: /tls-secret
readOnly: true
- name: irc-tls
mountPath: /tls
- name: unrealircd-data
mountPath: /data
containers:
- name: unrealircd
image: djlegolas/unrealircd:6.1.9.1
ports:
- containerPort: 6667
name: irc-plain
- containerPort: 6697
name: irc-tls
- containerPort: 8067
name: services-link
volumeMounts:
- name: injected-config
mountPath: /app/conf/unrealircd.conf
subPath: unrealircd.conf
- name: unrealircd-config-template
mountPath: /app/conf/ircd.motd
subPath: ircd.motd
readOnly: true
- name: unrealircd-data
mountPath: /app/data
- name: irc-tls
mountPath: /app/conf/tls
resources:
requests:
memory: 64Mi
cpu: 50m
limits:
memory: 256Mi
cpu: 250m
volumes:
- name: irc-credentials
secret:
secretName: irc-credentials
- name: unrealircd-config-template
configMap:
name: unrealircd-config-template
- name: injected-config
emptyDir: {}
- name: unrealircd-data
persistentVolumeClaim:
claimName: unrealircd-data
- name: irc-tls-secret
secret:
secretName: irc-tls
- name: irc-tls
emptyDir: {}
---
# Anope IRC Services Deployment
apiVersion: apps/v1
kind: Deployment
metadata:
name: anope
namespace: irc
labels:
app: anope
spec:
replicas: 1
selector:
matchLabels:
app: anope
template:
metadata:
labels:
app: anope
spec:
initContainers:
- name: inject-credentials
image: busybox:1.36
command: ["sh", "-c"]
args:
- |
LINK_PW=$(cat /secrets/Link-Password)
sed -e "s|__LINK_PASSWORD__|${LINK_PW}|g" \
/config-template/services.conf > /injected-config/services.conf
echo "Credentials injected into services.conf"
volumeMounts:
- name: irc-credentials
mountPath: /secrets
readOnly: true
- name: anope-config-template
mountPath: /config-template
readOnly: true
- name: injected-config
mountPath: /injected-config
- name: fix-perms
image: busybox:1.36
command: ["sh", "-c"]
args:
- |
mkdir -p /data/db /data/logs /data/runtime
touch /data/anope.db /data/services.motd
chmod 666 /data/anope.db
chown -R 10000:10000 /data 2>/dev/null || chmod -R 777 /data
echo "Anope data dir prepared: $(ls -la /data/anope.db)"
volumeMounts:
- name: anope-data
mountPath: /data
containers:
- name: anope
image: anope/anope:latest
volumeMounts:
- name: injected-config
mountPath: /anope/conf/services.conf
subPath: services.conf
- name: anope-data
mountPath: /anope/data
resources:
requests:
memory: 64Mi
cpu: 25m
limits:
memory: 128Mi
cpu: 100m
volumes:
- name: irc-credentials
secret:
secretName: irc-credentials
- name: anope-config-template
configMap:
name: anope-config-template
- name: injected-config
emptyDir: {}
- name: anope-data
persistentVolumeClaim:
claimName: anope-data
---
# The Lounge web IRC Deployment
apiVersion: apps/v1
kind: Deployment
metadata:
name: thelounge
namespace: irc
labels:
app: thelounge
spec:
replicas: 1
selector:
matchLabels:
app: thelounge
template:
metadata:
labels:
app: thelounge
spec:
containers:
- name: thelounge
image: ghcr.io/thelounge/thelounge:4.4.3
ports:
- containerPort: 9000
name: http
readinessProbe:
httpGet:
path: /
port: http
initialDelaySeconds: 10
periodSeconds: 10
livenessProbe:
httpGet:
path: /
port: http
initialDelaySeconds: 30
periodSeconds: 20
resources:
requests:
memory: 64Mi
cpu: 50m
limits:
memory: 256Mi
cpu: 250m
volumeMounts:
- name: thelounge-config
mountPath: /var/opt/thelounge/config.js
subPath: config.js
volumes:
- name: thelounge-config
configMap:
name: thelounge-config
---
# UnrealIRCd Service
apiVersion: v1
kind: Service
metadata:
name: unrealircd
namespace: irc
spec:
selector:
app: unrealircd
ports:
- port: 6667
targetPort: 6667
name: irc-plain
- port: 6697
targetPort: 6697
name: irc-tls
- port: 8067
targetPort: 8067
name: services-link
---
# Anope Service
apiVersion: v1
kind: Service
metadata:
name: anope
namespace: irc
spec:
selector:
app: anope
ports:
- port: 8067
targetPort: 8067
name: services-link
---
# The Lounge web IRC Service
apiVersion: v1
kind: Service
metadata:
name: thelounge
namespace: irc
spec:
selector:
app: thelounge
ports:
- port: 9000
targetPort: 9000
name: http
---
# Traefik IngressRouteTCP - IRC plain (6667)
apiVersion: traefik.io/v1alpha1
kind: IngressRouteTCP
metadata:
name: irc-plain
namespace: irc
spec:
entryPoints:
- irc
routes:
- match: HostSNI(`*`)
services:
- name: unrealircd
port: 6667
---
# Traefik IngressRouteTCP - IRC TLS passthrough (6697)
apiVersion: traefik.io/v1alpha1
kind: IngressRouteTCP
metadata:
name: irc-tls
namespace: irc
spec:
entryPoints:
- irctls
routes:
- match: HostSNI(`*`)
services:
- name: unrealircd
port: 6697
tls:
passthrough: true
---
# Traefik IngressRoute - The Lounge web IRC
apiVersion: traefik.io/v1alpha1
kind: IngressRoute
metadata:
name: webirc
namespace: irc
spec:
entryPoints:
- websecure
routes:
- match: Host(`webirc.iamworkin.lan`)
kind: Rule
services:
- name: thelounge
port: 9000
tls:
secretName: webirc-tls