games/factorio: add modproxy

This adds a mod proxy system, called, well, modproxy.

It sits between Factorio server instances and the Factorio mod portal,
allowing for arbitrary mod download without needing the servers to know
Factorio credentials.

Change-Id: I7bc405a25b6f9559cae1f23295249f186761f212
diff --git a/personal/q3k/factorio/kube/factorio.libsonnet b/personal/q3k/factorio/kube/factorio.libsonnet
index fc64aaa..4f2ff39 100644
--- a/personal/q3k/factorio/kube/factorio.libsonnet
+++ b/personal/q3k/factorio/kube/factorio.libsonnet
@@ -11,6 +11,7 @@
         appName: "factorio",
         storageClassName: "waw-hdd-redundant-2",
         prefix: "", # if set, should be 'foo-'
+        proxyImage: error "proxyImage must be set",
 
         rconPort: 2137,
         rconPassword: "farts",
@@ -27,6 +28,8 @@
                 memory: "1Gi",
             },
         },
+
+        mods: [],
     },
 
 
@@ -67,6 +70,16 @@
         },
     },
 
+    configMap: kube.ConfigMap(factorio.makeName("config")) {
+        metadata+: factorio.metadata,
+        data: {
+            "mods.pb.text": std.join("\n", [
+                "mod { name: \"%s\" version: \"%s\" }" % [m.name, m.version],
+                for m in cfg.mods
+            ]),
+        },
+    },
+
     deployment: kube.Deployment(factorio.makeName("factorio")) {
         metadata+: factorio.metadata,
         spec+: {
@@ -76,6 +89,23 @@
                     volumes_: {
                         data: kube.PersistentVolumeClaimVolume(factorio.volumeClaimData),
                         mods: kube.PersistentVolumeClaimVolume(factorio.volumeClaimMods),
+                        config: kube.ConfigMapVolume(factorio.configMap),
+                    },
+                    initContainers_: {
+                        modproxy: kube.Container("modproxy") {
+                            image: cfg.proxyImage,
+                            command: [
+                                "/games/factorio/modproxy/client",
+                                "-hspki_disable",
+                                "-factorio_path", "/factorio",
+                                "-proxy", "proxy.factorio.svc.cluster.local:4200",
+                                "-config_path", "/factorio/mods.pb.text",
+                            ],
+                            volumeMounts_: {
+                                mods: { mountPath: "/factorio/mods" },
+                                config: { mountPath: "/factorio/mods.pb.text", subPath: "mods.pb.text" },
+                            },
+                        },
                     },
                     containers_: {
                         factorio: kube.Container(factorio.makeName("factorio")) {
diff --git a/personal/q3k/factorio/kube/prod.jsonnet b/personal/q3k/factorio/kube/prod.jsonnet
index 7a8a060..27dd38d 100644
--- a/personal/q3k/factorio/kube/prod.jsonnet
+++ b/personal/q3k/factorio/kube/prod.jsonnet
@@ -9,15 +9,106 @@
 {
     local prod = self,
 
+    proxyImage:: "registry.k0.hswaw.net/games/factorio/modproxy:1589157915-eafe7be328477e8a6590c4210466ef12901f1b9a",
+
     namespace: kube.Namespace("factorio"),
     instance(name, tag):: factorio {
         cfg+: {
             namespace: "factorio",
             prefix: name + "-",
             tag: tag,
+            proxyImage: prod.proxyImage,
         }
     },
 
-    q3k: prod.instance("q3k", "1.0.0-1"),
-    pymods: prod.instance("pymods", "1.0.0-1"),
+    proxy: {
+        pvc: kube.PersistentVolumeClaim("proxy-cas") {
+            metadata+: {
+                namespace: "factorio",
+            },
+            spec+: {
+                storageClassName: "waw-hdd-redundant-3",
+                accessModes: [ "ReadWriteOnce" ],
+                resources: {
+                    requests: {
+                        storage: "32Gi",
+                    },
+                },
+            },
+        },
+        deploy: kube.Deployment("proxy") {
+            metadata+: {
+                namespace: "factorio",
+            },
+            spec+: {
+                template+: {
+                    spec+: {
+                        volumes_: {
+                            cas: kube.PersistentVolumeClaimVolume(prod.proxy.pvc),
+                        },
+                        containers_: {
+                            proxy: kube.Container("proxy") {
+                                image:prod.proxyImage,
+                                command: [
+                                    "/games/factorio/modproxy/modproxy",
+                                    "-hspki_disable",
+                                    "-cas_directory", "/mnt/cas",
+                                    "-listen_address", "0.0.0.0:4200",
+                                ],
+                                volumeMounts_: {
+                                    cas: { mountPath: "/mnt/cas" },
+                                },
+                                ports_: {
+                                    client: { containerPort: 4200 },
+                                },
+                            },
+                        },
+                    },
+                },
+            },
+        },
+        svc: kube.Service("proxy") {
+            metadata+: {
+                namespace: "factorio",
+            },
+            target_pod:: prod.proxy.deploy.spec.template,
+            spec+: {
+                ports: [
+                    { name: "client", port: 4200, targetPort: 4200, protocol: "TCP" },
+                ],
+            },
+        },
+    },
+
+    local mod = function(name, version) { name: name, version: version },
+
+    q3k: prod.instance("q3k", "1.0.0-1") {
+        cfg+: {
+            mods: [
+                mod("Squeak Through", "1.8.0"),
+                mod("Bottleneck", "0.11.4"),
+            ],
+        },
+    },
+    pymods: prod.instance("pymods", "1.0.0-1") {
+        cfg+: {
+            mods: [
+                mod("Bottleneck", "0.11.4"),
+                mod("FARL", "4.0.2"),
+                mod("Squeak Through", "1.8.0"),
+                mod("pycoalprocessing", "1.8.3"),
+                mod("pycoalprocessinggraphics", "1.0.7"),
+                mod("pyfusionenergy", "1.6.3"),
+                mod("pyfusionenergygraphics", "1.0.5"),
+                mod("pyhightech", "1.6.2"),
+                mod("pyhightechgraphics", "1.0.8"),
+                mod("pyindustry", "1.4.7"),
+                mod("pyrawores", "2.1.5"),
+                mod("pyraworesgraphics", "1.0.4"),
+                mod("rso-mod", "6.0.11"),
+                mod("stdlib", "1.4.3"),
+                mod("what-is-it-really-used-for", "1.5.13"),
+            ],
+        },
+    },
 }