Move to src dir
6
src/archetypes/default.md
Normal file
|
@ -0,0 +1,6 @@
|
|||
---
|
||||
title: "{{ replace .Name "-" " " | title }}"
|
||||
date: {{ .Date }}
|
||||
draft: true
|
||||
---
|
||||
|
8
src/archetypes/rambles/default.md
Normal file
|
@ -0,0 +1,8 @@
|
|||
---
|
||||
title: "{{ replace .Name "-" " " | title }}"
|
||||
date: {{ .Date }}
|
||||
author: "$HUMANOID"
|
||||
tags: ["", ""]
|
||||
description: "This is the default article description"
|
||||
draft: true
|
||||
---
|
45
src/config.toml
Normal file
|
@ -0,0 +1,45 @@
|
|||
baseURL = '/'
|
||||
languageCode = 'en-us'
|
||||
title = 'The Voidcruiser'
|
||||
theme = 'vugo'
|
||||
|
||||
paginate = 100
|
||||
|
||||
[params]
|
||||
contentTypeName = 'rambles'
|
||||
[params.logo]
|
||||
logoText = "The Voidcruiser"
|
||||
logoHomeLink = "/"
|
||||
[menu]
|
||||
[[menu.main]]
|
||||
identifier = "about"
|
||||
name = "about"
|
||||
url = "/about/"
|
||||
[[menu.main]]
|
||||
identifier = "insanity"
|
||||
name = "aural insanity"
|
||||
url = "/insanity/"
|
||||
[[menu.main]]
|
||||
identifier = "services"
|
||||
name = "services"
|
||||
url = "/services/"
|
||||
[[menu.main]]
|
||||
identifier = "rambles"
|
||||
name = "rambles"
|
||||
url = "/rambles/"
|
||||
[[menu.services]]
|
||||
identifier = "alpine repo"
|
||||
name = "alpine repo"
|
||||
url = "https://alpine.voidcruiser.nl"
|
||||
[[menu.services]]
|
||||
identifier = "searXNG instance"
|
||||
name = "searXNG instance"
|
||||
url = "/searx/"
|
||||
[markup]
|
||||
[markup.tableOfContents]
|
||||
endLevel = 4
|
||||
startLevel = 1
|
||||
[markup.highlight]
|
||||
noClasses = true
|
||||
style = 'gruvbox'
|
||||
|
21
src/content/_index.md
Normal file
|
@ -0,0 +1,21 @@
|
|||
---
|
||||
title: "Home"
|
||||
date: 2022-08-10T14:19:40+02:00
|
||||
description: "Digital enclave of the Voidcruiser"
|
||||
---
|
||||
# This thing might contain a ramble every now and then
|
||||
|
||||
Welcome to my little corner of the internet!
|
||||
|
||||
If you're a sane person, you might be wondering why I made this site.
|
||||
That is to say, you are the type of person who hasn't seen several dozen sites like this one yet.
|
||||
To you, dear sane person, the quickest way to explain why I made this site would to to call it an expression of ideology.
|
||||
Or at least part of it.
|
||||
|
||||
Other reasons I made this site include wanting a place to shout into the public consciousness known as the internet without making use of well known/existing platforms.
|
||||
I don't like mainstream platforms as they restrict me to what big tech wants me to express.
|
||||
As you might be able to guess from the design of this page, I am not a big fan of big tech and centralisation of power.
|
||||
|
||||
I might kick various services into the air at some point if I feel like it (have been thinking about PeerTube and Mastodon or Pleroma not that I really know what I'd do with them).
|
||||
|
||||
{{< noscript content="Hello fellow Non-JavaScript-runner! This site page doesn't use any JavaScript unless mentioned." >}}
|
56
src/content/about.md
Normal file
|
@ -0,0 +1,56 @@
|
|||
---
|
||||
title: About
|
||||
description: "The obligatory about page"
|
||||
---
|
||||
# The HUMANOID running this thing {#HUMANOID}
|
||||
|
||||
## Identity - 's a funny thing isn't it?
|
||||
|
||||
{{< about >}}
|
||||
|
||||
# The site itself
|
||||
|
||||
|
||||
- Colorscheme: [Gruvbox](https://github.com/morhetz/gruvbox)
|
||||
Depending on `prefers-color-scheme:` in your browser settings being `light` or not, you get to see the light variant or not.
|
||||
- This site is viewable and usable on every browser I've thrown at it so far.\
|
||||
I even spent a 10 minutes making this site look reasonable on mobile devices.\
|
||||
Here's the browser list:
|
||||
- Qutebrowser
|
||||
- Bromite
|
||||
- LibreWolf
|
||||
- FireFox
|
||||
- UnGoogled Chromium
|
||||
- Lynx
|
||||
- Midori
|
||||
- w3m
|
||||
- Dillo
|
||||
- Netsurf
|
||||
- Nyxt
|
||||
|
||||
# Various devices in posession of [$HUMANOID](#HUMANOID)
|
||||
|
||||
| hostname | os | device/model/main board | role |
|
||||
|---|---|---|---|
|
||||
| hazyMonolith | Debian 11 | HP Z210 Workstation | Used to be an entertainment box but got superceded by voidBerry. These days, mainly standing under my desk being a thing to put my feed up on.|
|
||||
| voidSlab | Alpine Edge | ThinkPad T440p |Daily driver for lighter and less serious things.|
|
||||
| RejuvinatedBrick | Alpine Edge |Dell Latitude E5500 |Being a beautiful brick to nagivate Gopher and Gemini.|
|
||||
| voidCreeper | NixOS 22.05 | HP Omen 15 |Central heating if my actual central heating fails and running the few games I still play these days.|
|
||||
| HappyThonk | NixOS 21.11 | ThinkCentre Edge something-something |Experimental box if I need something slightly stronger then a Raspberry Pi.|
|
||||
| PicturePlanck | Debian 11 | Raspberry Pi 4B |Home server.|
|
||||
| voidBerry | Debian 11 | Raspberry Pi 4B |Light entertainment box.|
|
||||
|
||||
# Software used by [$HUMANOID](#HUMANOID)
|
||||
|
||||
| category | programs |
|
||||
|---|---|
|
||||
| Window manager: | DWM, XMonad
|
||||
| Graphical browser: | FireFox, LibreWolf, UnGoogled Chromium, Brave, Qutebrowser
|
||||
| Text browser: | Lynx, w3m
|
||||
| Gemini client: | Lagrange, Amfora
|
||||
| Terminal: | ST, Kitty
|
||||
| Text editors: | Vim, NeoVim VSCodium with NVim plugin
|
||||
| Image viewer: | sxiv, nsxiv
|
||||
| Video player: | MPV
|
||||
| Music player: | MPD + NCMPCPP
|
||||
|
8
src/content/ideas.md
Normal file
|
@ -0,0 +1,8 @@
|
|||
---
|
||||
title: "Ideas"
|
||||
date: 2023-03-26T22:21:28+02:00
|
||||
description: "List of ideas hacked together for a friend"
|
||||
---
|
||||
- Car bine
|
||||
- Gun named Tommy (Chicago mafia accent)
|
||||
- Pee/Pea shooter
|
79
src/content/insanity.md
Normal file
|
@ -0,0 +1,79 @@
|
|||
---
|
||||
title: "Aural Insanity"
|
||||
date: 2022-08-10T15:09:31+02:00
|
||||
description: "Insanity of the aural variety."
|
||||
---
|
||||
# Bid Farewel to your Sanity
|
||||
## A thing consisting of 4 audio tracks.
|
||||
|
||||
The tracks used in this... thing are from the following games:
|
||||
|
||||
|Track Source | Channel |
|
||||
|---------------------------------|---------|
|
||||
|Don\'t Starve OST - Ragtime | Center |
|
||||
|Blood - The Chanting track | Center |
|
||||
|Painkiller - The Carnival level | Left |
|
||||
|Blood - The Mall/Elevator music | Right |
|
||||
|
||||
This is a file dedicated to aural insanity produced every now and then
|
||||
|
||||
{{< audio src=/audio/insanity.wav audio=wav >}}
|
||||
|
||||
# Anvil
|
||||
This is the result of deciding to mess around with LMMS, a distorted Minecraft anvil sound ripped from YouTube and having a friend nearby with knowledge on how to create music (in other words, a musician).
|
||||
|
||||
I made an anvil melody of sorts and Vlams added a drum sample loop and a few synthesizers.
|
||||
|
||||
{{< audio src=/audio/aaaaanvil.flac audio=flac >}}
|
||||
|
||||
# Orca
|
||||
|
||||
Some [Orca experiments.](/rambles/orca/)
|
||||
|
||||
## plinkinator
|
||||
|
||||
{{< start-details summary="Orca source" >}}
|
||||
```orca
|
||||
..................
|
||||
..nVC.............
|
||||
..................
|
||||
5U9..Vn..4U9..Vn..
|
||||
.*:04C....*:03C...
|
||||
..................
|
||||
3U5..Vn...........
|
||||
..:05C............
|
||||
..................
|
||||
3U8..Vn...........
|
||||
.*:06C............
|
||||
..................
|
||||
```
|
||||
{{< end-details >}}
|
||||
|
||||
### First result
|
||||
|
||||
{{< audio src=/audio/orca/plinkinator.flac audio=flac >}}
|
||||
|
||||
### Later variant
|
||||
{{< audio src="/audio/orca/varied_plinkinator.flac" audio=flac >}}
|
||||
|
||||
## Shepard tone
|
||||
|
||||
{{< start-details summary="Orca source" >}}
|
||||
```orca
|
||||
.C8C7........
|
||||
.1.67TABCDEFG
|
||||
.J.3XG.......
|
||||
.18T1234567..
|
||||
...2.........
|
||||
D1.J.........
|
||||
*:02G.2......
|
||||
```
|
||||
{{< end-details >}}
|
||||
|
||||
### Climb
|
||||
{{< audio src="/audio/orca/climb.flac" audio=flac >}}
|
||||
|
||||
### Fall
|
||||
|
||||
Same as 'climb' but with the note and octave loops reversed.
|
||||
{{< audio src="/audio/orca/fall.flac" audio=flac >}}
|
57
src/content/rambles/adblock.md
Normal file
|
@ -0,0 +1,57 @@
|
|||
---
|
||||
title: "Adblock"
|
||||
date: "2022-08-22T23:46:09+02:00"
|
||||
author: "$HUMANOID"
|
||||
tags: ["internet", "advertisements"]
|
||||
description: "The morality and utility of adblockers"
|
||||
---
|
||||
### UBlock Origin
|
||||
Before I even get started, I'm going to tell you to use [UBlock Origin](https://ublockorigin.com/).
|
||||
|
||||
It is by far the most configurable and extensive adblocker that I know of.
|
||||
I use it in every browser that supports it.
|
||||
|
||||
|
||||
# Moral Conundrums
|
||||
You often hear people proclaim that blocking ads is bad for the internet the way that it is.
|
||||
After which they will say that the alternative is to pay for every now-free aspect of the internet.
|
||||
Usually through something like subscriptions.
|
||||
|
||||
My response is to this is "Have you seen how popular crowd funding has gotten over the last few years?"
|
||||
People already frequently pay for the content they want to consume.
|
||||
Sure, it might be less than one might get through ad revenue, but that pays off in not having to shill to daddy {Google,Facebook,Apple,Amazon}.
|
||||
This in turn means not indirectly violating your user's privacy.
|
||||
|
||||
But let's analyse that a bit further.
|
||||
The claim here is that the lack of advertisements would be bad, because it would mean large portions of the internet would become subscription based in some way.
|
||||
For one, I'd argue that the presence of ads on the internet has done more harm than good, going as far as saying that, no, the internet would be better off without ads plastered everywhere.
|
||||
This is never going to happen as there is, understandably a cooperate interest in the internet.
|
||||
And two, not-insignificant portions of the internet _already are_ subscription based.
|
||||
|
||||
{{< hyperbowl content="By blocking ads you are stealing from creators!" >}}
|
||||
|
||||
<!--Sometimes you will even come across people who go as far as claiming that blocking ads is theft.-->
|
||||
Sometimes you will even come across people who go as far to genuinely proclaim the preceding.
|
||||
To those people, the only thing I can do is quote the classic "You wouldn't download a car, would you?"
|
||||
Shortly followed by saying "Yes I fucking would, given the opportunity"
|
||||
No one owes you shit based on the amount of people have ignored the ads on your website.
|
||||
|
||||
Then there is also the argument to be made that ads steal one's attention.
|
||||
The goal here being to manipulate you into buying a product.
|
||||
|
||||
Much like with piracy, the you're not missing out on anything.
|
||||
The only way you would've seen any money from it, would be when you're audience directly sees your content and happens to get an ad shoved in their face.
|
||||
Though I suppose in the case of ads, your audience has no choice but to support you when they don't use an adblocker.
|
||||
In other words, you are forcing your audience to sacrifice their a part attention to support you, whether they want to or not.
|
||||
In my opinion, this makes it morally wrong to make money off of ads.
|
||||
|
||||
I'd argue it is a moral imperative to use an adblocker.
|
||||
|
||||
Then there is also the angle of corporations focussing on ads more and more.
|
||||
This in turn means that avoiding an absurd portion of cooperate influence has become hilariously easy;
|
||||
Just install an adblocker that isn't sponsored by an ad company.
|
||||
The thing is that most people are too lazy to install an adblocker and thus ads are still a viable business model.
|
||||
|
||||
Subscription services such as the one that started as a DVD and VHS rental business and the other one with a monopoly over a significant portion of pop culture, have been working towards ad-supported tiers while drastically increasing the price of their paid tiers.
|
||||
It would be pretty funny if these ads could be avoided using UBlock Origin.
|
||||
Personally, I prefer using MPV wherever possible, so I probably won't find out how well that might work.
|
79
src/content/rambles/browsers.md
Normal file
|
@ -0,0 +1,79 @@
|
|||
---
|
||||
title: Browsers
|
||||
date: 2022-04-04T02:54:33+02:00
|
||||
author: $HUMANOID
|
||||
description: "A little ramble mainly about browsers with Vi-like bindings"
|
||||
tags: ["vim", "linux"]
|
||||
---
|
||||
|
||||
# Web Browsers With Vim Bindings
|
||||
|
||||
If there's one thing I like it's Vim bindings everywhere.
|
||||
Since all Vim browser extentions suck on some level, the remaining choice I have is to use browser with built in vim bindings.
|
||||
To that end, there are technically a few options.
|
||||
|
||||
Those being:
|
||||
|
||||
- [Qutebrowser](https://qutebrowser.org)
|
||||
- [Vieb](https://vieb.dev)
|
||||
- [Vimb](https://fanglingsu.github.io/vimb)
|
||||
- [Nyxt](https://nyxt.atlas.engineer)
|
||||
|
||||
These projects are great but all have their problems.
|
||||
Let's go through them bottom-to-top.
|
||||
|
||||
## Nyxt
|
||||
|
||||
Nyxt is written in Common Lisp, making it very easy to hack on.
|
||||
Supports either QtWebEngine or WebKitGTK.
|
||||
It also appeals more to the Emacs world then the Vim world.
|
||||
Much like Emacs, Nyxt _does_ have the option of Vim bindings, but they feel somewhat half baked.
|
||||
Then there is the problem of what little interface there is not being particularly clear.
|
||||
To be fair, I haven't really given it a proper shot because it's configured on Common fucking Lisp.
|
||||
Not being much of an Emacs user, I have little to no interest in learning Lisp to configure a browser.
|
||||
|
||||
## Vimb
|
||||
|
||||
The suckless option.
|
||||
|
||||
Uses WebKitGTK.
|
||||
Is written in C and configured in a vimscript-esque syntax.
|
||||
The thing that turns me off from this browser is it being _too_ suckless.
|
||||
It almost feels like surf with a slightly more accessible way of configuring it through something that _isn't_ a header file.
|
||||
|
||||
Practical reasons that prevent me from using it are the lack of proper ad blocking and tabs.
|
||||
By default, it also uses the number keys for URL hinting instead of the home row.
|
||||
It also has similar performance to suckless' surf
|
||||
In other words, it takes quite a long time to load pages, especially if they contain a large amount of JavaScript (though you shouldn't be visiting those pages unless strictly necessary) and rather quickly hogs quite a lot of resources.
|
||||
|
||||
## Vieb
|
||||
|
||||
An Electron based browser.
|
||||
|
||||
Say whatever you want about Vimb's performance, but Vieb makes Google Chrome seem like a well balanced product.
|
||||
I've seen it eat 25% of a CPU thread with a single tab containing nothing but the Vieb documentation.
|
||||
|
||||
It has a rather pretty interface and could (if the github page is to be believed) be used to interact with other Electron applications like the Discord client as if it were ran natively (don't quote me on that, I'm too lazy to check and probably wrong).
|
||||
|
||||
## Qutebrowser
|
||||
|
||||
Which brings us here.
|
||||
Qutebrowser is written and configured in Python; and uses the QtWebEngine.
|
||||
It's extremely flexible and easy to configure.
|
||||
It has reasonable ad blocking that allows for the addition of blocklists in a plain text file.
|
||||
A problem I have with it are that I can't choose which JavaScript elements to block and which to allow, like UMatrix.
|
||||
But then I did see plans to add support for Firefox extensions which would solve this problem.
|
||||
Another minor gripe I have is that scrolling through long pages can look a little sluggish.
|
||||
|
||||
With these changes and (if it weren't a gigantic nightmare) Gecko support, it would be the perfect browser for me.
|
||||
|
||||
# Other Web Browsers
|
||||
|
||||
When I need to use a site that requires some JS, but still functions without enabling everything, my choice of browser is either Librewolf or a heavily configured Firefox.
|
||||
|
||||
From there I use the following list of extensions to make the web usable:
|
||||
|
||||
- [vim-vixen](https://github.com/ueokande/vim-vixen)
|
||||
- [uBlockOrigin](https://github.com/gorhill/uBlock) (Installed by default on Librewolf)
|
||||
- [redirector](https://github.com/einaregilsson/Redirector)
|
||||
- [ToS;DR](https://github.com/tosdr/browser-extensions)
|
133
src/content/rambles/emergent-mechanics.md
Normal file
|
@ -0,0 +1,133 @@
|
|||
---
|
||||
title: "Emergent Game Mechanics"
|
||||
date: "2023-09-25T02:36:11+02:00"
|
||||
author: "$HUMANOID"
|
||||
tags: ["gamedesign", "quake", "doom"]
|
||||
description: "A little piece about emergent mechanics"
|
||||
---
|
||||
|
||||
# Preamble
|
||||
|
||||
I usually talk about whatever hobby project I've been hacking together when the
|
||||
inspiration to write strikes. In a sense this is a similar ramble, just on a
|
||||
wildly different topic, namely video game design.
|
||||
|
||||
# The Meat and Potatoes
|
||||
|
||||
For some time now I've been attempting to wrap my head around why I find games
|
||||
like Doom Eternal to be boring, while games like Ultrakill and Quake are some of
|
||||
my favourites of all time. And I think I've found the answer.
|
||||
|
||||
Doom Eternal has location based damage and various enemy weak spots. For
|
||||
instance, shooting the Mancubus' arm cannons causes them to be disabled, making
|
||||
the Mancubus significantly more manageable. Likewise, shooting a grenade at a
|
||||
Cacodemon causes it swallow the grenade and be stunned for an easy Glory Kill.
|
||||
The Arachnotron has a turret that can be shot and disabled; the Makyr Drones can
|
||||
be shot in the head as quick ammo piñatas; the shields on the dudes carrying
|
||||
them can be easily disabled using the Plasma Rifle, causing a shock wave... so
|
||||
on and so forth.
|
||||
|
||||
Initially, I found these to be really neat ideas. However, it rather quickly
|
||||
became boring when I found out shooting the weak spots is really the only viable
|
||||
way to get rid of enemies, especially on higher difficulties. Sure, Doom Eternal
|
||||
allows you to be very creative in _how_ you shoot those weak spots, but the
|
||||
moment you stop bothering with them, the difficulty curve starts to resemble
|
||||
something closer to a vertical line than an actual curve. The end result is that
|
||||
the combat feels pre-baked and static if you want to be remotely efficient.
|
||||
|
||||
Ultrakill does this right. It has -- as far as I'm aware -- two ways the
|
||||
trajectory of projectiles can be influenced. One being by punching a projectile
|
||||
as it is about to hit you. The other is with a shock wave, often caused by an
|
||||
explosion. Shock waves can be caused using the Knuckleduster (heavy melee
|
||||
attack), the Core Eject (shotgun altfire that shoots a grenade), the rocket
|
||||
launcher, parrying your own shotgun blast as it exists the barrel and several
|
||||
that I can't think of right now. This is the only rule regarding projectiles
|
||||
that is set in stone. Because of this simplicity and how many enemies have
|
||||
projectile attacks, you can get very creative in how you kill them, while
|
||||
keeping all approaches equally viable at any given moment. When an enemy has
|
||||
just shot a projectile at you, you can chose to dodge it, answer it with a
|
||||
shotgun blast; punch it back to the culprit using a light melee attack; switch
|
||||
to heavy melee and shoot it back using a shock wave; overcharge the pump action
|
||||
shotgun, dodge out of its explosion damage using the I-frames received when
|
||||
dodging, while using said same explosion to send the projectile back to the
|
||||
unsuspecting enemy; use those I-frames from the dodge to go through the
|
||||
projectile and punch the enemy in the face retrieving some health in the
|
||||
process, etc, etc.
|
||||
|
||||
All of these are viable stratagies depending on how much health you still have
|
||||
and how many other enemies there are. Getting out of the way of overcharge
|
||||
explosion damage by dodging is no small feat, but might save you from more
|
||||
damage if you're surrounded by a lot of high level enemies. Punching a single
|
||||
enemy isn't something you'll want to do when still surrounded as it means doing
|
||||
less damage in that moment and only damaging a single enemy. I could go on and
|
||||
on, describing how and when one might chose any given strategy, but I'll try to
|
||||
stop here.
|
||||
|
||||
The result is an incredibly dynamic combat system that allows for endless
|
||||
creativity in dealing with any given scenario. If Ultrakill's design philosophy
|
||||
were closer to that of Doom Eternal, there would be a dedicated "redirect
|
||||
projectile" button/weapon. It wouldn't serve much of a purpose outside of
|
||||
deflecting the occasional projectile. On top of that, each enemy attack would
|
||||
have one and only one viable way to answer it.
|
||||
|
||||
For me an encounter in Doom Eternal would go something like the following:
|
||||
|
||||
> Mancubus, take out its cannons using machine gun altfire; Arachnotron, ditto;
|
||||
> Cacodemon, switch to shotgun, shoot grenade using altfire, Glory Kill; "oh
|
||||
> hey, shield dudes", switch to Plasma Rifle and pepper them a bit...
|
||||
|
||||
There is (usually) one "proper" strategy to deal with each enemy. As a result,
|
||||
instead of improvising throughout the entire fight, I'm focussing on what hoops
|
||||
to jump through and in what order. And I guess I don't like being treated as a
|
||||
circus lion. In Ultrakill, I just do what comes natural in any given situation
|
||||
without feeling like I'm being forced down a rather
|
||||
face-paced-yet-impressively-boring path.
|
||||
|
||||
Doom Eternal's movement is even more strict. There is a double jump, double dash
|
||||
and later on a grapple hook. Sure, there are some interesting things you can do
|
||||
here, but short of exploiting the physics engine by manipulating the framerate
|
||||
by opening the weapon wheel, you're not going to see any crazy movement
|
||||
strategies.
|
||||
|
||||
Ultrakill has a triple dash, a slide, dash jump, ground pound and the player
|
||||
character can be knocked about by explosions and shock waves. On top of that,
|
||||
there is also the very intricate interplay between these elements due to how the
|
||||
physics engine works.
|
||||
|
||||
The result here is a set of extremely complex mechanics that are never set in
|
||||
stone; instead emerging from a few laws of the game that don't have total death
|
||||
grip control over their domain.
|
||||
|
||||
There is no way I could write an article about emergent mechanics without
|
||||
mentioning Quake. So lets do that now.
|
||||
|
||||
None of the high level mechanics in Quake are intended. Everything from
|
||||
straferunning, to wallrunning, to rocket jumping, to bunnyhopping and power
|
||||
bunnyhopping (also called strafejumping in the context of Quake) is the result
|
||||
of a few basic rules and wack physics.
|
||||
|
||||
1. "The player can turn"
|
||||
2. "The player can move in 8 cardinal directions"
|
||||
3. "The player can jump"
|
||||
4. "The player can sprint, increasing their speed"
|
||||
5. "There are entities that are affected by physics"
|
||||
6. "The player is one such entity"
|
||||
|
||||
And that's it as far as I'm aware.
|
||||
|
||||
All the of the complex movement mechanics emerge from the interplay of these
|
||||
rules. One of the first that was found was straferunning. What this means is
|
||||
sending both a move-forward and strafe input. Due to the way velocity is
|
||||
calculated, this results in a slight speed increase. Shortly after this, it was
|
||||
discovered that doing this while running into a wall causes a greater speed
|
||||
increase. As did wiggling the mouse left and right. When jumping you don't lose
|
||||
any momentum. Put all of these together -- with the exception of wallrunning --
|
||||
add little finesse on top and the result is strafejumping.
|
||||
|
||||
To me there is something about using these emergent mechanics that is infinitely
|
||||
more satisfying than the pre baked equivalents in modern games. Figuring out how
|
||||
to (ab)use the game's rules to your advantage is half the fun if you ask me and
|
||||
games like Doom Eternal take most of that away from you by giving you one or
|
||||
very few viable strategies to approach a given situation and then telling you
|
||||
most of them from the get go. Getting it to do something that the developers
|
||||
never originally intended.
|
126
src/content/rambles/git-server.md
Normal file
|
@ -0,0 +1,126 @@
|
|||
---
|
||||
title: 'Hosting your own Git "server"'
|
||||
date: "2022-09-30T14:27:31+02:00"
|
||||
author: "$HUMANOID"
|
||||
tags: ["git", "ssh"]
|
||||
description: "The basis of a Git server is a machine accessible in a way that Git understands with a bunch of bare repositories"
|
||||
toc: true
|
||||
---
|
||||
|
||||
# Introduction
|
||||
|
||||
Pretty much the moment I started to work with Git a few years ago, I've wanted to host my own server.
|
||||
I didn't really bother looking into how that could work for a very long time.
|
||||
I assumed it would be rather complex considering Git itself is an extremely complex (set of) tool(s) to work with.
|
||||
I was hilariously wrong.
|
||||
It's _so_ simple to me that I'm under the impression that the lack of posts regarding the topic on blogs like this, is because it's so simple.
|
||||
Still, I'm going to spend the rest of this post explaining how to set up a server and why even calling a basic configuration a server, is a bit of an exaggeration.
|
||||
|
||||
# Setting up the server
|
||||
|
||||
The first step is to set up a Unix box of any kind.
|
||||
Linux, any of the BSDs and probably even Darwin will work (hell, even Haiku might suffice).
|
||||
The rest of this post assumes a Debian system (or rather, that you have the core GNUtils available).
|
||||
Basics requirements are quite few:
|
||||
|
||||
- It needs to be accessible using SSH.
|
||||
- It needs Git installed.
|
||||
|
||||
## Adding an account
|
||||
|
||||
Common practice dictates that you have a dedicated account for managing your Git repositories.
|
||||
Common practice again dictates that this account be called `git`.
|
||||
Its home directory doesn't really matter, but I like tossing it into `/srv/git`.
|
||||
Login shell also doesn't matter _yet_, I tend to change it to `bash` for when I need to do things as the `git` user.
|
||||
|
||||
You can create an account with the following:
|
||||
```sh
|
||||
sudo useradd git -md /srv/git -s /bin/bash
|
||||
```
|
||||
- `-d` specifies where the home directory should be
|
||||
- `-m` creates the home directory
|
||||
- `-s` specifies what binary to use as login shell
|
||||
|
||||
## Setting up SSH
|
||||
|
||||
### Client side
|
||||
|
||||
On the machine from where you want to push your git repositories, generate a new SSH key to access your Git server:
|
||||
```sh
|
||||
ssh-keygen -t ed25519 -f ~/.ssh/<git-key>
|
||||
```
|
||||
I also recommend creating an entry in `~/.ssh/config` for your Git server:
|
||||
```ssh_config
|
||||
Host <host>-git
|
||||
Hostname <hostname>
|
||||
User git
|
||||
Identityfile ~/.ssh/<git-key>
|
||||
```
|
||||
|
||||
### Server side
|
||||
Next step is to make the `git` account accessible over SSH.
|
||||
`ssh-copy-id` won't work because the `git` user doesn't have a password.
|
||||
So instead, log in as the `git` user and create a `.ssh` directory with an `authorized_keys` file with the public half of the `<git-key>` keypair:
|
||||
```sh
|
||||
sudo su - git
|
||||
umask 077 # strict read write permissions
|
||||
mkdir ~/.ssh
|
||||
cat <git-key>.pub >> ~/.ssh/authorized_keys
|
||||
```
|
||||
|
||||
## Adding a repository
|
||||
|
||||
You should now be able to log into your host as the `git` user with using `<git-key>`.
|
||||
Setting up the route from client to server is half the work.
|
||||
|
||||
Next step.
|
||||
Make this actually serve Git repositories.
|
||||
All this requires is a bare Git repository somewhere within `/srv/git`.
|
||||
To do this, log in as the `git` user and run:
|
||||
```sh
|
||||
git init --bare <repo-name>.git
|
||||
```
|
||||
|
||||
That's it.
|
||||
|
||||
You now have a Git "server" with a repo named `<repo-name>`.
|
||||
|
||||
To add something to this repo, on your client machine create a new repository and add `<host>-git:/srv/git/<repo-name>.git` as remote and push your project to it.
|
||||
Common practice dictates that this remote be called `origin`:
|
||||
```sh
|
||||
mkdir my-fancy-project
|
||||
cd my-fancy-project
|
||||
git init
|
||||
git remote add origin <host>-git:/srv/git/<repo-name>.git
|
||||
echo "# Self-hosted repo!" > README.md
|
||||
git add -A
|
||||
git commit -m "Initial commit"
|
||||
git push -u origin main # assuming you use main as your default branch name
|
||||
```
|
||||
|
||||
To clone this repository on another machine, add the `<git-key>` SSH keypair and related configuration section in `~/.ssh/config` to said machine and run:
|
||||
```sh
|
||||
git clone <host>-git:/srv/git/<repo-name>.git
|
||||
```
|
||||
|
||||
# Automation
|
||||
|
||||
Creating bare repositories by hand gets annoying quite quickly.
|
||||
I have an SSH forced command for another key that runs a script with the following:
|
||||
```sh
|
||||
#!/bin/sh
|
||||
|
||||
BASELOC="/srv/git"
|
||||
echo "Enter new repo name:"
|
||||
read REPONAME
|
||||
[ -z $REPONAME ] && echo "No repo initialised" && exit 1
|
||||
git init --bare $BASELOC/$REPONAME.git
|
||||
```
|
||||
`authorized_keys` entry for `git` user:
|
||||
|
||||
```authorized_keys
|
||||
command="/srv/git/.bin/new-repo" ssh-ed25519 <new-repo-git-key>.pub
|
||||
```
|
||||
|
||||
Another thing you could do is change the login shell binary to a script that does something similar.
|
||||
I haven't bothered to do so for reasons that presently escape me.
|
53
src/content/rambles/hugo.md
Normal file
|
@ -0,0 +1,53 @@
|
|||
---
|
||||
title: "Hugo"
|
||||
date: "2022-08-18T14:53:00+02:00"
|
||||
author: "$HUMANOID"
|
||||
tags: ["hugo"]
|
||||
description: "A little ramble about Hugo"
|
||||
---
|
||||
|
||||
# Reflection
|
||||
I've tried to take a look at [Hugo](https://gohugo.io) for a few times before properly diving into it.
|
||||
|
||||
The first time was back when I first decided to look into making a website not-completely by hand.
|
||||
I had hacked a few previous iterations of this site together and was happy with the design.
|
||||
The next step was to write some blobs of text for people to read besides the random antics that can be found in the side bar.
|
||||
|
||||
A few problems arose here.
|
||||
For one, writing directly in HTML is a pain.
|
||||
So I decided to write in markdown and convert that to HTML using [Pandoc](https://pandoc.org).
|
||||
From there, I still had to index every page completely by hand.
|
||||
The prospect of this annoyed me quite a bit, so I decided to take a cursory glance at Hugo.
|
||||
|
||||
The quick start guide showed how to take a pre-existing theme, a bunch of markdown files and turn that into website.
|
||||
Never having been one to use other people's work as-is, I quickly gave up when I saw the structure of the themes and couldn't be bothered to give it a proper shot.
|
||||
This also gave me the impression of it being a Wordpress-esque environment that works great as long as you stick within the standards.
|
||||
|
||||
A bit later I found out that Pandoc supports templates.
|
||||
So I tried my hand at hacking a site generator together based on a Makefile and Pandoc.
|
||||
This worked... somewhat.
|
||||
But I deemed it more trouble than it was worth and never got very far with it.
|
||||
|
||||
A few weeks ago, I came across someone mentioning Hugo again and how it was really great.
|
||||
I decided to give it another shot.
|
||||
This time really diving into the structure of the themes and it clicked.
|
||||
A thing that probably helped this time around was that it very quickly became clear to me that the features I had been half arsedly implementing in my Makefile were standard features for Hugo.
|
||||
Another thing that undoubtedly helped being that the philosophy and syntax of Pandoc's more advanced features are quite similar to Hugo's.
|
||||
|
||||
## Disservice to Hugo
|
||||
|
||||
One big thing I bump into with Hugo is the fact that most Hugo websites I come across take one template and _maybe_ change the colorscheme.
|
||||
I feel this is a great disservice to the potential that Hugo has to offer in terms of flexibility.
|
||||
Yes, you _can_ have it act similar to Wordpress, but you don't _have to_.
|
||||
I was scared off because I assumed would have to use some existing template or get my CSS to work within a Hugo context.
|
||||
The process of which, I assumed, would be mostly having beat Hugo into submission before it would display the things I wanted in the way I wanted.
|
||||
|
||||
The beauty of Hugo and ultimately the reason this is now a Hugo site, is it's flexibility.
|
||||
You wouldn't be able to tell this is a Hugo site if you don't dig through the metadata fields in the `<head>` tag.
|
||||
And I guess that is also kind of the problem with it.
|
||||
Most people visibly using Hugo take some standard template proudly proclaiming that the page we're presently looking at is generated using it.
|
||||
|
||||
And the people who want to use Hugo but don't care for the pre-existing themes, don't care to proudly shout to the world that their site is made with it.
|
||||
This is why I got the impressions of Hugo that I did.
|
||||
|
||||
In that sense, I'm part of the problem in not showing that Hugo can do pretty much anything you can think of in the realm of static, somewhat blog oriented websites.
|
232
src/content/rambles/i2p-on-nixos.md
Normal file
|
@ -0,0 +1,232 @@
|
|||
---
|
||||
title: "I2p on NixOS"
|
||||
date: 2023-07-27T00:21:37+02:00
|
||||
draft: true
|
||||
author: "$HUMANOID"
|
||||
tags: ["nix", "linux", "i2p"]
|
||||
description: "My i2p configuration on NixOS"
|
||||
draft: false
|
||||
toc: true
|
||||
---
|
||||
|
||||
# Introduction
|
||||
|
||||
Recently I've decided to move most of my hardware over to NixOS. It's been an
|
||||
amazing experience being able to configure everything OS related from one
|
||||
central point.
|
||||
|
||||
A few years ago, I discovered [i2p](https://geti2p.net/en/). I really liked the
|
||||
idea of it; a different darknet than tor with different kinds of people on it --
|
||||
more blog oriented. At the time I ran a node on a Raspberry Pi running Debian. I
|
||||
connected to it using an ssh tunnel over
|
||||
[Yggdrasil](https://yggdrasil-network.github.io/) and used an
|
||||
[Arkenfox](https://github.com/arkenfox/user.js) hardened Firefox profile to
|
||||
browse around.
|
||||
|
||||
That Raspberry Pi no longer serves as a (har) server. Instead I now have a NixOS
|
||||
machine churning away. As with a lot of NixOS topics, there isn't a lot
|
||||
of documentation on configuring i2p. Though truth be told, when comparing it to
|
||||
the config files I had on Debian, a lot of it was rather self explanatory and
|
||||
easy to implement in a Nix module.
|
||||
|
||||
What follows is my current setup on my desktop. Initially I set this up as a
|
||||
test environment, but it works well to the point where I don't really feel the
|
||||
need to move it to another machine.
|
||||
|
||||
# The i2p module
|
||||
|
||||
I'll be using the `i2pd` implementation of the i2p daemon as it requires less
|
||||
resources than the java implementation and has less features out of the box that
|
||||
need configuring.
|
||||
|
||||
Since it is safe to assume that the i2p stack is frequently under attack as it
|
||||
is a darknet protocol, I decided to run it inside of a Nix container. If you
|
||||
don't want to do this, only declare a `services.i2pd` instance.
|
||||
|
||||
With that out of the way, let's declare a basic container environment named
|
||||
`i2pd-container`.
|
||||
|
||||
```nix
|
||||
{ ... }: {
|
||||
containers.i2pd-container = {
|
||||
autoStart = true;
|
||||
config = { ... }: {
|
||||
|
||||
system.stateVersion = "23.05"; # If you don't add a state version, nix will complain at every rebuild
|
||||
|
||||
};
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
From here there are a couple of things you can do with your i2p configuration.
|
||||
My initial goal was to be able to search the i2p net. To that end, the following
|
||||
will suffice.
|
||||
```nix
|
||||
...
|
||||
# Exposing the nessecary ports in order to interact with i2p from outside the container
|
||||
networking.firewall.allowedTCPPorts = [
|
||||
7070 # default web interface port
|
||||
4447 # default socks proxy port
|
||||
4444 # default http proxy port
|
||||
];
|
||||
|
||||
services.i2pd = {
|
||||
enable = true;
|
||||
address = "127.0.0.1"; # you may want to set this to 0.0.0.0 if you are planning to use an ssh tunnel
|
||||
proto = {
|
||||
http.enable = true;
|
||||
socksProxy.enable = true;
|
||||
httpProxy.enable = true;
|
||||
};
|
||||
};
|
||||
...
|
||||
```
|
||||
The SOCKS proxy isn't strictly required, but needed when wanting to use anything
|
||||
else than an HTTP connection to interact with i2p.
|
||||
|
||||
## Torrents
|
||||
|
||||
After a little while, I wanted to see what kinds of _linux distros_ the i2p
|
||||
network has to offer, I also enabled `services.i2pd.proto.sam` and added the
|
||||
corresponding default port to the container's firewall in order to get
|
||||
[XD](https://xd-torrent.github.io/) (which can be found in the Nix repo) to
|
||||
behave properly:
|
||||
|
||||
```nix
|
||||
...
|
||||
networking.firewall.allowedTCPPorts = [ 7656 ];
|
||||
services.i2pd.proto.sam.enable = true;
|
||||
...
|
||||
```
|
||||
{{< start-details summary="Click here to see everything together" >}}
|
||||
```nix
|
||||
{ ... }: {
|
||||
containers.i2pd-container = {
|
||||
autoStart = true;
|
||||
config = { ... }: {
|
||||
|
||||
system.stateVersion = "23.05"; # If you don't add a state version, nix will complain at every rebuild
|
||||
|
||||
# Exposing the nessecary ports in order to interact with i2p from outside the container
|
||||
networking.firewall.allowedTCPPorts = [
|
||||
7656 # default sam port
|
||||
7070 # default web interface port
|
||||
4447 # default socks proxy port
|
||||
4444 # default http proxy port
|
||||
];
|
||||
|
||||
services.i2pd = {
|
||||
enable = true;
|
||||
address = "127.0.0.1"; # you may want to set this to 0.0.0.0 if you are planning to use an ssh tunnel
|
||||
proto = {
|
||||
http.enable = true;
|
||||
socksProxy.enable = true;
|
||||
httpProxy.enable = true;
|
||||
sam.enable = true;
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
}
|
||||
```
|
||||
{{< end-details >}}
|
||||
|
||||
# Tying it to `/etc/nixos/configuration.nix`
|
||||
|
||||
I have a perhaps somewhat over engineered structure when it comes to adding my
|
||||
modules to `/etc/nixos/configuration.nix`.
|
||||
|
||||
I have a `/etc/nixos/services` directory with my own modules, one of which being
|
||||
`services.nix`. `services.nix` imports all the other modules found in the
|
||||
directory. From there, `/etc/nixos/configuration.nix` has
|
||||
`./services/services.nix` in it's import section:
|
||||
|
||||
`/etc/nixos/configuration.nix`:
|
||||
```nix
|
||||
{ pkgs, config, ... }: {
|
||||
imports = [
|
||||
./hardware-configuration.nix
|
||||
./services/services.nix
|
||||
...
|
||||
];
|
||||
...
|
||||
}
|
||||
```
|
||||
`/etc/nixos/services/services.nix`:
|
||||
```nix
|
||||
{ ... }: {
|
||||
imports = [
|
||||
./i2pd-container.nix
|
||||
...
|
||||
];
|
||||
}
|
||||
```
|
||||
|
||||
# Browser profile
|
||||
|
||||
The next step was to set up a browser profile. Since Mullvad recently released a
|
||||
browser, I decided to use it as a basis for my i2p browser.
|
||||
|
||||
To create a new profile in Firefox or any of it's forks, type/paste
|
||||
`about:profiles` in the URL bar and click the "Create a New Profile" button at
|
||||
the top. You will then be prompted for a name. I named mine "i2p".
|
||||
|
||||
Open this profile in a new session and navigate to `about:preferencess#general`
|
||||
and scroll all the way down to "Network Settings". In there, check "Manual proxy
|
||||
configuration" and "Also use this proxy for HTTPS". From there, fill in the
|
||||
address you used in `services.i2pd.address` or the address of the machine you're
|
||||
tying it to if you're using an ssh tunnel. For ports, use `4444` for the HTTP
|
||||
proxy and `4447` for the SOCKS proxy.
|
||||
|
||||
Then move over to `about:preferencess#privacy` and scroll down to "Security". I
|
||||
recommend putting it to "Safest". If you _really need_ to run any JavaScript,
|
||||
you can always add the NoScript icon to your window bar.
|
||||
|
||||
Then scroll a bit further down to "HTTPS-Only Mode" and disable it, as the i2p
|
||||
network doesn't use browser level encryption and thus it would only get in the
|
||||
way.
|
||||
|
||||
As a final step, go to `about:config` and set `keyword.enabled` to false. This
|
||||
isn't strictly necessary, but will make your like a whole lot easier when typing
|
||||
`.i2p` domains and not having your browser attempt -- and fail -- to query the
|
||||
default search engine.
|
||||
|
||||
## `.desktop` entry via `home-manager`
|
||||
|
||||
If you're using the `home-manager`, you can quite easily create a `.desktop`
|
||||
file to make starting your i2p profile more convenient. This is assuming that
|
||||
have either a full on desktop environment or use some other way of parsing
|
||||
`.desktop` entries such as `rofi`'s `drun` mode.
|
||||
|
||||
I have the following in my `$HOME/.config/home-manager/home.nix`:
|
||||
```nix
|
||||
xdg.desktopEntries = {
|
||||
i2p-browser = {
|
||||
name = "i2p Browser";
|
||||
genericName = "Web Browser";
|
||||
exec = "${pkgs.mullvad-browser}/bin/mullvad-browser -p i2p";
|
||||
};
|
||||
};
|
||||
```
|
||||
|
||||
Firefox and it's forks allow you to start a session with a profile using the
|
||||
`-p` parameter. Similarly the `-P` (that is, an upper case 'P' rather than lower
|
||||
case) parameter brings up a profiles menu that allows you to easily change the
|
||||
default profile when running the binary the next time.
|
||||
|
||||
# Using everything
|
||||
|
||||
After starting the container, wait at least 15 minutes for i2pd to build a few
|
||||
tunnels before attempting to connect to anything. Once you have, open your
|
||||
i2p browser profile and you should be able to browse the i2p net. Don't expect
|
||||
things to be fast however, i2p is a much slower network in my experience than
|
||||
Tor.
|
||||
|
||||
Some pages to get you started:
|
||||
|
||||
- [reg.i2p](http://reg.i2p) - link index
|
||||
- [stats.i2p](http://stats.i2p/) - link index
|
||||
- [notbob.i2p](http://notbob.i2p/) - link index
|
||||
- [My site](http://lmwr2pugnmv4pkkxlneeepi4oysfly33zfuj7x25s6hfydysnpfq.b32.i2p/)
|
||||
|
183
src/content/rambles/mpd-and-icecast-on-nix.md
Normal file
|
@ -0,0 +1,183 @@
|
|||
---
|
||||
title: "Mpd and Icecast on Nix"
|
||||
date: "2022-11-06T22:11:10+01:00"
|
||||
author: "$HUMANOID"
|
||||
tags: ["nix", "linux"]
|
||||
description: "Ramble about setting up MPD + Icecast on NixOS"
|
||||
draft: True
|
||||
---
|
||||
# Introduction
|
||||
|
||||
For quite some time now, I've been wanting to move my home server from Debian to
|
||||
NixOS. There were a few things that kept me from starting with the migration.
|
||||
The main one being that the Debian installation still worked reasonably well. I
|
||||
only had occasional hickups in regards to the two USB disks hooked up to my
|
||||
Raspberry Pi 4. Every now and then they'd disappear. My guess is that it was due
|
||||
to power draw as it would consistently happen when putting heavy IO loads on
|
||||
either disk. One of these disks is home to my music collection and used by my
|
||||
internet radio setup (in other words, the main thing I listen to when at home).
|
||||
And this nicely brings my to my next issue.
|
||||
|
||||
{{< img class="stickers" src="/images/graphs/radio.png" mouse="Flowchart demonstrating software stack">}}
|
||||
|
||||
This radio consists of an MPD instance streaming to an Icecast2 instance,
|
||||
being reverse proxied through an NGINX instance (future plans include managing
|
||||
the music collection through Git Annex...). I have tried setting up MPD on NixOS
|
||||
in the past, but couldn't get it to work for some odd reason. I don't remember
|
||||
why I never managed it. I just know that I gave up after a few hours. Today I
|
||||
decided to spend as much time as necessary to get at least the MPD + Icecast2
|
||||
portion to work. The rest of this post will be detailing how I went about
|
||||
setting configuring MPD + Icecast2 on NixOS as there were a few non-obvious
|
||||
things I bumped into.
|
||||
|
||||
# Setting up MPD
|
||||
|
||||
To prevent my `/etc/nixos/configuration.nix` from ballooning into absurdity,
|
||||
you'll want to create something like a `/etc/nixos/services/radio.nix` file
|
||||
containing everything related to the radio stack. This file is then imported in
|
||||
`/etc/nixos/configration.nix` in the `imports` section as follows:
|
||||
|
||||
```nix
|
||||
{ config, pkgs, ... }: {
|
||||
imports =
|
||||
[ # Include the results of the hardware scan.
|
||||
./hardware-configuration.nix
|
||||
./drives.nix
|
||||
./services/radio.nix
|
||||
|
||||
...
|
||||
|
||||
];
|
||||
...
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
The first thing you'll want to do is enable the MPD service:
|
||||
```nix
|
||||
{ config, pkgs, ... }: {
|
||||
services.mpd = {
|
||||
enable = true;
|
||||
...
|
||||
|
||||
```
|
||||
|
||||
From here, there are a few rather basic options that you'll want to set:
|
||||
```nix
|
||||
...
|
||||
musicDirectory = "/path/to/music-collection/";
|
||||
network.listenAddress = "any"; # not quite sure if this is needed
|
||||
...
|
||||
```
|
||||
|
||||
At the `extraConfig` section is where things get interesting. Here I assumed you
|
||||
could point Nix to an existing config file with `(builtins.readFile
|
||||
/path/to/mpd.conf)` but it seems I am wrong. It doesn't throw any errors when
|
||||
switching to a new build, but it doesn't read the given config file either. I'm
|
||||
probably missing something here. Please shout at me if you happen to know what
|
||||
it is.
|
||||
|
||||
Regardless, you're going to want to add at least one audio output method. Since
|
||||
we're going to make MPD talk to Icecast2, we're going to add `audio_output`
|
||||
section of type "shout":
|
||||
|
||||
```nix
|
||||
...
|
||||
musicDirectory = "/path/to/music-collection/";
|
||||
extraConfig = ''
|
||||
audio_output {
|
||||
type "shout"
|
||||
encoder "vorbis" # FOSS codec for the win
|
||||
name "My Shouty Stream"
|
||||
host "<name found in the networking.hostName variable>"
|
||||
port "8000" # or use whatever meme you like
|
||||
mount "/mpd.ogg"
|
||||
password "hunter2"
|
||||
quality "5.0"
|
||||
format "44100:16:2"
|
||||
genre "Rythmic Noise!"
|
||||
protocol "icecast2"
|
||||
}
|
||||
'';
|
||||
};
|
||||
...
|
||||
```
|
||||
|
||||
Please don't take this snippet as gospel and read through the MPD configuration.
|
||||
Whatever is found in `extraConfig` will be directly given to MPD as if through
|
||||
`/etc/mpd.conf` on e.g. Debian. For instance, if you only locally want to listen
|
||||
to your music through say PulseAudio, than you'll need a corresponding output
|
||||
section.
|
||||
|
||||
{{< start-details summary="Example PulseAudio configuration I have on my laptop" >}}
|
||||
```conf
|
||||
audio_output {
|
||||
type "pulse"
|
||||
name "Pulse output"
|
||||
audio_output_format "44100:16:2"
|
||||
samplerate_converter "Medium Sinc Interpolator"
|
||||
mixer_type "software"
|
||||
replaygain "album"
|
||||
volume_normalization "no"
|
||||
}
|
||||
```
|
||||
{{< end-details >}}
|
||||
|
||||
## Mounting a music disk
|
||||
|
||||
In order to automount a disk on NixOS, add an expression something like the
|
||||
following (indirectly) in your `/etc/nixos/configration.nix`:
|
||||
|
||||
```nix
|
||||
fileSystems."/srv/music" = {
|
||||
device = "/dev/sdb1"; # assuming that sdb1 is the partition with your music on it
|
||||
# device = "/dev/disk/by-label/Sauserer"; # or ideally, if you have a labelled disk
|
||||
fsType = "ext4"; # assuming it has a ext4 filesystem on it
|
||||
};
|
||||
```
|
||||
I have my drives listed in `/etc/nixos/drives.nix` and import that in my
|
||||
`configration.nix`
|
||||
|
||||
# Setting up Icecast2
|
||||
|
||||
The next step is to set up Icecast2. Same as with MPD, the first thing you'll
|
||||
want to do is enable it...
|
||||
|
||||
```nix
|
||||
...
|
||||
services.icecast = {
|
||||
enable = true;
|
||||
...
|
||||
```
|
||||
... then add at least the necessary lines of configuration...
|
||||
```nix
|
||||
...
|
||||
hostname = "hostname:8000";
|
||||
listen.port = 8000;
|
||||
admin.password = "it gives me a headache that I can't get Nix to read this from a file, I am probably too much of an idiot figure out how this works";
|
||||
...
|
||||
```
|
||||
|
||||
```nix
|
||||
extraConf = ''
|
||||
<location>Floating around in an example configuration snippet on the internet</location>
|
||||
<admin>icemaster@localhost</admin>
|
||||
<authentication>
|
||||
<source-password>hunter2</source-password>
|
||||
<relay-password>even-more-super-secret-password</relay-password>
|
||||
</authentication>
|
||||
|
||||
<limits>
|
||||
<clients>100</clients>
|
||||
<sources>2</sources>
|
||||
<queue-size>524288</queue-size>
|
||||
<client-timeout>30</client-timeout>
|
||||
<header-timeout>15</header-timeout>
|
||||
<source-timeout>10</source-timeout>
|
||||
<burst-on-connect>1</burst-on-connect>
|
||||
<burst-size>65535</burst-size>
|
||||
</limits>
|
||||
'';
|
||||
};
|
||||
}
|
||||
```
|
161
src/content/rambles/nix-home-manager.md
Normal file
|
@ -0,0 +1,161 @@
|
|||
---
|
||||
title: "Nix Home Manager"
|
||||
date: "2022-09-19T21:49:46+02:00"
|
||||
author: "$HUMANOID"
|
||||
tags: ["nix", "linux"]
|
||||
description: "Basics of using the Nix Home Manager in a way that makes sense to me and probably just me"
|
||||
draft: false
|
||||
toc: true
|
||||
---
|
||||
|
||||
# The Home Manager
|
||||
|
||||
The Nix Home Manager is a way to work with Nix in a declarative environment to manage your dotfiles and user environment packages.
|
||||
This can be used on any system running the Nix packagemanager.
|
||||
Personally, I've started to dabble around with it a bit after installing the Nix packagemanager in my Alpine installation.
|
||||
Since I've already dabbled with Nix on a few systems through NixOS, I already knew the basics of functionally managing packages (or derivations) with the Nix packagemanager.
|
||||
Hence I really quickly got sick of installing packages the imperative way.
|
||||
After looking around for a bit, I found some post mentioning that managing packages in a functional environment outside of NixOS is a bit of a hack.
|
||||
The post referred to some other sources, one of which used a custom meta package and another referred to the [Home Manager](https://nix-community.github.io/home-manager/).
|
||||
|
||||
I had messed around with the Nix Home Manager a tiny bit before, but never _really_ gave it a chance to shine as I didn't really understand it.
|
||||
This time around, I decided to look up some tutorials and see how it worked.
|
||||
When going through the instructions of either tutorial I used, I quickly came to the realisation why I had never _really_ looked at it.
|
||||
Both tutorials took a lot of unnecessary steps according to the author's will and assumed that everyone following their tutorials had the same preferences as them.
|
||||
I obviously don't.
|
||||
So here's my take on installing the Home Manager outside of NixOS.
|
||||
|
||||
# Actually using Home Manager
|
||||
|
||||
Assuming you're continuing from [my previous article on Nix](/rambles/nix-on-other-distros-packagemanagers), the first step is to go to the Home Manager github page and go to the [page regarding the standalone installation](https://nix-community.github.io/home-manager/index.html#sec-install-standalone).
|
||||
|
||||
From there, things will be quite self explanatory if you're used to the (bare) basics of NixOS.
|
||||
Though instead of using `/etc/nixos/configuration.nix` you'll be using `$HOME/.config/nixpkgs/home.nix` by default.
|
||||
A basic Home Manager installation will leave you with a `home.nix` file with the following content:
|
||||
|
||||
```nix
|
||||
{ config, pkgs, ... }:
|
||||
|
||||
{
|
||||
# Home Manager needs a bit of information about you and the
|
||||
# paths it should manage.
|
||||
home.username = "$USER";
|
||||
home.homeDirectory = "/home/$USER";
|
||||
|
||||
# This value determines the Home Manager release that your
|
||||
# configuration is compatible with. This helps avoid breakage
|
||||
# when a new Home Manager release introduces backwards
|
||||
# incompatible changes.
|
||||
#
|
||||
# You can update Home Manager without changing this value. See
|
||||
# the Home Manager release notes for a list of state version
|
||||
# changes in each release.
|
||||
home.stateVersion = "22.05";
|
||||
|
||||
# Let Home Manager install and manage itself.
|
||||
programs.home-manager.enable = true;
|
||||
}
|
||||
```
|
||||
|
||||
After making any changes to your `home.nix` file, you can apply them with:
|
||||
```sh
|
||||
home-manager switch
|
||||
```
|
||||
If you first want to see whether you build is going to be successful or not, run:
|
||||
```sh
|
||||
home-manager build
|
||||
```
|
||||
|
||||
## Installing packages
|
||||
|
||||
To add some packages, you'll need to add them to the `home.packages` array.
|
||||
On my Alpine installation I have the following:
|
||||
|
||||
```nix
|
||||
...
|
||||
home.packages = with pkgs; [
|
||||
brave
|
||||
vscodium
|
||||
];
|
||||
...
|
||||
```
|
||||
Notice the `with pkgs;` section.
|
||||
This prevents you from having to add the `pkgs` prefix to every package you want to add.
|
||||
I don't think this is the idiomatic way of adding packages to your configuration, but it allows me to be a bit lazier and it hasn't caused any breakages yet.
|
||||
|
||||
|
||||
## Managing dotfiles and configuration
|
||||
|
||||
Another amazing thing the Home Manager can do is manage your dotfiles.
|
||||
And this in turn can be managed with Git.
|
||||
More on this later.
|
||||
|
||||
### Git configuration
|
||||
|
||||
For instance, I have it manage my Git config using the git module.
|
||||
To do this, I have the something like the following in my `home.nix`:
|
||||
```nix
|
||||
...
|
||||
programs.git = {
|
||||
enable = true;
|
||||
ignores = [ "*.swp" ]; # I don't need to see that I still have a file open in vim
|
||||
signing = {
|
||||
key = "<gpg-fingerprint>";
|
||||
signByDefault = false; # it would probably be better for security to have this be true, but doing so gets annoying really fast
|
||||
};
|
||||
userEmail = "<user-email>";
|
||||
userName = "<user-name>";
|
||||
extraConfig = {
|
||||
init = {
|
||||
defaultBranch = "main";
|
||||
};
|
||||
};
|
||||
};
|
||||
...
|
||||
```
|
||||
|
||||
Here's the [list of options](https://nix-community.github.io/home-manager/options.html#opt-programs.git.enable) supported by the git module.
|
||||
|
||||
I _strongly_ recommend digging through the documentation, looking for things that interest you in your current situation and setup.
|
||||
|
||||
### Integrating existing (dot)files
|
||||
|
||||
The Home Manager can also manage arbitrary (dot)files for you.
|
||||
I have it link my `.zshrc` into place with the following line:
|
||||
```nix
|
||||
...
|
||||
home.file.".zshrc".source = ./zshrc;
|
||||
...
|
||||
```
|
||||
This looks for a file called `zshrc` in the same directory as `home.nix`.
|
||||
From there, it symlinks it to `~/.zshrc`
|
||||
|
||||
It can also manage recursive file structures.
|
||||
I have it keep track of my `sxiv` configuration with the following few lines:
|
||||
```nix
|
||||
...
|
||||
home.file.".config/sxiv" = {
|
||||
source = ./sxiv;
|
||||
recursive = true;
|
||||
};
|
||||
...
|
||||
```
|
||||
The interesting portion of this snippet is the `recursive` boolean.
|
||||
Because of this, Home Manager will recreate the directory structure found in `./sxiv` in `~/.config/sxiv` and symlinks the files found inside into place.
|
||||
|
||||
# Git integration
|
||||
|
||||
Since all the Home Manager requires is (at least) a single text file to manage your dotfiles and Nix environment packages, it's really easy to keep track of your configuration using Git.
|
||||
To that end, I have a bare repository on one of my home servers over at `/srv/git/nix/<nix-configuration>.git`
|
||||
As a remote, I have pointed my repo in `$HOME/.config/nixpkgs/` to `<server-name>:/srv/git/nix/<nix-configuration>.git`.
|
||||
This works over ssh.
|
||||
My `$HOME/.ssh/config` contains the following lines to make the preceding work:
|
||||
|
||||
```ssh
|
||||
Host <server-name>
|
||||
Hostname <ip-address>
|
||||
User git
|
||||
Identityfile ~/.ssh/<private-key>
|
||||
Port 4242
|
||||
IdentitiesOnly yes # this makes using an ssh agent a bit easier when using multiple keys on the same host
|
||||
```
|
159
src/content/rambles/nix-on-other-distros-packagemanagers.md
Normal file
|
@ -0,0 +1,159 @@
|
|||
---
|
||||
title: "Nix on Other Distros' Packagemangers"
|
||||
date: "2022-09-12T11:37:11+02:00"
|
||||
author: "$HUMANOID"
|
||||
tags: ["nix", "linux"]
|
||||
description: "A guide on installing the Nix packagemanger on Alpine and Debian"
|
||||
toc: true
|
||||
---
|
||||
|
||||
# Introduction
|
||||
|
||||
The Nix package manager is an amazing tool that allows you to manage your packages through a purely functional environment.
|
||||
I'm not going to get into why it's amazing or how it really works in this article.
|
||||
This is purely a guide to installing it in Alpine and Debian Linux.
|
||||
|
||||
> "Why not use the official instructions?"
|
||||
|
||||
The official instructions require you to `curl` a script directly into `sh`.
|
||||
From there it requires sudo privileges to install the package manager itself.
|
||||
This sets of a load of alarm bells in terms of security and what it's going to do from there.
|
||||
As the Nix package manager is packaged for both Debian and Alpine, my two favourite distros next to NixOS, there is no real reason _not_ to use their respective package managers.
|
||||
|
||||
When you try to install Nix on Alpine using the aforementioned installation script you also get the warning that it only supports systemd and that you're on your own when it comes to getting it to work on any other init system (Alpine uses OpenRC).
|
||||
This problem is fixed in the Alpine package.
|
||||
|
||||
Then there is also the benefit of of being able to remove it with either `apt autoremove --purge nix-setup-systemd` or `apk del nix` instead of having to run said same script a second time.
|
||||
|
||||
Yes, I'm aware that you don't _have_ to curl the script directly into `sh` and that you can download it to your local system to see what it's actually doing.
|
||||
But that doesn't take away from the fact that the official instructions tell you to perform an inherently insecure set of actions by trusting what is effectively a random script on the internet.
|
||||
|
||||
> "Doesn't this mixing of packagemanagers cause a huge amount of anomalies?"
|
||||
|
||||
No, that's part of the beauty of Nix.
|
||||
Pretty much everything it does lives in `/nix` and is softlinked into place, meaning it never interferes with your existing package manager.
|
||||
|
||||
_*DISCLAIMER*_: Debian Stable at time of writing has Nix version `2.3.1`, which appears to have some issues that would be fixed by moving to a later version.
|
||||
Using the official instructions would sidestep this.
|
||||
|
||||
# Installation Process
|
||||
|
||||
## Package Installation
|
||||
|
||||
The first step is to install the package itself.
|
||||
On Debian this is done with:
|
||||
|
||||
```sh
|
||||
sudo apt install nix
|
||||
```
|
||||
|
||||
On Alpine, you first have to add the `testing` repo.
|
||||
This is part of the `edge` branch.
|
||||
As far as I know, you _could in theory_ get away with just adding the `testing` repo and not moving the rest of your system over to `edge`, but I highly doubt that will do any good for the stability of your system.
|
||||
|
||||
Moving your system over to the `edge` branch is done by opening `/etc/apk/repositories`, uncommenting the mirrors referring to `edge` and commenting out the lines referring to (at time of writing) version `3.16`.
|
||||
|
||||
From there, run...
|
||||
```sh
|
||||
doas apk -U upgrade
|
||||
```
|
||||
...to move your system over to `edge`.
|
||||
|
||||
Now installing Nix can be done as usual with:
|
||||
```sh
|
||||
doas apk add nix
|
||||
```
|
||||
|
||||
This will install the required binaries and the Nix daemon.
|
||||
|
||||
## Starting the Daemon
|
||||
|
||||
On Debian the Nix daemon is enabled by default.
|
||||
To make sure it's running and enabled, run:
|
||||
```sh
|
||||
sudo systemctl status nix-daemon.service
|
||||
```
|
||||
|
||||
If for whatever reason it's not started and/or enabled, run:
|
||||
```sh
|
||||
sudo systemctl enable --now nix-daemon.service
|
||||
```
|
||||
|
||||
On Alpine, the daemon isn't added to any run level by default.
|
||||
This is done using:
|
||||
```sh
|
||||
doas rc-update add nix-daemon
|
||||
```
|
||||
Starting it is then done using:
|
||||
```sh
|
||||
doas rc-service nix-daemon start
|
||||
```
|
||||
|
||||
## Groups
|
||||
|
||||
Next step is to add your account to the `nix-user` or `nix` group depending whether you are on Alpine or Debian respectively.
|
||||
|
||||
On Debian this can be done with:
|
||||
```sh
|
||||
sudo usermod -aG nix-user $USER
|
||||
```
|
||||
|
||||
And on Alpine this can be done with:
|
||||
```sh
|
||||
doas addgroup $USER nix
|
||||
```
|
||||
|
||||
## Environment variables
|
||||
|
||||
The next step is to set add the Nix binary dir to your `PATH`.
|
||||
To that end, I have the following in my `.profile`
|
||||
```sh
|
||||
# nix
|
||||
export PATH=$PATH:$HOME/.nix-profile/bin
|
||||
```
|
||||
|
||||
Now source your `.profile` again, either using...
|
||||
```sh
|
||||
source ~/.profile
|
||||
```
|
||||
...or by logging out and back in again.
|
||||
|
||||
## Adding a channel
|
||||
|
||||
Now you have a functioning Nix package manager configuration and all that remains to be done is adding and syncing the repo.
|
||||
|
||||
The Alpine package defaults to adding the `unstable` channel.
|
||||
The Debian package doesn't add any channels at all by default.
|
||||
This can be verified with:
|
||||
```sh
|
||||
nix-channel --list
|
||||
```
|
||||
|
||||
To add the unstable channel to your channels, run the following command:
|
||||
```sh
|
||||
nix-channel --add https://nixos.org/channels/nixpkgs-unstable
|
||||
```
|
||||
|
||||
You can probably also add stable channels, but I haven't tried that, as one of the reasons I want to use the Nix package manager on other distros than NixOS is for it's newer packages.
|
||||
(I am aware that the stable channel of NixOS is a lot newer than the Debian stable branch, which is precisely why I don't want to to be my base system on machines where I have Debian installed.)
|
||||
|
||||
## Syncing channels
|
||||
|
||||
To sync your (just added) channel(s), run:
|
||||
```sh
|
||||
nix-channel --update
|
||||
```
|
||||
From this point on you can query your channel(s) using either [the website](https://search.nixos.org/packages) (which is a lot quicker) or:
|
||||
```sh
|
||||
nix-env -qa <query>
|
||||
```
|
||||
Packages can be imperatively installed using...
|
||||
```sh
|
||||
nix-env -iA nixpkgs.<package-name>
|
||||
```
|
||||
...and removed using...
|
||||
```sh
|
||||
nix-env --uninstall <package-name>
|
||||
```
|
||||
|
||||
Consult [the manual](https://nixos.org/manual/nix/stable/) for further usage.
|
37
src/content/rambles/nixvim.md
Normal file
|
@ -0,0 +1,37 @@
|
|||
---
|
||||
title: "NixVim"
|
||||
date: "2024-01-15T02:53:23+01:00"
|
||||
author: "$HUMANOID"
|
||||
tags: ["nix", "linux", "vim"]
|
||||
description: "Some thoughts on Vim, NeoVim and NixVim"
|
||||
draft: true
|
||||
---
|
||||
|
||||
# Vim and Editors in General
|
||||
|
||||
So I've been using Vim for nearly as long as I've been using Linux. I still
|
||||
vaguely remember awkwardly working `vimtutor` for the first time. Before I
|
||||
touched Vim, I didn't really have any strong preference for an editor on Linux.
|
||||
I'd just come from Windows where I'd used Notepad++ to edit small text files and
|
||||
Visual Studio and JetBrains Rider for my college work at the time -- mainly C#
|
||||
in conjunction with Unity. Sure, these editors got the job done, but they
|
||||
weren't... Elegant. Visual Studio and Rider are monstrous pieces of software
|
||||
with several kitchen sinks. And I'm sure Notepad++ has some really cool features
|
||||
that I've never touched, but then it didn't really feel inviting to me and thus
|
||||
I never dug deeper. When moving to Linux, I mainly used Gedit to edit the odd
|
||||
ini file. This got rather tedious when needing root privileges to edit, say an
|
||||
`sshd_config` file. Sure, you can open Gedit using `sudo`, but that makes the
|
||||
terminal window from which you start it useless as long as Gedit is running. On
|
||||
top of that, it being a GTK application means that it spews a lot of output to
|
||||
`STDOUT`. At this point -- not wanting to dig into Vim yet -- I found out Nano
|
||||
is a thing. I never liked it. It's always felt something like a clunky
|
||||
on-graphical Windows Notepad of Linux.
|
||||
|
||||
So after a little while I decided to bite the bullet and start up `vimtutor`.
|
||||
As mentioned before, it was awkward at first. But I could also see the power
|
||||
that the anatomy of Vim allows the user to wield and decided to commit at least
|
||||
the basics to muscle memory.
|
||||
|
||||
# Plugins and NeoVim
|
||||
|
||||
From here it would only be a matter of time.
|
115
src/content/rambles/orca.md
Normal file
|
@ -0,0 +1,115 @@
|
|||
---
|
||||
title: "Orca"
|
||||
date: "2023-09-09T15:59:21+02:00"
|
||||
author: "$HUMANOID"
|
||||
tags: ["orca", "audio"]
|
||||
description: "Rambles on Experiments with Orca"
|
||||
---
|
||||
|
||||
# Brainy Music
|
||||
|
||||
There's something I really like about the idea of mathematically and
|
||||
programmatically making music. As a result, I've harboured an interest in
|
||||
trackers for quite some time, but never got into attempting to actually use one.
|
||||
In a similar vein, I've looked at [Hundred Rabbits'
|
||||
Orca](https://100r.co/site/orca.html) every now and then, but once again
|
||||
couldn't be bothered to figure it out.
|
||||
|
||||
That was until recently, due to unrelated circumstances, I got access to Ableton
|
||||
Live. This piqued my interest in Orca again and I decided to grit my teeth and
|
||||
learn the syntax. The result is that I have now produced some blobs of
|
||||
characters that -- when paired with the right software -- produce some really
|
||||
interesting noises.
|
||||
|
||||
I won't go into detail on how everything works as the documentation is easy
|
||||
enough to read. The syntax is the biggest hurdle.
|
||||
|
||||
# Patterns
|
||||
|
||||
My favourite operator so far is `U` -- the eUclidian rhythm operator. It takes a
|
||||
left and right argument -- lets refer to them as `X` and `Y` respectively -- and
|
||||
then attempts to produce a bang -- that is to say pulse on `X` out of `Y`
|
||||
frames. So `3U8` attempts to produce 3 equally spaced bangs in 8 frames, which
|
||||
will fail. As a result, it spaces the bangs as close to equal as possible. This
|
||||
can lead to some really interesting patterns and rhythms.
|
||||
|
||||
Here's an example of when I wrapped my head around it for the first time:
|
||||
|
||||
{{< audio src=/audio/orca/plinkinator.flac audio=flac >}}
|
||||
|
||||
And the Orca code:
|
||||
|
||||
```orca
|
||||
..nVC...........
|
||||
................
|
||||
5U9..Vn..4U9..Vn
|
||||
.*:04C....*:03C.
|
||||
................
|
||||
3U5..Vn.........
|
||||
..:05C..........
|
||||
................
|
||||
3U8..Vn.........
|
||||
.*:06C..........
|
||||
```
|
||||
|
||||
Here's a variant with a different synth and where I've been messing around with
|
||||
the note being played while recording:
|
||||
|
||||
{{< audio src="/audio/orca/varied_plinkinator.flac" audio=flac >}}
|
||||
|
||||
# Numbers and stuff
|
||||
|
||||
Because I suck at basic calculus and have been half arsedly learning BQN, I
|
||||
wrote a function to get a list of numbers to produce potentially interesting
|
||||
patterns for the `U` operator:
|
||||
|
||||
```bqn
|
||||
OrcaList ← {1↕(⊢⋈¨𝕩⊸÷)1+↕𝕩}
|
||||
```
|
||||
|
||||
This function requires a number and returns a list of all possible divisions
|
||||
from 1 up to the given number.
|
||||
|
||||
```bqn
|
||||
Orcalist 9
|
||||
┌─
|
||||
╵ ⟨ 1 9 ⟩
|
||||
⟨ 2 4.5 ⟩
|
||||
⟨ 3 3 ⟩
|
||||
⟨ 4 2.25 ⟩
|
||||
⟨ 5 1.8 ⟩
|
||||
⟨ 6 1.5 ⟩
|
||||
⟨ 7 1.2857142857142858 ⟩
|
||||
⟨ 8 1.125 ⟩
|
||||
⟨ 9 1 ⟩
|
||||
┘
|
||||
```
|
||||
|
||||
Rough Haskell equivalent for readability's sake:
|
||||
```haskell
|
||||
orcaList x = map ((,) <$> id <*> (x/)) [1..x]
|
||||
```
|
||||
|
||||
# Recording from LMMS
|
||||
|
||||
Since LMMS puts whatever MIDI notes it gets into a grid (by default at least),
|
||||
most of the intricacies of timing are lost. As a result, when using LMMS, I use
|
||||
`pw-record` to record the live audio output with timing still intact.
|
||||
|
||||
This audio journey has also lead me to appreciate Pipewire a lot more. In that
|
||||
regard, I have the following audio stack on NixOS:
|
||||
```nix
|
||||
{ config, ... }: {
|
||||
sound.enable = true;
|
||||
hardware.pulseaudio.enable = false;
|
||||
services.pipewire = {
|
||||
enable = true;
|
||||
alsa.enable = true;
|
||||
alsa.support32Bit = true;
|
||||
pulse.enable = true;
|
||||
jack.enable = true;
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
And I use `qwpgraph` as patchbay.
|
143
src/content/rambles/pwr-switch.md
Normal file
|
@ -0,0 +1,143 @@
|
|||
---
|
||||
title: "PWR Switch"
|
||||
date: "2022-10-14T10:50:56+02:00"
|
||||
author: "$HUMANOID"
|
||||
tags: ["linux", "ssh", "iot"]
|
||||
description: "Hacked together IOT setup with some semblance of security"
|
||||
---
|
||||
|
||||
# Introduction
|
||||
|
||||
As we all know, the "S" in IOT stands for security.
|
||||
We've all seen countless stories of people's networks getting hijacked due to some 3 year old vulnerability in the software stack of their *insert smart device here*.
|
||||
These things barely get any patches at all.
|
||||
On top of that, the most they do is spy on you and send [your footage to the police without asking for your consent](https://edition.cnn.com/2022/07/14/tech/amazon-ring-police-footage/index.html "Load without JS to make the page behave.").
|
||||
|
||||
Still, on the other hand, it can be really convenient to be able to control some of the electronics in your home without having to walk towards them.
|
||||
I personally have a lamp that I frequently forget to turn off when leaving my room.
|
||||
The solution was an [Olimex PWR-switch](https://www.olimex.com/Products/Duino/Shields/PWR-SWITCH/) in combination with a Raspberry Pi 4 that I have running 24/7 regardless (mostly to watch YouTube videos and listen to music over my speaker set; I might write a post about this beautifully hacky contraption at some point in the future).
|
||||
|
||||
# Setup
|
||||
|
||||
Setting it up was relatively easy.
|
||||
|
||||
First I connected a European extension socket to a random 230 volt cable I still had laying around.
|
||||
Then I hooked that up to my PWR-switch and plugged my lamp into that contraption.
|
||||
|
||||
From there I got little bag of jumper wires from my local electronics store and hooked up the PWR-switch to my Raspberry Pi.
|
||||
I hooked up the PWR-switch's positive to `GPIO 15` and it's ground to the preceding `ground` pin.
|
||||
[Here's](https://pinout.xyz) a reference page.
|
||||
|
||||
_Note_, `GPIO 15` is in reference to pin number 10 and the preceding ground pin is pin number 6.
|
||||
|
||||
From there I looked up how to send power to `GPIO 15` in order to have it toggle to the "on" state.
|
||||
The first step to this end is to initialise the pin.
|
||||
This can be done with:
|
||||
```sh
|
||||
echo 15 > /sys/class/gpio/export
|
||||
echo out > /sys/class/gpio/gpio15/direction
|
||||
```
|
||||
Then, sending power to it can be done using something like...
|
||||
```sh
|
||||
echo 1 > /sys/class/gpio/gpio15/value
|
||||
```
|
||||
...and the opposite can be done using:
|
||||
```sh
|
||||
echo 0 > /sys/class/gpio/gpio15/value
|
||||
```
|
||||
|
||||
Deinitialising the pin can be done with:
|
||||
```sh
|
||||
echo 15 > /sys/class/gpio/unexport
|
||||
```
|
||||
To make this all a little easier I have written a wrapper in POSIX Shell:
|
||||
{{< start-details summary="Click to see script" >}}
|
||||
```sh
|
||||
#!/bin/sh
|
||||
|
||||
INIT_PIN(){
|
||||
# the PWR-SWITCH is connected to GPIO 15 and the preceding ground pin
|
||||
# reference page https://pinout.xyz/pinout/
|
||||
[ -d /sys/class/gpio/gpio15 ] && echo "Pin already initialised" && exit 0
|
||||
echo 15 > /sys/class/gpio/export
|
||||
echo out > /sys/class/gpio/gpio15/direction
|
||||
}
|
||||
|
||||
DEINIT_PIN(){
|
||||
[ -d /sys/class/gpio/gpio15 ] || echo "Pin not initialised" && exit 0
|
||||
echo 15 > /sys/class/gpio/unexport
|
||||
}
|
||||
|
||||
PIN_STATUS(){
|
||||
if [ -d /sys/class/gpio/gpio15 ] ; then
|
||||
echo "pin status: Pin initialised"
|
||||
else
|
||||
echo "pin status: Pin not initialised"
|
||||
fi
|
||||
}
|
||||
|
||||
SWITCH_ON(){
|
||||
# initialise pin before doing anything
|
||||
[ -d /sys/class/gpio/gpio15 ] || INIT_PIN
|
||||
echo 1 > /sys/class/gpio/gpio15/value
|
||||
}
|
||||
|
||||
SWITCH_OFF(){
|
||||
# initialise pin before doing anything
|
||||
[ -d /sys/class/gpio/gpio15 ] || INIT_PIN
|
||||
echo 0 > /sys/class/gpio/gpio15/value
|
||||
}
|
||||
|
||||
TOGGLE_SWITCH(){
|
||||
# initialise pin before doing anything
|
||||
[ -d /sys/class/gpio/gpio15 ] || INIT_PIN
|
||||
case $(cat /sys/class/gpio/gpio15/value) in
|
||||
1) SWITCH_OFF;;
|
||||
0) SWITCH_ON;;
|
||||
esac
|
||||
}
|
||||
|
||||
USAGE(){
|
||||
cat >&2 <<EOF
|
||||
Usage: pwr-switch <on|off|init|deinit>
|
||||
on: send on signal
|
||||
off: send off signal
|
||||
init: initialise pin
|
||||
deinit: deinitialise pin
|
||||
status: show pin initialisation state
|
||||
toggle: toggle on/off signal
|
||||
EOF
|
||||
}
|
||||
|
||||
case $1 in
|
||||
on) SWITCH_ON;;
|
||||
off) SWITCH_OFF;;
|
||||
init) INIT_PIN;;
|
||||
deinit) DEINIT_PIN;;
|
||||
status) PIN_STATUS;;
|
||||
toggle) TOGGLE_SWITCH;;
|
||||
*) USAGE;;
|
||||
esac
|
||||
```
|
||||
{{< end-details >}}
|
||||
|
||||
You could probably also do this in Python, but my knowledge of Python is nearly none.
|
||||
|
||||
# Remote interaction
|
||||
|
||||
You can probably make a guess as to how I set up the automation and interaction from other devices.
|
||||
That's right, I used an SSH forced command:
|
||||
```
|
||||
no-pty,command="/usr/local/sbin/pwr-switch toggle" ssh-ed25519 <contents of <lamp-key>.pub>
|
||||
```
|
||||
The unique thing here being that it needs to be executed as the root user in order to mess with the state of the pins.
|
||||
(Or at least in the case of my setup.)
|
||||
So having it be a forced command is a security benefit here as it prevents whatever device you're using to toggle your lamp from having to log in as the root user.
|
||||
All a device with the `<lamp-key>` can do, is toggle the state of `GPIO 15`.
|
||||
|
||||
You could contrivedly call this SIOT...
|
||||
|
||||
Since Android 12, there is the "Device controls" tile in the swipe-down-from-the-top-of-the-screen-menu.
|
||||
Termux can interact with the API that this system provides and allows you to add buttons corresponding to Termux' shortcuts.
|
||||
These shortcuts are executables found in `~/.shortcuts` within the Termux environment.
|
||||
Here I've created a little script that logs into my Raspberry Pi using the `<lamp-key>`, allowing me to toggle my lamp at the tap of a button as long as I'm on my home network.
|
265
src/content/rambles/ssh.md
Normal file
|
@ -0,0 +1,265 @@
|
|||
---
|
||||
title: "SSH Configuration"
|
||||
date: "2022-09-27T11:40:31+02:00"
|
||||
author: "$HUMANOID"
|
||||
tags: ["ssh"]
|
||||
description: "An article on configuring SSH from the ground up to something that can grow out into something like my monster of a config file"
|
||||
toc: true
|
||||
---
|
||||
|
||||
# Introduction
|
||||
|
||||
If you're anything like me, you'll have at least a hand full of servers you'll log into (at least) every now and then using SSH.
|
||||
These servers don't all have the same key.
|
||||
And on top of that, I frequently have multiple keys per host.
|
||||
Managing this by hand is a pain in the arse.
|
||||
Thankfully there are a few tools that make it a breeze.
|
||||
I will be going through what I have done to both my local configuration and what I tend to do with my server configuration.
|
||||
|
||||
# Generating keys
|
||||
|
||||
In order to do anything with SSH in a secure way, you'll need to make use of public and private keys.
|
||||
These keypairs are generated using `ssh-keygen`.
|
||||
I typically generate mine using:
|
||||
```sh
|
||||
ssh-keygen -t ed25519 -f ~/.ssh/<new-key>
|
||||
```
|
||||
Without any parameters, it will generate an rsa3072 key.
|
||||
This form of cryptography isn't recommended as it's become a bit flimsy with computers becoming stronger.
|
||||
Instead I recommend at least adding the `-t ed25519` flag to generate a ed25519 key.
|
||||
|
||||
When prompted for a passphrase, **_always_** give it one.
|
||||
The only situation where _not_ using a passphrase is acceptable is when you are planning on using the key for a [forced command](#forced-commands).
|
||||
|
||||
# Server Configuration
|
||||
|
||||
This is all done under the assumption that the you use the OpenSSH implementation on your server.
|
||||
If you use something like Dropbear, I can't help you as haven't properly dug through it's configuration file (yet).
|
||||
|
||||
The things I see _way_ to often on the internet are...
|
||||
|
||||
* People not disabling password authentication.
|
||||
* People not changing the default port<!--or only allowing a range of IPs to log in-->.
|
||||
* People not disabling root login and never using it.
|
||||
|
||||
So lets go through these steps one by one.
|
||||
|
||||
## Password Authentication
|
||||
|
||||
Password authentication is _the_ most basic thing every server should have disabled.
|
||||
Otherwise, it is possible to brute force a connection into your server.
|
||||
|
||||
> "But my server is not exposed to the internet."
|
||||
|
||||
I guess you _could_ get away with not disabling password authentication, but it's still not a good idea in case, say, your network gets compromised.
|
||||
On top of that, it's less convenient to have to type in a password every time you want to log into your server (more in that in the SSH Agent section).
|
||||
|
||||
In order to disable password authentication, open your SSH daemon configuration file: `/etc/ssh/sshd_config` and look for the following lines...
|
||||
```sshd_config
|
||||
...
|
||||
# To disable tunneled clear text passwords, change to no here!
|
||||
#PasswordAuthentication yes
|
||||
#PermitEmptyPasswords no
|
||||
...
|
||||
```
|
||||
...uncomment `PasswordAuthentication` and replace "yes" for "no".
|
||||
Make sure you still have a way into your server before restarting the daemon.
|
||||
|
||||
If you're not planning on logging in as the root user, uncomment and set the following setting to "no".
|
||||
```sshd_config
|
||||
...
|
||||
#PermitRootLogin prohibit-password
|
||||
...
|
||||
```
|
||||
|
||||
Be aware of the fact that you can still utilise the root account using `sudo su -` (assuming you're using `sudo` on your server, else use whatever other privilege escalation tool you have at hand).
|
||||
|
||||
Restarting the daemon on modern systems is usually done using:
|
||||
```sh
|
||||
systemctl restart sshd
|
||||
```
|
||||
If you're not using systemd, I'm sure you know what command to use instead.
|
||||
|
||||
## Adding keys to `authorized_keys`
|
||||
|
||||
When going through `/etc/ssh/sshd_config` you've probably come across a few lines resembling:
|
||||
```sshd_config
|
||||
...
|
||||
# Expect .ssh/authorized_keys2 to be disregarded by default in future.
|
||||
#AuthorizedKeysFile .ssh/authorized_keys .ssh/authorized_keys2
|
||||
...
|
||||
```
|
||||
This means that the SSH daemon will check in `.ssh/authorized_keys` in the home directory of the user as whom you're trying to log in for authorized keys.
|
||||
So the next step is to append your public key to this file in the home directory of the user as whom you want to be able to log in.
|
||||
This can be done in a few ways.
|
||||
The proper way is by using:
|
||||
|
||||
```sh
|
||||
ssh-copy-id -i ~/.ssh/<key-file> <user>@<host>
|
||||
```
|
||||
I'm usually too lazy to remember there is a proper way and just open the file in `vi` paste and it in there by hand during the same initial login when I'm disabling password authentication.
|
||||
Either way works fine.
|
||||
|
||||
## Changing the port
|
||||
|
||||
The advantage to this is that your logs will be a lot cleaner if your host is exposed to the internet.
|
||||
There are large amounts of bots hammering port `22` on every IP address they can find with common usernames and passwords like "root" and "admin".
|
||||
A solution next to this is to use `fail2ban` along side changing the port.
|
||||
|
||||
> "Won't this mean I have to add the port to my login command every time I go to this server?"
|
||||
|
||||
No, more in this in [the client configuration](#client-configuration) section
|
||||
|
||||
In `/etc/ssh/sshd_config` look for...
|
||||
```sshd_config
|
||||
...
|
||||
#Port 22
|
||||
#AddressFamily any
|
||||
#ListenAddress 0.0.0.0
|
||||
#ListenAddress ::
|
||||
...
|
||||
```
|
||||
...and change the `Port` to your liking, I tend to change this to something like 6969 or some other meme number.
|
||||
|
||||
Another thing I tend to do is not open a port in my firewall, thus preventing any normal outside connections all together.
|
||||
Instead opting to only connect over Yggdrasil and/or Tor.
|
||||
|
||||
# Client configuration
|
||||
|
||||
If people tend not to think much about their server configuration, their client configuration is probably not even touched at all.
|
||||
|
||||
## The `~/.ssh/config` file
|
||||
|
||||
The very first thing I do after setting up a server, is add an entry to my `~/.ssh/config` in order to manage its key, the user, the port and possibly subdomain should the need arise.
|
||||
|
||||
A basic configuration section looks like the following:
|
||||
```ssh_config
|
||||
Host <host> # this is something you can easily identify
|
||||
Host <hostname> # this does need to be an IP address or DNS record pointing to an IP address
|
||||
IdentityFile ~/.ssh/<key-file>
|
||||
User <user-name>
|
||||
Port <meme-number>
|
||||
```
|
||||
This allows you to log into host `<host>` with on port `<meme-number>` with key `~/.ssh/<key-file>` as user `<user-name>` without by typing:
|
||||
```sh
|
||||
ssh <user-name>@<hostname> -p <meme-number> -i ~/.ssh/<key-file>
|
||||
```
|
||||
Instead the following command will work:
|
||||
```sh
|
||||
ssh <host>
|
||||
```
|
||||
|
||||
More information on the SSH config file can be found in the `ssh_config` manpage.
|
||||
|
||||
Some other frequently used settings for me are:
|
||||
* `AddKeysToAgent`
|
||||
* `IdentitiesOnly`
|
||||
* `ProxyCommand`
|
||||
|
||||
### `AddKeysToAgent`
|
||||
|
||||
Automatically adds the key to your SSH agent if you have one running.
|
||||
|
||||
This is useful if you frequently log in and out of a certain host and don't want to take the time to add it's key to your agent manually.
|
||||
|
||||
### `IdentitiesOnly`
|
||||
|
||||
Ignore whatever keys your agent has and only use the contents of `IdentityFile`.
|
||||
|
||||
Useful for when you want to be able to log into the same host using multiple keys while using an SSH agent session.
|
||||
|
||||
### `ProxyCommand`
|
||||
|
||||
Always connect to your host using a proxy, using a given command.
|
||||
|
||||
Useful for when you can only access a host through a certain proxy.
|
||||
|
||||
I use this for my Tor hosts:
|
||||
|
||||
```ssh_config
|
||||
Host tor-<host>
|
||||
Hostname <lengthy-56-character-string>.onion
|
||||
# this is dependent on the netcat implementation of the OpenBSD project, often packaged as "netcat-openbsd"
|
||||
ProxyCommand nc -X 5 -x localhost:9050 %h %p # this assumes you are running a tor proxy on your local system and attempts to make a connection through that
|
||||
Identityfile ~/.ssh/<key-file>
|
||||
User <user>
|
||||
```
|
||||
|
||||
# The SSH Agent
|
||||
|
||||
If you're using SSH keys with passphrases, it will very quickly get annoying to type in the passphrase every time you use a certain key.
|
||||
To alleviate this tedium, the SSH agent exists.
|
||||
|
||||
If you're using a full desktop environment, chances are that you already have an SSH agent running in the background.
|
||||
You can check this by seeing if `$SSH_AGENT_PID` is set to anything.
|
||||
```sh
|
||||
echo $SSH_AGENT_PID
|
||||
```
|
||||
|
||||
If this isn't set to anything, you can start an agent session by running:
|
||||
```sh
|
||||
eval $(ssh-agent)
|
||||
```
|
||||
|
||||
Now you can add keys to your agent with:
|
||||
```sh
|
||||
ssh-add </path/to/key-file>
|
||||
```
|
||||
|
||||
You can also have it automatically drop keys after a specified amount of time with the `-t` flag.
|
||||
I tend to do this with my root keys as a security precaution.
|
||||
|
||||
```sh
|
||||
ssh-add -t 1h ~/.ssh/<root-key>
|
||||
```
|
||||
|
||||
Starting an SSH agent every time you open a new shell session gets quite annoying quite quickly.
|
||||
There are a few things you can automate this.
|
||||
The simplest is to add `eval $(ssh-agent)` to your `~/.profile`.
|
||||
Another option, the one I prefer, is to use [keychain](https://www.funtoo.org/Funtoo:Keychain) from the Funtoo project.
|
||||
It checks whether there's an agent running every time you start a new login session.
|
||||
If there is, it sets the SSH agent environment variables to the existing ones from some other session.
|
||||
If there isn't a running SSH session, it will start one.
|
||||
|
||||
I have the following in my `~/.profile`:
|
||||
```sh
|
||||
...
|
||||
eval $(keychain --agents 'gpg,ssh' --eval)
|
||||
...
|
||||
```
|
||||
As you can see, it can also keep track of your GPG agent.
|
||||
|
||||
# Forced Commands
|
||||
|
||||
Another amazing feature of SSH is forced commands.
|
||||
The name is relatively self explanatory.
|
||||
You give a public key the privilege to execute a single command and nothing else.
|
||||
This is really useful if there is something that you frequently do on a server which is simple enough that it can be automated but still requires interaction from device.
|
||||
|
||||
I make use of a forced command to open NCMPCPP on my MPD server.
|
||||
Logging in every time and typing `ncmpcpp` every time I wanted to add or remove some songs from the current playlist got annoying really fast.
|
||||
|
||||
On my client machines I have an entry in my `~/.ssh/config` with roughly the following:
|
||||
```ssh_config
|
||||
Host <host>-radio
|
||||
Hostname <hostname>
|
||||
User <mpd-user>
|
||||
Port <meme-number>
|
||||
Identityfile ~/.ssh/<host>-radio
|
||||
IdentitiesOnly yes # here's where forcing it to ignore the ssh agent comes in useful
|
||||
```
|
||||
On my MPD server, I have the following in `~/.ssh/authorized_keys`:
|
||||
```authorized_keys
|
||||
command="ncmpcpp" ssh-ed25519 <contents of <host>-radio.pub>
|
||||
```
|
||||
|
||||
When I `ssh` to `<host>-radio`, all I get is an NCMPCPP session.
|
||||
|
||||
On one of my Raspberry Pis, I make use of a forced command to toggle my desk lamp using a little wrapper I wrote around the Pi's GPIO interface.
|
||||
It has the following line in `~/.ssh/authorized_keys`:
|
||||
```authorized_keys
|
||||
no-pty,command="/path/to/script/lamp toggle" ssh-ed25519 <contents of lamp-key.pub>
|
||||
```
|
||||
The `no-pty` option prevents any sessions opened with this key from starting an interactive shell session.
|
||||
|
||||
Documentation of the `authorized_keys` file can be found in the `sshd` manpage under the `AUTHORIZED_KEYS FILE FORMAT` section.
|
104
src/content/rambles/vim.md
Normal file
|
@ -0,0 +1,104 @@
|
|||
---
|
||||
title: Vim
|
||||
date: 2022-03-21T01:17:06+02:00
|
||||
author: $HUMANOID
|
||||
tags: ["linux", "vim"]
|
||||
---
|
||||
|
||||
# Vim Cheat sheet
|
||||
|
||||
This is a little Vim cheat sheet with thing people generally don't seem to be particularly familiar with when it comes to Vim.
|
||||
This is about Vim, not NeoVim, but I most things with still apply to NeoVim.
|
||||
|
||||
## Self-explanatory things
|
||||
These are a few bindings that I don't feel require an extensive explanation.
|
||||
|
||||
| binding | action |
|
||||
| --- | --- |
|
||||
| Z Z | write and quit; equivalent to `:wq` |
|
||||
| Z Q | quit; equivalent to `:q!` |
|
||||
| :x | write and quit; equivalent to `:wq` |
|
||||
| g u | swap characters to lower case |
|
||||
| g u u | swap characters to lower case on current line |
|
||||
| g U | swap characters to upper case |
|
||||
| g U U | swap characters to upper case on current line |
|
||||
| g {j,k} | if the current line you're on spans more then the width of your screen, go down to the portion that's past the linebreak |
|
||||
| ~ | toggle case |
|
||||
| z g | add word to current dictionary |
|
||||
| [ s | highlight previous misspelled word |
|
||||
| ] s | highlight next misspelled word |
|
||||
| z = | open spelling suggestions based on current dictionary and highlighted word |
|
||||
| . | repeat last modify operation |
|
||||
| ; | repeat last move operation |
|
||||
| ! | start command with `:<selection>!` |
|
||||
|
||||
## Splits and Terminals
|
||||
You can split your vim window with somewhat Emacs-like chords by default; I don't bother changing them because I use with quite a few different computers, not all of which have my Vim dotfiles installed.
|
||||
|
||||
| binding | action |
|
||||
| --- | --- |
|
||||
| C-w s | Horizontal split |
|
||||
| C-w v | Vertical split |
|
||||
| C-w {h,j,k,l} | Move focus to split {left,down,up,right} |
|
||||
| C-w S-{h,j,k,l} | Move split to {left,down,up,right} |
|
||||
|
||||
Another usefull thing I don't see a lot of people do is open a terminal session in vim.
|
||||
This can be really quite useful if you quickly need to change or check something.
|
||||
You do this with the `:terminal` command.
|
||||
In Vim, this will horizontally split your current window and open a terminal session in the top half.
|
||||
(Yes, you can also open Vim sessions in these terminal sessions if you feel like it).
|
||||
You can navigate to and from it just like any other splits.
|
||||
In NeoVim terminals are handled significantly worse and is the main reason I went back to base Vim.
|
||||
|
||||
## Tips
|
||||
|
||||
Something I came across some time ago is that you can interpet the current line with whatever you want using
|
||||
|
||||
```
|
||||
:.!<interpeter>
|
||||
```
|
||||
|
||||
So for instance running...
|
||||
|
||||
```
|
||||
:.!bash
|
||||
```
|
||||
|
||||
...while highlighting the line...
|
||||
|
||||
``` bash
|
||||
echo Line{1..5} | sed 's/ /\n/g'
|
||||
```
|
||||
|
||||
...will result in:
|
||||
|
||||
```
|
||||
Line1
|
||||
Line2
|
||||
Line3
|
||||
Line4
|
||||
Line5
|
||||
```
|
||||
|
||||
# Configuration
|
||||
|
||||
Some settings I like putting in my git configuration files.
|
||||
```vimrc
|
||||
set nocompatible
|
||||
set termguicolors
|
||||
set cursorline
|
||||
set hlsearch
|
||||
set ignorecase
|
||||
set smartcase
|
||||
set number
|
||||
set relativenumber
|
||||
set wildmenu
|
||||
set mouse=
|
||||
set scrolloff=3
|
||||
set listchars=tab:│\ ,lead:·,trail:·,eol:¬ "'lead' is a NeoVim specific option
|
||||
set list
|
||||
|
||||
" I haven't tried if this works in base vim.
|
||||
match ExtraWhitespace /\s\+$/
|
||||
highlight ExtraWhitespace guifg=red
|
||||
```
|
144
src/content/rambles/windowmanagers.md
Normal file
|
@ -0,0 +1,144 @@
|
|||
---
|
||||
title: "On window managers and XMonad"
|
||||
date: "2022-11-03T23:17:35+01:00"
|
||||
author: "$HUMANOID"
|
||||
tags: ["linux", "window managers", "xmonad"]
|
||||
description: "A ramble about, among other things, a highly configurable window manager"
|
||||
---
|
||||
|
||||
# My journey into Tiling Window Managers
|
||||
|
||||
When I started my Linux journey, I stuck with GNOME 3 something for around the
|
||||
first year. Sure, I tried KDE and Cinnamon and XFCE, but GNOME is the one I
|
||||
always kept coming back to. I think it's because it's pretty much the only one
|
||||
that felt completely different from what the rest of the world was doing. It
|
||||
helped me with thinking about Linux as being different from windows. After this
|
||||
first year though, I came across a few videos about tiling window managers and
|
||||
wanted to try one. The first one I installed was [i3](https://i3wm.org/).
|
||||
|
||||
I hated it.
|
||||
|
||||
I had a hard time configuring it as I didn't really know what I was doing.
|
||||
Other than that, I found it plain awkward to use. The way it tiles windows --
|
||||
and how you _still_ have to babysit pretty much every last one of them --
|
||||
drives me away from i3 to this day. I'm sure it has the potential to be a great
|
||||
window manager. It's just _really_ not for me. Which is a shame, I would really
|
||||
like to properly live in Wayland with [Sway](https://swaywm.org/ "Sway is like
|
||||
i3 in Wayland") for at least a little while.
|
||||
|
||||
After a few hours of trying to get things to work in i3, I went back to GNOME.
|
||||
Some time later, I came across [AwesomeWM](https://awesomewm.org/). It was
|
||||
being recommended as a fairly easy window manager to start with -- it having
|
||||
window decorations and it's own menu system. The first thing I did was try-and
|
||||
fail to rip out said menu system and window decorations. I was putting too much
|
||||
on myself trying to learn the basics of both using a tiling window manager and
|
||||
Lua.
|
||||
|
||||
Some time after that, I came across
|
||||
[BSPWM](https://github.com/baskerville/bspwm). This was the first time where I
|
||||
felt like I _really_ managed to get a tiling window manger to do what I wanted
|
||||
it to do. For some odd reason, there are people out there who consider BSPWM a
|
||||
more "advanced" window manger. I really don't get why. To this day, I am of the
|
||||
opinion that SXHKD's configuration syntax is some of the best out there. I
|
||||
think it took me around 10 minutes to wrap my head around the basics. Somewhere
|
||||
around an afternoon later, I had a config that served me well for the next few
|
||||
months to come.
|
||||
|
||||
A while after BSPWM, I decided to give suckless'
|
||||
[DWM](https://dwm.suckless.org/) a shot. Despite my lack of knowledge of C,
|
||||
this very quickly became my favorite window manager at the time. There is just
|
||||
something about the insanity of using diff files to configure your piece of
|
||||
software when perfectly functional configuration libraries and languages exist
|
||||
that got it's hooks in me. I also caught the minimalism bug around this time,
|
||||
so DWM's nearly non-existent memory footprint was also great. Despite this
|
||||
really being the first really "advanced" window manager, I had an easier time
|
||||
configuring it than AwesomeWM or i3. It was also the first time where I could
|
||||
appreciate the master-stack layout properly and not having to think about
|
||||
keeping track of windows in two dimensions anymore. It made me realise that I
|
||||
want to have to think as little as possible about window positioning. It was
|
||||
the reason I couldn't deal with i3's paradigm and shifted away from BSPWM the
|
||||
moment I found DWM.
|
||||
|
||||
I ran DWM as my main window manger for over a year before having issues with
|
||||
some fullscreen applications and the JetBrains suite, which I had to use for
|
||||
college activities.
|
||||
|
||||
{{< img link="https://xkcd.com/1806/" src="/images/xkcd/borrow_your_laptop.png" >}}
|
||||
|
||||
I decided to give AwesomeWM another shot after having figured out what I want
|
||||
from a tiling window manger. This time I managed to get something that worked
|
||||
pretty much exactly how I wanted it to. In other words, a fairly basic
|
||||
configuration with most of the default features ripped out and instead my
|
||||
partially-organically-grown-probably-batshit-insane-keybindings (I use `Super` +
|
||||
`Space` to open my run launcher. In fact, on my keyboard I have holding the big
|
||||
space bar (yes it has two space bars, its layout is about as insane as my
|
||||
window management keybindings) bound to `Super` + `Space`).
|
||||
|
||||
To this day I still use DWM quite frequently on machines where I don't really
|
||||
want to think about what graphical interface to chuck on it (hence I half
|
||||
arsedly maintain an Alpine package of my fork).
|
||||
|
||||
For quite a long time I used DWM and AwesomeWM depending on whether I was
|
||||
planning on frequently using fullscreen applications and how strong the machine
|
||||
in question was; AwesomeWM being noticeably slower than DWM on _really_ old
|
||||
machines (like RejuvinatedBrick). Until at some point, I came across
|
||||
[XMonad](https://xmonad.org/).
|
||||
|
||||
I tried it for an evening.
|
||||
|
||||
I hated the fact that it's configured in _sodding Haskell_ and went back to DWM.
|
||||
|
||||
A few months later, I came across an implementation of chorded keys through
|
||||
the `XMonad.Util.EZConfig` module and decided to give it another shot.
|
||||
|
||||
This time I was hooked.
|
||||
|
||||
The biggest problem I had with it was _still_ the fact that it was configured
|
||||
in Haskell, but the absurd level of customisability made it worth dealing with
|
||||
the functional pain. It was also the first time I decided to not bother with a
|
||||
status bar as getting a basic configuration going had given me enough grief for
|
||||
one month.
|
||||
|
||||
As for gripes I had (and partially still have) with XMonad, they were quite few,
|
||||
surprisingly. Coming from AwesomeWM and DWM, I would've liked XMonad to use tags
|
||||
instead of workspaces; fullscreen is a bit of a pain to get working, but has
|
||||
less fuckups than DWM; the fact that it's configured in Haskell; it not having a
|
||||
set of workspaces per monitor (at least by default, I kind of stopped caring
|
||||
after a little while); the fact that it's configured in Haskell; the
|
||||
`XMonad.Layout.ShowWName` module being kind of unstable and last but not least,
|
||||
the fact that it's configured in _fucking Haskell_.
|
||||
|
||||
It almost seems like I got sick of being sick of it being configured in Haskell
|
||||
and I decided to dive into learning the language with the goal of being able to
|
||||
fully understand my monstrous 384 line config file (586 lines including the
|
||||
documentation in commented sections).
|
||||
|
||||
{{< img src="/images/config_length.png" >}}
|
||||
|
||||
I don't fully understand it yet at the time of writing, but I do understand it
|
||||
a hell of a lot better than when I produced most of those 300 lines.
|
||||
|
||||
The greatest thing about XMonad is simultaneously the thing that kept me away
|
||||
from it: it's written and configured in bloody Haskell. Thus there is barely a
|
||||
separation between configuration and source code. The only difference there
|
||||
_really_ is, is the filename. Once you understand a bit of Haskell (no small
|
||||
task if you're used to imperative languages) adding and integrating your own
|
||||
features is really easy. And then they are _properly_ integrated. Sort of like
|
||||
how DWM works, only with proper documentation and support. Part of me wants to
|
||||
see if I can figure out a way to package my XMonad build as a single binary to
|
||||
be able to chuck it onto systems without putting much thought into it.
|
||||
|
||||
Weird thing with Haskell I'm noticing so far is that I'm slowly but surely
|
||||
managing to dig up old concepts that I tried to implement in imperative
|
||||
languages, but couldn't due to their nature. Things like pattern matching and
|
||||
maps are quite intuitive to me. It's just that Haskell's syntax takes a _lot_ of
|
||||
getting used to. In the mean time, I guess that my experience with POSIX Shell
|
||||
bridged the gap between my knowledge of Go and C#; and Haskell.
|
||||
|
||||
I highly recommend giving XMonad a shot if you're willing to bash your head
|
||||
against the Haskell wall for a while before understanding how your window
|
||||
manager works.
|
||||
|
||||
Looking back I find it quite funny to see how I went from not being able to get
|
||||
i3 to do what I wanted it too and giving up; to barely being able to write or
|
||||
understand Haskell but putting up with it.
|
91
src/content/rambles/xmonad-bits.md
Normal file
|
@ -0,0 +1,91 @@
|
|||
---
|
||||
title: "Xmonad Configuration Bits"
|
||||
date: "2023-09-13T16:47:48+02:00"
|
||||
author: "$HUMANOID"
|
||||
tags: ["xmonad", "haskell"]
|
||||
description: "A few interesting things I've done with my XMonad config"
|
||||
---
|
||||
|
||||
So the other day I was hacking some more on my XMonad config and came across a
|
||||
few quirks that I don't think I've seen other people implement in their configs.
|
||||
Though truth be told, I haven't really looked at a lot of other people's config
|
||||
files.
|
||||
|
||||
> "Where is your config file anyway?"
|
||||
|
||||
Haven't published it yet as it's still a huge mess of half baked Haskell
|
||||
snippets.
|
||||
|
||||
## Complex terminal sessions
|
||||
|
||||
I use [Kitty](https://sw.kovidgoyal.net/kitty/) as my terminal. It's amazing and
|
||||
probably warrants its own article one day. One of the really cool features is
|
||||
that it allows you to override options on launch with the `-o` or `--override`
|
||||
parameter. This allows you to change any of the values in your `kitty.conf`,
|
||||
this includes the font and colour pallet.
|
||||
|
||||
I've been using this to start terminals with
|
||||
[BQN](https://mlochbaum.github.io/BQN/) compatible font.
|
||||
|
||||
```sh
|
||||
kitty -o font_size=17 -o font_family='BQN386 Unicode' -e bqn
|
||||
```
|
||||
|
||||
This works great and I assigned it to a keybinding for a while. However, some
|
||||
time later I decided to check out [R](https://www.r-project.org/) and wanted to
|
||||
have a similar keybinding with slightly different options. Here it dawned on me
|
||||
that it would be silly to copy-paste the BQN keybinding and change a few things,
|
||||
so I abstracted that away into a function that takes a list of options and feeds
|
||||
that to Kitty.
|
||||
|
||||
```haskell
|
||||
runInKittyWithOpts :: [String] -> String -> X ()
|
||||
runInKittyWithOpts opts executable = spawn $ "kitty "
|
||||
++ (unwords . map ("-o " ++)) opts
|
||||
++ " -e " ++ executable
|
||||
```
|
||||
|
||||
If I now want to start a kitty session with a BQN font and R repl, I can add
|
||||
something like the following to a keybinding:
|
||||
```haskell
|
||||
runInKittyWithOpts rOptions "R"
|
||||
where rOptions = [ "font_size=17"
|
||||
, "font_family='BQN386 Unicode'"
|
||||
]
|
||||
```
|
||||
|
||||
Since I write quite a bit, I also have a kitty session with a giant margin left
|
||||
and right of the "terminal" section of the window as that empty space helps me
|
||||
concentrate. All this is, is the previous function without the `-e` parameter:
|
||||
```haskell
|
||||
kittyWithOpts :: [String] -> X ()
|
||||
kittyWithOpts opts = spawn $ "kitty "
|
||||
++ (unwords . map ("-o " ++)) opts
|
||||
```
|
||||
Though I didn't let that stop me from golfing it down to a point free version:
|
||||
```haskell
|
||||
kittyWithOpts :: [String] -> X ()
|
||||
kittyWithOpts = spawn . ("kitty " ++) . (unwords . map ("-o " ++))
|
||||
```
|
||||
My prefered settings for a "text hole" terminal.
|
||||
```haskell
|
||||
kittyWithOpts [ "font_family='BQN386 Unicode'"
|
||||
, "font_size=14"
|
||||
, "single_window_margin_width='5 200'"
|
||||
]
|
||||
```
|
||||
|
||||
If I were to implement this properly, I would make a kitty module with all
|
||||
options in DataType. But alas, I'm not patient/bored enough for that... yet.
|
||||
|
||||
## Mouse bindings
|
||||
|
||||
I thought it would be neat to be able to scroll up and down while holding my
|
||||
modifier key to change my screen brightness and audio levels. The basic concept
|
||||
here was quite simple; us a lambda function that chucks whatever data it gets
|
||||
onto the ground because the type signature for mousebindings forces you to deal
|
||||
with a focussed window.
|
||||
|
||||
```haskell
|
||||
, ((modMask, button4), \_ -> safeSpawn "pulsemixer" ["--change-volume","+1"])
|
||||
```
|
308
src/content/rambles/xmonad-prompts.md
Normal file
|
@ -0,0 +1,308 @@
|
|||
---
|
||||
title: "XMonad Promtps"
|
||||
date: "2023-03-08T14:20:11+01:00"
|
||||
author: "$HUMANOID"
|
||||
tags: ["haskell", "xmonad", "linux"]
|
||||
description: "There aren't a lot of instructions or explanations on creating XMonad prompts, or at least not within a minute of checking my searx instance. This is my attempt at filling that gap."
|
||||
toc: true
|
||||
---
|
||||
# Introduction
|
||||
|
||||
XMonad has it's own prompt system. Some time ago, I wanted to see if it could
|
||||
replace dmenu entirely. I managed it for the more common usages I had for it. My
|
||||
application launcher, `ssh` prompt and pass interface were easy to replace using
|
||||
standard XMonad Contrib modules (`XMonad.Prompt.Shell`, `XMonad.Prompt.Ssh` and
|
||||
`XMonad.Prompt.Pass` respectively). However, things became more difficult when
|
||||
it came to my universal/external Qutebrowser bookmarks menu and
|
||||
`yt-dlp`-and-`pipe-viewer` wrapper.
|
||||
|
||||
This tutorial-of-sorts will assume _some_ Haskell knowledge or not being afraid
|
||||
of diving straight into how Haskell works. I'm not going into great detail on
|
||||
how everything works here.
|
||||
|
||||
# Bookmarks menu
|
||||
|
||||
The first one I decided to tackle was the bookmarks menu, as it is by far the
|
||||
simplest of the two.
|
||||
|
||||
Let's take a look at the original:
|
||||
|
||||
```sh
|
||||
#!/bin/sh
|
||||
bookmarks="$HOME/.config/qutebrowser/bookmarks/urls"
|
||||
choice="$(awk '{print$1}' $bookmarks | sort | dmenu -p "Bookmark:" -l 30)"
|
||||
[ -z $choice ] || qutebrowser "$choice"
|
||||
```
|
||||
|
||||
Things get interesting at the initialisation of the `choice` variable:
|
||||
|
||||
1. It takes the contents of Qutebrowser's bookmarks file
|
||||
2. It sorts the results of that
|
||||
3. Sends that to `dmenu`, prompting the user to make a choice
|
||||
|
||||
|
||||
After this, it checks whether `choice` is empty or not and in case it isn't,
|
||||
opens Qutebrowser with its contents.
|
||||
|
||||
Here is an example of how Qutebrowser saves its bookmarks:
|
||||
```
|
||||
https://www.alpinelinux.org/ index | Alpine Linux
|
||||
https://www.openbsd.org/ftp.html OpenBSD: Mirrors
|
||||
https://commonmark.org/ CommonMark
|
||||
https://xxiivv.com/ Echorridoors
|
||||
https://100r.co/site/home.html 100R — home
|
||||
https://solar.lowtechmagazine.com/about.html About this website | LOW←TECH MAGAZINE
|
||||
```
|
||||
|
||||
## Implementation
|
||||
|
||||
Its functionality does boils down to the following:
|
||||
|
||||
1. Parse a given file according to a set of rules, returning it's contents in
|
||||
the form of a list
|
||||
2. Allow the user to make a choice from that list
|
||||
3. Launch an application with that choice as parameter
|
||||
|
||||
Seems easy enough to implement.
|
||||
|
||||
### Parsing the Bookmarks file
|
||||
|
||||
Let's start off by creating a function that can parse our bookmarks file. Here we
|
||||
need something to read a file -- in this case a bookmarks file -- and return its
|
||||
contents in the form of a list of strings.
|
||||
|
||||
```haskell
|
||||
fileContentList :: FilePath -> IO [String]
|
||||
```
|
||||
|
||||
This function takes a filepath -- the `Filepath` datatype is an alias for
|
||||
`String` -- and returns `IO [String]`.
|
||||
|
||||
Now for the body of the function:
|
||||
|
||||
```haskell
|
||||
fileContentList :: FilePath -> IO [String]
|
||||
fileContentList f = do
|
||||
homeDir <- getEnv "HOME"
|
||||
file <- readFile (homeDir ++ "/" ++ f)
|
||||
return . uniqSort . lines $ file
|
||||
```
|
||||
|
||||
Let's go over what is happening here line by line.
|
||||
|
||||
`fileContentList` is a function that takes an argument `f`; then it starts a
|
||||
`do` block. `do` blocks are used to put multiple functions in sequence in the
|
||||
scope of a single function without having them interact with eachother.
|
||||
|
||||
Within the `do` block, it first retrieves the current home directory based on
|
||||
the `$HOME` environment variable and binds it to `homeDir` using the `getEnv`
|
||||
function from the `System.Environment` module. `getEnv` returns a string with
|
||||
the contents of the variable given as its argument.
|
||||
|
||||
Next, it retrieves the file contents from `$HOME/path/to/file` using the
|
||||
`readFile`. This path is created by appending `f` to the `homeDir`.
|
||||
|
||||
Now for the final line.
|
||||
|
||||
First it takes the `file` and splits it up into a list of strings based on
|
||||
newlines using the `lines` function.
|
||||
|
||||
```haskell
|
||||
lines $ file
|
||||
```
|
||||
|
||||
Then it pipes the result from that into `uniqSort` from the `XMonad.Prompt`
|
||||
module in order to -- as the name implies -- sort it and get rid of any
|
||||
duplicate items.
|
||||
|
||||
|
||||
```haskell
|
||||
uniqSort . lines $ file
|
||||
```
|
||||
|
||||
And the output of that is piped into `return`:
|
||||
```haskell
|
||||
return . uniqSort . lines $ file
|
||||
```
|
||||
|
||||
This function will allow us to parse any given text file. To parse the
|
||||
Qutebrowser bookmarks file, call it using `.config/qutebrowser/bookmarks/url`
|
||||
|
||||
> _Note_: I say "pipe" because the '`.`' function behaves quite similar to
|
||||
> pipes in POSIX Shell. However, the correct way of referring to what it does
|
||||
> is composition; it takes two functions and passes the output of the first
|
||||
> function to the second, thereby creating -- or composing a new function. As
|
||||
> apposed to how pipes in POSIX Shell work, function composition chains are
|
||||
> executed from right to left.
|
||||
|
||||
### Creating a Prompt
|
||||
|
||||
Let's see if there is anything in the
|
||||
[`XMonad.Prompt`](https://hackage.haskell.org/package/xmonad-contrib-0.17.1/docs/XMonad-Prompt.html)
|
||||
module that looks like it could help us in creating a prompt.
|
||||
|
||||
|
||||
> ```haskell
|
||||
> mkXPrompt :: XPrompt p => p -> XPConfig -> ComplFunction -> (String -> X ()) -> X ()
|
||||
> ```
|
||||
> ---
|
||||
> Creates a prompt given:
|
||||
> - a prompt type, instance of the `XPrompt` class.
|
||||
> - a prompt configuration (`def` can be used as a starting point)
|
||||
> - a completion function (`mkComplFunFromList` can be used to create a completions function given a list of possible completions)
|
||||
> - an action to be run: the action must take a string and return `X ()`
|
||||
|
||||
This looks like it could serve as the basis for our prompt. The description and
|
||||
type signature tell us that it is going to require an instance of the `XPrompt`
|
||||
typeclass. So let's create a `Bookmark` datatype and implement the `showXPrompt`
|
||||
function from `XPrompt` in order to give it a default message when executed and
|
||||
thereby having it derive from `XPrompt`.
|
||||
|
||||
```haskell
|
||||
data Bookmark = Bookmark
|
||||
|
||||
instance XPrompt Bookmark where
|
||||
showXPrompt Bookmark = "Bookmark: "
|
||||
```
|
||||
As its second argument, `mkXPrompt` requires an instance of `XPConfig`. The
|
||||
`XPConfig` typeclass is where you -- as the name implies -- specify the
|
||||
configuration of XMonad's prompts. Knowing this we can start to write function that
|
||||
uses `mkXPrompt`:
|
||||
|
||||
```haskell
|
||||
bookmarkPrompt c = do
|
||||
mkXPrompt Bookmark c
|
||||
```
|
||||
`c` is our `XPConfig` argument.
|
||||
|
||||
This takes care of the `XPrompt p => p -> XPConfig` portion of the function.
|
||||
|
||||
Now for the completion function, that will handle the list given to our prompt.
|
||||
Let's mostly follow the suggestion in the description of `mkXPrompt` and lets
|
||||
take a look at:
|
||||
|
||||
> ```haskell
|
||||
> mkComplFunFromList' :: XPConfig -> [String] -> String -> IO [String]
|
||||
> ```
|
||||
> ---
|
||||
> This function takes a list of possible completions and returns a completions
|
||||
> function to be used with mkXPrompt. If the string is null it will return all
|
||||
> completions.
|
||||
|
||||
This is how Qutebrowser and `dmenu` act by default with a given list of possible
|
||||
options.
|
||||
|
||||
```haskell
|
||||
bookmarksFile = ".config/qutebrowser/bookmarks/urls" :: String
|
||||
```
|
||||
> I didn't know where to put this, but I created a string to hold the path to my
|
||||
> bookmarks
|
||||
|
||||
So it takes an instance of `XPConfig` -- that will again be our `c` argument,
|
||||
and a list of strings. Here is where we feed it the contents of our file using
|
||||
our `fileContentList` function. We will do this by binding the output to, say
|
||||
`bl` for "bookmark list" with `<-`. Since `fileContentList` is a member of the
|
||||
`IO` monad and we're working in, we have to call it using the `io` function,
|
||||
which is an alias for the `liftIO` function.
|
||||
|
||||
```haskell
|
||||
bookmarkPrompt :: XPConfig -> (String -> X ()) -> X ()
|
||||
bookmarkPrompt c f = do
|
||||
bl <- io fileContentList bookmarksFile
|
||||
mkXPrompt Bookmark c (mkComplFunFromList' c bl) f
|
||||
```
|
||||
|
||||
You'll see that I've also added argument `f`, this is the function we're going
|
||||
to use to actually do something with our prompt output. Considering we're
|
||||
working with bookmarks, opening them in a browser would make sense.
|
||||
|
||||
```haskell
|
||||
openBookmark :: String -> X ()
|
||||
openBookmark bookmark = do
|
||||
browser <- io getBrowser
|
||||
spawn $ browser ++ " '" ++ getUrl bookmark ++ "'"
|
||||
where getUrl = head . words
|
||||
```
|
||||
`openBookmark` is a function that takes a string and returns something in the
|
||||
context of the `X` monad (hence the name "XMonad", it's a monad that interacts
|
||||
with Xorg). Let's go through it line by line.
|
||||
|
||||
```haskell
|
||||
browser <- io getBrowser
|
||||
```
|
||||
First we get user's browser using the `getBrowser` function from the
|
||||
`XMonad.Prompt.Shell` module and bind that to `browser`.
|
||||
|
||||
This function checks the `$BROWSER` environment variable and if it isn't set, it
|
||||
defaults to "firefox".
|
||||
|
||||
```haskell
|
||||
spawn $ browser ++ " '" ++ getUrl bookmark ++ "'"
|
||||
```
|
||||
Since `getBrowser` returns a string, we can append things to it and feed that to
|
||||
`spawn`. In this case, we get the URL portion of the bookmark entry surrounded by
|
||||
single quotes in case a given bookmark contains any symbols that mess up our
|
||||
shell. After all, what `spawn` ultimately does is feed a given string to
|
||||
`/bin/sh` as a command to execute.
|
||||
|
||||
```haskell
|
||||
where getUrl = head . words
|
||||
```
|
||||
For get `getUrl`, we take the given string, split it into a list of strings
|
||||
based on space characters, pipe that into head, thus retrieving the first item.
|
||||
|
||||
## Keybinding
|
||||
|
||||
We now have a set of functions that create a prompt populated with our
|
||||
Qutebrowser bookmarks file (any other list of URLs will also work) which will
|
||||
open our browser when choosing one.
|
||||
|
||||
Now all we have to do is bind it to a key. Personally I use the
|
||||
`XMonad.Util.EZConfig` so I have the following in my keybindings:
|
||||
|
||||
```haskell
|
||||
, ("M-M1-C-b", bookmarkPrompt (myXPConfig {autoComplete = Just 200000}) openBookmark Bookmark)
|
||||
```
|
||||
If you use the default way of defining keybindings you can use something like
|
||||
the following:
|
||||
|
||||
```haskell
|
||||
, ((modm .|. controlMask, xK_b), bookmarkPrompt def openBookmark)
|
||||
|
||||
```
|
||||
`def` is a reference to the default implementation of `XPConfig`.
|
||||
|
||||
# Everything Together
|
||||
|
||||
Everything put together, your config should have something like the following
|
||||
added.
|
||||
|
||||
```haskell
|
||||
data Bookmark = Bookmark
|
||||
|
||||
instance XPrompt Bookmark where
|
||||
showXPrompt Bookmark = "Bookmark: "
|
||||
|
||||
bookmarksFile = ".config/qutebrowser/bookmarks/urls" :: String
|
||||
|
||||
fileContentList :: FilePath -> IO [String]
|
||||
fileContentList f = do
|
||||
homeDir <- getEnv "HOME"
|
||||
file <- readFile (homeDir ++ "/" ++ f)
|
||||
return . uniqSort . lines $ file
|
||||
|
||||
bookmarkPrompt :: XPConfig -> (String -> X ()) -> X ()
|
||||
bookmarkPrompt c f = do
|
||||
bl <- io fileContentList bookmarksFile
|
||||
mkXPrompt Bookmark c (mkComplFunFromList' c bl) f
|
||||
|
||||
openBookmark :: String -> X ()
|
||||
openBookmark bookmark = do
|
||||
browser <- io getBrowser
|
||||
spawn $ browser ++ " '" ++ getUrl bookmark ++ "'"
|
||||
where getUrl = head . words
|
||||
|
||||
-- ... keybindings
|
||||
, ((modm .|. controlMask, xK_b), bookmarkPrompt def openBookmark)
|
||||
-- more keybindings ...
|
||||
```
|
26
src/content/services.md
Normal file
|
@ -0,0 +1,26 @@
|
|||
---
|
||||
title: Services
|
||||
---
|
||||
|
||||
These are a few serices I provide to the people who may be interested in them.
|
||||
|
||||
# [SearXNG instance](/searx)
|
||||
|
||||
Since DuckDuckGo decided to censor content a few weeks ago at time of writing, the thought of spinning up another SearX instance started to form in the back of my mind.
|
||||
Then *at* time of writing, they decided to start to block more content in relation to copyright infringing material.
|
||||
This still doesn't nessecarily mean too much, but who knows how much they're going to continue blocking now they've started in the first place.
|
||||
|
||||
So here's that SearX instance, {{< bold-gay content="enjoy!" >}}
|
||||
|
||||
I have got to say that the theming of SearXNG has improved quite a lot either over the years or in my mind.
|
||||
At the very least I think the default theme looks quite nice these days.
|
||||
|
||||
{{< noscript content="SearXNG does make use of JavaScript for certain functions, but it's purely to make using it a smoother experiece and it's perfectly functional without JavaScript." >}}
|
||||
|
||||
# [Alpine repository](https://alpine.voidcruiser.nl)[(onion)](http://imerwns46jfdawado7xxb42i2kx7wjy6eyzugxehehxluh4hjqpebmyd.onion)
|
||||
|
||||
I use [suckless' dwm](https://dwm.suckless.org) on every other machine I use.
|
||||
Since I can't be bothered to always keep cloning the repo, I thought I'd make package based on it.
|
||||
Why Alpine of all things? It's my second most used distro after Debian.
|
||||
Since it's as lightweight as it is, I'd say it's a natural fit for suckless utilities.
|
||||
(also perhaps because the process of making package was really quite easy).
|
16
src/layouts/_default/index.html
Normal file
|
@ -0,0 +1,16 @@
|
|||
{{ define "main" }}
|
||||
{{ .Content -}}
|
||||
<div class="posts">
|
||||
{{ $isntDefault := not (or (eq (trim $.Site.Params.contentTypeName " ") "rambles") (eq (trim $.Site.Params.contentTypeName " ") "")) }} {{ $contentTypeName := cond $isntDefault (string $.Site.Params.contentTypeName) "rambles" }} {{ $PageContext := . }} {{ if .IsHome }} {{ $PageContext = .Site }} {{ end }} {{ $paginator := .Paginate (where $PageContext.RegularPages "Type" $contentTypeName) }}
|
||||
<table class="post-entry-meta"> {{ range first 10 .Paginator.Pages }}
|
||||
<tr>
|
||||
<td><a href="{{ .RelPermalink }}">{{ .Title | markdownify}}</a></td>
|
||||
<td>{{ .Params.Date.Format "2006-01-02" }}</td>
|
||||
<td>{{ .Params.Author }}</td>
|
||||
<td>{{ range .Params.tags }} #<a href="{{ (urlize (printf "tags/%s/" .)) | absLangURL }}">{{ . }}</a>{{ end }}</td>
|
||||
</tr> {{ end }}
|
||||
</table>
|
||||
<p><a class="button" href="/rambles">Read more</a></p>
|
||||
</div>
|
||||
{{ partial "stickers.html" }}
|
||||
{{ end }}
|
8
src/layouts/partials/footer.html
Normal file
|
@ -0,0 +1,8 @@
|
|||
<footer>
|
||||
<div class="mirror">
|
||||
[<a rel="me" href="https://tech.lgbt/@nuclearegg">Mastodon</a>]
|
||||
[<a rel="rss" href="/index.xml">rss</a>]
|
||||
[<a href="http://void5xgev2n26prpzqvf2g3gqv5kx7kz6md4skqngrfm23rmwkbralad.onion">onion_1</a>|<a href="http://fxawfamgojpoxxgzmmjiro4wx4eycempujmryk6miz4a64ljl6sz7dqd.onion">onion_2</a>|<a href="http://lmwr2pugnmv4pkkxlneeepi4oysfly33zfuj7x25s6hfydysnpfq.b32.i2p">i2p</a>|<a href="http://[201:e2aa:7c70:666b:9a28:6406:be7f:bf0d]">yggdrasil</a>]
|
||||
<a rel="lisence" href="http://creativecommons.org/licenses/by-sa/4.0/"><img alt="CC BY-SA 4.0" src="/stickers/cc4.png"></a>
|
||||
</div>
|
||||
</footer>
|
8
src/layouts/partials/stickers.html
Normal file
|
@ -0,0 +1,8 @@
|
|||
<div class="stickers">
|
||||
<a target="_blank" href="https://yesterweb.org/no-to-web3/" ><img alt="Web 3 is a scam" src="/stickers/notoweb3.gif" /></a>
|
||||
<a target="_blank" href="https://mullvad.net/en/browser" ><img alt="Fuck Chromium" src="/stickers/same-shit-different-asshole_banner.gif" /></a>
|
||||
<a target="_blank" href="https://www.vim.org" ><img alt="I ♡ vim" src="/stickers/edited-with-vim_banner.gif" /></a>
|
||||
<a target="_blank" href="https://yewtu.be/watch?v=TdkdR92a7dU"><img alt="Nazi punks, fuck off!" src="/stickers/nonazis.png"></a>
|
||||
<a target="_blank" href="https://wiby.me"><img alt="Wiby is great" src="/stickers/wiby.gif"></a>
|
||||
<a target="_blank" href="https://anybrowser.org/campaign/"><img alt="Viewable in any browser" src="/stickers/4nobody.gif"></a>
|
||||
</div>
|
14
src/layouts/shortcodes/about.html
Normal file
|
@ -0,0 +1,14 @@
|
|||
<div class="about">
|
||||
<a title="Vector graphic" href="/logoOptimised.svg"><img src="/logoOptimised.png" /></a>
|
||||
<ul>
|
||||
<li><b>Name:</b> Marty</li>
|
||||
<li title="13-6-2001 Gregorian calendar"><b>Date of birth:</b> 18-con-3167</li>
|
||||
<li><b>Gender:</b> Not really</li>
|
||||
<li><b>Email:</b> <a href="mailto:marty.wanderer@disroot.org">marty.wanderer@disroot.org</a></li>
|
||||
<li><b><a href="/keys/gpg.asc">GPG key</a></b></li>
|
||||
<li><b>Country:</b> Check the TLD</li>
|
||||
<li><b><a href="gemini://voidcruiser.nl">Gemini</a></b></li>
|
||||
<li><b><a href="https://gitlab.com/EternalWanderer">Gitlab</a></b> - because I evidently can't be arsed to host my own git interface yet...</li>
|
||||
<li><b><a rel="me" href="https://tech.lgbt/@nuclearegg">Mastodon</a></b></li>
|
||||
</ul>
|
||||
</div>
|
BIN
src/static/audio/aaaaanvil.flac
Normal file
BIN
src/static/audio/insanity.wav
Normal file
BIN
src/static/audio/orca/climb.flac
Normal file
BIN
src/static/audio/orca/fall.flac
Normal file
BIN
src/static/audio/orca/plinkinator.flac
Normal file
BIN
src/static/audio/orca/varied_plinkinator.flac
Normal file
BIN
src/static/images/config_length.png
Normal file
After Width: | Height: | Size: 30 KiB |
BIN
src/static/images/graphs/radio.png
Normal file
After Width: | Height: | Size: 15 KiB |
BIN
src/static/images/xkcd/borrow_your_laptop.png
Normal file
After Width: | Height: | Size: 26 KiB |
9
src/static/keys/alpine@voidcruiser.nl.rsa.pub
Normal file
|
@ -0,0 +1,9 @@
|
|||
-----BEGIN PUBLIC KEY-----
|
||||
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA2y/Ij9YlP9/CxAQoYrJU
|
||||
lZ5c6Srk99a+tYu02yr3ffyHGJJ+K5HMFT5v8JEgXYtNs5cq2thl9kcInizQg4N5
|
||||
l+NwHt/GlOKtsx8BAzWjN39GMwEylOdmM2AecPCAYRVAcxGgykxNW5oH29ncuEOL
|
||||
ATGr6wcxawSue2V6PVx781UEb9aEAhRYsg+0OZN08Qm8dg9Oxh9fHOfpBBajpv8E
|
||||
/6/oPEPtJGN1Nauz8MA5lzyDFHxMRYvVX+YCjAD8HvNU8VEH3PVHJJ6R4mhOKBcv
|
||||
wehaCJY+RP9HCbu5OENS0ForzReO1zSaqtq1r4pcDQbFXT/enB0R5RjxSLKMH24a
|
||||
aQIDAQAB
|
||||
-----END PUBLIC KEY-----
|
13
src/static/keys/gpg.asc
Normal file
|
@ -0,0 +1,13 @@
|
|||
-----BEGIN PGP PUBLIC KEY BLOCK-----
|
||||
|
||||
mDMEYlbtQRYJKwYBBAHaRw8BAQdAfnOdIezMvkyaBths3yFxT5dk9nm37jpUh9jb
|
||||
IQr5trG0Ik1hcnR5IDxtYXJ0eS53YW5kZXJlckBkaXNyb290Lm9yZz6IkAQTFggA
|
||||
OAIbAwULCQgHAgYVCgkICwIEFgIDAQIeAQIXgBYhBNK25epl5gCIsN0oT1dsSfma
|
||||
HwhLBQJiVu+3AAoJEFdsSfmaHwhLhBMBALLFkddAJiMRU77YLk74wGNX+ghG/VDu
|
||||
vpoYqk/7fuVGAP9sLnx8+UFNGCGbfdLS05w297o7BOIX3zsOnhrvE+M7B7g4BGJW
|
||||
7UESCisGAQQBl1UBBQEBB0DuwfvvsSLyLEiPW0DPJu6RsgUq2YfhRnu2abPJYncM
|
||||
QQMBCAeIeAQYFggAIBYhBNK25epl5gCIsN0oT1dsSfmaHwhLBQJiVu1BAhsMAAoJ
|
||||
EFdsSfmaHwhLTwIBAJqBpCv+FVwvisiOplPlw6AMbwBcRtuUpNIJh1boNiYrAP9U
|
||||
YgS1gHu06Tblwp+TqHQ7oppSg6jc7xxhiSXIy2ZaDQ==
|
||||
=1rR/
|
||||
-----END PGP PUBLIC KEY BLOCK-----
|
BIN
src/static/stickers/4nobody.gif
Normal file
After Width: | Height: | Size: 2.9 KiB |
7
src/static/stickers/80x15.png
Normal file
|
@ -0,0 +1,7 @@
|
|||
<html>
|
||||
<head><title>301 Moved Permanently</title></head>
|
||||
<body bgcolor="white">
|
||||
<center><h1>301 Moved Permanently</h1></center>
|
||||
<hr><center>nginx</center>
|
||||
</body>
|
||||
</html>
|
BIN
src/static/stickers/cc4.png
Normal file
After Width: | Height: | Size: 446 B |
BIN
src/static/stickers/edited-with-vim_banner.gif
Normal file
After Width: | Height: | Size: 1.3 KiB |
BIN
src/static/stickers/nonazis.png
Normal file
After Width: | Height: | Size: 2.2 KiB |
BIN
src/static/stickers/notoweb3.gif
Normal file
After Width: | Height: | Size: 2.5 KiB |
BIN
src/static/stickers/same-shit-different-asshole_banner.gif
Normal file
After Width: | Height: | Size: 16 KiB |
BIN
src/static/stickers/wiby.gif
Normal file
After Width: | Height: | Size: 2.1 KiB |
661
src/themes/vugo/LICENSE
Normal file
|
@ -0,0 +1,661 @@
|
|||
GNU AFFERO GENERAL PUBLIC LICENSE
|
||||
Version 3, 19 November 2007
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
The GNU Affero General Public License is a free, copyleft license for
|
||||
software and other kinds of works, specifically designed to ensure
|
||||
cooperation with the community in the case of network server software.
|
||||
|
||||
The licenses for most software and other practical works are designed
|
||||
to take away your freedom to share and change the works. By contrast,
|
||||
our General Public Licenses are intended to guarantee your freedom to
|
||||
share and change all versions of a program--to make sure it remains free
|
||||
software for all its users.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
them if you wish), that you receive source code or can get it if you
|
||||
want it, that you can change the software or use pieces of it in new
|
||||
free programs, and that you know you can do these things.
|
||||
|
||||
Developers that use our General Public Licenses protect your rights
|
||||
with two steps: (1) assert copyright on the software, and (2) offer
|
||||
you this License which gives you legal permission to copy, distribute
|
||||
and/or modify the software.
|
||||
|
||||
A secondary benefit of defending all users' freedom is that
|
||||
improvements made in alternate versions of the program, if they
|
||||
receive widespread use, become available for other developers to
|
||||
incorporate. Many developers of free software are heartened and
|
||||
encouraged by the resulting cooperation. However, in the case of
|
||||
software used on network servers, this result may fail to come about.
|
||||
The GNU General Public License permits making a modified version and
|
||||
letting the public access it on a server without ever releasing its
|
||||
source code to the public.
|
||||
|
||||
The GNU Affero General Public License is designed specifically to
|
||||
ensure that, in such cases, the modified source code becomes available
|
||||
to the community. It requires the operator of a network server to
|
||||
provide the source code of the modified version running there to the
|
||||
users of that server. Therefore, public use of a modified version, on
|
||||
a publicly accessible server, gives the public access to the source
|
||||
code of the modified version.
|
||||
|
||||
An older license, called the Affero General Public License and
|
||||
published by Affero, was designed to accomplish similar goals. This is
|
||||
a different license, not a version of the Affero GPL, but Affero has
|
||||
released a new version of the Affero GPL which permits relicensing under
|
||||
this license.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
TERMS AND CONDITIONS
|
||||
|
||||
0. Definitions.
|
||||
|
||||
"This License" refers to version 3 of the GNU Affero General Public License.
|
||||
|
||||
"Copyright" also means copyright-like laws that apply to other kinds of
|
||||
works, such as semiconductor masks.
|
||||
|
||||
"The Program" refers to any copyrightable work licensed under this
|
||||
License. Each licensee is addressed as "you". "Licensees" and
|
||||
"recipients" may be individuals or organizations.
|
||||
|
||||
To "modify" a work means to copy from or adapt all or part of the work
|
||||
in a fashion requiring copyright permission, other than the making of an
|
||||
exact copy. The resulting work is called a "modified version" of the
|
||||
earlier work or a work "based on" the earlier work.
|
||||
|
||||
A "covered work" means either the unmodified Program or a work based
|
||||
on the Program.
|
||||
|
||||
To "propagate" a work means to do anything with it that, without
|
||||
permission, would make you directly or secondarily liable for
|
||||
infringement under applicable copyright law, except executing it on a
|
||||
computer or modifying a private copy. Propagation includes copying,
|
||||
distribution (with or without modification), making available to the
|
||||
public, and in some countries other activities as well.
|
||||
|
||||
To "convey" a work means any kind of propagation that enables other
|
||||
parties to make or receive copies. Mere interaction with a user through
|
||||
a computer network, with no transfer of a copy, is not conveying.
|
||||
|
||||
An interactive user interface displays "Appropriate Legal Notices"
|
||||
to the extent that it includes a convenient and prominently visible
|
||||
feature that (1) displays an appropriate copyright notice, and (2)
|
||||
tells the user that there is no warranty for the work (except to the
|
||||
extent that warranties are provided), that licensees may convey the
|
||||
work under this License, and how to view a copy of this License. If
|
||||
the interface presents a list of user commands or options, such as a
|
||||
menu, a prominent item in the list meets this criterion.
|
||||
|
||||
1. Source Code.
|
||||
|
||||
The "source code" for a work means the preferred form of the work
|
||||
for making modifications to it. "Object code" means any non-source
|
||||
form of a work.
|
||||
|
||||
A "Standard Interface" means an interface that either is an official
|
||||
standard defined by a recognized standards body, or, in the case of
|
||||
interfaces specified for a particular programming language, one that
|
||||
is widely used among developers working in that language.
|
||||
|
||||
The "System Libraries" of an executable work include anything, other
|
||||
than the work as a whole, that (a) is included in the normal form of
|
||||
packaging a Major Component, but which is not part of that Major
|
||||
Component, and (b) serves only to enable use of the work with that
|
||||
Major Component, or to implement a Standard Interface for which an
|
||||
implementation is available to the public in source code form. A
|
||||
"Major Component", in this context, means a major essential component
|
||||
(kernel, window system, and so on) of the specific operating system
|
||||
(if any) on which the executable work runs, or a compiler used to
|
||||
produce the work, or an object code interpreter used to run it.
|
||||
|
||||
The "Corresponding Source" for a work in object code form means all
|
||||
the source code needed to generate, install, and (for an executable
|
||||
work) run the object code and to modify the work, including scripts to
|
||||
control those activities. However, it does not include the work's
|
||||
System Libraries, or general-purpose tools or generally available free
|
||||
programs which are used unmodified in performing those activities but
|
||||
which are not part of the work. For example, Corresponding Source
|
||||
includes interface definition files associated with source files for
|
||||
the work, and the source code for shared libraries and dynamically
|
||||
linked subprograms that the work is specifically designed to require,
|
||||
such as by intimate data communication or control flow between those
|
||||
subprograms and other parts of the work.
|
||||
|
||||
The Corresponding Source need not include anything that users
|
||||
can regenerate automatically from other parts of the Corresponding
|
||||
Source.
|
||||
|
||||
The Corresponding Source for a work in source code form is that
|
||||
same work.
|
||||
|
||||
2. Basic Permissions.
|
||||
|
||||
All rights granted under this License are granted for the term of
|
||||
copyright on the Program, and are irrevocable provided the stated
|
||||
conditions are met. This License explicitly affirms your unlimited
|
||||
permission to run the unmodified Program. The output from running a
|
||||
covered work is covered by this License only if the output, given its
|
||||
content, constitutes a covered work. This License acknowledges your
|
||||
rights of fair use or other equivalent, as provided by copyright law.
|
||||
|
||||
You may make, run and propagate covered works that you do not
|
||||
convey, without conditions so long as your license otherwise remains
|
||||
in force. You may convey covered works to others for the sole purpose
|
||||
of having them make modifications exclusively for you, or provide you
|
||||
with facilities for running those works, provided that you comply with
|
||||
the terms of this License in conveying all material for which you do
|
||||
not control copyright. Those thus making or running the covered works
|
||||
for you must do so exclusively on your behalf, under your direction
|
||||
and control, on terms that prohibit them from making any copies of
|
||||
your copyrighted material outside their relationship with you.
|
||||
|
||||
Conveying under any other circumstances is permitted solely under
|
||||
the conditions stated below. Sublicensing is not allowed; section 10
|
||||
makes it unnecessary.
|
||||
|
||||
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
|
||||
|
||||
No covered work shall be deemed part of an effective technological
|
||||
measure under any applicable law fulfilling obligations under article
|
||||
11 of the WIPO copyright treaty adopted on 20 December 1996, or
|
||||
similar laws prohibiting or restricting circumvention of such
|
||||
measures.
|
||||
|
||||
When you convey a covered work, you waive any legal power to forbid
|
||||
circumvention of technological measures to the extent such circumvention
|
||||
is effected by exercising rights under this License with respect to
|
||||
the covered work, and you disclaim any intention to limit operation or
|
||||
modification of the work as a means of enforcing, against the work's
|
||||
users, your or third parties' legal rights to forbid circumvention of
|
||||
technological measures.
|
||||
|
||||
4. Conveying Verbatim Copies.
|
||||
|
||||
You may convey verbatim copies of the Program's source code as you
|
||||
receive it, in any medium, provided that you conspicuously and
|
||||
appropriately publish on each copy an appropriate copyright notice;
|
||||
keep intact all notices stating that this License and any
|
||||
non-permissive terms added in accord with section 7 apply to the code;
|
||||
keep intact all notices of the absence of any warranty; and give all
|
||||
recipients a copy of this License along with the Program.
|
||||
|
||||
You may charge any price or no price for each copy that you convey,
|
||||
and you may offer support or warranty protection for a fee.
|
||||
|
||||
5. Conveying Modified Source Versions.
|
||||
|
||||
You may convey a work based on the Program, or the modifications to
|
||||
produce it from the Program, in the form of source code under the
|
||||
terms of section 4, provided that you also meet all of these conditions:
|
||||
|
||||
a) The work must carry prominent notices stating that you modified
|
||||
it, and giving a relevant date.
|
||||
|
||||
b) The work must carry prominent notices stating that it is
|
||||
released under this License and any conditions added under section
|
||||
7. This requirement modifies the requirement in section 4 to
|
||||
"keep intact all notices".
|
||||
|
||||
c) You must license the entire work, as a whole, under this
|
||||
License to anyone who comes into possession of a copy. This
|
||||
License will therefore apply, along with any applicable section 7
|
||||
additional terms, to the whole of the work, and all its parts,
|
||||
regardless of how they are packaged. This License gives no
|
||||
permission to license the work in any other way, but it does not
|
||||
invalidate such permission if you have separately received it.
|
||||
|
||||
d) If the work has interactive user interfaces, each must display
|
||||
Appropriate Legal Notices; however, if the Program has interactive
|
||||
interfaces that do not display Appropriate Legal Notices, your
|
||||
work need not make them do so.
|
||||
|
||||
A compilation of a covered work with other separate and independent
|
||||
works, which are not by their nature extensions of the covered work,
|
||||
and which are not combined with it such as to form a larger program,
|
||||
in or on a volume of a storage or distribution medium, is called an
|
||||
"aggregate" if the compilation and its resulting copyright are not
|
||||
used to limit the access or legal rights of the compilation's users
|
||||
beyond what the individual works permit. Inclusion of a covered work
|
||||
in an aggregate does not cause this License to apply to the other
|
||||
parts of the aggregate.
|
||||
|
||||
6. Conveying Non-Source Forms.
|
||||
|
||||
You may convey a covered work in object code form under the terms
|
||||
of sections 4 and 5, provided that you also convey the
|
||||
machine-readable Corresponding Source under the terms of this License,
|
||||
in one of these ways:
|
||||
|
||||
a) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by the
|
||||
Corresponding Source fixed on a durable physical medium
|
||||
customarily used for software interchange.
|
||||
|
||||
b) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by a
|
||||
written offer, valid for at least three years and valid for as
|
||||
long as you offer spare parts or customer support for that product
|
||||
model, to give anyone who possesses the object code either (1) a
|
||||
copy of the Corresponding Source for all the software in the
|
||||
product that is covered by this License, on a durable physical
|
||||
medium customarily used for software interchange, for a price no
|
||||
more than your reasonable cost of physically performing this
|
||||
conveying of source, or (2) access to copy the
|
||||
Corresponding Source from a network server at no charge.
|
||||
|
||||
c) Convey individual copies of the object code with a copy of the
|
||||
written offer to provide the Corresponding Source. This
|
||||
alternative is allowed only occasionally and noncommercially, and
|
||||
only if you received the object code with such an offer, in accord
|
||||
with subsection 6b.
|
||||
|
||||
d) Convey the object code by offering access from a designated
|
||||
place (gratis or for a charge), and offer equivalent access to the
|
||||
Corresponding Source in the same way through the same place at no
|
||||
further charge. You need not require recipients to copy the
|
||||
Corresponding Source along with the object code. If the place to
|
||||
copy the object code is a network server, the Corresponding Source
|
||||
may be on a different server (operated by you or a third party)
|
||||
that supports equivalent copying facilities, provided you maintain
|
||||
clear directions next to the object code saying where to find the
|
||||
Corresponding Source. Regardless of what server hosts the
|
||||
Corresponding Source, you remain obligated to ensure that it is
|
||||
available for as long as needed to satisfy these requirements.
|
||||
|
||||
e) Convey the object code using peer-to-peer transmission, provided
|
||||
you inform other peers where the object code and Corresponding
|
||||
Source of the work are being offered to the general public at no
|
||||
charge under subsection 6d.
|
||||
|
||||
A separable portion of the object code, whose source code is excluded
|
||||
from the Corresponding Source as a System Library, need not be
|
||||
included in conveying the object code work.
|
||||
|
||||
A "User Product" is either (1) a "consumer product", which means any
|
||||
tangible personal property which is normally used for personal, family,
|
||||
or household purposes, or (2) anything designed or sold for incorporation
|
||||
into a dwelling. In determining whether a product is a consumer product,
|
||||
doubtful cases shall be resolved in favor of coverage. For a particular
|
||||
product received by a particular user, "normally used" refers to a
|
||||
typical or common use of that class of product, regardless of the status
|
||||
of the particular user or of the way in which the particular user
|
||||
actually uses, or expects or is expected to use, the product. A product
|
||||
is a consumer product regardless of whether the product has substantial
|
||||
commercial, industrial or non-consumer uses, unless such uses represent
|
||||
the only significant mode of use of the product.
|
||||
|
||||
"Installation Information" for a User Product means any methods,
|
||||
procedures, authorization keys, or other information required to install
|
||||
and execute modified versions of a covered work in that User Product from
|
||||
a modified version of its Corresponding Source. The information must
|
||||
suffice to ensure that the continued functioning of the modified object
|
||||
code is in no case prevented or interfered with solely because
|
||||
modification has been made.
|
||||
|
||||
If you convey an object code work under this section in, or with, or
|
||||
specifically for use in, a User Product, and the conveying occurs as
|
||||
part of a transaction in which the right of possession and use of the
|
||||
User Product is transferred to the recipient in perpetuity or for a
|
||||
fixed term (regardless of how the transaction is characterized), the
|
||||
Corresponding Source conveyed under this section must be accompanied
|
||||
by the Installation Information. But this requirement does not apply
|
||||
if neither you nor any third party retains the ability to install
|
||||
modified object code on the User Product (for example, the work has
|
||||
been installed in ROM).
|
||||
|
||||
The requirement to provide Installation Information does not include a
|
||||
requirement to continue to provide support service, warranty, or updates
|
||||
for a work that has been modified or installed by the recipient, or for
|
||||
the User Product in which it has been modified or installed. Access to a
|
||||
network may be denied when the modification itself materially and
|
||||
adversely affects the operation of the network or violates the rules and
|
||||
protocols for communication across the network.
|
||||
|
||||
Corresponding Source conveyed, and Installation Information provided,
|
||||
in accord with this section must be in a format that is publicly
|
||||
documented (and with an implementation available to the public in
|
||||
source code form), and must require no special password or key for
|
||||
unpacking, reading or copying.
|
||||
|
||||
7. Additional Terms.
|
||||
|
||||
"Additional permissions" are terms that supplement the terms of this
|
||||
License by making exceptions from one or more of its conditions.
|
||||
Additional permissions that are applicable to the entire Program shall
|
||||
be treated as though they were included in this License, to the extent
|
||||
that they are valid under applicable law. If additional permissions
|
||||
apply only to part of the Program, that part may be used separately
|
||||
under those permissions, but the entire Program remains governed by
|
||||
this License without regard to the additional permissions.
|
||||
|
||||
When you convey a copy of a covered work, you may at your option
|
||||
remove any additional permissions from that copy, or from any part of
|
||||
it. (Additional permissions may be written to require their own
|
||||
removal in certain cases when you modify the work.) You may place
|
||||
additional permissions on material, added by you to a covered work,
|
||||
for which you have or can give appropriate copyright permission.
|
||||
|
||||
Notwithstanding any other provision of this License, for material you
|
||||
add to a covered work, you may (if authorized by the copyright holders of
|
||||
that material) supplement the terms of this License with terms:
|
||||
|
||||
a) Disclaiming warranty or limiting liability differently from the
|
||||
terms of sections 15 and 16 of this License; or
|
||||
|
||||
b) Requiring preservation of specified reasonable legal notices or
|
||||
author attributions in that material or in the Appropriate Legal
|
||||
Notices displayed by works containing it; or
|
||||
|
||||
c) Prohibiting misrepresentation of the origin of that material, or
|
||||
requiring that modified versions of such material be marked in
|
||||
reasonable ways as different from the original version; or
|
||||
|
||||
d) Limiting the use for publicity purposes of names of licensors or
|
||||
authors of the material; or
|
||||
|
||||
e) Declining to grant rights under trademark law for use of some
|
||||
trade names, trademarks, or service marks; or
|
||||
|
||||
f) Requiring indemnification of licensors and authors of that
|
||||
material by anyone who conveys the material (or modified versions of
|
||||
it) with contractual assumptions of liability to the recipient, for
|
||||
any liability that these contractual assumptions directly impose on
|
||||
those licensors and authors.
|
||||
|
||||
All other non-permissive additional terms are considered "further
|
||||
restrictions" within the meaning of section 10. If the Program as you
|
||||
received it, or any part of it, contains a notice stating that it is
|
||||
governed by this License along with a term that is a further
|
||||
restriction, you may remove that term. If a license document contains
|
||||
a further restriction but permits relicensing or conveying under this
|
||||
License, you may add to a covered work material governed by the terms
|
||||
of that license document, provided that the further restriction does
|
||||
not survive such relicensing or conveying.
|
||||
|
||||
If you add terms to a covered work in accord with this section, you
|
||||
must place, in the relevant source files, a statement of the
|
||||
additional terms that apply to those files, or a notice indicating
|
||||
where to find the applicable terms.
|
||||
|
||||
Additional terms, permissive or non-permissive, may be stated in the
|
||||
form of a separately written license, or stated as exceptions;
|
||||
the above requirements apply either way.
|
||||
|
||||
8. Termination.
|
||||
|
||||
You may not propagate or modify a covered work except as expressly
|
||||
provided under this License. Any attempt otherwise to propagate or
|
||||
modify it is void, and will automatically terminate your rights under
|
||||
this License (including any patent licenses granted under the third
|
||||
paragraph of section 11).
|
||||
|
||||
However, if you cease all violation of this License, then your
|
||||
license from a particular copyright holder is reinstated (a)
|
||||
provisionally, unless and until the copyright holder explicitly and
|
||||
finally terminates your license, and (b) permanently, if the copyright
|
||||
holder fails to notify you of the violation by some reasonable means
|
||||
prior to 60 days after the cessation.
|
||||
|
||||
Moreover, your license from a particular copyright holder is
|
||||
reinstated permanently if the copyright holder notifies you of the
|
||||
violation by some reasonable means, this is the first time you have
|
||||
received notice of violation of this License (for any work) from that
|
||||
copyright holder, and you cure the violation prior to 30 days after
|
||||
your receipt of the notice.
|
||||
|
||||
Termination of your rights under this section does not terminate the
|
||||
licenses of parties who have received copies or rights from you under
|
||||
this License. If your rights have been terminated and not permanently
|
||||
reinstated, you do not qualify to receive new licenses for the same
|
||||
material under section 10.
|
||||
|
||||
9. Acceptance Not Required for Having Copies.
|
||||
|
||||
You are not required to accept this License in order to receive or
|
||||
run a copy of the Program. Ancillary propagation of a covered work
|
||||
occurring solely as a consequence of using peer-to-peer transmission
|
||||
to receive a copy likewise does not require acceptance. However,
|
||||
nothing other than this License grants you permission to propagate or
|
||||
modify any covered work. These actions infringe copyright if you do
|
||||
not accept this License. Therefore, by modifying or propagating a
|
||||
covered work, you indicate your acceptance of this License to do so.
|
||||
|
||||
10. Automatic Licensing of Downstream Recipients.
|
||||
|
||||
Each time you convey a covered work, the recipient automatically
|
||||
receives a license from the original licensors, to run, modify and
|
||||
propagate that work, subject to this License. You are not responsible
|
||||
for enforcing compliance by third parties with this License.
|
||||
|
||||
An "entity transaction" is a transaction transferring control of an
|
||||
organization, or substantially all assets of one, or subdividing an
|
||||
organization, or merging organizations. If propagation of a covered
|
||||
work results from an entity transaction, each party to that
|
||||
transaction who receives a copy of the work also receives whatever
|
||||
licenses to the work the party's predecessor in interest had or could
|
||||
give under the previous paragraph, plus a right to possession of the
|
||||
Corresponding Source of the work from the predecessor in interest, if
|
||||
the predecessor has it or can get it with reasonable efforts.
|
||||
|
||||
You may not impose any further restrictions on the exercise of the
|
||||
rights granted or affirmed under this License. For example, you may
|
||||
not impose a license fee, royalty, or other charge for exercise of
|
||||
rights granted under this License, and you may not initiate litigation
|
||||
(including a cross-claim or counterclaim in a lawsuit) alleging that
|
||||
any patent claim is infringed by making, using, selling, offering for
|
||||
sale, or importing the Program or any portion of it.
|
||||
|
||||
11. Patents.
|
||||
|
||||
A "contributor" is a copyright holder who authorizes use under this
|
||||
License of the Program or a work on which the Program is based. The
|
||||
work thus licensed is called the contributor's "contributor version".
|
||||
|
||||
A contributor's "essential patent claims" are all patent claims
|
||||
owned or controlled by the contributor, whether already acquired or
|
||||
hereafter acquired, that would be infringed by some manner, permitted
|
||||
by this License, of making, using, or selling its contributor version,
|
||||
but do not include claims that would be infringed only as a
|
||||
consequence of further modification of the contributor version. For
|
||||
purposes of this definition, "control" includes the right to grant
|
||||
patent sublicenses in a manner consistent with the requirements of
|
||||
this License.
|
||||
|
||||
Each contributor grants you a non-exclusive, worldwide, royalty-free
|
||||
patent license under the contributor's essential patent claims, to
|
||||
make, use, sell, offer for sale, import and otherwise run, modify and
|
||||
propagate the contents of its contributor version.
|
||||
|
||||
In the following three paragraphs, a "patent license" is any express
|
||||
agreement or commitment, however denominated, not to enforce a patent
|
||||
(such as an express permission to practice a patent or covenant not to
|
||||
sue for patent infringement). To "grant" such a patent license to a
|
||||
party means to make such an agreement or commitment not to enforce a
|
||||
patent against the party.
|
||||
|
||||
If you convey a covered work, knowingly relying on a patent license,
|
||||
and the Corresponding Source of the work is not available for anyone
|
||||
to copy, free of charge and under the terms of this License, through a
|
||||
publicly available network server or other readily accessible means,
|
||||
then you must either (1) cause the Corresponding Source to be so
|
||||
available, or (2) arrange to deprive yourself of the benefit of the
|
||||
patent license for this particular work, or (3) arrange, in a manner
|
||||
consistent with the requirements of this License, to extend the patent
|
||||
license to downstream recipients. "Knowingly relying" means you have
|
||||
actual knowledge that, but for the patent license, your conveying the
|
||||
covered work in a country, or your recipient's use of the covered work
|
||||
in a country, would infringe one or more identifiable patents in that
|
||||
country that you have reason to believe are valid.
|
||||
|
||||
If, pursuant to or in connection with a single transaction or
|
||||
arrangement, you convey, or propagate by procuring conveyance of, a
|
||||
covered work, and grant a patent license to some of the parties
|
||||
receiving the covered work authorizing them to use, propagate, modify
|
||||
or convey a specific copy of the covered work, then the patent license
|
||||
you grant is automatically extended to all recipients of the covered
|
||||
work and works based on it.
|
||||
|
||||
A patent license is "discriminatory" if it does not include within
|
||||
the scope of its coverage, prohibits the exercise of, or is
|
||||
conditioned on the non-exercise of one or more of the rights that are
|
||||
specifically granted under this License. You may not convey a covered
|
||||
work if you are a party to an arrangement with a third party that is
|
||||
in the business of distributing software, under which you make payment
|
||||
to the third party based on the extent of your activity of conveying
|
||||
the work, and under which the third party grants, to any of the
|
||||
parties who would receive the covered work from you, a discriminatory
|
||||
patent license (a) in connection with copies of the covered work
|
||||
conveyed by you (or copies made from those copies), or (b) primarily
|
||||
for and in connection with specific products or compilations that
|
||||
contain the covered work, unless you entered into that arrangement,
|
||||
or that patent license was granted, prior to 28 March 2007.
|
||||
|
||||
Nothing in this License shall be construed as excluding or limiting
|
||||
any implied license or other defenses to infringement that may
|
||||
otherwise be available to you under applicable patent law.
|
||||
|
||||
12. No Surrender of Others' Freedom.
|
||||
|
||||
If conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot convey a
|
||||
covered work so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you may
|
||||
not convey it at all. For example, if you agree to terms that obligate you
|
||||
to collect a royalty for further conveying from those to whom you convey
|
||||
the Program, the only way you could satisfy both those terms and this
|
||||
License would be to refrain entirely from conveying the Program.
|
||||
|
||||
13. Remote Network Interaction; Use with the GNU General Public License.
|
||||
|
||||
Notwithstanding any other provision of this License, if you modify the
|
||||
Program, your modified version must prominently offer all users
|
||||
interacting with it remotely through a computer network (if your version
|
||||
supports such interaction) an opportunity to receive the Corresponding
|
||||
Source of your version by providing access to the Corresponding Source
|
||||
from a network server at no charge, through some standard or customary
|
||||
means of facilitating copying of software. This Corresponding Source
|
||||
shall include the Corresponding Source for any work covered by version 3
|
||||
of the GNU General Public License that is incorporated pursuant to the
|
||||
following paragraph.
|
||||
|
||||
Notwithstanding any other provision of this License, you have
|
||||
permission to link or combine any covered work with a work licensed
|
||||
under version 3 of the GNU General Public License into a single
|
||||
combined work, and to convey the resulting work. The terms of this
|
||||
License will continue to apply to the part which is the covered work,
|
||||
but the work with which it is combined will remain governed by version
|
||||
3 of the GNU General Public License.
|
||||
|
||||
14. Revised Versions of this License.
|
||||
|
||||
The Free Software Foundation may publish revised and/or new versions of
|
||||
the GNU Affero General Public License from time to time. Such new versions
|
||||
will be similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the
|
||||
Program specifies that a certain numbered version of the GNU Affero General
|
||||
Public License "or any later version" applies to it, you have the
|
||||
option of following the terms and conditions either of that numbered
|
||||
version or of any later version published by the Free Software
|
||||
Foundation. If the Program does not specify a version number of the
|
||||
GNU Affero General Public License, you may choose any version ever published
|
||||
by the Free Software Foundation.
|
||||
|
||||
If the Program specifies that a proxy can decide which future
|
||||
versions of the GNU Affero General Public License can be used, that proxy's
|
||||
public statement of acceptance of a version permanently authorizes you
|
||||
to choose that version for the Program.
|
||||
|
||||
Later license versions may give you additional or different
|
||||
permissions. However, no additional obligations are imposed on any
|
||||
author or copyright holder as a result of your choosing to follow a
|
||||
later version.
|
||||
|
||||
15. Disclaimer of Warranty.
|
||||
|
||||
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
|
||||
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
|
||||
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
|
||||
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
|
||||
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
|
||||
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
|
||||
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||
|
||||
16. Limitation of Liability.
|
||||
|
||||
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
|
||||
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
|
||||
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
|
||||
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
|
||||
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
|
||||
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
|
||||
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
|
||||
SUCH DAMAGES.
|
||||
|
||||
17. Interpretation of Sections 15 and 16.
|
||||
|
||||
If the disclaimer of warranty and limitation of liability provided
|
||||
above cannot be given local legal effect according to their terms,
|
||||
reviewing courts shall apply local law that most closely approximates
|
||||
an absolute waiver of all civil liability in connection with the
|
||||
Program, unless a warranty or assumption of liability accompanies a
|
||||
copy of the Program in return for a fee.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Programs
|
||||
|
||||
If you develop a new program, and you want it to be of the greatest
|
||||
possible use to the public, the best way to achieve this is to make it
|
||||
free software which everyone can redistribute and change under these terms.
|
||||
|
||||
To do so, attach the following notices to the program. It is safest
|
||||
to attach them to the start of each source file to most effectively
|
||||
state the exclusion of warranty; and each file should have at least
|
||||
the "copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
vugo
|
||||
Copyright (C) 2022 Voidcruiser
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published
|
||||
by the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If your software can interact with users remotely through a computer
|
||||
network, you should also make sure that it provides a way for users to
|
||||
get its source. For example, if your program is a web application, its
|
||||
interface could display a "Source" link that leads users to an archive
|
||||
of the code. There are many ways you could offer source, and different
|
||||
solutions will be better for different programs; see section 13 for the
|
||||
specific requirements.
|
||||
|
||||
You should also get your employer (if you work as a programmer) or school,
|
||||
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
||||
For more information on this, and how to apply and follow the GNU AGPL, see
|
||||
<https://www.gnu.org/licenses/>.
|
3
src/themes/vugo/README.md
Normal file
|
@ -0,0 +1,3 @@
|
|||
# Voidcruiser Hugo theme
|
||||
|
||||
Hacked together based on the CSS I used before hugo and a whole bunch of examples on the internet
|
6
src/themes/vugo/archetypes/default.md
Normal file
|
@ -0,0 +1,6 @@
|
|||
---
|
||||
title: "{{ replace .Name "-" " " | title }}"
|
||||
date: {{ .Date }}
|
||||
draft: true
|
||||
---
|
||||
|
7
src/themes/vugo/archetypes/rambles.md
Normal file
|
@ -0,0 +1,7 @@
|
|||
---
|
||||
title: "{{ replace .TranslationBaseName "-" " " | title }}"
|
||||
date: "{{ .Date }}"
|
||||
author: "$HUMANOID"
|
||||
tags: ["", ""]
|
||||
description: ""
|
||||
---
|
8
src/themes/vugo/config.toml
Normal file
|
@ -0,0 +1,8 @@
|
|||
title = "Website Name"
|
||||
baseURL = '/'
|
||||
languageCode = 'en-us'
|
||||
|
||||
[params]
|
||||
# "relatedtext" is the text that appears above the tag list at the bottom of pages.
|
||||
#relatedtext = "Related:"
|
||||
favicon = "/favicon.ico"
|
22
src/themes/vugo/layouts/_default/baseof.html
Normal file
|
@ -0,0 +1,22 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="{{ $.Site.Language }}">
|
||||
<head>
|
||||
{{ block "title" . }}
|
||||
<title>{{ if .IsHome }}{{ $.Site.Title }}{{ else }}{{ .Title }} | {{ $.Site.Title }}{{ end }}</title>
|
||||
{{ end }}
|
||||
{{ partial "head.html" . }}
|
||||
</head>
|
||||
<body>
|
||||
{{ partial "logo.html" . }}
|
||||
{{ partial "menu.html" . }}
|
||||
<h1 class="page-head">{{ .Title | markdownify }}</h1>
|
||||
<main>
|
||||
{{ block "main" . }}
|
||||
{{ end }}
|
||||
</main>
|
||||
{{ block "footer" . }}
|
||||
{{ partial "footer.html" . }}
|
||||
{{ end }}
|
||||
|
||||
</body>
|
||||
</html>
|
16
src/themes/vugo/layouts/_default/index.html
Normal file
|
@ -0,0 +1,16 @@
|
|||
{{ define "main" }}
|
||||
{{ .Content -}}
|
||||
<div class="posts">
|
||||
{{ $isntDefault := not (or (eq (trim $.Site.Params.contentTypeName " ") "rambles") (eq (trim $.Site.Params.contentTypeName " ") "")) }} {{ $contentTypeName := cond $isntDefault (string $.Site.Params.contentTypeName) "rambles" }} {{ $PageContext := . }} {{ if .IsHome }} {{ $PageContext = .Site }} {{ end }} {{ $paginator := .Paginate (where $PageContext.RegularPages "Type" $contentTypeName) }}
|
||||
<table class="post-entry-meta"> {{ range first 10 .Paginator.Pages }}
|
||||
<tr>
|
||||
<td><a href="{{ .RelPermalink }}">{{ .Title | markdownify}}</a></td>
|
||||
<td>{{ .Params.Date.Format "2006-01-02" }}</td>
|
||||
<td>{{ .Params.Author }}</td>
|
||||
<td>{{ range .Params.tags }} #<a href="{{ (urlize (printf "tags/%s/" .)) | absLangURL }}">{{ . }}</a>{{ end }}</td>
|
||||
</tr> {{ end }}
|
||||
</table>
|
||||
<p><a class="button" href="/rambles">Read more</a></p>
|
||||
</div>
|
||||
</div>
|
||||
{{ end }}
|
25
src/themes/vugo/layouts/_default/list.html
Normal file
|
@ -0,0 +1,25 @@
|
|||
{{ define "main" }}
|
||||
{{ if .Content }}
|
||||
<article>
|
||||
{{ .Content -}}
|
||||
</article>
|
||||
{{ end }}
|
||||
<div class="posts">
|
||||
<table class="post-entry-meta">
|
||||
{{ range .Paginator.Pages }}
|
||||
<tr>
|
||||
<td><a href="{{ .RelPermalink }}">{{ .Title | markdownify}}</a></td>
|
||||
<td>{{ .Params.Date.Format "2006-01-02" }}</td>
|
||||
<td>{{ .Params.Author }}</td>
|
||||
<td > {{ range .Params.tags }} #<a href="{{ (urlize (printf "tags/%s/" .)) | absLangURL }}">{{ . }}</a> {{ end }}</td>
|
||||
<!--
|
||||
{{ if and (.Param "readingTime") (eq (.Param "readingTime") true) }}
|
||||
<td class="post-reading-time">- {{ .ReadingTime }} min read ({{ .WordCount }} words)</td>
|
||||
{{ end }}
|
||||
-->
|
||||
</tr>
|
||||
{{ end }}
|
||||
</table>
|
||||
</div>
|
||||
{{ partial "pagination.html" . }}
|
||||
{{ end }}
|
39
src/themes/vugo/layouts/_default/rss.xml
Normal file
|
@ -0,0 +1,39 @@
|
|||
{{- $pctx := . -}}
|
||||
{{- if .IsHome -}}{{ $pctx = .Site }}{{- end -}}
|
||||
{{- $pages := slice -}}
|
||||
{{- if or $.IsHome $.IsSection -}}
|
||||
{{- $pages = $pctx.RegularPages -}}
|
||||
{{- else -}}
|
||||
{{- $pages = $pctx.Pages -}}
|
||||
{{- end -}}
|
||||
{{- $limit := .Site.Config.Services.RSS.Limit -}}
|
||||
{{- if ge $limit 1 -}}
|
||||
{{- $pages = $pages | first $limit -}}
|
||||
{{- end -}}
|
||||
{{- printf "<?xml version=\"1.0\" encoding=\"utf-8\" standalone=\"yes\"?>" | safeHTML }}
|
||||
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
|
||||
<channel>
|
||||
<title>{{ .Site.Title }}</title>
|
||||
<link>{{ .Permalink }}</link>
|
||||
<description>Recent content {{ if ne .Title .Site.Title }}{{ with .Title }}in {{.}} {{ end }}{{ end }}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 -}}
|
||||
{{ 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>{{ .Content | html }}</description>
|
||||
</item>
|
||||
{{ end }}
|
||||
</channel>
|
||||
</rss>
|
43
src/themes/vugo/layouts/_default/single.html
Normal file
|
@ -0,0 +1,43 @@
|
|||
{{ define "main" }}
|
||||
{{ if eq .Type $.Site.Params.contentTypeName }}
|
||||
<div class="post-meta">
|
||||
{{ if .Params.Date }} <span class="post-date"> {{ .Date.Format "2006-01-02" }} </span> {{ end }}
|
||||
{{ with .Params.Author }} <span> - </span> <span class="post-author">{{ . }}</span> {{ end }}
|
||||
<span class="post-reading-time">- {{ .ReadingTime }} min read ({{ .WordCount }} words)</span>
|
||||
</span>
|
||||
|
||||
{{ if .Params.tags }}
|
||||
<span class="post-tags">
|
||||
{{ range .Params.tags }}
|
||||
#<a href="{{ (urlize (printf "tags/%s/" .)) | absLangURL }}">{{ . }}</a>
|
||||
{{ end }}
|
||||
</span>
|
||||
{{ end }}
|
||||
</div>
|
||||
{{ end }}
|
||||
|
||||
{{ if (.Params.Toc | default .Site.Params.Toc) }}
|
||||
<div class="table-of-contents">
|
||||
<h2>
|
||||
{{ (.Params.TocTitle | default .Site.Params.TocTitle) | default "Table of Contents" }}
|
||||
</h2>
|
||||
{{ .TableOfContents }}
|
||||
</div>
|
||||
{{ end }}
|
||||
|
||||
{{ if eq .Type $.Site.Params.contentTypeName }}
|
||||
<div class="post-content">
|
||||
{{- with .Content -}}
|
||||
{{ . | replaceRE "(<h[1-9] id=\"([^\"]+)\".+)(</h[1-9]+>)" `${1} <a href="#${2}" class="anchor" ariaLabel="Anchor">#</a> ${3}` | safeHTML }}
|
||||
{{- end -}}
|
||||
</div>
|
||||
{{ else }}
|
||||
{{ .Content }}
|
||||
{{ end }}
|
||||
|
||||
{{ if eq .Type $.Site.Params.contentTypeName }}
|
||||
{{ partial "posts_pagination.html" . }}
|
||||
{{ end }}
|
||||
|
||||
</div>
|
||||
{{ end }}
|
7
src/themes/vugo/layouts/partials/footer.html
Normal file
|
@ -0,0 +1,7 @@
|
|||
<footer>
|
||||
<div class="mirror">
|
||||
[<a rel="rss" href="/index.xml">rss</a>]
|
||||
[*Insert mirrors here*]
|
||||
<a rel="lisence" href="http://creativecommons.org/licenses/by-sa/4.0/"><img alt="CC BY-SA 4.0" src="/stickers/cc4.png"></a>
|
||||
</div>
|
||||
</footer>
|
13
src/themes/vugo/layouts/partials/head.html
Normal file
|
@ -0,0 +1,13 @@
|
|||
<head>
|
||||
<link rel="canonical" href="{{ .Site.BaseURL }}">
|
||||
<link rel='alternate' type='application/rss+xml' title="{{ .Site.Title }} RSS" href='/index.xml'>
|
||||
<link rel='stylesheet' type='text/css' href='/style.css'>
|
||||
{{ with .Site.Params.favicon }}<link rel="icon" href="{{ . }}">
|
||||
{{ end -}}
|
||||
<meta name="description" content="{{ with .Params.description }}{{ . }}{{ else }}{{ .Summary }}{{ end }}">
|
||||
{{ if isset .Params "tags" }}<meta name="keywords" content="{{ with .Params.tags }}{{ delimit . ", " }}{{ end }}">
|
||||
{{ end -}}
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta name="robots" content="index, follow">
|
||||
<meta charset="utf-8">
|
||||
</head>
|
8
src/themes/vugo/layouts/partials/logo.html
Normal file
|
@ -0,0 +1,8 @@
|
|||
<div class="logo">
|
||||
<img src="/logoOptimised.png" />
|
||||
<h1>
|
||||
<a href="{{ if $.Site.Params.Logo.LogoHomeLink }}{{ $.Site.Params.Logo.LogoHomeLink }}{{else}}{{ $.Site.BaseURL }}{{ end }}">
|
||||
{{ with $.Site.Params.Logo.logoText }}{{ . }}{{ else }}Void{{ end }}
|
||||
</a>
|
||||
</h1>
|
||||
</div>
|
6
src/themes/vugo/layouts/partials/menu.html
Normal file
|
@ -0,0 +1,6 @@
|
|||
<menu>
|
||||
<a href="/">home<br/></a> {{ range $.Site.Menus.main }} {{ if not .HasChildren }}
|
||||
<a href="{{ .URL }}">{{ .Name }}<br/></a> {{ end }} {{ end }}
|
||||
<hr />
|
||||
{{ range $.Site.Menus.services }} {{ if not .HasChildren }} <a href="{{ .URL }}">{{ .Name }}<br/></a> {{ end }} {{ end }}
|
||||
</menu>
|
10
src/themes/vugo/layouts/partials/nextprev.html
Normal file
|
@ -0,0 +1,10 @@
|
|||
{{ if or .Next .Prev -}}
|
||||
<div id="nextprev">
|
||||
{{- with .Prev }}
|
||||
<a href="{{ .RelPermalink}}"><div id="prevart">Previous:<br>{{.Title}}</div></a>
|
||||
{{ end -}}
|
||||
{{- with .Next -}}
|
||||
<a href="{{ .RelPermalink}}"><div id="nextart">Next:<br>{{.Title}}</div></a>
|
||||
{{ end -}}
|
||||
</div>
|
||||
{{ end -}}
|
8
src/themes/vugo/layouts/partials/pagination.html
Normal file
|
@ -0,0 +1,8 @@
|
|||
<div class="pagination__buttons">
|
||||
{{ if .Paginator.HasPrev }}
|
||||
<a class="button prev" href="{{ .Paginator.Prev.URL }}"> {{ $.Site.Params.newerPosts | default "Newer posts" }} </a>
|
||||
{{ end }}
|
||||
{{ if .Paginator.HasNext }}
|
||||
<a class="button next" href="{{ .Paginator.Next.URL }}"> {{ $.Site.Params.olderPosts | default "Older posts" }} </a>
|
||||
{{ end }}
|
||||
</div>
|
8
src/themes/vugo/layouts/partials/posts_pagination.html
Normal file
|
@ -0,0 +1,8 @@
|
|||
<div class="pagination__buttons">
|
||||
{{ if .NextInSection }}
|
||||
<a class="button prev" href="{{ .NextInSection.RelPermalink }}">{{ .NextInSection.Title }}</a>
|
||||
{{ end }}
|
||||
{{ if .PrevInSection }}
|
||||
<a class="button next" href="{{ .PrevInSection.RelPermalink }}">{{ .PrevInSection.Title }}</a>
|
||||
{{ end }}
|
||||
</div>
|
6
src/themes/vugo/layouts/shortcodes/audio.html
Normal file
|
@ -0,0 +1,6 @@
|
|||
<audio controls="true">
|
||||
<source src="{{.Get "src" }}" type="audio/{{.Get "audio" }}">
|
||||
Sorry mate, your browser doesn't support playing audio files.
|
||||
</source>
|
||||
</audio>
|
||||
|
1
src/themes/vugo/layouts/shortcodes/bold-gay.html
Normal file
|
@ -0,0 +1 @@
|
|||
<b class="gay">{{ .Get "content" }}</b>
|
1
src/themes/vugo/layouts/shortcodes/bold-italic-gay.html
Normal file
|
@ -0,0 +1 @@
|
|||
<b class="gay"><i>{{ .Get "content" }}</i></b>
|
1
src/themes/vugo/layouts/shortcodes/end-details.html
Normal file
|
@ -0,0 +1 @@
|
|||
</details>
|
1
src/themes/vugo/layouts/shortcodes/gay.html
Normal file
|
@ -0,0 +1 @@
|
|||
<span class="gay">{{ .Get "content" }}</span>
|
1
src/themes/vugo/layouts/shortcodes/hyperbowl.html
Normal file
|
@ -0,0 +1 @@
|
|||
<blockquote class="hyperbowl">"{{ .Get "content" }}"</blockquote>
|
20
src/themes/vugo/layouts/shortcodes/img.html
Normal file
|
@ -0,0 +1,20 @@
|
|||
<!--
|
||||
class: class of the figure
|
||||
link: url the image directs to
|
||||
alt: alternative text
|
||||
caption: caption
|
||||
mouse: what the image says when moused over ("title" in HTML)
|
||||
-->
|
||||
<figure {{ with .Get "class" }}class="{{.}}"{{ end -}}>
|
||||
{{- with .Get "link"}}<a href="{{.}}">{{ end -}}
|
||||
<img src="{{ .Get "src" }}"
|
||||
{{- with .Get "mouse" }} title="{{.}}"{{ end -}}
|
||||
{{- with .Get "alt" }} alt="{{.}}"{{ end -}}
|
||||
>
|
||||
{{- if .Get "link"}}</a>{{ end -}}
|
||||
{{- with .Get "caption" -}}
|
||||
<figcaption>
|
||||
{{- . -}}
|
||||
</figcaption>
|
||||
{{- end -}}
|
||||
</figure>
|
1
src/themes/vugo/layouts/shortcodes/italic-gay.html
Normal file
|
@ -0,0 +1 @@
|
|||
<i class="gay">{{ .Get "content" }}</i>
|
4
src/themes/vugo/layouts/shortcodes/noscript.html
Normal file
|
@ -0,0 +1,4 @@
|
|||
<noscript>
|
||||
<hr />
|
||||
<p class="gay">{{ .Get "content" }}</p>
|
||||
</noscript>
|
4
src/themes/vugo/layouts/shortcodes/start-details.html
Normal file
|
@ -0,0 +1,4 @@
|
|||
<details>
|
||||
<summary>
|
||||
{{ .Get "summary" }}
|
||||
</summary>
|
3
src/themes/vugo/layouts/shortcodes/tagcloud.html
Normal file
|
@ -0,0 +1,3 @@
|
|||
{{ if isset .Site.Taxonomies "tags" }}{{ if not (eq (len .Site.Taxonomies.tags) 0) }} <ul id="tagcloud">
|
||||
{{ range $name, $items := .Site.Taxonomies.tags }}{{ $url := printf "%s/%s" "tags" ($name | urlize | lower)}}<li><a href="{{ $url | absURL }}" id="tag_{{ $name }}">{{ $name | title }}</a></li>
|
||||
{{ end }}</ul>{{ end }}{{ end }}
|
BIN
src/themes/vugo/static/fonts/BQN386.ttf
Normal file
48
src/themes/vugo/static/gruvbox.css
Normal file
|
@ -0,0 +1,48 @@
|
|||
:root {
|
||||
/* gruvbox dark */
|
||||
--ansi00: #282828;
|
||||
--ansi00-hard: #1d2021;
|
||||
--ansi00-soft: #32302f;
|
||||
--ansi01: #cc241d;
|
||||
--ansi02: #98971a;
|
||||
--ansi03: #d79921;
|
||||
--ansi04: #458588;
|
||||
--ansi05: #b16286;
|
||||
--ansi06: #689d6a;
|
||||
--ansi07: #a89984;
|
||||
--ansi08: #928374;
|
||||
--ansi09: #fb4934;
|
||||
--ansi10: #b8bb26;
|
||||
--ansi11: #fabd2f;
|
||||
--ansi12: #83a598;
|
||||
--ansi13: #d3869b;
|
||||
--ansi14: #8ec07c;
|
||||
--ansi15: #ebdbb2;
|
||||
--selection-fg: #ededed;
|
||||
--selection-bg: #d65d0e;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme:light){
|
||||
:root{
|
||||
/* gruvbox light */
|
||||
--ansi00: #fbf1c7;
|
||||
--ansi00-hard: #f9f5d7;
|
||||
--ansi00-soft: #f2e5bc;
|
||||
--ansi01: #cc241d;
|
||||
--ansi02: #98971a;
|
||||
--ansi03: #d79921;
|
||||
--ansi04: #458588;
|
||||
--ansi05: #b16286;
|
||||
--ansi06: #689d6a;
|
||||
--ansi07: #7c6f64;
|
||||
--ansi08: #928374;
|
||||
--ansi09: #9d0006;
|
||||
--ansi10: #79740e;
|
||||
--ansi11: #b57614;
|
||||
--ansi12: #076678;
|
||||
--ansi13: #8f3f71;
|
||||
--ansi14: #427b58;
|
||||
--ansi15: #3c3836;
|
||||
--selection-fg: #3c3836;
|
||||
}
|
||||
}
|
BIN
src/themes/vugo/static/logoOptimised.png
Normal file
After Width: | Height: | Size: 15 KiB |
95
src/themes/vugo/static/logoOptimised.svg
Normal file
|
@ -0,0 +1,95 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
viewBox="0 0 128pt 128pt"
|
||||
version="1.1"
|
||||
id="svg363"
|
||||
sodipodi:docname="logoOptimised.svg"
|
||||
width="500"
|
||||
height="500"
|
||||
inkscape:version="1.2.1 (9c6d41e410, 2022-07-14)"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/">
|
||||
<title
|
||||
id="title18">Chaos Wheel</title>
|
||||
<defs
|
||||
id="defs28" />
|
||||
<g
|
||||
id="g22"
|
||||
transform="matrix(2.2026076,0,0,2.2026173,117.84692,117.84376)">
|
||||
<path
|
||||
d="M 60,0 A 60,60 0 0 0 0,60 60,60 0 0 0 60,120 60,60 0 0 0 120,60 60,60 0 0 0 60,0 Z m 0,9.2377 A 50.762,50.762 0 0 1 110.762,59.9997 50.762,50.762 0 0 1 60,110.7617 50.762,50.762 0 0 1 9.238,59.9997 50.762,50.762 0 0 1 60,9.2377 Z"
|
||||
id="path2" />
|
||||
<path
|
||||
transform="scale(0.26458)"
|
||||
d="m 207.87,-113.38 -0.002,203.98 20.121,22.4 17.674,-19.682 0.002,-206.7 H 207.87 Z M 28.49,4.49 5.088,27.82 l 116.5,116.86 26.496,1.4199 -1.2285,-22.879 -118.37,-118.73 z m 397.22,0.60742 -116.48,116.13 -1.332,24.863 24.52,-1.3164 116.63,-116.27 -23.33,-23.402 z m -539.1,202.77 v 37.797 h 206.5 l 20.275,-18.213 v -2.8984 l -18.58,-16.686 h -208.19 z m 680.3,0.002 -205.75,0.006 -20.164,18.111 21.92,19.686 204,-0.006 -0.002,-37.797 z m -259.01,98.023 1.457,27.135 115.67,116.02 23.402,-23.332 -118.28,-118.63 -22.254,-1.1934 z m -159.81,0.01 -25.5,1.3691 -118.12,117.76 23.332,23.4 119.01,-118.65 1.2793,-23.885 z m 78.871,34.244 -19.113,21.283 0.004,205.49 h 37.795 l -0.004,-208.28 -16.611,-18.494 h -2.0703 z"
|
||||
id="path4" />
|
||||
<path
|
||||
transform="scale(0.26458)"
|
||||
d="m -202.22,226.77 76.81,-20.827 56.441,-56.106 -20.368,76.933 20.368,76.933 -56.441,-56.106 z"
|
||||
id="path6" />
|
||||
<path
|
||||
transform="matrix(-0.26458,6.6311e-6,-6.6311e-6,-0.26458,120,120)"
|
||||
d="m -202.22,226.77 76.81,-20.827 56.441,-56.106 -20.368,76.933 20.368,76.933 -56.441,-56.106 z"
|
||||
id="path8" />
|
||||
<path
|
||||
transform="matrix(-2.9145e-6,0.26458,-0.26458,-2.9145e-6,120,0.0023416)"
|
||||
d="m -202.22,226.77 76.81,-20.827 56.441,-56.106 -20.368,76.933 20.368,76.933 -56.441,-56.106 z"
|
||||
id="path10" />
|
||||
<path
|
||||
transform="matrix(-3.7181e-6,-0.26458,0.26458,-3.7181e-6,-0.0032404,120)"
|
||||
d="m -202.22,226.77 76.81,-20.827 56.441,-56.106 -20.368,76.933 20.368,76.933 -56.441,-56.106 z"
|
||||
id="path12" />
|
||||
<path
|
||||
transform="matrix(0.16332,0.16382,-0.16382,0.16332,60.112,-14.187)"
|
||||
d="m -202.22,226.77 76.81,-20.827 56.441,-56.106 -20.368,76.933 20.368,76.933 -56.441,-56.106 z"
|
||||
id="path14" />
|
||||
<path
|
||||
transform="matrix(-0.16333,-0.16382,0.16382,-0.16333,59.885,134.19)"
|
||||
d="m -202.22,226.77 76.81,-20.827 56.441,-56.106 -20.368,76.933 20.368,76.933 -56.441,-56.106 z"
|
||||
id="path16" />
|
||||
<path
|
||||
transform="matrix(-0.16382,0.16332,-0.16332,-0.16382,134.19,60.115)"
|
||||
d="m -202.22,226.77 76.81,-20.827 56.441,-56.106 -20.368,76.933 20.368,76.933 -56.441,-56.106 z"
|
||||
id="path18" />
|
||||
<path
|
||||
transform="matrix(0.16382,-0.16333,0.16333,0.16382,-14.19,59.886)"
|
||||
d="m -202.22,226.77 76.81,-20.827 56.441,-56.106 -20.368,76.933 20.368,76.933 -56.441,-56.106 z"
|
||||
id="path20" />
|
||||
</g>
|
||||
<metadata
|
||||
id="metadata16">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<cc:license
|
||||
rdf:resource="http://artlibre.org/licence/lal" />
|
||||
<dc:title>Chaos Wheel</dc:title>
|
||||
<dc:creator>
|
||||
<cc:Agent>
|
||||
<dc:title>The Voidcruiser</dc:title>
|
||||
</cc:Agent>
|
||||
</dc:creator>
|
||||
</cc:Work>
|
||||
<cc:License
|
||||
rdf:about="http://artlibre.org/licence/lal">
|
||||
<cc:permits
|
||||
rdf:resource="http://creativecommons.org/ns#Reproduction" />
|
||||
<cc:permits
|
||||
rdf:resource="http://creativecommons.org/ns#Distribution" />
|
||||
<cc:permits
|
||||
rdf:resource="http://creativecommons.org/ns#DerivativeWorks" />
|
||||
<cc:requires
|
||||
rdf:resource="http://creativecommons.org/ns#ShareAlike" />
|
||||
<cc:requires
|
||||
rdf:resource="http://creativecommons.org/ns#Notice" />
|
||||
<cc:requires
|
||||
rdf:resource="http://creativecommons.org/ns#Attribution" />
|
||||
</cc:License>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
</svg>
|
After Width: | Height: | Size: 4.4 KiB |
148
src/themes/vugo/static/style.css
Normal file
|
@ -0,0 +1,148 @@
|
|||
@charset "UTF-8";
|
||||
@import "gruvbox.css";
|
||||
|
||||
@font-face {
|
||||
font-family: "BQN386";
|
||||
src: url('/fonts/BQN386.ttf');
|
||||
}
|
||||
|
||||
|
||||
:root{
|
||||
scrollbar-color: var(--ansi08) transparent;
|
||||
}
|
||||
:target{border-bottom: 1pt dashed; background-color: var(--ansi00-soft);}
|
||||
::selection{
|
||||
background-color: var(--selection-bg);
|
||||
color: var(--selection-fg);
|
||||
}
|
||||
a:hover::selection{ background-color: var(--ansi03); }
|
||||
html{
|
||||
color: var(--ansi15);
|
||||
background-color: var(--ansi00-hard);
|
||||
}
|
||||
body{ padding: 0; margin: 0; }
|
||||
a:hover:active{ background-color: var(--ansi03); }
|
||||
a{ color: var(--ansi11); }
|
||||
a:hover{ color: var(--ansi00-hard); background-color: var(--ansi11); text-decoration: none;}
|
||||
a:hover > code{ color: var(--ansi00-hard); background-color: var(--ansi11); text-decoration: none;}
|
||||
audio{ width: 100%; display: block; margin-top: 1em; }
|
||||
a.anchor{visibility:hidden; text-decoration: none;}
|
||||
h1:hover>a,h2:hover>a,h3:hover>a,h4:hover>a,h5:hover>a,h6:hover>a{visibility:visible;}
|
||||
main{ margin-left: 12em; margin-right: 1em; padding: 1em; border:1pt solid; margin-bottom: 1em;}
|
||||
main h1{border-bottom: 1pt solid;}
|
||||
main a h1 {border-bottom: transparent 0pt solid;}
|
||||
li::marker{color: var(--ansi05);}
|
||||
menu{
|
||||
height: 100%;
|
||||
padding: 0.5em;
|
||||
float:left;
|
||||
background: var(--ansi00-soft);
|
||||
border-right:solid 1pt;
|
||||
border-bottom: solid 1pt;
|
||||
width: 10em;
|
||||
text-transform: capitalize;
|
||||
margin-top:0;
|
||||
}
|
||||
menu li{list-style: none; list-style-position:outside;}
|
||||
menu a{ display: block;}
|
||||
nav{
|
||||
border-bottom: 1pt solid;
|
||||
margin-bottom: .5em;
|
||||
}
|
||||
summary{cursor:pointer;}
|
||||
summary:hover{ color: var(--ansi00-hard); background-color: var(--ansi11); }
|
||||
footer{text-align:center; border-top:1pt solid; margin-top: 1em;}
|
||||
footer a{text-decoration:none;}
|
||||
footer .mirror{text-align:center; color: var(--ansi11);}
|
||||
code{ background: var(--ansi00); }
|
||||
pre{ overflow-x: auto; background: var(--ansi00); padding: 1em; border-radius: 1em;}
|
||||
blockquote{ background-color: var(--ansi00); padding:0.5em; border-radius:1em;}
|
||||
hr{color: var(--ansi00);}
|
||||
.hyperbowl{ font-style: italic; font-size: 1.2em;}
|
||||
.logo{
|
||||
padding: 5pt 0;
|
||||
margin:0;
|
||||
width:100%;
|
||||
border-bottom: 2pt solid;
|
||||
background-color: var(--ansi00-soft);
|
||||
background: linear-gradient(to right,var(--ansi05),var(--ansi00));
|
||||
}
|
||||
.logo a{ color: var(--ansi00); text-decoration: none; }
|
||||
.logo a:hover{ background-color: var(--ansi03); }
|
||||
.logo img{
|
||||
float:left;
|
||||
max-height:5em;
|
||||
}
|
||||
.about ul{list-style:none;}
|
||||
.about img{height:10em; float:left;}
|
||||
@media (prefers-color-scheme:dark){
|
||||
.about img{filter:invert(.92)}
|
||||
}
|
||||
header{text-transform: capitalize; text-align:center;border-bottom:2pt solid; font-weight:bold;}
|
||||
.page-head{text-transform: capitalize; text-align:center;border-bottom:2pt solid; font-weight:bold;}
|
||||
@media (max-width: 800px){
|
||||
main{max-width:100%; margin: 1em;}
|
||||
menu{padding:0; width: 100%; border-right:solid 0pt;}
|
||||
menu a{ padding-left: 1em;}
|
||||
|
||||
.post-content{
|
||||
max-width: 80ch;
|
||||
}
|
||||
}
|
||||
@media (min-width: 80ch){
|
||||
.post-content{ column-count: 1;
|
||||
max-width: 80ch;
|
||||
margin: auto;
|
||||
}
|
||||
}
|
||||
@media (min-width: 160ch){
|
||||
.post-content{ column-count: 2;
|
||||
max-width: 160ch;
|
||||
margin: auto;
|
||||
}
|
||||
}
|
||||
@media (min-width: 240ch){
|
||||
.post-content{ column-count: 3;
|
||||
max-width: 240ch;
|
||||
margin: auto;
|
||||
}
|
||||
}
|
||||
.post-content{
|
||||
margin: auto;
|
||||
}
|
||||
.post-content img{
|
||||
max-width: 100%;
|
||||
text-align: center;
|
||||
}
|
||||
.stickers{ text-align:center; }
|
||||
.gay{
|
||||
background:linear-gradient(-45deg, var(--ansi01), var(--ansi02), var(--ansi03), var(--ansi04), var(--ansi05), var(--ansi06));
|
||||
background-clip: text;
|
||||
background-size:300%;
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
animation:gay 8s ease-in-out infinite;
|
||||
-moz-animation: gay 8s ease-in-out infinite;
|
||||
-webkit-animation: gay 8s ease-in-out infinite;
|
||||
}
|
||||
table{ border-spacing:0; border: 1pt solid; padding:1em; margin:1em 0; }
|
||||
td{ border-bottom: 1pt dashed; }
|
||||
.posts{ overflow-x: auto; }
|
||||
.posts p{ text-align: center;}
|
||||
.post-meta{ color: var(--ansi08); border-bottom: 1pt solid var(--ansi15); padding-bottom:.25em; margin-bottom: 1em;}
|
||||
.post-meta a{ text-decoration: none; color: inherit;}
|
||||
.post-meta a:hover{ color: var(--ansi15); background-color: var(--ansi00-hard);}
|
||||
.post-entry-meta{ width: 100%; background-color: var(--ansi00); padding:.5em;}
|
||||
.post-entry-meta table{color: var(--ansi07); margin-bottom: .5em;}
|
||||
.button{padding:.3em;border-radius:.3em;}
|
||||
.pagination__buttons{display:flex; justify-content:center;}
|
||||
.pagination__buttons .next::after{content:" →";}
|
||||
.pagination__buttons .prev::before{content:"← ";}
|
||||
.language-bqn{font-family: "BQN386", monospace;}
|
||||
.language-orca{font-family: "BQN386", monospace;}
|
||||
|
||||
@keyframes gay {
|
||||
0%{background-position: 0px 50%;}
|
||||
50%{background-position: 100% 50%;}
|
||||
10%{background-position: 0px 50%;}
|
||||
}
|