Merge "update kube.libsonnet"
diff --git a/WORKSPACE b/WORKSPACE
index 5c987ff..9370030 100644
--- a/WORKSPACE
+++ b/WORKSPACE
@@ -276,6 +276,28 @@
     build_file = "@hscloud//third_party/jq:BUILD.external",
 )
 
+# leaflet.js from NPM, used by //hswaw/site.
+http_archive(
+    name = "com_npmjs_leaflet",
+    urls = ["https://registry.npmjs.org/leaflet/-/leaflet-1.7.1.tgz"],
+    sha256 = "43aca726165904ff73a34571c263b561cec94e6b9992a4414a600f3e984d1b03",
+    build_file_content = """
+filegroup(
+    name = "distfiles",
+    srcs = [
+        "package/dist/leaflet.js",
+        "package/dist/leaflet.css",
+        "package/dist/images/layers-2x.png",
+        "package/dist/images/layers.png",
+        "package/dist/images/marker-icon-2x.png",
+        "package/dist/images/marker-icon.png",
+        "package/dist/images/marker-shadow.png",
+    ],
+    visibility = ["//visibility:public"],
+)
+""",
+)
+
 go_repository(
     name = "com_github_gorilla_sessions",
     importpath = "github.com/gorilla/sessions",
diff --git a/app/matrix/lib/appservice-irc.libsonnet b/app/matrix/lib/appservice-irc.libsonnet
index fd51e4e..7906463 100644
--- a/app/matrix/lib/appservice-irc.libsonnet
+++ b/app/matrix/lib/appservice-irc.libsonnet
@@ -70,7 +70,7 @@
                 template+: {
                     spec+: {
                         volumes_: {
-                            config: kube.ConfigMapVolume(bridge.config),
+                            config: kube.SecretVolume(bridge.config),
                         },
                         containers_: {
                             bootstrap: kube.Container("appservice-irc-%s-bootstrap" % [name]) {
diff --git a/app/matrix/lib/matrix-ng.libsonnet b/app/matrix/lib/matrix-ng.libsonnet
index eb1a025..95e9257 100644
--- a/app/matrix/lib/matrix-ng.libsonnet
+++ b/app/matrix/lib/matrix-ng.libsonnet
@@ -62,8 +62,8 @@
         storageClassName: "waw-hdd-redundant-3",
 
         images: {
-            synapse: "matrixdotorg/synapse:v1.25.0",
-            riot: "vectorim/riot-web:v1.7.18",
+            synapse: "matrixdotorg/synapse:v1.35.1",
+            riot: "vectorim/riot-web:v1.7.29",
             casProxy: "registry.k0.hswaw.net/q3k/oauth2-cas-proxy:0.1.4",
             appserviceIRC: "matrixdotorg/matrix-appservice-irc:release-0.26.0",
             appserviceTelegram: "dock.mau.dev/tulir/mautrix-telegram@sha256:c6e25cb57e1b67027069e8dc2627338df35d156315c004a6f2b34b6aeaa79f77",
diff --git a/app/matrix/lib/synapse/homeserver-ng.yaml b/app/matrix/lib/synapse/homeserver-ng.yaml
index c57d650..f4fb22e 100644
--- a/app/matrix/lib/synapse/homeserver-ng.yaml
+++ b/app/matrix/lib/synapse/homeserver-ng.yaml
@@ -105,12 +105,6 @@
 

 ## API Configuration ##

 

-room_invite_state_types:

-    - "m.room.join_rules"

-    - "m.room.canonical_alias"

-    - "m.room.avatar"

-    - "m.room.name"

-

 expire_access_token: False

 

 ## Signing Keys ##

diff --git a/app/matrix/matrix.hackerspace.pl.jsonnet b/app/matrix/matrix.hackerspace.pl.jsonnet
index 7c022d4..9654c53 100644
--- a/app/matrix/matrix.hackerspace.pl.jsonnet
+++ b/app/matrix/matrix.hackerspace.pl.jsonnet
@@ -56,15 +56,15 @@
                         servers+: {
                             local servers = self,
                             "irc.freenode.net"+: {
-                                mappings+: import "secrets/plain/appservice-irc-freenode-mappings.jsonnet",
+                                mappings+: {},
                                 ircClients+: {
                                     maxClients: 150,
                                 },
                             },
                             "irc.libera.chat": servers["irc.freenode.net"] {
-                                mappings: {},
+                                mappings+: import "secrets/plain/appservice-irc-libera-mappings.jsonnet",
                                 ircClients+: {
-                                    maxClients: 20,
+                                    maxClients: 150,
                                 },
                                 name: "Libera Chat",
                                 networkId: "libera",
diff --git a/app/matrix/secrets/cipher/appservice-irc-freenode-mappings.jsonnet b/app/matrix/secrets/cipher/appservice-irc-freenode-mappings.jsonnet
deleted file mode 100644
index b2feb10..0000000
--- a/app/matrix/secrets/cipher/appservice-irc-freenode-mappings.jsonnet
+++ /dev/null
@@ -1,29 +0,0 @@
------BEGIN PGP MESSAGE-----
-
-hQEMAzhuiT4RC8VbAQf/WtXUDVxZS6kGiCvfebsc20kVpLWCrgb8Yx+Jc91M8Sh1
-3cAqxE41kis7tXgeFVsw6gR04r3IJkF8Xjywhohmu7QteW6uPQ6rYUXFjIDuPdur
-RgpoeolgfrhWZ1Ea5YUIIfAbwTOdca3/VLwA/QSYCUCr9FiZ3utL4jXWyukayfDN
-u+9mPa+0oANyDTrRlaKj9wp/jfoDfEoJ3gucaQoE0XJsmaHl3XLX0YC2Y4599Vmw
-WxK/4NxIlfLHz+bskLlBaaHTZm5cipkrrNiyF63W4/3G3AxALOQfBAYnGjI2j+NO
-G3sBZvu22vW0kxOiD+NaQciSZY65SmRjVeqjuoXVIYUBDANcG2tp6fXqvgEIALSh
-osli8DG5feBQLXzv/Vs4loTuB2COJ29IyjzKQo0NoeScJgz4NU/K/pWKZOsYCPjy
-VV0jt2ZVUAD4j8ahmwzlEN1hvuphtd+WHM5O68if9KfDNcCML5qJOw7uVF3leAgM
-KVVyteDc22paM9s++nOFJdaaUpGAWlqsucCOT7glBmUyPMIRSSnWDLXL97GfLRdM
-JDTqR4MvZA2GilOeB/RYUNjHN22RDYFyAE7jrJ8soLWLhFOddLpMqKMZIxWh/NyA
-okM2i999VKN20HTBsbfdyExQx49djrLngADKJZs6czvqa1dxE/+PKFrBKO+MCNTP
-nqyFaRTKaPnoKJWymd+FAgwD4gPJTlzrs+8BEACd+ScJWkk55ntTsi9nJe3qf/Cw
-Xxi/M+plY/kmpryMJIhiZ4CqacGhLF3GVsKgaLTBzHRrdgJL+DACbC2uCsjl2UcX
-e0wal4Jf6H5E87fSxLIwgtaHPvQApMRCg1sueR71mYvGTLqYhjn/jhh0ydzGhZ95
-/iXAgtHRSQtxAzAYDsSVcHgxPELlZ4/xiuUfqjX9Qs7u52j997qsLMnlAsqBsZ/6
-CuC4Wcy/liRL7kERZ4ZkKu1cL9eDAiCZUsUWaFtHknsvfhRvmAmBN7vSDNb1hRFI
-6O+p5YLv1AMYfiQAhpYDKiTyPZ9+ZJKM50QnjKJ3cE8pDe9zDeFsLOGwhBL6svn6
-bFHDlsQJqWxZQRD0DfC6f+GFvYl1o7Zg3tSkBejq+vCESIRgC4fiifCBWW9TlMiI
-TrbRpBgBPQnzt5T+LzaWAlNkhGPe87C8tHZd15UZ4SNKxaEyruB84qylz+Nvt8jl
-CIxcJO13zRstvTBSvbmvG4ZZrkb4dApUTOYA8HNpeeVmU2Dem7uQHgLPmqSvyerP
-eP7O6mJ3QOuotl3jqVlwSZw2+8m2IfPGCQCtDrgQV3pzg3mvEJjftWgAzF4u/7Ea
-42o98+QCK4x/VFmLYuFD04MlNY7Ctkuccv4kFeJm9ee0Y81Nvs/ZXcO+UzXWWfRW
-4UwA0txqM24bkDaAb9JmAV2/AEGyySo75Gjhu8jODoj9ZnRWO+IRBSvedittOogW
-CTsFX1pG4YGw3SdUUrtJWn3uVjxdDloZUFWzWZ2KOOIe/S8ipChi/rkgk3k6HwnW
-pK3jT5vP6QeRLp9hp5yXBp+65Jmt
-=G6Xz
------END PGP MESSAGE-----
diff --git a/app/matrix/secrets/cipher/appservice-irc-libera-mappings.jsonnet b/app/matrix/secrets/cipher/appservice-irc-libera-mappings.jsonnet
new file mode 100644
index 0000000..8202c68
--- /dev/null
+++ b/app/matrix/secrets/cipher/appservice-irc-libera-mappings.jsonnet
@@ -0,0 +1,42 @@
+-----BEGIN PGP MESSAGE-----
+
+hQEMAzhuiT4RC8VbAQf/dA66pxtLrNVMV2+NN0hHwo5PoDVeJkT6FCTkd+YdOI2N
+trVM8r0UXato6r+uy5WUAZs2VJypz0C/AwrJDLHOXopVpMatDLVkZvUMoci/KZZl
+RQsmp1ifcPjPSr5LoPNeIwmtW7h0wvpP+pEjKy95steYxfNBIjtdVZemoOI8t1M1
+xdzZmgMJkca00wPGuRVRicMib3QqK5A8m6TiCLwNJSY+nIsZPV98jGJZj3Mq2bXP
+Sn+v72OgRa4Bps3UNM/JsWVIiY9lfb5mNTzc2x8yFj6W585yzJRvvb6NxP7FG/vA
+aoRohG6WC6Nkacnt8Fpfx0Zi3TX2KbG1tVEVawzGwIUBDANcG2tp6fXqvgEH/3f0
+C9j04zdI8Oe9Cn4onPnxypmkEpdqGhrV5Iq9VQxMyBj0WL7jbUsbPNSpj90Z4N85
+G85udHTH25lAwpwu5Hr7DxRV0NjxoKH2zqdEx3hpylspY8IaMfCLY2B1YAmBI/AP
+M8dcXwWNRka/Hsafe+wzLdkg4+N8mSuePZadrPTXqRchUlPsJMCeoFcsN/kOYcYM
+CjZI3CVKlegN8n/svD4LNQx1tV9VMiiOP9IOFHaCi5YNXji2k7dUg+BEZXO5E0X8
+uDC2hg7X+XTP9aVWayzvGM7rmfOpt/i/GKBSKA/dfMp3mpsb6PyxoysdzkcuaZ4j
+7GUG8v+FFq0hxluumYiFAgwDodoT8VqRl4UBD/4wC7/PHefs6FLVwftIYt5k8XYV
+qURYL+MRnnxHK21vvaWjZmhml+4L2uKFrxdIMuHbI9RBjD9O9uPCAaUgaGnQunT0
+ti7sanwuTKOOzUXxDcgC6P5GDVW+nrrXKnUYZtlFkopcJf5DApJr7M2Nfr/xiBsX
+Sa/OhqB8B1cAz/8F8MOD9JnnGwHEEvrMfMUt2UclCDxPuK3nDnUhHWNi6HvL2yfc
+D6RBoX7Caj2Z9JPEF7cAZ2wobIxWF8is1FoS5eEZRf2tS8tJWgICfVFQkXlC7kLX
+FJE+Ai6mpU6MH3pPNnwYYoTOeZM/potHy38j5du+EF1/WtoZADN5EMFI3MgCm6hw
+6Kd2+5BVS0K/9vH2oLJSG1BbcQIxFuloSmXqIYRN94Niql5P/RqIKLpPxXp8Xzar
+TZ0TRUkHNLcpmxYaNw3WwmkboLnPcC5Erx+gnfV43OHvkcE6ILMmTOLK9xqUe+Gj
+buQTTp0PCwf7kVp/EhJooAXc2DI5sVaN1zYvtlIR7I4nwvYvahn/fWR0Dk1kh7wc
+Krfl3YX6u2YIPcYQPKFEXcRA5bjb1PsskVFlSMMkIdkS1t8HNfLLlH44h7sfOP9E
+4PSTpv2wbbOsc1F9TOpc7BvIjrpFTctO/QFejGOMD/UAcr2hVOjbCEfA8egT3qXW
+3nqYKynVUeGA+s5rr4UCDAPiA8lOXOuz7wEP/2E2IJnmLrLB6hLAYk8TCmMQSqwl
+qBViqIXx/GsUb0h3fVHkZRt/RbLf6ipFM3u5wl/FnyRks5DVqVG20VSA4/ZKh+ZV
+5KPAzoi19J06TMyTeRRBy6JfK57ooTGl9Ga+6qXEwSjKhDoTzeEP7d/qtak6Swr8
+zU9FBnTCsrcLUQHXhVE+WShsCoeBhBh1mlZ9XXnd5jjRJPpbjnmQbV86HuIxBFH5
+A1edmlIQI1XG3nlItKW+AgCKft+6nnU36ZOxHzeB1im6Cu9lPdBKyZHeAjRdOg4W
+NNMJp4wNSzNyqcToWwzlcFuxVHDofD4+MusZXbzyWcJFYELxShmVKfpdtyCSyk0o
+jlviS2F9YNl6/4NWfTT1zBl+r2yXjxFJCv6hIKo61foGKvZa/O8mffqCwXiFUmh6
+ymtEcXrjk0Fa7eYYl5i8PSjmNcJxPYw4X/f84/SjwKTianLd7gmk7ASmw5ZcyQKF
+WUuLYmyV48gBrNQopD7DDq3nEVHV2cQWj9uttyrylUPJs+kKBnLPbkK5Bh84z1qy
+ZGeQESfiTYbAjC9npShuaiyAdouGL6+Xo5Xu4GOYcHiGGt/3bwQkE7J5rj1DOjsh
+JlT379xiA5N6mectgbcSEVM7LSxFDKWl96Ac18hK1IwrIQIKzSAPLV5npPV0nYPR
+kaaBc39wXGSugxGY0r4BEeDWMpvUu9dx0l4TjDCfZNltC6nK5P1I15PTH7l8r42g
+DVDxS4KTVoyBrNE0DQRqSpjEHVRPWn/ywTTlSPw/UHDMVah177T+8iUKNtQzcypv
+877nZr7tMgd+p6pFSdpBjL8NI89Ky1LBop1XAxpkpE139FivBkgWsX1mtMP2DE6f
+HHtewolFmSgGWz+eBg/jnht8zHlxQxvVBpQOLhZiw+/4Q2mWQAeRR7HNAiWeuO+o
+byK/cJOHpY5uj94G
+=TR11
+-----END PGP MESSAGE-----
diff --git a/cluster/admitomatic/ingress.go b/cluster/admitomatic/ingress.go
index 6b8a365..22e9dab 100644
--- a/cluster/admitomatic/ingress.go
+++ b/cluster/admitomatic/ingress.go
@@ -210,6 +210,7 @@
 		"proxy-body-size":  true,
 		"ssl-redirect":     true,
 		"backend-protocol": true,
+		"use-regex":        true,
 		// Used by cert-manager
 		"whitelist-source-range": true,
 	}
diff --git a/cluster/kube/lib/admitomatic.libsonnet b/cluster/kube/lib/admitomatic.libsonnet
index d8e0440..305df94 100644
--- a/cluster/kube/lib/admitomatic.libsonnet
+++ b/cluster/kube/lib/admitomatic.libsonnet
@@ -32,7 +32,7 @@
 
         cfg:: {
             namespace: "admitomatic",
-            image: "registry.k0.hswaw.net/q3k/admitomatic:315532800-6cc2f867951e123909b23955cd7bcbcc3ec24f8a",
+            image: "registry.k0.hswaw.net/q3k/admitomatic:1622912229-383fefa14bddd51c1573fb9b5fcc6ecea958f50a",
 
             proto: {},
         },
diff --git a/personal/q3k/factorio/kube/factorio.libsonnet b/games/factorio/kube/factorio.libsonnet
similarity index 96%
rename from personal/q3k/factorio/kube/factorio.libsonnet
rename to games/factorio/kube/factorio.libsonnet
index 4f2ff39..6684fc0 100644
--- a/personal/q3k/factorio/kube/factorio.libsonnet
+++ b/games/factorio/kube/factorio.libsonnet
@@ -1,6 +1,7 @@
 # Factorio on Kubernetes.
 
-local kube = import "../../../../kube/kube.libsonnet";
+local kube = import "../../../kube/kube.libsonnet";
+local proxy = import "proxy.libsonnet";
 
 {
     local factorio = self,
@@ -9,9 +10,9 @@
     cfg:: {
         namespace: error "namespace must be set",
         appName: "factorio",
-        storageClassName: "waw-hdd-redundant-2",
+        storageClassName: "waw-hdd-redundant-3",
         prefix: "", # if set, should be 'foo-'
-        proxyImage: error "proxyImage must be set",
+        proxyImage: proxy.cfg.image,
 
         rconPort: 2137,
         rconPassword: "farts",
diff --git a/games/factorio/kube/prod.jsonnet b/games/factorio/kube/prod.jsonnet
new file mode 100644
index 0000000..7a6afe5
--- /dev/null
+++ b/games/factorio/kube/prod.jsonnet
@@ -0,0 +1,79 @@
+local factorio = import "factorio.libsonnet";
+local proxy = import "proxy.libsonnet";
+local kube = import "../../../kube/kube.libsonnet";
+
+// This deploys factorio instances and the modproxy in the `factorio`
+// Kubernetes namespace.
+//
+// Available factorio versions:
+//  - 1.0.0-1
+//  - 1.1.34-1
+// See: //third_party/factorio.
+
+{
+    local prod = self,
+
+    namespace: kube.Namespace("factorio"),
+
+    // instance makes a factorio server with a given name and at a
+    // given tag/version.
+    instance(name, tag):: factorio {
+        cfg+: {
+            namespace: "factorio",
+            prefix: name + "-",
+            tag: tag,
+        }
+    },
+
+    proxy: proxy {
+        cfg+: {
+            namespace: "factorio",
+        },
+    },
+
+    local mod = function(name, version) { name: name, version: version },
+    pymods: prod.instance("pymods", "1.1.34-1") {
+        cfg+: {
+            mods: [
+                // Stdlib for mods
+                mod("stdlib", "1.4.6"),
+                // General QOL
+                // Better loaders
+                mod("LoaderRedux", "1.7.1"),
+                mod("LoaderReduxForFactorioExtendedPlus", "1.1.0"),
+                // Trains
+                mod("traintunnels", "0.0.11"),
+                mod("FARL", "4.1.2"),
+                // Move between buildings
+                mod("Squeak Through", "1.8.2"),
+                // Requirements calculator
+                mod("helmod", "0.12.5"),
+                // Compact loaders for easier and faster transportation
+                mod("deadlock-beltboxes-loaders", "2.4.2"),
+                // Better inserters
+                mod("bobinserters", "1.1.0"),
+                // Teleport to tags for easier movement with huge server
+                mod("TagToTeleport", "1.1.1"),
+                // Necessary for PYmods to get proper list of ingredients for the next step
+                mod("what-is-it-really-used-for", "1.6.0"),
+                // Resource monitor
+                mod("YARM", "0.8.203"),
+                // YEET
+                mod("RenaiTransportation", "0.8.6"),
+
+                // PYmods
+                mod("pypetroleumhandling", "2.0.6"),
+                mod("pyrawores", "2.2.7"),
+                mod("pyraworesgraphics", "1.0.9"),
+                mod("pycoalprocessing", "1.9.3"),
+                mod("pycoalprocessinggraphics", "1.0.9"),
+                // Resource overhaul
+                mod("rso-mod", "6.2.6"),
+                // mciancia
+                mod("Warehousing", "0.5.2"),
+                mod("AbandonedRuins", "1.1.4"),
+                mod("Factorissimo2", "2.5.2"),
+            ],
+        },
+    },
+}
diff --git a/games/factorio/kube/proxy.libsonnet b/games/factorio/kube/proxy.libsonnet
new file mode 100644
index 0000000..514281a
--- /dev/null
+++ b/games/factorio/kube/proxy.libsonnet
@@ -0,0 +1,68 @@
+local kube = import "../../../kube/kube.libsonnet";
+
+{
+    local proxy = self,
+    local cfg = proxy.cfg,
+
+    cfg:: {
+        image:: "registry.k0.hswaw.net/games/factorio/modproxy:1589157915-eafe7be328477e8a6590c4210466ef12901f1b9a",
+        namespace: error "namespace must be set",
+    },
+
+    pvc: kube.PersistentVolumeClaim("proxy-cas") {
+        metadata+: {
+            namespace: cfg.namespace,
+        },
+        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(proxy.pvc),
+                    },
+                    containers_: {
+                        proxy: kube.Container("proxy") {
+                            image: cfg.image,
+                            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:: proxy.deploy.spec.template,
+        spec+: {
+            ports: [
+                { name: "client", port: 4200, targetPort: 4200, protocol: "TCP" },
+            ],
+        },
+    },
+}
diff --git a/hswaw/site/BUILD.bazel b/hswaw/site/BUILD.bazel
index 0b05bf3..21edded 100644
--- a/hswaw/site/BUILD.bazel
+++ b/hswaw/site/BUILD.bazel
@@ -1,8 +1,10 @@
+load("@io_bazel_rules_docker//container:container.bzl", "container_image", "container_push")
 load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library")
 
 go_library(
     name = "go_default_library",
     srcs = [
+        "feeds.go",
         "main.go",
         "views.go",
     ],
@@ -21,3 +23,20 @@
     embed = [":go_default_library"],
     visibility = ["//visibility:public"],
 )
+
+container_image(
+    name="latest",
+    base="@prodimage-bionic//image",
+    files = [":site"],
+    directory = "/hswaw/site/",
+    entrypoint = ["/hswaw/site/site"],
+)
+
+container_push(
+    name = "push",
+    image = ":latest",
+    format = "Docker",
+    registry = "registry.k0.hswaw.net",
+    repository = "q3k/hswaw-site",
+    tag = "1622585979-{STABLE_GIT_COMMIT}",
+)
diff --git a/hswaw/site/COPYING b/hswaw/site/COPYING
new file mode 100644
index 0000000..c29d0d6
--- /dev/null
+++ b/hswaw/site/COPYING
@@ -0,0 +1 @@
+The files in this directory (hswaw/site/*) and its subdirectories are, unless otherwise noted, licensed under the Creative Commons Attribution 4.0 International License. To view a copy of this license, visit http://creativecommons.org/licenses/by/4.0/ or send a letter to Creative Commons, PO Box 1866, Mountain View, CA 94042, USA.
diff --git a/hswaw/site/feeds.go b/hswaw/site/feeds.go
new file mode 100644
index 0000000..ad9bc31
--- /dev/null
+++ b/hswaw/site/feeds.go
@@ -0,0 +1,164 @@
+package main
+
+import (
+	"context"
+	"encoding/xml"
+	"fmt"
+	"html/template"
+	"net/http"
+	"sort"
+	"time"
+
+	"github.com/golang/glog"
+)
+
+// This implements 'Atom' feed parsing. Honestly, this was written without
+// looking at any spec. If it ever breaks, you know why.
+
+var (
+	// feedURLs is a map from an atom feed name to its URL. All the following
+	// feeds will be combined and rendered on the main page of the website.
+	feedsURLs = map[string]string{
+		"blog": "https://blog.hackerspace.pl/feed/atom/",
+	}
+)
+
+// atomFeed is a retrieved atom feed.
+type atomFeed struct {
+	XMLName xml.Name     `xml:"feed"`
+	Entries []*atomEntry `xml:"entry"`
+}
+
+// atomEntry is an entry (eg. blog post) from an atom feed. It contains fields
+// directly from the XML, plus some additional parsed types and metadata.
+type atomEntry struct {
+	XMLName      xml.Name      `xml:"entry"`
+	Author       string        `xml:"author>name"`
+	Title        template.HTML `xml:"title"`
+	Summary      template.HTML `xml:"summary"`
+	UpdatedRaw   string        `xml:"updated"`
+	PublishedRaw string        `xml:"published"`
+	Link         struct {
+		Href string `xml:"href,attr"`
+	} `xml:"link"`
+
+	// Updated is the updated time parsed from UpdatedRaw.
+	Updated time.Time
+	// UpdatedHuman is a human-friendly representation of Updated for web rendering.
+	UpdatedHuman string
+	// Published is the published time parsed from PublishedRaw.
+	Published time.Time
+	// Source is the name of the feed that this entry was retrieved from. Only
+	// set after combining multiple feeds together (ie. when returned from
+	// getFeeds).
+	Source string
+}
+
+// getAtomFeed retrieves a single Atom feed from the given URL.
+func getAtomFeed(ctx context.Context, url string) (*atomFeed, error) {
+	r, err := http.NewRequestWithContext(ctx, "GET", url, nil)
+	if err != nil {
+		return nil, fmt.Errorf("NewRequest(%q): %w", url, err)
+	}
+	res, err := http.DefaultClient.Do(r)
+	if err != nil {
+		return nil, fmt.Errorf("Do(%q): %w", url, err)
+	}
+	defer res.Body.Close()
+
+	var feed atomFeed
+	d := xml.NewDecoder(res.Body)
+	if err := d.Decode(&feed); err != nil {
+		return nil, fmt.Errorf("Decode: %w", err)
+	}
+
+	for i, e := range feed.Entries {
+		updated, err := time.Parse(time.RFC3339, e.UpdatedRaw)
+		if err != nil {
+			return nil, fmt.Errorf("entry %d: cannot parse updated date %q: %v", i, e.UpdatedRaw, err)
+		}
+		published, err := time.Parse(time.RFC3339, e.PublishedRaw)
+		if err != nil {
+			return nil, fmt.Errorf("entry %d: cannot parse published date %q: %v", i, e.PublishedRaw, err)
+		}
+		e.Updated = updated
+		e.Published = published
+		e.UpdatedHuman = e.Updated.Format("02-01-2006")
+		if e.Author == "" {
+			e.Author = "Anonymous"
+		}
+	}
+
+	return &feed, nil
+}
+
+// feedWorker runs a worker which retrieves all atom feeds every minute and
+// updates the services' feeds map with the retrieved data. On error, the feeds
+// are not updated (whatever is already cached in the map will continue to be
+// available) and the error is logged.
+func (s *service) feedWorker(ctx context.Context) {
+	okay := false
+	get := func() {
+		feeds := make(map[string]*atomFeed)
+
+		prev := okay
+		okay = true
+		for name, url := range feedsURLs {
+			feed, err := getAtomFeed(ctx, url)
+			if err != nil {
+				glog.Errorf("Getting feed %v failed: %v", feed, err)
+				okay = false
+				continue
+			}
+			feeds[name] = feed
+		}
+
+		// Log whenever the first fetch succeeds, or whenever the fetch
+		// succeeds again (avoiding polluting logs with success messages).
+		if !prev && okay {
+			glog.Infof("Feeds okay.")
+		}
+
+		// Update cached feeds.
+		s.feedsMu.Lock()
+		s.feeds = feeds
+		s.feedsMu.Unlock()
+	}
+	// Perform initial fetch.
+	get()
+
+	// ... and update every minute.
+	t := time.NewTicker(time.Minute)
+	defer t.Stop()
+
+	for {
+		select {
+		case <-ctx.Done():
+			return
+		case <-t.C:
+			get()
+		}
+	}
+}
+
+// getFeeds retrieves the currently cached feeds and combines them into a
+// single reverse-chronological timeline, annotating each entries' Source field
+// with the name of the feed from where it was retrieved.
+func (s *service) getFeeds() []*atomEntry {
+	s.feedsMu.RLock()
+	feeds := s.feeds
+	s.feedsMu.RUnlock()
+
+	var res []*atomEntry
+	for n, feed := range feeds {
+		for _, entry := range feed.Entries {
+			e := *entry
+			e.Source = n
+			res = append(res, &e)
+		}
+	}
+	sort.Slice(res, func(i, j int) bool {
+		return res[j].Published.Before(res[i].Published)
+	})
+	return res
+}
diff --git a/hswaw/site/main.go b/hswaw/site/main.go
index f5e42d0..a7f3e54 100644
--- a/hswaw/site/main.go
+++ b/hswaw/site/main.go
@@ -5,7 +5,9 @@
 	"fmt"
 	"mime"
 	"net/http"
+	"regexp"
 	"strings"
+	"sync"
 
 	"code.hackerspace.pl/hscloud/go/mirko"
 	"github.com/golang/glog"
@@ -18,6 +20,11 @@
 )
 
 type service struct {
+	// feeds is a map from atom feed name to atom feed. This is updated by a
+	// background worker.
+	feeds map[string]*atomFeed
+	// feedsMu locks the feeds field.
+	feedsMu sync.RWMutex
 }
 
 func main() {
@@ -30,6 +37,7 @@
 	}
 
 	s := &service{}
+	go s.feedWorker(mi.Context())
 
 	mux := http.NewServeMux()
 	s.registerHTTP(mux)
@@ -48,23 +56,51 @@
 	<-mi.Done()
 }
 
+var (
+	// staticRoutes define the resolution of static file paths into assets
+	// built into //hswaw/site/static, whose names correspond to their origin
+	// within the Bazel workspace.
+	// The regexp will be matched against the normalized within the URL. If it
+	// matches, its first group/submatch will be used to format the string
+	// corresponding to this regexp, and that string will be then used to
+	// retrieve a path embedded within //hswaw/site/static:static
+	// (go_embed_data).
+	//
+	// To see paths available within that go_embed_data, you can do:
+	//  $ bazel build //hswaw/site/static:static
+	//  $ grep -A100 'Data =' bazel-bin/hswaw/site/static/static.go
+	staticRoutes = map[*regexp.Regexp]string{
+		regexp.MustCompile(`^static/site/(.+)$`):    "hswaw/site/static/%s",
+		regexp.MustCompile(`^static/leaflet/(.+)$`): "external/com_npmjs_leaflet/package/dist/%s",
+	}
+)
+
+// handleHTTPStatic uses staticRoutes to serve static files embedded within
+// //hswaw/site/static.
 func (s *service) handleHTTPStatic(w http.ResponseWriter, r *http.Request) {
 	path := strings.TrimPrefix(r.URL.Path, "/")
-
-	staticPath := fmt.Sprintf("hswaw/site/%s", path)
-	if data, ok := static.Data[staticPath]; ok {
+	for from, to := range staticRoutes {
+		match := from.FindStringSubmatch(path)
+		if match == nil {
+			continue
+		}
+		to := fmt.Sprintf(to, match[1])
+		data, ok := static.Data[to]
+		if !ok {
+			continue
+		}
 		parts := strings.Split(path, ".")
 		ext := fmt.Sprintf(".%s", parts[len(parts)-1])
 		t := mime.TypeByExtension(ext)
 		w.Header().Set("Content-Type", t)
 		w.Write(data)
-	} else {
-		http.NotFound(w, r)
+		return
+
 	}
+	http.NotFound(w, r)
 }
 
 func (s *service) registerHTTP(mux *http.ServeMux) {
 	mux.HandleFunc("/static/", s.handleHTTPStatic)
-	mux.HandleFunc("/about", s.handleAbout)
-	mux.HandleFunc("/about_en", s.handleAboutEn)
+	mux.HandleFunc("/", s.handleIndex)
 }
diff --git a/hswaw/site/static/BUILD.bazel b/hswaw/site/static/BUILD.bazel
index 141161c..7fdce2d 100644
--- a/hswaw/site/static/BUILD.bazel
+++ b/hswaw/site/static/BUILD.bazel
@@ -4,9 +4,9 @@
 go_embed_data(
     name = "static",
     srcs = [
-        "kontakt.png",
-        "main.css",
-        "mapka.png",
+        "landing.css",
+        "syrenka.png",
+        "@com_npmjs_leaflet//:distfiles",
     ],
     package = "static",
 )
diff --git a/hswaw/site/static/kontakt.png b/hswaw/site/static/kontakt.png
deleted file mode 100644
index 4db0eef..0000000
--- a/hswaw/site/static/kontakt.png
+++ /dev/null
Binary files differ
diff --git a/hswaw/site/static/landing.css b/hswaw/site/static/landing.css
new file mode 100644
index 0000000..431d8ea
--- /dev/null
+++ b/hswaw/site/static/landing.css
@@ -0,0 +1,149 @@
+body {
+    margin: 0;
+    padding: 0;
+    background-color: #444;
+    color: #fffdf3;
+    font-weight: 100;
+    font-family: 'Lato', sans-serif;
+    font-size: 18px;
+}
+
+#page {
+    max-width: 75rem;
+    margin: auto;
+    padding-top: 2rem;
+    padding: 1rem;
+
+    display: flex;
+    flex-direction: column;
+}
+
+.top {
+    display: flex;
+    flex-direction: row;
+    margin-bottom: 1rem;
+}
+
+.top .logo {
+    display: flex;
+    flex-direction: row;
+    flex-grow: 1;
+    justify-content: right;
+}
+
+.top .logo img {
+    margin-top: 2rem;
+    max-height: 25rem
+}
+
+.top .mapcontainer {
+    flex-grow: 1;
+    min-width: 35%;
+}
+
+#map {
+    margin-bottom: 1rem;
+    height: 16rem;
+}
+
+
+.logo h1 {
+    display: block;
+    max-width: 20rem;
+    text-align: center;
+    font-size: 52px;
+    margin-right: 8rem;
+    padding-top: 5rem;
+}
+
+.covid {
+    padding: 1rem 2rem;
+    background-color: #9f0000;
+}
+
+.covid span {
+    font-size: 20px;
+    font-style: italic;
+}
+
+.bottom {
+    display: flex;
+    flex-direction: row;
+}
+
+.bottom .about {
+    padding: 1rem 1rem 1rem 2rem;
+}
+
+.bottom .blog {
+    padding: 1rem 2rem 1rem 1rem;
+    flex-grow: 1;
+    min-width: 40%;
+}
+
+p {
+    line-height: 150%;
+    text-align: justify;
+}
+
+h1 {
+    font-size: 30px;
+}
+h2 {
+    font-size: 20px;
+}
+
+h1, h2, h3, h4 {
+    font-family: 'Allerta', sans-serif;
+}
+
+pre {
+    background-color: #141000;
+    padding: 1rem;
+}
+
+a {
+    color: #fffdf3;
+}
+
+b {
+    font-weight: 800;
+}
+
+ul {
+    padding: 0 0 0 1em;
+}
+
+li {
+    list-style: none;
+}
+
+li i {
+    font-size: 0.8em;
+}
+
+#background-logo {
+    position: absolute;
+    width: 100%;
+    height: 100%;
+    z-index: -10;
+}
+
+#background-logo img {
+    opacity: 3%;
+    margin-top: 2%;
+    margin-left: 5%;
+}
+
+#footer {
+    margin-top: 2rem;
+    font-size: 0.8rem;
+    opacity: 60%;
+}
+
+#quicklinks {
+    float: right;
+    font-family: monospace;
+    font-size: 14px;
+    margin: 2rem;
+}
diff --git a/hswaw/site/static/main.css b/hswaw/site/static/main.css
deleted file mode 100644
index cfda0e8..0000000
--- a/hswaw/site/static/main.css
+++ /dev/null
@@ -1,251 +0,0 @@
-a.news-title {
-  text-decoration: none;
-  color: #222
-}
-
-p.news-footer {
-  font-size: 12px !important;
-  color: #999;
-  text-align: right !important;
-}
-
-.news-rectangle {
-  background-color: #21a;
-  border-radius: 5px;
-  color: #fff;
-  display: inline-block;
-  font-size: 14px;
-  font-weight: bold;
-  margin: 8px 5px 5px 5px;
-  padding: 0 0 2px 0;
-  text-align: center;
-  vertical-align: middle;
-  width: 80px;
-}
-
-.news .date:before {
-  font-size: 14px;
-  font-weight: bold;
-  display: block;
-  font-family: 'Titillium Web', sans;
-  color: #eee;
-  border-radius: 5px;
-  margin: 8px 0 5px 5px;
-  padding: 0 0 2px 0;
-  vertical-align: middle;
-  text-align: center;
-  width: 80px;
-  margin-right: 0.5em;
-}
-.news .redmine .date:before {
-  content: "redmine";
-  background: #c3352b;
-}
-
-.news .blog .date:before {
-  content: "blog";
-  background: #21a;
-}
-
-ul.news {
-  list-style: none;
-  padding: 0;
-}
-
-ul.news li {
-  border-bottom: 2px groove #fff;
-  padding-top: 8px;
-  padding-right: 16px;
-}
-
-#hs_content .news li {
-  margin: 0;
-}
-
-#about {
-  border-bottom: 2px groove #fff;
-  padding: 15px;
-}
-
-#hs_branding {
-  min-width: 0;
-}
-
-#hs_main {
-  min-width: 0;
-}
-
-body {
-  overflow-x: hidden;
-}
-
-span.date { 
-  font-weight: bold;
-  display: block;
-  width: 90px;
-  text-align: center;
-  float: left;
-}
-
-span.author {
-  text-decoration: italic;
-}
-
-#hs_main {
-  position: relative;
-}
-
-#left {
-  border-right: 2px groove #fff;
-  margin-right: 320px;
-}
-
-#right {
-  position: absolute;
-  width: 300px;
-  top: 100px;
-  padding-top: 16px;
-  right: 0;
-  margin-right: 20px;
-  font-size: 16px;
-}
-
-.clear {
-  clear: both;
-  display: block;
-  text-align: center;
-}
-
-.moar {
-  font-family: "Titillium Web", sans;
-  font-weight: bold;
-//  background: url("/static/cutcube.png");
-  background-color: #fd6;
-//  background-color: #2c702a;
-  color: #222;
-  padding: 4px;
-  border-radius: 5px;
-  text-align: center;
-  display: block;
-}
-
-@media screen and (max-width: 1024px) {
-  #hs_branding {
-    width: 95%;
-  }
-  #hs_main {
-    width: 95%;
-  }
-}       
-
-@media screen and (max-width: 625px) {
-  #hs_branding {
-    width: 100%;
-  }
-  #hs_main {
-    width: 100%;
-  }
-  #right {
-    position: static;
-    float: none;
-    width: 100%;
-  }  
-  #left {
-    float: none;
-    width: 100%;
-    margin-right: 0;
-    border-right: 0;
-  }
-}
-
-@media screen and (max-width: 480px) {
-  #hs_branding a {
-    font-size: 1.8em;
-  }
-  #hs_branding li a {
-    font-size: 1.2em;
-  }
-}
-
-#right h4 {
-  margin-bottom: 0px;
-  margin-left: 0px;
-  margin-top: 2px;
-}
-
-h1.mail {
-  margin-bottom: 2px !important;
-}
-
-#right .email-entry {
-  width: 90%;
-  border: 1px groove #777;
-  padding: 2px;
-  background: none repeat scroll 0% 0% rgb(250, 250, 250);
-  color: rgb(34, 34, 34);
-  margin: 10px;
-  font-size: 14px;
-}
-
-#right .email-submit {
-  padding: 4px 14px 4px 14px;
-}
-
-.mailcheck {
-  margin-right: 8px;
-  position: relative;
-  top: 2px;
-}
-
-div.mail-desc {
-  font-size: 14px;
-  padding-left: 26px;
-  line-height: 15px;
-}
-
-div.mail-captcha {
-  width: 276px;
-  height: 40px;
-}
-div.mail-captcha img {
-  border: 1px groove #777;
-  width: 80px;
-  height: 30px;
-  float: left;
-}
-
-div.mail-captcha input {
-  float: left;
-  width: 178px !important;
-  height: 26px !important;
-  margin-top: 0 !important;
-  margin-bottom: 0 !important;
-  margin-right: 0 !important;
-}
-
-div.flashes {
-  padding: 0;
-  margin-left: auto;
-  margin-right: auto;
-  width: 80%;
-  margin-bottom: 10px;
-}
-
-div.flashes ul {
-  list-style-type: none;
-  padding: 0 0 5px 0;
-}
-
-div.flashes li {
-  width: 90%;
-  border-radius: 4px;
-  background-color: #308033;
-  padding: 5px;
-  margin-top: 5px;
-  margin-left: auto;
-  margin-right: auto;
-}
-
-div.flashes li.error {
-  background-color: #a01023;
-}
diff --git a/hswaw/site/static/mapka.png b/hswaw/site/static/mapka.png
deleted file mode 100644
index 8bebd7f..0000000
--- a/hswaw/site/static/mapka.png
+++ /dev/null
Binary files differ
diff --git a/hswaw/site/static/syrenka.png b/hswaw/site/static/syrenka.png
new file mode 100644
index 0000000..9a0d481
--- /dev/null
+++ b/hswaw/site/static/syrenka.png
Binary files differ
diff --git a/hswaw/site/templates/BUILD.bazel b/hswaw/site/templates/BUILD.bazel
index cc47353..07f5112 100644
--- a/hswaw/site/templates/BUILD.bazel
+++ b/hswaw/site/templates/BUILD.bazel
@@ -4,12 +4,7 @@
 go_embed_data(
     name = "templates",
     srcs = [
-        "about.html",
-        "about_en.html",
-        "basic.html",
-        "main.html",
-        "subscribe.html",
-        "subscribe_en.html",
+        "index.html",
     ],
     package = "templates",
 )
diff --git a/hswaw/site/templates/about.html b/hswaw/site/templates/about.html
deleted file mode 100644
index aa6aecd..0000000
--- a/hswaw/site/templates/about.html
+++ /dev/null
@@ -1,102 +0,0 @@
-{{ define "page_scripts" }}
-  <script type="text/javascript" src="https://widgets.twimg.com/j/2/widget.js"></script>
-{{ end }}
-
-{{ define "page_style" }}
-  <link rel="stylesheet" href="static/main.css"/>
-{{ end }}
-
-{{ define "title" }}O Hackerspace Warszawa{{ end }}
-
-{{ define "content" }}
-  <div id="left">
-      <div id="about">
-          <h2><a href="about_en">Read about us in English</a></h2>
-      <h1>O Hackerspace Warszawa</h1>
-      <p>
-        Hackerspace to przestrzeń stworzona i utrzymywana przez grupę kreatywnych osób, które łączy fascynacja 
-        ogólno pojętym tworzeniem w duchu kultury hackerskiej. Przestrzeń stymuluje rozwój projektów, organizując i 
-        użyczając potrzebnych narzędzi. Hackerspace nie zna barier, jeśli masz ciekawy pomysł i szukasz ludzi 
-        chętnych do współpracy lub po prostu potrzebujesz miejsca i sprzętu - zapraszamy! 
-      </p>
-      <p>
-        Jeżeli chcesz się do nas przyłączyć, przeczytaj dokładniejsze informacje o naszych celach i zapisz się 
-        na jedną z naszych list mailingowych. Zapraszamy również w każdy czwartek po 18:00, kiedy hackerspace jest otwarty
-	dla wszystkich zainteresowanych. Posiadamy również mail kontaktowy: <img src="static/kontakt.png"/>
-      </p>
-      <h3>Cele</h3>
-      <ul>
-        <li>Chcemy tworzyć miejsce, w którym ludzie zainteresowani techniką, elektroniką, informatyką, mechaniką, sztuką i pokrewnymi dziedzinami tworzenia mogą się zbierać aby rozmawiać, wymieniać się pomysłami i rozwiązaniami, oraz pracować nad projektami.</li>
-        <li>Chcemy żeby miejsce to było w Warszawie, w miejscu zapewniającym optymalny dojazd, zwłaszcza komunikacją publiczną.</li>
-        <li>Chcemy niezależności, dlatego uznajemy że optymalnym źródłem finansowania są obowiązkowe składki. Składki zostaną wykorzystane na finansowanie miejsca oraz, gdy to zostanie zapewnione, na zakup narzędzi.</li>
-        <li>Chcemy żeby miejsce było otwarte na nowych ludzi i nowe pomysły.</li>
-        <li>Chcemy zebrać narzędzia i wiedzę jak się ich używa w miejscu umożliwiającym ich używanie.</li>
-        <li>Chcemy się rozwijać i poznawać nowe dziedziny wiedzy</li>
-        <li>Chcemy się dobrze bawić.</li>
-      </ul>
-      <h3>Lokalizacja</h3>
-      <p>
-        Warszawski Hackerspace mieści się na podwyższonym parterze w budynku na ul. Wolność 2A.
-        <pre>
-          Warszawski Hackerspace
-	  ul. Wolność 2A
-	  01-018 Warszawa 
-	  52°14'29.8"N 20°59'5.5"E
-        </pre>
-	Wejście od ulicy Żelaznej. Po wejściu na plac/parking należy przejść tunelem pod budynkiem i wejść na klatkę schodową po lewej stronie. 
-      </p>
-      <div style="align:center, text-align:center"><iframe width="90%" height="550" frameborder="0" scrolling="no" marginheight="0" marginwidth="0" src="https://www.openstreetmap.org/export/embed.html?bbox=20.983859896659855%2C52.24110833176657%2C20.985909104347233%2C52.24213811954793&amp;layer=mapnik&amp;marker=52.241623228644364%2C20.98488450050354" style="border: 1px solid black"></iframe><br /></div><small><a href="https://www.openstreetmap.org/?mlat=52.24162&amp;mlon=20.98488#map=19/52.24162/20.98488">View Larger Map</a></small>
-      <h3>Członkowie</h3>
-      <ul>
-        <li>Hackerspace składa się z członków.</li>
-        <li>O członkostwie decyduje regularne płacenie składek.</li>
-        <li>Członkostwo daje dostęp do hackerspace 24/7.</li>
-        <li>Wszyscy członkowie są równi.</li>
-      </ul>
-      <h3>Składki</h3>
-      <ul>
-        <li><b>Starving Hacker</b>, minimalnie 50 PLN miesięcznie. Ograniczone do uczniów / studentów (lub specjalnych przypadków).</li>
-        <li><b>Fatty Hacker</b>, minimalnie 100 PLN miesięcznie.</li>
-        <li><b>Super-Fatty Hacker</b>, 150 PLN lub więcej dla bogatych Hackerów, którzy chcą dodatkowo wesprzeć rozwój HS.</li>
-      </ul>
-      <p>Potrzebujemy minimalnie 6000 PLN na miesiąc, żeby opłacić czynsz i rachunki. Dodatkowe fundusze są szczególnie potrzebne do dalszej ekspansji (nowe pomieszczenia), a także inwestycje w gadżety promujące, sprzęt i infrastrukturę HS.</p>
-      <h3>Dodatkowe informacje:</h3>
-      <ul>
-        <li>Nasza <a href="https://wiki.hackerspace.pl/faq">lista pytań i odpowiedzi (FAQ)</a></li>
-        <li>Informacje dla osób <a href="https://wiki.hackerspace.pl/jak-dolaczyc">chcących do nas dołączyć</a></li>
-        <li>Samouczek <a href="http://catb.org/~esr/faqs/hacker-howto.html">How To Become A Hacker</a> autorstwa ESR</li>
-      </ul>
-  </div>
-  <div id="right">
-    <!-- TODO(q3k): add this {% include "subscribe.html" %} -->
-    <h1 class="twitter">Twitter</h1>
-    <script type="text/javascript">
-    new TWTR.Widget({
-      version: 2,
-      type: 'profile',
-      rpp: 5,
-      interval: 6000,
-      theme: {
-        shell: {
-          background: '#eee',
-          color: '#222'
-        },
-        tweets: {
-          background: '#eee',
-          color: '#222',
-          links: '#001ea6'
-        }
-      },
-      features: {
-        scrollbar: false,
-        loop: false,
-        live: false,
-        hashtags: true,
-        timestamp: true,
-        avatars: true,
-        behavior: 'all'
-      }
-    }).render().setUser('hackerspacepl').start();
-    </script>
-  </div>
-{{ end }}
diff --git a/hswaw/site/templates/about_en.html b/hswaw/site/templates/about_en.html
deleted file mode 100644
index 9805785..0000000
--- a/hswaw/site/templates/about_en.html
+++ /dev/null
@@ -1,100 +0,0 @@
-{{ define "page_scripts" }}
-  <script type="text/javascript" src="https://widgets.twimg.com/j/2/widget.js"></script>
-{{ end }}
-
-{{ define "page_style" }}
-  <link rel="stylesheet" href="static/main.css"/>
-{{ end }}
-
-{{ define "title" }}About Warsaw Hackerspace{{ end }}
-
-{{ define "content" }}
-  <div id="left">
-      <div id="about">
-          <h2><a href="about">Poczytaj o nas po polsku</a></h2>
-      <h1>About the Warsaw Hackerspace</h1>
-      <p>
-        A hackerspace is a place created and maintained by a group of people whish combines fascination
-        about new technologies with a broadly understood creativity and hacker spirit. A hackerspace stimulates
-        the development of interesting projects by supplying necessary tools and attracting people with knowledge to
-        use them. A hackerspace know no limits, if you have an interesting idea and you are looking for people to help you
-        with it's development or simply a place to work - you are most welcome!
-      </p>
-      <p>
-        If you want to join us, read about our goals and join one of our mailing lists. We meet live at the Hackerspace every Thursday from 6pm. We also have a contact email address: <img src="static/kontakt.png"/>.
-      </p>
-      <h3>Warsaw Hackerspace Goals</h3>
-      <ul>
-        <li>We want to maintain a place in which people interested in technology, electronics, informatics, mechanics, art and related activities can meet to talk, exchange ideas, solutions and work together on their projects.</li>
-        <li>We want a Hackerspace to be located in Warsaw, in an easily accessbile place, especially using public transport.</li>
-        <li>We want independence, thats why we think that member fees are the best financial model for us. The collected money is used to rent physical space and buy necessary tools and equipment.</li>
-        <li>We want the Warsaw Hackerspace to be a friendly and open place for new people and ideas.</li>
-        <li>We want to maintain tools and knowledge about their usage in a place which allow for their safe use.</li>
-        <li>We want to develop and explore new areas of interest.</li>
-        <li>We want to have fun.</li>
-      </ul>
-      <h3>Location</h3>
-      <p>
-        The Warsaw Hackerspace is located on the ground floor at Wolność 2A road in Warsaw. The main entrance is from ul. Żelazna. After getting to the parking lot, use the tunnel under the building and enter the stairwell on the left side. 
-	<pre>
-          Warszawski Hackerspace
-	  ul. Wolność 2A
-	  01-018 Warszawa 
-	  52°14'29.8"N 20°59'5.5"E
-        </pre>
-      </p>
-      <div style="align:center, text-align:center"><iframe width="90%" height="550" frameborder="0" scrolling="no" marginheight="0" marginwidth="0" src="https://www.openstreetmap.org/export/embed.html?bbox=20.983859896659855%2C52.24110833176657%2C20.985909104347233%2C52.24213811954793&amp;layer=mapnik&amp;marker=52.241623228644364%2C20.98488450050354" style="border: 1px solid black"></iframe><br /></div><small><a href="https://www.openstreetmap.org/?mlat=52.24162&amp;mlon=20.98488#map=19/52.24162/20.98488">View Larger Map</a></small>
-      <h3>Membership</h3>
-      <ul>
-        <li>The Hackerspace is by and for members.</li>
-        <li>All members are required to pay a monthly fee.</li>
-        <li>All members are allowed to be in the Hackerspace 24/7.</li>
-        <li>All members are equal.</li>
-      </ul>
-      <h3>Contributions</h3>
-      <ul>
-        <li><b>Starving Hacker</b>, at least 50 PLN per month. Limited to students (and other special financial situations).</li>
-        <li><b>Fatty Hacker</b>, at least 100 PLN per month.</li>
-        <li><b>Super-Fatty Hacker</b>, 150 PLN or more - for rich Hackers who want to support our Hackerspace.</li>
-      </ul>
-      <p>We need at least 6000 PLN per monts to be able to pay the rent and bills. Additional funding is especially needed for further remodelling and adaption of our space, and for investments in gadgets, promotion and infrastructure.</p>
-      <h3>More informations:</h3>
-      <ul>
-        <li>Our <a href="https://wiki.hackerspace.pl/faq">Frequently Asked Questions (FAQ)</a></li>
-        <li>Information for people who wants to <a href="https://wiki.hackerspace.pl/jak-dolaczyc">join us</a></li>
-        <li>Tutorial <a href="http://catb.org/~esr/faqs/hacker-howto.html">How To Become A Hacker</a> by ESR</li>
-      </ul>
-  </div>
-  <div id="right">
-    <!-- TODO(q3k): add this {% include "subscribe_en.html" %} -->
-    <h1 class="twitter">Twitter</h1>
-    <script type="text/javascript">
-    new TWTR.Widget({
-      version: 2,
-      type: 'profile',
-      rpp: 5,
-      interval: 6000,
-      theme: {
-        shell: {
-          background: '#eee',
-          color: '#222'
-        },
-        tweets: {
-          background: '#eee',
-          color: '#222',
-          links: '#001ea6'
-        }
-      },
-      features: {
-        scrollbar: false,
-        loop: false,
-        live: false,
-        hashtags: true,
-        timestamp: true,
-        avatars: true,
-        behavior: 'all'
-      }
-    }).render().setUser('hackerspacepl').start();
-    </script>
-  </div>
-{{ end }}
diff --git a/hswaw/site/templates/basic.html b/hswaw/site/templates/basic.html
deleted file mode 100644
index d9f3b91..0000000
--- a/hswaw/site/templates/basic.html
+++ /dev/null
@@ -1,63 +0,0 @@
-<!DOCTYPE html>
-<!--[if lt IE 7]> <html class="no-js lt-ie9 lt-ie8 lt-ie7" lang="en"> <![endif]-->
-<!--[if IE 7]>    <html class="no-js lt-ie9 lt-ie8" lang="en"> <![endif]-->
-<!--[if IE 8]>    <html class="no-js lt-ie9" lang="en"> <![endif]-->
-<!--[if gt IE 8]><!--> <html class="no-js" lang="en"> <!--<![endif]-->
-<head>
-  <meta charset="utf-8" />
-  <!--[if IE]><meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" /><![endif]-->
-  <title>{{ template "title" . }}</title>
-  <script src="https://static.hackerspace.pl/js/jquery.min.js"></script>
-  <script src="https://static.hackerspace.pl/js/checkinator-header.js"></script>
-  {{ template "page_scripts" . }}
-  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
-  <meta name="google-site-verification" content="trUiKRnUNhystQjYg-j5j_0qEn09Wijd94aYhv3RyB8" />
-
-  <link rel="shortcut icon" href="https://static.hackerspace.pl/img/favicon.ico"/>
-  <link rel="apple-touch-icon" href="https://static.hackerspace.pl/img/glider.png"/>
-  <link rel="stylesheet" href="https://static.hackerspace.pl/fonts/news-cycle/stylesheet.css"/>
-  <link rel="stylesheet" href="https://static.hackerspace.pl/fonts/titillium/stylesheet.css"/>
-  <link rel="stylesheet" href="https://static.hackerspace.pl/css/style.css?v=2"/>
-  {{ template "page_style" . }}
-</head>
-<body>
-  <a name="top"></a>
-  <header id="hs_branding">
-    <a id="hs_header" href="https://hackerspace.pl/">Warsaw Hackerspace</a>
-    <ul id="hs_header_menu">
-      <li><a href="https://hackerspace.pl/about">about</a></li>
-      <li><a href="https://blog.hackerspace.pl">blog</a></li>
-      <li><a href="https://gallery.hackerspace.pl">gallery</a></li>
-      <li><a href="https://wiki.hackerspace.pl">wiki</a></li>
-      <li><a href="https://code.hackerspace.pl">code</a></li>
-      <li><a href="https://webchat.hackerspace.pl">chat</a></li>
-      <li><a href="https://redmine.hackerspace.pl">tasks</a></li>
-      <li><a href="https://wiki.hackerspace.pl/partners">partners</a></li>
-    </ul>
-  </header>
-  <div id="hs_main">
-    <div id="hs_rotimage">
-      <div id="status" style="display: none;">
-       <div id="status-tooltip" style="display: none;">
-         <p>Refreshing...</p>
-       </div>
-       <img src="https://static.hackerspace.pl/img/status-open.png" alt="Sorry, we're open! - The Warsaw Hackerspace is open right now." />
-      </div>
-      <div class="hs_login_bar">
-          <ul>
-            <li><a class="action user" href="https://ldap.hackerspace.pl">Member profile</a></li>
-          </ul>
-      </div>
-    </div>
-    <div id="hs_content">
-      {{ template "content" . }}
-    </div>
-  </div>
-  {{ template "footer" . }}
-</body>
-</html>
-
-{{ define "page_scripts" }}{{ end }}
-{{ define "page_style" }}{{ end }}
-{{ define "content" }}{{ end }}
-{{ define "footer" }}{{ end }}
diff --git a/hswaw/site/templates/index.html b/hswaw/site/templates/index.html
new file mode 100644
index 0000000..48f0acd
--- /dev/null
+++ b/hswaw/site/templates/index.html
@@ -0,0 +1,75 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<!-- https://html.spec.whatwg.org/multipage/syntax.html#syntax-tag-omission -->
+<title>Warszawski Hackerspace</title>
+<meta name="viewport" content="width=device-width, initial-scale=1.0" />
+<link rel="preconnect" href="https://fonts.gstatic.com">
+<link rel="stylesheet" href="/static/site/landing.css"/>
+<link rel="stylesheet" href="/static/leaflet/leaflet.css"/>
+<link href="https://fonts.googleapis.com/css2?family=Allerta&family=Lato&display=swap" rel="stylesheet">
+<style>
+</style>
+<div id="page">
+    <div class="top">
+        <div class="logo">
+            <img src="/static/site/syrenka.png" />
+            <h1>Warszawski Hackerspace</h1>
+        </div>
+        <div class="mapcontainer">
+            <h2>Gdzie jesteśmy?</h2>
+            <div id="map"></div>
+            <pre>Warszawski Hackerspace
+ul. Wolność 2A
+01-018 Warszawa 
+52°14'29.8"N 20°59'5.5"E</pre>
+        </div>
+    </div>
+    <div class="covid">
+        <span>Na okres pandemii Hackerspace jest <b>zamknięty</b>, więcej informacji: <a href="">projekt covid-19</a></span>
+    </div>
+    <div class="bottom">
+        <div class="about">
+            <h2>Czym jest Hackerspace?</h2>
+            <p>
+              Przestrzeń stworzona i utrzymywana przez grupę kreatywnych osób, które łączy fascynacja ogólno pojętym tworzeniem w duchu <a href="https://pl.wikipedia.org/wiki/Spo%C5%82eczno%C5%9B%C4%87_haker%C3%B3w">kultury hackerskiej</a>.
+            </p>
+            <p>
+              <b>Hackerspace nie zna barier.</b> Jeśli masz ciekawy pomysł i szukasz ludzi chętnych do współpracy lub po prostu potrzebujesz miejsca i sprzętu - <a href="">zapraszamy</a>!
+            </p>
+        </div>
+        <div class="blog">
+            <h2>Blog</h2>
+            <p>
+                Najnowsze wpisy z naszego <a href="https://blog.hackerspace.pl">bloga</a>:
+                <ul>
+                    {{ range .Entries }}
+                    <li><a href="{{ .Link.Href }}">{{ .Title }}</a> <i>{{ .UpdatedHuman }}, {{ .Author }}</i></li>
+                    {{ else }}
+                    <li><i>Ups, nie udało się załadować wpisów.</i></li>
+                    {{ end }}
+                </ul>
+            <p>
+        </div>
+    </div>
+
+    <div id="footer">
+        <span>&copy; 2021 <a href="https://cs.hackerspace.pl/hscloud/-/tree/hswaw/site">Autorzy Strony</a>. Ten utwór jest dostępny na <a rel="license" href="http://creativecommons.org/licenses/by/4.0/">licencji Creative Commons Uznanie autorstwa 4.0 Międzynarodowe</a>.</span>
+    </div>
+</div>
+
+<script>
+window.loadMap = () => {
+    let map = L.map('map', {
+        center: [52.24158, 20.9848],
+        zoom: 13,
+        scrollWheelZoom: false,
+    });
+
+    L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
+        attribution: '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a>'
+    }).addTo(map);
+
+    L.marker([52.24158, 20.9848]).addTo(map);
+}
+</script>
+<script src="/static/leaflet/leaflet.js" crossorigin="" onload="loadMap()"></script>
diff --git a/hswaw/site/templates/main.html b/hswaw/site/templates/main.html
deleted file mode 100644
index 536ffa7..0000000
--- a/hswaw/site/templates/main.html
+++ /dev/null
@@ -1,74 +0,0 @@
-{% extends 'basic.html' %}
-{% block page_scripts %}
-  <script type="text/javascript" src="https://widgets.twimg.com/j/2/widget.js"></script>
-{% endblock %}
-{% block page_style %}
-  <link rel="stylesheet" href="static/main.css"/>
-{% endblock %}
-{% block title %}Hackerspace Warszawa - strona główna{% endblock %}
-{% block content %}
-  <div id="left">
-    <div id="about">
-      <h1>Czym jest Hackerspace?</h1>
-      <p>
-        Hackerspace to przestrzeń stworzona i utrzymywana przez grupę kreatywnych osób, które łączy fascynacja 
-        ogólno pojętym tworzeniem w duchu <a href="http://ultra.ap.krakow.pl/~raj/hacker-howto.html">kultury 
-        hackerskiej</a>. Przestrzeń stymuluje rozwój projektów, organizując i użyczając potrzebnych narzędzi. 
-        Hackerspace nie zna barier, jeśli masz ciekawy pomysł i szukasz ludzi chętnych do współpracy lub po 
-        prostu potrzebujesz miejsca i sprzętu - zapraszamy!
-      </p>
-      <a href="/about" class="moar">Gdzie jesteśmy? Co robimy? Jak do nas dołączyć? Dowiedz się więcej!</a>
-    </div>
-  <h1>Nowości</h1>
-  <ul class="news">
-  {% for e in entries %}
-  <li class="{{e.tag}}">
-      <a class="news-title" href="{{e.link}}">
-        <h3><span class="news-rectangle">blog</span>{{e.title|safe}}</h3>
-      </a>
-      <p class="news">
-        {{e.summary|safe}}
-      </p>
-      <p class="news-footer">
-        Ostatnio aktualizowane {{e.updated_display}} przez {{e.author_detail.name or 'Anonymous'}}
-      </p>
-    </li>
-  {% endfor %}
-  </ul>
-  <h1>Kalendarz</h1>
-  <iframe style="max-width: 750px;width:100%;border: none;" height="315" src="https://owncloud.hackerspace.pl/index.php/apps/calendar/embed/g8toktZrA9fyAHNi"></iframe>
-  </div>
-  <div id="right">
-    {% include "subscribe.html" %}
-    <h1 class="twitter">Twitter</h1>
-    <script type="text/javascript">
-    new TWTR.Widget({
-      version: 2,
-      type: 'profile',
-      rpp: 5,
-      interval: 6000,
-      theme: {
-        shell: {
-          background: '#eee',
-          color: '#222'
-        },
-        tweets: {
-          background: '#eee',
-          color: '#222',
-          links: '#001ea6'
-        }
-      },
-      features: {
-        scrollbar: false,
-        loop: false,
-        live: false,
-        hashtags: true,
-        timestamp: true,
-        avatars: true,
-        behavior: 'all'
-      }
-    }).render().setUser('hackerspacepl').start();
-    </script>
-  </div>
-  <span class="clear"><a href="#top">↑ Powrót na górę ↑</a></span>
-{% endblock %}
diff --git a/hswaw/site/templates/subscribe.html b/hswaw/site/templates/subscribe.html
deleted file mode 100644
index 61ad4b0..0000000
--- a/hswaw/site/templates/subscribe.html
+++ /dev/null
@@ -1,19 +0,0 @@
-<form action="/" method="post">
-  <input name=_csrf_token type=hidden value="{{ csrf_token() }}">
-  <h1 class="mail" style="text-align: center;">Listy mailingowe</h1>
-  <h2 style="text-align: center; margin-top: -6px;">Zapisz się i porozmawiaj</h2>
-  <h4><input name="mail-waw" type="checkbox" class="mailcheck" checked="true" />Warszawska</h4>
-  <div class="mail-desc">Główna lista dyskusyjna hackerspaceu - organizacja i rozwiązywanie problemów.</div>
-  <h4><input name="mail-proj" type="checkbox" class="mailcheck" checked="true"/>Warszawska Projektowa</h4>
-  <div class="mail-desc">Informacje o postępie projektów, pomysły, ogłaszanie sukcesów i porażek.</div>
-  <h4><input name="mail-offtopic" type="checkbox" class="mailcheck" />Off Topic</h4>
-  <div class="mail-desc">Dyskusja o wszystkim i o niczym. Niski stosunek sygnału do szumu.</div>
-  <center>
-    <input type="text" name="email" class="email-entry" placeholder="twoj@email.com" />
-    <div class="mail-captcha">
-        {{ captcha() }}
-        <input type="text" name="captcha" class="email-entry" placeholder="Przepisz CAPTCHA...">
-    </div>
-    <input type="submit" value="Zapisz się" class="email-submit" />
-  </center>
-</form>
diff --git a/hswaw/site/templates/subscribe_en.html b/hswaw/site/templates/subscribe_en.html
deleted file mode 100644
index e8c352c..0000000
--- a/hswaw/site/templates/subscribe_en.html
+++ /dev/null
@@ -1,19 +0,0 @@
-<form action="/" method="post">
-  <input name=_csrf_token type=hidden value="{{ csrf_token() }}">
-  <h1 class="mail" style="text-align: center;">Mailing lists</h1>
-  <h2 style="text-align: center; margin-top: -6px;">Subscribe and chat</h2>
-  <h4><input name="mail-waw" type="checkbox" class="mailcheck" checked="true" />Warsaw</h4>
-  <div class="mail-desc">Main discussion list - organization and problem solving.</div>
-  <h4><input name="mail-proj" type="checkbox" class="mailcheck" checked="true"/>Warsaw Projects</h4>
-  <div class="mail-desc">Information about our projects.</div>
-  <h4><input name="mail-offtopic" type="checkbox" class="mailcheck" />Off Topic</h4>
-  <div class="mail-desc">Discussion about everything and nothing. Low signal to noise ratio.</div>
-  <center>
-    <input type="text" name="email" class="email-entry" placeholder="you@example.com" />
-    <div class="mail-captcha">
-        {{ captcha() }}
-        <input type="text" name="captcha" class="email-entry" placeholder="Solve the CAPTCHA...">
-    </div>
-    <input type="submit" value="Subscribe" class="email-submit" />
-  </center>
-</form>
diff --git a/hswaw/site/views.go b/hswaw/site/views.go
index ae62322..59f948e 100644
--- a/hswaw/site/views.go
+++ b/hswaw/site/views.go
@@ -40,8 +40,7 @@
 }
 
 var (
-	tmplAbout   = template.Must(parseTemplates("basic", "about"))
-	tmplAboutEn = template.Must(parseTemplates("basic", "about_en"))
+	tmplIndex = template.Must(parseTemplates("index"))
 )
 
 // render attempts to render a given Go template with data into the HTTP
@@ -52,12 +51,9 @@
 	}
 }
 
-// handleAbout handles rendering the about page at /about
-func (s *service) handleAbout(w http.ResponseWriter, r *http.Request) {
-	render(w, tmplAbout, nil)
-}
-
-// handleAboutEn handles rendering the about page at /about_en
-func (s *service) handleAboutEn(w http.ResponseWriter, r *http.Request) {
-	render(w, tmplAboutEn, nil)
+// handleIndex handles rendering the main page at /.
+func (s *service) handleIndex(w http.ResponseWriter, r *http.Request) {
+	render(w, tmplIndex, map[string]interface{}{
+		"Entries": s.getFeeds(),
+	})
 }
diff --git a/personal/q3k/factorio/kube/prod.jsonnet b/personal/q3k/factorio/kube/prod.jsonnet
deleted file mode 100644
index 27dd38d..0000000
--- a/personal/q3k/factorio/kube/prod.jsonnet
+++ /dev/null
@@ -1,114 +0,0 @@
-local factorio = import "factorio.libsonnet";
-local kube = import "../../../../kube/kube.libsonnet";
-
-
-// Available versions:
-//  - 0.18.40-1
-//  - 1.0.0-1
-
-{
-    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,
-        }
-    },
-
-    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"),
-            ],
-        },
-    },
-}
diff --git a/shell.nix b/shell.nix
index 3e0bdc4..1b504cf 100644
--- a/shell.nix
+++ b/shell.nix
@@ -25,6 +25,8 @@
     openldap.dev cyrus_sasl.dev # for python-ldap
     wkhtmltopdf
     gcc binutils
+    pwgen
+    tmate
   ];
   multiPkgs = pkgs: [
     (pkgs.runCommand "protocols" {}
diff --git a/third_party/factorio/factorio.bzl b/third_party/factorio/factorio.bzl
index 67ae5c1..b6ed8e1 100644
--- a/third_party/factorio/factorio.bzl
+++ b/third_party/factorio/factorio.bzl
@@ -17,6 +17,7 @@
 # version -> sha256 of server tarball
 _versions = {
     "1.0.0": "81d9e1aa94435aeec4131c8869fa6e9331726bea1ea31db750b65ba42dbd1464",
+    "1.1.34": "21969321cf370e95066f86fddfcb83d1a23ed9b67d087c1cb47d43e87673ca69",
 }
 
 def factorio_repository(version, sha256):