Updates
This commit is contained in:
parent
cac775b076
commit
6d54ca2954
11 changed files with 1043 additions and 11 deletions
470
feed.xml
470
feed.xml
|
@ -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"><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>.
|
||||
I’m very happy how it turned out.
|
||||
</p>
|
||||
<h2 id="the-result" tabindex="-1">
|
||||
<a
|
||||
class="header-anchor"
|
||||
href="https://joshua.seigler.net/posts/my-very-own-github-pages/#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="https://joshua.seigler.net/posts/my-very-own-github-pages/#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="https://joshua.seigler.net/posts/my-very-own-github-pages/#debian-server-preparation"
|
||||
aria-hidden="true"
|
||||
></a>
|
||||
Debian server preparation
|
||||
</h3>
|
||||
<p>
|
||||
In case you don’t 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 root’s 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 don’t 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="https://joshua.seigler.net/posts/my-very-own-github-pages/#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="https://joshua.seigler.net/posts/my-very-own-github-pages/#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="https://joshua.seigler.net/posts/my-very-own-github-pages/#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="https://joshua.seigler.net/posts/my-very-own-github-pages/#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="https://joshua.seigler.net/posts/my-very-own-github-pages/#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="https://joshua.seigler.net/posts/my-very-own-github-pages/#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="https://joshua.seigler.net/posts/my-very-own-github-pages/#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="https://joshua.seigler.net/posts/my-very-own-github-pages/#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="https://joshua.seigler.net/posts/my-very-own-github-pages/#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="https://joshua.seigler.net/posts/my-very-own-github-pages/#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 don’t 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>
|
||||
I’m also putting off rolling my own CI server, but I imagine that’s the next
|
||||
stage here. Stay tuned.
|
||||
</p>
|
||||
</content>
|
||||
</entry>
|
||||
<entry>
|
||||
<title>Tools of the trade</title>
|
||||
<link href="https://joshua.seigler.net/posts/tools-of-the-trade/" />
|
||||
|
|
18
index.html
18
index.html
|
@ -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…</a>
|
||||
</li>
|
||||
|
|
BIN
pagefind/fragment/en_2bc7cda.pf_fragment
Normal file
BIN
pagefind/fragment/en_2bc7cda.pf_fragment
Normal file
Binary file not shown.
BIN
pagefind/fragment/en_78d1642.pf_fragment
Normal file
BIN
pagefind/fragment/en_78d1642.pf_fragment
Normal file
Binary file not shown.
BIN
pagefind/fragment/en_dbd9c78.pf_fragment
Normal file
BIN
pagefind/fragment/en_dbd9c78.pf_fragment
Normal file
Binary file not shown.
BIN
pagefind/index/en_e079f4f.pf_index
Normal file
BIN
pagefind/index/en_e079f4f.pf_index
Normal file
Binary file not shown.
|
@ -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}}}
|
BIN
pagefind/pagefind.en_e6a3ea1351.pf_meta
Normal file
BIN
pagefind/pagefind.en_e6a3ea1351.pf_meta
Normal file
Binary file not shown.
|
@ -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>
|
||||
|
|
544
posts/my-very-own-github-pages/index.html
Normal file
544
posts/my-very-own-github-pages/index.html
Normal 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
|
||||
>. I’m 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 don’t 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 root’s 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 don’t 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 "WWW Full"</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 # <<<< change this
|
||||
on_demand_tls {
|
||||
ask http://localhost/check
|
||||
}
|
||||
}
|
||||
|
||||
omitted.webhooks.subdomain.tld { # <<<< 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">[
|
||||
{
|
||||
"id": "create-pages",
|
||||
"execute-command": "/home/joshua/webhooks/create-pages.sh",
|
||||
"command-working-directory": "/var/www",
|
||||
"pass-arguments-to-command":
|
||||
[
|
||||
{
|
||||
"source": "payload",
|
||||
"name": "repository.name"
|
||||
},
|
||||
{
|
||||
"source": "payload",
|
||||
"name": "clone_url",
|
||||
}
|
||||
],
|
||||
"trigger-rule":
|
||||
{
|
||||
"and":
|
||||
[
|
||||
{
|
||||
"match":
|
||||
{
|
||||
"type": "payload-hmac-sha256",
|
||||
"secret": "(omitted)",
|
||||
"parameter":
|
||||
{
|
||||
"source": "header",
|
||||
"name": "X-Forgejo-Signature"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
</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">[
|
||||
{
|
||||
"id": "remove-pages",
|
||||
"execute-command": "/home/joshua/webhooks/remove-pages.sh",
|
||||
"command-working-directory": "/var/www",
|
||||
"pass-arguments-to-command":
|
||||
[
|
||||
{
|
||||
"source": "payload",
|
||||
"name": "repository.name"
|
||||
},
|
||||
],
|
||||
"trigger-rule":
|
||||
{
|
||||
"and":
|
||||
[
|
||||
{
|
||||
"match":
|
||||
{
|
||||
"type": "payload-hmac-sha256",
|
||||
"secret": "(omitted)",
|
||||
"parameter":
|
||||
{
|
||||
"source": "header",
|
||||
"name": "X-Forgejo-Signature"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
</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">[
|
||||
{
|
||||
"id": "update-pages",
|
||||
"execute-command": "/home/joshua/webhooks/update-pages.sh",
|
||||
"command-working-directory": "/var/www",
|
||||
"pass-arguments-to-command":
|
||||
[
|
||||
{
|
||||
"source": "payload",
|
||||
"name": "repository.name"
|
||||
},
|
||||
],
|
||||
"trigger-rule":
|
||||
{
|
||||
"and":
|
||||
[
|
||||
{
|
||||
"match":
|
||||
{
|
||||
"type": "payload-hmac-sha256",
|
||||
"secret": "(omitted)",
|
||||
"parameter":
|
||||
{
|
||||
"source": "header",
|
||||
"name": "X-Forgejo-Signature"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"match":
|
||||
{
|
||||
"type": "value",
|
||||
"value": "refs/heads/gh-pages",
|
||||
"parameter":
|
||||
{
|
||||
"source": "payload",
|
||||
"name": "ref"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
</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
|
||||
[[ "$1" == *"/"* ]] && exit 1; # no slashes in the name
|
||||
[[ "$1" == *".."* ]] && exit 1; # no .. in the name
|
||||
[[ "$1" == *"*"* ]] && exit 1; # no wildcards in the name
|
||||
[ -d "/var/www/$1" ] && exit 1; # the directory must not exist
|
||||
cd "/var/www";
|
||||
git clone -b gh-pages --single-branch "$2" "$1" || exit 1;
|
||||
</code></pre>
|
||||
<p><code>webhooks/remove-pages.sh</code></p>
|
||||
<pre><code class="language-bash">#!/bin/bash
|
||||
# parameter 1 is repo name
|
||||
[[ "$1" == *"/"* ]] && exit 1; # no slashes in the name
|
||||
[[ "$1" == *".."* ]] && exit 1; # no .. in the name
|
||||
[[ "$1" == *"*"* ]] && exit 1; # no wildcards in the name
|
||||
[ -d "/var/www/$1" ] && exit 1; # the directory must exist
|
||||
cd "/var/www";
|
||||
rm -rf "/var/www/$1";
|
||||
</code></pre>
|
||||
<p><code>webhooks/update-pages.sh</code></p>
|
||||
<pre><code class="language-bash">#!/bin/bash
|
||||
# parameter 1 is repo name
|
||||
[[ "$1" == *"/"* ]] && exit 1; # no slashes in the name
|
||||
[[ "$1" == *".."* ]] && exit 1; # no .. in the name
|
||||
[[ "$1" == *"*"* ]] && exit 1; # no wildcards in the name
|
||||
[ -d "/var/www/$1" ] || exit 1; # the directory must exist
|
||||
cd "/var/www/$1";
|
||||
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 > 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 > Repository > 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 don’t 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 "https://git.apps.seigler.net/joshua/marklink.pages.seigler.net.git"
|
||||
</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>
|
||||
I’m also putting off rolling my own CI server, but I imagine that’s 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>
|
||||
© 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>
|
|
@ -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>
|
||||
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue