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/" />

View file

@ -91,6 +91,16 @@
</p>
<h2>Posts</h2>
<ul class="collection">
<li>
<a href="/posts/my-very-own-github-pages/"
>My Very Own GitHub Pages</a
>
<aside>June 15, 2025</aside>
<p>
How to self-host Forgejo and automatically serve your web build
branches with SSL
</p>
</li>
<li>
<a href="/posts/tools-of-the-trade/">Tools of the trade</a>
<aside>May 15, 2025</aside>
@ -113,14 +123,6 @@
<aside>July 14, 2023</aside>
<p>Why did I think I could figure everything out on my own?</p>
</li>
<li>
<a href="/posts/embracing-mysticism/">Embracing Mysticism</a>
<aside>October 16, 2021</aside>
<p>
Society is moving from a materialistic era into a mystical one. I
describe my attempt to gain mystical proficiency.
</p>
</li>
<li>
<a href="/posts/">More posts&hellip;</a>
</li>

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View file

@ -1 +1 @@
{"version":"1.3.0","languages":{"en":{"hash":"en_fa88efad88","wasm":"en","page_count":25}}}
{"version":"1.3.0","languages":{"en":{"hash":"en_e6a3ea1351","wasm":"en","page_count":26}}}

Binary file not shown.

View file

@ -72,6 +72,17 @@
</header>
<main data-pagefind-body>
<section>
<article class="item-summary">
<a href="/posts/my-very-own-github-pages/"
>My Very Own GitHub Pages</a
>
<aside>June 15, 2025</aside>
<p class="item-summary-description">
How to self-host Forgejo and automatically serve your web build
branches with SSL
</p>
</article>
<article class="item-summary">
<a href="/posts/tools-of-the-trade/">Tools of the trade</a>
<aside>May 15, 2025</aside>

View file

@ -0,0 +1,544 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta
name="viewport"
content="width=device-width, initial-scale=1.0, viewport-fit=cover"
/>
<link rel="stylesheet" href="/site.css?v=b0813e810319" />
<link rel="me" href="https://github.com/seigler" />
<link
rel="webmention"
href="https://webmention.io/joshua.seigler.net/webmention"
/>
<title>My Very Own GitHub Pages - joshua.seigler.net</title>
<meta
name="description"
content="How to self-host Forgejo and automatically serve your web build branches with SSL"
/>
<meta property="og:title" content="My Very Own GitHub Pages" />
<meta property="og:type" content="" />
<meta
property="og:url"
content="https://joshua.seigler.net/posts/my-very-own-github-pages/"
/>
<meta name="twitter:title" content="My Very Own GitHub Pages" />
<meta
name="twitter:description"
content="How to self-host Forgejo and automatically serve your web build branches with SSL"
/>
<meta name="twitter:card" content="summary" />
<meta name="generator" content="Eleventy v3.1.0" />
<script
defer
src="https://stats.apps.seigler.net/script.js"
data-website-id="ccb4bd94-2a71-47fe-8eea-d85bf75b7f6d"
></script>
<script defer src="/scripts/effects.js?v=cd61c39d895d"></script>
</head>
<body data-font="english" data-path="/posts/my-very-own-github-pages/">
<header>
<nav>
<div class="nav-row">
<div class="nav-home"><a href="/">joshua.seigler.net</a></div>
<div class="nav-settings">
<div class="nav-toggles">
<label class="nav-toggle-button" title="Light mode"
>☀️<input type="radio" name="theme" value="light"
/></label>
<label class="nav-toggle-button" title="Automatic"
>🔄<input type="radio" name="theme" value="auto"
/></label>
<label class="nav-toggle-button" title="Dark mode"
>🌒<input type="radio" name="theme" value="dark"
/></label>
</div>
<div class="nav-toggles">
<label class="nav-toggle-button" data-language="english"
>English<input type="radio" name="language" value="english"
/></label>
<label class="nav-toggle-button" data-language="aurebesh"
>Aurebesh<input type="radio" name="language" value="aurebesh"
/></label>
</div>
</div>
</div>
<div class="nav-categories">
<a class="nav-active" href="/posts/">/posts</a>
<a class="" href="/about/">/about</a>
<a class="" href="/now/">/now</a>
<a class="" href="/uses/">/uses</a>
<a class="" href="/recipes/">/recipes</a>
<a class="" href="/music/">/music</a>
<a class="" href="/books/">/books</a>
<a class="" href="/search/">/search</a>
</div>
</nav>
<h1>My Very Own GitHub Pages</h1>
<div class="header-meta">
<author>Joshua Seigler</author><date>June 15, 2025</date>
</div>
</header>
<main data-pagefind-body>
<p>
I recently started self-hosting
<a href="https://forgejo.org/" target="_blank" rel="noopener">Forgejo</a
>, 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
<a
href="https://github.com/adnanh/webhook"
target="_blank"
rel="noopener"
>webhook</a
>
and
<a href="https://caddyserver.com/" target="_blank" rel="noopener"
>Caddy</a
>. Im very happy how it turned out.
</p>
<h2 id="the-result" tabindex="-1">
<a class="header-anchor" href="#the-result" aria-hidden="true"></a> The
result
</h2>
<p>
When I push a <code>gh-pages</code> branch to any public repository on
my Forgejo instance, the name of the repo is used as a domain name (e.g.
<a
href="https://marklink.pages.seigler.net/"
target="_blank"
rel="noopener"
>marklink.pages.seigler.net</a
>) 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.
</p>
<h2 id="how-to-do-it" tabindex="-1">
<a class="header-anchor" href="#how-to-do-it" aria-hidden="true"></a>
How to do it
</h2>
<h3 id="debian-server-preparation" tabindex="-1">
<a
class="header-anchor"
href="#debian-server-preparation"
aria-hidden="true"
></a>
Debian server preparation
</h3>
<p>
In case you dont have a basic server setup routine yet, this is a good
start:
</p>
<ul>
<li>Change the default root password</li>
<li>
Create a new user, add it to the sudo group. In my examples below the
user is <code>joshua</code>.
</li>
<li>
Use <code>ssh-copy-id</code> to install your ssl pubkey for easier
login
</li>
<li>Disable/lock roots password</li>
<li>
Disable root login over ssh and change the SSL port number. Pick a new
port lower than 1024.
</li>
<li>
Edit your local <code>~/.ssh/config</code> so you dont have to
specify the port number every time you connect.
</li>
<li>
On the server, install and enable <code>ufw</code> and
<code>fail2ban</code>. In addition to allowing your custom SSL port,
be sure to enable ports 80 and 443 with
<code>sudo ufw allow &quot;WWW Full&quot;</code>.
</li>
</ul>
<h3 id="caddy" tabindex="-1">
<a class="header-anchor" href="#caddy" aria-hidden="true"></a> Caddy
</h3>
<p>
I usually use nginx, but I wanted to give Caddy a shot, and it has been
a great experience. I installed Caddy using the
<a
href="https://caddyserver.com/docs/install"
target="_blank"
rel="noopener"
>official instructions</a
>.<br />
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.
</p>
<p><code>/etc/caddy/Caddyfile</code></p>
<pre><code># Global options block
{
email you@example.com # &lt;&lt;&lt;&lt; change this
on_demand_tls {
ask http://localhost/check
}
}
omitted.webhooks.subdomain.tld { # &lt;&lt;&lt;&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`
</code></pre>
<p>
I also took ownership of <code>/var/www</code> with
<code>chown -R joshua:joshua /var/www</code> since the webhooks will run
as my login account.
</p>
<h3 id="webhook" tabindex="-1">
<a class="header-anchor" href="#webhook" aria-hidden="true"></a> Webhook
</h3>
<p>
I altered the systemd service definition for <code>webhook</code> so I
could organize the hook definitions into separate files. I also set
<code>User=joshua</code> and <code>Group=joshua</code> so the commands
run as my user instead of root.
</p>
<p><code>sudo mkdir /etc/webhook.conf.d/</code></p>
<p><code>/lib/systemd/system/webhook.service</code></p>
<pre><code class="language-ini">[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
</code></pre>
<p>
If you are debugging your webhook output, consider adding
<code>-debug</code> next to <code>-nopanic</code> for more useful logs.
</p>
<p>
After changing the service definition, reload systemd to run the updated
service:
</p>
<pre><code class="language-bash">sudo systemctl daemon-reload
</code></pre>
<p>Then you can remove the now-unused config file:</p>
<pre><code class="language-bash">sudo rm /etc/webhook.conf
</code></pre>
<p>Here are the three hook definitions:</p>
<h4 id="create-pages" tabindex="-1">
<a class="header-anchor" href="#create-pages" aria-hidden="true"></a>
Create pages
</h4>
<p><code>/etc/webhook.conf.d/create-pages.conf</code></p>
<pre><code class="language-json">[
{
&quot;id&quot;: &quot;create-pages&quot;,
&quot;execute-command&quot;: &quot;/home/joshua/webhooks/create-pages.sh&quot;,
&quot;command-working-directory&quot;: &quot;/var/www&quot;,
&quot;pass-arguments-to-command&quot;:
[
{
&quot;source&quot;: &quot;payload&quot;,
&quot;name&quot;: &quot;repository.name&quot;
},
{
&quot;source&quot;: &quot;payload&quot;,
&quot;name&quot;: &quot;clone_url&quot;,
}
],
&quot;trigger-rule&quot;:
{
&quot;and&quot;:
[
{
&quot;match&quot;:
{
&quot;type&quot;: &quot;payload-hmac-sha256&quot;,
&quot;secret&quot;: &quot;(omitted)&quot;,
&quot;parameter&quot;:
{
&quot;source&quot;: &quot;header&quot;,
&quot;name&quot;: &quot;X-Forgejo-Signature&quot;
}
}
}
]
}
}
]
</code></pre>
<h4 id="remove-pages" tabindex="-1">
<a class="header-anchor" href="#remove-pages" aria-hidden="true"></a>
Remove pages
</h4>
<p><code>/etc/webhook.conf.d/remove-pages.conf</code></p>
<pre><code class="language-json">[
{
&quot;id&quot;: &quot;remove-pages&quot;,
&quot;execute-command&quot;: &quot;/home/joshua/webhooks/remove-pages.sh&quot;,
&quot;command-working-directory&quot;: &quot;/var/www&quot;,
&quot;pass-arguments-to-command&quot;:
[
{
&quot;source&quot;: &quot;payload&quot;,
&quot;name&quot;: &quot;repository.name&quot;
},
],
&quot;trigger-rule&quot;:
{
&quot;and&quot;:
[
{
&quot;match&quot;:
{
&quot;type&quot;: &quot;payload-hmac-sha256&quot;,
&quot;secret&quot;: &quot;(omitted)&quot;,
&quot;parameter&quot;:
{
&quot;source&quot;: &quot;header&quot;,
&quot;name&quot;: &quot;X-Forgejo-Signature&quot;
}
}
}
]
}
}
]
</code></pre>
<h4 id="update-pages" tabindex="-1">
<a class="header-anchor" href="#update-pages" aria-hidden="true"></a>
Update pages
</h4>
<p><code>/etc/webhook.conf.d/update-pages.conf</code></p>
<pre><code class="language-json">[
{
&quot;id&quot;: &quot;update-pages&quot;,
&quot;execute-command&quot;: &quot;/home/joshua/webhooks/update-pages.sh&quot;,
&quot;command-working-directory&quot;: &quot;/var/www&quot;,
&quot;pass-arguments-to-command&quot;:
[
{
&quot;source&quot;: &quot;payload&quot;,
&quot;name&quot;: &quot;repository.name&quot;
},
],
&quot;trigger-rule&quot;:
{
&quot;and&quot;:
[
{
&quot;match&quot;:
{
&quot;type&quot;: &quot;payload-hmac-sha256&quot;,
&quot;secret&quot;: &quot;(omitted)&quot;,
&quot;parameter&quot;:
{
&quot;source&quot;: &quot;header&quot;,
&quot;name&quot;: &quot;X-Forgejo-Signature&quot;
}
}
},
{
&quot;match&quot;:
{
&quot;type&quot;: &quot;value&quot;,
&quot;value&quot;: &quot;refs/heads/gh-pages&quot;,
&quot;parameter&quot;:
{
&quot;source&quot;: &quot;payload&quot;,
&quot;name&quot;: &quot;ref&quot;
}
}
}
]
}
}
]
</code></pre>
<p>In my home directory I defined all three hook scripts:</p>
<p><code>webhooks/create-pages.sh</code></p>
<pre><code class="language-bash">#!/bin/bash
# parameter 1 is repo name, parameter 2 is clone URL
[[ &quot;$1&quot; == *&quot;/&quot;* ]] &amp;&amp; exit 1; # no slashes in the name
[[ &quot;$1&quot; == *&quot;..&quot;* ]] &amp;&amp; exit 1; # no .. in the name
[[ &quot;$1&quot; == *&quot;*&quot;* ]] &amp;&amp; exit 1; # no wildcards in the name
[ -d &quot;/var/www/$1&quot; ] &amp;&amp; exit 1; # the directory must not exist
cd &quot;/var/www&quot;;
git clone -b gh-pages --single-branch &quot;$2&quot; &quot;$1&quot; || exit 1;
</code></pre>
<p><code>webhooks/remove-pages.sh</code></p>
<pre><code class="language-bash">#!/bin/bash
# parameter 1 is repo name
[[ &quot;$1&quot; == *&quot;/&quot;* ]] &amp;&amp; exit 1; # no slashes in the name
[[ &quot;$1&quot; == *&quot;..&quot;* ]] &amp;&amp; exit 1; # no .. in the name
[[ &quot;$1&quot; == *&quot;*&quot;* ]] &amp;&amp; exit 1; # no wildcards in the name
[ -d &quot;/var/www/$1&quot; ] &amp;&amp; exit 1; # the directory must exist
cd &quot;/var/www&quot;;
rm -rf &quot;/var/www/$1&quot;;
</code></pre>
<p><code>webhooks/update-pages.sh</code></p>
<pre><code class="language-bash">#!/bin/bash
# parameter 1 is repo name
[[ &quot;$1&quot; == *&quot;/&quot;* ]] &amp;&amp; exit 1; # no slashes in the name
[[ &quot;$1&quot; == *&quot;..&quot;* ]] &amp;&amp; exit 1; # no .. in the name
[[ &quot;$1&quot; == *&quot;*&quot;* ]] &amp;&amp; exit 1; # no wildcards in the name
[ -d &quot;/var/www/$1&quot; ] || exit 1; # the directory must exist
cd &quot;/var/www/$1&quot;;
git fetch origin gh-pages;
git reset --hard origin/gh-pages;
exit;
</code></pre>
<h3 id="forgejo" tabindex="-1">
<a class="header-anchor" href="#forgejo" aria-hidden="true"></a> Forgejo
</h3>
<p>
Forgejo supports running webhooks conditionally triggered by certain
conditions.<br />
Under my main user settings I set up each webhook:
</p>
<h4 id="create-pages-1" tabindex="-1">
<a class="header-anchor" href="#create-pages-1" aria-hidden="true"></a>
Create pages
</h4>
<p>
Target URL: https:// <em>your domain here</em> /hooks/create-pages<br />
HTTP Method: <code>POST</code> (the default)<br />
POST content type: <code>application/json</code> (the default)<br />
Secret: <em>omitted, use your own</em><br />
Trigger on: Custom Events &gt; Create<br />
Branch filter: <code>gh-pages</code>
</p>
<h4 id="remove-pages-1" tabindex="-1">
<a class="header-anchor" href="#remove-pages-1" aria-hidden="true"></a>
Remove pages
</h4>
<p>
Target URL: https:// <em>your domain here</em> /hooks/remove-pages<br />
HTTP Method: <code>POST</code> (the default)<br />
POST content type: <code>application/json</code> (the default)<br />
Secret: <em>omitted, use your own</em><br />
Trigger on: Custom Events &gt; Repository &gt; Delete<br />
Branch filter: <code>gh-pages</code>
</p>
<h4 id="update-pages-1" tabindex="-1">
<a class="header-anchor" href="#update-pages-1" aria-hidden="true"></a>
Update pages
</h4>
<p>
Target URL: https:// <em>your domain here</em> /hooks/update-pages<br />
HTTP Method: <code>POST</code> (the default)<br />
POST content type: <code>application/json</code> (the default)<br />
Secret: <em>omitted, use your own</em><br />
Trigger on: Push events<br />
Branch filter: <code>gh-pages</code>
</p>
<h2 id="conclusion" tabindex="-1">
<a class="header-anchor" href="#conclusion" aria-hidden="true"></a>
Conclusion
</h2>
<p>
It works!<br />
This repo is in my Forgejo instance:
<a
href="https://git.apps.seigler.net/joshua/marklink.pages.seigler.net"
target="_blank"
rel="noopener"
>https://git.apps.seigler.net/joshua/marklink.pages.seigler.net</a
><br />
And its contents are visible here on my Caddy server:
<a
href="https://marklink.pages.seigler.net/"
target="_blank"
rel="noopener"
>https://marklink.pages.seigler.net/</a
>
</p>
<p>
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:
</p>
<pre><code class="language-bash">~/webhooks/create-pages.sh marklink.pages.seigler.net &quot;https://git.apps.seigler.net/joshua/marklink.pages.seigler.net.git&quot;
</code></pre>
<p>
The really important thing is that updates just require pushing to
<code>gh-pages</code> which you can easily do with e.g.
<a
href="https://www.npmjs.com/package/gh-pages"
target="_blank"
rel="noopener"
>gh-pages @ npm</a
>.
</p>
<p>
Im also putting off rolling my own CI server, but I imagine thats the
next stage here. Stay tuned.
</p>
<script
data-isso="//comments.apps.seigler.net/"
src="//comments.apps.seigler.net/js/embed.min.js"
></script>
<section id="isso-thread" data-title="">
<noscript>Javascript needs to be activated to view comments.</noscript>
</section>
</main>
<footer>
<section>
&copy; Joshua Seigler 2025. -
<a rel="me" href="mailto:joshua@seigler.net?subject=Hello">Contact</a>
-
<a href="/feed.xml">RSS</a>
-
<a href="/unoffice-hours/">Unoffice Hours</a>
</section>
<section>
Webrings:
<strong>Unoffice Hours</strong>
<a href="https://unofficehours.com/prev.html" rel="noopener">Prev</a>
<a href="https://unofficehours.com/next.html" rel="noopener">Next</a>
<a href="https://unofficehours.com/random.html" rel="noopener"
>Random</a
>
<a href="https://unofficehours.com" rel="noopener">List</a>
</section>
</footer>
<div id="effects"></div>
</body>
</html>

View file

@ -219,9 +219,16 @@
<url>
<loc>/posts/my-very-own-github-pages/</loc>
<lastmod>2025-06-15T00:00:00.000Z</lastmod>
</url>
<url>
<loc>/feed.xml</loc>
<lastmod>2025-06-14T08:41:49.608Z</lastmod>
<lastmod>2025-06-15T05:11:26.173Z</lastmod>
</url>