Migrated all old posts, added gemini, made it look a little snazzy

This commit is contained in:
Amarpreet Minhas 2023-09-07 18:24:35 -04:00
parent bac149f80b
commit d96fab7639
50 changed files with 1503 additions and 0 deletions

2
.gitignore vendored
View file

@ -21,3 +21,5 @@
# Go workspace file # Go workspace file
go.work go.work
public/
public-gg/

3
.gitmodules vendored Normal file
View file

@ -0,0 +1,3 @@
[submodule "themes/Hugo-2-Gopher-and-Gemini"]
path = themes/Hugo-2-Gopher-and-Gemini
url = https://github.com/mkamarin/Hugo-2-Gopher-and-Gemini.git

0
.hugo_build.lock Normal file
View file

5
archetypes/default.md Normal file
View file

@ -0,0 +1,5 @@
+++
title = '{{ replace .File.ContentBaseName "-" " " | title }}'
date = {{ .Date }}
draft = true
+++

96
config-gg.toml Normal file
View file

@ -0,0 +1,96 @@
title = "sudoscientist"
theme = "Hugo-2-Gopher-and-Gemini"
languageCode = "en-us"
defaultcontentlanguage = "en-us"
# Ignore content written in html files
ignoreFiles = [".html$"]
[params]
author = "Asara"
copyright = "© [Asara](https://sudoscientist.com)"
info = "Asara's personal blog"
returnHome = "Return to main page"
[params.gemini]
description = "Asara's blog as a gemblog/gemini capsule"
menuText = "## Site sections"
postText = "## Posts"
socialText = "## Social media links"
includeMenu = ["main", "lists", "pages" ]
includeCategories = ["main", "lists", "pages" ]
includeSocial = ["main", "lists", "pages" ]
includeReturnHome = ["main", "lists", "pages" ]
includeAuthor = ["main", "lists", "pages" ]
[[menu.main]]
name = "Home"
weight = 1
url = "/"
[[menu.main]]
name = "Posts"
weight = 2
url = "/posts/"
[[menu.main]]
name = "About"
weight = 3
url = "/about/"
[[params.social]]
name = "personal git server"
weight = 1
url = "https://git.minhas.io/Asara"
[[params.social]]
name = "github"
weight = 2
url = "https://github.com/Asara"
[[params.social]]
name = "email"
weight = 3
url = "mailto:amarpreet@minhas.io"
#######################################################
# You may not need to touch the following configuration
config = "config-gg.toml"
metaDataFormat = "toml"
# Disable files not needed for Gopher and Gemini
disableRSS = true
disableSitemap = true
disable404 = true
enableEmoji = false
# Directories used for Gopher and Gemini generation
layoutDir = "layouts-gg"
publishDir = "public-gg"
uglyURLs = true
relativeURLs = false
canonifyURLs = true
[mediaTypes]
[mediaTypes."text/gemini"]
suffixes = ["gmi"]
[mediaTypes."text/plain"]
suffixes = ["txt"]
[outputs]
home = ["gemini",]
section = ["gemini",]
taxonomy = ["gemini",]
term = ["gemini",]
page = ["gemini",]
[outputFormats]
[outputFormats.Gemini]
name = "gemini"
mediaType = "text/gemini"
baseName = "gemini-page"
isPlainText = true
permalinkable = true
isHTML = false
protocol = "gemini://"
noUgly = false
path = "gemini/"

75
config.toml Normal file
View file

@ -0,0 +1,75 @@
baseURL = 'https://sudoscientist.com/'
languageCode = 'en-us'
title = 'sudoscientist'
author = 'Asara <amarpreet@minhas.io>'
theme = 'risotto'
copyright = "© [Asara](https://sudoscientist.com)"
paginate = 3
DefaultContentLanguage = "en"
enableInlineShortcodes = true
sectionPagesMenu = "main"
[Author]
name = "Asara"
email = "amarpreet@minhas.io"
[params]
noindex = false
[params.theme]
palette = "personal"
# Sidebar: about/bio
[params.about]
description = "Asara's personal blog"
logo = "images/profile.jpg"
[[params.socialLinks]]
icon = "fa fa-matrix-org"
title = "matrix"
url = "https://matrix.to/#/@Asara:devvul.com"
[[params.socialLinks]]
icon = "fa fa-gitea"
title = "personal git"
url = "https://git.minhas.io/Asara"
[[params.socialLinks]]
icon = "fa-brands fa-github-alt"
title = "GitHub"
url = "https://github.com/Asara"
[[params.socialLinks]]
icon = "fa-solid fa-envelope"
title = "Email"
url = "mailto:amarpreet@minhas.io"
[[params.socialLinks]]
icon = "fa-solid fa-rss"
title = "RSS feed"
url = "https://sudoscientist.com/feed.xml"
[menu]
[[menu.main]]
identifier = "about"
name = "About"
url = "/about/"
weight = 10
[taxonomies]
category = "categories"
tag = "tags"
# For hugo >= 0.60.0, enable inline HTML
[markup.goldmark.renderer]
unsafe = true
[markup]
[markup.tableOfContents]
startLevel = 2
endLevel = 3
ordered = true
[outputFormats]
[outputFormats.RSS]
mediaType = "application/rss+xml"
baseName = "feed"

15
content/_index.md Normal file
View file

@ -0,0 +1,15 @@
+++
title = ':~/#'
+++
## Welcome to my blog!
Allo! I go by Asara online, and I appreciate you visiting my site!
## Quick summary
I will primarily post about technology, personal projects, and other sudo-scientific and sudo-philosophical ramblings.
Check out the [posts](https://sudoscientist.com/posts) tab above to read some of my blog posts, or the [about](https://sudoscientist.com/about) to learn a bit about the creation of the blog itself.
If you'd like to subscribe to the blog, you can hit the RSS icon in the sidebar, or add the [feed.xml](https://sudoscientist.com/feed.xml) to your RSS client.
You can also find my contact links on the sidebar. Get in touch if you want to chat!

30
content/about.md Normal file
View file

@ -0,0 +1,30 @@
+++
title = 'about'
+++
## History
This is the latest revision of my blog.
The v1 of this was run using [Pelican](https://getpelican.com/), and the archived code can be found on [Github](https://github.com/asara/ampx). Originally it was running on a raspberry pi in my parent's house, on a domain long gone.
The v2 of the website was created to learn Go and JavaScript. It is technically the first version of sudoscientist. The [backend](https://git.minhas.io/Asara/sudoscientist-go-backend) was written using go-chi, and the [frontend](https://git.minhas.io/Asara/sudoscientist-js-frontend) was written using the React/Redux framework.
This is [v3](https://git.minhas.io/asara/sudoscientist.com) of my blog, which is powered by [Hugo](https://gohugo.io/), and uses a modified version of [risotto](https://risotto.joeroe.io/) as its theme. It has been extended to include [forkawesome](https://forkaweso.me) and some small cleanup. I made the decision to return to using a static site generator as I have mostly forgotten React/Redux. Along with this, using Go in multiple other projects, I do not feel like I need to run my blog with golang anymore.
The inspiration for this theming, and returning to a static site generator was in part due to the awesome community over at [Ctrl-C](https://ctrl-c.club). The `tildeverse` in general is a great reminder that minimalism is not only useful, but sometimes more fulfilling.
Moving to hugo also allowed me to quickly get my blog into the [Gemini](https://gemini.circumlunar.space/) protocol, and can be seen hosted at [ctrl-c](gemini://gemini.ctrl-c.club/~Asara)!
At a v3, I hope this time around I actually write some blog posts!
## Connect with me
Matrix: `@Asara:devvul.com`
Email: `amarpreet@minhas.io`
IRC: `Asara` on OFTC/Libra/tilde.chat
## Lightning Network
Pubkey: `0214b1f6b48998b9eb19d8a756af39a027202cecfe608450109465bbccf3bb74ed`
Node: `redwingxusk66wrrm4yyqyuyndvum4jsnloroxqmie6wv4wefadqgsqd.onion:9735`

View file

@ -0,0 +1,3 @@
+++
title = 'posts'
+++

View file

@ -0,0 +1,9 @@
+++
title = 'Docker Support on the Google Pixel'
date = 2014-03-28T00:00:00-00:00
category = 'pixel'
tags = ['docker', 'howto']
+++
## Docker
I've been fooling around with Docker and decided to upload a new kernel [config](http://pastebin.com/sqsJhBn2) that includes support for Docker.

View file

@ -0,0 +1,31 @@
+++
title = 'Gentoo on the Google Pixel Pt. 1'
date = 2013-12-02T00:00:00-00:00
category = "pixel"
+++
## Notes
1) I would recommend getting a USB mouse because it is a pain otherwise. The modules are not built in for the touchscreen (atmel_mxt_ts nor chromeos_laptop), and as such you might get annoyed. If you can deal with working in term, ignore this.
2) This will remove ChromeOS from the SDD, but it is fairly straightforward recovering it if you get sick of Linux. [Here](http://support.google.com/chromeos/bin/answer.py?hl=en&answer=1080595) is how you restore your machine.
## Enabling access to SeaBIOS
Before installing Linux to the SDD, you have to first enable access to the secondary BIOS installed on the device (currently no way to access this manually and you are stuck with a 5 second wait on each boot.)
1) In order to put your Pixel into developer mode, hold the Escape and Refresh keys while booting the machine (ESC is F1, Refresh is F3). Press Ctrl-D followed by Enter, which will enable dev mode and wipe all user data from the Pixel. Dev mode also disables OS checking, so you will get a message at every boot.
2) Boot into ChromeOS after enabling dev mode (press Ctrl-D in order to get into the OS when the OS checking message appears). Log in as a guest, press Ctrl-Alt-T to bring up a crosh (ChromeOS Shell) window, and start a shell:
```bash
# Get root
sudo bash
# enable USB booting in SeaBIOS
crossystem dev_boot_usb=1
```
3) Reboot the machine, and once the OS check window comes up, press Ctrl-L to enter SeaBIOS. After this the process is simple. My next post will be about installing specifically Gentoo onto the SSD. For any other OS, just install onto /dev/sda1, with the bootloader installed to the MBR of the disk, and you should be good to go. On bootup, enable the two drivers I mentioned up there, and you will have a working system.
## Aditional note
If you are unable to boot from the USB due to memory issues, add mem=4G to the kernel command line, and if that doesn't work, as a failsafe, mem=1G should get you into your distro's LiveOS.
## [Part 2](http://ampx.minhas.io/posts/2014/Jan/23/gentoo-on-the-google-pixel-pt-2.html)

View file

@ -0,0 +1,157 @@
+++
title = 'Gentoo on the Google Pixel Pt. 2'
date = 2014-01-23T00:00:00-00:00
category = 'pixel'
tags = ['howto']
+++
## Notes
I ended up slowing down and taking a while to fully explore all the options in building a Gentoo system on my Pixel. This included me reinstalling from scratch multiple times and learning along the way. This final product is a Pixel install with a 3.12.8 kernel, with the /,/home, and swap partitions sitting in a LVM encrypted with LUKS. Follow through and you will be able to get the same. Also, this install uses the SystemRescueCD (which is based on Gentoo).
## Paritioning Drives
Since we are using a SSD for the install, GPT is recommended, which complicates things a tad bit.
Using parted, we have to create 3 partitions, the grub-bios partition, /boot, and the 3rd for the LVM. The grub-bios partition is 2MB, the /boot is 512MB (for multiple kernels), and the LVM will be the rest of the drive.
```bash
parted -a optimal /dev/sda
mklabel gpt
(parted) unit mib
(parted) mkpart primary 1 3
(parted) name 1 grub
(parted) set 1 bios_grub on
(parted) print
(parted) mkpart primary 3 515
(parted) name 2 boot
(parted) mkpart primary 515 -1
(parted) name 3 rootfs
```
## Encrypting and Creating LVM/File Systems
Encrypt /dev/sda3, mount it and create the LVM
```bash
cryptsetup -y --cipher aes-cbc-essiv:sha256 --key-size 256 luksFormat /dev/sda3
cryptsetup luksOpen /dev/sda3 rootfs
pvcreate /dev/mapper/rootfs
lvcreate -L1024m -nswap rootfs
lvcreate -L20480m -nroot rootfs
lvcreate -l 100%FREE home rootfs
```
Create file systems on the multiple logical volumes you've created and mount them to their proper locations:
```bash
mkswap -L SWAP /dev/mapper/rootfs-swap
swapon /dev/mapper/rootfs-swap
mkfs.ext4 -j /dev/mapper/rootfs-root -L ROOT
mount /dev/mapper/rootfs-root /mnt/gentoo
mkdir /mnt/gentoo/home
mkfs.ext4 -j /dev/mapper/rootfs-home -L HOME
mount /dev/mapper/rootfs-home /mnt/gentoo/home
mkdir /mnt/gentoo/boot
mkfs.ext4 -j /dev/sda2 -L BOOT
mount /dev/sda2 /mnt/gentoo/boot
```
## Download Gentoo Stage3 tarball
Change directories into /mnt/gentoo, download the stage3 and extract it. Copy over resolv.conf
```bash
cd /mnt/gentoo
elinks http://www.gentoo.org/main/en/mirror.xml
tar xvjpf stage3-.tar.bz2
cp -L /etc/resolv.conf /mnt/gentoo/etc/
```
## Prepare Portage
```bash
nano /mnt/gentoo/etc/portage/make.conf
CFLAGS="-march=k8 -O2 -pipe"
MAKEOPTS="-j2"
mirrorselect -i -o >> /mnt/gentoo/etc/portage/make.conf
mirrorselect -i -r -o >> /mnt/gentoo/etc/portage/make.conf
```
## Mount Virtual Filesystems
```bash
mount -t proc proc /mnt/gentoo/proc
mount --rbind /sys /mnt/gentoo/sys
mount --rbind /dev /mnt/gentoo/dev
```
## Chroot into your new system
```bash
chroot /mnt/gentoo /bin/bash
source /etc/profile
export PS1="(chroot) $PS1"
```
## Initialize Portage
```bash
emerge-webrsync
emerge --sync
```
## Set up localization information
```bash
echo "Continent/Country" > /etc/timezone
emerge --config sys-libs/timezone-data
nano -w /etc/locale.gen
locale-gen
eselect locale list
eselect locale set # Your locale here
env-update && source /etc/profile
```
## Notes
At this point I like to install a few apps I use, that way I won't have to worry about them later. I'd recommend installing vim, NetworkManager (for nmcli), and really anything else you expect to use. I also wanted to use systemd, so I had to prep for that. That includes installing udev with -systemd in make.conf, the installing systemd (obviously remove the - after installing udev), and then uninstalling udev, since systemd provides virtual/udev.
## Kernel Setup
```bash
echo "=sys-kernel/gentoo-sources-3.12.8" >> /etc/portage/package.keywords
emerge gentoo-sources genkernel-next lvm2 cryptsetup grub vim
```
(As a side note, genkernel-next is required for a systemd install to include udev in the kernel)
```bash
vim /etc/genkernel.conf
LVM="yes"
LUKS="yes"
BUSYBOX="yes"
MENUCONFIG="yes"
DISKLABEL="yes"
```
## Create the kernel
```bash
genkernel --udev all
```
Remember to enable support for crypto devices in the kernel, along with anything else you may need/want.
```
Device Drivers
Multi-device support (RAID and LVM)
[*] Multiple devices driver support (RAID and LVM)
<*> Device mapper support
<*> Crypt target support
Cryptographic API
<*> SHA256 digest algorithm
<*> AES cipher algorithms
```
## Installing grub2
```bash
vim /etc/default/grub
GRUB_DISTRIBUTOR="Gentoo"
GRUB_DEFAULT=0
GRUB_HIDDEN_TIMEOUT=0
GRUB_HIDDEN_TIMEOUT_QUIET=true
GRUB_TIMEOUT=3
GRUB_PRELOAD_MODULES=lvm
GRUB_CRYPTODISK_ENABLE=y
GRUB_DEVICE=/dev/sda1
GRUB_CMDLINE_LINUX="real_init=/usr/lib/systemd/systemd quiet real_root=/dev/mapper/rootfs-root crypt_root=/dev/sda3 dolvm"
grub2-install --modules="configfile linux crypto search_fs_uuid luks lvm" --recheck /dev/sda
grub2-mkconfig -o /boot/grub/grub.cfg
```
Reboot the machine and you should have a working Gentoo install on your Google Pixel!
## [Part 3](http://ampx.minhas.io/posts/2014/Jan/29/gentoo-on-the-google-pixel-pt-3.html)

View file

@ -0,0 +1,15 @@
+++
title = 'Gentoo on the Google Pixel Pt. 3'
date = 2014-01-29T00:00:00-00:00
category = 'pixel'
tags = ['howto']
+++
## Pixel Configs
Decided to upload some config files to pastebin for everyone to use and easily get gentoo up and running on their Pixel.
[Kernel config](http://pastebin.com/bX4ayMEM)
[grub2](http://pastebin.com/mhhPSVT3)
## [Part 4](http://ampx.minhas.io/posts/2014/Feb/17/gentoo-on-the-google-pixel-pt-4.html)

View file

@ -0,0 +1,48 @@
+++
title = 'Gentoo on the Google Pixel Pt. 2'
date = 2014-02-17T00:00:00-00:00
category = 'pixel'
tags = ['howto']
+++
## Tiny Fixes
These are really just fixes for the font rendering/zoom levels of everything. As it turns out the world/userspace tools aren't really ready for high DPI. As such these are some quick tweaks to get everything not looking ridiculous. I use Firefox as my browser, urxvt (with 256-color) as my terminal, and i3 as my window manager.
## Fonts
Enable these using eselect
```
Pixel Sub-Rendering: 10-sub-pixel-rgb.conf
LCD Filter: 11-lcdfilter-default.conf
```
## Xdefaults
```bash
!-- Xft settings -- !
Xft.dpi: 180
Xft.rgba: rgb
Xft.hinting: true
Xft.hintstyle: hintfull
Xft.antialiasing: false
! -- Fonts -- !
urxvt.font:xft:DejaVu Sans Mono:size=8
urxvt.boldfont:xft:DejaVu Sans Mono:size=8
! -- URxvt settings -- !
urxvt*geometry: 80x30
!urxvt.font: 9x15
urxvt*background: #212629
urxvt*foreground: #FFFFFF
urxvt*scrollBar: false
urxvt*matcher.button: 1
urxvt*cursorBlink: true
urxvt*cursorColor: #c1c8c9
urxvt.transparent: false
urxvt*allow_bold: true
urxvt*inheritPixmap: false
```
## Firefox Settings
After some tinkering, I've realized the best bet is to get the Default Full Zoom add-on and set the default zoom between 130% and 140%.

View file

@ -0,0 +1,195 @@
+++
title = 'Hello World Deuxième Partie'
date = 2020-01-18T19:35:18.446638Z
category = 'sudoscientist'
+++
```bash
sudoscientist:~# apt install golang nodejs
```
## Building sudoscientist
With the languages for my project picked out, I began working on a simple RESTful backend in golang. Here, there were choices for which router I would use, and the decision came down to either the `gorilla/mux` or `go-chi/chi`. While I could have used just used `net/http`, I wanted something simple to organize my routes and not have to worry about starting entirely from scratch. A routing frameworks was something that I had no intention of writing from scratch, nor did I want to start going down the path of writing a simple routing utility only to have to replace it later. The ability to plug in middleware to handle authentication and other common roadblocks also made using a routing framework attractive. The decision to use `go-chi/chi` was mostly down to how easy it was to read the source code and grasp what was happening. The simplicity of the tools allowed me to easily understand what I was doing, and hack on whatever else I needed along the way. With this, and the decision to use React/Redux already in place, I started building.
## Golang, and what makes a website
The proverb measured twice, cut once is a useful philosophy when building websites, or applications in general. The foresight of knowing what needs to get done makes it much easier to actually build it. This project was made easier because of the iterations I went through when using a traditional model, view, and controller framework. I knew that I needed separate data stores for my various components, such as users, profiles, and posts. I also knew I had to implement some type of authentication and authorization, possibly using JavaScript Web Tokens (JTWs) to pass the data between the clients and servers. I knew if I could implement this process using cURL, it would be fairly easy to transition to a proper JavaScript frontend. To store data I would use PostgreSQL, since that was the database I was most comfortable with, and it has plugins for some of the future projects I want to work on.
The first step was implementing a simple database connection. After reading some best practices for handling having multiple modules, connecting to the same database, I opted for using dependency injection to pass the database details around to my various sub-components.
```golang
func main() {
// initiate the database
db, _ := database.NewDB()
defer db.Close()
auth.DB = db
auth.Init()
users.DB = db
users.Init()
blog.DB = db
blog.Init()
```
This allowed me to have one database connection, and the ability to pass the reference of that connection to my sub-components. It would also make it a matter of adding two lines to main.go for each new component that I needed accessing the database. Additionally, until I set up migrations, the `Init()` calls would allow me to initialize my tables.
The next step was building out a bare bones user system. This would be split into two separate but interrelated packages. The first would be the `auth` package, which would be designated to handle authentication and passwords. The second would be the `users` package, which would contain the user's profile and other associated information. This would allow for me to decouple the profiles of users from the user themselves.
The primary information needed in the authentication package would be a user's name, email address, and password. The creation of this was a simple SQL command.
```golang
func Init() {
DB.Exec("CREATE TABLE IF NOT EXISTS users (username text primary key, email text, password text, admin boolean);")
}
```
Creating a `struct` for passing this data around was also straightforward.
```golang
type SignUpCredentials struct {
Username string `json:"username", db:"username"`
Email string `json:"email", db:"email"`
Password string `json:"password", db:"password"`
}
```
As was getting the data into code.
```golang
creds := &SignUpCredentials{}
err := json.NewDecoder(r.Body).Decode(creds)
```
This allowed me to push the data to the backend service via json data `curl -d @file.json` which would add the file.json to the request and that could be deserialized and processed in golang. Using a mix of `dgrijalva/jwt-go` and `go-chi/jwtauth` allowed me to quickly prototype and keep most of the authentication process in the background. This was until I learned about the inherent insecure nature of JWT tokens being stored in JavaScript's `sessionstorage` and `localstorage`, which we will come back to later.
For now, authentication was handled, and I proceeded to build out the basic functionality of a blog. This meant user profiles, which was minimized to:
```golang
type User struct {
Username string `json:"username",db:"username"`
Email string `json:"email",db:"email"`
Country string `json:"country",db:"country"`
Bio string `json:"bio",db:"bio"`
}
```
Keeping things simple and modular was good for the first iteration of this blog. I can add arbitrary data to the `Bio` field until I feel like more fields are needed. The `Email` and `Username` fields are currently redundant, as they exist in the `auth` package as well, and will probably be stripped out later in favor of foreign keys. The bulk of the work left was the `blog` package. As the rest of this project, this was heavily influenced by the Django Framework's handling of `Posts`.
```golang
type BlogPost struct {
ID int `json:"id",db:"id"`
Title string `json:"title",db:"title"`
Slug string `json:"slug",db:"slug"`
Author string `json:"author",db:"author"`
Content string `json:"content",db:"content"`
TimePublished time.Time `json:"time_published", db:"time_published"`
Modified bool `json:"modified", db:"modified"`
TimeModified time.Time `json:"last_modified", db:"last_modified"`
}
```
Eventually, I may add a string field for backlinks, which would be useful for this post, and possibly other things as required. I wrote some simple routes to GET posts, and some routes to POST and PATCH.
```golang
func Routes() *chi.Mux {
r := chi.NewRouter()
r.Group(func(r chi.Router) {
r.Use(jwtauth.Verifier(TokenAuth))
r.Use(jwtauth.Authenticator)
r.Post("/", createBlogPost)
r.Patch("/by-id/{id}", updateBlogPostById)
})
r.Get("/", getBlogPosts)
r.Get("/by-slug/{slug}", getBlogPostBySlug)
r.Get("/by-id/{id}", getBlogPostById)
r.Get("/by-tag/{tag}", getBlogPostsByTag)
r.Get("/by-author/{author}", getBlogPostsByAuthor)
return r
}
```
In a few lines, I was able to declare the routes I would need, as well as apply authentication to certain routes using `go-chi/jwtauth`. Being able to authenticate via cURL, and create new blog posts with cURL, I decided to start working on the frontend. I knew I would have to fiddle with the backend further along the process, but I was content with the base I had and decided to dive into my apprehensions and fears.
## NodeJS, and how to show a website
JavaScript has always been a language I avoided. Between the hellhole that is `npm`, my discomfort with visuals, and my lack of need in making websites, I was able to avoid it for the most part. I made some simple websites in the Web 1.0 days, and even then my aesthetic senses were sub par. Even now, the v1.0.0 of sudoscientist is very minimalistic. I prefer the simple look of white on black text in a terminal over most fancy graphics. NodeJS and JavaScript as a whole is a very different from most development related work I've done. The JavaScript world as a whole seems very different. The JavaScript world seems very framework heavy. Do you use bootstrap or semantic? Angular? Vue? Meteor? Ionic? My decision in picking React/Redux was straight forward, I have a good friend (thanks Mark!) who is a seasoned user and someone that I could turn to for help in my misadventures. If it wasn't for that fact, I would probably still be trying to figure out which framework would work best for this project, and the other projects I want to pursue later. Like the decision to use Postgres, I wanted to make sure I could use the same methods in the future, and reuse as much code as possible.
Learning React/Redux, even now, feels very much like learning React/Redux and not learning JavaScript. There is a ton of knowledge I now have that very specific to one framework. I don't think I would feel comfortable working with any other framework, and I don't even feel comfortable with React itself yet. Regardless, the projects goals were set to be straight forward. I would go public with the site once I was able to display the posts from my previous blog, and a v1.0.0 would be cut once I was able to post from the blog's UI itself. This meant I would leave authentication until the end, and just work on getting RESTful interactions working with the backend, and displaying markdown.
While working with the frontend, I employed a similar pattern for building out the application. I first focused on getting the appropriate libraries installed, and then working with the browser console to figure out what would be needed to make REST requests against the backend. Once this was completed, the next task was actually displaying posts in the browser. This would become a challenging task because it involves understanding the difference between the states of React, and Redux, along with understanding the event loop of rendering in React. This lead me to a lot of confusion, between the commits of [I have no idea what i'm doing :(](https://git.minhas.io/Asara/sudoscientist-js-frontend/commit/fabf06b8a6a53a39e7812fcc5eb4f47f634e33cf) and [It makes more sense now](https://git.minhas.io/Asara/sudoscientist-js-frontend/commit/922299010c3853d59c8af29bd151bb4a4d864934), I finally understood the difference between states, and how to merge JavaScript Objects together to push them to the state and have React rerender the web page.
```javascript
const initialState = {
entities: {},
};
const normalizeEntities = (entities, payloadData) => {
const entitiesMap = {}
payloadData.forEach(post => entitiesMap[post.id] = post)
return {...entities, ...entitiesMap}
}
export default (state = initialState, action) => {
switch (action.type) {
case 'FETCH_POSTS':
const mergedEntities = normalizeEntities(state.entities, action.payload)
return {...state, ...{entities: mergedEntities}}
...
```
This little bit of (magic) code allowed me to progress further and actually get blog posts in the browser. With this, I was content on publishing the blog with my old blog's posts, and start working on the next steps, authentication and making POST requests.
## Authentication, JWTs, and Cookies
Once I posted the blog, the next step would be to allow myself to make POST requests via the browser. This would first require me to set up some form of authentication. The initial revision of this would be very straightforward. Using `go-chi/jwtauth` allowed to quickly add a few lines to set up a simple JWT verifier and authenticator around specific routes
```golang
r.Group(func(r chi.Router) {
r.Use(jwtauth.Verifier(TokenAuth))
r.Use(jwtauth.Authenticator)
r.Post("/", createBlogPost)
r.Patch("/by-id/{id}", updateBlogPostById)
})
```
While this was a simple solution, along with storing the JWT in `localstorage` or `sessionstorage`, I started reading security guidelines on how to handle secrets in the browser. Upon coming across the OWASP requirement that local or session storage should not contain sensitive information, I reevaluated my solution. The implementation that made most sense to me was that of split-cookie authentication. Implementing this on the backend is a fun task, and is always my first step, I started working on a system that would be able to function with cURL. This would require writing some middleware for `go-chi/jwtauth`, which was as simple as:
```golang
func TokenFromSplitCookie(r *http.Request) string {
dataCookie, err := r.Cookie("DataCookie")
if err != nil {
return ""
}
signatureCookie, err := r.Cookie("SignatureCookie")
if err != nil {
return ""
}
cookie := dataCookie.Value + "." + signatureCookie.Value
return cookie
}
```
In addition to this, I added a function on the backend to set cookies in a secure manner:
```golang
func setCookies(w http.ResponseWriter, jwt string, expiration time.Time) string {
splitToken := strings.Split(jwt, ".")
dataCookie := http.Cookie{Name: "DataCookie", Value: strings.Join(splitToken[:2], "."), Expires: expiration, HttpOnly: false, Path: "/", Domain: ".sudoscientist.com", MaxAge: 360, Secure: true}
http.SetCookie(w, &dataCookie)
signatureCookie := http.Cookie{Name: "SignatureCookie", Value: splitToken[2], Expires: expiration, HttpOnly: true, Path: "/", Domain: ".sudoscientist.com", MaxAge: 360, Secure: true}
http.SetCookie(w, &signatureCookie)
return strings.Join(splitToken[:2], ".")
}
```
This bit of code would take the cookies passed up by the browser, of which the DataCookie was accessible to the JavaScript frontend, and the SignatureCookie would be HttpOnly, functionally disallowed any JavaScrpit from having the entire contents of the cookie, and would disallow updating the cookie since the signature would be invalid on altered data. Furthermore, since most of this was being handled by cookies, the process to incorporate getting data out of the was as simple as `const datacookie = cookies.get('DataCookie');`. This allowed me to get the data required to render user information accurately in the UI. Finally, with this in place, I was able to have a secure authentication system, and all that was left was setting up a way to make new posts!
## First POSTs and the future of sudoscientist
One thing that is understandable about the Node.js ecosystem is that the vast library of node modules is a bit of a necessity. There is a LOT of things that need to be done repeatedly to render data in a browser. One of these repeated functions is saving, loading, and rendering Markdown. In order to accomplish this in React, I ended up using React Markdown Editor. The code for this was as simple as:
```javascript
<div className="markdown-body">
<ReactMde
value={content}
onChange={setContent}
selectedTab={selectedTab}
onTabChange={setSelectedTab}
generateMarkdownPreview={(markdown) =>
Promise.resolve(<ReactMarkdown source={markdown} />)}
/>
</div>
```
The `react-mde` library made it simple to edit and view my markdown files, load it into a JSON blob, and POST it to the backend. This will also be leveraged for the PATCH function to update old posts in the future.
Looking forward, the next steps for the blog will be implementing the PATCH feature, maybe adding some regions for backlinks, figuring out and implementing database migrations, and finally comments. Once that is done, I think I will continue to work on other projects and document them on sudoscientist. These two initial blog posts were primarily retrospectives, and as such were written with me just referencing old git commits and piecing the story together. In the future I plan on writing down notes and actually document what I'm doing while I do it.
It's a pleasure to have you reading, and thanks! Hope to see you in the next one.

View file

@ -0,0 +1,35 @@
+++
title = 'Hello World!'
date = 2019-12-21T22:25:09-04:00
category = 'sudoscientist'
+++
```bash
scientist:~$ sudo su -
sudoscientist:~#
```
## Welcome to my blog!
Nice to meet you. Thanks for being here. Here is a little bit about me, and my blog, sudoscientist. I go by Asara on the internet, and I am an aspiring engineer. I've been in the industry for a while, but it is an infinitely wide industry, and there is still much more to learn. This will be a pretty long blog post, but not a very technical one. The next post will deal with the actual process of building sudoscientist! The motivation for sudoscientist was twofold.
The primary motivation was that I wanted to get back into blogging, and being that I haven't made any posts in about 5 years, I had to take action. I have learned a ton in that time period, and have shifted my focus from the general catchall of `Linux` to `architecture`, `the cloud`, and `automation`. Docker has absorbed the industry in this time, and from my humble dabbling in building docker back in 2014, to building Kubernetes in 2019, a lot has changed. I've flipped through a few jobs, built some really cool solutions to interesting problems, and it would be fun to write about the challenges that I will face going forward.
The second motivation was technical development. My previous blog was built with Pelican, the static site generator. This was a workable solution, but left a lot to be desired. I had no feeling of ownership over it. Like most `things` in life, it seemed like a tool that was given to me, which I used. I realized I didn't truly understand what was going on under the hood.
A static site generator is a simple tool, it will take some plain text files, applying some templating to them, and rendering them as HTML. This however wasn't interactive. I wouldn't be able to improve or extend this solution without adding other `tools` from other `providers`. The feeling that I was just using tools and not building my own solutions bothered me. That lead to the beginning of sudoscientist.
## Iterations
Blog engines are easy right? Everyone has one!
My personal experience has been that many engineers enter technology via content management systems. WordPress, Django, (insert your favorite content management system here), etc. This is a great start. Most of the `confusing` or `complicated` features are hidden from the developer. Model, view, and controller allows engineers to build solutions much faster, but leaves them tangentially dealing with databases, having a hands off approach to networking, and rarely working with serialization of data. Authentication? Already built in. Comments? A click away.
This was my experience. I started with Python, the language I was most comfortable with, and hacked away at Django. Django was a nice starting point. I had always been apprehensive about programming, as I never had any real math training. I still don't know much about algorithms, or what the best data structures fit which situations, but that will come with due time. Django allowed me to rapidly build out a solution, but it didn't really allow me to fiddle with things. I always felt that building around `middleware` was cumbersome, and frankly annoying. Plug and play works great, but comes at the cost of understanding.
The next step for me was moving from Django to Flask. Another Python framework, but a `micro-framework` this time. This again, was a nice comfortable median for me. I wasn't so deep in the weeds; I was overwhelmed, but I still didn't really need to learn what I was doing. Documentation such as `The Flask Mega-Tutorial` made working with Flask very straightforward. This was a good way to take off the training wheels, but I still felt as if I was selling myself short. Templating was all done server side, and the modern world was moving towards technologies such as Angular.js and the then, very young, React.js. Like Docker, JavaScript has taken over the world as well. This lead me to realize that I would have to bite the bullet eventually, and I decided to move away from the model/view/controller methodology, and towards the paradigm of RESTful APIs and static sites written in Javascript to access those APIs.
## Enter sudoscientist
I began writing the back-end to sudoscientist in Python. Flask-restful is a fun tool, but I decided quickly that I wanted to move past Python for larger projects. Python is a great scripting tool, and I wanted to use it in that context. The decision to learn React was similar to that of choosing Go. Go has been gaining steam over the past few years and has a very mature HTTP standard library. While I didn't write sudoscientist with only the standard library, go-chi, the routing framework used, is a very simple one, and is fairly easy to read and understand. I have also over time come to avoid, and possibly dislike object oriented programming. Golang's hands off approach felt smooth. Along with this, many tools in my `${DAY_JOB}`` are built in Go. It made it a compelling language to work with. The focus on a low level of external dependencies when building, and the ethos of using the standard library for as much as you can also piqued my interest. With the tools chosen, I began working.
I [started](https://git.minhas.io/Asara/sudoscientist-go-backend/commit/b7132ce2dc631f1b30c7f0e95b1f46ec6626081e) with the back-end in February, and hacked around with learning the language, the tools, and using curl to make requests. Once I had something I thought I could work with, I [began](https://git.minhas.io/Asara/sudoscientist-js-frontend/commit/79ca8e12975d4c2a8d61ec00d7a71f3987e6bc68) the frontend, with a read me stating `FML`. About 9 months after starting this project, I am now able to say Hello World!
Thanks for being here, and I hope to catch you on the next one!

View file

@ -0,0 +1,7 @@
+++
title = 'Recovered the Blog'
date = 2014-08-15T00:00:00-00:00
+++
## Back up and running
Fixed most of the issues with the blog. Everything should be up and running now! Expect posts soon.

View file

@ -0,0 +1,7 @@
+++
title = 'Recovering the Blog'
date = 2014-08-04T00:00:00-00:00
+++
## Slowly but surely
After a failure of my SD card from a power surge, I've got my blog back up. Just have to get the theme and all working again, ignore the french!

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1 @@
{"Target":"styles.css","MediaType":"text/css","Data":{}}

BIN
static/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

BIN
static/images/profile.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

@ -0,0 +1 @@
Subproject commit 87ce2329dea2cf2903fb044b9eb6c9f14f7d77dd

1
themes/risotto/.gitignore vendored Normal file
View file

@ -0,0 +1 @@
.hugo_build.lock

20
themes/risotto/LICENSE Normal file
View file

@ -0,0 +1,20 @@
The MIT License (MIT)
Copyright (c) 2021 Joe Roe
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View file

@ -0,0 +1,3 @@
[module]
[module.hugoVersion]
min = "0.41.0"

View file

View file

@ -0,0 +1,36 @@
<!DOCTYPE html>
<html lang="{{- site.Language.Lang -}}">
<head>
{{- partial "head.html" . -}}
</head>
<body>
<div class="page">
<header class="page__header">
{{- partial "header.html" . -}}
</header>
<section class="page__body">
{{- block "main" . }}{{- end }}
</section>
<section class="page__aside">
<div class="aside__about">
{{- partial "about.html" . -}}
</div>
<hr>
<div class="aside__content">
{{- block "aside" . }}{{- end }}
</div>
</section>
<footer class="page__footer">
{{- partial "footer.html" . -}}
</footer>
</div>
</body>
</html>

View file

@ -0,0 +1,34 @@
{{- printf "<?xml version=\"1.0\" encoding=\"utf-8\" standalone=\"yes\"?>" | safeHTML }}
<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom">
<channel>
<title>{{ .Site.Title }}</title>
<link>{{ .Permalink }}</link>
<description>Recent content on {{ .Site.Title }}</description>
<generator>Hugo -- gohugo.io</generator>{{ with .Site.LanguageCode }}
<language>{{.}}</language>{{end}}{{ with .Site.Author.email }}
<managingEditor>{{.}}{{ with $.Site.Author.name }} ({{.}}){{end}}</managingEditor>{{end}}{{ with .Site.Author.email }}
<webMaster>{{.}}{{ with $.Site.Author.name }} ({{.}}){{end}}</webMaster>{{end}}{{ with .Site.Copyright }}
<copyright>{{.}}</copyright>{{end}}{{ if not .Date.IsZero }}
<lastBuildDate>{{ .Date.Format "Mon, 02 Jan 2006 15:04:05 -0700" | safeHTML }}</lastBuildDate>{{ end }}
{{ with .OutputFormats.Get "RSS" }}
{{ printf "<atom:link href=%q rel=\"self\" type=%q />" .Permalink .MediaType | safeHTML }}
{{ end }}
<!-- Because my site has content in multiple languages, but blog posts only in English, it merges all posts -->
{{ $pages := (where .Site.RegularPages "Section" "posts") }}
{{ range .Translations }}
{{ $pages = $pages | lang.Merge (where .Site.RegularPages "Section" "posts") }}
{{ end }}
{{ range $pages }}
<item>
<title>{{ .Title }}</title>
<link>{{ .Permalink }}</link>
<pubDate>{{ .Date.Format "Mon, 02 Jan 2006 15:04:05 -0700" | safeHTML }}</pubDate>
{{ with .Site.Author.email }}<author>{{.}}{{ with $.Site.Author.name }} ({{.}}){{end}}</author>{{end}}
<guid>{{ .Permalink }}</guid>
<description>{{ "<![CDATA[" | safeHTML }} {{ .Summary }}]]></description>
</item>
{{ end }}
</channel>
</rss>

View file

@ -0,0 +1 @@
<li><a href="{{ .Permalink }}">{{ .Title | markdownify }}</a></li>

View file

@ -0,0 +1,16 @@
{{ define "main" }}
<h1 id="{{ .Title | urlize }}">{{ .Title | markdownify }}</h1>
{{ .Content }}
<ul>
{{ range .Pages }}
{{ .Render "li" }}
{{ end }}
</ul>
{{ end }}
{{define "aside" }}
{{ if .Params.description }}<p>{{ .Params.description }}</p>{{ end }}
{{ end }}

View file

@ -0,0 +1,29 @@
{{ define "main" }}
<header class="content__header">
<h1>{{ .Title | markdownify }}</h1>
</header>
{{ if .Params.toc }}
<aside>
</aside>
{{ end }}
<div class="content__body">
{{ .Content }}
</div>
<footer class="content__footer"></footer>
{{ end }}
{{define "aside" }}
{{ if .Params.description }}<p>{{ .Params.description }}</p>{{ end }}
{{ if or (.Params.author) (.Params.date) }}
<p>
{{ if .Params.author }}By {{ .Params.author }}{{ if .Date }}, {{ end }}{{ end }}
{{ if .Date }}{{ .Date.Format "2006-01-02" }}{{ end }}
</p>
{{ end }}
{{ if and (.Params.toc) (.TableOfContents) }}
<hr>
On this page:
{{ .TableOfContents }}
{{ end }}
{{ end }}

View file

@ -0,0 +1,4 @@
{{ define "main" }}
{{ .Content }}
{{ end }}

View file

@ -0,0 +1,15 @@
{{ with .Site.Params.about }}
<div class="aside__about">
{{ with .logo }}<img class="about__logo" src="{{ . | absURL }}" alt="Logo">{{ end }}
<h1 class="about__title">{{ .title }}</h1>
{{ with .description }}<p class="about__description">{{ . | markdownify }}</p>{{ end }}
</div>
{{ end }}
<ul class="aside__social-links">
{{ range $item := .Site.Params.socialLinks }}
<li>
<a href="{{ $item.url }}" rel="me" aria-label="{{ $item.title }}" title="{{ $item.title }}"><i class="{{ $item.icon }}" aria-hidden="true"></i></a>&nbsp;
</li>
{{ end }}
</ul>

View file

@ -0,0 +1,2 @@
{{- partial "lang.html" . -}}
<p class="copyright">{{ .Site.Copyright | markdownify }}</p>

View file

@ -0,0 +1,36 @@
<title>{{ with .Title }}{{ . }} {{end}}</title>
{{ with .Site.Params.about }}<meta name="description" content="{{ .description }}">{{ end }}
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta charset="UTF-8"/>
{{ if .Site.Params.noindex }}<meta name="robots" content="noindex" /> {{ end }}
<!-- FontAwesome <https://fontawesome.com/> -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.1.2/css/all.min.css" integrity="sha512-1sCRPdkRXhBV2PBLUdRb4tMg1w2YPf37qatUFeS7zlBy7jJI8Lf4VHwWfZZfpXtYSLy85pkm9GaYVYMfw5BC1A==" crossorigin="anonymous" />
<!-- Academicons <https://jpswalsh.github.io/academicons/> -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/academicons/1.9.1/css/academicons.min.css" integrity="sha512-b1ASx0WHgVFL5ZQhTgiPWX+68KjS38Jk87jg7pe+qC7q9YkEtFq0z7xCglv7qGIs/68d3mAp+StfC8WKC5SSAg==" crossorigin="anonymous" />
<!-- ForkAwesome <https://forkaweso.me/> -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/fork-awesome@1.2.0/css/fork-awesome.min.css" integrity="sha256-XoaMnoYC5TH6/+ihMEnospgm0J1PM/nioxbOUdnM8HY=" crossorigin="anonymous">
<!-- risotto theme -->
<link rel="stylesheet" href="{{ printf "css/palettes/%s.css" (.Site.Params.theme.palette | default "base16-dark") | absURL }}">
<link rel="stylesheet" href="{{ "css/risotto.css" | absURL }}">
<link rel="stylesheet" href="{{ "css/custom.css" | absURL }}">
<!-- favicon -->
{{ if os.FileExists "static/favicon.ico" }}<link rel="icon" href="{{ "favicon.ico" | absURL }}">{{ end }}
{{ if os.FileExists "static/favicon-32x32.png" }}<link rel="icon" type="image/png" sizes="32x32" href="{{ "favicon-32x32.png" | absURL }}">{{ end }}
{{ if os.FileExists "static/favicon-16x16.png" }}<link rel="icon" type="image/png" sizes="16x16" href="{{ "favicon-16x16.png" | absURL }}">{{ end }}
{{ if os.FileExists "static/apple-touch-icon.png" }}<link rel="apple-touch-icon" href="{{ "apple-touch-icon.png" | absURL }}">{{ end }}
{{ if os.FileExists "static/site.webmanifest" }}<link rel="manifest" href="{{ "site.webmanifest" | absURL }}">{{ end }}
<!-- RSS -->
{{ range .AlternativeOutputFormats -}}
<link
rel="{{ .Rel }}"
type="{{ .MediaType.Type }}"
href="{{ .Permalink }}"
title="{{ $.Site.Title }}"
/>
{{ end -}}

View file

@ -0,0 +1,10 @@
<nav class="page__nav main-nav">
<ul>
<h1 class="page__logo"><a href="{{ .Site.BaseURL }}" class="page__logo-inner">{{ .Site.Title }}</a></h1>
{{ $currentPage := . }}
{{ range .Site.Menus.main }}
<li class="main-nav__item"><a class="nav-main-item{{ if or ($currentPage.IsMenuCurrent "main" .) ($currentPage.HasMenuCurrent "main" .) (eq ($currentPage.Permalink) (.URL | absLangURL)) }} active{{end}}" href="{{ .URL | absLangURL }}" title="{{ .Title }}">{{ .Name }}</a></li>
{{ end }}
</ul>
</nav>

View file

@ -0,0 +1,28 @@
<p>
{{ $siteLanguages := .Site.Languages }}
{{ $pageLang := .Page.Lang }}
{{ $currentPage := . }}
{{ $pageName := "" }}
{{ range .Site.Menus.main }}
{{ if eq ($currentPage.Permalink) (.URL | absLangURL) }}
{{ $pageName = .Name }}
{{ end }}
{{ end }}
{{ range .Page.AllTranslations }}
{{ $translation := .}}
{{ range $siteLanguages }}
{{ if eq $translation.Lang .Lang }}
{{ $selected := false }}
{{ if eq $pageLang .Lang }}
<br/><span class="active">$ echo $LANG<br/><b>{{ .LanguageName }}</b></span><br/>
{{ else }}
<br/><a href="{{ $translation.Permalink }}">export LANG={{ .LanguageName }}; ./{{ $pageName }}</a><br/>
{{ end }}
{{ end }}
{{ end }}
{{ end }}
</p>
<br /><br />

View file

@ -0,0 +1,26 @@
{{ define "main" }}
<header class="content__header">
<h1>{{ .Title | markdownify }}</h1>
{{ .Content }}
</header>
{{ range .Pages }}
<article class="post">
<header class="post__header">
<h1><a href="{{ .Permalink }}">{{ .Title | markdownify }}</a></h1>
<p class="post__meta">
{{ .Params.author }},
<span class="date">{{ .Date.Format "2 January 2006" }}</span>
</p>
</header>
<section class="post__summary">
{{ .Summary }}
</section>
</article>
{{ end }}
{{ end }}
{{define "aside" }}
{{ if .Params.description }}<p>{{ .Params.description }}</p>{{ end }}
{{ end }}

View file

@ -0,0 +1,26 @@
/* About/bio section */
.about__logo {
height: 1.5rem;
}
.about__title {
display: inline;
vertical-align: top;
}
.about__title::before {
content: none;
}
/* Social media links */
.aside__social-links {
padding: 0;
}
.aside__social-links li {
display: inline-block;
}
.aside__social-links li::marker {
content: none;
}

View file

@ -0,0 +1,17 @@
:root {
/* Background */
--bg: var(--base00);
--off-bg: var(--base01);
--inner-bg: var(--base02);
/* Text */
--fg: var(--base05);
--off-fg: var(--base04);
--muted: var(--base03);
--link: var(--base0D);
--hover: var(--base0C);
--highlight: var(--base0A);
/* Logo */
--logo: var(--base0B);
}

View file

@ -0,0 +1 @@
/* Override this file to customise the theme's CSS for your site */

View file

@ -0,0 +1,7 @@
.page__footer {
color: var(--off-fg);
}
.page__footer p {
margin: 0;
}

View file

@ -0,0 +1,20 @@
/* Main menu */
.main-nav ul {
display: flex;
flex-flow: row wrap;
justify-content: flex-start;
margin: 0;
padding: 0 0 0.25rem 0;
gap: 0rem 1.5rem;
}
.main-nav li {
padding-top: 0.25rem;
margin-left: 1rem;
text-transform: lowercase;
}
.main-nav li::marker {
content: "./";
}

View file

@ -0,0 +1,57 @@
/* 1rem = 16px by default */
.page {
max-width: 64rem;
margin: 1rem auto;
display: grid;
grid-template-areas:
"header"
"body"
"aside"
"footer";
grid-template-columns: minmax(0, 1fr); /* https://css-tricks.com/preventing-a-grid-blowout/ */
grid-row-gap: 2rem;
}
@media (min-width: 45rem) {
.page {
grid-template-areas:
"header header"
"body aside"
"footer footer";
grid-template-columns: minmax(0, 1fr) 15rem;
grid-column-gap: 2rem;
}
}
/* Header */
.page__header {
grid-area: header;
display: flex;
}
.page__logo {
flex-shrink: 0;
}
.page__nav {
flex-grow: 1;
}
/* Body + aside */
.page__body {
grid-area: body;
background-color: var(--off-bg);
box-shadow: 0 0 0 1rem var(--off-bg);
overflow-wrap: break-word;
}
.page__aside {
grid-area: aside;
color: var(--off-fg);
}
/* Footer */
.page__footer {
grid-area: footer;
}

View file

@ -0,0 +1,37 @@
.page__logo {
padding: 0;
margin: 0;
font-weight: inherit;
color: var(--bg);
}
.page__logo:before {
content: none;
}
.page__logo-inner {
display: block;
background: var(--logo);
opacity: 0.90;
padding: 0.25rem;
}
a.page__logo-inner:link, a.page__logo-inner:visited {
color: inherit;
text-decoration: inherit;
}
a.page__logo-inner:hover,
a.page__logo-inner:active {
opacity: 1;
}
.page__logo-inner:before {
content: "";
color: var(--bg);
}
.page__logo-inner:after {
content: ":~#";
color: var(--bg);
}

View file

@ -0,0 +1,20 @@
/* Dracula by Mike Barkmin (http://github.com/mikebarkmin) based on Dracula Theme (http://github.com/dracula) */
:root {
--base00: #111313;
--base01: #212629;
--base02: #282424;
--base03: #986c9e;
--base04: #6c8b9e;
--base05: #c1c8c9;
--base06: #6b9e98;
--base07: #b8baba;
--base08: #9e6b71;
--base09: #424446;
--base0A: #cdcfce;
--base0B: #b290b6;
--base0C: #90b6b3;
--base0D: #90a7b6;
--base0E: #986b9e;
--base0F: #b5b18f;
}

View file

@ -0,0 +1,12 @@
@import 'colours.css';
@import 'typography.css';
@import 'layout.css';
@import 'header.css';
@import 'logo.css';
@import 'about.css';
@import 'footer.css';
body {
background-color: var(--bg);
color: var(--fg);
}

View file

@ -0,0 +1,86 @@
/* Background */ .bg { color: #fab387; background-color: #1e1e2e; }
/* PreWrapper */ .chroma { color: #fab387; background-color: #1e1e2e; }
/* Other */ .chroma .x { }
/* Error */ .chroma .err { color: #f38ba8 }
/* CodeLine */ .chroma .cl { }
/* LineLink */ .chroma .lnlinks { outline: none; text-decoration: none; color: inherit }
/* LineTableTD */ .chroma .lntd { vertical-align: top; padding: 0; margin: 0; border: 0; }
/* LineTable */ .chroma .lntable { border-spacing: 0; padding: 0; margin: 0; border: 0; }
/* LineHighlight */ .chroma .hl { background-color: #ffffcc }
/* LineNumbersTable */ .chroma .lnt { white-space: pre; -webkit-user-select: none; user-select: none; margin-right: 0.4em; padding: 0 0.4em 0 0.4em;color: #7d5943 }
/* LineNumbers */ .chroma .ln { white-space: pre; -webkit-user-select: none; user-select: none; margin-right: 0.4em; padding: 0 0.4em 0 0.4em;color: #7d5943 }
/* Line */ .chroma .line { display: flex; }
/* Keyword */ .chroma .k { color: #cba6f7 }
/* KeywordConstant */ .chroma .kc { color: #cba6f7; font-style: italic }
/* KeywordDeclaration */ .chroma .kd { color: #cba6f7 }
/* KeywordNamespace */ .chroma .kn { color: #cba6f7 }
/* KeywordPseudo */ .chroma .kp { color: #cba6f7; font-weight: bold }
/* KeywordReserved */ .chroma .kr { color: #cba6f7 }
/* KeywordType */ .chroma .kt { color: #f9e2af }
/* Name */ .chroma .n { color: #b4befe }
/* NameAttribute */ .chroma .na { color: #f9e2af }
/* NameBuiltin */ .chroma .nb { font-style: italic }
/* NameBuiltinPseudo */ .chroma .bp { color: #b4befe }
/* NameClass */ .chroma .nc { color: #f9e2af }
/* NameConstant */ .chroma .no { color: #f9e2af }
/* NameDecorator */ .chroma .nd { color: #f5c2e7 }
/* NameEntity */ .chroma .ni { color: #f5c2e7 }
/* NameException */ .chroma .ne { color: #eba0ac }
/* NameFunction */ .chroma .nf { color: #89dceb }
/* NameFunctionMagic */ .chroma .fm { color: #b4befe }
/* NameLabel */ .chroma .nl { color: #f9e2af }
/* NameNamespace */ .chroma .nn { color: #f9e2af }
/* NameOther */ .chroma .nx { }
/* NameProperty */ .chroma .py { color: #b4befe }
/* NameTag */ .chroma .nt { color: #cba6f7 }
/* NameVariable */ .chroma .nv { }
/* NameVariableClass */ .chroma .vc { color: #b4befe }
/* NameVariableGlobal */ .chroma .vg { color: #b4befe }
/* NameVariableInstance */ .chroma .vi { color: #b4befe }
/* NameVariableMagic */ .chroma .vm { color: #b4befe }
/* Literal */ .chroma .l { }
/* LiteralDate */ .chroma .ld { }
/* LiteralString */ .chroma .s { color: #a6e3a1 }
/* LiteralStringAffix */ .chroma .sa { color: #a6e3a1 }
/* LiteralStringBacktick */ .chroma .sb { color: #a6e3a1 }
/* LiteralStringChar */ .chroma .sc { color: #a6e3a1 }
/* LiteralStringDelimiter */ .chroma .dl { color: #a6e3a1 }
/* LiteralStringDoc */ .chroma .sd { color: #a6e3a1 }
/* LiteralStringDouble */ .chroma .s2 { color: #a6e3a1 }
/* LiteralStringEscape */ .chroma .se { color: #89b4fa }
/* LiteralStringHeredoc */ .chroma .sh { color: #a6e3a1 }
/* LiteralStringInterpol */ .chroma .si { color: #a6e3a1 }
/* LiteralStringOther */ .chroma .sx { color: #a6e3a1 }
/* LiteralStringRegex */ .chroma .sr { color: #89b4fa }
/* LiteralStringSingle */ .chroma .s1 { color: #a6e3a1 }
/* LiteralStringSymbol */ .chroma .ss { color: #a6e3a1 }
/* LiteralNumber */ .chroma .m { }
/* LiteralNumberBin */ .chroma .mb { }
/* LiteralNumberFloat */ .chroma .mf { }
/* LiteralNumberHex */ .chroma .mh { }
/* LiteralNumberInteger */ .chroma .mi { }
/* LiteralNumberIntegerLong */ .chroma .il { }
/* LiteralNumberOct */ .chroma .mo { }
/* Operator */ .chroma .o { color: #89dceb }
/* OperatorWord */ .chroma .ow { color: #89dceb; font-weight: bold }
/* Punctuation */ .chroma .p { color: #cdd6f4 }
/* Comment */ .chroma .c { color: #585b70; font-style: italic }
/* CommentHashbang */ .chroma .ch { color: #585b70; font-style: italic }
/* CommentMultiline */ .chroma .cm { color: #585b70; font-style: italic }
/* CommentSingle */ .chroma .c1 { color: #585b70; font-style: italic }
/* CommentSpecial */ .chroma .cs { color: #585b70; font-style: italic }
/* CommentPreproc */ .chroma .cp { color: #89b4fa; font-style: italic }
/* CommentPreprocFile */ .chroma .cpf { color: #89b4fa; font-style: italic }
/* Generic */ .chroma .g { }
/* GenericDeleted */ .chroma .gd { color: #eba0ac }
/* GenericEmph */ .chroma .ge { font-style: italic }
/* GenericError */ .chroma .gr { color: #eba0ac }
/* GenericHeading */ .chroma .gh { color: #89dceb; font-weight: bold }
/* GenericInserted */ .chroma .gi { color: #a6e3a1 }
/* GenericOutput */ .chroma .go { }
/* GenericPrompt */ .chroma .gp { color: #6c7086; font-weight: bold }
/* GenericStrong */ .chroma .gs { font-weight: bold }
/* GenericSubheading */ .chroma .gu { color: #89dceb; font-weight: bold }
/* GenericTraceback */ .chroma .gt { color: #eba0ac }
/* GenericUnderline */ .chroma .gl { }
/* TextWhitespace */ .chroma .w { color: #313244 }

View file

@ -0,0 +1,221 @@
/* Fonts */
:root {
--font-monospace: "Fira Mono", monospace;
}
body {
font-family: var(--font-monospace);
font-size: 16px;
line-height: 1.5rem;
}
/* Headings */
h1,
h2,
h3,
h4,
h5,
h6 {
font-size: 1rem;
margin: 1.5rem 0 0 0;
font-weight: 600;
}
h1+h2,
h1+h3,
h1+h4,
h1+h5,
h1+h6,
h2+h3,
h2+h4,
h2+h5,
h2+h6,
h3+h4,
h3+h5,
h3+h6,
h4+h5,
h4+h6,
h5+h6 {
margin: 0;
}
h1:before { content: "# "; }
h2:before { content: "## "; }
h3:before { content: "### "; }
h4:before { content: "#### "; }
h5:before { content: "##### "; }
h6:before { content: "###### "; }
h1:before,
h2:before,
h3:before,
h4:before,
h5:before,
h6:before {
color: var(--muted);
}
h1:first-child {
margin-top: 0;
}
/* Paragraphs */
p {
margin: 0 0 1.5rem 0;
}
/* Links */
a:link, a:visited {
color: var(--link);
}
a:hover, a:active, a.active {
color: var(--hover);
}
/* Lists */
ul {
margin: 0 0 1.5rem 0;
padding-left: 1.25rem;
}
ol {
margin: 0 0 1.5rem 0;
padding-left: 1.75rem;
}
ul ul,
ul ol,
ol ul,
ol ol {
margin: 0;
}
ul li::marker {
content: '\00A0';
color: var(--muted);
}
ol li::marker {
color: var(--muted);
}
dt {
margin: 0;
font-weight: bold;
}
dd {
margin: 0 0 0 1.5rem;
font-style: italic;
}
dd + dt {
margin-top: 1.5rem;
}
dl {
margin: 0 0 1.5rem 0;
}
/* Blockquotes */
blockquote {
position: relative;
margin: 0 0 1.5rem 1.5rem;
}
blockquote::before {
position: absolute;
left: -1.5rem;
content: ">";
color: var(--muted);
}
.twitter-tweet::before {
content: "\f099";
font-family: "Font Awesome 5 Brands";
font-weight: 400;
}
/* Code */
pre,
code,
kbd,
samp {
background: var(--inner-bg) !important;
font-family: var(--font-monospace);
color: var(--off-fg);
}
pre {
overflow-x: auto;
padding: 1.5rem;
margin: 0 0 1.5rem 0;
}
/* Fix overflow when config markup.highlight.lineNos is true */
/* See https://github.com/joeroe/risotto/issues/41 */
.highlight div {
overflow-x: auto;
}
/* Emphasis */
b,
strong {
font-weight: 600;
}
/* Highlighting */
::selection,
mark {
background-color: var(--highlight);
color: var(--bg);
}
/* Other typographic elements */
hr {
border: 0;
margin-bottom: 1.5rem;
}
hr:after {
content: '---';
color: var(--muted);
}
/* Prevent super/sub from affecting line height */
sup, sub {
vertical-align: baseline;
position: relative;
top: -0.25rem;
font-size: unset;
}
sub {
top: 0.25rem;
}
/* Tables */
table {
border-spacing: 0;
margin: 0 0 1.5rem 0;
overflow-wrap: anywhere;
}
th, td {
padding: 0 .75rem;
vertical-align: top;
}
th:first-child, td:first-child {
padding-left: 0;
}
th {
text-align: inherit;
}
/* Figures */
img {
max-width: 100%;
height: auto;
}