This commit is contained in:
Joshua Seigler 2025-06-15 01:11:28 -04:00
parent cac775b076
commit 6d54ca2954
11 changed files with 1043 additions and 11 deletions

470
feed.xml
View file

@ -5,11 +5,479 @@
<subtitle>Personal homepage of Joshua Seigler</subtitle>
<link href="https://joshua.seigler.net/feed.xml" rel="self" />
<link href="https://joshua.seigler.net/" />
<updated>2025-05-15T00:00:00Z</updated>
<updated>2025-06-15T00:00:00Z</updated>
<id>https://joshua.seigler.net/</id>
<author>
<name>Joshua Seigler</name>
</author>
<entry>
<title>My Very Own GitHub Pages</title>
<link href="https://joshua.seigler.net/posts/my-very-own-github-pages/" />
<updated>2025-06-15T00:00:00Z</updated>
<id>https://joshua.seigler.net/posts/my-very-own-github-pages/</id>
<content type="html">&lt;p&gt;
I recently started self-hosting
&lt;a href=&quot;https://forgejo.org/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Forgejo&lt;/a&gt;, but
I wanted something to replace GitHub pages, which has been very convenient for
publishing little website projects. My server runs Debian, so I decided to use
&lt;a href=&quot;https://github.com/adnanh/webhook&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;
&gt;webhook&lt;/a
&gt;
and
&lt;a href=&quot;https://caddyserver.com/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Caddy&lt;/a&gt;.
Im very happy how it turned out.
&lt;/p&gt;
&lt;h2 id=&quot;the-result&quot; tabindex=&quot;-1&quot;&gt;
&lt;a
class=&quot;header-anchor&quot;
href=&quot;https://joshua.seigler.net/posts/my-very-own-github-pages/#the-result&quot;
aria-hidden=&quot;true&quot;
&gt;&lt;/a&gt;
The result
&lt;/h2&gt;
&lt;p&gt;
When I push a &lt;code&gt;gh-pages&lt;/code&gt; branch to any public repository on my
Forgejo instance, the name of the repo is used as a domain name (e.g.
&lt;a href=&quot;https://marklink.pages.seigler.net/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;
&gt;marklink.pages.seigler.net&lt;/a
&gt;) and the branch contents are automatically served with SSL. If I push
updates to the branch, they are automatically published. If the branch or repo
is deleted, the site is taken down.
&lt;/p&gt;
&lt;h2 id=&quot;how-to-do-it&quot; tabindex=&quot;-1&quot;&gt;
&lt;a
class=&quot;header-anchor&quot;
href=&quot;https://joshua.seigler.net/posts/my-very-own-github-pages/#how-to-do-it&quot;
aria-hidden=&quot;true&quot;
&gt;&lt;/a&gt;
How to do it
&lt;/h2&gt;
&lt;h3 id=&quot;debian-server-preparation&quot; tabindex=&quot;-1&quot;&gt;
&lt;a
class=&quot;header-anchor&quot;
href=&quot;https://joshua.seigler.net/posts/my-very-own-github-pages/#debian-server-preparation&quot;
aria-hidden=&quot;true&quot;
&gt;&lt;/a&gt;
Debian server preparation
&lt;/h3&gt;
&lt;p&gt;
In case you dont have a basic server setup routine yet, this is a good start:
&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Change the default root password&lt;/li&gt;
&lt;li&gt;
Create a new user, add it to the sudo group. In my examples below the user
is &lt;code&gt;joshua&lt;/code&gt;.
&lt;/li&gt;
&lt;li&gt;
Use &lt;code&gt;ssh-copy-id&lt;/code&gt; to install your ssl pubkey for easier login
&lt;/li&gt;
&lt;li&gt;Disable/lock roots password&lt;/li&gt;
&lt;li&gt;
Disable root login over ssh and change the SSL port number. Pick a new port
lower than 1024.
&lt;/li&gt;
&lt;li&gt;
Edit your local &lt;code&gt;~/.ssh/config&lt;/code&gt; so you dont have to specify the
port number every time you connect.
&lt;/li&gt;
&lt;li&gt;
On the server, install and enable &lt;code&gt;ufw&lt;/code&gt; and
&lt;code&gt;fail2ban&lt;/code&gt;. In addition to allowing your custom SSL port, be sure
to enable ports 80 and 443 with
&lt;code&gt;sudo ufw allow &amp;quot;WWW Full&amp;quot;&lt;/code&gt;.
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;caddy&quot; tabindex=&quot;-1&quot;&gt;
&lt;a
class=&quot;header-anchor&quot;
href=&quot;https://joshua.seigler.net/posts/my-very-own-github-pages/#caddy&quot;
aria-hidden=&quot;true&quot;
&gt;&lt;/a&gt;
Caddy
&lt;/h3&gt;
&lt;p&gt;
I usually use nginx, but I wanted to give Caddy a shot, and it has been a
great experience. I installed Caddy using the
&lt;a href=&quot;https://caddyserver.com/docs/install&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;
&gt;official instructions&lt;/a
&gt;.&lt;br /&gt;
Here is the Caddyfile I made - you will need to change the domains names and
the email. Email could be removed, but it is recommended so SSL certificate
issues can contact you if there is a problem with your certificates.
&lt;/p&gt;
&lt;p&gt;&lt;code&gt;/etc/caddy/Caddyfile&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# Global options block
{
email you@example.com # &amp;lt;&amp;lt;&amp;lt;&amp;lt; change this
on_demand_tls {
ask http://localhost/check
}
}
omitted.webhooks.subdomain.tld { # &amp;lt;&amp;lt;&amp;lt;&amp;lt; change this
reverse_proxy localhost:9000
}
http://localhost {
handle /check {
root * /var/www
@deny not file /{query.domain}/
respond @deny 404
}
}
https:// {
tls {
on_demand
}
root /var/www
rewrite /{host}{uri}
# Block files that start with a .
@forbidden {
path /.*
}
respond @forbidden 404
file_server
}
# Refer to the Caddy docs for more information:
# https://caddyserver.com/docs/caddyfile
# This config based on information at
# https://caddy.community/t/on-demand-tls-with-dynamic-content-paths/18140
# checked and corrected with `caddy validate`
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;
I also took ownership of &lt;code&gt;/var/www&lt;/code&gt; with
&lt;code&gt;chown -R joshua:joshua /var/www&lt;/code&gt; since the webhooks will run as my
login account.
&lt;/p&gt;
&lt;h3 id=&quot;webhook&quot; tabindex=&quot;-1&quot;&gt;
&lt;a
class=&quot;header-anchor&quot;
href=&quot;https://joshua.seigler.net/posts/my-very-own-github-pages/#webhook&quot;
aria-hidden=&quot;true&quot;
&gt;&lt;/a&gt;
Webhook
&lt;/h3&gt;
&lt;p&gt;
I altered the systemd service definition for &lt;code&gt;webhook&lt;/code&gt; so I could
organize the hook definitions into separate files. I also set
&lt;code&gt;User=joshua&lt;/code&gt; and &lt;code&gt;Group=joshua&lt;/code&gt; so the commands run as
my user instead of root.
&lt;/p&gt;
&lt;p&gt;&lt;code&gt;sudo mkdir /etc/webhook.conf.d/&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;/lib/systemd/system/webhook.service&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ini&quot;&gt;[Unit]
Description=Small server for creating HTTP endpoints (hooks)
Documentation=https://github.com/adnanh/webhook/
ConditionPathExists=/etc/webhook.conf
[Service]
ExecStart=/usr/bin/webhook -nopanic -hooks /etc/webhook.conf.d/*.conf
User=joshua
Group=joshua
[Install]
WantedBy=multi-user.target
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;
If you are debugging your webhook output, consider adding
&lt;code&gt;-debug&lt;/code&gt; next to &lt;code&gt;-nopanic&lt;/code&gt; for more useful logs.
&lt;/p&gt;
&lt;p&gt;
After changing the service definition, reload systemd to run the updated
service:
&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sudo systemctl daemon-reload
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then you can remove the now-unused config file:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sudo rm /etc/webhook.conf
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Here are the three hook definitions:&lt;/p&gt;
&lt;h4 id=&quot;create-pages&quot; tabindex=&quot;-1&quot;&gt;
&lt;a
class=&quot;header-anchor&quot;
href=&quot;https://joshua.seigler.net/posts/my-very-own-github-pages/#create-pages&quot;
aria-hidden=&quot;true&quot;
&gt;&lt;/a&gt;
Create pages
&lt;/h4&gt;
&lt;p&gt;&lt;code&gt;/etc/webhook.conf.d/create-pages.conf&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;[
{
&amp;quot;id&amp;quot;: &amp;quot;create-pages&amp;quot;,
&amp;quot;execute-command&amp;quot;: &amp;quot;/home/joshua/webhooks/create-pages.sh&amp;quot;,
&amp;quot;command-working-directory&amp;quot;: &amp;quot;/var/www&amp;quot;,
&amp;quot;pass-arguments-to-command&amp;quot;:
[
{
&amp;quot;source&amp;quot;: &amp;quot;payload&amp;quot;,
&amp;quot;name&amp;quot;: &amp;quot;repository.name&amp;quot;
},
{
&amp;quot;source&amp;quot;: &amp;quot;payload&amp;quot;,
&amp;quot;name&amp;quot;: &amp;quot;clone_url&amp;quot;,
}
],
&amp;quot;trigger-rule&amp;quot;:
{
&amp;quot;and&amp;quot;:
[
{
&amp;quot;match&amp;quot;:
{
&amp;quot;type&amp;quot;: &amp;quot;payload-hmac-sha256&amp;quot;,
&amp;quot;secret&amp;quot;: &amp;quot;(omitted)&amp;quot;,
&amp;quot;parameter&amp;quot;:
{
&amp;quot;source&amp;quot;: &amp;quot;header&amp;quot;,
&amp;quot;name&amp;quot;: &amp;quot;X-Forgejo-Signature&amp;quot;
}
}
}
]
}
}
]
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;remove-pages&quot; tabindex=&quot;-1&quot;&gt;
&lt;a
class=&quot;header-anchor&quot;
href=&quot;https://joshua.seigler.net/posts/my-very-own-github-pages/#remove-pages&quot;
aria-hidden=&quot;true&quot;
&gt;&lt;/a&gt;
Remove pages
&lt;/h4&gt;
&lt;p&gt;&lt;code&gt;/etc/webhook.conf.d/remove-pages.conf&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;[
{
&amp;quot;id&amp;quot;: &amp;quot;remove-pages&amp;quot;,
&amp;quot;execute-command&amp;quot;: &amp;quot;/home/joshua/webhooks/remove-pages.sh&amp;quot;,
&amp;quot;command-working-directory&amp;quot;: &amp;quot;/var/www&amp;quot;,
&amp;quot;pass-arguments-to-command&amp;quot;:
[
{
&amp;quot;source&amp;quot;: &amp;quot;payload&amp;quot;,
&amp;quot;name&amp;quot;: &amp;quot;repository.name&amp;quot;
},
],
&amp;quot;trigger-rule&amp;quot;:
{
&amp;quot;and&amp;quot;:
[
{
&amp;quot;match&amp;quot;:
{
&amp;quot;type&amp;quot;: &amp;quot;payload-hmac-sha256&amp;quot;,
&amp;quot;secret&amp;quot;: &amp;quot;(omitted)&amp;quot;,
&amp;quot;parameter&amp;quot;:
{
&amp;quot;source&amp;quot;: &amp;quot;header&amp;quot;,
&amp;quot;name&amp;quot;: &amp;quot;X-Forgejo-Signature&amp;quot;
}
}
}
]
}
}
]
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;update-pages&quot; tabindex=&quot;-1&quot;&gt;
&lt;a
class=&quot;header-anchor&quot;
href=&quot;https://joshua.seigler.net/posts/my-very-own-github-pages/#update-pages&quot;
aria-hidden=&quot;true&quot;
&gt;&lt;/a&gt;
Update pages
&lt;/h4&gt;
&lt;p&gt;&lt;code&gt;/etc/webhook.conf.d/update-pages.conf&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;[
{
&amp;quot;id&amp;quot;: &amp;quot;update-pages&amp;quot;,
&amp;quot;execute-command&amp;quot;: &amp;quot;/home/joshua/webhooks/update-pages.sh&amp;quot;,
&amp;quot;command-working-directory&amp;quot;: &amp;quot;/var/www&amp;quot;,
&amp;quot;pass-arguments-to-command&amp;quot;:
[
{
&amp;quot;source&amp;quot;: &amp;quot;payload&amp;quot;,
&amp;quot;name&amp;quot;: &amp;quot;repository.name&amp;quot;
},
],
&amp;quot;trigger-rule&amp;quot;:
{
&amp;quot;and&amp;quot;:
[
{
&amp;quot;match&amp;quot;:
{
&amp;quot;type&amp;quot;: &amp;quot;payload-hmac-sha256&amp;quot;,
&amp;quot;secret&amp;quot;: &amp;quot;(omitted)&amp;quot;,
&amp;quot;parameter&amp;quot;:
{
&amp;quot;source&amp;quot;: &amp;quot;header&amp;quot;,
&amp;quot;name&amp;quot;: &amp;quot;X-Forgejo-Signature&amp;quot;
}
}
},
{
&amp;quot;match&amp;quot;:
{
&amp;quot;type&amp;quot;: &amp;quot;value&amp;quot;,
&amp;quot;value&amp;quot;: &amp;quot;refs/heads/gh-pages&amp;quot;,
&amp;quot;parameter&amp;quot;:
{
&amp;quot;source&amp;quot;: &amp;quot;payload&amp;quot;,
&amp;quot;name&amp;quot;: &amp;quot;ref&amp;quot;
}
}
}
]
}
}
]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In my home directory I defined all three hook scripts:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;webhooks/create-pages.sh&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;#!/bin/bash
# parameter 1 is repo name, parameter 2 is clone URL
[[ &amp;quot;$1&amp;quot; == *&amp;quot;/&amp;quot;* ]] &amp;amp;&amp;amp; exit 1; # no slashes in the name
[[ &amp;quot;$1&amp;quot; == *&amp;quot;..&amp;quot;* ]] &amp;amp;&amp;amp; exit 1; # no .. in the name
[[ &amp;quot;$1&amp;quot; == *&amp;quot;*&amp;quot;* ]] &amp;amp;&amp;amp; exit 1; # no wildcards in the name
[ -d &amp;quot;/var/www/$1&amp;quot; ] &amp;amp;&amp;amp; exit 1; # the directory must not exist
cd &amp;quot;/var/www&amp;quot;;
git clone -b gh-pages --single-branch &amp;quot;$2&amp;quot; &amp;quot;$1&amp;quot; || exit 1;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;webhooks/remove-pages.sh&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;#!/bin/bash
# parameter 1 is repo name
[[ &amp;quot;$1&amp;quot; == *&amp;quot;/&amp;quot;* ]] &amp;amp;&amp;amp; exit 1; # no slashes in the name
[[ &amp;quot;$1&amp;quot; == *&amp;quot;..&amp;quot;* ]] &amp;amp;&amp;amp; exit 1; # no .. in the name
[[ &amp;quot;$1&amp;quot; == *&amp;quot;*&amp;quot;* ]] &amp;amp;&amp;amp; exit 1; # no wildcards in the name
[ -d &amp;quot;/var/www/$1&amp;quot; ] &amp;amp;&amp;amp; exit 1; # the directory must exist
cd &amp;quot;/var/www&amp;quot;;
rm -rf &amp;quot;/var/www/$1&amp;quot;;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;webhooks/update-pages.sh&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;#!/bin/bash
# parameter 1 is repo name
[[ &amp;quot;$1&amp;quot; == *&amp;quot;/&amp;quot;* ]] &amp;amp;&amp;amp; exit 1; # no slashes in the name
[[ &amp;quot;$1&amp;quot; == *&amp;quot;..&amp;quot;* ]] &amp;amp;&amp;amp; exit 1; # no .. in the name
[[ &amp;quot;$1&amp;quot; == *&amp;quot;*&amp;quot;* ]] &amp;amp;&amp;amp; exit 1; # no wildcards in the name
[ -d &amp;quot;/var/www/$1&amp;quot; ] || exit 1; # the directory must exist
cd &amp;quot;/var/www/$1&amp;quot;;
git fetch origin gh-pages;
git reset --hard origin/gh-pages;
exit;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;forgejo&quot; tabindex=&quot;-1&quot;&gt;
&lt;a
class=&quot;header-anchor&quot;
href=&quot;https://joshua.seigler.net/posts/my-very-own-github-pages/#forgejo&quot;
aria-hidden=&quot;true&quot;
&gt;&lt;/a&gt;
Forgejo
&lt;/h3&gt;
&lt;p&gt;
Forgejo supports running webhooks conditionally triggered by certain
conditions.&lt;br /&gt;
Under my main user settings I set up each webhook:
&lt;/p&gt;
&lt;h4 id=&quot;create-pages-1&quot; tabindex=&quot;-1&quot;&gt;
&lt;a
class=&quot;header-anchor&quot;
href=&quot;https://joshua.seigler.net/posts/my-very-own-github-pages/#create-pages-1&quot;
aria-hidden=&quot;true&quot;
&gt;&lt;/a&gt;
Create pages
&lt;/h4&gt;
&lt;p&gt;
Target URL: https:// &lt;em&gt;your domain here&lt;/em&gt; /hooks/create-pages&lt;br /&gt;
HTTP Method: &lt;code&gt;POST&lt;/code&gt; (the default)&lt;br /&gt;
POST content type: &lt;code&gt;application/json&lt;/code&gt; (the default)&lt;br /&gt;
Secret: &lt;em&gt;omitted, use your own&lt;/em&gt;&lt;br /&gt;
Trigger on: Custom Events &amp;gt; Create&lt;br /&gt;
Branch filter: &lt;code&gt;gh-pages&lt;/code&gt;
&lt;/p&gt;
&lt;h4 id=&quot;remove-pages-1&quot; tabindex=&quot;-1&quot;&gt;
&lt;a
class=&quot;header-anchor&quot;
href=&quot;https://joshua.seigler.net/posts/my-very-own-github-pages/#remove-pages-1&quot;
aria-hidden=&quot;true&quot;
&gt;&lt;/a&gt;
Remove pages
&lt;/h4&gt;
&lt;p&gt;
Target URL: https:// &lt;em&gt;your domain here&lt;/em&gt; /hooks/remove-pages&lt;br /&gt;
HTTP Method: &lt;code&gt;POST&lt;/code&gt; (the default)&lt;br /&gt;
POST content type: &lt;code&gt;application/json&lt;/code&gt; (the default)&lt;br /&gt;
Secret: &lt;em&gt;omitted, use your own&lt;/em&gt;&lt;br /&gt;
Trigger on: Custom Events &amp;gt; Repository &amp;gt; Delete&lt;br /&gt;
Branch filter: &lt;code&gt;gh-pages&lt;/code&gt;
&lt;/p&gt;
&lt;h4 id=&quot;update-pages-1&quot; tabindex=&quot;-1&quot;&gt;
&lt;a
class=&quot;header-anchor&quot;
href=&quot;https://joshua.seigler.net/posts/my-very-own-github-pages/#update-pages-1&quot;
aria-hidden=&quot;true&quot;
&gt;&lt;/a&gt;
Update pages
&lt;/h4&gt;
&lt;p&gt;
Target URL: https:// &lt;em&gt;your domain here&lt;/em&gt; /hooks/update-pages&lt;br /&gt;
HTTP Method: &lt;code&gt;POST&lt;/code&gt; (the default)&lt;br /&gt;
POST content type: &lt;code&gt;application/json&lt;/code&gt; (the default)&lt;br /&gt;
Secret: &lt;em&gt;omitted, use your own&lt;/em&gt;&lt;br /&gt;
Trigger on: Push events&lt;br /&gt;
Branch filter: &lt;code&gt;gh-pages&lt;/code&gt;
&lt;/p&gt;
&lt;h2 id=&quot;conclusion&quot; tabindex=&quot;-1&quot;&gt;
&lt;a
class=&quot;header-anchor&quot;
href=&quot;https://joshua.seigler.net/posts/my-very-own-github-pages/#conclusion&quot;
aria-hidden=&quot;true&quot;
&gt;&lt;/a&gt;
Conclusion
&lt;/h2&gt;
&lt;p&gt;
It works!&lt;br /&gt;
This repo is in my Forgejo instance:
&lt;a
href=&quot;https://git.apps.seigler.net/joshua/marklink.pages.seigler.net&quot;
target=&quot;_blank&quot;
rel=&quot;noopener&quot;
&gt;https://git.apps.seigler.net/joshua/marklink.pages.seigler.net&lt;/a
&gt;&lt;br /&gt;
And its contents are visible here on my Caddy server:
&lt;a href=&quot;https://marklink.pages.seigler.net/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;
&gt;https://marklink.pages.seigler.net/&lt;/a
&gt;
&lt;/p&gt;
&lt;p&gt;
There is room to make the scripts a little smarter. They dont handle renaming
very well right now, and a few times I had to log in and manually run my
webhook scripts, like this:
&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;~/webhooks/create-pages.sh marklink.pages.seigler.net &amp;quot;https://git.apps.seigler.net/joshua/marklink.pages.seigler.net.git&amp;quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;
The really important thing is that updates just require pushing to
&lt;code&gt;gh-pages&lt;/code&gt; which you can easily do with e.g.
&lt;a
href=&quot;https://www.npmjs.com/package/gh-pages&quot;
target=&quot;_blank&quot;
rel=&quot;noopener&quot;
&gt;gh-pages @ npm&lt;/a
&gt;.
&lt;/p&gt;
&lt;p&gt;
Im also putting off rolling my own CI server, but I imagine thats the next
stage here. Stay tuned.
&lt;/p&gt;
</content>
</entry>
<entry>
<title>Tools of the trade</title>
<link href="https://joshua.seigler.net/posts/tools-of-the-trade/" />