app/registry: initial docker registry setup
diff --git a/app/registry/prod.jsonnet b/app/registry/prod.jsonnet
new file mode 100644
index 0000000..0b7fea5
--- /dev/null
+++ b/app/registry/prod.jsonnet
@@ -0,0 +1,240 @@
+local kube = import "../../kube/kube.libsonnet";
+local cm = import "../../cluster/kube/lib/cert-manager.libsonnet";
+
+{
+    local app = self,
+    local cfg = app.cfg,
+    cfg:: {
+        namespace: "registry",
+        domain: "k0.hswaw.net",
+    },
+
+    metadata(component):: {
+        namespace: cfg.namespace,
+        labels: {
+            "app.kubernetes.io/name": "registry",
+            "app.kubernetes.io/managed-by": "kubecfg",
+            "app.kubernetes.io/component": component,
+        },
+    },
+
+    namespace: kube.Namespace(cfg.namespace),
+
+    registryIssuer: cm.Issuer("registry-issuer") {
+        metadata+: app.metadata("registry-issuer"),
+        spec: {
+            selfSigned: {},
+        },
+    },
+    authCertificate: cm.Certificate("auth") {
+        metadata+: app.metadata("auth"),
+        spec: {
+            secretName: "auth-internal",
+            duration: "43800h0m0s", // 5 years
+            issuerRef: {
+                name: app.registryIssuer.metadata.name,
+            },
+            commonName: "auth.registry",
+        },
+    },
+    registryCertificate: cm.Certificate("registry") {
+        metadata+: app.metadata("registry"),
+        spec: {
+            secretName: "registry-internal",
+            duration: "43800h0m0s", // 5 years
+            issuerRef: {
+                name: app.registryIssuer.metadata.name,
+            },
+            commonName: "registry.registry",
+        },
+    },
+
+    registryConfig: kube.ConfigMap("registry-config") {
+        metadata+: app.metadata("registry-config"),
+        data: {
+            "config.yml": std.manifestYamlDoc({
+                version: "0.1",
+                log: {
+                    fields: {
+                        service: "registry",
+                    },
+                },
+                storage: {
+                    cache: {
+                        blobdescriptor: "inmemory",
+                    },
+                    filesystem: {
+                        rootdirectory: "/var/lib/registry",
+                    },
+                },
+                http: {
+                    addr: ":5000",
+                    headers: {
+                        "X-Content-Type-Options": ["nosniff"],
+                    },
+                    tls: {
+                        certificate: "/certs/tls.crt",
+                        key: "/certs/tls.key",
+                    },
+                },
+                health: {
+                    storagedriver: {
+                        enabled: true,
+                        interval: "10s",
+                        threshold: 3,
+                    },
+                },
+                auth: {
+                    token: {
+                        realm: "https://registry.%s/auth" % [cfg.domain],
+                        service: "my.docker.registry",
+                        issuer: "registry.%s auth server" % [cfg.domain],
+                        rootcertbundle: "/authcerts/tls.crt",
+                    },
+                },
+            }),
+        },
+    },
+
+    authConfig: kube.ConfigMap("auth-config") {
+        metadata+: app.metadata("auth-config"),
+        data: {
+            "auth_config.yml": std.manifestYamlDoc({
+                server: {
+                    addr: ":5001",
+                    certificate: "/certs/tls.crt",
+                    key: "/certs/tls.key",
+                },
+                token: {
+                    issuer: "registry.%s auth server" % [cfg.domain],
+                    expiration: 900,
+                },
+                users: {
+                    # Password is specified as a BCrypt hash. Use `htpasswd -nB USERNAME` to generate.
+                    "admin": {
+                         password: "$2y$05$LO.vzwpWC5LZGqThvEfznu8qhb5SGqvBSWY1J3yZ4AxtMRZ3kN5jC",  # badmin
+                    },
+                    "test": {
+                         password: "$2y$05$WuwBasGDAgr.QCbGIjKJaep4dhxeai9gNZdmBnQXqpKly57oNutya",  # 123
+                    },
+                },
+                acl: [
+                    {
+                        match: {account: "admin"},
+                        actions: ["*"],
+                        comment: "Admin has full access to everything.",
+                    },
+                    {
+                        match: {account: "user"},
+                        actions: ["pull"],
+                        comment: "User \"user\" can pull stuff.",
+                    },
+                ],
+            }),
+        }
+    },
+
+    authDeployment: kube.Deployment("auth") {
+        metadata+: app.metadata("auth"),
+        spec+: {
+            replicas: 1,
+            template+: {
+                spec+: {
+                    volumes_: {
+                        config: kube.ConfigMapVolume(app.authConfig),
+                        certs: {
+                            secret: { secretName: app.authCertificate.spec.secretName },
+                        },
+                    },
+                    containers_: {
+                        auth: kube.Container("auth") {
+                            image: "cesanta/docker_auth:1",
+                            volumeMounts_: {
+                                config: { mountPath: "/config" },
+                                certs: { mountPath: "/certs" },
+                            },
+                        },
+                    },
+                },
+            },
+        },
+    },
+    authService: kube.Service("auth") {
+        metadata+: app.metadata("auth"),
+        target_pod:: app.authDeployment.spec.template,
+        spec+: {
+            type: "ClusterIP",
+            ports: [
+                { name: "auth", port: 5001, targetPort: 5001, protocol: "TCP" },
+            ],
+        }
+    },
+    registryDeployment: kube.Deployment("docker-registry") {
+        metadata+: app.metadata("docker-registry"),
+        spec+: {
+            replicas: 1,
+            template+: {
+                spec+: {
+                    volumes_: {
+                        config: kube.ConfigMapVolume(app.registryConfig),
+                        certs: {
+                            secret: { secretName: app.registryCertificate.spec.secretName },
+                        },
+                        authcerts: {
+                            secret: { secretName: app.authCertificate.spec.secretName },
+                        },
+                    },
+                    containers_: {
+                        registry: kube.Container("docker-registry") {
+                            image: "registry:2",
+                            args: ["/config/config.yml"],
+                            volumeMounts_: {
+                                config: { mountPath: "/config" },
+                                certs: { mountPath: "/certs" },
+                                authcerts: { mountPath: "/authcerts" },
+                            },
+                        },
+                    },
+                },
+            },
+        },
+    },
+    registryService: kube.Service("docker-registry") {
+        metadata+: app.metadata("docker-registry"),
+        target_pod:: app.registryDeployment.spec.template,
+        spec+: {
+            type: "ClusterIP",
+            ports: [
+                { name: "registry", port: 5000, targetPort: 5000, protocol: "TCP" },
+            ],
+        }
+    },
+    registryIngress: kube.Ingress("registry") {
+        metadata+: app.metadata("registry") {
+            annotations+: {
+                "kubernetes.io/tls-acme": "true",
+                "certmanager.k8s.io/cluster-issuer": "letsencrypt-prod",
+                "nginx.ingress.kubernetes.io/backend-protocol": "HTTPS",
+            },
+        },
+        spec+: {
+            tls: [
+                {
+                    hosts: ["registry.%s" % [cfg.domain]],
+                    secretName: "registry-tls",
+                },
+            ],
+            rules: [
+                {
+                    host: "registry.%s" % [cfg.domain],
+                    http: {
+                        paths: [
+                            { path: "/auth", backend: app.authService.name_port },
+                            { path: "/", backend: app.registryService.name_port },
+                        ]
+                    },
+                }
+            ],
+        },
+    },
+}