Removes old blog
This commit is contained in:
@@ -1,11 +0,0 @@
|
||||
pipeline:
|
||||
build:
|
||||
image: jbrechtel/zola:latest
|
||||
commands:
|
||||
- "zola build"
|
||||
|
||||
deploy:
|
||||
image: jbrechtel/rsync:latest
|
||||
commands:
|
||||
- ./ci-deploy
|
||||
secrets: [ DEPLOY_USER, DEPLOY_PATH, DEPLOY_HOST, DEPLOY_PASSWORD ]
|
||||
19
README.md
19
README.md
@@ -1,19 +0,0 @@
|
||||
# My personal website
|
||||
|
||||
## Building
|
||||
This is a static site built with [Zola](https://www.getzola.org/).
|
||||
|
||||
It uses Zola inside of docker to build and serve locally. In 'production' it's just static files served up by [Caddy](https://caddyserver.com)
|
||||
|
||||
You must have [Docker](https://docker.com) installed to build the site.
|
||||
|
||||
Once you clone this repo you can
|
||||
|
||||
`./build` to build the static HTML.
|
||||
|
||||
`./serve` to run a local webserver to serve up the content and re-build on changes.
|
||||
|
||||
`./zola` will let you run zola itself for other Zola specific commands.
|
||||
|
||||
## Deploying
|
||||
Manual deploys can be done with `rsync` but the site should be built automatically upon a push to the `main` branch.
|
||||
10
ci-deploy
10
ci-deploy
@@ -1,10 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
mkdir -p $HOME/.ssh
|
||||
ssh-keyscan -t rsa $DEPLOY_HOST >> $HOME/.ssh/known_hosts
|
||||
|
||||
chown -R $(whoami) .
|
||||
|
||||
sshpass -p "$DEPLOY_PASSWORD" \
|
||||
rsync -avzrO \
|
||||
./public/ $DEPLOY_USER@$DEPLOY_HOST:$DEPLOY_PATH
|
||||
27
config.toml
27
config.toml
@@ -1,27 +0,0 @@
|
||||
base_url = "https://jamesbrechtel.com"
|
||||
|
||||
compile_sass = true
|
||||
|
||||
build_search_index = true
|
||||
|
||||
theme = "apollo"
|
||||
|
||||
title = "jamesbrechtel.com"
|
||||
[markdown]
|
||||
highlight_code = true
|
||||
highlight_theme = "ayu-dark"
|
||||
|
||||
[extra]
|
||||
theme = "dark"
|
||||
socials = [
|
||||
{ name = "mastodon", url = "https://hachyderm.io/web/@eightball", icon = "mastodon" },
|
||||
{ name = "github", url = "https://github.com/jbrechtel/", icon = "github" },
|
||||
{ name = "twitter", url = "https://twitter.com/8brechtel", icon = "twitter" },
|
||||
]
|
||||
|
||||
menu = [
|
||||
{ name = "/posts", url = "/posts", weight = 1 },
|
||||
{ name = "/projects", url = "/projects", weight = 2 },
|
||||
{ name = "/about", url = "/about", weight = 3 },
|
||||
{ name = "/now", url = "/now", weight = 3 },
|
||||
]
|
||||
@@ -1,4 +0,0 @@
|
||||
+++
|
||||
[extra]
|
||||
section_path = "posts/_index.md"
|
||||
+++
|
||||
@@ -1,6 +0,0 @@
|
||||
+++
|
||||
title = "About"
|
||||
path = "about"
|
||||
+++
|
||||
|
||||
Bikes, Hikes, Haskell
|
||||
@@ -1,27 +0,0 @@
|
||||
+++
|
||||
title = "Now"
|
||||
path = "now"
|
||||
+++
|
||||
|
||||
# Books
|
||||
I'm enjoying reading [The
|
||||
Peripheral](https://en.wikipedia.org/wiki/The_Peripheral) while I await the
|
||||
release of Children of Memory.
|
||||
|
||||
It's a bit strange just how wildly different the plot is between The Peripheral
|
||||
book and The Peripheral TV show. They're both fun but are really just entirely
|
||||
different plots involving the same characters.
|
||||
|
||||
I'm also slowly reading [Buddhism without
|
||||
beliefs](https://www.goodreads.com/book/show/90557.Buddhism_without_Beliefs`).
|
||||
I've heard the author on a few podcasts
|
||||
|
||||
# Podcasts
|
||||
|
||||
[If books could
|
||||
kill](https://podcasts.apple.com/us/podcast/if-books-could-kill/id1651876897)
|
||||
is my favorite new podcast.
|
||||
|
||||
# Other
|
||||
I'm really enjoying Mastodon these days. It has a nice "early Twitter" feel but
|
||||
with a lot more people than Twitter did at the time, for me.
|
||||
@@ -1,6 +0,0 @@
|
||||
+++
|
||||
paginate_by = 7
|
||||
path = "posts"
|
||||
title = "Posts"
|
||||
sort_by = "date"
|
||||
+++
|
||||
@@ -1,54 +0,0 @@
|
||||
+++
|
||||
title = "Structed and passively collected metrics via AWS CloudWatch"
|
||||
date = "2022-11-12"
|
||||
+++
|
||||
|
||||
AWS is a vast and sprawling set of services. It can be hard to find the hidden
|
||||
gems like this one so I wanted to point this one out.
|
||||
|
||||
Structured metrics are very helpful to monitoring the health and function of an
|
||||
software system.
|
||||
|
||||
- Do you want to know how long a particular transaction typically takes?
|
||||
- How fast your database queries are?
|
||||
- How long external APIs take to respond?
|
||||
- Fire an alert when a particular function on the site happens too many times? Or too few times?
|
||||
|
||||
...plus a million other things specific to whatever system you're working on.
|
||||
|
||||
There are a lot of great tools for doing this and one that you might not know
|
||||
about is AWS CloudWatch Metric Filters. If you're already on AWS then you
|
||||
should consider these because it requires only that your application logs to CloudWatch.
|
||||
|
||||
If you're on ECS then the
|
||||
[awslogs](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/using_awslogs.html)
|
||||
log driver for Docker gets you that nearly for free. By "free" I mean that your
|
||||
application itself can have *zero* dependencies on AWS services and not require
|
||||
any AWS credentials or libraries to start pumping out metrics that you can
|
||||
visualize, alert on and record over time.
|
||||
|
||||
The [AWS
|
||||
docs](https://docs.aws.amazon.com/AmazonCloudWatch/latest/logs/MonitoringLogData.html)
|
||||
themselves offer the canonical reference for configuring these so I won't go
|
||||
into detail here.
|
||||
|
||||
However, the gist is that for a log filter you define the following properties
|
||||
|
||||
- A filter pattern for extracting a discrete metric value out of a log entry
|
||||
- A metric name to store the value in
|
||||
- An optional dimension for sub-classifying the value
|
||||
- And finally a log group to extract the metric values from
|
||||
|
||||
After that you just run the application and as the logs roll in the metric
|
||||
values get pumped out. Then you can [define alarms for
|
||||
alerting](https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/Create-alarm-on-metric-math-expression.html)
|
||||
on them, [graph
|
||||
them](https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/CloudWatch_Dashboards.html),
|
||||
[define autoscaling
|
||||
rules](https://docs.aws.amazon.com/autoscaling/ec2/userguide/as-scaling-simple-step.html#policy-creating-alarm-console)
|
||||
from them and more.
|
||||
|
||||
To conclude - AWS is big and hairy. While there are benefits to staying
|
||||
platform agnostic, some AWS services don't require much or any coupling of your
|
||||
application code to take advantage of. Cloudwatch Metrics is one of those
|
||||
services and you can get a lot of value out of it with not much effort.
|
||||
@@ -1,25 +0,0 @@
|
||||
+++
|
||||
title = "Syntax test post"
|
||||
date = "2022-11-08"
|
||||
+++
|
||||
|
||||
## Code Block
|
||||
|
||||
```haskell
|
||||
main :: IO ()
|
||||
main = do
|
||||
putStrLn "Nice"
|
||||
}
|
||||
```
|
||||
|
||||
{% mermaid() %}
|
||||
journey
|
||||
title My working day
|
||||
section Go to work
|
||||
Make tea: 5: Me
|
||||
Go upstairs: 3: Me
|
||||
Do work: 1: Me, Cat
|
||||
section Go home
|
||||
Go downstairs: 5: Me
|
||||
Sit down: 5: Me
|
||||
{% end %}
|
||||
@@ -1,30 +0,0 @@
|
||||
+++
|
||||
title = "My multiroom audio setup"
|
||||
date = "2022-11-08"
|
||||
+++
|
||||
|
||||
I've put my home audio solution together out of the following components.
|
||||
|
||||
- [Snapcast](https://github.com/badaix/snapcast)
|
||||
- [MPD](https://www.musicpd.org/)
|
||||
- [Librespot](https://github.com/librespot-org/librespot)
|
||||
- [Shairport-sync](https://github.com/mikebrady/shairport-sync)
|
||||
|
||||
- A mini-PC in my closet running the above software
|
||||
- Two Raspberry Pi 4s
|
||||
- Four Raspberry Pi Zero Ws
|
||||
- Some desktop speakers and some Bluetooth speakers (wired to the Pis)
|
||||
|
||||
Each of the Raspberry Pis is in a room or porch attached to a speaker.
|
||||
|
||||
Snapcast lets me take an audio source and synchronize it across multiple
|
||||
clients. Each of the Raspberry Pis are running a `snapclient` instance and play
|
||||
whatever the `snapserver` instance tells them to.
|
||||
|
||||
Snapcast is setup to send whichever of the streams (MPD, Spotify, Shairport-sync/AirPlay) is playing audio to each of the clients that are connected to it.
|
||||
|
||||
This lets me or anyone else on my WiFi network play directly on one or more of the speakers - each named for the room that they're in using either Spotify, AirPlay, picking from my own music collection or by pointing at a URL (like to a podcast episode).
|
||||
|
||||
This works out great and we've used it at home for the past year.
|
||||
|
||||
I'd like to get the podcast experience to a more seamless place but it's pretty OK right now using AirMusic on my phone to play audio to the speakers over AirPlay.
|
||||
@@ -1,43 +0,0 @@
|
||||
+++
|
||||
title = "HTTPS @ Home"
|
||||
date = "2022-11-08"
|
||||
+++
|
||||
|
||||
I run a lot of services at home.
|
||||
|
||||
This includes, but isn't limited to
|
||||
|
||||
- [ArchiveBox](https://archivebox.io/)
|
||||
- [VaultWarden](https://github.com/dani-garcia/vaultwarden)
|
||||
- [Navidrome](https://github.com/navidrome/navidrome)
|
||||
- [Plex](https://plex.tv)
|
||||
- [LibrePhotos](https://github.com/LibrePhotos/librephotos)
|
||||
- This blog
|
||||
|
||||
and a lot more.
|
||||
|
||||
Pretty much anything that's served up over HTTP is always nice if not necessary to have behind TLS.
|
||||
|
||||
[LetsEncrypt](https://letsencrypt.org/) long ago brought free certs to the masses and there are a lot of tools for automating that nowadays.
|
||||
|
||||
My preferred approach for getting all the unnecessary nonsense I self-host at home behind TLS is [Caddy](https://caddyserver.com).
|
||||
|
||||
I have a super straight forward setup, generally:
|
||||
|
||||
- Run Caddy in a docker container
|
||||
- Create a wildcard CNAME record in my DNS pointing at my home's (effectively) static IP
|
||||
- Add an entry in my Caddyfile for each services I'm running at home on its own subdomain
|
||||
- If it's a service then I add it with a `reverse_proxy` block
|
||||
- If it's a static site (like this) then there's a block for
|
||||
- If it's something I want only accessible on my home network then I put a block like
|
||||
|
||||
```txt
|
||||
@local_network {
|
||||
path *
|
||||
remote_ip <home subnet here>
|
||||
}
|
||||
```
|
||||
|
||||
in the directive. And voila.
|
||||
|
||||
Then tell Caddy to reload the config and I'm done.
|
||||
@@ -1,32 +0,0 @@
|
||||
+++
|
||||
title = "vi-mode editing in most places"
|
||||
date = "2022-11-13"
|
||||
+++
|
||||
|
||||
# Vi/Vim bindings
|
||||
|
||||
For my sake, I prefer to have Vim bindings in as many places as possible.
|
||||
|
||||
Most shells can be configured to use Vim bindings by putting `set -o vi` somewhere in your shell startup script.
|
||||
|
||||
If you're using ZSH then you'll probably want an additional binding to restore CTRL-R reverse history search.
|
||||
|
||||
`bindkey '^R' history-incremental-search-backward`
|
||||
|
||||
For CLI tools that use the `readline` library then you can configure its input mode using a `.inputrc` file in your `$HOME` directory.
|
||||
|
||||
This affects REPLs like `ghci` and tools like `psql`.
|
||||
|
||||
```txt
|
||||
set editing-mode vi
|
||||
$if mode=vi
|
||||
|
||||
set keymap vi-command
|
||||
# these are for vi-command mode
|
||||
Control-l: clear-screen
|
||||
|
||||
set keymap vi-insert
|
||||
# these are for vi-insert mode
|
||||
Control-l: clear-screen
|
||||
$endif
|
||||
```
|
||||
@@ -1,109 +0,0 @@
|
||||
+++
|
||||
title = "The allure of cloud services - AWS Transfer Server"
|
||||
date = "2023-01-05"
|
||||
+++
|
||||
|
||||
## Build vs buy
|
||||
|
||||
I like cloud services as much as the next person. Definitely not _too_ much but
|
||||
definitely, umm, much?
|
||||
|
||||
Anyway, I think cloud specific services can _sometimes_ be worth the sort of
|
||||
vendor lock-in that they facilitate.
|
||||
|
||||
Vendor lock in sucks, but so does re-inventing the wheel. As mentioned in an
|
||||
[earlier post](/posts/cloudwatch-metric-filters/) it can be pretty nice to
|
||||
create alerts and application metrics passively from your log files.
|
||||
|
||||
Knowing when to use vendor-specific services and APIs versus rolling your own
|
||||
is, in some ways, more art than science. Reasonable teams won't change their
|
||||
infrastructure provider very many times and the difference between the two
|
||||
choices is often less about lock-in and more about whether or not the problem
|
||||
you're solving is really the special unicorn that you want it to be or not.
|
||||
|
||||
Sometimes, the cloud vendor solution is just bad, though.
|
||||
|
||||
## Case in point - AWS Transfer Server
|
||||
|
||||
On my team, we need to accept SFTP file transfers from about 20-30 vendors.
|
||||
|
||||
Sadly we can't avoid SFTP for this. It's an industry standard for the kind of
|
||||
data we're receiving and we aren't in a position to dictate otherwise.
|
||||
|
||||
AWS offers a suite of products which include a hosted SFTP solution, [AWS Transfer](https://aws.amazon.com/aws-transfer-family/).
|
||||
|
||||
SFTP is simple enough and I'd ultimately like the uploaded files in an S3
|
||||
bucket so seems like a great fit, right?
|
||||
|
||||
Wrong.
|
||||
|
||||
You have to jump through [a hundred](https://aws.amazon.com/secrets-manager/)
|
||||
[different](https://aws.amazon.com/lambda/)
|
||||
[hoops](https://aws.amazon.com/iam/) to event attempt to have password-based
|
||||
authentication working on this service.
|
||||
|
||||
The services involved to make password based authentication work here aren't in
|
||||
and of themselves a problem. The problem is the multiple points of failure they
|
||||
represent. Each service can fail on its own, the permissions between the
|
||||
services can fail, or the code in the Lambda itself can fail.
|
||||
|
||||
Debugging it was a nightmare. It ultimately worked but I wasn't sure _why_. I
|
||||
had a strange permissions error about the IAM Role being used to either launch
|
||||
the Lambda or the role used to access the S3 bucket for uploads. I don't
|
||||
remember at this point. But it was really clear that if it ever broke again
|
||||
then myself or whatever poor soul had to work on it was going to going to
|
||||
regret the choice of technologies to make this work.
|
||||
|
||||
You know what is easy and simple and well understood? OpenSSH and Linux user
|
||||
accounts.
|
||||
|
||||
In the end, the AWS Transfer option proved to be just complicated and enciting
|
||||
enough for me to waste about a day and a half on it before I realize, in a
|
||||
drunken stupor, that I'd be better off creating a snow-flaked EC2 instance and
|
||||
calling it a day.
|
||||
|
||||
## Resisting temptation
|
||||
|
||||
I do think I could have identified that this was a bad idea before I wasted
|
||||
over a day. One doesn't have to be doomed to repeat this mistake to learn the
|
||||
lesson here.
|
||||
|
||||
The way to avoid this is to interrogate the path before you start.
|
||||
|
||||
Empathy is a good tool, here.
|
||||
|
||||
Picture the thing working as intended. Then imagine someone else (or future you
|
||||
a year from now) having to search for how to change something or track down an
|
||||
error.
|
||||
|
||||
- Are they likely to find many other people using these services? (In this case, no)
|
||||
- Are the services involved purpose build for the task at hand? (In this case, no none of them)
|
||||
- Does your team have pre-existing expertise around the services involved? (Again, no)
|
||||
- What benefits does this approach yield over a traditional solution? (Upload to S3 is nice)
|
||||
- Are those benefits important to your use case? (Not for us)
|
||||
|
||||
I didn't ask myself of those questions. I _really_ should have. Because the
|
||||
answers are easy to get and they clearly indicate AWS Transfer Server is not
|
||||
the best answer.
|
||||
|
||||
# Timeboxes
|
||||
Another tool that could have helped here is a timebox.
|
||||
|
||||
A timebox is when you decided to give yourself a fixed, and often pretty short,
|
||||
amount of time to accomplish something or to at least get meaningful insight
|
||||
into a potential solution.
|
||||
|
||||
Sometimes I will demand that a working solution can be done inside a given
|
||||
timebox or then the approach is abandoned. But other times I might just say
|
||||
that I'm going to spend X amount of time on an approach and then make a point
|
||||
to re-evaluate how long the complete solution will take.
|
||||
|
||||
It's important to remember how valuable timeboxes can be. The more time we
|
||||
waste on things like this then the more attached we become to it. [Sunk
|
||||
cost](https://en.wikipedia.org/wiki/Escalation_of_commitment) can get even the
|
||||
best of us.
|
||||
|
||||
For me the simplest tool for avoiding sunk cost fallacy is to timebox risky
|
||||
endeavors like this one. The time inside a timebox starts out as a write off. I
|
||||
never feel bad about discarding failed results inside a timebox. I should use
|
||||
them more aggressively.
|
||||
@@ -1,15 +0,0 @@
|
||||
+++
|
||||
title = "Rhetorical techniques - The uncharitably extrapolated strawman"
|
||||
date = "2022-12-06"
|
||||
+++
|
||||
|
||||
Ever find yourself arguing with someone? It's usually an idiot, isn't it?
|
||||
|
||||
When arguing an idiot will often "take a position" or "make a claim".
|
||||
|
||||
They'll say something like "you shouldn't kill people" to defend their stupid little point about "the law" or "murder" or some other nonsense.
|
||||
|
||||
--- draft below
|
||||
|
||||
- "here's what you do"
|
||||
- "I'm really surprised to hear you'd just let Nazis kill your friends and family, but OK"
|
||||
@@ -1,24 +0,0 @@
|
||||
+++
|
||||
title = "Twitter's ultimate demise"
|
||||
date = "2022-11-18"
|
||||
+++
|
||||
|
||||
I _think_ Twitter will die but I don't exactly hope that it will.
|
||||
|
||||
The best thing that could come out of this is that Mastodon gets critical mass
|
||||
such that it can survive on its own. We don't have any popular services besides
|
||||
email which are decentralized anymore.
|
||||
|
||||
Mastodon is more complicated than Twitter because you have to "pick a server"
|
||||
and that's true but at the end of the day all that matters is where the people
|
||||
are.
|
||||
|
||||
If enough content generating posters will just make the shift at once then this
|
||||
thing can be self-sustaining...even if it is more complicated.
|
||||
|
||||
Twitter was more complicated than not using Twitter and people still started
|
||||
using it. This is perenially true for all new things.
|
||||
|
||||
Anyway, it's late and I'm tired.
|
||||
|
||||
Long live microblogging.
|
||||
@@ -1,59 +0,0 @@
|
||||
+++
|
||||
title = "Setting up Woodpecker CI at home"
|
||||
date = "2022-11-10"
|
||||
+++
|
||||
|
||||
[Woodpecker CI](https://woodpecker-ci.org/) is very straight forward and seems to basically have what I want from a CI tool without much beyond that.
|
||||
|
||||
- In-repo pipeline definition
|
||||
- Docker job execution
|
||||
- Docker image for deploying server and agents
|
||||
- Parallel execution on a single agent (looking at you GitHub)
|
||||
|
||||
But of course I still had to fight with it for a couple hours to make it do something super simple. Because everything is awful and I'm an idiot.
|
||||
|
||||
I just wanted to have it build (`zola build`) this site and deploy it (`rsync`).
|
||||
|
||||
Building was easy enough - I needed a docker image with Zola on it. I couldn't use the "official" zola one, though, because it lacks even `/bin/sh` and Woodpecker needs some way to execute your scripts on the container you want a job to run in.
|
||||
|
||||
So I made a small alpine based image to do that - no problem.
|
||||
|
||||
Then I needed to rsync. And SSH auth and secrets.
|
||||
|
||||
There are a million ways for things to go wrong here that are painful and just take time to figure out.
|
||||
|
||||
Here are the issues I ran into - many were my fault and others were simply not-great UX from Woodpecker.
|
||||
|
||||
- Woodpecker supports per-repo secrets - great - but they have to be specified
|
||||
by name in the YAML of each pipeline step that wants to use them. This is
|
||||
clearly stated in the docs and I followed the instructions clearly but at one
|
||||
point I removed them from the YAML and didn't realize it.
|
||||
|
||||
Unfortunately Woodpecker doesn't have a great way of letting you know you're
|
||||
referencing secrets which you haven't asked for because it exposes secrets as
|
||||
just plain environment variables.
|
||||
|
||||
I don't really know what I would want from Woodpecker here - this was my
|
||||
fault and it was hard to notice once I'd mistakenly removed the secrets.
|
||||
|
||||
- Putting "-" in your secret names breaks your YAML and Woodpecker fails
|
||||
without much information when you give it broken pipeline YAML.
|
||||
|
||||
- Copying environment variables into files and using them as ssh keys is super
|
||||
annoying and error prone. Between key file permissions and the whole process
|
||||
failing because the file contents were empty because the secret being echo'ed
|
||||
into the file was actually not present. Ugh.
|
||||
|
||||
- SSH [TOFU](https://en.wikipedia.org/wiki/Trust_on_first_use) is great for users but annoying for automation.
|
||||
You have to pick from one of several annoying options.
|
||||
- use ssh-keyscan during the automation to trust the host key
|
||||
- Muck with ssh command line arguments to turn StrictHostKeyChecking off
|
||||
- Pre-emptively load an `authorized_hosts` file onto the machine you're deploying from.
|
||||
|
||||
The last option is probably the best because it's the most secure but it's
|
||||
also annoying because it means you can't use a generic image for that
|
||||
deployment step.
|
||||
|
||||
Ultimately it all worked out - I ended up using some Alpine docker images, sshpass and rsync.
|
||||
|
||||
But it's always a fight to get these things working. Even when I'm using software that seems like such a nice fit for my personal approach like Woodpecker.
|
||||
@@ -1,5 +0,0 @@
|
||||
+++
|
||||
title = "Projects"
|
||||
sort_by = "weight"
|
||||
template = "cards.html"
|
||||
+++
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 6.1 MiB |
@@ -1,11 +0,0 @@
|
||||
+++
|
||||
title = "Apollo"
|
||||
description = "Modern and minimalistic blog theme."
|
||||
weight = 1
|
||||
|
||||
[extra]
|
||||
local_image = "/projects/project-1.jpg"
|
||||
link_to = "https://github.com/not-matthias/apollo"
|
||||
+++
|
||||
|
||||
Example project page
|
||||
@@ -1,11 +0,0 @@
|
||||
+++
|
||||
title = "Project 2"
|
||||
description = "Example description"
|
||||
weight = 1
|
||||
|
||||
[extra]
|
||||
# You can also crop the image in the url by adjusting w=/h=
|
||||
remote_image = "https://images.unsplash.com/photo-1523821741446-edb2b68bb7a0?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1470&q=80"
|
||||
+++
|
||||
|
||||
Example project page
|
||||
@@ -1,10 +0,0 @@
|
||||
+++
|
||||
title = "Project 3"
|
||||
description = "Example description"
|
||||
weight = 1
|
||||
|
||||
[extra]
|
||||
remote_image = "https://images.unsplash.com/photo-1462556791646-c201b8241a94?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1465&q=80"
|
||||
+++
|
||||
|
||||
Example project page
|
||||
@@ -1,10 +0,0 @@
|
||||
+++
|
||||
title = "Project 4"
|
||||
description = "Example description with a lot of words but without any meaning. Why use lorem ipsum when you can just write a lot of text that has no underlying meaning?"
|
||||
weight = 1
|
||||
|
||||
[extra]
|
||||
remote_image = "https://images.unsplash.com/photo-1620121692029-d088224ddc74?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1632&q=80"
|
||||
+++
|
||||
|
||||
Example project page
|
||||
@@ -1,7 +0,0 @@
|
||||
+++
|
||||
title = "Project 4"
|
||||
description = "Example description"
|
||||
weight = 1
|
||||
+++
|
||||
|
||||
Example project page
|
||||
@@ -1,15 +0,0 @@
|
||||
@font-face {
|
||||
font-family: 'Jetbrains Mono';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
src: url('../fonts/JetbrainsMono/JetBrainsMono-Regular.ttf'), local('ttf');
|
||||
font-display: swap;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Space Grotesk';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
src: url('../fonts/SpaceGrotesk/SpaceGrotesk-Regular.ttf'), local('ttf');
|
||||
font-display: swap;
|
||||
}
|
||||
@@ -1,49 +0,0 @@
|
||||
@import "parts/_cards.scss";
|
||||
@import "parts/_code.scss";
|
||||
@import "parts/_header.scss";
|
||||
@import "parts/_image.scss";
|
||||
@import "parts/misc.scss";
|
||||
@import "parts/table.scss";
|
||||
@import "parts/tags.scss";
|
||||
|
||||
:root {
|
||||
/* Used for: block comment, hr, ... */
|
||||
--border-color: var(--bg-1);
|
||||
|
||||
/* Fonts */
|
||||
--text-font: 'Jetbrains Mono';
|
||||
--header-font: 'Space Grotesk';
|
||||
--code-font: 'Jetbrains Mono';
|
||||
}
|
||||
|
||||
html {
|
||||
background-color: var(--bg-0);
|
||||
color: var(--text-0);
|
||||
font-family: var(--text-font);
|
||||
line-height: 1.6em;
|
||||
}
|
||||
|
||||
.content {
|
||||
max-width: 944px;
|
||||
margin: 0 auto;
|
||||
padding: 0 24px;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
@media all and (min-width:640px) {
|
||||
html {
|
||||
font-size: 16.5px;
|
||||
}
|
||||
}
|
||||
|
||||
@media all and (min-width:720px) {
|
||||
html {
|
||||
font-size: 17px;
|
||||
}
|
||||
}
|
||||
|
||||
@media all and (min-width:960px) {
|
||||
html {
|
||||
font-size: 18px;
|
||||
}
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
.cards {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
|
||||
grid-template-rows: auto;
|
||||
gap: 24px;
|
||||
padding: 12px 0;
|
||||
}
|
||||
|
||||
.card {
|
||||
min-height: 100px;
|
||||
background: var(--bg-2);
|
||||
border: 2px solid var(--border-color);
|
||||
border-radius: 10px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.card-info {
|
||||
padding: 0 24px 24px 24px;
|
||||
}
|
||||
|
||||
.card-title {
|
||||
margin-top: 0.7em;
|
||||
}
|
||||
|
||||
.card-image {
|
||||
border: unset;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.card-image-placeholder {
|
||||
height: 12px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.card-description {
|
||||
margin-top: 0.5em;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
@media all and (max-width:720px) {
|
||||
.cards {
|
||||
gap: 18px;
|
||||
}
|
||||
}
|
||||
@@ -1,113 +0,0 @@
|
||||
|
||||
code {
|
||||
background-color: var(--bg-1);
|
||||
padding: 0.1em 0.2em;
|
||||
border-radius: 5px;
|
||||
border: 1px solid var(--border-color);
|
||||
}
|
||||
|
||||
pre {
|
||||
/* Rounded border */
|
||||
border-radius: 5px;
|
||||
border: 1px solid var(--border-color);
|
||||
|
||||
line-height: 1.4;
|
||||
overflow-x: auto;
|
||||
padding: 1em;
|
||||
}
|
||||
|
||||
pre code {
|
||||
background-color: transparent;
|
||||
color: inherit;
|
||||
font-size: 100%;
|
||||
padding: 0;
|
||||
|
||||
// We only want a border around `code` and not `pre code` blocks.
|
||||
border: 0;
|
||||
}
|
||||
|
||||
pre {
|
||||
font-family: var(--code-font);
|
||||
position: relative;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
}
|
||||
|
||||
pre code[class*="language-"] {
|
||||
-webkit-overflow-scrolling: touch;
|
||||
}
|
||||
|
||||
pre code[class*="language-"]::before {
|
||||
background: black;
|
||||
border-radius: 0 0 0.25rem 0.25rem;
|
||||
color: white;
|
||||
font-size: 12px;
|
||||
letter-spacing: 0.025rem;
|
||||
padding: 0.1rem 0.5rem;
|
||||
position: absolute;
|
||||
right: 0.1rem;
|
||||
margin-top: 0.1rem;
|
||||
text-align: right;
|
||||
text-transform: uppercase;
|
||||
top: 0;
|
||||
}
|
||||
|
||||
pre code[class="language-javaScript"]::before,
|
||||
pre code[class="language-js"]::before {
|
||||
content: "js";
|
||||
background: #f7df1e;
|
||||
color: black;
|
||||
}
|
||||
|
||||
pre code[class*="language-yml"]::before,
|
||||
pre code[class*="language-yaml"]::before {
|
||||
content: "yaml";
|
||||
background: #f71e6a;
|
||||
color: white;
|
||||
}
|
||||
|
||||
pre code[class*="language-shell"]::before,
|
||||
pre code[class*="language-bash"]::before,
|
||||
pre code[class*="language-sh"]::before {
|
||||
content: "shell";
|
||||
background: green;
|
||||
color: white;
|
||||
}
|
||||
|
||||
pre code[class*="language-json"]::before {
|
||||
content: "json";
|
||||
background: dodgerblue;
|
||||
color: #000000;
|
||||
}
|
||||
|
||||
pre code[class*="language-python"]::before,
|
||||
pre code[class*="language-py"]::before {
|
||||
content: "py";
|
||||
background: blue;
|
||||
color: yellow;
|
||||
}
|
||||
|
||||
pre code[class*="language-css"]::before {
|
||||
content: "css";
|
||||
background: cyan;
|
||||
color: black;
|
||||
}
|
||||
|
||||
pre code[class*="language-go"]::before {
|
||||
content: "Go";
|
||||
background: cyan;
|
||||
color: royalblue;
|
||||
}
|
||||
|
||||
pre code[class*="language-md"]::before,
|
||||
pre code[class*="language-md"]::before {
|
||||
content: "Markdown";
|
||||
background: royalblue;
|
||||
color: whitesmoke;
|
||||
}
|
||||
|
||||
pre code[class*="language-rust"]::before,
|
||||
pre code[class*="language-rs"]::before {
|
||||
content: "rust";
|
||||
background: #fff8f6;
|
||||
color: #ff4647;
|
||||
}
|
||||
@@ -1,114 +0,0 @@
|
||||
.page-header {
|
||||
font-size: 3em;
|
||||
line-height: 100%;
|
||||
font-family: var(--header-font);
|
||||
margin: 4rem 0px 1rem 0px;
|
||||
}
|
||||
|
||||
.centered-header {
|
||||
font-family: var(--header-font);
|
||||
|
||||
position: absolute;
|
||||
top: 40%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
text-align: center;
|
||||
font-size: 4em;
|
||||
}
|
||||
|
||||
header {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
justify-content: space-between;
|
||||
padding: 1em 0;
|
||||
}
|
||||
|
||||
header .main {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
justify-content: space-between;
|
||||
align-items: flex-start;
|
||||
gap: 12px;
|
||||
font-size: 1.5rem;
|
||||
|
||||
/* Otherwise header and menu is too close on small screens*/
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.socials {
|
||||
/* flex-child */
|
||||
flex-grow: 0;
|
||||
/* flex-container */
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
justify-content: flex-start;
|
||||
align-items: flex-end;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.social {
|
||||
border-bottom: unset;
|
||||
background-image: unset;
|
||||
padding: 2px;
|
||||
}
|
||||
|
||||
.social>img {
|
||||
border: unset;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
|
||||
}
|
||||
|
||||
.meta {
|
||||
color: #999;
|
||||
letter-spacing: -0.5px;
|
||||
}
|
||||
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
h4,
|
||||
h5,
|
||||
h6 {
|
||||
font-size: 1.2rem;
|
||||
margin-top: 2em;
|
||||
}
|
||||
|
||||
h1::before {
|
||||
color: var(--primary-color);
|
||||
content: "# ";
|
||||
}
|
||||
|
||||
h2::before {
|
||||
color: var(--primary-color);
|
||||
content: "## ";
|
||||
}
|
||||
|
||||
h3::before {
|
||||
color: var(--primary-color);
|
||||
content: "### ";
|
||||
}
|
||||
|
||||
h4::before {
|
||||
color: var(--primary-color);
|
||||
content: "#### ";
|
||||
}
|
||||
|
||||
h5::before {
|
||||
color: var(--primary-color);
|
||||
content: "##### ";
|
||||
}
|
||||
|
||||
h6::before {
|
||||
color: var(--primary-color);
|
||||
content: "###### ";
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
.social>img {
|
||||
filter: invert(1);
|
||||
}
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
img.profile {
|
||||
border: 0px;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
img {
|
||||
border: 3px solid #ececec;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
figure {
|
||||
box-sizing: border-box;
|
||||
display: inline-block;
|
||||
margin: 0;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
figure img {
|
||||
max-height: 500px;
|
||||
}
|
||||
|
||||
@media screen and (min-width: 600px) {
|
||||
figure {
|
||||
padding: 0 40px;
|
||||
}
|
||||
}
|
||||
|
||||
figure h4 {
|
||||
font-size: 1rem;
|
||||
margin: 0;
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
|
||||
figure h4::before {
|
||||
content: "↳ ";
|
||||
}
|
||||
|
||||
svg {
|
||||
max-height: 15px;
|
||||
}
|
||||
@@ -1,63 +0,0 @@
|
||||
.primary-color {
|
||||
color: var(--primary-color);
|
||||
}
|
||||
|
||||
.draft-label {
|
||||
color: var(--hover-color);
|
||||
text-decoration: none;
|
||||
padding: 2px 4px;
|
||||
border-radius: 4px;
|
||||
margin-left: 6px;
|
||||
background-color: var(--primary-color);
|
||||
}
|
||||
|
||||
::-moz-selection {
|
||||
background: var(--primary-color);
|
||||
color: var(--hover-color);
|
||||
text-shadow: none;
|
||||
}
|
||||
|
||||
::selection {
|
||||
background: var(--primary-color);
|
||||
color: var(--hover-color);
|
||||
}
|
||||
|
||||
p {
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
hr {
|
||||
border: 0;
|
||||
border-top: 3px solid var(--border-color);
|
||||
margin: 1em 0;
|
||||
}
|
||||
|
||||
blockquote {
|
||||
border-left: 3px solid var(--border-color);
|
||||
color: #737373;
|
||||
margin: 0;
|
||||
padding-left: 1em;
|
||||
}
|
||||
|
||||
a {
|
||||
border-bottom: 3px solid var(--primary-color);
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
background-color: var(--primary-color);
|
||||
color: var(--hover-color);
|
||||
}
|
||||
|
||||
time {
|
||||
color: grey;
|
||||
}
|
||||
|
||||
/* Remove post list padding */
|
||||
@media screen and (max-width: 600px) {
|
||||
.list>ul {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
table {
|
||||
border-spacing: 0;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
table th {
|
||||
padding: 6px 13px;
|
||||
border: 1px solid #dfe2e5;
|
||||
font-size: large;
|
||||
}
|
||||
|
||||
table td {
|
||||
padding: 6px 13px;
|
||||
border: 1px solid #dfe2e5;
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
.tags li::before {
|
||||
content: "🏷 ";
|
||||
}
|
||||
|
||||
.tags a {
|
||||
border-bottom: 3px solid var(--primary-color);
|
||||
}
|
||||
|
||||
.tags a:hover {
|
||||
color: var(--hover_color);
|
||||
background-color: var(--primary-color);
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
:root {
|
||||
--text-0: rgba(255, 255, 255, 87%);
|
||||
--text-1: rgba(255, 255, 255, 60%);
|
||||
|
||||
--bg-0: #121212;
|
||||
--bg-1: rgba(255, 255, 255, 5%);
|
||||
--bg-2: rgba(23, 23, 23, 100%);
|
||||
|
||||
--primary-color: #ef5350;
|
||||
--hover-color: white;
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
:root {
|
||||
--text-0: rgba(0, 0, 0, 87%);
|
||||
--text-1: rgba(0, 0, 0, 66%);
|
||||
|
||||
--bg-0: #fff;
|
||||
--bg-1: #f2f2f2;
|
||||
--bg-2: #fefefe;
|
||||
|
||||
--primary-color: #ef5350;
|
||||
--hover-color: white;
|
||||
}
|
||||
3
serve
3
serve
@@ -1,3 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
./zola serve --interface 0.0.0.0 --port 7121 --base-url localhost
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 75 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 45 KiB |
@@ -1,19 +0,0 @@
|
||||
{% import "macros/macros.html" as post_macros %}
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
{% include "partials/header.html" %}
|
||||
|
||||
<body>
|
||||
<div class="content">
|
||||
{% include "partials/nav.html" %}
|
||||
|
||||
{# Post page is the default #}
|
||||
{% block main_content %}
|
||||
Nothing here?!
|
||||
{% endblock main_content %}
|
||||
</div>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
|
||||
@@ -1,72 +0,0 @@
|
||||
{% import "macros/macros.html" as post_macros %}
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<script type="module">
|
||||
import mermaid from 'https://unpkg.com/mermaid@9/dist/mermaid.esm.min.mjs';
|
||||
mermaid.initialize({ startOnLoad: true });
|
||||
</script>
|
||||
|
||||
{# Site title #}
|
||||
{% set current_path = current_path | default(value="/") %}
|
||||
{% if current_path == "/" %}
|
||||
<title>
|
||||
{{ config.title | default(value="Home") }}
|
||||
</title>
|
||||
{% else %}
|
||||
<title>
|
||||
{{ page.title | default(value=config.title) | default(value="Post") }}
|
||||
</title>
|
||||
{% endif %}
|
||||
|
||||
{# Favicon #}
|
||||
{% if config.extra.favicon %}
|
||||
<link rel="icon" type="image/png" href={{ config.extra.favicon }} />
|
||||
{% endif %}
|
||||
|
||||
{# Font from cdn or disk #}
|
||||
{% if config.extra.use_cdn | default(value=false) %}
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/jetbrains-mono@1.0.6/css/jetbrains-mono.min.css">
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource/space-grotesk@4.5.8/index.min.css">
|
||||
{% else %}
|
||||
<link href={{ get_url(path="fonts.css") }} rel="stylesheet" />
|
||||
{% endif %}
|
||||
|
||||
{# Analytics #}
|
||||
{% if config.extra.analytics.enabled and config.extra.analytics.goatcounter_user %}
|
||||
{% set user = config.extra.analytics.goatcounter_user %}
|
||||
{% set host = config.extra.analytics.goatcounter_host | default(value="goatcounter.com") %}
|
||||
|
||||
<script data-goatcounter="https://{{ user }}.{{ host }}/count" async src="{{ get_url(path="js/count.js") }}"></script>
|
||||
<noscript>
|
||||
{# EasyList blocks '.com/count?', so we have to use '.com//count' #}
|
||||
<img src="https://{{ user }}.{{ host }}//count?p={{ current_path }}&t={{ page.title | default(value=config.title) }}">
|
||||
</noscript>
|
||||
{% endif %}
|
||||
|
||||
{# RSS #}
|
||||
<link rel="alternate" type="application/atom+xml" title="{{ config.title }}" href="{{ get_url(path="atom.xml", trailing_slash=false) }}">
|
||||
|
||||
{# Theme #}
|
||||
{% set theme = config.extra.theme | default(value="light") %}
|
||||
{% if theme == "dark" %}
|
||||
<link rel="stylesheet" type="text/css" href="{{ get_url(path="theme/dark.css") }}"/>
|
||||
{% elif theme == "light" %}
|
||||
<link rel="stylesheet" type="text/css" href="{{ get_url(path="theme/light.css") }}"/>
|
||||
{% elif theme == "auto" %}
|
||||
<link rel="stylesheet" type="text/css" href="{{ get_url(path="theme/light.css") }}"/>
|
||||
<link rel="stylesheet" type="text/css" href="{{ get_url(path="theme/dark.css") }}" media="(prefers-color-scheme: dark)"/>
|
||||
{% endif %}
|
||||
|
||||
<link rel="stylesheet" type="text/css" media="screen" href={{ get_url(path="main.css") }} />
|
||||
|
||||
|
||||
{% if config.extra.stylesheets %}
|
||||
{% for stylesheet in config.extra.stylesheets %}
|
||||
<link rel="stylesheet" href="{{ get_url(path=stylesheet) }}">
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
</head>
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
<header>
|
||||
<div class="main">
|
||||
<div class="socials">
|
||||
{% for social in config.extra.socials %}
|
||||
<a href="{{ social.url }}" class="social" rel="me">
|
||||
<img alt={{ social.name }} src="/social_icons/{{ social.icon }}.svg">
|
||||
</a>
|
||||
{% endfor %}
|
||||
</div>
|
||||
<a class="h-card" href={{ config.base_url }}>{{ config.title }}</a>
|
||||
</div>
|
||||
|
||||
<nav>
|
||||
{% for menu in config.extra.menu %}
|
||||
<a href={{ menu.url }} style="margin-left: 0.7em">{{ menu.name }}</a>
|
||||
{% endfor %}
|
||||
</nav>
|
||||
</header>
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
<div class="mermaid">
|
||||
{{ body }}
|
||||
</div>
|
||||
Submodule themes/apollo deleted from e6aa946afa
Reference in New Issue
Block a user