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