136 lines
No EOL
25 KiB
HTML
136 lines
No EOL
25 KiB
HTML
<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1,viewport-fit=cover"><link rel="preconnect" href="https://stats.apps.seigler.net/"><style>@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}@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><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="/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 type="module" crossorigin src="/assets/main-BbblKnOV.js"></script><link rel="stylesheet" crossorigin href="/assets/main-n1kH6TzA.css"></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 aria-current="page" href="/posts/">/posts</a> <a href="/about/">/about</a> <a href="/now/">/now</a> <a href="/uses/">/uses</a> <a href="/recipes/">/recipes</a> <a href="/music/">/music</a> <a href="/books/">/books</a> <a href="/links/">/links</a> <a href="/search/">/search</a></div></nav><h1>My Very Own GitHub Pages</h1><div class="header-meta"><span>June 15, 2025</span> <span class="tags" style="--totalTags:12"><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>. I’m 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 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 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 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 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 # <<<< CHANGE THIS <<<<
|
||
on_demand_tls {
|
||
ask http://localhost/check
|
||
}
|
||
}
|
||
|
||
# Webhooks
|
||
https://webhooks.subdomain.here.tld { <<<< CHANGE THIS <<<<
|
||
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">&&</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">&&</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">&&</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">&&</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">&&</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">&&</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 > Repository > 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>I’m putting off rolling my own CI server, but I imagine that’s 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>© 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> |