457 lines
25 KiB
HTML
457 lines
25 KiB
HTML
<!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=9f2b70ad5d78" />
|
||
<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
|
||
class="language-undefined"
|
||
><code class="language-undefined"># 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">"su joshua /home/joshua/webhooks/update-pages.sh"</span><span class="token punctuation">,</span>
|
||
<span class="token property">"command-working-directory"</span><span class="token operator">:</span> <span class="token string">"/var/www"</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">"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 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">"su joshua /home/joshua/webhooks/remove-pages.sh"</span><span class="token punctuation">,</span>
|
||
<span class="token property">"command-working-directory"</span><span class="token operator">:</span> <span class="token string">"/var/www"</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">"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 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 />
|
||
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>
|
||
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 the 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>
|
||
|
||
<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>
|