Merge "third_party/py: bump cffi and psycopg2 to latest versions"
diff --git a/.bazelrc b/.bazelrc
index 03cd77d..419641e 100644
--- a/.bazelrc
+++ b/.bazelrc
@@ -2,6 +2,7 @@
 build --host_force_python=PY2
 test --host_force_python=PY2
 run --host_force_python=PY2
+build --stamp
 build --workspace_status_command=./bzl/workspace-status.sh
 test --build_tests_only
 test --test_output=errors
diff --git a/BUILD b/BUILD
index 8c4b2f8..1264f0a 100644
--- a/BUILD
+++ b/BUILD
@@ -2,7 +2,6 @@
 load("@bazel_gazelle//:def.bzl", "gazelle")
 
 # gazelle:prefix code.hackerspace.pl/hscloud
-# gazelle:repository_macro third_party/go/repositories.bzl%go_repositories
 gazelle(
     name = "gazelle",
 )
diff --git a/OWNERS b/OWNERS
index eff4374..559b342 100644
--- a/OWNERS
+++ b/OWNERS
@@ -1,3 +1,4 @@
 owners:
 - q3k
 - informatic
+- implr
diff --git a/WORKSPACE b/WORKSPACE
index 9474e10..031d55a 100644
--- a/WORKSPACE
+++ b/WORKSPACE
@@ -82,6 +82,7 @@
 go_rules_dependencies()
 go_register_toolchains()
 
+# gazelle:repository_macro third_party/go/repositories.bzl%go_repositories
 load("@bazel_gazelle//:deps.bzl", "gazelle_dependencies")
 gazelle_dependencies()
 
diff --git a/cluster/kube/k0.libsonnet b/cluster/kube/k0.libsonnet
index 06671b4..c230913 100644
--- a/cluster/kube/k0.libsonnet
+++ b/cluster/kube/k0.libsonnet
@@ -16,7 +16,7 @@
         local k0 = self,
         cluster: cluster.Cluster("k0", "hswaw.net") {
             cfg+: {
-                storageClassNameParanoid: k0.ceph.waw2Pools.blockParanoid.name,
+                storageClassNameParanoid: k0.ceph.waw3Pools.blockRedundant.name,
             },
             metallb+: {
                 cfg+: {
@@ -52,7 +52,7 @@
             cfg+: {
                 domain: "registry.%s" % [k0.cluster.fqdn],
                 storageClassName: k0.cluster.cfg.storageClassNameParanoid,
-                objectStorageName: "waw-hdd-redundant-2-object",
+                objectStorageName: "waw-hdd-redundant-3-object",
             },
         },
 
@@ -67,6 +67,9 @@
                     ],
                     // Host path on SSD.
                     hostPath: "/var/db/crdb-waw1",
+                    extraDNS: [
+                        "crdb-waw1.hswaw.net",
+                    ],
                 },
             },
             clients: {
@@ -74,6 +77,7 @@
                 cccampixDev: k0.cockroach.waw2.Client("cccampix-dev"),
                 buglessDev: k0.cockroach.waw2.Client("bugless-dev"),
                 sso: k0.cockroach.waw2.Client("sso"),
+                herpDev: k0.cockroach.waw2.Client("herp-dev"),
             },
         },
 
diff --git a/cluster/kube/lib/cockroachdb.libsonnet b/cluster/kube/lib/cockroachdb.libsonnet
index 0b58180..8ebad52 100644
--- a/cluster/kube/lib/cockroachdb.libsonnet
+++ b/cluster/kube/lib/cockroachdb.libsonnet
@@ -53,6 +53,7 @@
 
             namespace: null,
             ownNamespace: cluster.cfg.namespace == null,
+            extraDNS: [],
         },
 
         namespaceName:: if cluster.cfg.namespace != null then cluster.cfg.namespace else name,
@@ -122,7 +123,7 @@
                     ] + [
                         "%s.cluster.local" % s.service.host
                         for s in cluster.servers
-                    ],
+                    ] + cluster.cfg.extraDNS,
                 },
             },
 
diff --git a/cluster/kube/lib/prodvider.libsonnet b/cluster/kube/lib/prodvider.libsonnet
index 5805993..8eaa834 100644
--- a/cluster/kube/lib/prodvider.libsonnet
+++ b/cluster/kube/lib/prodvider.libsonnet
@@ -9,7 +9,7 @@
 
         cfg:: {
             namespace: "prodvider",
-            image: "registry.k0.hswaw.net/cluster/prodvider:1567256363-71a21c769369d013972d8dd0a71b83bee3e6848e",
+            image: "registry.k0.hswaw.net/q3k/prodvider:1596298570-f3312ef77ed0db94e20944efc6395750072f54d5",
 
             apiEndpoint: error "API endpoint must be set",
 
diff --git a/cluster/kube/lib/registry.libsonnet b/cluster/kube/lib/registry.libsonnet
index d909403..cb2f3f7 100644
--- a/cluster/kube/lib/registry.libsonnet
+++ b/cluster/kube/lib/registry.libsonnet
@@ -73,9 +73,9 @@
                             blobdescriptor: "inmemory",
                         },
                         s3: {
-                            regionendpoint: "https://object.ceph-waw2.hswaw.net",
+                            regionendpoint: "https://object.ceph-waw3.hswaw.net",
                             bucket: "registry",
-                            region: "waw-hdd-redunant-2-object:default-placement",
+                            region: "waw-hdd-redunant-3-object:default-placement",
                         },
                     },
                     http: {
@@ -110,8 +110,8 @@
             },
         },
 
-        authVolumeClaim: kube.PersistentVolumeClaim("auth-token-storage") {
-            metadata+: env.metadata("auth-token-storage"),
+        authVolumeClaim: kube.PersistentVolumeClaim("auth-token-storage-3") {
+            metadata+: env.metadata("auth-token-storage-3"),
             spec+: {
                 storageClassName: cfg.storageClassName,
                 accessModes: [ "ReadWriteOnce" ],
@@ -314,7 +314,7 @@
 
         registryStorageUser: kube.CephObjectStoreUser("registry") {
             metadata+: {
-                namespace: "ceph-waw2",
+                namespace: "ceph-waw3",
             },
             spec: {
                 store: cfg.objectStorageName,
diff --git a/cluster/prodaccess/BUILD.bazel b/cluster/prodaccess/BUILD.bazel
index 5124ffc..6c72082 100644
--- a/cluster/prodaccess/BUILD.bazel
+++ b/cluster/prodaccess/BUILD.bazel
@@ -3,6 +3,7 @@
 go_library(
     name = "go_default_library",
     srcs = [
+        "hspki.go",
         "kubernetes.go",
         "prodaccess.go",
     ],
@@ -11,6 +12,7 @@
     deps = [
         "//cluster/certs:go_default_library",
         "//cluster/prodvider/proto:go_default_library",
+        "//go/pki:go_default_library",
         "@com_github_golang_glog//:go_default_library",
         "@org_golang_google_grpc//:go_default_library",
         "@org_golang_google_grpc//credentials:go_default_library",
diff --git a/cluster/prodaccess/hspki.go b/cluster/prodaccess/hspki.go
new file mode 100644
index 0000000..2fcfaf0
--- /dev/null
+++ b/cluster/prodaccess/hspki.go
@@ -0,0 +1,33 @@
+package main
+
+import (
+	"io/ioutil"
+	"os"
+
+	"github.com/golang/glog"
+
+	pb "code.hackerspace.pl/hscloud/cluster/prodvider/proto"
+	"code.hackerspace.pl/hscloud/go/pki"
+)
+
+func useHSPKIKeys(keys *pb.HSPKIKeys) {
+	path, err := pki.DeveloperCredentialsLocation()
+	err = os.MkdirAll(path, 0700)
+	if err != nil {
+		glog.Exitf("mkdir %q: %v", path, err)
+	}
+
+	for _, el := range []struct {
+		target string
+		data   []byte
+	}{
+		{path + "/ca.crt", keys.Ca},
+		{path + "/tls.crt", keys.Cert},
+		{path + "/tls.key", keys.Key},
+	} {
+		err := ioutil.WriteFile(el.target, el.data, 400)
+		if err != nil {
+			glog.Exitf("Failed to write %q: %v", el.target, err)
+		}
+	}
+}
diff --git a/cluster/prodaccess/prodaccess.go b/cluster/prodaccess/prodaccess.go
index e0e8ec2..1153bab 100644
--- a/cluster/prodaccess/prodaccess.go
+++ b/cluster/prodaccess/prodaccess.go
@@ -99,6 +99,9 @@
 	}
 
 	useKubernetesKeys(res.KubernetesKeys)
+	fmt.Printf("-> Kubernetes credentials installed\n")
+	useHSPKIKeys(res.HspkiKeys)
+	fmt.Printf("-> HSPKI credentials installed\n")
 
 	return true
 }
diff --git a/cluster/prodvider/BUILD.bazel b/cluster/prodvider/BUILD.bazel
index 14690b7..c15ab66 100644
--- a/cluster/prodvider/BUILD.bazel
+++ b/cluster/prodvider/BUILD.bazel
@@ -5,6 +5,7 @@
     name = "go_default_library",
     srcs = [
         "certs.go",
+        "hspki.go",
         "kubernetes.go",
         "main.go",
         "service.go",
@@ -15,6 +16,7 @@
         "//cluster/prodvider/proto:go_default_library",
         "@com_github_cloudflare_cfssl//config:go_default_library",
         "@com_github_cloudflare_cfssl//csr:go_default_library",
+        "@com_github_cloudflare_cfssl//helpers:go_default_library",
         "@com_github_cloudflare_cfssl//signer:go_default_library",
         "@com_github_cloudflare_cfssl//signer/local:go_default_library",
         "@com_github_golang_glog//:go_default_library",
@@ -59,6 +61,6 @@
     image = ":runtime",
     format = "Docker",
     registry = "registry.k0.hswaw.net",
-    repository = "cluster/prodvider",
+    repository = "q3k/prodvider",
     tag = "{BUILD_TIMESTAMP}-{STABLE_GIT_COMMIT}",
 )
diff --git a/cluster/prodvider/hspki.go b/cluster/prodvider/hspki.go
new file mode 100644
index 0000000..e747889
--- /dev/null
+++ b/cluster/prodvider/hspki.go
@@ -0,0 +1,103 @@
+package main
+
+import (
+	"encoding/pem"
+	"fmt"
+	"time"
+
+	"github.com/cloudflare/cfssl/config"
+	"github.com/cloudflare/cfssl/csr"
+	"github.com/cloudflare/cfssl/helpers"
+	"github.com/cloudflare/cfssl/signer"
+	"github.com/cloudflare/cfssl/signer/local"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+
+	pb "code.hackerspace.pl/hscloud/cluster/prodvider/proto"
+)
+
+// hspkiSigner returns a cfssl signer (CA) for HSPKI, by loading the CA
+// cert/key from Kubernetes.
+func (p *prodvider) hspkiSigner() (*local.Signer, error) {
+	policy := &config.Signing{
+		Profiles: map[string]*config.SigningProfile{
+			"client-server": &config.SigningProfile{
+				Usage:        []string{"signing", "key encipherment", "server auth", "client auth"},
+				ExpiryString: "30d",
+			},
+		},
+		Default: config.DefaultConfig(),
+	}
+
+	secret, err := p.k8s.CoreV1().Secrets("cert-manager").Get("pki-selfsigned-cert", metav1.GetOptions{})
+	if err != nil {
+		return nil, fmt.Errorf("hspki secret get failed: %w", err)
+	}
+
+	parsedCa, err := helpers.ParseCertificatePEM(secret.Data["tls.crt"])
+	if err != nil {
+		return nil, fmt.Errorf("when parsing tls.crt: %w", err)
+	}
+
+	priv, err := helpers.ParsePrivateKeyPEMWithPassword(secret.Data["tls.key"], nil)
+	if err != nil {
+		return nil, fmt.Errorf("when parsing tls.key: %w", err)
+	}
+
+	return local.NewSigner(priv, parsedCa, signer.DefaultSigAlgo(priv), policy)
+}
+
+// hspkiCreds returns a HSPKI certificate/key for an SSO user. The returned
+// certificate is valida for both server and client usage.
+func (p *prodvider) hspkiCreds(username string) (*pb.HSPKIKeys, error) {
+	principal := fmt.Sprintf("%s.sso.hswaw.net", username)
+
+	s, err := p.hspkiSigner()
+	if err != nil {
+		return nil, fmt.Errorf("hspkiSigner: %w", err)
+	}
+
+	signerCert, _ := s.Certificate("", "")
+	req := &csr.CertificateRequest{
+		CN: principal,
+		KeyRequest: &csr.BasicKeyRequest{
+			A: "rsa",
+			S: 4096,
+		},
+		Names: []csr.Name{
+			{
+				O:  "prodvider",
+				OU: fmt.Sprintf("Prodvider HSPKI Cert for %s", username),
+			},
+		},
+	}
+
+	g := &csr.Generator{
+		Validator: func(req *csr.CertificateRequest) error { return nil },
+	}
+
+	csrPEM, keyPEM, err := g.ProcessRequest(req)
+	if err != nil {
+		return nil, fmt.Errorf("when making CSR: %w", err)
+	}
+
+	signReq := signer.SignRequest{
+		Hosts:    []string{},
+		Request:  string(csrPEM),
+		Profile:  "client-server",
+		NotAfter: time.Now().Add(9 * time.Hour),
+	}
+
+	certPEM, err := s.Sign(signReq)
+	if err != nil {
+		return nil, fmt.Errorf("when issuing certificate: %w", err)
+	}
+
+	caPEM := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: signerCert.Raw})
+
+	return &pb.HSPKIKeys{
+		Ca:        caPEM,
+		Cert:      certPEM,
+		Key:       keyPEM,
+		Principal: principal,
+	}, nil
+}
diff --git a/cluster/prodvider/proto/BUILD.bazel b/cluster/prodvider/proto/BUILD.bazel
index 2efd457..0817dfb 100644
--- a/cluster/prodvider/proto/BUILD.bazel
+++ b/cluster/prodvider/proto/BUILD.bazel
@@ -1,3 +1,4 @@
+load("@rules_proto//proto:defs.bzl", "proto_library")
 load("@io_bazel_rules_go//go:def.bzl", "go_library")
 load("@io_bazel_rules_go//proto:def.bzl", "go_proto_library")
 
diff --git a/cluster/prodvider/proto/prodvider.proto b/cluster/prodvider/proto/prodvider.proto
index 1ae2798..ba5bf9d 100644
--- a/cluster/prodvider/proto/prodvider.proto
+++ b/cluster/prodvider/proto/prodvider.proto
@@ -15,6 +15,7 @@
     }
     Result result = 1;
     KubernetesKeys kubernetes_keys = 2;
+    HSPKIKeys hspki_keys = 3;
 }
 
 message KubernetesKeys {
@@ -24,6 +25,13 @@
     bytes key = 4;
 }
 
+message HSPKIKeys {
+    bytes ca = 1;
+    bytes cert = 2;
+    bytes key = 3;
+    string principal = 4;
+}
+
 service Prodvider {
     rpc Authenticate(AuthenticateRequest) returns (AuthenticateResponse);
 }
diff --git a/cluster/prodvider/service.go b/cluster/prodvider/service.go
index 0409884..17dfe6e 100644
--- a/cluster/prodvider/service.go
+++ b/cluster/prodvider/service.go
@@ -69,14 +69,22 @@
 		return nil, status.Error(codes.Unavailable, "could not set up objects in Kubernetes")
 	}
 
-	keys, err := p.kubernetesCreds(username)
+	kubernetesKeys, err := p.kubernetesCreds(username)
 	if err != nil {
 		glog.Errorf("kubernetesCreds(%q): %v", username, err)
 		return nil, status.Error(codes.Unavailable, "could not generate k8s keys")
 	}
+
+	hspkiKeys, err := p.hspkiCreds(username)
+	if err != nil {
+		glog.Errorf("hspkiCreds(%q): %v", username, err)
+		return nil, status.Error(codes.Unavailable, "could not generate hspki keys")
+	}
+
 	return &pb.AuthenticateResponse{
 		Result:         pb.AuthenticateResponse_RESULT_AUTHENTICATED,
-		KubernetesKeys: keys,
+		KubernetesKeys: kubernetesKeys,
+		HspkiKeys:      hspkiKeys,
 	}, nil
 }
 
diff --git a/go/pki/BUILD.bazel b/go/pki/BUILD.bazel
index 5bc7522..f2eae41 100644
--- a/go/pki/BUILD.bazel
+++ b/go/pki/BUILD.bazel
@@ -2,7 +2,10 @@
 
 go_library(
     name = "go_default_library",
-    srcs = ["grpc.go"],
+    srcs = [
+        "grpc.go",
+        "locate.go",
+    ],
     importpath = "code.hackerspace.pl/hscloud/go/pki",
     visibility = ["//visibility:public"],
     deps = [
diff --git a/go/pki/grpc.go b/go/pki/grpc.go
index 1720ad8..44099c0 100644
--- a/go/pki/grpc.go
+++ b/go/pki/grpc.go
@@ -20,7 +20,6 @@
 	"crypto/x509"
 	"flag"
 	"fmt"
-	"io/ioutil"
 	"strings"
 
 	"github.com/golang/glog"
@@ -210,18 +209,19 @@
 		return []grpc.ServerOption{}
 	}
 
-	serverCert, err := tls.LoadX509KeyPair(flagCertificatePath, flagKeyPath)
+	loc, err := loadCredentials()
+	if err != nil {
+		glog.Exitf("WithServerHSPKI: loadCredentials: %v", err)
+	}
+
+	serverCert, err := tls.X509KeyPair(loc.cert, loc.key)
 	if err != nil {
 		glog.Exitf("WithServerHSPKI: cannot load service certificate/key: %v", err)
 	}
 
 	certPool := x509.NewCertPool()
-	ca, err := ioutil.ReadFile(flagCAPath)
-	if err != nil {
-		glog.Exitf("WithServerHSPKI: cannot load CA certificate: %v", err)
-	}
-	if ok := certPool.AppendCertsFromPEM(ca); !ok {
-		glog.Exitf("WithServerHSPKI: cannot use CA certificate: %v", err)
+	if ok := certPool.AppendCertsFromPEM(loc.ca); !ok {
+		glog.Exitf("WithServerHSPKI: cannot use CA certificate")
 	}
 
 	creds := grpc.Creds(credentials.NewTLS(&tls.Config{
@@ -235,7 +235,15 @@
 	return []grpc.ServerOption{creds, interceptor}
 }
 
-func WithClientHSPKI() grpc.DialOption {
+type ClientHSPKIOption func(c *tls.Config)
+
+func OverrideServerName(name string) ClientHSPKIOption {
+	return func(c *tls.Config) {
+		c.ServerName = name
+	}
+}
+
+func WithClientHSPKI(opts ...ClientHSPKIOption) grpc.DialOption {
 	if !flag.Parsed() {
 		glog.Exitf("WithServerHSPKI called before flag.Parse!")
 	}
@@ -243,23 +251,30 @@
 		return grpc.WithInsecure()
 	}
 
-	certPool := x509.NewCertPool()
-	ca, err := ioutil.ReadFile(flagCAPath)
+	loc, err := loadCredentials()
 	if err != nil {
-		glog.Exitf("WithClientHSPKI: cannot load CA certificate: %v", err)
-	}
-	if ok := certPool.AppendCertsFromPEM(ca); !ok {
-		glog.Exitf("WithClientHSPKI: cannot use CA certificate: %v", err)
+		glog.Exitf("WithServerHSPKI: loadCredentials: %v", err)
 	}
 
-	clientCert, err := tls.LoadX509KeyPair(flagCertificatePath, flagKeyPath)
+	certPool := x509.NewCertPool()
+	if ok := certPool.AppendCertsFromPEM(loc.ca); !ok {
+		glog.Exitf("WithServerHSPKI: cannot use CA certificate")
+	}
+
+	clientCert, err := tls.X509KeyPair(loc.cert, loc.key)
 	if err != nil {
 		glog.Exitf("WithClientHSPKI: cannot load service certificate/key: %v", err)
 	}
 
-	creds := credentials.NewTLS(&tls.Config{
+	config := &tls.Config{
 		Certificates: []tls.Certificate{clientCert},
 		RootCAs:      certPool,
-	})
+	}
+
+	for _, opt := range opts {
+		opt(config)
+	}
+
+	creds := credentials.NewTLS(config)
 	return grpc.WithTransportCredentials(creds)
 }
diff --git a/go/pki/locate.go b/go/pki/locate.go
new file mode 100644
index 0000000..3b4ca29
--- /dev/null
+++ b/go/pki/locate.go
@@ -0,0 +1,108 @@
+package pki
+
+import (
+	"crypto/tls"
+	"crypto/x509"
+	"fmt"
+	"io/ioutil"
+	"os"
+
+	"github.com/golang/glog"
+)
+
+// DeveloperCredentialsLocation returns the path containing HSPKI credentials
+// on developer machines. These are provisioned by //cluster/prodaccess, and
+// are used if available.
+func DeveloperCredentialsLocation() (string, error) {
+	cfgDir, err := os.UserConfigDir()
+	if err != nil {
+		glog.Exitf("UserConfigDir: %w", err)
+	}
+
+	return fmt.Sprintf("%s/hspki", cfgDir), nil
+}
+
+// DeveloperCredentialsPrincipal returns the principal/DN for which the local
+// developer credentials are provisioned.
+func DeveloperCredentialsPrincipal() (string, error) {
+	creds, err := loadDeveloperCredentials()
+	if err != nil {
+		return "", fmt.Errorf("when loading developer credentials: %w", err)
+	}
+	pair, err := tls.X509KeyPair(creds.cert, creds.key)
+	if err != nil {
+		return "", fmt.Errorf("when loading developer client cert: %w", err)
+	}
+	cert, err := x509.ParseCertificate(pair.Certificate[0])
+	if err != nil {
+		return "", fmt.Errorf("when parsing developer client cert: %w", err)
+	}
+	return cert.Subject.CommonName, nil
+}
+
+type creds struct {
+	ca   []byte
+	cert []byte
+	key  []byte
+}
+
+func loadDeveloperCredentials() (*creds, error) {
+	path, err := DeveloperCredentialsLocation()
+	if err != nil {
+		return nil, fmt.Errorf("DeveloperCredentialsLocation: %w")
+	}
+
+	c := creds{}
+	for _, el := range []struct {
+		target *[]byte
+		path   string
+	}{
+		{&c.ca, path + "/" + "ca.crt"},
+		{&c.cert, path + "/" + "tls.crt"},
+		{&c.key, path + "/" + "tls.key"},
+	} {
+		data, err := ioutil.ReadFile(el.path)
+		if err != nil {
+			return nil, fmt.Errorf("ReadFile(%q): %w", el.path, err)
+		}
+		*el.target = data
+	}
+
+	return &c, nil
+}
+
+func loadFlagCredentials() (*creds, error) {
+	c := creds{}
+	for _, el := range []struct {
+		target *[]byte
+		path   string
+	}{
+		{&c.ca, flagCAPath},
+		{&c.cert, flagCertificatePath},
+		{&c.key, flagKeyPath},
+	} {
+		data, err := ioutil.ReadFile(el.path)
+		if err != nil {
+			return nil, fmt.Errorf("ReadFile(%q): %w", el.path, err)
+		}
+		*el.target = data
+	}
+
+	return &c, nil
+}
+
+func loadCredentials() (*creds, error) {
+	dev, err := loadDeveloperCredentials()
+	if err == nil {
+		return dev, nil
+	}
+	glog.Warningf("Could not load developer PKI credentials: %v", err)
+
+	fl, err := loadFlagCredentials()
+	if err == nil {
+		return fl, err
+	}
+	glog.Warningf("Could not load flag-defined PKI credentials: %v", err)
+
+	return nil, fmt.Errorf("could not load any credentials")
+}
diff --git a/hswaw/laserproxy/README.md b/hswaw/laserproxy/README.md
index 3a2e8db..56af4b7 100644
--- a/hswaw/laserproxy/README.md
+++ b/hswaw/laserproxy/README.md
@@ -15,3 +15,14 @@
 A lock is taken through a web interface by the user that wants to access the laser.
 
 When a lock is taken, the Locker will notify a Proxy worker about this address. The Proxy will then perform the UDP proxying. As traffic is proxied, the Proxy will send bump updates to the Locker to extend the lock deadline.
+
+Deployment
+----------
+
+You'll need root access to customs.
+
+    bazel build --platforms=@io_bazel_rules_go//go/toolchain:openbsd_amd64 //hswaw/laserproxy
+    ssh root@customs supervisorctl stop laserproxy
+    scp bazel-bin/hswaw/laserproxy/laserproxy_/laserproxy root@customs:/var/laserproxy/laserproxy
+    ssh root@customs supervisorctl start laserproxy
+
diff --git a/personal/q3k/factorio/kube/factorio.libsonnet b/personal/q3k/factorio/kube/factorio.libsonnet
index 6ee7b49..fc64aaa 100644
--- a/personal/q3k/factorio/kube/factorio.libsonnet
+++ b/personal/q3k/factorio/kube/factorio.libsonnet
@@ -16,7 +16,7 @@
         rconPassword: "farts",
 
         tag: "latest",
-        image: "registry.k0.hswaw.net/app/factorio:" + cfg.tag,
+        image: "registry.k0.hswaw.net/q3k/factorio:" + cfg.tag,
         resources: {
             requests: {
                 cpu: "500m",
diff --git a/personal/q3k/factorio/kube/prod.jsonnet b/personal/q3k/factorio/kube/prod.jsonnet
index d30b652..7fea8e5 100644
--- a/personal/q3k/factorio/kube/prod.jsonnet
+++ b/personal/q3k/factorio/kube/prod.jsonnet
@@ -3,12 +3,7 @@
 
 
 // Available versions:
-//  - 0.16.51-1
-//  - 0.17.41-1
-//  - 0.17.52-1
-//  - 0.17.79-1
-//  - 0.18.12-2
-//  - 0.18.17-1
+//  - 0.18.40-1
 
 {
     local prod = self,
@@ -22,7 +17,7 @@
         }
     },
 
-    q3k: prod.instance("q3k", "0.18.17-1"),
-    ds: prod.instance("ds", "0.18.17-1"),
-    pymods: prod.instance("pymods", "0.18.17-1"),
+    q3k: prod.instance("q3k", "0.18.40-1"),
+    ds: prod.instance("ds", "0.18.40-1"),
+    pymods: prod.instance("pymods", "0.18.40-1"),
 }
diff --git a/personal/q3k/minecraft/Dockerfile-paper b/personal/q3k/minecraft/Dockerfile-paper
index d1c39e4..048524d 100644
--- a/personal/q3k/minecraft/Dockerfile-paper
+++ b/personal/q3k/minecraft/Dockerfile-paper
@@ -30,7 +30,7 @@
 
 RUN set -e -x ;\
     wget --quiet https://papermc.io/api/v1/paper/${VERSION}/latest/download ;\
-    mv download paper.jar
+    mv download server.jar
 
 ADD worldedit-bukkit-7.2.0-beta-01.jar .
 ADD worldguard-bukkit-7.0.4-beta1.jar .
diff --git a/personal/q3k/minecraft/Dockerfile-spigot b/personal/q3k/minecraft/Dockerfile-spigot
index cf47ea8..2d65017 100644
--- a/personal/q3k/minecraft/Dockerfile-spigot
+++ b/personal/q3k/minecraft/Dockerfile-spigot
@@ -36,7 +36,7 @@
     cp spigot*jar .. ;\
     cd .. ;\
     rm -rf build ;\
-    mv spigot*.jar spigot.jar
+    mv spigot*.jar server.jar
 
 ADD worldedit-bukkit-7.2.0-beta-01.jar .
 ADD worldguard-bukkit-7.0.4-beta1.jar .
diff --git a/personal/q3k/minecraft/prod.jsonnet b/personal/q3k/minecraft/prod.jsonnet
index eef3b35..7c7b0ae 100644
--- a/personal/q3k/minecraft/prod.jsonnet
+++ b/personal/q3k/minecraft/prod.jsonnet
@@ -4,7 +4,8 @@
 {
     local minecraft = self,
     versions:: {
-        "spigot-1.15.2": "registry.k0.hswaw.net/q3k/minecraft:spigot-1.15.2-r3",
+        "spigot-1.16.1": "registry.k0.hswaw.net/q3k/minecraft:spigot-1.16.1-r2",
+        "paper-1.16.1": "registry.k0.hswaw.net/q3k/minecraft:paper-1.16.1-r2",
     },
     server(name, version):: {
         local server = self,
@@ -72,7 +73,7 @@
             #!/usr/bin/env bash
             cd /home/minecraft/world
             cp /home/minecraft/config/server.properties .
-            cp /home/minecraft/spigot.jar .
+            cp /home/minecraft/server.jar .
             mkdir -p plugins/WorldGuard
             cp /home/minecraft/worldedit-*.jar plugins
             cp /home/minecraft/worldguard-*.jar plugins
@@ -80,7 +81,7 @@
             echo "eula=true" > eula.txt
 
             bash /home/minecraft/config/overviewer.sh &
-            exec java -Xmx4G -Xms4G -jar spigot.jar
+            exec java -Xmx4G -Xms4G -jar server.jar
         |||,
 
         overviewersh:: |||
@@ -201,7 +202,7 @@
     ns: kube.Namespace("minecraft"),
 
     q3k: {
-        survival: minecraft.server("q3k-survival", "spigot-1.15.2") {
+        survival: minecraft.server("q3k-survival", "paper-1.16.1") {
             properties+: {
                 motd: "wypierdol z polski kropka pe el",
                 "enforce-whitelist": true,
diff --git a/third_party/factorio/factorio.bzl b/third_party/factorio/factorio.bzl
index b0d661e..8083cae 100644
--- a/third_party/factorio/factorio.bzl
+++ b/third_party/factorio/factorio.bzl
@@ -23,6 +23,7 @@
     "0.18.12": "e0c6a46d66cfc02cba294a5fd34265e7e7a5168b8c8a7b16ad8dbac31470ed33",
     "0.18.17": "42adce9fddde393023afb0aae19dd030a32ca0810191c0e7b9b7c55556e9bbce",
     "0.18.22": "d90e349b61182c1e48bd34797faedc2f9b5b4e349d218ef3d987ae9d90762f7f",
+    "0.18.40": "696fe660fea945f38d12d49cf0b4737522d061fab5b3afd59467c4b2e375711a",
 }
 
 def factorio_repository(version, sha256):
@@ -76,6 +77,6 @@
         image = ":%s-%d" % (highest_version, revision),
         format = "Docker",
         registry = "registry.k0.hswaw.net",
-        repository = "app/factorio",
+        repository = "q3k/factorio",
         tag = "%s-%d" % (highest_version, revision),
     )
diff --git a/tools/secretstore.py b/tools/secretstore.py
index 85a3164..b0d2cfe 100644
--- a/tools/secretstore.py
+++ b/tools/secretstore.py
@@ -217,7 +217,7 @@
         return f'Decrypting {os.path.split(self.src)[-1]} ({self.reason})'
 
     def act(self):
-        return encrypt(self.src, self.dst)
+        return decrypt(self.src, self.dst)
 
 
 def sync(path: str, dry: bool):