wow: init

This is a shitty MMORPG server. Private. Do not touch.

Change-Id: Iddfce069f5895632d305a73fcaa2d963e25dc600
diff --git a/personal/q3k/wow/lib.libsonnet b/personal/q3k/wow/lib.libsonnet
new file mode 100644
index 0000000..4a9517b
--- /dev/null
+++ b/personal/q3k/wow/lib.libsonnet
@@ -0,0 +1,301 @@
+local kube = import "../../../kube/kube.libsonnet";
+
+{
+    local wow = self,
+    local cfg = wow.cfg,
+    local ns = wow.ns,
+    cfg:: {
+        namespace: error "namespace must be set",
+        prefix: "",
+        images: {
+            acore: "registry.k0.hswaw.net/q3k/azerothcore-wowtlk:1606950998",
+            panel: "registry.k0.hswaw.net/q3k/panel:1607033741-f18a531f9b84c5b33653c8db5d64aaa0af337541",
+        },
+        db: {
+            local mkConfig = function(name) {
+                host: error ("db.%s.host must be set" % [name]),
+                port: error ("db.%s.prt must be set" % [name]),
+                user: error ("db.%s.user must be set" % [name]),
+                password: error ("db.%s.password must be set" % [name]),
+                database: "acore_%s" % [name],
+            },
+            auth: mkConfig("auth"),
+            world: mkConfig("world"),
+            characters: mkConfig("characters"),
+        },
+        panel: {
+            domain: error "panel.domain must be set",
+            soap: {
+                username: error "panel.soap.username must be set",
+                password: error "panel.soap.password must be set",
+            },
+            secret: error "panel.secret must be set",
+            oauth: {
+                clientID: error "panel.oauth.clientID must set",
+                clientSecret: error "panel.oauth.clientSecret must set",
+                redirectURL:  "https://%s/callback" % [cfg.panel.domain],
+            },
+            motd: "",
+        },
+        overrides: {
+            authserver: {},
+            worldserver: {},
+            ahbot: {},
+        },
+    },
+
+    ns: kube.Namespace(cfg.namespace),
+
+    data: ns.Contain(kube.PersistentVolumeClaim(cfg.prefix + "data")) {
+        spec+: {
+            storageClassName: "waw-hdd-redundant-3",
+            accessModes: ["ReadWriteOnce"],
+            resources: {
+                requests: {
+                    storage: "50Gi",
+                },
+            },
+        },
+    },
+
+    // Make a *DatabaseInfo string for use by acore config. These are not any real
+    // standardized DSN format, just some semicolon-delimited proprietary format.
+    local mkDbString = function(config) (
+        "%s;%d;%s;%s;%s" % [
+            config.host,
+            config.port,
+            config.user,
+            config.password,
+            config.database,
+        ]
+    ),
+
+    etc: ns.Contain(kube.Secret(cfg.prefix + "etc")) {
+        data: {
+            "worldserver.conf": std.base64(std.manifestIni({
+                sections: {
+                    worldserver: {
+                        RealmID: 1,
+                        DataDir: "/data/current",
+                        LoginDatabaseInfo: mkDbString(cfg.db.auth),
+                        WorldDatabaseInfo: mkDbString(cfg.db.world),
+                        CharacterDatabaseInfo: mkDbString(cfg.db.characters),
+                        LogLevel: 2,
+
+                        "Console.Enable": 0,
+                        "Ra.Enable": 1,
+                        "Ra.IP": "127.0.0.1",
+                        "SOAP.Enabled": 1,
+                        "SOAP.IP": "0.0.0.0",
+
+                    } + cfg.overrides.worldserver,
+
+                },
+            })),
+            "mod_ahbot.conf": std.base64(std.manifestIni({
+                sections: {
+                    worldserver: cfg.overrides.ahbot,
+                },
+            })),
+            "authserver.conf": std.base64(std.manifestIni({
+                sections: {
+                    authserver: {
+                        LoginDatabaseInfo: mkDbString(cfg.db.auth),
+                    } + cfg.overrides.authserver,
+                },
+            })),
+        },
+    },
+
+    worldserverDeploy: ns.Contain(kube.Deployment(cfg.prefix + "worldserver")) {
+        spec+: {
+            template+: {
+                spec+: {
+                    containers_: {
+                        default: kube.Container("default") {
+                            image: cfg.images.acore,
+                            volumeMounts: [
+                                { name: "data", mountPath: "/data" },
+                                { name: "etc", mountPath: "/azeroth-server/etc/worldserver.conf", subPath: "worldserver.conf", },
+                                { name: "etc", mountPath: "/azeroth-server/etc/mod_ahbot.conf", subPath: "mod_ahbot.conf", },
+                            ],
+                            command: [
+                                "/entrypoint.sh",
+                                "/azeroth-server/bin/worldserver",
+                            ],
+                        },
+                    },
+                    securityContext: {
+                        runAsUser: 999,
+                        runAsGroup: 999,
+                        fsGroup: 999,
+                    },
+                    volumes_: {
+                        data: kube.PersistentVolumeClaimVolume(wow.data),
+                        etc: kube.SecretVolume(wow.etc),
+                    },
+                },
+            },
+        },
+    },
+
+    authserverDeploy: ns.Contain(kube.Deployment(cfg.prefix + "authserver")) {
+        spec+: {
+            template+: {
+                spec+: {
+                    containers_: {
+                        default: kube.Container("default") {
+                            image: cfg.images.acore,
+                            volumeMounts_: {
+                                etc: { mountPath: "/azeroth-server/etc/authserver.conf", subPath: "authserver.conf", },
+                            },
+                            command: [
+                                "/azeroth-server/bin/authserver",
+                            ],
+                        },
+                    },
+                    securityContext: {
+                        runAsUser: 999,
+                        runAsGroup: 999,
+                    },
+                    volumes_: {
+                        etc: kube.SecretVolume(wow.etc),
+                    },
+                },
+            },
+        },
+    },
+    
+    soapSvc: ns.Contain(kube.Service(cfg.prefix + "worldserver-soap")) {
+        target_pod:: wow.worldserverDeploy.spec.template,
+        spec+: {
+            ports: [
+                { name: "soap", port: 7878, targetPort: 7878, protocol: "TCP" },
+            ],
+        },
+    },
+    worldserverSvc: ns.Contain(kube.Service(cfg.prefix + "worldserver")) {
+        target_pod:: wow.worldserverDeploy.spec.template,
+        metadata+: {
+            annotations+: {
+                "metallb.universe.tf/allow-shared-ip": "%s/%ssvc" % [cfg.namespace, cfg.prefix],
+            },
+        },
+        spec+: {
+            ports: [
+                { name: "worldserver", port: 8085, targetPort: 8085, protocol: "TCP" },
+            ],
+            type: "LoadBalancer",
+            externalTrafficPolicy: "Cluster",
+            loadBalancerIP: cfg.address,
+        },
+    },
+    authserverSvc: ns.Contain(kube.Service(cfg.prefix + "authserver")) {
+        target_pod:: wow.authserverDeploy.spec.template,
+        metadata+: {
+            annotations+: {
+                "metallb.universe.tf/allow-shared-ip": "%s/%ssvc" % [cfg.namespace, cfg.prefix],
+            },
+        },
+        spec+: {
+            ports: [
+                { name: "authserver", port: 3724, targetPort: 3724, protocol: "TCP" },
+            ],
+            type: "LoadBalancer",
+            externalTrafficPolicy: "Cluster",
+            loadBalancerIP: cfg.address,
+        },
+    },
+
+    panelSecret: ns.Contain(kube.Secret(cfg.prefix + "panel-secret")) {
+        data+: {
+            soapPassword: std.base64(cfg.panel.soap.password),
+            secret: std.base64(cfg.panel.secret),
+            oauthSecret: std.base64(cfg.panel.oauth.clientSecret),
+            "motd.txt": std.base64(cfg.panel.motd),
+        },
+    },
+    panelData: ns.Contain(kube.PersistentVolumeClaim(cfg.prefix + "panel-data")) {
+        spec+: {
+            storageClassName: "waw-hdd-redundant-3",
+            accessModes: ["ReadWriteOnce"],
+            resources: {
+                requests: {
+                    storage: "128Mi",
+                },
+            },
+        },
+    },
+    panelDeploy: ns.Contain(kube.Deployment(cfg.prefix + "panel")) {
+        spec+: {
+            template+: {
+                spec+: {
+                    containers_: {
+                        default: kube.Container("default") {
+                            image: cfg.images.panel,
+                            env_: {
+                                SOAP_PASSWORD: kube.SecretKeyRef(wow.panelSecret, "soapPassword"),
+                                SECRET: kube.SecretKeyRef(wow.panelSecret, "secret"),
+                                OAUTH_SECRET: kube.SecretKeyRef(wow.panelSecret, "oauthSecret"),
+                            },
+                            command: [
+                                "/personal/q3k/wow/panel/panel",
+                                "-listen", "0.0.0.0:8080",
+                                "-db", "/data/panel.db",
+                                "-soap_address", "http://%s" % [wow.soapSvc.host_colon_port],
+                                "-soap_password", "$(SOAP_PASSWORD)",
+                                "-secret", "$(SECRET)",
+                                "-oauth_client_id", cfg.panel.oauth.clientID, 
+                                "-oauth_client_secret", "$(OAUTH_SECRET)",
+                                "-oauth_redirect_url", cfg.panel.oauth.redirectURL,
+                                "-motd", "/secret/motd.txt",
+                            ],
+                            volumeMounts_: {
+                                data: { mountPath: "/data" },
+                                secret: { mountPath: "/secret" },
+                            },
+                        },
+                    },
+                    volumes_: {
+                        data: kube.PersistentVolumeClaimVolume(wow.panelData),
+                        secret: kube.SecretVolume(wow.panelSecret),
+                    },
+                },
+            },
+        },
+    },
+    panelSvc: ns.Contain(kube.Service(cfg.prefix + "panel")) {
+        target_pod:: wow.panelDeploy.spec.template,
+        spec+: {
+            ports: [
+                { name: "web", port: 8080, targetPort: 8080, protocol: "TCP" },
+            ],
+        },
+    },
+    panelIngress: ns.Contain(kube.Ingress(cfg.prefix + "panel")) {
+        metadata+: {
+            annotations+: {
+                "kubernetes.io/tls-acme": "true",
+                "certmanager.k8s.io/cluster-issuer": "letsencrypt-prod",
+            },
+        },
+        spec+: {
+            tls: [
+                {
+                    hosts: [cfg.panel.domain],
+                    secretName: cfg.prefix + "panel-tls",
+                },
+            ],
+            rules: [
+                {
+                    host: cfg.panel.domain,
+                    http: {
+                        paths: [
+                            { path: "/", backend: wow.panelSvc.name_port },
+                        ],
+                    },
+                }
+            ],
+        },
+    },
+}