joshua.seigler.net/posts/my-very-own-github-pages/index.html
Joshua Seigler 16685bd250 Updates
2025-06-26 19:41:23 -04:00

482 lines
28 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!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=ddfb0107ac54" />
<style>
/* inter-latin-wght-normal */
@font-face {
font-family: "Inter Variable";
font-style: normal;
font-display: swap;
font-weight: 100 900;
src: url(/fonts/inter-latin-wght-normal.woff2)
format("woff2-variations");
unicode-range:
U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC,
U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193,
U+2212, U+2215, U+FEFF, U+FFFD;
} /* inter-latin-wght-italic */
@font-face {
font-family: "Inter Variable";
font-style: italic;
font-display: swap;
font-weight: 100 900;
src: url(/fonts/inter-latin-wght-italic.woff2)
format("woff2-variations");
unicode-range:
U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC,
U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193,
U+2212, U+2215, U+FEFF, U+FFFD;
}
</style>
<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=d86d3b7642f1"></script>
<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 name="keywords" content="posts, how to, technical, selfhosting" />
<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" />
</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>
<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>
<span class="tags" style="--totalTags: 11"
><a class="tag" style="--tagIndex: 2" href="/tags/how-to">how to</a>
<a class="tag" style="--tagIndex: 5" href="/tags/technical"
>technical</a
>
<a class="tag" style="--tagIndex: 9" href="/tags/selfhosting"
>selfhosting</a
>
</span>
</div>
</header>
<header class="toc">
<span class="toc">
<ol>
<li><a href="#the-objective"> The objective</a></li>
<li>
<a href="#how-to-do-it"> How to do it</a>
<ol>
<li>
<a href="#debian-server-preparation">
Debian server preparation</a
>
</li>
<li><a href="#caddy"> Caddy</a></li>
<li><a href="#webhooks"> Webhooks</a></li>
<li><a href="#forgejo"> Forgejo</a></li>
</ol>
</li>
<li><a href="#conclusion"> Conclusion</a></li>
</ol>
</span>
</header>
<main data-pagefind-body="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-objective" tabindex="-1">
<a class="header-anchor" href="#the-objective" aria-hidden="true"></a>
The objective
</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 and 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 domain names
and the email. Email could be removed, but it is there so that SSL
certificate issuers can contact you if there is a problem with your
certificates.
</p>
<p><code>/etc/caddy/Caddyfile</code></p>
<pre
class="language-caddy"
><code class="language-caddy"># Global options block
{
email you@example.com # &lt;&lt;&lt;&lt; CHANGE THIS &lt;&lt;&lt;&lt;
on_demand_tls {
ask http://localhost/check
}
}
# Webhooks
https://webhooks.subdomain.here.tld { &lt;&lt;&lt;&lt; CHANGE THIS &lt;&lt;&lt;&lt;
reverse_proxy localhost:9000
}
# Filter for which SSL certs we will create. Prevents abuse.
http://localhost {
handle /check {
root * /var/www
@deny not file /{query.domain}/
respond @deny 404
}
}
# This automatically handles upgrading http:// requests with a redirect
https:// {
tls {
on_demand
}
root /var/www
rewrite /{host}{uri}
@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="webhooks" tabindex="-1">
<a class="header-anchor" href="#webhooks" aria-hidden="true"></a>
Webhooks
</h3>
<p>In my home directory I defined two hook scripts:</p>
<p><code>~/webhooks/update-pages.sh</code></p>
<pre
class="language-bash"
><code class="language-bash"><span class="token shebang important">#!/bin/bash</span>
<span class="token comment"># parameter 1 is repo name, parameter 2 is clone url</span>
<span class="token punctuation">[</span><span class="token punctuation">[</span> <span class="token string">"<span class="token variable">$1</span>"</span> <span class="token operator">==</span> *<span class="token string">"/"</span>* <span class="token punctuation">]</span><span class="token punctuation">]</span> <span class="token operator">&amp;&amp;</span> <span class="token builtin class-name">exit</span> <span class="token number">1</span><span class="token punctuation">;</span>
<span class="token punctuation">[</span><span class="token punctuation">[</span> <span class="token string">"<span class="token variable">$1</span>"</span> <span class="token operator">==</span> *<span class="token string">".."</span>* <span class="token punctuation">]</span><span class="token punctuation">]</span> <span class="token operator">&amp;&amp;</span> <span class="token builtin class-name">exit</span> <span class="token number">1</span><span class="token punctuation">;</span>
<span class="token punctuation">[</span><span class="token punctuation">[</span> <span class="token string">"<span class="token variable">$1</span>"</span> <span class="token operator">==</span> *<span class="token string">"*"</span>* <span class="token punctuation">]</span><span class="token punctuation">]</span> <span class="token operator">&amp;&amp;</span> <span class="token builtin class-name">exit</span> <span class="token number">1</span><span class="token punctuation">;</span>
<span class="token keyword">if</span> <span class="token punctuation">[</span> <span class="token parameter variable">-d</span> <span class="token string">"/var/www/<span class="token variable">$1</span>"</span> <span class="token punctuation">]</span><span class="token punctuation">;</span> <span class="token keyword">then</span>
<span class="token builtin class-name">cd</span> <span class="token string">"/var/www/<span class="token variable">$1</span>"</span><span class="token punctuation">;</span>
<span class="token function">git</span> fetch origin gh-pages<span class="token punctuation">;</span>
<span class="token function">git</span> reset <span class="token parameter variable">--hard</span> origin/gh-pages<span class="token punctuation">;</span>
<span class="token builtin class-name">exit</span><span class="token punctuation">;</span>
<span class="token keyword">fi</span><span class="token punctuation">;</span>
<span class="token function">git</span> clone <span class="token parameter variable">-b</span> gh-pages --single-branch <span class="token string">"<span class="token variable">$2</span>"</span> <span class="token string">"<span class="token variable">$1</span>"</span> <span class="token operator">||</span> <span class="token builtin class-name">exit</span> <span class="token number">1</span><span class="token punctuation">;</span>
</code></pre>
<p><code>~/webhooks/remove-pages.sh</code></p>
<pre
class="language-bash"
><code class="language-bash"><span class="token shebang important">#!/bin/bash</span>
<span class="token comment"># parameter 1 is repo name</span>
<span class="token punctuation">[</span><span class="token punctuation">[</span> <span class="token string">"<span class="token variable">$1</span>"</span> <span class="token operator">==</span> *<span class="token string">"/"</span>* <span class="token punctuation">]</span><span class="token punctuation">]</span> <span class="token operator">&amp;&amp;</span> <span class="token builtin class-name">exit</span> <span class="token number">1</span><span class="token punctuation">;</span>
<span class="token punctuation">[</span><span class="token punctuation">[</span> <span class="token string">"<span class="token variable">$1</span>"</span> <span class="token operator">==</span> *<span class="token string">".."</span>* <span class="token punctuation">]</span><span class="token punctuation">]</span> <span class="token operator">&amp;&amp;</span> <span class="token builtin class-name">exit</span> <span class="token number">1</span><span class="token punctuation">;</span>
<span class="token punctuation">[</span><span class="token punctuation">[</span> <span class="token string">"<span class="token variable">$1</span>"</span> <span class="token operator">==</span> *<span class="token string">"*"</span>* <span class="token punctuation">]</span><span class="token punctuation">]</span> <span class="token operator">&amp;&amp;</span> <span class="token builtin class-name">exit</span> <span class="token number">1</span><span class="token punctuation">;</span>
<span class="token punctuation">[</span> <span class="token parameter variable">-d</span> <span class="token string">"/var/www/<span class="token variable">$1</span>"</span> <span class="token punctuation">]</span> <span class="token operator">||</span> <span class="token builtin class-name">exit</span> <span class="token number">1</span><span class="token punctuation">;</span>
<span class="token builtin class-name">cd</span> <span class="token string">"/var/www"</span><span class="token punctuation">;</span>
<span class="token function">rm</span> <span class="token parameter variable">-rf</span> <span class="token string">"/var/www/<span class="token variable">$1</span>"</span><span class="token punctuation">;</span>
</code></pre>
<p>
To trigger these hooks I am using <code>webhook</code> which is in the
default Debian repository.
</p>
<p>
Here are the hook definitions: one for creating/updating a site, and one
for deleting. You will need to generate one or two secret values that
the server can use to know that the webhook is authorized to run. I used
linux command <code>uuidgen -r</code> to create mine. Save these values
so you can enter them in Forgejo later.
</p>
<p>
Also make sure to replace your execute-command lines with ones
referencing your username and script paths.
</p>
<p><code>/etc/webhook.conf</code></p>
<pre
class="language-json"
><code class="language-json"><span class="token punctuation">[</span>
<span class="token punctuation">{</span>
<span class="token property">"id"</span><span class="token operator">:</span> <span class="token string">"update-pages"</span><span class="token punctuation">,</span>
<span class="token property">"execute-command"</span><span class="token operator">:</span> <span class="token string">"/usr/bin/sudo"</span><span class="token punctuation">,</span>
<span class="token property">"pass-arguments-to-command"</span><span class="token operator">:</span>
<span class="token punctuation">[</span>
<span class="token punctuation">{</span> <span class="token property">"source"</span><span class="token operator">:</span> <span class="token string">"string"</span><span class="token punctuation">,</span> <span class="token property">"name"</span><span class="token operator">:</span> <span class="token string">"-u"</span><span class="token punctuation">}</span><span class="token punctuation">,</span>
<span class="token punctuation">{</span> <span class="token property">"source"</span><span class="token operator">:</span> <span class="token string">"string"</span><span class="token punctuation">,</span> <span class="token property">"name"</span><span class="token operator">:</span> <span class="token string">"joshua"</span><span class="token punctuation">}</span><span class="token punctuation">,</span>
<span class="token punctuation">{</span> <span class="token property">"source"</span><span class="token operator">:</span> <span class="token string">"string"</span><span class="token punctuation">,</span> <span class="token property">"name"</span><span class="token operator">:</span> <span class="token string">"/home/joshua/webhooks/update-pages.sh"</span><span class="token punctuation">}</span><span class="token punctuation">,</span>
<span class="token punctuation">{</span> <span class="token property">"source"</span><span class="token operator">:</span> <span class="token string">"payload"</span><span class="token punctuation">,</span> <span class="token property">"name"</span><span class="token operator">:</span> <span class="token string">"repository.name"</span> <span class="token punctuation">}</span><span class="token punctuation">,</span>
<span class="token punctuation">{</span> <span class="token property">"source"</span><span class="token operator">:</span> <span class="token string">"payload"</span><span class="token punctuation">,</span> <span class="token property">"name"</span><span class="token operator">:</span> <span class="token string">"repository.clone_url"</span> <span class="token punctuation">}</span>
<span class="token punctuation">]</span><span class="token punctuation">,</span>
<span class="token property">"trigger-rule"</span><span class="token operator">:</span>
<span class="token punctuation">{</span>
<span class="token property">"and"</span><span class="token operator">:</span>
<span class="token punctuation">[</span>
<span class="token punctuation">{</span>
<span class="token property">"match"</span><span class="token operator">:</span>
<span class="token punctuation">{</span>
<span class="token property">"type"</span><span class="token operator">:</span> <span class="token string">"payload-hmac-sha256"</span><span class="token punctuation">,</span>
<span class="token property">"secret"</span><span class="token operator">:</span> <span class="token string">"(omitted)"</span><span class="token punctuation">,</span>
<span class="token property">"parameter"</span><span class="token operator">:</span>
<span class="token punctuation">{</span>
<span class="token property">"source"</span><span class="token operator">:</span> <span class="token string">"header"</span><span class="token punctuation">,</span>
<span class="token property">"name"</span><span class="token operator">:</span> <span class="token string">"X-Forgejo-Signature"</span>
<span class="token punctuation">}</span>
<span class="token punctuation">}</span>
<span class="token punctuation">}</span><span class="token punctuation">,</span>
<span class="token punctuation">{</span>
<span class="token property">"match"</span><span class="token operator">:</span>
<span class="token punctuation">{</span>
<span class="token property">"type"</span><span class="token operator">:</span> <span class="token string">"value"</span><span class="token punctuation">,</span>
<span class="token property">"value"</span><span class="token operator">:</span> <span class="token string">"refs/heads/gh-pages"</span><span class="token punctuation">,</span>
<span class="token property">"parameter"</span><span class="token operator">:</span>
<span class="token punctuation">{</span>
<span class="token property">"source"</span><span class="token operator">:</span> <span class="token string">"payload"</span><span class="token punctuation">,</span>
<span class="token property">"name"</span><span class="token operator">:</span> <span class="token string">"ref"</span>
<span class="token punctuation">}</span>
<span class="token punctuation">}</span>
<span class="token punctuation">}</span>
<span class="token punctuation">]</span>
<span class="token punctuation">}</span>
<span class="token punctuation">}</span><span class="token punctuation">,</span>
<span class="token punctuation">{</span>
<span class="token property">"id"</span><span class="token operator">:</span> <span class="token string">"remove-pages"</span><span class="token punctuation">,</span>
<span class="token property">"execute-command"</span><span class="token operator">:</span> <span class="token string">"/usr/bin/sudo"</span><span class="token punctuation">,</span>
<span class="token property">"pass-arguments-to-command"</span><span class="token operator">:</span>
<span class="token punctuation">[</span>
<span class="token punctuation">{</span> <span class="token property">"source"</span><span class="token operator">:</span> <span class="token string">"string"</span><span class="token punctuation">,</span> <span class="token property">"name"</span><span class="token operator">:</span> <span class="token string">"-u"</span><span class="token punctuation">}</span><span class="token punctuation">,</span>
<span class="token punctuation">{</span> <span class="token property">"source"</span><span class="token operator">:</span> <span class="token string">"string"</span><span class="token punctuation">,</span> <span class="token property">"name"</span><span class="token operator">:</span> <span class="token string">"joshua"</span><span class="token punctuation">}</span><span class="token punctuation">,</span>
<span class="token punctuation">{</span> <span class="token property">"source"</span><span class="token operator">:</span> <span class="token string">"string"</span><span class="token punctuation">,</span> <span class="token property">"name"</span><span class="token operator">:</span> <span class="token string">"/home/joshua/webhooks/remove-pages.sh"</span><span class="token punctuation">}</span><span class="token punctuation">,</span>
<span class="token punctuation">{</span> <span class="token property">"source"</span><span class="token operator">:</span> <span class="token string">"payload"</span><span class="token punctuation">,</span> <span class="token property">"name"</span><span class="token operator">:</span> <span class="token string">"repository.name"</span> <span class="token punctuation">}</span>
<span class="token punctuation">]</span><span class="token punctuation">,</span>
<span class="token property">"trigger-rule"</span><span class="token operator">:</span>
<span class="token punctuation">{</span>
<span class="token property">"and"</span><span class="token operator">:</span>
<span class="token punctuation">[</span>
<span class="token punctuation">{</span>
<span class="token property">"match"</span><span class="token operator">:</span>
<span class="token punctuation">{</span>
<span class="token property">"type"</span><span class="token operator">:</span> <span class="token string">"payload-hmac-sha256"</span><span class="token punctuation">,</span>
<span class="token property">"secret"</span><span class="token operator">:</span> <span class="token string">"(omitted)"</span><span class="token punctuation">,</span>
<span class="token property">"parameter"</span><span class="token operator">:</span>
<span class="token punctuation">{</span>
<span class="token property">"source"</span><span class="token operator">:</span> <span class="token string">"header"</span><span class="token punctuation">,</span>
<span class="token property">"name"</span><span class="token operator">:</span> <span class="token string">"X-Forgejo-Signature"</span>
<span class="token punctuation">}</span>
<span class="token punctuation">}</span>
<span class="token punctuation">}</span>
<span class="token punctuation">]</span>
<span class="token punctuation">}</span>
<span class="token punctuation">}</span>
<span class="token punctuation">]</span>
</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="update-pages" tabindex="-1">
<a class="header-anchor" href="#update-pages" 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>
<h4 id="remove-pages" tabindex="-1">
<a class="header-anchor" href="#remove-pages" 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>
<h2 id="conclusion" tabindex="-1">
<a class="header-anchor" href="#conclusion" aria-hidden="true"></a>
Conclusion
</h2>
<p>
It works!<br />
Here is
<a
href="https://git.apps.seigler.net/joshua/marklink.pages.seigler.net"
target="_blank"
rel="noopener"
>the marklink repo in my Forgejo instance</a
>
and
<a
href="https://marklink.pages.seigler.net/"
target="_blank"
rel="noopener"
>its contents on my Caddy server</a
>.
</p>
<p>
That repo is just HTML and JS with only a gh-pages branch, but for repos
with npm build scripts, I use
<a
href="https://www.npmjs.com/package/gh-pages"
target="_blank"
rel="noopener"
>gh-pages @ npm</a
>
to push the build to a gh-pages branch and up to the server.
</p>
<p>
Im putting off rolling my own CI server, but I imagine thats the next
stage here. Stay tuned.
</p>
</main>
<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>
<footer>
&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>
-
<a href="/webrings/">Webrings</a>
</footer>
<div id="effects"></div>
</body>
</html>