~glorifiedgluer/monorepo

61c0e9d6a85526c6a38b379933093b08d9f7b229 — Victor Freire 1 year, 3 months ago b5ca379 main
glorifiedgluercom: refactor: remove from repo
30 files changed, 0 insertions(+), 2146 deletions(-)

M .build.yml
M flake.nix
D glorifiedgluercom/.gitignore
D glorifiedgluercom/LICENSE
D glorifiedgluercom/README.md
D glorifiedgluercom/config.toml
D glorifiedgluercom/content/content.org
D glorifiedgluercom/content/export.el
D glorifiedgluercom/default.nix
D glorifiedgluercom/layouts/404.html
D glorifiedgluercom/layouts/_default/blogroll.html
D glorifiedgluercom/layouts/_default/list.atom.xml
D glorifiedgluercom/layouts/_default/single.html
D glorifiedgluercom/layouts/blog/section.html
D glorifiedgluercom/layouts/blog/single.html
D glorifiedgluercom/layouts/index.html
D glorifiedgluercom/layouts/partials/footer.html
D glorifiedgluercom/layouts/partials/nav.html
D glorifiedgluercom/layouts/shortcodes/toc.html
D glorifiedgluercom/static/LICENSE
D glorifiedgluercom/static/blogroll.xml
D glorifiedgluercom/static/css/main.scss
D glorifiedgluercom/static/favicon.ico
D glorifiedgluercom/static/img/nana-icons/README.md
D glorifiedgluercom/static/img/nana-icons/capybara.jpg
D glorifiedgluercom/static/img/nana-icons/shiba-banana.jpg
D glorifiedgluercom/static/img/nana-icons/shiba-computer-low.jpg
D glorifiedgluercom/static/img/nana-icons/shiba-computer.jpg
D glorifiedgluercom/static/img/nana-icons/shy-rat.jpg
D glorifiedgluercom/static/img/raspberry-argon.jpg
M .build.yml => .build.yml +0 -9
@@ 21,15 21,6 @@ tasks:
          --domain capivaras.dev \
          --not-found 404.html
      fi
  - glorifiedgluercom: |
      cd monorepo
      if ! $(git diff --quiet HEAD HEAD^ -- "glorifiedgluercom")
      then
        nix build .#glorifiedgluercom
        nix develop .#glorifiedgluercom-ci -c hut pages publish ./result/site.tar.gz \
          --domain glorifiedgluer.com \
          --not-found 404.html
      fi
  - mata: |
      cd monorepo
      if ! $(git diff --quiet HEAD HEAD^ -- "mata")

M flake.nix => flake.nix +0 -1
@@ 39,7 39,6 @@
      projects = builtins.map (project: import project { inherit inputs; }) [
        ./capivarasdev
        ./dotfiles
        ./glorifiedgluercom
        ./mata
      ];
    in

D glorifiedgluercom/.gitignore => glorifiedgluercom/.gitignore +0 -13
@@ 1,13 0,0 @@
# Generated files by hugo
/public/
/resources/_gen/
/assets/jsconfig.json
hugo_stats.json

# Executable may be added to repository
hugo.exe
hugo.darwin
hugo.linux

# Temporary lock file while building
/.hugo_build.lock

D glorifiedgluercom/LICENSE => glorifiedgluercom/LICENSE +0 -26
@@ 1,26 0,0 @@
Copyright 2020 Drew DeVault <sir@cmpwn.com>

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:

1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.

2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.

3. Neither the name of the copyright holder nor the names of its contributors
may be used to endorse or promote products derived from this software without
specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

D glorifiedgluercom/README.md => glorifiedgluercom/README.md +0 -10
@@ 1,10 0,0 @@
# glorifiedgluer.com

Home to [glorifiedgluer.com](https://glorifiedgluer.com). Check out the 
[about](./content/about.md).

## Features

- `toc` or `table of contents`: add `toc: true` to the frontmatter of a blog
post to render it below the title or use the shortcode `{{< toc >}}` to render
it wherever you want to inside the document.
\ No newline at end of file

D glorifiedgluercom/config.toml => glorifiedgluercom/config.toml +0 -33
@@ 1,33 0,0 @@
title = "~glorifiedgluer"
baseURL = "https://glorifiedgluer.com"
languageCode = "en-us"

pygmentsUseClasses = true
assetDir = "static"

[outputs]
  section = ["HTML", "ATOM"]

[permalinks]
  blog = '/blog/:year/:slug/'

[author]
	name = "Victor Freire"

[mediaTypes."application/atom+xml"]
  suffixes = ["xml"]

[outputFormats.Atom]
  name = "Atom"
  mediaType = "application/atom+xml"
  baseName = "atom"

[outputFormats.RSS]
	mediaType = "application/rss+xml"
	baseName = "rss"

[markup.goldmark.renderer]
unsafe = true

[markup.tableOfContents]
ordered = true

D glorifiedgluercom/content/content.org => glorifiedgluercom/content/content.org +0 -1051
@@ 1,1051 0,0 @@
#+TITLE: ~glorifiedgluer
#+AUTHOR: ~glorifiedgluer

#+HUGO_BASE_DIR: ../

* Pages
:PROPERTIES:
:EXPORT_HUGO_SECTION: /
:END:
** About
:PROPERTIES:
:EXPORT_TITLE: About
:EXPORT_FILE_NAME: about
:EXPORT_HUGO_CUSTOM_FRONT_MATTER: :description About this site and me.
:END:
*** About me
I'm *Victor Freire*, a student and software developer based in *Guarulhos,
Brazil*, currently working at [[https://divisionsmg.com][Divisions Maintenance Group]] as a
software engineer writing [[https://fsharp.org/][F#]] and [[https://nixos.org][Nix]].

I've taught myself coding, cloud computing and linux for years, out of passion.
At the moment I'm mostly interested in learning about highly scalable systems.

My projects live at [[https://git.sr.ht/~glorifiedgluer][sourcehut]] and [[https://github.com/ratsclub][GitHub]], you can also contact me via [[mailto:victor@freire.dev.br][email]] or
[[https://matrix.to/#/@stoicallyincorrect:mdzk.app][matrix]].

*** About this site

**** Why?
This site was built for the only reason of sharing and documenting most of my
interests.

**** How?
I intend to only use [[https://www.gnu.org/philosophy/floss-and-foss.html][FOSS]] while producing its content.

- Icon made by my beloved nana
- Generated using [[https://hugo.io][Hugo]], theme inspired by [[https://harelang.org][The Hare programming language website]]
- Source code at [[https://git.sr.ht/~glorifiedgluer/monorepo/tree/main/item/glorifiedgluercom][sourcehut]]
- Built with [[https://nixos.org][Nix]] and [[https://builds.sr.ht/~glorifiedgluer/monorepo][builds.sr.ht]]
- Hosted at [[https://srht.site/][pages.sr.ht]]

** Blogroll
:PROPERTIES:
:EXPORT_TITLE: Blogroll
:EXPORT_FILE_NAME: blogroll
:EXPORT_HUGO_CUSTOM_FRONT_MATTER: :description A rendered version of my OPML file. :layout blogroll
:END:
This page is a rendered version of my [[https://en.wikipedia.org/wiki/OPML][OPML]] file.

** Home
:PROPERTIES:
:EXPORT_TITLE: ~glorifiedgluer
:EXPORT_FILE_NAME: _index
:EXPORT_HUGO_TYPE: homepage
:EXPORT_HUGO_CUSTOM_FRONT_MATTER: :description ~glorifiedgluer's personal website.
:END:
This page is empty because I didn't find anything interesting to put here yet.

Feel free to check out the navigation links above.

* Blog :@blog:
:PROPERTIES:
:EXPORT_HUGO_SECTION: blog
:END:
** Index
:PROPERTIES:
:EXPORT_TITLE: ~glorifiedgluer blog
:EXPORT_FILE_NAME: _index
:EXPORT_DATE: 1970-01-01
:END:
#+begin_description
Scattered thoughts, ideas and experiences.
#+end_description
** This will deteriorate quickly
:PROPERTIES:
:EXPORT_FILE_NAME: this-will-deteriorate-quickly
:EXPORT_HUGO_CUSTOM_FRONT_MATTER: :slug this-will-deteriorate-quickly
:EXPORT_DATE: 2020-03-01
:END:
#+begin_description
Thoughts about the current state of the web, content consuming and its longevity.
#+end_description

In my teenager years I was a member of my school's student council. Part of our
job was to propose changes to our current infrastructure, organize study groups
and plan social projects. We were a bunch of youngsters without much experience
-- or no experience at all -- in the education field, therefore we resorted to
know from those who had it over the Internet. At the time there wasn't a lot of
content about it on social media, the majority was writing at Blogspot (now
[[https://blogger.com][Blogger]]) or forums.

Here comes [[https://pt.wikipedia.org/wiki/RSS][RSS]]. Blogspot offered a *RSS Feed* by default so it was rather simple
to follow dozens of blogs. Every member of the council used a RSS reader they
liked the most and it worked great.

Years passed and I found the list of blogs I used to read at that time. While
diving through the links I found out most of them didn't receive an update since
my highschool (or have just disappeared). However, they always had a link to the
author's social media (Twitter, Facebook or LinkedIn) and you could see those
profiles were being updated regularly. A lot of the educators continued to write
posts about their field of interest, but now on social media.

In the meantime, I observed a pattern between profiles. They tended to reference
their old posts from the Blogger platform instead of publishing them again on
the new platform. Why so? You could see some of them tried to do that but
formatting was awkward as Facebook doesn't provide the same tools Blogger
offered and Twitter's posts are too short. So I feel they just couldn't retrieve
their posts and they were long gone from their hands.

So how would an individual escape this madness? It's quite simple, a blog post
is nothing but static content. Text, images videos and audios. Use this in your
favor. Write text on known formats such as Markdown[fn:1], [[https://pt.wikipedia.org/wiki/ReStructuredText][ReStructuredText]] or
even plain-text. Store all files in a [[https://git-scm.com][Git]] repository, external hard drive,
Dropbox... whatever. Just stick to the simplest file formats and reliable
storage. Services like [[https://medium.com][Medium]], [[https://substack.com][Substack]] and [[https://tumblr.com][Tumblr]] can die and take all your
posts with them at any time.

At the moment I'm using [[https://neovim.io][Neovim]] to edit my blog posts in Markdown and hosting
them in [[https://mataroa.blog][mataroa]][fn:2]. Alternatives are: [[https://gohugo.io][Hugo]], [[https://getzola.org][Zola]] and more. All you need to setup a
web site is one of these programs, a bunch of Markdown files and a web server.
You'll also find a lot of places to host your content for free such as [[https://gitlab.com][GitLab]],
[[https://github.com][GitHub]] or [[https://netlify.com][Netlify]].

Although this approach brings some advantages, it has some shortcomings too. In
a static website you won't have a comments section without a third-party
service; basic tech knowledge is needed to know where to put files in your web
server provider of choice and if you want a custom domain such as this one
you'll have to do some configuration. A quick YouTube tutorial might be
sufficient to teach you how to do all of the above in minutes.

** Burning out and finding out
:PROPERTIES:
:EXPORT_FILE_NAME: burning-out-and-finding-out
:EXPORT_HUGO_CUSTOM_FRONT_MATTER: :slug burning-out-and-finding-out
:EXPORT_DATE: 2021-08-31
:END:
#+begin_description
On being burnout for the first time.
#+end_description

At the time of this writing I'm burned out. I had no doubt it was a thing and
that it could happen to anyone. However, I couldn't see myself suffering from
this. At least not so soon.

#+begin_quote
Burnout is caused when you repeatedly make large amounts of sacrifice and or
effort into high-risk problems that fail. It's the result of a negative
prediction error in the nucleus accumbens. You effectively condition your brain
to associate work with failure. -- [[https://news.ycombinator.com/item?id=5630618][Source]]
#+end_quote

I've been pretty active for the past 4 to 5 years due to college, courses, work
and other activities. Yet all of this haven't bothered me in the slightest,
until now. I couldn't take control of basic chores, missed the point of meetings
after a few moments, had no will to leave the bed and many other things.
Moreover, It's a strange feeling with predefined steps:

1. You know what you have to do;
2. You know it's within your capabilities;
3. You get excited to do the task;
4. Your body just /will not do it/.

There you go, you now have the recipe to fight against yourself for a whole day.
This will be a excruciating battle until you hit the bed and repeat it the next
morning. That is, if you aren't already going to sleep late due to forcing
yourself to be productive throughout the day. Realizing I was battling my own
was really important to improve my situation. I started by cleaning my room,
then exercising, putting a alarm to tell me when to eat and so on. My next step
was to change my whole environment by looking after a new job.

Currently I'm not in position to take a sabbatical period of time to discover
new hobbies or a new career - and I don't want to, as I love my current
profession. So, what's left to ponder to change this situation, I may ask
myself? To be honest, I have no clue. While I'm in the process to land a new job
I feel that this might be the response I needed to this feeling. New challenges,
new people, new technologies and new everything.

** write: broken pipe
:PROPERTIES:
:EXPORT_FILE_NAME: write-broken-pipe
:EXPORT_HUGO_CUSTOM_FRONT_MATTER: :slug write-broken-pipe
:EXPORT_DATE: 2022-04-06
:END:
#+begin_description
The adventure of figuring out the "tcp: write: broken pipe" error.
#+end_description

*tl;dr*: [[https://docs.konghq.com/kubernetes-ingress-controller/][Kong Ingress Controller]] was the culprit. Its timeout setting was
closing the connection before the file could be sent. /If you're facing this
issue in a long-lasting request, check your reverse proxy configuration, as it
may have a different configuration than your application./ ;-)

At Grupo SBF we have an HTTP server written in [[https://go.dev/][Go]] that queries [[https://cloud.google.com/bigquery][BigQuery]] and
returns the result as a *big* csv file. However, after some time we sent a
request and instead of a file, we received this error message:

#+begin_src
write tcp 10.0.0.1:8080->10.0.0.2:38302: write: broken pipe
#+end_src

Well, this is quite a surprise as we haven't seen this error message before...
After all, what does it even mean? A quick Google search returned this:

#+begin_quote
A condition in programming (also known in POSIX as EPIPE error code and SIGPIPE
signal), when a process requests an output to pipe or socket, which was closed
by peer. -- [[https://en.wikipedia.org/wiki/Broken_pipe][Wikipedia]]
#+end_quote

Hmm, this /definitely/ shed some light on the problem. Considering that the HTTP
server is provided by the powerful [[https://pkg.go.dev/net/http][net/http]] package in Go's standard library, we
might have some obvious places to check out.

Cloudflare has a [[https://blog.cloudflare.com/exposing-go-on-the-internet/][great article]] talking about the default configuration on Go's
HTTP server and how to avoid making mistakes with them. We jumped straight to
the article's timeout section and checked if we didn't forget to configure them.

#+begin_src go
srv := &http.Server{
    ReadTimeout:  10 * time.Minute, // 10 minutes
    WriteTimeout: 10 * time.Minute,
    Addr:         ":8080",
    Handler:      r,
}
#+end_src

For context, our application takes about 2 minutes to send a response so this
isn't a problem for us as we have 10 minutes until a [[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/504][504 server error]] is
returned.

To our amazement, sending the request to a local server returned no error
whatsoever. Comparing our local environment with production we also noticed that
our request was /dropped/ at exactly 1 minute of execution in production.
Therefore it must be something between our client and server!

Knowing that we deploy to a Kubernetes cluster with a [[https://docs.konghq.com/kubernetes-ingress-controller/][Kong Ingress Controller]]
_{controlling} taking care of our reverse proxy, we checked its documentation
and... Bingo! This is the root of our problem, as per the [[https://docs.konghq.com/gateway/1.1.x/reference/proxy/#3-proxying-and-upstream-timeouts][Kong Ingress
Controller Documentation]] the default timeout is =60_000= milliseconds -- in
other words, 1 minute!

*** Replicating the behavior
   :PROPERTIES:
   :CUSTOM_ID: replicating-the-behavior
   :END:
Before trying something on our servers, why don't we replicate this behavior
locally? For this purpose we can run a =nginx= container and a simple Go HTTP
server with a similar functionality of our service.

The idea behind the test is to setup an endpoint that takes a lot of time
writing on the buffer while our reverse proxy has a timeout of only 2 seconds.

**** Go server and Dockerfile
    :PROPERTIES:
    :CUSTOM_ID: go-server-and-dockerfile
    :END:
#+begin_src go
func main() {
    mux := http.NewServeMux()
    mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        time.Sleep(time.Second * 10)

        // creating a large data size
        // that will take a long time to be written
        size := 900 * 1000 * 1000
        tpl := make([]byte, size)
        t, err := template.New("page").Parse(string(tpl))
        if err != nil {
            log.Printf("error parsing template: %s", err)
            return
        }

        if err := t.Execute(w, nil); err != nil {
            log.Printf("error writing: %s", err)
            return
        }
    })

    srv := &http.Server{
        ReadTimeout: 10 * time.Minute,
        WriteTimeout: 10 * time.Minute,
        Addr: ":8080",
        Handler: mux,
    }

    log.Println("server is running!")
    log.Println(srv.ListenAndServe())
}
#+end_src

And then the Dockerfile:

#+begin_src Dockerfile
# server.Dockerfile
FROM golang:alpine AS build
RUN apk --no-cache add gcc g++ make git
WORKDIR /go/src/app
COPY . .
RUN go mod init server
RUN go mod tidy
RUN GOOS=linux go build -ldflags="-s -w" -o ./bin/web-app ./server.go

FROM alpine:3.13
RUN apk --no-cache add ca-certificates
WORKDIR /usr/bin
COPY --from=build /go/src/app/bin /go/bin
EXPOSE 8080
ENTRYPOINT /go/bin/web-app --port 8080
#+end_src

**** nginx configuration and Dockerfile
    :PROPERTIES:
    :CUSTOM_ID: nginx-configuration-and-dockerfile
    :END:
#+begin_src conf
# nginx.conf
events {
    worker_connections 1024;
}

http {
  server_tokens off;
  server {
    listen 80;

    location / {
      proxy_set_header X-Forwarded-For $remote_addr;
      proxy_set_header Host            $http_host;

      # timeout set to 2 seconds
      proxy_read_timeout 2s;
      proxy_connect_timeout 2s;
      proxy_send_timeout 2s;

      proxy_pass http://goservice:8080/;
    }
  }
}
#+end_src

And then the Dockerfile:

#+begin_src Dockerfile
# nginx.Dockerfile
FROM nginx:latest
EXPOSE 80
COPY nginx.conf /etc/nginx/nginx.conf
#+end_src

**** Docker Compose
    :PROPERTIES:
    :CUSTOM_ID: docker-compose
    :END:
The last piece missing is a [[https://docs.docker.com/compose/][Docker
Compose]] file to help us run these containers:

#+begin_src yaml
# docker-compose.yaml
version: "3.6"
services:
  goservice:
    build: "server.Dockerfile"
    ports:
      - "8080"
  nginx:
    build: "nginx.Dockerfile"
    ports:
      - "80:80"
    depends_on:
      - "goservice"
#+end_src

**** Running and testing
    :PROPERTIES:
    :CUSTOM_ID: running-and-testing
    :END:
After setting up our environment we can test it by running the commands below:

- =docker-compose up --build= to run our containers
- =curl localhost= to make a request to our server

Voilá! The error shows up confirming our theory!

#+begin_src
goservice_1  | 2022/04/07 01:12:14 error writing: write tcp 172.18.0.2:8080->172.18.0.3:56768: write: broken pipe
#+end_src

*** Conclusion
  :PROPERTIES:
  :CUSTOM_ID: conclusion
  :END:
This was a lot of fun to figure it! As noted by our tests the timeout
configuration of our cluster's reverse proxy was indeed the issue, overriding
the timeout settings with the snippet below solved the issue instantly!

#+begin_src yaml
apiVersion: configuration.konghq.com/v1
kind: KongIngress
metadata:
  annotations:
    kubernetes.io/ingress.class: "kong"
  name: kong-timeout-conf
proxy:
  connect_timeout: 10000000 # 10 minutes
  protocol: http
  read_timeout: 10000000
  retries: 10
  write_timeout: 10000000
---
apiVersion: v1
kind: Service
metadata:
  annotations:
    konghq.com/override: kong-timeout-conf
#+end_src

** Notes on builds.sr.ht
:PROPERTIES:
:EXPORT_DATE: 2022-04-29
:EXPORT_FILE_NAME: notes-on-buildssrht
:EXPORT_HUGO_CUSTOM_FRONT_MATTER: :slug notes-on-buildssrht
:END:
#+begin_description
I quite like builds.sr.ht and want to share some of the reasons.
#+end_description

For the past few months I've been using [[https://sr.ht][sourcehut]]'s platform to work on software
an it has been quite an interesting experience. Nonetheless, one of the services
I really enjoy using is the their build service called [[https://builds.sr.ht][builds.sr.ht]].

#+begin_quote
builds.sr.ht is a service on sr.ht that allows you to submit "build manifests"
for us to work on. -- [[https://man.sr.ht/builds.sr.ht/][man.sr.ht]]
#+end_quote

The thing I don't like on [[https://github.com/features/actions][GitHub Actions]] is that it is kind of /magical/. For
example, you don't actually know what it is doing when you define that an
=action= should only run when a specific path is modified. Not to even mention
their [[https://docs.github.com/pt/actions/creating-actions][custom actions]] which usually takes a non-trivial amount of
TypeScript/JavaScript.

Contrary to this, [[https://builds.sr.ht][builds.sr.ht]] is /really/ explicit on its [[https://man.sr.ht/builds.sr.ht/manifest.md][build manifest]].
You're basically expected to write plain shell scripts for your builds.

*** Reducing resource usage
   :PROPERTIES:
   :CUSTOM_ID: reducing-resource-usage
   :END:
As I said previously, there's no special syntax to work on specific paths,
branches, pull requests and such. By default your task will run on every commit
you push. In order to reduce our CI usage we can restrain our tasks to run on
specific scenarios:

**** On path change
    :PROPERTIES:
    :CUSTOM_ID: on-path-change
    :END:
#+begin_src sh
if ! $(git diff --quiet HEAD HEAD^ -- "<your-path>")
then
  # do something
fi
#+end_src

**** On branch change
    :PROPERTIES:
    :CUSTOM_ID: on-branch-change
    :END:
This tip was taken from [[https://todo.sr.ht/~sircmpwn/builds.sr.ht/170][issue #170]].

#+begin_example
tasks:
- check-branch: |
   cd repo_name
   if [ "$(git rev-parse your-branch)" != "$(git rev-parse HEAD)" ]; then \
      complete-build; \
   fi
#+end_example

*** NixOS on builds.sr.ht
   :PROPERTIES:
   :CUSTOM_ID: nixos-on-builds.sr.ht
   :END:
As I don't like to write shell scripts I use Nix and this is my favorite feature
of this service. builds.sr.ht supports [[https://nixos.org][NixOS]] by default[fn:3]. This means that
we can leverage Nix Flakes for truly declarative and reproducible builds there!
Let's consider a small example using [[https://go.dev][Go]] to show you how easy it really is. A
small =flake.nix= containing the following content should suffice our needs:

#+begin_example
{
  inputs.nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable";

  outputs = { self, nixpkgs, ... }:
    let pkgs = import nixpkgs { system = "x86_64-linux"; };
    in
    {
      devShells."x86_64-linux".ci = with pkgs; mkShell {
        buildInputs = [ go golangci-lint ];
      };
    };
}
#+end_example

This definition is capable of giving us a shell containing Go and [[https://github.com/golangci/golangci-lint][golangci-lint]]
on =$PATH=.

Now let's write the build manifest for our CI:

#+begin_example
image: nixos/unstable
packages:
- nixos.nixUnstable
environment:
  NIX_CONFIG: "experimental-features = nix-command flakes"
tasks:
  - lint: |
      cd source
      nix develop .#ci -c golangci-lint run
  - test: |
      cd source
      nix develop .#ci -c go test ./...
  - build: |
      cd source
      nix develop .#ci -c go build
#+end_example

And that's it! We have our CI up and running with the guarantee of having our
tools being the same on every run. No sudden updates or unexpected behavior.

** Running a Raspberry Pi 4 with NixOS
:PROPERTIES:
:EXPORT_DATE: 2022-05-09
:EXPORT_FILE_NAME: running-a-raspberry-pi-4-with-nixos
:EXPORT_HUGO_CUSTOM_FRONT_MATTER: :slug running-a-raspberry-pi-4-with-nixos
:END:
#+begin_description
Configuring and running NixOS on a Raspberry Pi 4.
#+end_description

For quite some time I've been wanting to run a small homelab with [[https://nixos.org][NixOS]]. I don't
host much services myself, however I feel that I can have a lot of fun (and
learn /a bit/) by maintaining my own server. All the services I run on the
Cloud™ (Matrix Dendrite and a Nix Binary Cache) could be running on a Raspberry
Pi inside my drawer. So that be it!

#+caption: A picture of Raspberry Pi inside an Argon One case and a Keychron K2V2 behind
[[/img/raspberry-argon.jpg]]

*** Setup
   :PROPERTIES:
   :CUSTOM_ID: setup
   :END:
At the time of writing my setup looks like this:

- Case Argon ONE M.2
- KingSpec SSD M.2 SATA - 512GB
- Random Flash Drive - 8GB (you can also use a SD Card)
- Raspberry Pi 4 - 8GB

*** Flashing
   :PROPERTIES:
   :CUSTOM_ID: flashing
   :END:
Download the NixOS =aarch64= image. Personally I went with the [[https://hydra.nixos.org/job/nixos/trunk-combined/nixos.sd_image_new_kernel.aarch64-linux][unstable branch]]
as I like to live dangerously but you can choose [[https://nixos.wiki/wiki/NixOS_on_ARM#SD_card_images_.28SBCs_and_similar_platforms.29][other versions]] if you want to.
After that you just need to =dd= it to your flash drive and boot it:

#+begin_src shell
$ sudo dd if=nixos.img of=/dev/sdX bs=4096 conv=fsync status=progress
#+end_src

*Notes*:
- Don't forget to extract the image before flashing it.
- If using the Argon One M.2 case, don't boot the USB Drive with your SSD connected. Otherwise your raspberry will try to boot from the SSD and not your Flash Drive/SD Card.

*** Formatting
   :PROPERTIES:
   :CUSTOM_ID: formatting
   :END:
You can actually follow the [[https://nixos.org/manual/nixos/stable][NixOS Manual]] to partition your hard drive. However
I've written a script to help me do this:

#+begin_src shell
# replace /dev/sda with your SSD
export FMT_DISK=/dev/sda

wipefs -a $FMT_DISK

export DISK=/dev/disk/by-id/ata*

parted $FMT_DISK -- mklabel msdos
parted $FMT_DISK -- mkpart primary fat32 0MiB 512MiB # $DISK-part1 is /boot
parted $FMT_DISK -- mkpart primary 512MiB -4GiB # $DISK-part2 is the ext4 partition
parted $FMT_DISK -- mkpart primary linux-swap -4GiB 100% # Swap

mkfs.ext4 -L nixos $DISK-part2
mount $DISK-part2 /mnt

mkfs.vfat -F32 $DISK-part1
mkdir -p /mnt/boot
mount $DISK-part1 /mnt/boot
#+end_src

*** NixOS Configuration
   :PROPERTIES:
   :CUSTOM_ID: nixos-configuration
   :END:
In order to boot correctly, you need to define some boot options[fn:4]:

#+begin_src nix
{
  boot = {
    initrd.availableKernelModules = [ "usbhid" "usb_storage" ];
    kernelPackages = pkgs.linuxPackages_rpi4;
    kernelParams = [
      "8250.nr_uarts=1"
      "cma=128M"
      "console=tty1"
      "console=ttyAMA0,115200"
    ];

    loader = {
      raspberryPi = {
        enable = true;
        version = 4;
      };

      grub.enable = false;
      generic-extlinux-compatible.enable = true;
    };
  };

  hardware.enableRedistributableFirmware = true;
}
#+end_src

*** Boot firmware
   :PROPERTIES:
   :CUSTOM_ID: boot-firmware
   :END:
The installer disk has a partition containing the necessary firmwares to boot
(it was on =/dev/sda1/= for me). Just copy it to your boot partition.

#+begin_src shell
mkdir /firmware
mount /dev/sda1 /firmware
cp /firmware/* /mnt/boot
#+end_src

*** Installing
   :PROPERTIES:
   :CUSTOM_ID: installing
   :END:
**** With Channels
    :PROPERTIES:
    :CUSTOM_ID: with-channels
    :END:
The only step left is to install the system:

#+begin_src shell
nixos-install --root /mnt
#+end_src

**** With Flakes
    :PROPERTIES:
    :CUSTOM_ID: with-flakes
    :END:
Another way to install it is to make use of Nix [[https://nixos.wiki/wiki/Flakes][Flakes]]. This way we can ensure
that our build is completely reproducible and/or running the same software
version as the other machines.

This is a rather simple process if you already have a repo configured with your
[[https://nixos.org][NixOS]] configurations. First, I need a shell with =git= and a [[https://nixos.org][Nix]] version that
supports the experimental [[https://nixos.wiki/wiki/Flakes][Flakes]] commands.

#+begin_src shell
nix-shell -p git nixUnstable
#+end_src

After that I just clone my repository, copy the =hardware-configuration.nix=
file over and install the system.

#+begin_src shell
# clone the repository
git clone https://git.sr.ht/~glorifiedgluer/dotfiles
cd dotfiles

# copy hardware-configuration.nix
cp /mnt/etc/nixos/hardware-configuration.nix hosts/rpi4/

# install the system
nixos-install --flake .#rpi4
#+end_src
** Starting a personal monorepo
:PROPERTIES:
:EXPORT_DATE: 2022-05-11
:EXPORT_FILE_NAME: starting-a-personal-monorepo
:EXPORT_HUGO_CUSTOM_FRONT_MATTER: :slug starting-a-personal-monorepo
:ID:       143b30fd-8d32-4e67-9e13-5bf8a47ea8e2
:END:
#+begin_description
Starting my journey with a personal monorepo managed by Nix.
#+end_description

I've been using [[https://nixos.org][Nix]] as my package manager for 4 years now and it has been the
best /computer-related/ decision I have ever made and fortunately, for the past
few years its ecosystem has been growing a lot[fn:5] [fn:6] [fn:7]. Some of this
movement is due to the advent o [[https://nixos.wiki/wiki/Flakes][Flakes]] that makes it /way/ easier to share
reproducible outputs than the previous Nix solution of channels.

Considering that I can use Nix:

- to share build artifacts (binaries, Nix modules and such);
- to manage my dependencies;
- as a build system.

I thought to myself: "Why not build a personal monorepo"? I mean, this might
sound like a weird conclusion to take from all of this but I can explain! I
swear!

*** Rationale
  :PROPERTIES:
  :CUSTOM_ID: rationale
  :END:
Sometimes I just get bored setting up a new project. Create a new repository,
setup the dependencies, write a CI manifest... it's too tiresome! I won't even
mention the pain in the ass that is to write multiple projects on the multiple
machines. The clone, fetch, pull and push dance is just too much when I could be
coding already.

Most of my personal projects are written in [[https://go.dev][Go]], a really boring language that
takes its time to include new features and release new versions. This means that
an update won't break them and that I can take advantage of a way to share the
same compiler and tooling version through my projects.

If you're a Nix user, a single command would show you all the outputs available
for use: =nix flake show sourcehut:~glorifiedgluer/monorepo=. This also means
that you can import this repo as an input on your =flake.nix= file and use any
of my projects as you please.

The CI can be simplified to a simple shell conditional:

#+begin_src yaml
tasks:
  - someproject: |
      cd monorepo
      if ! $(git diff --quiet HEAD HEAD^ -- "someproject")
      then
        # do something if the project got an update
      fi
#+end_src

Nonetheless, the best reason to try this is out is to have some fun and explore
new challenges with version control and build systems! ;-)

*** Expectation
  :PROPERTIES:
  :CUSTOM_ID: expectation
  :END:
I mean... none? lol. Being serious now, I don't expect my projects to become
something used by hundreds or thousands of users as most of them are done out of
passion/need. So the rationale above is composed of things that personally took
out part of the joy of bulding out something and seeing it run.

Is this going to work? I have no idea as I don't have much experience with
monorepos. I'm not really sure if this is going to scale or bore me in other
ways. The only certainty I have is that I'm having fun doing it /right now/!

You can see the repository on the links below:

- [[https://github.com/ratsclub/monorepo][GitHub]]
- [[https://git.sr.ht/~glorifiedgluer/monorepo/][sourcehut]]

** Git mirroring, systemd and NixOS
:PROPERTIES:
:EXPORT_DATE: 2022-06-14
:EXPORT_FILE_NAME: git-mirroring-systemd-nixos
:EXPORT_HUGO_CUSTOM_FRONT_MATTER: :slug git-mirroring-systemd-nixos
:END:
#+begin_description
Configuring a Git mirror with systemd services and timers on NixOS.
#+end_description

For the past few years I have been collecting contributions to multiple projects
on multiple platforms such as GitHub, GitLab, self-hosted Gitea instances and so
on. It's rather boring to go to a website and see the source code there... Then
I thought to myself: "Why not write about a made up need I don't have just to
learn something new?".

So, the idea here was to mirror those repositories into my [[https://sourcehut.org][sourcehut]] account
(although this should work for any remote repository). For this we will use a
[[https://nixos.org][NixOS]] system and [[https://www.freedesktop.org/software/systemd/man/systemd.timer.html][systemd timers]]. The idea is dead simple, we clone the
repositories and push them to our desired remote.

*** Configuring the repository
   :PROPERTIES:
   :CUSTOM_ID: configuring-the-repository
   :END:
This step is pretty easy and can be done in two steps:

1. Clone the repository

#+begin_src sh
$ git clone --mirror https://git.com/repo
#+end_src

2. Configure the remote as to ensure that we will only push to the
   desired remote.

#+begin_src sh
$ cd repo
$ git remote set-url --push origin https://remote.com/repo-mirror
#+end_src

*** systemd to the rescue
   :PROPERTIES:
   :CUSTOM_ID: systemd-to-the-rescue
   :END:
We have our repository but we are still missing an important step that is to
keep pushing new changes to our mirror.

[[https://nixos.org][NixOS]] has a pretty good declarative way of declaring systemd services and timers
that we can take advantage of here. The idea is to have a script being ran in
our diretory through a systemd /service/ that will be invoked by a systemd
/timer/ hourly.

**** The script
    :PROPERTIES:
    :CUSTOM_ID: the-script
    :END:
There's nothing novel here. This script will iterate over the directories inside
the =WorkingDirectory=, fetch updates and then push it to our mirror.

#+begin_src nix
let
  gitmirrorScript = pkgs.writeShellScriptBin "gitmirror" ''
    for d in */ ; do
      git -C "$d" fetch -p origin
      git -C "$d" push --mirror
    done
  '';
in
#+end_src

**** The service and timer
    :PROPERTIES:
    :CUSTOM_ID: the-service-and-timer
    :END:
The service is rather simple too, we pass our repository's directory through the
=WorkingDirectory= value and set the =gitmirror= service as the unit to be
invoked by our timer. Note, however, that we added =git= /and/ =openssh= to the
path. Your root user should be able to authenticate on boths repos with its ssh
key.

#+begin_src nix
{
  systemd.services.gitmirror = {
    enable = true;
    description = "Git mirror service";
    after = [ "network.target" ];
    path = with pkgs; [ git openssh ];
    serviceConfig = {
      Type="oneshot";
      WorkingDirectory = "/home/glorifiedgluer/repo";
      ExecStart = "${gitmirrorScript}/bin/gitmirror";
    };
    wantedBy = [ "multi-user.target" ];
  };

  systemd.timers.gitmirror = {
    description = "Git mirror timer";
    timerConfig = {
      OnCalendar = "hourly";
      Unit = "gitmirror.service";
    };
    wantedBy = [ "timers.target" ];
  };
}
#+end_src

** Moving this website to a single Org Mode file
:PROPERTIES:
:EXPORT_DATE: 2022-07-11
:EXPORT_FILE_NAME: moving-site-org-mode
:EXPORT_HUGO_CUSTOM_FRONT_MATTER: :slug moving-site-org-mode
:END:
#+begin_description
This website is now contained in a single Org Mode file.
#+end_description

I have always loved [[https://www.gnu.org/software/emacs/][GNU Emacs]] and its integrated computing environment. It has
been even better after I started using [[https://github.com/doomemacs/doomemacs][Doom Emacs]][fn:8], it basically took care of
things I was unable to do properly: make it fast and semantically coherent.
Either for the lack of time or technical knowledge.

As most GNU Emacs users, I love [[https://orgmode.org/][Org Mode]] and I love to write for this blog. So
why not join these two things together? [[https://ox-hugo.scripter.co/][ox-hugo]] let's me write a /org/ file and
turn it into multiple /hugo-compatible/ markdown files. This is quite a feature
for me as I like to keep all my /stuff/ into one place[fn:9].

It would be pretty cool to have a place to share small trips with pictures and
some comments. Thinking about it a bit more, it might work like some sort of
microblog but... different? I should start doing it and stop ovethinking. It
would be pretty cool to read a huge file with years of history written on it!

Oh, and you can see the file I'm talking about right here:
[[https://git.sr.ht/~glorifiedgluer/monorepo/blob/main/glorifiedgluercom/content/content.org][sourcehut:~glorifiedgluer/monorepo/glorifiedgluercom/content/content.org]].
** ErgoJourney - Choosing a new keyboard layout
:PROPERTIES:
:EXPORT_DATE: 2022-07-18
:EXPORT_FILE_NAME: ergojourney-choosing-a-new-keyboard-layout
:END:
#+begin_description
The beginning of my journey for an ergonomic setup. Starting with a new keyboard layout.
#+end_description

After multiple injuries to my right wrist due to a multitude of activities
(sports, bad typing[fn:13] and an /act of god/) I decided to change my keyboard layout
to one that could possibly demand less work off of my hands.

First let's go through a small history of keyboards I've previously used.
Unfortunately I don't actually have pictures of them as I don't have the habit
to take pictures of things (which I should reconsider!). Briefly, the complete
list is the following:

1. IBM Model M
2. ThinkPad X230
3. HyperX Alloy FPS Pro (Cherry MX Blue)
4. Keychron K2V2 (Cherry MX Red)
5. Corne V3 (failed attempt, the PCB wasn't delivered)
6. SZA Moonlander Mark I

*** Previously used keyboards

Let's talk about the keyboards I have owned for the past decade.
There was a place near (São Paulo is huge but everything is close if you can
walk to the subway) my work called /Santa Efigênia/. At the time, this was the
biggest place to go look after tech gadgets here in São Paulo.

As all places like this, there were a lot of second-hand shops. Places that
bought boxes after boxes of old corporate hardware. And this is how I got my
hands on an *[[https://en.wikipedia.org/wiki/Model_M_keyboard][IBM Model M]]*! I'm going to be honest with you, I didn't know it was
a /rare/ keyboard nor that it was an icon of some sort. I just liked the design
and bought it for a cheap price as it was the cooler PS/2 keyboard I could find
there.

After selling my /Model M/ way cheaper than I should (😭) I got a *[[https://en.wikipedia.org/wiki/ThinkPad_X_series#X230][ThinkPad
X230]]* that I used for about 10 years or so. I really liked the feeling of the
keyboard and even tried to mod it to use the X220 but I have the unpopular
opinion that the X230 has the best keyboard.

While using my /X230/ I finally discovered what a mechanical keyboard is +and
instantly regretted my decisions on the /Model M/+ and got a *[[https://row.hyperx.com/pt-br/products/hyperx-alloy-fps-pro-mechanical-gaming-keyboard][HyperX Alloy FPS
Pro]]* with /Cherry MX Blue/ switches for a steal. For the price I paid it was an
actually OK keyboard, however the full price was not worth it in my opinion. I
found the switch too heavy for hours of typing and the sound was just... weird.
I can't explain but for me it was not that pleasant type on it. Anyway, I ended
up selling it too.[fn:10]

*** Current keyboard

My current keyboard is the [[https://www.keychron.com/products/keychron-k2-hot-swappable-wireless-mechanical-keyboard][Keychron K2 Version 2]]. It's Wireless, Hot-swappable
(meaning that I can /swap/ the switches), Compact layout (84 keys[fn:11]) and
Gateron G Red Switch (pre-lubed).

Some things I learned with this keyboard is that I more fond of linear switches
than clicky/tactile ones. The thing that bothered me the most is that the
keycaps accumulated a lot of grease and started to get too shiny[fn:12].

# TODO add a picture of my keychron

*** Future Keyboards
**** Corne V3

One of the first things you discover when you start to look after ergonomic
keyboards is that you can build one yourself. There is a multitude of
communities, projects and contents over the internet.

I really liked some models:

1. [[https://github.com/davidphilipbarr/Sweep][Sweep]] is a /34 keys/ split keyboard. I wanted a bit more keys, so I
   discarded this one.
2. [[https://github.com/diepala/cantor/][Cantor]] is a /42 keys/ split keyboard. The problem with this one is that I
   couldn't find the required low-profile switches for cheap, so I discarded
   this option. However, it was my favorite design!
3. [[https://github.com/foostan/crkbd][Corne]] is a /36 keys/ split keyboard. This is probably the most famous split
   keyboard. I chose it because it was basically the cheapest option for me and
   also had more keys than Sweep.

Although I bought everything needed to start soldering the Corne together, my
country's post office probably lost my PCB during delivery. So I don't have much
to say about, if they happen to deliver it I might write about my experience
soldering it or just straight out buy the [[https://keyhive.xyz/shop/corne-v3][complete kit from KeyHive]].

**** SZA Moonlader Mark I

At the moment I'm waiting for my [[https://www.zsa.io/moonlander/][Moonlander SZA Mark I]] to arrive. I didn't do
much research on the keyboard as I wasn't intending on buying one (too expensive
here) and instead I got one as a gift! Given this, I thought it would be cool to wait
for a cool unboxing experience to a novel technology for me.

# TODO add a picture of my moonlander with the keycaps, don't forget to link the keycaps

*** Drinking the Colemak Kool-Aid

Considering this huge introduction, my conclusion was that I should probably
take advantage of this new keyboard form I'm getting and learn a new keyboard
layout. This might give me some benefits upon my wrist injuries and make typing
less painful.

I was between [[https://en.wikipedia.org/wiki/Dvorak_keyboard_layout][Dvorak]] and [[https://colemak.com/][Colemak]] but the thing is, all the discussions around
these layouts seemed to be mostly about personal preferences so I decided to
pick one with the most sensible technique: *the coin flip* and the coin told me
to go with Colemak.

Through my small research I found out that Colemak ships by default on most
Linux distros and it works very good with other languages (Brazilian Portuguese
🇧🇷).

I guess that the only thing left is to practice typing on it now!

* Footnotes

[fn:1] https://commonmark.org/

[fn:2] This post did indeed deteriorate quickly as 2 years later I'm writing this blog on [[https://www.gnu.org/software/emacs/][GNU Emacs]] with [[https://orgmode.org/][Org Mode]] + [[https://ox-hugo.scripter.co/][ox-hugo]] and hosting it on [[https://srht.site/][sourcehut pages]].

[fn:3] https://nixos.org

[fn:4] https://nixos.wiki/wiki/NixOS_on_ARM/Raspberry_Pi_4#Configuration

[fn:5] https://blog.replit.com/nix

[fn:6] https://shopify.engineering/what-is-nix

[fn:7] https://hercules-ci.com/

[fn:8] For the past few weeks it has been even better with the work of
[[https://github.com/thiagokokada][github:thiagokokada]] on the [[https://github.com/nix-community/nix-doom-emacs/][github:nix-community/nix-doom-emacs]] repository.
Really, kudos for taking care of this project! 🎉

[fn:9] [[id:143b30fd-8d32-4e67-9e13-5bf8a47ea8e2][Starting a personal monorepo]]

[fn:13] I've never learned how to touch type correctly and as such I only use at most 3 fingers on each hand.

[fn:10] [[https://github.com/yuri-potatoq][Yuri]] was the friend of mine that bought it and actually liked to type on it. Different people, different switch tastes. 😊

[fn:11] Most known as a 75% layout.

[fn:12] I fixed this with a cheap [[https://pt.aliexpress.com/item/32946133227.html][keyset from AliExpress]] that meant to go to my Corne V3.

D glorifiedgluercom/content/export.el => glorifiedgluercom/content/export.el +0 -13
@@ 1,13 0,0 @@
;;; export --- used to export my org-roam notes with ox-hugo

(require 'ox)
(require 'org)

(setq org-directory "."
      org-id-locations-file ".orgids")

(with-current-buffer (find-file "content.org")
  (message (format "exporting content/content.org context=build"))
  (org-hugo-export-wim-to-md :all-subtrees nil nil nil))

;;; export.el ends here

D glorifiedgluercom/default.nix => glorifiedgluercom/default.nix +0 -60
@@ 1,60 0,0 @@
{ inputs, ... }:

let
  inherit (inputs) nixpkgs utils;
in
utils.lib.eachDefaultSystem
  (system:
  let
    pkgs = import nixpkgs { inherit system; };
    inherit (pkgs)
      emacs-nox
      emacsPackagesFor
      hugo
      hut
      lib
      mkShell
      stdenv
      ;
  in
  {
    packages.glorifiedgluercom =
      let
        customEmacs = (emacsPackagesFor emacs-nox).emacsWithPackages
          (epkgs: with epkgs.melpaPackages; [
            ox-hugo
          ]
          ++ (with epkgs.elpaPackages; [
            org
          ]));
      in
      stdenv.mkDerivation {
        name = "glorifiedgluercom";
        src = lib.cleanSource ./.;

        buildInputs = [
          customEmacs
          hugo
        ];

        configurePhase = ''
          cd content
          emacs $(pwd) --batch -load export.el
          cd ..
        '';

        buildPhase = ''
          hugo
          tar -cvzf site.tar.gz -C public .
        '';

        installPhase = ''
          mkdir -p $out
          cp -r site.tar.gz $out
        '';
      };

    devShells.glorifiedgluercom-ci = mkShell {
      buildInputs = [ hut ];
    };
  })

D glorifiedgluercom/layouts/404.html => glorifiedgluercom/layouts/404.html +0 -18
@@ 1,18 0,0 @@
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <title>{{$.Title}}</title>
    {{ $style := resources.Get "css/main.scss" | resources.ToCSS | resources.Minify
    | resources.Fingerprint }}
    <link rel="stylesheet" href="{{ $style.RelPermalink }}" />

  </head>
  <body>
    {{ partial "nav.html" }}
    <main>
        404. Page not found.
    </main>
  </body>
</html>

D glorifiedgluercom/layouts/_default/blogroll.html => glorifiedgluercom/layouts/_default/blogroll.html +0 -39
@@ 1,39 0,0 @@
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <title>{{.Title}}</title>
    {{ $style := resources.Get "css/main.scss" | resources.ToCSS | resources.Minify
    | resources.Fingerprint }}
    <link rel="stylesheet" href="{{ $style.RelPermalink }}" />

    {{ template "_internal/opengraph.html" . }}
  </head>
  <body>
    {{ partial "nav.html" }}
    <main>
      <h2>{{.Title}}</h2>
      <p><a href="{{ .Site.BaseURL }}/blogroll.xml">OPML File</a></p>
      
      {{.Content}}
      
      {{ $resource := resources.Get "blogroll.xml" | transform.Unmarshal }}
      {{ range $resource.body }}
        {{ range $category := . }}
          <h2>
            {{ index $category "-title" }}
            <small>{{ index $category "-text" }}</small>
          </h2>
          {{ range $entry := .outline }}
            <h4>
              <a href="{{ index $entry "-htmlUrl" }}">{{ index $entry "-title" }}</a>
              <sup>(<a href="{{ index $entry "-xmlUrl" }}">feed</a>)</sup>
            </h4>
            <p>{{ index $entry "-text" | markdownify | safeHTML }}</p>
          {{ end }}
        {{ end }}
      {{ end }}
    </main>
  </body>
</html>

D glorifiedgluercom/layouts/_default/list.atom.xml => glorifiedgluercom/layouts/_default/list.atom.xml +0 -32
@@ 1,32 0,0 @@
{{ printf "<?xml version=\"1.0\" encoding=\"utf-8\" standalone=\"yes\" ?>" | safeHTML }}
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>{{ .Site.Title }}</title>
  <link href="{{ .Permalink }}"/>
  {{ with .OutputFormats.Get "Atom" }}
    {{ printf "<link href=%q rel=\"self\" type=%q />" .Permalink .MediaType | safeHTML }}
  {{ end }}
  <updated>{{ .Date.Format "2006-01-02T15:04:05Z07:00" | safeHTML }}</updated>
  <author>
    {{ with $.Site.Author.name }}<name>{{ . }}</name>{{ end }}
    {{ with $.Site.Author.email }}<email>{{ . }}</email>{{ end }}
  </author>
  <generator>Hugo</generator>
  <id>{{ .Permalink }}</id>
  {{ range first 15 .Pages }}
  <entry>
    <title>{{ .Title }}</title>
    <link rel="alternate" href="{{ .Permalink }}"/>
    <id>{{ .Permalink }}</id>
    <published>{{ .Date.Format "2006-01-02T15:04:05Z07:00" | safeHTML }}</published>
    <updated>{{ .Lastmod.Format "2006-01-02T15:04:05Z07:00" | safeHTML }}</updated>
    <summary>{{ .Summary | html }}</summary>
    <content type="html">{{ .Content | html }}</content>
    {{ range .Params.tags }}
    <category term="{{ . }}"/>
    {{ end }}
    {{ range .Params.categories }}
    <category term="{{ . }}"/>
    {{ end }}
  </entry>
  {{ end }}
</feed>

D glorifiedgluercom/layouts/_default/single.html => glorifiedgluercom/layouts/_default/single.html +0 -20
@@ 1,20 0,0 @@
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <title>{{.Title}}</title>
    {{ $style := resources.Get "css/main.scss" | resources.ToCSS | resources.Minify
    | resources.Fingerprint }}
    <link rel="stylesheet" href="{{ $style.RelPermalink }}" />

    {{ template "_internal/opengraph.html" . }}
  </head>
  <body>
    {{ partial "nav.html" }}
    <main>
      <h2>{{.Title}}</h2>
      {{.Content}}
    </main>
  </body>
</html>

D glorifiedgluercom/layouts/blog/section.html => glorifiedgluercom/layouts/blog/section.html +0 -36
@@ 1,36 0,0 @@
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />

    {{ range .AlternativeOutputFormats -}}
			<link rel="{{ .Rel }}" type="{{ .MediaType.Type }}" href="{{ .Permalink }}" title="{{ $.Site.Title }}" />
		{{ end -}}

    <title>{{ .Site.Title }}</title>
    {{ $style := resources.Get "css/main.scss" | resources.ToCSS | resources.Minify
    | resources.Fingerprint }}
    <link rel="stylesheet" href="{{ $style.RelPermalink }}" />

    {{ template "_internal/opengraph.html" . }}
  </head>
  {{ partial "nav.html" }}
  <main>
    <h2>Posts</h2>
    <p><a href="{{.Site.BaseURL}}/blog/atom.xml">RSS feed</a></p>
    {{ range .Data.Pages }}
    <div class="stub">
      <h2>
        <a href="{{.Permalink}}"> {{.Title}} </a>
      </h2>
      <small>
        {{.Date.Format "January 2, 2006"}}
        {{ with .Params.author }}
          by {{ . }}
        {{ end }}
      </small>
    </div>
    {{ end }}
  </main>
</html>

D glorifiedgluercom/layouts/blog/single.html => glorifiedgluercom/layouts/blog/single.html +0 -37
@@ 1,37 0,0 @@
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <title>{{$.Title}} - {{site.Title}}</title>

    {{ $style := resources.Get "css/main.scss" | resources.ToCSS | resources.Minify
    | resources.Fingerprint }}
    <link rel="stylesheet" href="{{ $style.RelPermalink }}" />

    {{ template "_internal/opengraph.html" . }}
  </head>
  {{ partial "nav.html" }}
  <body>
    <main>
      <h2>
        {{.Title}}
        <small>
          {{.Date.Format "January 2, 2006"}}
          {{ with .Params.author }}
            by {{ . }}
          {{ end }}
        </small>
      </h2>

      {{ if .Params.toc }}
        <h2>Table of Contents</h2>
        {{ .TableOfContents }}
      {{ end }}

      {{.Content}}

      {{ partial "footer.html" }}
    </main>
  </body>
</html>

D glorifiedgluercom/layouts/index.html => glorifiedgluercom/layouts/index.html +0 -17
@@ 1,17 0,0 @@
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <title>{{$.Title}}</title>
    {{ $style := resources.Get "css/main.scss" | resources.ToCSS | resources.Minify
    | resources.Fingerprint }}
    <link rel="stylesheet" href="{{ $style.RelPermalink }}" />

    {{ template "_internal/opengraph.html" . }}
  </head>
  <body>
    {{ partial "nav.html" }}
    <main>{{.Content}}</main>
  </body>
</html>

D glorifiedgluercom/layouts/partials/footer.html => glorifiedgluercom/layouts/partials/footer.html +0 -6
@@ 1,6 0,0 @@
<footer>
    <p><em>
        Have something to say about this? Feel free to send me an email at
        <a href="mailto:~glorifiedgluer/inbox@lists.sr.ht">my inbox</a>!
    </em></p>
</footer>

D glorifiedgluercom/layouts/partials/nav.html => glorifiedgluercom/layouts/partials/nav.html +0 -15
@@ 1,15 0,0 @@
<nav>
  <img
    src="/img/nana-icons/shiba-computer-low.jpg"
    alt="A shiba using the computer"
    width="128"
    height="128"
  />
  <h1>{{ site.Title }}</h1>
  <ul>
    <li><a href="/">Home</a></li>
    <li><a href="/about">About</a></li>
    <li><a href="/blog">Blog</a></li>
    <li><a href="/blogroll">Blogroll</a></li>
  </ul>
</nav>

D glorifiedgluercom/layouts/shortcodes/toc.html => glorifiedgluercom/layouts/shortcodes/toc.html +0 -2
@@ 1,2 0,0 @@
<h2>Table of Contents</h2>
{{ .Page.TableOfContents }}
\ No newline at end of file

D glorifiedgluercom/static/LICENSE => glorifiedgluercom/static/LICENSE +0 -177
@@ 1,177 0,0 @@

                                 Apache License
                           Version 2.0, January 2004
                        http://www.apache.org/licenses/

   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION

   1. Definitions.

      "License" shall mean the terms and conditions for use, reproduction,
      and distribution as defined by Sections 1 through 9 of this document.

      "Licensor" shall mean the copyright owner or entity authorized by
      the copyright owner that is granting the License.

      "Legal Entity" shall mean the union of the acting entity and all
      other entities that control, are controlled by, or are under common
      control with that entity. For the purposes of this definition,
      "control" means (i) the power, direct or indirect, to cause the
      direction or management of such entity, whether by contract or
      otherwise, or (ii) ownership of fifty percent (50%) or more of the
      outstanding shares, or (iii) beneficial ownership of such entity.

      "You" (or "Your") shall mean an individual or Legal Entity
      exercising permissions granted by this License.

      "Source" form shall mean the preferred form for making modifications,
      including but not limited to software source code, documentation
      source, and configuration files.

      "Object" form shall mean any form resulting from mechanical
      transformation or translation of a Source form, including but
      not limited to compiled object code, generated documentation,
      and conversions to other media types.

      "Work" shall mean the work of authorship, whether in Source or
      Object form, made available under the License, as indicated by a
      copyright notice that is included in or attached to the work
      (an example is provided in the Appendix below).

      "Derivative Works" shall mean any work, whether in Source or Object
      form, that is based on (or derived from) the Work and for which the
      editorial revisions, annotations, elaborations, or other modifications
      represent, as a whole, an original work of authorship. For the purposes
      of this License, Derivative Works shall not include works that remain
      separable from, or merely link (or bind by name) to the interfaces of,
      the Work and Derivative Works thereof.

      "Contribution" shall mean any work of authorship, including
      the original version of the Work and any modifications or additions
      to that Work or Derivative Works thereof, that is intentionally
      submitted to Licensor for inclusion in the Work by the copyright owner
      or by an individual or Legal Entity authorized to submit on behalf of
      the copyright owner. For the purposes of this definition, "submitted"
      means any form of electronic, verbal, or written communication sent
      to the Licensor or its representatives, including but not limited to
      communication on electronic mailing lists, source code control systems,
      and issue tracking systems that are managed by, or on behalf of, the
      Licensor for the purpose of discussing and improving the Work, but
      excluding communication that is conspicuously marked or otherwise
      designated in writing by the copyright owner as "Not a Contribution."

      "Contributor" shall mean Licensor and any individual or Legal Entity
      on behalf of whom a Contribution has been received by Licensor and
      subsequently incorporated within the Work.

   2. Grant of Copyright License. Subject to the terms and conditions of
      this License, each Contributor hereby grants to You a perpetual,
      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
      copyright license to reproduce, prepare Derivative Works of,
      publicly display, publicly perform, sublicense, and distribute the
      Work and such Derivative Works in Source or Object form.

   3. Grant of Patent License. Subject to the terms and conditions of
      this License, each Contributor hereby grants to You a perpetual,
      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
      (except as stated in this section) patent license to make, have made,
      use, offer to sell, sell, import, and otherwise transfer the Work,
      where such license applies only to those patent claims licensable
      by such Contributor that are necessarily infringed by their
      Contribution(s) alone or by combination of their Contribution(s)
      with the Work to which such Contribution(s) was submitted. If You
      institute patent litigation against any entity (including a
      cross-claim or counterclaim in a lawsuit) alleging that the Work
      or a Contribution incorporated within the Work constitutes direct
      or contributory patent infringement, then any patent licenses
      granted to You under this License for that Work shall terminate
      as of the date such litigation is filed.

   4. Redistribution. You may reproduce and distribute copies of the
      Work or Derivative Works thereof in any medium, with or without
      modifications, and in Source or Object form, provided that You
      meet the following conditions:

      (a) You must give any other recipients of the Work or
          Derivative Works a copy of this License; and

      (b) You must cause any modified files to carry prominent notices
          stating that You changed the files; and

      (c) You must retain, in the Source form of any Derivative Works
          that You distribute, all copyright, patent, trademark, and
          attribution notices from the Source form of the Work,
          excluding those notices that do not pertain to any part of
          the Derivative Works; and

      (d) If the Work includes a "NOTICE" text file as part of its
          distribution, then any Derivative Works that You distribute must
          include a readable copy of the attribution notices contained
          within such NOTICE file, excluding those notices that do not
          pertain to any part of the Derivative Works, in at least one
          of the following places: within a NOTICE text file distributed
          as part of the Derivative Works; within the Source form or
          documentation, if provided along with the Derivative Works; or,
          within a display generated by the Derivative Works, if and
          wherever such third-party notices normally appear. The contents
          of the NOTICE file are for informational purposes only and
          do not modify the License. You may add Your own attribution
          notices within Derivative Works that You distribute, alongside
          or as an addendum to the NOTICE text from the Work, provided
          that such additional attribution notices cannot be construed
          as modifying the License.

      You may add Your own copyright statement to Your modifications and
      may provide additional or different license terms and conditions
      for use, reproduction, or distribution of Your modifications, or
      for any such Derivative Works as a whole, provided Your use,
      reproduction, and distribution of the Work otherwise complies with
      the conditions stated in this License.

   5. Submission of Contributions. Unless You explicitly state otherwise,
      any Contribution intentionally submitted for inclusion in the Work
      by You to the Licensor shall be under the terms and conditions of
      this License, without any additional terms or conditions.
      Notwithstanding the above, nothing herein shall supersede or modify
      the terms of any separate license agreement you may have executed
      with Licensor regarding such Contributions.

   6. Trademarks. This License does not grant permission to use the trade
      names, trademarks, service marks, or product names of the Licensor,
      except as required for reasonable and customary use in describing the
      origin of the Work and reproducing the content of the NOTICE file.

   7. Disclaimer of Warranty. Unless required by applicable law or
      agreed to in writing, Licensor provides the Work (and each
      Contributor provides its Contributions) on an "AS IS" BASIS,
      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
      implied, including, without limitation, any warranties or conditions
      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
      PARTICULAR PURPOSE. You are solely responsible for determining the
      appropriateness of using or redistributing the Work and assume any
      risks associated with Your exercise of permissions under this License.

   8. Limitation of Liability. In no event and under no legal theory,
      whether in tort (including negligence), contract, or otherwise,
      unless required by applicable law (such as deliberate and grossly
      negligent acts) or agreed to in writing, shall any Contributor be
      liable to You for damages, including any direct, indirect, special,
      incidental, or consequential damages of any character arising as a
      result of this License or out of the use or inability to use the
      Work (including but not limited to damages for loss of goodwill,
      work stoppage, computer failure or malfunction, or any and all
      other commercial damages or losses), even if such Contributor
      has been advised of the possibility of such damages.

   9. Accepting Warranty or Additional Liability. While redistributing
      the Work or Derivative Works thereof, You may choose to offer,
      and charge a fee for, acceptance of support, warranty, indemnity,
      or other liability obligations and/or rights consistent with this
      License. However, in accepting such obligations, You may act only
      on Your own behalf and on Your sole responsibility, not on behalf
      of any other Contributor, and only if You agree to indemnify,
      defend, and hold each Contributor harmless for any liability
      incurred by, or claims asserted against, such Contributor by reason
      of your accepting any such warranty or additional liability.

   END OF TERMS AND CONDITIONS

D glorifiedgluercom/static/blogroll.xml => glorifiedgluercom/static/blogroll.xml +0 -79
@@ 1,79 0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<opml version="1.0">
    <head>
        <title>~glorifiedgluer feed</title>
    </head>
    <body>
        <outline title="Blogs" text="Blogs I personally like to read">
            <outline
                title="kmaasrud"
                text="Magnus is a friend of mine. Such an interesting guy with interesting ideas."
                type="rss"
                xmlUrl="https://kmaasrud.com/blog/rss.xml"
                htmlUrl="https://kmaasrud.com/blog" />
            <outline
                title="Drew Devault"
                text=""
                type="rss"
                xmlUrl="https://drewdevault.com/blog/index.xml"
                htmlUrl="https://drewdevault.com" />
            <outline
                title="emersion"
                text=""
                type="atom"
                xmlUrl="https://emersion.fr/blog/atom.xml"
                htmlUrl="https://emersion.fr/blog" />
            <outline
                title="Fatih Arslan"
                text="Programming, coffee and various other stuff."
                type="rss"
                xmlUrl="https://arslan.io/posts/index.xml"
                htmlUrl="https://arslan.io/posts/" />
            <outline
                title="Julia Evans"
                text=""
                type="atom"
                xmlUrl="https://jvns.ca/atom.xml"
                htmlUrl="https://jvns.ca/" />
            <outline
                title="research!rsc"
                text="Thoughts and links about programming, by [Russ Cox](https://swtch.com/~rsc/)."
                type="atom"
                xmlUrl="https://research.swtch.com/feed.atom"
                htmlUrl="https://research.swtch.com/" />
            <outline
                title="Xe"
                text=""
                type="rss"
                xmlUrl="https://christine.website/blog.rss"
                htmlUrl="https://christine.website/blog" />
        </outline>

        <outline title="Open Source Projects" text="Projects to watch closely">
            <outline
                title="Go Programming Language Blog"
                text=""
                type="atom"
                xmlUrl="https://go.dev/blog/feed.atom?format=xml"
                htmlUrl="https://go.dev/blog/feed.atom?format=xml" />
            <outline
                title="NixOS News"
                text=""
                type="rss"
                xmlUrl="https://nixos.org/blog/announcements-rss.xml"
                htmlUrl="https://nixos.org/news.html" />
            <outline
                title="This Week in GNOME"
                text=""
                type="rss"
                xmlUrl="https://thisweek.gnome.org/index.xml"
                htmlUrl="https://thisweek.gnome.org" />
            <outline
                title="sourcehut"
                text=""
                type="rss"
                xmlUrl="https://sourcehut.org/blog/index.xml"
                htmlUrl="https://sourcehut.org/blog" />
        </outline>
    </body>
</opml>

D glorifiedgluercom/static/css/main.scss => glorifiedgluercom/static/css/main.scss +0 -451
@@ 1,451 0,0 @@
$primary: #007bff;

html {
  font-family: sans-serif;
}

body {
  line-height: 1.4;
  margin-bottom: 5rem;

  @media(prefers-color-scheme: dark) {
    background-color: #121415;
    color: #e1dfdc;
  }
}

nav:not(#TableOfContents) {
  max-width: calc(800px + 128px * 2); // mascot.png
  margin: 1rem auto 0;
  display: grid;
  grid-template-rows: auto auto 1fr;
  grid-template-columns: auto 1fr;
  grid-template-areas:
    "logo header"
    "logo nav"
    "logo none";

  img {
    grid-area: logo;

    @media(prefers-color-scheme: dark) {
      filter: invert(.92);
    }
  }

  h1 {
    grid-area: header;
    margin: 0;
    padding: 0;
  }

  ul {
    grid-area: nav;
    margin: 0.5rem 0 0 0;
    padding: 0;
    list-style: none;
    display: flex;
    flex-direction: row;
    justify-content: flex-start;
    flex-wrap: wrap;

    li {
      padding-right: 1rem;
    }
  }
}

main {
  padding: 0 128px;
  max-width: 800px;
  margin: 0 auto;

  @media(max-width: 1000px) {
    padding: 0;
  }
}

pre {
  background-color: #eee;
  padding: 0.25rem 1rem;
  margin: 0 -1rem;
  overflow-x: auto;

  @media(max-width: 1200px) {
    padding: 0;
    margin: 0;
  }

  .cp, .c1 {
    color: #222;
    font-weight: bold;
  }

  .k {
    color: #008;
  }

  .kt {
    color: #44F;
  }

  .nt {
    color: #008;
  }

  .s {
    color: #484;
    font-style: italic;
  }

  @media(prefers-color-scheme: dark) {
    background-color: #16191c;

    .mi {
      color: #aac;
    }

    .c1 {
      color: #999;
    }

    .cp {
      color: #db8;
    }

    .kt {
      color: #3cf;
    }

    .k {
      color: #69f;
    }

    .nt {
      color: #69f;
    }

    .s {
      color: #f79;
    }
  }
}

span {
  .c {
    color: #222;
    font-weight: bold;
  }

  .s2 {
    color: #484;
    font-style: italic;
  }

  .kd {
    color: #008;
  }

  @media(prefers-color-scheme: dark) {
    .c {
      color: #999;
    }

    .s2 {
      color: #f79;
    }

    .kd {
      color: #69f;
    };
  }
}

.alert {
  padding: 0.5rem;
  border: 1px solid transparent;
  margin-bottom: 1rem;
  background: #d1ecf1;
  color: #0c5460;
  border-color: #bee5eb;

  @media(prefers-color-scheme: dark) {
    background-color: #0e343c;
    color: #87dcea;
    border-color: #18535a;
  }
}

a {
  color: $primary;

  &:visited {
    color: darken($primary, 25);
  }

  &:hover {
    text-decoration: none;
  }

  @media(prefers-color-scheme: dark) {
    color: #78bef8;

    &:visited {
      color: darken(#78bef8, 10);
    }
  }
}

.tutorial-link {
  text-decoration: none;
  border-bottom: 1.25px solid $primary;

  &:after {
    content: " ▶";
    border-bottom: 1.25px solid white;
  }

  @media(prefers-color-scheme: dark) {
    border-bottom: 1.25px solid #78bef8;

    &:after {
      content: " ▶";
      border-bottom: 1.25px solid #121415;
    }
  }

  &:hover {
    border-bottom: none;
  }
}

code {
  font-size: 1rem;
  background: #eee;
  padding: 0 0.3rem;

  @media(prefers-color-scheme: dark) {
    background: #16191c;
  }
}

pre code {
  font-size: inherit;
  padding: 0;
}

main img {
  display: block;
  margin: 0.5rem auto;
  max-width: 80%;
}

figcaption {
  text-align: center;
}

table {
  width: 100%;
  border-collapse: collapse;

  th {
    text-align: left;
  }

  td, th {
    border-bottom: 1px solid black;
  }

  .yes, .todo {
    display: inline-block;
    width: 1rem;
    text-align: center;
  }

  .yes {
    color: green;
  }

  .todo {
    color: red;
  }
}

details {
  background: #eee;
  margin-top: 1rem;

  summary {
    cursor: pointer;
    padding: 0.5rem 1rem;
  }

  @media(prefers-color-scheme: dark) {
    background: #24272b;
  }

  &[open] {
    summary {
      background: #eee;
      margin: auto -0.5rem -0.5rem calc(-0.5rem - 5px);

      @media(prefers-color-scheme: dark) {
        background: #24272b;
      }
    }

    padding: 0 0.5rem 0 0.5rem;
    border-left: 5px solid #eee;
    background: white;

    @media(prefers-color-scheme: dark) {
      background: #121415;
      border-left: 5px solid #24272b;
    }
  }
}

.stub h2 {
  margin-bottom: 0;
}

h2 small {
  font-weight: normal;
  font-size: 1.2rem;
  display: block;
  border-bottom: 1px solid #555;
  margin-top: 0.5rem;
  color: #555;
  font-style: italic;
}

h4 small {
  font-weight: normal;
  display: block;
  border-bottom: 1px solid #555;
  margin-top: 0.5rem;
  color: #555;
  font-style: italic;
}

blockquote {
  border-left: 4px solid #888;
  margin-left: 0;
  padding-left: 1rem;
}

ol li {
  line-height: 1.3;

  &:not(:last-child) {
    margin-bottom: 0.35rem;
  }
}

#TableOfContents {
  border-left: 2px solid gray;
  padding-left: 0;

  details & {
    border-left: none;
  }

  ol li {
    margin: auto;
    line-height: auto;
  }
}

.code-tutorial {
  max-width: 1200px;

  .content {
    margin-bottom: 16rem;
    max-width: 1000px;
    margin: 0 auto;
  }

  section {
    display: grid;
    grid-template-rows: 1fr;
    grid-template-columns: 1fr 1fr;
    grid-template-areas:
      "header header"
      "details sample"
      ;
    margin-bottom: 12rem;
    grid-gap: 0 1rem;

    @media(max-width: 1200px) {
      display: block;

      .highlight pre {
        overflow-x: auto;
      }
    }

    & > * {
      grid-area: details;
    }

    & > h3 {
      grid-area: header;
    }

    .highlight {
      grid-area: sample;

      @media(min-width: 1200px) {
        pre {
          margin: 0;
        }
      }
    }

    .details {
      grid-area: details;

      @media(min-width: 1200px) {
        padding-right: 1rem;
      }

      p:first-child {
        margin-top: 0;
      }

      .alert {
        margin: 0;
      }
    }

    pre {
      overflow-x: auto;
    }
  }
}

dl {
  display: grid;
  grid-template-columns: auto 1fr;
  grid-gap: 0.2rem 1rem;

  dt {
    font-weight: bold;
    grid-column-start: 1;
  }

  dd {
    grid-column-start: 2;
    margin: 0;
  }
}

.htmlsample pre {
  margin-bottom: 1rem !important;
}

iframe, video {
  margin: 0 auto;
  display: block;
  max-width: 100%;
}

D glorifiedgluercom/static/favicon.ico => glorifiedgluercom/static/favicon.ico +0 -0
D glorifiedgluercom/static/img/nana-icons/README.md => glorifiedgluercom/static/img/nana-icons/README.md +0 -1
@@ 1,1 0,0 @@
These icons were made by thayna malta.

D glorifiedgluercom/static/img/nana-icons/capybara.jpg => glorifiedgluercom/static/img/nana-icons/capybara.jpg +0 -0
D glorifiedgluercom/static/img/nana-icons/shiba-banana.jpg => glorifiedgluercom/static/img/nana-icons/shiba-banana.jpg +0 -0
D glorifiedgluercom/static/img/nana-icons/shiba-computer-low.jpg => glorifiedgluercom/static/img/nana-icons/shiba-computer-low.jpg +0 -0
D glorifiedgluercom/static/img/nana-icons/shiba-computer.jpg => glorifiedgluercom/static/img/nana-icons/shiba-computer.jpg +0 -0
D glorifiedgluercom/static/img/nana-icons/shy-rat.jpg => glorifiedgluercom/static/img/nana-icons/shy-rat.jpg +0 -0
D glorifiedgluercom/static/img/raspberry-argon.jpg => glorifiedgluercom/static/img/raspberry-argon.jpg +0 -0