app/radio: init
diff --git a/app/radio/kube/radio.libsonnet b/app/radio/kube/radio.libsonnet
new file mode 100644
index 0000000..08a7ffb
--- /dev/null
+++ b/app/radio/kube/radio.libsonnet
@@ -0,0 +1,154 @@
+local kube = import "../../../kube/kube.libsonnet";
+
+{
+    local radio = self,
+    local cfg = radio.cfg,
+
+    cfg:: {
+        namespace: error "namespace must be set",
+        appName: "radio",
+        prefix: "", # if set, should be 'foo-'
+        port: 2137,
+
+        icecast: {
+                location: error "location must be set",
+                admin: error "admin must be set",
+                limits: {
+                        clients: 100,
+                        sources: 2,
+                        threadpool: 5,
+                        queueSize: 524288,
+                        clientTimeout: 30,
+                        headerTimeout: 15,
+                        sourceTimeout: 10,
+                        burstOnConnect: true,
+                        burstSize: 65535,
+                },
+                authentication: {
+                        sourcePassword: error "source password must be set",
+                        relayPassword: error "relay password must be set",
+                        adminPassword: error "admin password must be set",
+                },
+                hostname: "localhost",
+                listenPort: 8080,
+                mounts: [],
+        },
+
+        tag: "latest",
+        image: "registry.k0.hswaw.net/app/radio:" + cfg.tag,
+        resources: {
+            requests: {
+                cpu: "100m",
+                memory: "100Mi",
+            },
+            limits: {
+                cpu: "300m",
+                memory: "200Mi",
+            },
+        },
+    },
+
+    mount:: {
+        username: error "mount username must be defined",
+        password: error "mount password must be defined",
+        genre: "Classical",
+        bitrate: 128,
+        hidden: false,
+    },
+
+    makeName(suffix):: cfg.prefix + suffix,
+
+    metadata:: {
+        namespace: cfg.namespace,
+        labels: {
+            "app.kubernetes.io/name": cfg.appName,
+            "app.kubernetes.io/managed-by": "kubecfg",
+            "app.kubernetes.io/component": "radio",
+        },
+    },
+
+    configMap: kube.ConfigMap(radio.makeName("icecast")) {
+        metadata+: radio.metadata,
+        data: {
+            "icecast.xml": std.manifestXmlJsonml(["icecast",
+                ["location", cfg.icecast.location],
+                ["admin", cfg.icecast.admin],
+                ["limits",
+                    ["clients", std.toString(cfg.icecast.limits.clients)],
+                    ["sources", std.toString(cfg.icecast.limits.sources)],
+                    ["threadpool", std.toString(cfg.icecast.limits.threadpool)],
+                    ["queue-size", std.toString(cfg.icecast.limits.queueSize)],
+                    ["client-timeout", std.toString(cfg.icecast.limits.clientTimeout)],
+                    ["header-timeout", std.toString(cfg.icecast.limits.headerTimeout)],
+                    ["source-timeout", std.toString(cfg.icecast.limits.sourceTimeout)],
+                    ["burst-on-connect", if cfg.icecast.limits.burstOnConnect then "1" else "0"],
+                    ["burst-size", std.toString(cfg.icecast.limits.burstSize)],
+                ],
+                ["authentication",
+                    ["source-password", cfg.icecast.authentication.sourcePassword],
+                    ["relay-password", cfg.icecast.authentication.relayPassword],
+                    ["admin-password", cfg.icecast.authentication.adminPassword],
+                ],
+                ["hostname", cfg.icecast.hostname],
+                ["listen-socket", 
+                    ["port", std.toString(cfg.icecast.listenPort)],
+                ],
+                ["logging",
+                    ["accesslog", "-"],
+                    ["errorlog", "-"],
+                    ["loglevel", "2"],
+                ],
+                ["security",
+                    ["chroot", "0"],
+                ],
+             ] + [
+                ["mount", {type: "normal"},
+                    ["mount-name", m],
+                    ["username", cfg.icecast.mounts[m].username],
+                    ["password", cfg.icecast.mounts[m].password],
+                    ["public", if cfg.icecast.mounts[m].public then "1" else "0"],
+                    ["genre", cfg.icecast.mounts[m].genre],
+                    ["bitrate", std.toString(cfg.icecast.mounts[m].bitrate)],
+                    ["hidden", if cfg.icecast.mounts[m].hidden then "1" else "0"],
+                ]
+                for m in std.objectFields(cfg.icecast.mounts)
+             ]),
+        },
+    },
+
+    deployment: kube.Deployment(radio.makeName("icecast")) {
+        metadata+: radio.metadata,
+        spec+: {
+            replicas: 1,
+            template+: {
+                spec+: {
+                    volumes_: {
+                        config: kube.ConfigMapVolume(radio.configMap),
+                    },
+                    containers_: {
+                        radio: kube.Container(radio.makeName("radio")) {
+                            image: cfg.image,
+                            ports_: {
+                                client: { containerPort: cfg.icecast.listenPort },
+                            },
+                            volumeMounts_: {
+                                config: { mountPath: "/usr/share/icecast/icecast.xml", subPath: "icecast.xml" },
+                            },
+                            resources: cfg.resources,
+                        },
+                    },
+                },
+            },
+        },
+    },
+    svc: kube.Service(radio.makeName("icecast")) {
+        metadata+: radio.metadata,
+        target_pod:: radio.deployment.spec.template,
+        spec+: {
+            ports: [
+                { name: "client", port: cfg.port, targetPort: cfg.icecast.listenPort, protocol: "TCP" },
+            ],
+            type: "LoadBalancer",
+        },
+    },
+}