<?xml version="1.0" encoding="UTF-8"?>
<rss xmlns:atom="http://www.w3.org/2005/Atom" version="2.0">
    <channel>
      <title>fluids.rocks</title>
      <link>https://fluids.rocks</link>
      <description></description>
      <generator>Zola</generator>
      <language>en</language>
      <atom:link href="https://fluids.rocks/rss.xml" rel="self" type="application/rss+xml"/>
      <lastBuildDate>Sat, 19 Sep 2026 00:00:00 +0000</lastBuildDate>
      <item>
          <title>tsgxr: Loading HyLogger TSG data into xarray</title>
          <pubDate>Sat, 19 Sep 2026 00:00:00 +0000</pubDate>
          <author>Morgan Williams</author>
          <link>https://fluids.rocks/posts/tsgxr/</link>
          <guid>https://fluids.rocks/posts/tsgxr/</guid>
          <description xml:base="https://fluids.rocks/posts/tsgxr/">&lt;p&gt;For those working with HyLogger data in &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://research.csiro.au/thespectralgeologist/&quot;&gt;TSG&lt;/a&gt; format, I&#39;ve put together an open source Python package  &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://github.com/CSIRO-GeoscienceAnalytics/tsg-xr&quot;&gt;&lt;code&gt;tsgxr&lt;/code&gt;&lt;/a&gt; which allows reading these relatively arcane formats directly into Xarray. &lt;code&gt;tsgxr&lt;/code&gt; is available via GitHub and also &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://pypi.org/project/tsgxr/&quot;&gt;directly from PyPI&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Over the past few weeks I&#39;ve restructured the package and added the ability to lazily-load data, meaning that even the largest files should now be able to be &lt;em&gt;opened&lt;/em&gt; (if not loaded, necessarily) on even modest hardware.&lt;/p&gt;
&lt;blockquote class=&quot;markdown-alert-note&quot;&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; &lt;code&gt;tsgxr&lt;/code&gt; leans on another package for some of the basic structure/loaders, &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://github.com/Geological-Survey-of-Western-Australia/pytsg&quot;&gt;&lt;code&gt;pytsg&lt;/code&gt;&lt;/a&gt;, now managed by the Geological Survey of Western Australia but originally written by &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://github.com/FractalGeoAnalytics&quot;&gt;Ben @ FractalGeoAnalytics&lt;/a&gt; and Andrew Rodger (ex-CSIRO). The dependence on &lt;code&gt;pytsg&lt;/code&gt; has lessened after writing the &lt;code&gt;xarray&lt;/code&gt; loaders, and some bugfixes in &lt;code&gt;tsgxr&lt;/code&gt; are yet to make it back to &lt;code&gt;pytsg&lt;/code&gt;, but they&#39;re on the way.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;hylogger-data&quot;&gt;HyLogger Data&lt;a class=&quot;zola-anchor&quot; href=&quot;#hylogger-data&quot; aria-label=&quot;Anchor link for: hylogger-data&quot; style=&quot;visibility: hidden;&quot;&gt;&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;HyLogger instruments are used principally for hyperspectral line-scanning of drillcore (typically at about 1cm resolution), from which spectral mineralogy is derived - allowing identification and unmixing of mineral signatures downhole, and later domaining along the drillhole trace ($\pm$ in 3D). The instrument generates a composite dataset including spectral images (for which there are typically two or three hyperspectral cubes - near-infrared, thermal-infrared and with the latest HyLogger instruments also mid-infrared; NIR, TIR and MIR for short), a lidar trace along the centre of the drillcore, and high resolution truecolor imagery of the samples.&lt;/p&gt;
&lt;p&gt;The datasets are provided in TSG formats (partly due to the fact that they&#39;re processed through TSG), which include one &lt;code&gt;.bip&lt;/code&gt; image per hyperspectral sensor, each paried with a &lt;code&gt;.tsg&lt;/code&gt; file containing metadata/headers, a lidar trace in &lt;code&gt;.dat&lt;/code&gt; and a &lt;code&gt;.bip&lt;/code&gt; file containing a concatenated series of compressed JPEGs (i.e., a small JPEG bomb, if you&#39;re short on RAM). This latter item has caused issues previously, particularly for longer drillholes (e.g., for one dataset, 6GB of compressed JPEGs would expand to ~220GB), and as such was something I was looking to handle more cleanly.&lt;/p&gt;
&lt;p&gt;HyLogger data is typically scanned by state geological surveys across Australia, and a large amoutn of this is provided openly via the &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://www.auscope.org/nvcl/&quot;&gt;National Virtual Core Library (NVCL)&lt;/a&gt;, supported by &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://www.auscope.org.au/&quot;&gt;AuScope&lt;/a&gt; and also available &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://portal.auscope.org.au/&quot;&gt;via the AuScope portal&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;recent-tsgxr-updates&quot;&gt;Recent &lt;code&gt;tsgxr&lt;/code&gt; Updates&lt;a class=&quot;zola-anchor&quot; href=&quot;#recent-tsgxr-updates&quot; aria-label=&quot;Anchor link for: recent-tsgxr-updates&quot; style=&quot;visibility: hidden;&quot;&gt;&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;In the interest of keeping this brief and minimially outdated over time I&#39;ll just mention a few things
which I think are useful below, and save the rest for the README or to be updated in the package directly.&lt;/p&gt;
&lt;h3 id=&quot;using-an-xarray-datatree-structure&quot;&gt;Using an &lt;code&gt;xarray.DataTree&lt;/code&gt; Structure&lt;a class=&quot;zola-anchor&quot; href=&quot;#using-an-xarray-datatree-structure&quot; aria-label=&quot;Anchor link for: using-an-xarray-datatree-structure&quot; style=&quot;visibility: hidden;&quot;&gt;&lt;/a&gt;
&lt;/h3&gt;
&lt;p&gt;Newer versions of &lt;code&gt;tsgxr&lt;/code&gt; use a &lt;code&gt;xarray.DataTree&lt;/code&gt; structure, which enables each of the varied parts of the dataset to be represented (and stored/serialized if desired) within a single heirarchical dataset. Being in &lt;code&gt;xarray&lt;/code&gt;, this also allows labelling the relevant physical/semantic dimensions of the dataset, assignment of the respective coordinates, and the inclusion of metadata as attributes. A short overview for an example (the STAVELY_17 hole as provided by the NVCL) is given below:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;python&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;DT&lt;/span&gt;&lt;span&gt; :&lt;/span&gt;&lt;span&gt; xarray&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;DataTree&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; =&lt;/span&gt;&lt;span&gt; open_tsg&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;    &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;./07e4dcac-5216-44a6-9a6b-0c4c1f7ce7d&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-12&quot;&gt; index_coord&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;depth&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;DT&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;&amp;lt;xarray.DataTree&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;Group: /&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;├── Group: /NIR&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;│       Dimensions:                    (depth: 19754, wavelength: 531, feature: 25)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;│       Coordinates:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;│         * depth                      (depth) float32 79kB 0.004111 0.004112 ... 156.0&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;│           sample                     (depth) uint64 158kB 1 4 2 ... 23233 23276 23251&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;│           tray                       (depth) uint16 40kB ...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;│           section                    (depth) uint8 20kB ...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;│           hole                       (depth) object 158kB ...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;│         * wavelength                 (wavelength) float64 4kB 380.0 384.0 ... 2.5e+03&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;│           band                       (wavelength) int64 4kB ...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;│         * feature                    (feature) uint8 25B 0 1 2 3 4 ... 20 21 22 23 24&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;│       Data variables: (12/54)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;│           Spectra                    (depth, wavelength) float32 42MB ...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;│           Centres                    (depth, feature) float32 2MB ...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;│           ...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;│       Attributes: (12/34)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;│           core_qual:                 [&amp;#39;Void&amp;#39;, &amp;#39;Rubble&amp;#39;, &amp;#39;Crack&amp;#39;, &amp;#39;Core&amp;#39;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;│           TSA704_S Minerals:         [&amp;#39;Opal&amp;#39;, &amp;#39;Dickite&amp;#39;, &amp;#39;Kaolinite-PX&amp;#39;, &amp;#39;Kaolinite...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;│           ...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;├── Group: /TIR&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;│       Dimensions:                        (depth: 19754, wavelength: 341, feature: 25)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;│       Coordinates:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;│         * depth                          (depth) float32 79kB 0.004111 ... 156.0&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;│           sample                         (depth) uint64 158kB 1 4 2 ... 23276 23251&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;│           tray                           (depth) uint16 40kB ...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;│           section                        (depth) uint8 20kB ...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;│           hole                           (depth) object 158kB ...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;│         * wavelength                     (wavelength) float64 3kB 6e+03 ... 1.45e+04&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;│           band                           (wavelength) int64 3kB ...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;│         * feature                        (feature) uint8 25B 0 1 2 3 4 ... 21 22 23 24&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;│       Data variables: (12/47)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;│           Spectra                        (depth, wavelength) float32 27MB ...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;│           Centres                        (depth, feature) float32 2MB ...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;│           ...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;│       Attributes: (12/28)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;│           TSA703_T Groups:           [&amp;#39;SILICA&amp;#39;, &amp;#39;K-FELDSPAR&amp;#39;, &amp;#39;PLAGIOCLASE&amp;#39;, &amp;#39;GARNE...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;│           ...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;├── Group: /Lidar&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;│       Dimensions:  (depth: 19754)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;│       Coordinates:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;│         * depth    (depth) float32 79kB 0.004111 0.004112 0.004114 ... 156.0 156.0&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;│           sample   (depth) uint64 158kB 1 4 2 5 0 3 ... 23204 23205 23233 23276 23251&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;│           tray     (depth) uint16 40kB ...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;│           section  (depth) uint8 20kB ...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;│           hole     (depth) object 158kB ...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;│       Data variables:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;│           Lidar    (depth) float32 79kB 92.44 75.37 75.98 77.04 ... 2.398 0.9316 58.43&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;└── Group: /Image&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        Dimensions:  (depth: 2898500, channel: 3, width: 926)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        Coordinates:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;          * depth    (depth) float32 12MB 0.004111 0.004177 0.004243 ... 156.0 156.0&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;            section  (depth) uint16 6MB 0 0 0 0 0 0 0 0 0 ... 49 49 49 49 49 49 49 49 49&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;            tray     (depth) uint16 6MB 0 0 0 0 0 0 0 0 ... 186 186 186 186 186 186 186&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;          * channel  (channel) int64 24B 0 1 2&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;          * width    (width) float64 7kB -0.03054 -0.03047 -0.0304 ... 0.03047 0.03054&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        Data variables:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;            Image    (depth, width, channel) uint8 8GB ...&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;h3 id=&quot;lazy-loading&quot;&gt;Lazy-Loading&lt;a class=&quot;zola-anchor&quot; href=&quot;#lazy-loading&quot; aria-label=&quot;Anchor link for: lazy-loading&quot; style=&quot;visibility: hidden;&quot;&gt;&lt;/a&gt;
&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;tsgxr&lt;/code&gt; now includes a &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://github.com/CSIRO-GeoscienceAnalytics/tsg-xr/blob/develop/README.md#tsg-xarray-driversengines&quot;&gt;series of backends for &lt;code&gt;xarray&lt;/code&gt;&lt;/a&gt; which allow opening of components of TSG datasets without reading all of the contained data (i.e. only the metadata and some of the smaller components are read at open-time). Notably, when loading data, some limitations of indexing and lazy loading mean that more data than necesary might be loaded, as the depth-slicing is used as the principal axis (this is how the files are arranged; e.g., if slicing across the drillcore width for imagery, or across wavelengths for spectra - all of the data for that depth slice will be loaded, then subset). Some improvements to this may come in the future.&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;python&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;import&lt;/span&gt;&lt;span&gt; xarray&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;ds&lt;/span&gt;&lt;span&gt; :&lt;/span&gt;&lt;span&gt; xarray&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Dataset&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; =&lt;/span&gt;&lt;span&gt; xarray&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;open_dataset&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;tsgfile&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-12&quot;&gt; engine&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;tsg&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; #&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; loading spectral .bip file lazily&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;ds&lt;/span&gt;&lt;span&gt; :&lt;/span&gt;&lt;span&gt; xarray&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Dataset&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; =&lt;/span&gt;&lt;span&gt; xarray&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;open_dataset&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;crasfile&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-12&quot;&gt; engine&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;lazycras&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; #&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; loading truecolor image .bip lazyily&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;These interfaces are used internally within &lt;code&gt;tsgxr&lt;/code&gt;, such that the key entry point &lt;code&gt;open_tsg()&lt;/code&gt; now by-default lazy-loads the components for which this is an option (spectra, truecolor imagery).&lt;/p&gt;
&lt;h3 id=&quot;simpler-spectral-products&quot;&gt;Simpler Spectral Products&lt;a class=&quot;zola-anchor&quot; href=&quot;#simpler-spectral-products&quot; aria-label=&quot;Anchor link for: simpler-spectral-products&quot; style=&quot;visibility: hidden;&quot;&gt;&lt;/a&gt;
&lt;/h3&gt;
&lt;p&gt;TSG has some limitations in how it runs its unmixing algorithms, such that for any given pixel along the line scan, only a few mineral species are unmixed by TSA (typically 2 or 3, depending on which spectral sensor is considered). These are provided in long-form in the TSG dataset, where paired records under e.g.(&lt;code&gt;Min1&lt;/code&gt;, &lt;code&gt;Grp1&lt;/code&gt;, &lt;code&gt;Wt1&lt;/code&gt;), (&lt;code&gt;Min2&lt;/code&gt;, &lt;code&gt;Grp2&lt;/code&gt;, &lt;code&gt;Wt2&lt;/code&gt;), represent series of values along the line scan identifying i) the mineral species unmixed (&lt;code&gt;Min1&lt;/code&gt; would be dominant), ii) the group assigned to that mineral (the more general classification, for which we can typically have more confidence) and iii) the unmixing fraction assigned to that mineral/group. &lt;code&gt;tsgxr&lt;/code&gt; provides some options for turning this into a nice wide-form (but sparse) table more amenable to statistics and visualization.&lt;/p&gt;
&lt;p&gt;Where &lt;code&gt;collapse_products=True&lt;/code&gt; is used in the &lt;code&gt;open_tsg()&lt;/code&gt; call, the spectral products/scalars will be reorganized into more useful tabluar forms (here e.g &lt;code&gt;sTSAS_Grp&lt;/code&gt;, &lt;code&gt;sTSAS_Min&lt;/code&gt;, ...) indexed by whichever coordinate used for spectra and the group/mineral as labelled under the respective systems used:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;python&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;DT&lt;/span&gt;&lt;span&gt; :&lt;/span&gt;&lt;span&gt; xarray&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;DataTree&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; =&lt;/span&gt;&lt;span&gt; open_tsg&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;    &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;./07e4dcac-5216-44a6-9a6b-0c4c1f7ce7d&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-12&quot;&gt; index_coord&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;depth&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-12&quot;&gt; collapse_products&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;True&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;DT&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;NIR&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;ds&lt;/span&gt;&lt;span&gt; :&lt;/span&gt;&lt;span&gt; xarray&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Dataset&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;&amp;lt;xarray.DatasetView&amp;gt; Size: 57MB&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;Dimensions:                    (depth: 19754, wavelength: 531, sTSASgroup: 7,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;                                sTSASmineral: 15, sTSAVgroup: 2,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;                                sTSAVmineral: 2, uTSASgroup: 7,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;                                uTSASmineral: 15, uTSAVgroup: 2,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;                                uTSAVmineral: 2, feature: 25)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;Coordinates: (12/16)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  * depth                      (depth) float32 79kB 0.004111 0.004112 ... 156.0&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    sample                     (depth) uint64 158kB 1 4 2 ... 23233 23276 23251&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    tray                       (depth) uint16 40kB ...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    section                    (depth) uint8 20kB ...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    hole                       (depth) object 158kB ...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  * wavelength                 (wavelength) float64 4kB 380.0 384.0 ... 2.5e+03&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    ...                         ...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  * sTSAVmineral               (sTSAVmineral) object 16B &amp;#39;Goethite&amp;#39; &amp;#39;Galvanis...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  * uTSASgroup                 (uTSASgroup) object 56B &amp;#39;KAOLIN&amp;#39; ... &amp;#39;CARBONATE&amp;#39;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  * uTSASmineral               (uTSASmineral) object 120B &amp;#39;Kaolinite-PX&amp;#39; ... ...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  * uTSAVgroup                 (uTSAVgroup) object 16B &amp;#39;OXIDE&amp;#39; &amp;#39;NOTAROK&amp;#39;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  * uTSAVmineral               (uTSAVmineral) object 16B &amp;#39;Goethite&amp;#39; &amp;#39;Galvanis...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  * feature                    (feature) uint8 25B 0 1 2 3 4 ... 20 21 22 23 24&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;Data variables: (12/22)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    Spectra                    (depth, wavelength) float32 42MB ...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    sTSAS_Grp                  (depth, sTSASgroup) float64 1MB ...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    sTSAS_Min                  (depth, sTSASmineral) float64 2MB ...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    sTSAV_Grp                  (depth, sTSAVgroup) float64 316kB ...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    sTSAV_Min                  (depth, sTSAVmineral) float64 316kB ...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    uTSAS_Grp                  (depth, uTSASgroup) float64 1MB ...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    ...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;Attributes: (12/34)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    core_qual:                 [&amp;#39;Void&amp;#39;, &amp;#39;Rubble&amp;#39;, &amp;#39;Crack&amp;#39;, &amp;#39;Core&amp;#39;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    TSA704_S Minerals:         [&amp;#39;Opal&amp;#39;, &amp;#39;Dickite&amp;#39;, &amp;#39;Kaolinite-PX&amp;#39;, &amp;#39;Kaolinite...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    TSA704_V Groups:           [&amp;#39;MISC-SILICATE&amp;#39;, &amp;#39;CARBONATE&amp;#39;, &amp;#39;SULPHATE&amp;#39;, &amp;#39;OX...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    ...&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The functions achieving this are also available for use more generally: &lt;code&gt;tsgxr.products.products_to_mineral_table()&lt;/code&gt;, &lt;code&gt;tsgxr.products.products_to_group_table()&lt;/code&gt;. These will take a given spectral dataset, extract the relevant unmixing system, and return a nicely formatted table. They can also perform compositing via suppling a &lt;code&gt;step&lt;/code&gt; argument (e.g. where depth is used as the primary index, &lt;code&gt;step=0.5&lt;/code&gt; will return a 50x downsampling to give a 50cm composite), and will return tables where the respective colormap as used within TSG is provided on the attribtes (mapping minerals or groups to hex-labelled colours).&lt;/p&gt;
&lt;h3 id=&quot;simple-access-to-downhole-plotting&quot;&gt;Simple Access to Downhole Plotting&lt;a class=&quot;zola-anchor&quot; href=&quot;#simple-access-to-downhole-plotting&quot; aria-label=&quot;Anchor link for: simple-access-to-downhole-plotting&quot; style=&quot;visibility: hidden;&quot;&gt;&lt;/a&gt;
&lt;/h3&gt;
&lt;p&gt;These nice mineralogy tables are also then amenable to simple downhole plotting, and &lt;code&gt;tsgxr.products.plot_product_downhole()&lt;/code&gt; will produce stacked bar plots for a specified unmixing system, at a specific level (mineral or group), while optionally providing compositing as desired.&lt;/p&gt;
</description>
      </item>
      <item>
          <title>Hosting &#39;The Littlest JupyterHub&#39;</title>
          <pubDate>Sat, 04 Apr 2026 00:00:00 +0000</pubDate>
          <author>Morgan Williams</author>
          <link>https://fluids.rocks/posts/tljh/</link>
          <guid>https://fluids.rocks/posts/tljh/</guid>
          <description xml:base="https://fluids.rocks/posts/tljh/">&lt;p&gt;Back in 2024 I ran &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://github.com/morganjwilliams/202411-sgtsg-pyrolite-workshop&quot;&gt;a &lt;code&gt;pyrolite&lt;/code&gt; workshop as part of the SGTSG conference&lt;/a&gt;
(The Geological Society of Australia&#39;s Specialist Group in Tectonics and Structural Geology Biennual Meeting),
where I used a hosted Jupyterlab instance to provide easy access to an environment with pyrolite installed for the attendees,
in an attempt to maximise the use of time and minimise technical issues.&lt;/p&gt;
&lt;p&gt;This proved a to be both technically fairly simple (in terms of getting a JupyterHub running) and logistically hard
(in terms of getting infrastructure to run it on). Since I&#39;ve wondered whether I can host such thing from a
server (desktop running Proxmox) I own, and save both the hassle and the bill. I think I&#39;ve figured out one way which will work,
and this post covers it.&lt;/p&gt;
&lt;h1 id=&quot;getting-a-large-vps-is-hard&quot;&gt;Getting a Large VPS is Hard&lt;a class=&quot;zola-anchor&quot; href=&quot;#getting-a-large-vps-is-hard&quot; aria-label=&quot;Anchor link for: getting-a-large-vps-is-hard&quot; style=&quot;visibility: hidden;&quot;&gt;&lt;/a&gt;
&lt;/h1&gt;
&lt;p&gt;I had a bunch of headaches trying to set up a server in multiple of the major players (AWS, Azure, OVH, Digital Ocean).
Azure even blocked my account for trying to do this, with no explanation as to why - not helpful a few days out from
the workshop. The other major issue was that to access nodes with enough resources to handle 40-odd simultaneous users
was nigh on impossible without having a previous record (and series of bills) with each of these providers.
I think in the end I went back to AWS and settled for something subpar
which would at least have a few folks running on the night. But the
experience wasn&#39;t particularly fun.&lt;/p&gt;
&lt;h1 id=&quot;setting-up-the-littlest-jupyterhub-tljh&quot;&gt;Setting up The Littlest JupyterHub (TLJH)&lt;a class=&quot;zola-anchor&quot; href=&quot;#setting-up-the-littlest-jupyterhub-tljh&quot; aria-label=&quot;Anchor link for: setting-up-the-littlest-jupyterhub-tljh&quot; style=&quot;visibility: hidden;&quot;&gt;&lt;/a&gt;
&lt;/h1&gt;
&lt;p&gt;As you&#39;ll find in other posts here, I use Proxmox as a host for many of my
self-hosted services (small and not-to-small). Within this, I principally use Linux Containers (LXC)
as my container solution. Typically, installing into an LXC will be pretty similar to installing
on a Virtual Private Server (if you&#39;re e.g. renting a cloud server, as I did for the workshop originally)
and especially similar to installing on any Linux host.&lt;/p&gt;
&lt;p&gt;Here I&#39;m going to start with a Debian 13 image (I think TLJH uses Debian 12 by default, which might explain
one or two of the changes I needed), which we can quickly update after starting:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;apt&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; update&lt;/span&gt;&lt;span&gt; &amp;amp;&amp;amp;&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; apt&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; upgrade&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;y&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote class=&quot;markdown-alert-important&quot;&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; I don&#39;t use &lt;code&gt;sudo&lt;/code&gt; here as I&#39;m running as &lt;code&gt;root&lt;/code&gt; for the majority of important tasks within the container,
but on your device or in your container you might need it. Particularly for this one, you might need &lt;code&gt;sudo -E&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;We&#39;ll loosely follow the &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://tljh.jupyter.org/en/latest/install/custom-server.html&quot;&gt;official guide for getting set up on your own server&lt;/a&gt;,
which starts with installing some basic pre-requisites.
I&#39;ve had issues running the &lt;code&gt;bootstrap.py&lt;/code&gt; install script on Debian 13 and Python 3.13, so have simplified
this down to a few commands below. Here this is updated to include &lt;code&gt;python3-venv&lt;/code&gt;, which for Python 3.13
as of writing this requires it:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;apt&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; install&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; python3&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; python3-dev&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; python3-venv&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; git&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; curl&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;y&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;bootstrap.py&lt;/code&gt; script as per the installation instructions, with defaults, essentially reduces
to creating a &lt;code&gt;venv&lt;/code&gt;, cloning a repo and running the installer:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;python3&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;m&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; venv&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; /opt/tljh/hub&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;/opt/tljh/hub/bin/pip&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; install&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;-upgrade&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; git+https://github.com/jupyterhub/the-littlest-jupyterhub.git&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;/opt/tljh/hub/bin/python&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;m&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; tljh.installer&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;-admin&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; admin&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;When this is finished, you can visit your local IP address (found with, for example, &lt;code&gt;ip a&lt;/code&gt;)
and you should see the Jupyter login page. It&#39;ll probably warn you that this is being served over HTTP
rather than HTTPS - and that&#39;s something we can deal with later.
Note that by default, the Jupyterhub Service is running on ports 80 and 8443 (HTTP/HTTPS),
but if you visit the IP address locally you should be mapped to the right place.
You can login with the admin username we set above (here, &lt;code&gt;admin&lt;/code&gt;), and choose a password to associate
with this account on first login.&lt;/p&gt;
&lt;h2 id=&quot;setting-up-default-authentication&quot;&gt;Setting up Default Authentication&lt;a class=&quot;zola-anchor&quot; href=&quot;#setting-up-default-authentication&quot; aria-label=&quot;Anchor link for: setting-up-default-authentication&quot; style=&quot;visibility: hidden;&quot;&gt;&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;Here we&#39;re going to modify the default authentication method (which allows anyone to
sign up, using a username and password of their choice at first login). We&#39;ll instead
stop this method from being able to create new users, so essentially whitelisting usernames.&lt;/p&gt;
&lt;p&gt;This allows whitelisted users to create a password at first login, but it won&#39;t automatically create
a user for any random person trying to access your server; this means you&#39;ll need to add
a username for anyone wanting to access it (or a set of usernames which people can take one of,
which is what I did for the workshop). This strikes a bit of a balance in terms of
security - while the presence of e.g. an &lt;code&gt;admin&lt;/code&gt; user is fairly guessable, the absence
of others makes it harder to gain access. As more users are added, there&#39;s a bit of
a security hole before they set a password, and &lt;em&gt;if they set a bad password&lt;/em&gt; but
it&#39;s manageable for e.g. workshops (the intended use case of exposing this, where you do).&lt;/p&gt;
&lt;p&gt;We can modify the YAML configuration file at &lt;code&gt;/opt/tljh/config/config.yaml&lt;/code&gt;
to have an authenticator set as follows, then reload:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;yaml&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;uthenticator&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;  t&lt;/span&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;ype&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; f&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;irstuseauthenticator.FirstUseAuthenticator&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;  c&lt;/span&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;reate_users&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; false&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;tljh-config&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; reload&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;h2 id=&quot;default-environment-management&quot;&gt;Default Environment Management&lt;a class=&quot;zola-anchor&quot; href=&quot;#default-environment-management&quot; aria-label=&quot;Anchor link for: default-environment-management&quot; style=&quot;visibility: hidden;&quot;&gt;&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;To add packages to the default environment (&lt;code&gt;apt&lt;/code&gt;, &lt;code&gt;pip&lt;/code&gt; or &lt;code&gt;conda&lt;/code&gt;),
you can log into the JupyterHub interface with your &lt;code&gt;admin&lt;/code&gt; user,
open a terminal and use e.g. &lt;code&gt;conda install -c conda-forge &amp;lt;package_name&amp;gt;&lt;/code&gt;
or &lt;code&gt;pip install &amp;lt;package_name&amp;gt;&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;For workshop-style work, consider using an &lt;code&gt;environment.yml&lt;/code&gt; or
&lt;code&gt;requirements.txt&lt;/code&gt; you manage in &lt;code&gt;git&lt;/code&gt; for this purpose, and use
e.g. &lt;code&gt;conda env update -n base -f environment.yml&lt;/code&gt; to update it.&lt;/p&gt;
&lt;h2 id=&quot;using-nbgitpuller&quot;&gt;Using &lt;code&gt;nbgitpuller&lt;/code&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#using-nbgitpuller&quot; aria-label=&quot;Anchor link for: using-nbgitpuller&quot; style=&quot;visibility: hidden;&quot;&gt;&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;If you&#39;re running a workshop with some standardised content you want
everyone to have a copy of - their own copy - then
&lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://nbgitpuller.readthedocs.io&quot;&gt;&lt;code&gt;nbgitpuller&lt;/code&gt;&lt;/a&gt;
can come in handy. This assumes you have &lt;code&gt;git&lt;/code&gt; installed (we do), and
in this instance works through providing a specific URL to attendees,
which itself includes instructions to pull the repository on login
For example, in my &lt;code&gt;pyrolite&lt;/code&gt; workshop I used this to pull a repository for
each user on login, and also some extra parameters to i) open a specific
file within that repository (here &lt;code&gt;./notebooks/00_overview.ipynb&lt;/code&gt; relative to the root
of that repository), and ii) pull down the &lt;code&gt;develop&lt;/code&gt; branch:
&lt;code&gt;https://{HUB_URL_OR_IP}/hub/user-redirect/git-pull?repo={REPOSITORY}&amp;amp;urlpath=lab%2Ftree%2F{REPOSITORY}%2Fnotebooks%2F00_overview.ipynb&amp;amp;branch=develop&lt;/code&gt;.
&lt;code&gt;nbgitpuller&lt;/code&gt; &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://nbgitpuller.readthedocs.io/en/latest/link.html&quot;&gt;provides a link generator for you&lt;/a&gt;, which is pretty simple to use.
Note this is a good way to distribute content, but in terms of environment and packages,
see &lt;a href=&quot;https://fluids.rocks/posts/tljh/#default-environment-management&quot;&gt;Default Enviroment Management&lt;/a&gt; to save doing this
per-user for packages/software.&lt;/p&gt;
&lt;h2 id=&quot;overriding-default-jupyter-settings&quot;&gt;Overriding Default Jupyter Settings&lt;a class=&quot;zola-anchor&quot; href=&quot;#overriding-default-jupyter-settings&quot; aria-label=&quot;Anchor link for: overriding-default-jupyter-settings&quot; style=&quot;visibility: hidden;&quot;&gt;&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;For example, we can create a file called &lt;code&gt;overrides.json&lt;/code&gt;, and add
&lt;code&gt;ruff&lt;/code&gt; as the default formatter:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;json&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;  &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;jupyterlab_code_formatter:settings&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;    &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;preferences&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;      &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;default_formatter&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;        &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;python&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;ruff&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;      }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;h2 id=&quot;caches-for-matplotlib-and-numba&quot;&gt;Caches for &lt;code&gt;matplotlib&lt;/code&gt; and &lt;code&gt;numba&lt;/code&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#caches-for-matplotlib-and-numba&quot; aria-label=&quot;Anchor link for: caches-for-matplotlib-and-numba&quot; style=&quot;visibility: hidden;&quot;&gt;&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;Some packages use cache directories, and wil complain about these
not existing for each user. You can set the environment variables
for the spawner with an extra configuration file:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;python&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;from&lt;/span&gt;&lt;span&gt; pathlib&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; import&lt;/span&gt;&lt;span&gt; Path&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;def&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; tmpdir&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;instance&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; key&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;    return&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; str&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;Path&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;~&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; /&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;{}&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;format&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;key&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;def&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; mpl_usr_tmp&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;instance&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;    return&lt;/span&gt;&lt;span&gt; tmpdir&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;instance&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;mpl&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;def&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; nb_usr_tmp&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;instance&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;    return&lt;/span&gt;&lt;span&gt; tmpdir&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;instance&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;numba&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;c&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Spawner&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;environment&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;update&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; #&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; noqa: F821 # type: ignore&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    {&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;MPLCONFIGDIR&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; mpl_usr_tmp&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;NUMBA_CACHE_DIR&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; nb_usr_tmp&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you write this to a file at e.g. &lt;code&gt;/opt/tljh/config/jupyterhub_config.d/usr_env_caches.py&lt;/code&gt;
the environment variables should be loaded when the notebook server starts.&lt;/p&gt;
&lt;h2 id=&quot;adding-users&quot;&gt;Adding Users&lt;a class=&quot;zola-anchor&quot; href=&quot;#adding-users&quot; aria-label=&quot;Anchor link for: adding-users&quot; style=&quot;visibility: hidden;&quot;&gt;&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;We&#39;ve set up our server to only allow users which are explicitly added
to a whitelist (i.e. already created). You can
&lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://tljh.jupyter.org/en/latest/install/custom-server.html#step-2-adding-more-users&quot;&gt;add these in bulk&lt;/a&gt;,
separated by lines. To add users in the JupyterHub interface, you
can you can go to &lt;code&gt;https://&amp;lt;HUB_DOMAIN&amp;gt;/hub/admin#/add-users&lt;/code&gt;, and add
them there.&lt;/p&gt;
&lt;h2 id=&quot;quickly-purging-users&quot;&gt;Quickly Purging Users&lt;a class=&quot;zola-anchor&quot; href=&quot;#quickly-purging-users&quot; aria-label=&quot;Anchor link for: quickly-purging-users&quot; style=&quot;visibility: hidden;&quot;&gt;&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;If you need to quickly purge the users of your hub (e.g. I used this after the
workshop), you can use this kind of setup where you obtain an API
token configuration (at &lt;code&gt;http://&amp;lt;HUB_DOMAIN_OR_IP&amp;gt;/hub/token&lt;/code&gt;):&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;python&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;import&lt;/span&gt;&lt;span&gt; requests&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;token&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;#39;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;lt;API_TOKEN&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;api_url&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;#39;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;https://&amp;lt;HUB_DOMAIN_OR_IP&amp;gt;/hub/api&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;r&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; =&lt;/span&gt;&lt;span&gt; requests&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;get&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;api_url&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; +&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;#39;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;/users&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-9 z-d-12&quot;&gt;    headers&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;=&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;             &amp;#39;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;Authorization&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;#39;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;token &lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;%s&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; %&lt;/span&gt;&lt;span&gt; token&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;            &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;Content-Type&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;application/json&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;            }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    )&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;r&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;raise_for_status&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;users&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; =&lt;/span&gt;&lt;span&gt; r&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;json&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;matching_users&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; =&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span&gt;u&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; for&lt;/span&gt;&lt;span&gt; u&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; in&lt;/span&gt;&lt;span&gt; users&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; if&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; not&lt;/span&gt;&lt;span&gt; u&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;startswith&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;admin&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;for&lt;/span&gt;&lt;span&gt; u&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; in&lt;/span&gt;&lt;span&gt; matching_users&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    requests&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;delete&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;api_url&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; +&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;/users/&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; +&lt;/span&gt;&lt;span&gt; u&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-9 z-d-12&quot;&gt;         headers&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;=&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;             &amp;#39;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;Authorization&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;#39;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;token &lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;%s&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; %&lt;/span&gt;&lt;span&gt; token&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;            &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;Content-Type&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;application/json&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;            }&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;h1 id=&quot;adding-a-tailscale-funnel-for-jupyterhub&quot;&gt;Adding a Tailscale Funnel for JupyterHub&lt;a class=&quot;zola-anchor&quot; href=&quot;#adding-a-tailscale-funnel-for-jupyterhub&quot; aria-label=&quot;Anchor link for: adding-a-tailscale-funnel-for-jupyterhub&quot; style=&quot;visibility: hidden;&quot;&gt;&lt;/a&gt;
&lt;/h1&gt;
&lt;p&gt;To make this locally hosted app available to external users, we&#39;ll use &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://tailscale.com/&quot;&gt;Tailscale&lt;/a&gt;
(and more specifically, &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://tailscale.com/docs/features/tailscale-funnel&quot;&gt;Tailscale funnel&lt;/a&gt;).
This only requires a Tailscale account, which is free. You&#39;ll end up with a publicly-accessible
URL for your JupyterHub, which if you want you could map to a subdomain in DNS configuration for a
domain you own.&lt;/p&gt;
&lt;blockquote class=&quot;markdown-alert-important&quot;&gt;
&lt;p&gt;If you&#39;re using an LXC, you&#39;ll need to mount &lt;code&gt;/dev/net/tun&lt;/code&gt; to use Tailscale.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;On Linux, you can &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://tailscale.com/docs/install/linux&quot;&gt;install Tailscale&lt;/a&gt; with a basic install script:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;curl&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;fsSL&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; https://tailscale.com/install.sh&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; |&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; sh&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To start a funnel providing access to JupyterHub on port 80, you can run:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;tailscale&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; funnel&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;bg&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;  http://localhost:80&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This will make the JupyterHub instance available on your tailnet; if you wanted, you
could point the NDS record on your custom domain to this also.&lt;/p&gt;
&lt;blockquote class=&quot;markdown-alert-caution&quot;&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; I did try doing this with &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://developers.cloudflare.com/tunnel/&quot;&gt;Cloudflare tunnels&lt;/a&gt;,
but was only half successful - I could get a tunnel to show parts of the JupyterHub interface
(the home, admin and token pages) but it wouldn&#39;t load a notebook. I suspect there&#39;s either some part of
JupyterHub running on different ports/protocols which I didn&#39;t see, or some setting I needed to enable
to get it working. After an evening of headaches trying to make this work
I switched to Tailscale and had it up after only a little fiddling.&lt;/p&gt;
&lt;/blockquote&gt;
</description>
      </item>
      <item>
          <title>Self-Hosted Discord Alternative: Haven</title>
          <pubDate>Thu, 02 Apr 2026 00:00:00 +0000</pubDate>
          <author>Morgan Williams</author>
          <link>https://fluids.rocks/posts/haven-tunnel/</link>
          <guid>https://fluids.rocks/posts/haven-tunnel/</guid>
          <description xml:base="https://fluids.rocks/posts/haven-tunnel/">&lt;p&gt;I&#39;m not the gamer I once was, but I still manage the odd evening game.
Now as before a voice chat option when gaming with friends is a necessity.
While a few more modern games (and now Steam) have voice chat built into them, I generally
prefer having an independent solution for this. I also mainly play old titles in any case.&lt;/p&gt;
&lt;p&gt;I&#39;ve used a variety of things for this in the past, including &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://www.ventrilo.com/&quot;&gt;Ventrilo&lt;/a&gt;,
the until-recently home of this in Discord, and I&#39;m currently hosting &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://www.teamspeak.com&quot;&gt;Teamspeak&lt;/a&gt;.
In the case of Discord - which I&#39;ve ceased using - I tend not to use 90% of it&#39;s &#39;features&#39;
(many of which are to me more in the realms of social media) and instead I&#39;m simply after some
basic chat (+GIFs) and voice chat capability, screen sharing is a bonus.
With Discord (among other things) moving to implement age verification, many others
also seem to be intending to leave; this serves as a bit of a template for one option:
host a server for your family/friends/community/group.&lt;/p&gt;
&lt;h2 id=&quot;looking-for-open-and-self-hosted-alternatives&quot;&gt;Looking for Open and Self-Hosted Alternatives&lt;a class=&quot;zola-anchor&quot; href=&quot;#looking-for-open-and-self-hosted-alternatives&quot; aria-label=&quot;Anchor link for: looking-for-open-and-self-hosted-alternatives&quot; style=&quot;visibility: hidden;&quot;&gt;&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;The first alternative I&#39;ve looked at is &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://www.teamspeak.com&quot;&gt;Teamspeak&lt;/a&gt;, a free
(but not open source) voice chat application, which as
noted above I&#39;m currently hosting on my Proxmox server. In general, this works fine for my
typical use case over a local connection (playing a game with my partner, who&#39;s in another room).
However, in order to invite other friends to this server, I&#39;d need to open respective ports on my
router in order for them to access the server (and probably would need a static IP). With a
hosted virtual private server (VPS) this is probably something I&#39;d go for (there are
&lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://www.teamspeak.com/en/more/find-a-host/&quot;&gt;providers which specifically host Teamspeak servers&lt;/a&gt;
if that&#39;s what you&#39;re after).&lt;/p&gt;
&lt;p&gt;The second option I looked at for Teamspeak is using a kind of tunnel service (first
&lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://tailscale.com/docs/features/tailscale-funnel&quot;&gt;Tailscale funnel&lt;/a&gt; and secondly
&lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://developers.cloudflare.com/tunnel/&quot;&gt;Cloudflare tunnel&lt;/a&gt;; I use Tailscale already
and have at least one domain managed in Cloudflare). However - this quickly runs into a
roadblock which wasn&#39;t initially apparent (to my uninitiated self), in that Teamspeak
communicates over &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://en.wikipedia.org/wiki/User_Datagram_Protocol&quot;&gt;UDP&lt;/a&gt;, TCP and HTTP -
&lt;em&gt;tunelling generally only works for HTTP/HTTPS traffic&lt;/em&gt; (at least without other work/conversion) -
so that plan was dead in the water.&lt;/p&gt;
&lt;blockquote class=&quot;markdown-alert-tip&quot;&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://www.mumble.info/&quot;&gt;Mumble&lt;/a&gt; is another open source (one-up on Teamspeak)
and fairly minimal text + voice chat option here, but similarly to Teamspeak
it also uses UDP so doesn&#39;t fit the requirements for use via a tunnelling service.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;So, looking for a voice chat application I could self host which communicates
over HTTP/HTTPS, I found &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://ancsemi.github.io/Haven/&quot;&gt;Haven&lt;/a&gt;. Generally it
has the look and feel of a slightly less flashy version of Discord (this is a plus)
while also retaining most of the functionality (and 100% of what I need from it).
As a client, the application is accessible directly in your browser,
but as a bonus for those who want it has both &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://github.com/ancsemi/Haven-Desktop&quot;&gt;desktop&lt;/a&gt;
and &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://amni-scient.com/amni-haven&quot;&gt;Android&lt;/a&gt; client applications.&lt;/p&gt;
&lt;h2 id=&quot;haven-getting-started&quot;&gt;Haven: Getting Started&lt;a class=&quot;zola-anchor&quot; href=&quot;#haven-getting-started&quot; aria-label=&quot;Anchor link for: haven-getting-started&quot; style=&quot;visibility: hidden;&quot;&gt;&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;Haven is MIT-licensed on &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://github.com/ancsemi/Haven&quot;&gt;GitHub&lt;/a&gt;, and the server
component has multiple documented installation methods for Linux systems, including:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The the &lt;em&gt;preferred method&lt;/em&gt; via Docker compose (this is arguably easier to update)&lt;/li&gt;
&lt;li&gt;Running a server directly on a host&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;accessing-your-server-networking-requirements&quot;&gt;Accessing your Server: Networking Requirements&lt;a class=&quot;zola-anchor&quot; href=&quot;#accessing-your-server-networking-requirements&quot; aria-label=&quot;Anchor link for: accessing-your-server-networking-requirements&quot; style=&quot;visibility: hidden;&quot;&gt;&lt;/a&gt;
&lt;/h3&gt;
&lt;p&gt;In terms of the networking setup to share your server with friends, Haven has multiple
options. The first two don&#39;t require any manual intervention on your part, and
will work with either installation method:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Use it locally, with IP addresses within your home network - works for who you live
with and maybe your neighbour which has your WiFi password&lt;/li&gt;
&lt;li&gt;Use the built-in Cloudflare or Localtunnel options -
these will give you (likely randomly generated) externally accessible URLs which
&#39;just work&#39;. If your server is intended to be ephemeral, or you&#39;re happy to ship around
a URL to friends whenever you want to jump into a game - this might work pretty well for you.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The third set of options does require some extra work, but will give you a persistent URL
to access Haven at, and is possible to configure via a range of services:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;With &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://developers.cloudflare.com/tunnel/&quot;&gt;Cloudflare tunnel&lt;/a&gt;, if you have a domain with them&lt;/li&gt;
&lt;li&gt;With &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://tailscale.com/docs/features/tailscale-funnel&quot;&gt;Tailscale funnel&lt;/a&gt;, although arguably less straightforward to setup&lt;/li&gt;
&lt;li&gt;With &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://localtunnel.app/&quot;&gt;Localtunnel&lt;/a&gt;, if you pay for the non-free tier (from US$5/month).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;We&#39;ll look at using a tunnel via Cloudflare below, and link it to the domain which this blog is hosted on.&lt;/p&gt;
&lt;blockquote class=&quot;markdown-alert-note&quot;&gt;
&lt;p&gt;While this is largely focused on using your own hardware (particularly if you have an old computer lying around
and a need for these kinds of basic services) - but f you&#39;re happy to pay for it, a Virtual Private Server (VSP)
is also a good option - you&#39;ll likely get relatively predictable monthly pricing, and this can be simpler and
&lt;em&gt;in some ways&lt;/em&gt; more secure than hosting something on your own machine. You won&#39;t necessarily need a domain
(although you could map a domain to your VPS if you wanted) and while the networking setup likely requires a
little configuration it&#39;s probably a well documented thing for whatever service you&#39;re looking at using.
Note above the limitations of having simultaneous usage of HTTP/HTTPS, TCP and UDP in an application would be
possible in this scenario (you could host Teamspeak or Mumble if voice chat is the core part you&#39;re after) -
and you could also just use a provider which hosts specific services for you, as I&#39;d mentioned above for Teamspeak.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;installation&quot;&gt;Installation&lt;a class=&quot;zola-anchor&quot; href=&quot;#installation&quot; aria-label=&quot;Anchor link for: installation&quot; style=&quot;visibility: hidden;&quot;&gt;&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;In this instance I&#39;m going to start with a Debian 12 LXC image, running on Proxmox.
We can quickly update it after starting:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;apt&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; update&lt;/span&gt;&lt;span&gt; &amp;amp;&amp;amp;&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; apt&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; upgrade&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;y&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote class=&quot;markdown-alert-important&quot;&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; I don&#39;t use &lt;code&gt;sudo&lt;/code&gt; here as I&#39;m running as &lt;code&gt;root&lt;/code&gt; for the majority of important tasks within the container,
but on your device or in your container you might need it.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;In either scenario, we&#39;ll use the git repository as a starting point:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;git&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; clone&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; https://github.com/ancsemi/Haven.git&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;cd&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; Haven&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;h2 id=&quot;option-1-docker&quot;&gt;Option 1: Docker&lt;a class=&quot;zola-anchor&quot; href=&quot;#option-1-docker&quot; aria-label=&quot;Anchor link for: option-1-docker&quot; style=&quot;visibility: hidden;&quot;&gt;&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;Haven hosts a &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://github.com/ancsemi/Haven/blob/main/docker-compose.yml&quot;&gt;&lt;code&gt;docker-compose.yml&lt;/code&gt;&lt;/a&gt;
in their repository, and &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://ghcr.io/ancsemi/haven:latest&quot;&gt;a pre-built image is available&lt;/a&gt;
to save building the repository from source; this image is used by default.&lt;/p&gt;
&lt;p&gt;To get setup with Docker, we can first install the Docker engine (if you don&#39;t already have Docker
in your environment):&lt;/p&gt;
&lt;details&gt;
&lt;summary&gt;Docker Installation&lt;/summary&gt;
&lt;p&gt;We&#39;ll follow the &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://docs.docker.com/engine/install/debian/#install-using-the-repository&quot;&gt;Docker instructions on doing
this in a Debian-based system using &lt;code&gt;apt&lt;/code&gt;&lt;/a&gt;
but I&#39;ve added the (March 2026) commands for doing this below.&lt;/p&gt;
&lt;p&gt;The first step is to remove any existing Docker components:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;apt&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; remove&lt;/span&gt;&lt;span&gt; $(&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;dpkg&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;-get-selections&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; docker.io&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; docker-compose&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; docker-doc&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; podman-docker&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; containerd&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; runc&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; |&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; cut&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;f1&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;After that we add Docker&#39;s GPG key:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;apt&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; install&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; curl&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;y&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;install&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;m&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; 0755&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;d&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; /etc/apt/keyrings&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;curl&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;fsSL&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; https://download.docker.com/linux/debian/gpg&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;o&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; /etc/apt/keyrings/docker.asc&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;chmod&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; a+r&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; /etc/apt/keyrings/docker.asc&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And from here modify the &lt;code&gt;apt&lt;/code&gt; sources to add the Docker repository:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;tee&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; /etc/apt/sources.list.d/docker.sources&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; &amp;lt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;EOF&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;Types: deb&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;URIs: https://download.docker.com/linux/debian&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;Suites: &lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;$(&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; /etc/os-release&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;amp;&amp;amp;&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; echo&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span&gt;$&lt;/span&gt;&lt;span&gt;VERSION_CODENAME&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;Components: stable&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;Architectures: &lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;$(&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;dpkg&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;-print-architecture&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;Signed-By: /etc/apt/keyrings/docker.asc&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;EOF&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now we can install the Docker packages:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;apt&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; update&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;apt&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; install&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; docker-ce&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; docker-ce-cli&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; containerd.io&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; docker-buildx-plugin&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; docker-compose-plugin&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;y&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And check the status of Docker with (&lt;code&gt;Ctrl+C&lt;/code&gt; /&lt;code&gt;:q&lt;/code&gt; to exit):&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;systemctl&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; status&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; docker&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/details&gt;
&lt;p&gt;From here, assuming we&#39;re in the Haven repository, we can use Docker compose to
pull the images and start the container in detached mode:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;docker&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; compose&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; up&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;d&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I&#39;ve added the default YAML compose file below for reference, if you&#39;re curious:&lt;/p&gt;
&lt;details&gt;
&lt;summary&gt;&lt;code&gt;docker-compose.yml&lt;/code&gt;
&lt;/summary&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;yaml&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;#&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; ── Haven Docker Compose ─────────────────────────────────&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;#&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; Quick start:  docker compose up -d&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;#&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; Stop:         docker compose down&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;#&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; View logs:    docker compose logs -f haven&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;#&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; Update:       docker compose pull &amp;amp;&amp;amp; docker compose up -d --force-recreate&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;#&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; Shell:        docker compose exec haven sh&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;#&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; ─────────────────────────────────────────────────────────&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;s&lt;/span&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;ervices&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;  h&lt;/span&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;aven&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;    i&lt;/span&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;mage&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; g&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;hcr.io/ancsemi/haven:latest&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;    #&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; build: .                    # Uncomment to build from source instead of using the pre-built image&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;    c&lt;/span&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;ontainer_name&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; h&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;aven&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;    p&lt;/span&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;orts&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;      -&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;${PORT:-3000}:${PORT:-3000}&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; #&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; Main HTTPS port&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;      -&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;${REDIRECT_PORT:-3001}:${REDIRECT_PORT:-3001}&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; #&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; HTTP → HTTPS redirect&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;    v&lt;/span&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;olumes&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;      -&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; h&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;aven_data:/data&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;      #&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; ── Or use a local folder instead (good for NAS / Synology): ──&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;      #&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; - ./haven-data:/data&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;    e&lt;/span&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;nvironment&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;      -&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; P&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;ORT=${PORT:-3000}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;      -&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; H&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;OST=0.0.0.0&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;      #&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; - SERVER_NAME=My Haven           # Name shown in the UI&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;      #&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; - ADMIN_USERNAME=admin            # First account to register becomes admin&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;      #&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; - GIPHY_API_KEY=                  # Optional: enable GIF search&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;      #&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; - VAPID_EMAIL=mailto:you@example  # Optional: email for push notifications&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;      #&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; - TURN_URL=turn:your-server:3478  # Optional: TURN relay for voice over internet&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;      #&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; - TURN_SECRET=your-secret         # Shared secret (coturn --use-auth-secret)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;    #&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; Uncomment to load settings from a .env file:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;    #&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; env_file: .env&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;    r&lt;/span&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;estart&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; u&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;nless-stopped&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;v&lt;/span&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;olumes&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;  h&lt;/span&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;aven_data&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/details&gt;
&lt;p&gt;After this completes, your service should now be running on &lt;code&gt;https://&amp;lt;LOCAL_IP_ADDRESS&amp;gt;:3000&lt;/code&gt;.
To check the IP address of your machine, you can use the following to list all of your network
interfaces:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;ip&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; a&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you know you have e.g. an ethernet interface on &lt;code&gt;eth0&lt;/code&gt;, you can use:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;ip&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; a&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; show&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; eth0&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You can take the address (an IPv4 address will look something like 192.168.0.127) you can visit
e.g. https://192.168.0.127:3000 to access your Haven server; note you&#39;ll probably have a
&#39;Be careful. Something doesn’t look right.&#39; kind of warning message
the first time you access this, you can click &#39;Advanced&#39; -&amp;gt; &#39;Proceed&#39;.
You should now see a login/register page for Haven. You can register with the username
&lt;code&gt;admin&lt;/code&gt; in order to have access to server admin functions (you can change the display name
later). If you don&#39;t need a persistent tunnel for external access, you&#39;re now done, congrats!
Have a look through setting up your server, and if you&#39;re going to use the built-in tunnel
functionality, at least consider whitelisting the usernames who are able to register.&lt;/p&gt;
&lt;h3 id=&quot;updating&quot;&gt;Updating&lt;a class=&quot;zola-anchor&quot; href=&quot;#updating&quot; aria-label=&quot;Anchor link for: updating&quot; style=&quot;visibility: hidden;&quot;&gt;&lt;/a&gt;
&lt;/h3&gt;
&lt;p&gt;In terms of updating, it&#39;s typically going to look like:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;docker&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; compose&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; pull&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;docker&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; compose&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; up&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;d&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;-force-recreate&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;h2 id=&quot;option-2-server-directly-on-the-host&quot;&gt;Option 2: Server Directly on the Host&lt;a class=&quot;zola-anchor&quot; href=&quot;#option-2-server-directly-on-the-host&quot; aria-label=&quot;Anchor link for: option-2-server-directly-on-the-host&quot; style=&quot;visibility: hidden;&quot;&gt;&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;If you&#39;re running this in a Linux Container (LXC), you already have one level
of isolation on your host, and you don&#39;t necessarily need to use Docker.
Similarly, if this e.g. the only thing running on a small machine, you could
install it directly on the host.&lt;/p&gt;
&lt;p&gt;To use the server, the main thing we&#39;ll need is&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;apt&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; install&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; nodejs&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; npm&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To run the server, from the repository we&#39;ve cloned above,
we can use the start scripts provided:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;chmod&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; +x&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; start.sh&lt;/span&gt;&lt;span&gt; &amp;amp;&amp;amp;&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; ./start.sh&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This will run until killed, so any other things we want running will
need to run another way.&lt;/p&gt;
&lt;h2 id=&quot;tunnel-setup&quot;&gt;Tunnel Setup&lt;a class=&quot;zola-anchor&quot; href=&quot;#tunnel-setup&quot; aria-label=&quot;Anchor link for: tunnel-setup&quot; style=&quot;visibility: hidden;&quot;&gt;&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;Once this is up and looks OK locally, we can &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://dash.cloudflare.com/?to=/:account/tunnels&quot;&gt;add the route in Cloudflare&lt;/a&gt;
by configuring your tunnel in the &lt;code&gt;...&lt;/code&gt; menu, and then clicking &lt;code&gt;Add Route&lt;/code&gt; in the Routes diagram.
You&#39;ll need to select your subdomain (I used &lt;code&gt;hvn&lt;/code&gt;), and add the service URL (Haven uses
&lt;code&gt;https://localhost:3000&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;We will need to &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://developers.cloudflare.com/tunnel/setup/&quot;&gt;create a tunnel on Cloudflare&lt;/a&gt;,
and get a tunnel token. Once you&#39;ve created your tunnel, you can extract the
token from one of the installation options which Cloudflare gives you
(copy paste and take the long text token at the end).&lt;/p&gt;
&lt;blockquote class=&quot;markdown-alert-important&quot;&gt;
&lt;div class=&quot;footnote-definition&quot; id=&quot;1&quot;&gt;&lt;sup class=&quot;footnote-definition-label&quot;&gt;1&lt;/sup&gt;
&lt;p&gt;One of the key things here which otherwise tripped me up was that
the setup for Haven requires &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://dash.cloudflare.com/?to=/:account/one/networks/connectors/&quot;&gt;a configuration change in Cloudflare Zero Trust&lt;/a&gt;
to &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://community.cloudflare.com/t/one-of-my-cloudflare-tunnels-is-returning-a-bad-gateway-error/483145/6&quot;&gt;turn off TLS verification &lt;/a&gt;
(you might need to make an Org to get in; the basics of this are still free-tier).
For Docker, I&#39;d tried using &lt;code&gt;--no-tls-verify&lt;/code&gt; in the run command and &lt;code&gt;NO_TLS_VERIFY=true&lt;/code&gt;
in the environment but neither helped, and I was relegated to the Cloudflare
dashboard.&lt;br&gt;
You&#39;ll need to select your tunnel in the page linked above, select the
&#39;published application routes&#39; tab, and then click on the &#39;⋮&#39; menu,
select &#39;Edit&#39;, go down to &#39;Additional application settings&#39;,
select &#39;TLS&#39; and turn on &#39;No TLS Verify&#39;. Save this, and with luck you
can access your server via your subdomain added earlier
(e.g. &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://hvn.fluids.rocks&quot;&gt;https://hvn.fluids.rocks&lt;/a&gt;).&lt;/p&gt;
&lt;/div&gt;
&lt;/blockquote&gt;
&lt;blockquote class=&quot;markdown-alert-tip&quot;&gt;
&lt;p&gt;If you want to see what this might look like with Tailscale funnel (would require
a Tailscale account but otherwise free and doesn&#39;t require you to own a domain, let alone
one in Cloudflare), let me know and I&#39;ll see what I can put together!&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&quot;tunnel-on-the-host&quot;&gt;Tunnel on the Host&lt;a class=&quot;zola-anchor&quot; href=&quot;#tunnel-on-the-host&quot; aria-label=&quot;Anchor link for: tunnel-on-the-host&quot; style=&quot;visibility: hidden;&quot;&gt;&lt;/a&gt;
&lt;/h3&gt;
&lt;p&gt;To use &lt;code&gt;cloudflared&lt;/code&gt; with an installation of Haven on the host,
we can pull down the binary from GitHub:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;curl&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;L&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-amd64&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;o&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; /usr/local/bin/cloudflared&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;chmod&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; +x&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; /usr/local/bin/cloudflared&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We can verify this is installed:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;cloudflared&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;-version&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You can run the binary itself to create a tunnel (e.g. without a token this would create an anonymous/
randomized url), but we&#39;ll set it up as a service using our tunnel token:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;cloudflared&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; service&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; install&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; &amp;lt;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;TUNNEL_TOKE&lt;/span&gt;&lt;span&gt;N&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Once installed, we can start the &lt;code&gt;cloudflared&lt;/code&gt; service:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;systemctl&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; start&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; cloudflared&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If any of the configuration (IP addresses, the TLS settings etc) changes, it&#39;s best to restart the service:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;systemctl&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; restart&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; cloudflared&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;From here, we would then run the server:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;chmod&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; +x&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; start.sh&lt;/span&gt;&lt;span&gt; &amp;amp;&amp;amp;&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; ./start.sh&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;h3 id=&quot;tunnel-in-docker&quot;&gt;Tunnel in Docker&lt;a class=&quot;zola-anchor&quot; href=&quot;#tunnel-in-docker&quot; aria-label=&quot;Anchor link for: tunnel-in-docker&quot; style=&quot;visibility: hidden;&quot;&gt;&lt;/a&gt;
&lt;/h3&gt;
&lt;p&gt;If you want to use Docker but want a peristent tunnel URL, we can modify the
&lt;code&gt;docker-compose.yml&lt;/code&gt; file to add a &lt;code&gt;cloudflared&lt;/code&gt; service which provides the
tunnel to the networks. This requires:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;A Cloudflare account&lt;/li&gt;
&lt;li&gt;A domain managed by Cloudflare which you can attach this to (i.e., the persistent part)&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Before we do anything else, we can quickly take down our
Docker services:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;docker&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; compose&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; down&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We&#39;ll insert our tunnel token in place of &lt;code&gt;&amp;lt;TUNNEL_TOKEN&amp;gt;&lt;/code&gt; in the below YAML file:&lt;/p&gt;
&lt;details&gt;
&lt;summary&gt;&lt;code&gt;docker-compose-cloudflared.yml&lt;/code&gt;
&lt;/summary&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;# ── Haven Docker Compose ─────────────────────────────────&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;# Quick start:  docker compose up -f docker-compose-cloudflared.yml -d&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;# Stop:         docker compose down&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;# View logs:    docker compose logs -f haven&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;# Update:       docker compose -f docker-compose-cloudflared.yml pull &amp;amp;&amp;amp; docker compose up -f docker-compose-cloudflared.yml -d --force-recreate&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;# Shell:        docker compose exec haven sh&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;# ─────────────────────────────────────────────────────────&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;services:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  cloudflared:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    image: cloudflare/cloudflared:latest&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    container_name: cloudflared&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    restart: unless-stopped&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    command: tunnel --no-autoupdate run&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    environment:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;      - TUNNEL_TOKEN=&amp;lt;TUNNEL_TOKEN&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    ports:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;      - &amp;quot;${PORT:-3000}:${PORT:-3000}&amp;quot; # Main HTTPS port&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;      - &amp;quot;${REDIRECT_PORT:-3001}:${REDIRECT_PORT:-3001}&amp;quot; # HTTP → HTTPS redirect&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  haven:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    image: ghcr.io/ancsemi/haven:latest&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    # build: .                    # Uncomment to build from source instead of using the pre-built image&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    container_name: haven&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    #ports:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    #  - &amp;quot;${PORT:-3000}:${PORT:-3000}&amp;quot;    # Main HTTPS port&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    #  - &amp;quot;${REDIRECT_PORT:-3001}:${REDIRECT_PORT:-3001}&amp;quot;  # HTTP → HTTPS redirect&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    volumes:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;      - haven_data:/data&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;      # ── Or use a local folder instead (good for NAS / Synology): ──&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;      # - ./haven-data:/data&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    environment:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;      - PORT=${PORT:-3000}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;      - HOST=0.0.0.0&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;      # - FORCE_HTTP=true&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;      # - SERVER_NAME=My Haven           # Name shown in the UI&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;      # - ADMIN_USERNAME=admin            # First account to register becomes admin&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;      # - GIPHY_API_KEY=                  # Optional: enable GIF search&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;      # - VAPID_EMAIL=mailto:you@example  # Optional: email for push notifications&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;      # - TURN_URL=turn:your-server:3478  # Optional: TURN relay for voice over internet&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;      # - TURN_SECRET=your-secret         # Shared secret (coturn --use-auth-secret)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    # Uncomment to load settings from a .env file:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    # env_file: .env&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    restart: unless-stopped&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    network_mode: service:cloudflared&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;volumes:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  haven_data:&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/details&gt;
&lt;p&gt;In this file we&#39;ve added a bit of network configuration:
for the &lt;code&gt;cloudflared&lt;/code&gt; service we&#39;ve mapped the ports 3000 and 3001
(used by Haven) and in the &lt;code&gt;haven&lt;/code&gt; service used &lt;code&gt;network_mode: service:cloudflared&lt;/code&gt;.
The port mapping allows local IP access (if you don&#39;t need it, you can remove this),
and the &lt;code&gt;network_mode: service:cloudflared&lt;/code&gt; ensures that Haven can use our tunnel.
Note that we&#39;re not mapping ports in the &lt;code&gt;haven&lt;/code&gt; service here (the port mapping
has just moved up).&lt;/p&gt;
&lt;blockquote class=&quot;markdown-alert-note&quot;&gt;
&lt;p&gt;There may be some secret sauce which means we don&#39;t need to
do the &lt;code&gt;no-tls-verify&lt;/code&gt; faff above&lt;sup class=&quot;footnote-reference&quot;&gt;&lt;a href=&quot;#1&quot;&gt;1&lt;/a&gt;&lt;/sup&gt;, but I haven&#39;t found it yet (tried the &lt;code&gt;PORT&lt;/code&gt; variable,
but I had no luck with this so far) .&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;We can copy the contents of the file above, and paste it to a file called
&lt;code&gt;docker-compose-cloudflared.yml&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;nano&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; docker-compose-cloudflared.yml&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;From here, we can start up our modified services:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;docker&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; compose&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; docker-compose-cloudflared.yml&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; up&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;d&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;-force-recreate&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Before we go any further, if you&#39;ve kept the port mapping it&#39;s best to check that you
can access your service at your local IP address (e.g. https://192.168.0.127:3000, but using
the IP address of your machine). If there&#39;s an issue already here, we might not succeed in the
tunnel bit.&lt;/p&gt;
&lt;h2 id=&quot;whitelisting-users-in-haven&quot;&gt;Whitelisting Users in Haven&lt;a class=&quot;zola-anchor&quot; href=&quot;#whitelisting-users-in-haven&quot; aria-label=&quot;Anchor link for: whitelisting-users-in-haven&quot; style=&quot;visibility: hidden;&quot;&gt;&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;By default, your Haven instance will allow registrations from anyone who accesses the server.
This may not be what you&#39;re after.
If you have a group of friends you&#39;re looking to host a server for, you can do a call
around and get a list of usernames to whitelist in Haven, which marginally improves
your security situation (random folks who find your server won&#39;t be able to sign up
without using one of these usernames).&lt;/p&gt;
&lt;p&gt;If you&#39;re logged in as the/an admin user, the configuration for this is currently under
Settings -&amp;gt; Whitelist. You&#39;ll need to both i) turn on whitelisting with the &#39;enabled&#39; toggle
and ii) add the users you&#39;re allowing to sign up.&lt;/p&gt;
&lt;p&gt;You can also use invite codes, and set channels to invite-only/private, if you&#39;re planning to
use multiple for different purposes/allow different folk to do and see different things.&lt;/p&gt;
</description>
      </item>
      <item>
          <title>Zola + Codeberg Pages: Deploying a Blog</title>
          <pubDate>Sun, 29 Mar 2026 00:00:00 +0000</pubDate>
          <author>Morgan Williams</author>
          <link>https://fluids.rocks/posts/codeberg-pages-zola/</link>
          <guid>https://fluids.rocks/posts/codeberg-pages-zola/</guid>
          <description xml:base="https://fluids.rocks/posts/codeberg-pages-zola/">&lt;h1 id=&quot;codeberg-pages&quot;&gt;Codeberg Pages&lt;a class=&quot;zola-anchor&quot; href=&quot;#codeberg-pages&quot; aria-label=&quot;Anchor link for: codeberg-pages&quot; style=&quot;visibility: hidden;&quot;&gt;&lt;/a&gt;
&lt;/h1&gt;
&lt;p&gt;Similar to GitHub pages (which this site was originally deployed on), Codeberg has &#39;Codeberg pages&#39; which uses a simliar but open technology to deploy the content of a git repository as a webpage. Similar to GitHub pages which allows you to have one uncomplicated URL tied to your repository (e.g., &lt;code&gt;{username}.github.io&lt;/code&gt;), Codeberg does the same (&lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://morganjwilliams.codeberg.page&quot;&gt;morganjwilliams.codeberg.page&lt;/a&gt;). It has a few prerequisites:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;You&#39;ll need a &lt;strong&gt;public&lt;/strong&gt; repository called &lt;code&gt;pages&lt;/code&gt; (for the variant tied to your username; you can also set up Codeberg pages for other repositories).&lt;/li&gt;
&lt;li&gt;Within this, you&#39;ll need a branch called &lt;code&gt;pages&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Beyond that you&#39;ll need to &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://docs.codeberg.org/codeberg-pages/#user%2Forganization-websites&quot;&gt;setup a webhook in settings&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;h1 id=&quot;moving-to-zola&quot;&gt;Moving to Zola&lt;a class=&quot;zola-anchor&quot; href=&quot;#moving-to-zola&quot; aria-label=&quot;Anchor link for: moving-to-zola&quot; style=&quot;visibility: hidden;&quot;&gt;&lt;/a&gt;
&lt;/h1&gt;
&lt;p&gt;This site was originally built in &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://github.com/jekyll/jekyll&quot;&gt;Jekyll&lt;/a&gt; (a static site generator using Ruby), and I&#39;m now using &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://www.getzola.org/&quot;&gt;Zola&lt;/a&gt; (a static site generator built in Rust). Both build HTML sites from templated markdown, Zola&#39;s a bit quicker, and for the time being I find it less complex to work with.&lt;/p&gt;
&lt;h2 id=&quot;installing-zola&quot;&gt;Installing Zola&lt;a class=&quot;zola-anchor&quot; href=&quot;#installing-zola&quot; aria-label=&quot;Anchor link for: installing-zola&quot; style=&quot;visibility: hidden;&quot;&gt;&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;If you have Rust and &lt;code&gt;cargo&lt;/code&gt; installed, you can build Zola from source (&lt;code&gt;cargo install --locked --git https://github.com/getzola/zola&lt;/code&gt;),
but there&#39;s a long list of binary/executable install methods on &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://www.getzola.org/documentation/getting-started/installation/&quot;&gt;Zola&#39;s installation page&lt;/a&gt;.
There are also prebuilt binaries in GitHub, should you wish to just use one (maybe do/don&#39;t add it to your git repo?).&lt;/p&gt;
&lt;h2 id=&quot;get-a-theme&quot;&gt;Get a Theme&lt;a class=&quot;zola-anchor&quot; href=&quot;#get-a-theme&quot; aria-label=&quot;Anchor link for: get-a-theme&quot; style=&quot;visibility: hidden;&quot;&gt;&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;Like Jekyll, Zola has a decent number of themes you can use to get a leg up on styling your page (or, &lt;em&gt;leave it to someone with the interest, and find something which fits your vibe&lt;/em&gt;). These inform both the structure and styling of the page, and while a bit of a learning curve, the main bits will likely be documented.&lt;/p&gt;
&lt;p&gt;Looking to simplify a little from where I&#39;d been (and the components I&#39;d modified/made with little &lt;em&gt;real&lt;/em&gt; understanding of what I was doing) with Jekyll, I went with &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://github.com/isunjn/serene&quot;&gt;&lt;code&gt;serene&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;blockquote class=&quot;markdown-alert-note&quot;&gt;
&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;: Themes in Zola are typically best managed as updatable git submodules; this isn&#39;t something everyone deals with every day, but it&#39;s something to consider
in your build and deploy workflows.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h1 id=&quot;building-your-site&quot;&gt;Building Your Site&lt;a class=&quot;zola-anchor&quot; href=&quot;#building-your-site&quot; aria-label=&quot;Anchor link for: building-your-site&quot; style=&quot;visibility: hidden;&quot;&gt;&lt;/a&gt;
&lt;/h1&gt;
&lt;p&gt;Zola has some simple commands for building your site. By default, &lt;code&gt;zola build&lt;/code&gt; will build your site and put the contents in &lt;code&gt;./public/&lt;/code&gt;. &lt;code&gt;zola serve&lt;/code&gt; will give you a live preview of content as you edit it on a local webserver. In terms of deployment, you&#39;ll typically want to run &lt;code&gt;zola build&lt;/code&gt; and deploy the rendered content within &lt;code&gt;./public&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id=&quot;without-ci-workflows&quot;&gt;Without CI Workflows&lt;a class=&quot;zola-anchor&quot; href=&quot;#without-ci-workflows&quot; aria-label=&quot;Anchor link for: without-ci-workflows&quot; style=&quot;visibility: hidden;&quot;&gt;&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;Unlike GitHub, &#39;actions&#39; in Codeberg are a little less plug and play, and building locally and just pushing rendered content  is a viable (but repetitive) option.
This is how I started, using a two-parallel-branch strategy in git - a main branch where &#39;structure&#39; lives (what I build from) and a &#39;pages&#39; branch where I push updates to content (HTML, mostly) which then gets rendered on Codeberg pages. I wrote a &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://codeberg.org/morganjwilliams/pages/src/branch/main/make.bat&quot;&gt;simple batch script&lt;/a&gt; to automate the &#39;build, switch branches, move things and stage changes on the &lt;code&gt;pages&lt;/code&gt; branch&#39; workflow.&lt;/p&gt;
&lt;h3 id=&quot;local-tools-of-choice-habit&quot;&gt;Local Tools (of &lt;em&gt;Choice/Habit&lt;/em&gt;)&lt;a class=&quot;zola-anchor&quot; href=&quot;#local-tools-of-choice-habit&quot; aria-label=&quot;Anchor link for: local-tools-of-choice-habit&quot; style=&quot;visibility: hidden;&quot;&gt;&lt;/a&gt;
&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;I&#39;m still on Windows for my main personal PC now, so it&#39;s good old &lt;code&gt;cmd&lt;/code&gt; and/or Powershell for a terminal.&lt;/li&gt;
&lt;li&gt;I use VS Code for work, and it&#39;s free, so in terms of editors, that&#39;s where it&#39;s at. I just find all the buttons to turn off anything referencing AI.&lt;/li&gt;
&lt;li&gt;I use GitKraken as a git GUI to save my brain a little with some git management. &lt;em&gt;But&lt;/em&gt;, having habitually run into authentication issues as 90% of the repositories I work on are private/not feeling the need to bake in credentials into this tool, I typically push to/pull from remotes from the command line. Also turned off all the AI features here.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;using-ci-workflows&quot;&gt;Using CI Workflows&lt;a class=&quot;zola-anchor&quot; href=&quot;#using-ci-workflows&quot; aria-label=&quot;Anchor link for: using-ci-workflows&quot; style=&quot;visibility: hidden;&quot;&gt;&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;Codeberg does have hosted solutions for basic workflows accessible after enabling them in a few settings (alongside their &#39;Woodpecker CI&#39; solution which
requires approval). I&#39;ve since set up self-hosted runners for this purpose, &lt;a href=&quot;https://fluids.rocks/posts/forgejo-self-hosted-runner/&quot;&gt;see the post covering that here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://codeberg.org/morganjwilliams/pages/src/branch/main/.forgejo/workflows/build.yaml&quot;&gt;action I&#39;ve set up for deploying the page&lt;/a&gt; currently uses a self-hosted runner, builds Zola from source, builds the site, and pushes the changes to the &lt;code&gt;pages&lt;/code&gt; branch in a simiiar fashion to how I&#39;d done it locally prior to this.&lt;/p&gt;
&lt;p&gt;&lt;del&gt;Note that the &lt;em&gt;caching&lt;/em&gt; of Zola/&lt;code&gt;cargo&lt;/code&gt; is a work in progress (it&#39;ll need some modifications to work...), but this should minimise the computational load
until a new version of Zola is requested in the workflow.&lt;/del&gt; I&#39;ve set up &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://codeberg.org/morganjwilliams/pages/src/branch/main/.forgejo/workflows/build.yaml&quot;&gt;caching of the Zola &lt;code&gt;cargo&lt;/code&gt; build&lt;/a&gt;, which makes the workflow about 6x faster; when a new Zola version is desired I can just bump an environment variable
and the cache should refresh.&lt;/p&gt;
&lt;h1 id=&quot;serene-reactions&quot;&gt;&lt;code&gt;serene&lt;/code&gt; Reactions&lt;a class=&quot;zola-anchor&quot; href=&quot;#serene-reactions&quot; aria-label=&quot;Anchor link for: serene-reactions&quot; style=&quot;visibility: hidden;&quot;&gt;&lt;/a&gt;
&lt;/h1&gt;
&lt;p&gt;I&#39;m not tracking you when you visit here, and as it&#39;s a static site you&#39;ll not have a great deal of interactivity here, and I won&#39;t know you&#39;ve been (feel free to ping me on Mastodon &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://aus.social/@metasomite&quot;&gt;@metasomite&lt;/a&gt; though!), making it feel very &#39;shout into the ether&#39;. To slightly remedy this and give an option for anonymously noting &#39;I was here&#39;,
I&#39;ve followed &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://github.com/isunjn/reaction&quot;&gt;&lt;code&gt;serene&lt;/code&gt;&#39;s guide to adding a basic reactions endpoint&lt;/a&gt; using a basic Cloudflare database and a hundred or so lines of Typescript defining the endpoint to allow each post to have reactions at the bottom. &lt;em&gt;If I&#39;m missing one you&#39;d rather use, let me know...&lt;/em&gt;&lt;/p&gt;
&lt;blockquote class=&quot;markdown-alert-tip&quot;&gt;
&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;: As I have &lt;code&gt;cargo&lt;/code&gt; installed locally (will get to talking about Rust one day), I used &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://github.com/Schniz/fnm&quot;&gt;&lt;code&gt;fnm&lt;/code&gt;&lt;/a&gt; to install Node; note the &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://github.com/Schniz/fnm?tab=readme-ov-file#shell-setup&quot;&gt;shell setup&lt;/a&gt; step if you want to follow along, in my case using Powershell.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h1 id=&quot;using-a-custom-domain&quot;&gt;Using a Custom Domain&lt;a class=&quot;zola-anchor&quot; href=&quot;#using-a-custom-domain&quot; aria-label=&quot;Anchor link for: using-a-custom-domain&quot; style=&quot;visibility: hidden;&quot;&gt;&lt;/a&gt;
&lt;/h1&gt;
&lt;p&gt;If you don&#39;t want to have &lt;code&gt;{username}.codeberg.page&lt;/code&gt; as your domain, you can fork out for a custom domain from one of the many registrars.
To use a custom domain to host your site on Codeberg Pages, at the root of your &lt;code&gt;pages&lt;/code&gt; branch of your repository, you&#39;ll need a file called &lt;code&gt;.domains&lt;/code&gt; containing your domain at the top.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Note that the workflow contained in the &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://www.getzola.org/documentation/deployment/codeberg-pages/&quot;&gt;Zola instructions for getting set up on Codeberg Pages&lt;/a&gt; contains a step which would remove the &lt;code&gt;.domains&lt;/code&gt; (&lt;code&gt;git rm -r &quot;*&quot; || true&lt;/code&gt;) and return an associated error in your page when deployed; in my deployment workflow I&#39;ve modified this to refer to specific filetypes/avoid the &lt;code&gt;.&lt;/code&gt; files with &lt;code&gt;git rm -r *.html *.json *.xml *.txt *.js *.css *.jpg *.png || true&lt;/code&gt; - YMMV.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;You will also need to &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://docs.codeberg.org/codeberg-pages/using-custom-domain/#option-3%3A-a%2Faaaa-record&quot;&gt;set up some custom DNS (I needed this variant)&lt;/a&gt;.&lt;/p&gt;
</description>
      </item>
      <item>
          <title>Setting Up Self-Hosted Forgejo Runners</title>
          <pubDate>Sat, 28 Mar 2026 00:00:00 +0000</pubDate>
          <author>Morgan Williams</author>
          <link>https://fluids.rocks/posts/forgejo-self-hosted-runner/</link>
          <guid>https://fluids.rocks/posts/forgejo-self-hosted-runner/</guid>
          <description xml:base="https://fluids.rocks/posts/forgejo-self-hosted-runner/">&lt;p&gt;In an earlier post I mentioned that Codeberg actions was less &#39;plug and play&#39; than GitHub actions, and it is.
But it&#39;s not quite as difficult to get going as I&#39;d thought, and regarding setting up self-hosted runners for Codeberg actions
went from &#39;not today&#39; to &#39;in a few days&#39; pretty quickly.&lt;/p&gt;
&lt;p&gt;First, you need to turn on actions in a Codeberg repository, at &lt;code&gt;https://codeberg.org/{username}/{repo}/settings/units&lt;/code&gt;
you&#39;ll need to select &#39;Actions&#39; to show the tab in the repo and also configure runners.&lt;/p&gt;
&lt;p&gt;Second, &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://codeberg.org/actions/meta&quot;&gt;hosted runners &lt;em&gt;are&lt;/em&gt; now a thing on Codeberg&lt;/a&gt;, but the resources are limited
and I&#39;d rather host my own, learn a bunch in the process, and let these be used by those who don&#39;t have another option,
those who don&#39;t have the time to do what I&#39;ve done here, and those who are otherwise doing better things for the world
than publishing this blog.&lt;/p&gt;
&lt;p&gt;Third, you can run self-hosted Forgejo Runners which are not publicly accessible/don&#39;t have a public/static IP address.
This means it&#39;s much more convenient to do from a home lab scenario.&lt;/p&gt;
&lt;p&gt;Fourth, I found a &lt;a href=&quot;https://fluids.rocks/posts/forgejo-self-hosted-runner/#proxmox-helper-scripts&quot;&gt;Proxmox helper script&lt;/a&gt; which enabled me to get up and running quickly (and subsequently enter a cycle of
breaking things, figuring it out and starting again for a day), which gave me sufficient confidence to try and make it work.
This is the debrief, and a place to add further notes as they arise.&lt;/p&gt;
&lt;h1 id=&quot;proxmox&quot;&gt;Proxmox&lt;a class=&quot;zola-anchor&quot; href=&quot;#proxmox&quot; aria-label=&quot;Anchor link for: proxmox&quot; style=&quot;visibility: hidden;&quot;&gt;&lt;/a&gt;
&lt;/h1&gt;
&lt;p&gt;I run all of my self-hosted services in &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://www.proxmox.com/en/products/proxmox-virtual-environment/overview&quot;&gt;Proxmox VE&lt;/a&gt;,
an open-source virtualization platform with a web-based GUI which you can run for free. I use this to enable one machine to serve
many purposes flexibly - and I run it from a desktop-format server sitting in my office, making good use of RAM purchased
before &lt;a href=&quot;/ai&quot;&gt;/ai&lt;/a&gt; ruined the personal computing market.&lt;/p&gt;
&lt;p&gt;Some of the information below is specific to using Proxmox, but beyond setting up a container to run things in,
a lot of it will be transferrable (e.g. if you set this up in a VM, on another virtualization host, or set up the runner
to run directly on a host like an old computer you have sitting around which you don&#39;t necessarily want to run anything else on).&lt;/p&gt;
&lt;p&gt;You can create runners which accept workflows for different scopes (all repositories - &lt;em&gt;at least of a Forgejo instance you run yourself&lt;/em&gt;,
an organization, all repositories of a your user, or a specific repository), and this comes into play when the runner is registered
with Codeberg (or an other Forgejo instance).&lt;/p&gt;
&lt;h2 id=&quot;proxmox-helper-scripts&quot;&gt;Proxmox Helper Scripts&lt;a class=&quot;zola-anchor&quot; href=&quot;#proxmox-helper-scripts&quot; aria-label=&quot;Anchor link for: proxmox-helper-scripts&quot; style=&quot;visibility: hidden;&quot;&gt;&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;One useful aspect of running services on Proxmox which makes some of it&#39;s technical features a little easier to handle is
the &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://community-scripts.org/&quot;&gt;Proxmox Community Helper Scripts page&lt;/a&gt; which provides pre-bakend solutions for getting
common (and some less common) services up and running under Proxmox. It doesn&#39;t have everything, and it&#39;s worth checking what
the setup scripts are actually doing, but generally this is a good way to try setting something up, and judge whether you want
to put effort into doing it properly/maintaining it.&lt;/p&gt;
&lt;p&gt;As of March 2026, there&#39;s &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://community-scripts.org/scripts/forgejo-runner&quot;&gt;a Proxmox Helper Script for Forgejo Runner&lt;/a&gt;, but
it&#39;s noted as being &#39;under development. I have successfully used this to set up a runner, but will also go through the detail
of setting it up in a LXC template below (this uses Debian but it could easily be adapted to similar distributions).&lt;/p&gt;
&lt;h1 id=&quot;setting-up-a-forgejo-runner-in-a-debian-lxc&quot;&gt;Setting Up a Forgejo Runner in a Debian LXC&lt;a class=&quot;zola-anchor&quot; href=&quot;#setting-up-a-forgejo-runner-in-a-debian-lxc&quot; aria-label=&quot;Anchor link for: setting-up-a-forgejo-runner-in-a-debian-lxc&quot; style=&quot;visibility: hidden;&quot;&gt;&lt;/a&gt;
&lt;/h1&gt;
&lt;p&gt;In Proxmox, I typically use &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://linuxcontainers.org/lxc/introduction/&quot;&gt;LXC&#39;s (Linux containers)&lt;/a&gt; to run services in.
These provide a simple layer of isolation, but enable flexible usage of resources on my server as
i) the kernel is shared and the templates are small, ii) resources allocated to the LXC aren&#39;t reserved
specifically but instead provide more of a cap on usage (as opposed to fully fledged virtual machines).&lt;/p&gt;
&lt;h2 id=&quot;creating-a-debian-lxc&quot;&gt;Creating a Debian LXC&lt;a class=&quot;zola-anchor&quot; href=&quot;#creating-a-debian-lxc&quot; aria-label=&quot;Anchor link for: creating-a-debian-lxc&quot; style=&quot;visibility: hidden;&quot;&gt;&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;In Proxmox, creating an LXC from a template involves:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Selecting your Proxmox node and clicking &#39;Create CT&#39; is a blue button on the top right, or you can right-click on your node and select &#39;Create CT&#39;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Selecting a hostname (what your runner will be called, locally and in Codeberg), and adding a password (containers are by-default password protected in Proxmox)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Selecting your template (different templates can be stored on different storage volumes).&lt;/p&gt;
&lt;p&gt;If the options you see here don&#39;t cover Debian (or whatever else you want to use), you&#39;ll need to browse to that storage volume in your Proxmox node, select &#39;CT Templates&#39; from the menu, and browse &#39;Templates&#39; for what you&#39;re after (this is a Proxmox-maintained list of templates). If you&#39;re after something not in this list, you can also manually upload your own from an external source.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Configuring template resources and options&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;For this exercise, I used the &lt;code&gt;debian-13&lt;/code&gt; template, with 4 cores and 8GB RAM. Make sure you configure the network to use DHCP/SLAAC unless you&#39;re happy to configure static IPs within your local network for this container. Don&#39;t run the container after creating it, instead modify the options to turn on &lt;code&gt;nesting&lt;/code&gt; and &lt;code&gt;keyctl&lt;/code&gt; (will
be needed to run the Docker engine).&lt;/p&gt;
&lt;p&gt;After you have an LXC, you&#39;ll want to open a console, and we&#39;ll get started.&lt;/p&gt;
&lt;h2 id=&quot;initial-setup&quot;&gt;Initial Setup&lt;a class=&quot;zola-anchor&quot; href=&quot;#initial-setup&quot; aria-label=&quot;Anchor link for: initial-setup&quot; style=&quot;visibility: hidden;&quot;&gt;&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;The first step in this is to quickly update your container, and add some basic dependencies.&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;apt&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; update&lt;/span&gt;&lt;span&gt; &amp;amp;&amp;amp;&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; apt&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; upgrade&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;y&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;apt&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; install&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; curl&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; jq&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; gpg&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; ca-certificates&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote class=&quot;markdown-alert-important&quot;&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; I don&#39;t use &lt;code&gt;sudo&lt;/code&gt; here as I&#39;m running as &lt;code&gt;root&lt;/code&gt; for the majority of important tasks within the container,
but on your device or in your container you might need it.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;install-docker&quot;&gt;Install Docker&lt;a class=&quot;zola-anchor&quot; href=&quot;#install-docker&quot; aria-label=&quot;Anchor link for: install-docker&quot; style=&quot;visibility: hidden;&quot;&gt;&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;The next step in the workflow is to install the Docker engine. We&#39;ll follow the &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://docs.docker.com/engine/install/debian/#install-using-the-repository&quot;&gt;Docker instructions on doing
this in a Debian-based system using &lt;code&gt;apt&lt;/code&gt;&lt;/a&gt;
but I&#39;ve added the (March 2026) commands for doing this below.&lt;/p&gt;
&lt;p&gt;The first step is to remove any existing Docker components:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;apt&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; remove&lt;/span&gt;&lt;span&gt; $(&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;dpkg&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;-get-selections&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; docker.io&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; docker-compose&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; docker-doc&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; podman-docker&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; containerd&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; runc&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; |&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; cut&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;f1&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;After that we add Docker&#39;s GPG key:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;install&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;m&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; 0755&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;d&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; /etc/apt/keyrings&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;curl&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;fsSL&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; https://download.docker.com/linux/debian/gpg&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;o&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; /etc/apt/keyrings/docker.asc&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;chmod&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; a+r&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; /etc/apt/keyrings/docker.asc&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And from here modify the &lt;code&gt;apt&lt;/code&gt; sources to add the Docker repository:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;tee&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; /etc/apt/sources.list.d/docker.sources&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; &amp;lt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;EOF&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;Types: deb&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;URIs: https://download.docker.com/linux/debian&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;Suites: &lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;$(&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; /etc/os-release&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;amp;&amp;amp;&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; echo&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span&gt;$&lt;/span&gt;&lt;span&gt;VERSION_CODENAME&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;Components: stable&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;Architectures: &lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;$(&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;dpkg&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;-print-architecture&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;Signed-By: /etc/apt/keyrings/docker.asc&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;EOF&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now we can install the Docker packages:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;apt&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; update&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;apt&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; install&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; docker-ce&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; docker-ce-cli&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; containerd.io&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; docker-buildx-plugin&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; docker-compose-plugin&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And check the status of Docker with (&lt;code&gt;Ctrl+C&lt;/code&gt; /&lt;code&gt;:q&lt;/code&gt; to exit):&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;systemctl&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; status&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; docker&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;h2 id=&quot;forgejo-runner-binary&quot;&gt;Forgejo Runner Binary&lt;a class=&quot;zola-anchor&quot; href=&quot;#forgejo-runner-binary&quot; aria-label=&quot;Anchor link for: forgejo-runner-binary&quot; style=&quot;visibility: hidden;&quot;&gt;&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;We&#39;ll use a &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://forgejo.org/docs/latest/admin/actions/runner-installation/#binary-installation&quot;&gt;binary installation for the Forgejo Runner&lt;/a&gt;
in our container; I&#39;ve added the (March 2026) commands for this below.&lt;/p&gt;
&lt;p&gt;The first part of this is just selecting the right binary for your system:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;export ARCH=$(uname -m | sed &amp;#39;s/x86_64/amd64/;s/aarch64/arm64/&amp;#39;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;export RUNNER_VERSION=$(curl -X &amp;#39;GET&amp;#39; https://data.forgejo.org/api/v1/repos/forgejo/runner/releases/latest | jq .name -r | cut -c 2-)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;export FORGEJO_URL=&amp;quot;https://code.forgejo.org/forgejo/runner/releases/download/v${RUNNER_VERSION}/forgejo-runner-${RUNNER_VERSION}-linux-${ARCH}&amp;quot;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We can then pull the runner binary from this URL, make it executable, and verify the GPG key:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;wget&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;O&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; forgejo-runner&lt;/span&gt;&lt;span&gt; $&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;FORGEJO_URL&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; |&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; curl&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;o&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; forgejo-runner&lt;/span&gt;&lt;span&gt; $&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;FORGEJO_URL&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;chmod&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; +x&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; forgejo-runner&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;wget&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;O&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; forgejo-runner.asc&lt;/span&gt;&lt;span&gt; $&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;FORGEJO_URL&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;.asc&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; |&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; curl&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;o&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; forgejo-runner.asc&lt;/span&gt;&lt;span&gt; $&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;FORGEJO_URL&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;.asc&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;gpg&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;-keyserver&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; hkps://keys.openpgp.org&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;-recv&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; EB114F5E6C0DC2BCDD183550A4B61A2DC5923710&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;gpg&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;-verify&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; forgejo-runner.asc&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; forgejo-runner&lt;/span&gt;&lt;span&gt; &amp;amp;&amp;amp;&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; echo&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;✓ Verified&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; |&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; echo&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;✗ Failed&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;From here, we copy the binary into &lt;code&gt;/usr/local/bin&lt;/code&gt; (we&#39;ll use this later).&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;cp&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; forgejo-runner&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; /usr/local/bin/forgejo-runner&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The Forgejo Runner installation guide also suggests creating a user for the runner;
as we&#39;re using Docker they&#39;ll need to be added to the &lt;code&gt;docker&lt;/code&gt; group too:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;useradd&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;-create-home&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; runner&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;usermod&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;aG&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; docker&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; runner&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;h2 id=&quot;register-the-runner&quot;&gt;Register the Runner&lt;a class=&quot;zola-anchor&quot; href=&quot;#register-the-runner&quot; aria-label=&quot;Anchor link for: register-the-runner&quot; style=&quot;visibility: hidden;&quot;&gt;&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;Now we have a runner, we need to &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://forgejo.org/docs/latest/admin/actions/runner-installation/#standard-registration&quot;&gt;register it with Codeberg&lt;/a&gt;
so we can use it in Codeberg Actions. The Forgejo Runner installation instructions
suggest doing this with the &lt;code&gt;runner&lt;/code&gt; user we created above, so we&#39;ll switch to
that user and switch into their home directory.&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;su&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; runner&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;cd&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; ~&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;At this point, if you want to create a configuration file, it&#39;s a convenient time.
You can create it with the &lt;code&gt;forgejo-runner&lt;/code&gt; binary and edit it with &lt;code&gt;nano&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;forgejo-runner&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; generate-config&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; &amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; config.yml&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;nano&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; config.yml&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The two things I edited here (as of this post) were the &lt;code&gt;runner.labels&lt;/code&gt; (I&#39;ll use
&lt;code&gt;&quot;rust-latest:docker://rust:latest&quot;&lt;/code&gt; for Zola, and I&#39;m sure I can find uses for the others)
and the &lt;code&gt;container.docker_host&lt;/code&gt;. The labels here are important in that
they indicate what can be run on the runner. Note that the labels have both a friendly name (e.g. &lt;code&gt;ubuntu-latest&lt;/code&gt; together with the identifier for the image to use, in this instance &lt;code&gt;docker://node:20-bookworm&lt;/code&gt;).&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;yaml&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;    l&lt;/span&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;abels&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;    &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;ubuntu-latest:docker://node:20-bookworm&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;    &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;ubuntu-22.04:docker://node:20-bookworm&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;    &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;python-latest:docker://python:latest&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;    &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;rust-latest:docker://rust:latest&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;          ]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;    ...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;    d&lt;/span&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;ocker_host&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;automount&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote class=&quot;markdown-alert-note&quot;&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; You can use a runner which isn&#39;t Docker-based, e.g. using Podman or LXC images; these involve a few different/extra steps &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://forgejo.org/docs/latest/admin/actions/runner-installation/#setting-up-the-container-environment&quot;&gt;see the installation guide for more&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;At this point, we also need to get a registration token from Codeberg (or
other Forgejo instance) in order to connect to it. Make sure you&#39;ve
added the &#39;Actions&#39; unit (at at &lt;code&gt;https://codeberg.org/{username}/{repo}/settings/units&lt;/code&gt;,
if you don&#39;t have it), and after that you should be able to
find the &#39;Create new runner&#39; button at &lt;code&gt;https://codeberg.org/{username}/{repo}/settings/actions/runners&lt;/code&gt;
which will create a new runner &lt;em&gt;specifically scoped for this repository&lt;/em&gt; (you can get a registration token scoped to your user, but you&#39;ll need to get it from &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://codeberg.org/user/settings/actions/runners&quot;&gt;&lt;code&gt;https://codeberg.org/user/settings/actions/runners&lt;/code&gt;&lt;/a&gt;).
Clicking this button will give you a registration token to copy, which
you&#39;ll need below - alongside the instance URL (i.e. &lt;code&gt;https://codeberg.org&lt;/code&gt;
for me).&lt;/p&gt;
&lt;p&gt;Then we can register the runner using this configuration, this will give you some prompts to reply to:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;forgejo-runner&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; register&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;-config&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; config.yml&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For my setup, the prompt replies to this were (kept the hostname, already had the labels in config):&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;&amp;gt; https://codeberg.org&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;&amp;gt; {registration token}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Almost there!&lt;/p&gt;
&lt;h2 id=&quot;start-the-runner-and-keep-it-running&quot;&gt;Start the Runner, and Keep it Running&lt;a class=&quot;zola-anchor&quot; href=&quot;#start-the-runner-and-keep-it-running&quot; aria-label=&quot;Anchor link for: start-the-runner-and-keep-it-running&quot; style=&quot;visibility: hidden;&quot;&gt;&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;You can can run the daemon with your specific configuration as follows:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;forgejo-runner&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; daemon&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;-config&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; config.yml&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;However, to have this start automatically with your container, you can &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://forgejo.org/docs/latest/admin/actions/runner-installation/#running-as-a-systemd-service&quot;&gt;add it as a
systemd service&lt;/a&gt;. You&#39;ll need do do this as &lt;code&gt;root&lt;/code&gt; (or otherwise give &lt;code&gt;runner&lt;/code&gt; sudo access). Here we change back to the &lt;code&gt;root&lt;/code&gt; user, and open a new file to edit with &lt;code&gt;nano&lt;/code&gt;;
the Forgejo Runner installation instructions point to adding/copy-pasting in &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://code.forgejo.org/forgejo/runner/src/branch/main/contrib/forgejo-runner.service&quot;&gt;this file&lt;/a&gt; - note that I needed to edit line 7 which refers to the configuration (replace &lt;code&gt;/home/runner/runner-config.yml&lt;/code&gt; with &lt;code&gt;/home/runner/config.yml&lt;/code&gt; which we created above):&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;su&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; root&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;nano&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; /etc/systemd/system/forgejo-runner.service&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;My file, after the edit to keep it in line with the file naming above:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;[Unit]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;Description=Forgejo Runner&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;Documentation=https://forgejo.org/docs/latest/admin/actions/&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;After=docker.service&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;[Service]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;ExecStart=/usr/local/bin/forgejo-runner daemon -c /home/runner/config.yml # I needed to edit this line&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;ExecReload=/bin/kill -s HUP $MAINPID&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;# This user and working directory must already exist&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;User=runner&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;WorkingDirectory=/home/runner&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;Restart=on-failure&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;TimeoutSec=0&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;RestartSec=10&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;[Install]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;WantedBy=multi-user.target&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;From here we can reload the daemon, start the service to check it&#39;s working, and from there enable the systemd service
for the Forgejo Runner.&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;systemctl&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; daemon-reload&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;systemctl&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; start&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; forgejo-runner.service&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;systemctl&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; enable&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; forgejo-runner.service&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;h1 id=&quot;back-to-codeberg&quot;&gt;Back to Codeberg&lt;a class=&quot;zola-anchor&quot; href=&quot;#back-to-codeberg&quot; aria-label=&quot;Anchor link for: back-to-codeberg&quot; style=&quot;visibility: hidden;&quot;&gt;&lt;/a&gt;
&lt;/h1&gt;
&lt;p&gt;In Codeberg, you should see your new runner in the list
at &lt;code&gt;https://codeberg.org/{username}/{repository}/settings/actions/runners&lt;/code&gt;- and with luck it&#39;ll have a green &#39;idle&#39; status alongside the
name you gave it (the hostname if you left as the default, otherwise the name you configured for it in &lt;a href=&quot;https://fluids.rocks/posts/forgejo-self-hosted-runner/#register-the-runner&quot;&gt;the section above&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;Now you can create actions which are triggered by Codeberg but run on your
self-hosted Forgejo Runner. In your repo, you can create YAML file defining your workflow (e.g. &lt;code&gt;.forgejo/workflows/build.yaml&lt;/code&gt;), and indicate what you want it to run on, e.g.:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;yaml&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;j&lt;/span&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;obs&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;  p&lt;/span&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;ublish&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;    r&lt;/span&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;uns-on&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; r&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;ust-latest&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;</description>
      </item>
      <item>
          <title>How This Site Was Built (Again)</title>
          <pubDate>Wed, 25 Mar 2026 00:00:00 +0000</pubDate>
          <author>Morgan Williams</author>
          <link>https://fluids.rocks/posts/how-this-site-was-built-again/</link>
          <guid>https://fluids.rocks/posts/how-this-site-was-built-again/</guid>
          <description xml:base="https://fluids.rocks/posts/how-this-site-was-built-again/">&lt;p&gt;I rebuilt the same site, ten years later!&lt;/p&gt;
&lt;p&gt;I&#39;ll save on some of the details for now, but there are some notes below on what&#39;s changed.&lt;/p&gt;
&lt;h2 id=&quot;moving-to-codeberg&quot;&gt;Moving to Codeberg&lt;a class=&quot;zola-anchor&quot; href=&quot;#moving-to-codeberg&quot; aria-label=&quot;Anchor link for: moving-to-codeberg&quot; style=&quot;visibility: hidden;&quot;&gt;&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;The world has changed a lot in 10 years, and Microsoft acquired GitHub since this was initially built.
I&#39;m looking to move back into more open spaces, and &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://codeberg.org/&quot;&gt;Codeberg&lt;/a&gt; seems to suit.&lt;/p&gt;
&lt;h2 id=&quot;codeberg-pages-and-zola&quot;&gt;Codeberg Pages and Zola&lt;a class=&quot;zola-anchor&quot; href=&quot;#codeberg-pages-and-zola&quot; aria-label=&quot;Anchor link for: codeberg-pages-and-zola&quot; style=&quot;visibility: hidden;&quot;&gt;&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;Instead of GitHub pages and Jekyll, I&#39;m now using Codeberg Pages and Zola - &lt;a href=&quot;https://fluids.rocks/posts/codeberg-pages-zola/&quot;&gt;you can see a post on that here&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;workflows-self-hosted-forgejo-runners&quot;&gt;Workflows: Self-Hosted Forgejo Runners&lt;a class=&quot;zola-anchor&quot; href=&quot;#workflows-self-hosted-forgejo-runners&quot; aria-label=&quot;Anchor link for: workflows-self-hosted-forgejo-runners&quot; style=&quot;visibility: hidden;&quot;&gt;&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;I&#39;ve set up self-hosted runners for my build workflows, and &lt;a href=&quot;https://fluids.rocks/posts/forgejo-self-hosted-runner/&quot;&gt;put together a basic guide for it&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;building-for-humans&quot;&gt;Building for Humans&lt;a class=&quot;zola-anchor&quot; href=&quot;#building-for-humans&quot; aria-label=&quot;Anchor link for: building-for-humans&quot; style=&quot;visibility: hidden;&quot;&gt;&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;A notable addition relative to the old site is that one implements the &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://codeberg.org/robida/human.json&quot;&gt;&lt;code&gt;human.json&lt;/code&gt; protocol&lt;/a&gt; (you can find pages of other humans &lt;a href=&quot;https://fluids.rocks/posts/how-this-site-was-built-again/human.json&quot;&gt;&lt;code&gt;here&lt;/code&gt;&lt;/a&gt;). I&#39;ve also added a page detailing how I don&#39;t use generative AI here (or elsewhere): &lt;a href=&quot;/ai/&quot;&gt;/ai&lt;/a&gt;.&lt;/p&gt;
</description>
      </item>
      <item>
          <title>PhD Research</title>
          <pubDate>Fri, 01 Mar 2019 00:00:00 +0000</pubDate>
          <author>Morgan Williams</author>
          <link>https://fluids.rocks/posts/phd/</link>
          <guid>https://fluids.rocks/posts/phd/</guid>
          <description xml:base="https://fluids.rocks/posts/phd/">&lt;p&gt;During my PhD at the Australian National University, I investigated how
oceanic rocks change when interacting with fluids (water, mostly) throughout the
lifetime of the oceanic plate, from its inception near mid-oceanic ridges to its destruction in
subduction zones. The chemical changes (especially hydration/dehydration) that happen
in these environments are the key to global chemical recycling and generation of
new continental material, and ultimately the plate tectonic system. Exploring
the what, when, how and why of these processes is central to understanding the
evolution of our planet.&lt;/p&gt;
&lt;p&gt;More specifically, I&#39;m was working on &#39;fingerprinting&#39; different processes and fluid
compositions that rocks encounter, and provide a better framework to interpret
rocks that aren&#39;t as well preserved. To get the chemical &#39;fingerprints&#39;, I sampled small
parts of minerals (using lasers and ion beams) and measure the composition of the material.
My core projects utilized the ratios of the light stable isotopes of
boron (&lt;sup&gt;10&lt;/sup&gt;B, &lt;sup&gt;11&lt;/sup&gt;B) and oxygen (&lt;sup&gt;16&lt;/sup&gt;O, &lt;sup&gt;18&lt;/sup&gt;O)
within hydrous minerals. The relative isotope ratios in fluids and minerals change
in response to both environmental variables (temperature, pH) and the initial fluid
and rock isotope compositions; when minerals (or zones within minerals) are preserved,
these isotope systems can be used as efficient tracers/proxies for events that happened
millions of years ago.&lt;/p&gt;
&lt;p&gt;In most metamorphic systems, a number of common rock-forming minerals grow in the presence of fluids.
Growth can occur over relatively short time periods, and successive periods of mineral
growth can result in zoned patterns (like tree rings). These preserving a time-sequence
which allows the extraction of information on how systems evolve. By combining a
suite of chemical systems, including different element and isotope systems,
we can accrue further information and better constrain how (and sometimes even why)
these geological systems change.&lt;/p&gt;
&lt;h2 id=&quot;investigating-geochemical-records-of-fluids-in-the-oceanic-crust&quot;&gt;Investigating Geochemical Records of Fluids in the Oceanic Crust&lt;a class=&quot;zola-anchor&quot; href=&quot;#investigating-geochemical-records-of-fluids-in-the-oceanic-crust&quot; aria-label=&quot;Anchor link for: investigating-geochemical-records-of-fluids-in-the-oceanic-crust&quot; style=&quot;visibility: hidden;&quot;&gt;&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;My research focused on the use of in-situ analytical methods (principally SIMS and LA-ICP-MS) to investigate the geochemical systematics of fluid-rock interaction from seafloor hydrothermal systems, through subduction and during ultra-high-pressure metamorphism.&lt;/p&gt;
&lt;h2 id=&quot;lago-di-cignana-nw-italy&quot;&gt;Lago di Cignana, NW Italy&lt;a class=&quot;zola-anchor&quot; href=&quot;#lago-di-cignana-nw-italy&quot; aria-label=&quot;Anchor link for: lago-di-cignana-nw-italy&quot; style=&quot;visibility: hidden;&quot;&gt;&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;To better constrain processes active in the uppermost subducting slab, I&#39;m investigatedthe boron and oxygen isotope systematics in metasediments, metabasalts and serpentinties from the Lago di Cignana Unit and underlying Zermatt-Saas Ophiolite (NW Italian Alps). Here we hoped to constrain the timing and extent of fluid infiltration within the unit, and better understand the fluid-mediated interactions between common lithologies in the slab. For this purpose, I developed standard operating routines for boron isotope analysis by SHRIMP, with the assistance of the technical staff here at RSES.&lt;/p&gt;
&lt;h2 id=&quot;the-atlantis-massif-30degn-mid-atlantic-ridge&quot;&gt;The Atlantis Massif, 30°N, Mid Atlantic Ridge&lt;a class=&quot;zola-anchor&quot; href=&quot;#the-atlantis-massif-30degn-mid-atlantic-ridge&quot; aria-label=&quot;Anchor link for: the-atlantis-massif-30degn-mid-atlantic-ridge&quot; style=&quot;visibility: hidden;&quot;&gt;&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;As part of the International Ocean Discovery Program (IODP) Expedition 357 - Atlantis Massif: Serpentinization and Life and in collaboration with Daniela Rubatto and Mark Kendrick, I&#39;m also worked on serpentinized ultramafic rocks within an active seafloor hydrothermal system adjacent to the Mid-Altantic Ridge. We constrained previously active fluid-rock interaction processes associated with the oceanic detachment fault using halogen, noble gas and in-situ oxygen isotope analysis.&lt;/p&gt;
</description>
      </item>
      <item>
          <title>pyrolite</title>
          <pubDate>Fri, 01 Mar 2019 00:00:00 +0000</pubDate>
          <author>Morgan Williams</author>
          <link>https://fluids.rocks/posts/pyrolite/</link>
          <guid>https://fluids.rocks/posts/pyrolite/</guid>
          <description xml:base="https://fluids.rocks/posts/pyrolite/">&lt;img src=&quot;https://raw.githubusercontent.com/morganjwilliams/pyrolite/develop/docs/source/_static/icon.jpg&quot; alt=&quot;pyrolite Logo&quot; height=&quot;200px&quot; width=&quot;200px&quot; align=&quot;right&quot;&gt;
&lt;p&gt;I&#39;m currently building an open-source python package for working with geochemical data,
&lt;code&gt;pyrolite&lt;/code&gt; (&lt;code&gt;pip install pyrolite&lt;/code&gt;). It&#39;s currently in &lt;code&gt;beta&lt;/code&gt;, and you can find the
source on &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://github.com/morganjwilliams/pyrolite&quot;&gt;GitHub&lt;/a&gt;, and
documentation on &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://pyrolite.readthedocs.io/&quot;&gt;Read the Docs&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;For a quick overview, see the &lt;a href=&quot;/publications/WilliamsDHS2019Poster.pdf&quot;&gt;poster&lt;/a&gt;
I put together for the Dorothy Hill Women in Earth Sciences Symposium.&lt;/p&gt;
</description>
      </item>
      <item>
          <title>Serpentine Nights</title>
          <pubDate>Thu, 16 Mar 2017 00:00:00 +0000</pubDate>
          <author>Morgan Williams</author>
          <link>https://fluids.rocks/posts/serpentinenights/</link>
          <guid>https://fluids.rocks/posts/serpentinenights/</guid>
          <description xml:base="https://fluids.rocks/posts/serpentinenights/">&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt; This post is an excerpt from an article I co-wrote with Joëlle Ducommun for the &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;http://www.eag.eu.com/&quot;&gt;European Association of Geochemistry (EAG)&lt;/a&gt;, which provided financial support enabling us to attend. The original article can be found &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;http://blog.eag.eu.com/general/serpentine-days-2016/&quot;&gt;here&lt;/a&gt;. We thank the EAG for the financial support which enabled us to attend Serpentine Days, and also the organisers of the workshop for their efforts in putting together a very successful and thoroughly enjoyable meeting.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;figure&gt;
&lt;img src=&quot;/img/SD_LherzGroupPhoto.jpg&quot; height=&quot;400&quot;&gt;&lt;figcaption&gt;&lt;p&gt;Group photo during the Serpentine Days field trip to the Lherz Massif, Southern France (Image Credit: Joëlle Ducommun).&lt;/p&gt;&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;This past September fellow ANU PhD student Joëlle and I had the privilege to attend &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;http://serpentines2016.gm.univ-montp2.fr/&quot;&gt;Serpentine Days&lt;/a&gt;, a biannual workshop on serpentinite and serpentinization processes which is distinctly multidisciplinary. The workshop covers much of the active research on serpentinites, including everything from microbiological communities in near surface environments, through serpentinite dehydration at great depth within subduction zones and all the way out to investigations of the surface of mars.  Serpentinization is one of the key processes controlling a number of global geochemical cycles, being the carrier of the bulk of bound water into subduction zones and with further relevance for carbon cycling. More recently, the discovery of microbial communities associated with serpentinite-hosted hydrothermal systems has spurred research into these environments as life-supporting habitats.&lt;/p&gt;
&lt;p&gt;This year the Serpentine Days workshop was held at Sète. Located approximately half an hour south of Montpellier, France and is situated on the Mediterranean Sea, the town is famous for its seafood and as a seaside destination for all ages. Despite the lack of any observable serpentinite, it made for an idyllic location to host a workshop. Our venue and accommodation for the workshop was Le Lazaret, a holiday village just off the beach. Each day of the workshop presented opportunities to network and discuss with other researchers; for a few days we were able to live and breathe science, all the while enjoying the delights of southern France. A field trip to the Pyrenees followed the completion of the workshop, visiting the hallowed Lherz peridotite (of lherzolite fame) and nearby serpentinite locality near Bestiac to investigate records of mantle exhumation during the Cretaceous rifting event in the North Pyrenean Zone.&lt;/p&gt;
&lt;figure&gt;
&lt;img src=&quot;/img/SN_Pic2.jpg&quot; alt=&quot;Yves Lagabrielle discussing sedimentary breccias overlying the ultramafic bodies at Lherz&quot; height=&quot;200&quot;&gt;&lt;/figure&gt;
&lt;figure&gt;
&lt;img src=&quot;/img/SN_Pic3.jpg&quot; alt=&quot;Emily, Suzanne and Joelle collecting samples in the field&quot; height=&quot;200&quot;&gt;&lt;figcaption&gt;&lt;p&gt;A few days in the French Pyrenees. a) Introduction to the field trip to the Lherzolite body led by Yves Lagabrielle, here pointing at the sedimentary breccias closely  associated with the Ultramafic bodies in the Etang de Lherz area; b) collecting samples in the field, from left to right: Emily H.G. Cooperdock (University of Texas, Austin), Suzanne Picazo (University of Lausanne) and Joëlle Ducommun (Australian National University, Canberra). Image Credit: Joëlle Ducommun.&lt;/p&gt;&lt;/figcaption&gt;&lt;/figure&gt;
&lt;h2 id=&quot;a-few-notes-of-my-own&quot;&gt;A Few Notes of My Own&lt;a class=&quot;zola-anchor&quot; href=&quot;#a-few-notes-of-my-own&quot; aria-label=&quot;Anchor link for: a-few-notes-of-my-own&quot; style=&quot;visibility: hidden;&quot;&gt;&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;During the workshop I presented a brief overview of my current research on serpentinites from the Atlantis Massif (which I’m completing as a science party member of IODP Expedition 357), and in particular how I’m investigating fluid-mobile/volatile elements and using in-situ techniques to provide greater constraints on the serpentinization process and evolution of the Massif. The novel aspects I discussed during this talk included microscale oxygen isotopic heterogeneities recording multistage serpentinization at generally elevated temperatures, and also atypically low serpentinite noble gas abundances.  Some of the many talks I found intriguing covered the potential incorporation of sulfate within the serpentine structure (Baptiste Debret), the formation of organic films/gels at mineral interfaces (Bénédicte Menez), and unravelling alteration histories in ultramafic rocks using magnetite U-Th/He dating (Emily Cooperdock). In addition to hearing talks directly relevant to my own work, I was able to satisfy my inner child and interact with scientists studying extreme environments both here on Earth and on Mars. This led to some great conversations, particularly regarding the formation and evolution of early life. I was also grateful to have the opportunity to catch up with fellow Expedition 357 scientists and discuss research to date, and potential for collaboration both within and beyond the science party.&lt;/p&gt;
&lt;figure&gt;
&lt;img src=&quot;/img/SN_Pic1.jpg&quot; alt=&quot;Line of attendees taking in the views of the Pyrenees&quot; height=&quot;200&quot;&gt;&lt;figcaption&gt;&lt;p&gt;Field trip attendees take in views of the French Pyrenees on the way to the next field stop at Bestiac.&lt;/p&gt;&lt;/figcaption&gt;&lt;/figure&gt;
&lt;h2 id=&quot;a-few-thoughts-from-joelle&quot;&gt;A Few Thoughts from Joëlle&lt;a class=&quot;zola-anchor&quot; href=&quot;#a-few-thoughts-from-joelle&quot; aria-label=&quot;Anchor link for: a-few-thoughts-from-joelle&quot; style=&quot;visibility: hidden;&quot;&gt;&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;The first international workshop I was privileged to attend was one of the most memorable experience I have had in my research journey so far. It was exactly what I needed at the time. Of course, I could start by mentioning that it was slightly intimidating to stand up in front of an expert audience, not feeling very much like a serpentinite expert myself, to present a research project on the quest of a noble gases and halogens surface signature from the Archaean. At the time, I had been working on my PhD project at the Australian National University for just over a year, and was starting to dig deeper into my main project focusing on the fate of volatiles during alteration of the oceanic crust. The workshop pushed me to take a step back to these remarkable Archaean samples that I had the opportunity to work with earlier on in my PhD. I am still astonished by the fact that these serpentinites, after surviving a 3.7 Ga year long history, sometimes still preserving their original textures, were eventually brought back all the way from Greenland to end up on my desk, offering an invaluable window into the early earth.&lt;/p&gt;
&lt;p&gt;The workshop gave me the opportunity to gain experience in presenting my research, but mostly it helped me recover the enthusiasm and faith in science. As for many of my fellow PhD friends, I soon realized that during the ebb and flow of PhD life, it appears to be common to go through a dark tunnel. Doubts and questions take hold of the mind; we start to be sceptical about the journey we have embarked on and the relevance of the small details we are trying to understand. But being able to meet peers and colleagues, exchange ideas, friendships, good times and mostly lots of laughter, made me realized that science is very attractive when it is shared, that communicating and exchanging is what makes science, and that put together, the small details we are all tackling are part of a much broader and comprehensive story. At times, I admit, I have felt caught up in a system that lost its soul, a system that pushes students and academics to focus on personal objectives, to advance their careers, a system I had much trouble to identify with. The workshop was a perfect reminder that behind the aggressive and competitive academic life, there is still room for a science community, room to build something greater that goes far beyond each individual personal goal. I am very thankful to have been given the opportunity to meet whole hearted scientists passionate about their work but mostly about passing on their knowledge and their experiences, and I am very much looking forward to crossing their paths again.&lt;/p&gt;
</description>
      </item>
      <item>
          <title>How This Site Was Built (the first time)</title>
          <pubDate>Sat, 16 Apr 2016 00:00:00 +0000</pubDate>
          <author>Morgan Williams</author>
          <link>https://fluids.rocks/posts/how-this-site-was-built/</link>
          <guid>https://fluids.rocks/posts/how-this-site-was-built/</guid>
          <description xml:base="https://fluids.rocks/posts/how-this-site-was-built/">&lt;p&gt;You know that feeling when you find a website that just works... and doesn&#39;t look like it was made in the 1990&#39;s?&lt;/p&gt;
&lt;p&gt;Don&#39;t get me wrong, I&#39;m a 90&#39;s kid through and through, but you have to admit it wasn&#39;t exactly
the height of design, and well-designed easy-to-navigate sites were virtually non-existent.&lt;/p&gt;
&lt;p&gt;In an academic world we tend to ignore these aspects (or leave them to someone else) in lieu of simply presenting the required information - but the look and feel of the site is an
aspect of communication that is too often disregarded. If we want people to read about science, we have to make it as easy as possible.
Despite what you may think, this need not be particularly difficult, nor costly.&lt;/p&gt;
&lt;p&gt;I&#39;ve been curious about how it would be to create a net-worth personal webpage. As I usually do with these things, I gave it a shot. A dash of research, a pinch
of frustration and a touch of tweaking here and there.. et voilà! Along the way I have aggregated a few tips from a variety of sources, which I&#39;ve summarised below.
Consider this a &lt;a href=&quot;../../tags/how-to&quot;&gt;&lt;code&gt;how-to&lt;/code&gt;&lt;/a&gt; for building a simple personal static website on a budget ($0-10/year).&lt;/p&gt;
&lt;h1 id=&quot;how-this-site-runs&quot;&gt;How This Site Runs&lt;a class=&quot;zola-anchor&quot; href=&quot;#how-this-site-runs&quot; aria-label=&quot;Anchor link for: how-this-site-runs&quot; style=&quot;visibility: hidden;&quot;&gt;&lt;/a&gt;
&lt;/h1&gt;
&lt;h2 id=&quot;before-you-start&quot;&gt;Before You Start&lt;a class=&quot;zola-anchor&quot; href=&quot;#before-you-start&quot; aria-label=&quot;Anchor link for: before-you-start&quot; style=&quot;visibility: hidden;&quot;&gt;&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;Before you embark on this quest, I&#39;d recommend getting a nice text editor to create and customize your site&#39;s files offline (I&#39;m currently writing this in &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://atom.io/&quot;&gt;Atom&lt;/a&gt;). Another option I&#39;d recommend
would be &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;http://prose.io&quot;&gt;Prose.io&lt;/a&gt;, which connects directly to GitHub and works quite well for small editing tasks, especially if you&#39;re on the go.&lt;/p&gt;
&lt;p&gt;While it&#39;s not completely necessary for the walk-through below, it would also be a great idea to learn a bit of HTML, CSS, Javascript and &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;http://markdown-guide.readthedocs.io/en/latest/index.html&quot;&gt;Markdown&lt;/a&gt;.
There are some great online courses you could try (I started on &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://www.codecademy.com/learn&quot;&gt;CodeAcademy&lt;/a&gt; and
have also been through a bit of &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://www.freecodecamp.com/&quot;&gt;freeCodeCamp&lt;/a&gt;).&lt;/p&gt;
&lt;h2 id=&quot;hosting-on-github&quot;&gt;Hosting On GitHub&lt;a class=&quot;zola-anchor&quot; href=&quot;#hosting-on-github&quot; aria-label=&quot;Anchor link for: hosting-on-github&quot; style=&quot;visibility: hidden;&quot;&gt;&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;This site is currently hosted free on &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://github.com/&quot;&gt;GitHub&lt;/a&gt;. GitHub is used primarily as a repository for code/software,
and is great for developing projects that are constantly being changed and improved. GitHub builds on the distributed versioning
control system &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://git-scm.com/&quot;&gt;Git&lt;/a&gt; and provides a platform to host your project in a single place, keep track of all the changes
and versions and work both effectively and collaboratively with others. You can sign up for a free account on GitHub,
after which you can start enjoying some of these benefits (perhaps even for your thesis!?). Make sure to check out the tutorial on the website if you&#39;re new to Git.&lt;/p&gt;
&lt;p&gt;If you&#39;re a student or involved in education I&#39;d encourage you to check out the &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://education.github.com/&quot;&gt;GitHub education website&lt;/a&gt;.
You can apply for a micro/personal account on GitHub (normally $7/month), with which you can create private repositories
should you wish to keep the website files out of sight (e.g. draft posts etc). Their &#39;Education Pack&#39; also includes
a bunch of useful tools, and a nifty offer from namecheap for a free &lt;code&gt;.me&lt;/code&gt; top-level domain for a year (here&#39;s your $0 website, folks).&lt;/p&gt;
&lt;p&gt;One way you can use GitHub to host a simple website is via &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://pages.github.com/&quot;&gt;GitHub Pages&lt;/a&gt;, a straightfoward tool to host
and publish sites on GitHub. Once you have the repository set up, you have two choices for creating your site:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;With &lt;strong&gt;GitHub&#39;s Automatic Page Generator&lt;/strong&gt;, you can can create a functional site in minutes.&lt;/li&gt;
&lt;li&gt;You can build your own site &lt;strong&gt;from scratch/from a template&lt;/strong&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;I went for the second option, using a template of a site which I then customized slightly.&lt;/p&gt;
&lt;h2 id=&quot;building-your-site-with-jekyll&quot;&gt;Building Your Site with Jekyll&lt;a class=&quot;zola-anchor&quot; href=&quot;#building-your-site-with-jekyll&quot; aria-label=&quot;Anchor link for: building-your-site-with-jekyll&quot; style=&quot;visibility: hidden;&quot;&gt;&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;One other great feature of GitHub is that it supports and encourages the use of Jekyll. In fact, Github Pages is powered by Jekyll. &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://jekyllrb.com/&quot;&gt;Jekyll&lt;/a&gt; is &quot;a simple, blog aware, static site generator&quot;. It&#39;s most commonly used to build simple personal websites and blogs,
but can be used to manage larger sites to great effect (e.g. &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://training.github.com/&quot;&gt;GitHub Training&lt;/a&gt;). GitHub have also put together a quick overview of &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://help.github.com/articles/using-jekyll-as-a-static-site-generator-with-github-pages/&quot;&gt;how to use Jekyll with GitHub Pages&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I find one of the most useful features to be the use of &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;http://markdown-guide.readthedocs.io/en/latest/index.html&quot;&gt;markdown&lt;/a&gt; to write posts and pages, rather than directly writing HTML.
This saves me quite a bit of time, and simplifies the workflow for building the site. The ability to easily use relative links and liquid templating also makes life a bit easier - but a bit more on that later.&lt;/p&gt;
&lt;h3 id=&quot;installing-jekyll-locally&quot;&gt;Installing Jekyll Locally&lt;a class=&quot;zola-anchor&quot; href=&quot;#installing-jekyll-locally&quot; aria-label=&quot;Anchor link for: installing-jekyll-locally&quot; style=&quot;visibility: hidden;&quot;&gt;&lt;/a&gt;
&lt;/h3&gt;
&lt;p&gt;First thing&#39;s first - you&#39;ll need to install Jekyll and its dependencies. While having a Jekyll-built site on Github is great, occasionally your website builds (which happen automatically after any updates) will fail due to the odd mistype. During development of your site, I&#39;d recommend you download Jekyll and build your sites from the terminal on your local computer. This is particularly important when you&#39;re having issues as you can catch the error messages and troubleshoot problems, and will allow you to work offline in case you live somewhere where internet (a connection, let alone speed) is not always guaranteed.&lt;/p&gt;
&lt;h4 id=&quot;ubuntu-installation&quot;&gt;Ubuntu Installation&lt;a class=&quot;zola-anchor&quot; href=&quot;#ubuntu-installation&quot; aria-label=&quot;Anchor link for: ubuntu-installation&quot; style=&quot;visibility: hidden;&quot;&gt;&lt;/a&gt;
&lt;/h4&gt;
&lt;p&gt;Firstly you&#39;ll want to install &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://www.ruby-lang.org/en/&quot;&gt;Ruby&lt;/a&gt;, and then use &lt;code&gt;gem&lt;/code&gt; to install the ruby gems &lt;code&gt;jekyll&lt;/code&gt; and &lt;code&gt;bundler&lt;/code&gt;. Additionally, &lt;code&gt;github-pages&lt;/code&gt; if you&#39;re likely to host on Github and &lt;code&gt;rouge&lt;/code&gt; if you&#39;re likely to want syntax highlighting as seen below.&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;sudo&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; apt-get&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; update&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;sudo&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; apt-get&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; install&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; ruby&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; ruby-dev&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; make&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; gcc&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;sudo&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; gem&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; install&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; jekyll&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; bundler&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; github-pages&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; rouge&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You should now create a project directory, and find an appropriate template if you choose that avenue: If you use a template and have a &lt;code&gt;Gemfile.lock&lt;/code&gt;, you should be able to &lt;code&gt;bundle install&lt;/code&gt; within your project directory to install required packages as specified by the template. Be aware that if you&#39;re using &lt;code&gt;bundler&lt;/code&gt; you&#39;ll also be required to prefix calls to &lt;code&gt;jekyll&lt;/code&gt; with &lt;code&gt;bundle exec&lt;/code&gt; (e.g. &lt;code&gt;bundle exec jekyll serve&lt;/code&gt;). To update the &lt;code&gt;Gemfile.lock&lt;/code&gt; to check for recently updated dependencies, you can run &lt;code&gt;bundle update&lt;/code&gt;.&lt;/p&gt;
&lt;h4 id=&quot;windows-installation&quot;&gt;Windows Installation&lt;a class=&quot;zola-anchor&quot; href=&quot;#windows-installation&quot; aria-label=&quot;Anchor link for: windows-installation&quot; style=&quot;visibility: hidden;&quot;&gt;&lt;/a&gt;
&lt;/h4&gt;
&lt;p&gt;To install Jekyll on Windows, a relatively straightfoward method has been documented on &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://davidburela.wordpress.com/2015/11/28/easily-install-jekyll-on-windows-with-3-command-prompt-entries-and-chocolatey/&quot;&gt;David Burela&#39;s blog&lt;/a&gt;:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Download &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://chocolatey.org/&quot;&gt;Chocolatey&lt;/a&gt; by entering the following into a command prompt with administrator access: &lt;code&gt;@powershell -NoProfile -ExecutionPolicy Bypass -Command &quot;iex ((new-object net.webclient).DownloadString(&#39;https://chocolatey.org/install.ps1&#39;))&quot; &amp;amp;&amp;amp; SET PATH=%PATH%;%ALLUSERSPROFILE%\chocolatey\bin&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;In a &lt;strong&gt;new&lt;/strong&gt; command prompt with administrator access, Ruby can now be installed using the following: &lt;code&gt;choco install ruby -y&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Likewise, in a &lt;strong&gt;new&lt;/strong&gt; command prompt with administrator access, Jekyll can be installed by entering the following: &lt;code&gt;gem install jekyll&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;If you want to use jekyll plugins you&#39;ll likely need to install them, a few examples are: &lt;code&gt;gem install jekyll-paginate jekyll-sitemap jekyll-gist jekyll-feed&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;: Another way to achieve this is using bundler (&lt;code class=&quot;highlighter-rouge&quot;&gt;gem install bundler&lt;/code&gt;) and a Gemfile (if you&#39;re using a template and it comes with one!). You can &lt;code class=&quot;highlighter-rouge&quot;&gt;bundle install&lt;/code&gt; in the folder containing the Gemfile, which will install the desired packages and dependencies. Note however that this puts you at the mercy of the designer, and may lead to problems if the site is used in a different way than originally intended (e.g. GitHub pages vs jekyll).&lt;/p&gt;
&lt;h3 id=&quot;using-templated-sites&quot;&gt;Using Templated Sites&lt;a class=&quot;zola-anchor&quot; href=&quot;#using-templated-sites&quot; aria-label=&quot;Anchor link for: using-templated-sites&quot; style=&quot;visibility: hidden;&quot;&gt;&lt;/a&gt;
&lt;/h3&gt;
&lt;p&gt;I&#39;ve built this site using a template built by &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://mademistakes.com&quot;&gt;Michael Rose&lt;/a&gt;. The easiest way to use a site template hosted on GitHub is to fork the repository, then rename it to the default &lt;code&gt;.io&lt;/code&gt; site recognised by GitHub pages (&lt;code&gt;USERNAME.github.io&lt;/code&gt;). Alternatively you can download the repository as a &lt;code&gt;.zip&lt;/code&gt; file and work with it offline as described briefly above.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;: You can only have one personal site - adding additional &#39;project pages&#39; is possible, though (using a &lt;code class=&quot;highlighter-rouge&quot;&gt;gh-pages&lt;/code&gt; subfolder).&lt;/p&gt;
&lt;h3 id=&quot;serving-up-your-page&quot;&gt;Serving Up Your Page&lt;a class=&quot;zola-anchor&quot; href=&quot;#serving-up-your-page&quot; aria-label=&quot;Anchor link for: serving-up-your-page&quot; style=&quot;visibility: hidden;&quot;&gt;&lt;/a&gt;
&lt;/h3&gt;
&lt;p&gt;Now that we have Jekyll installed and ready to go, you&#39;ll want to download a &#39;clone&#39; of your GitHub repository. This is rather easy to do using the downloadable clients - you&#39;ll want to select clone and
target your &lt;code&gt;username.github.io&lt;/code&gt; repository. After a minute, you&#39;ll have a local copy of your site on your computer. You can make local changes - and &#39;save&#39; them using Git commits, and push them to the server
as well as download any updates to the remote repository using the &#39;sync&#39; option. The internet has many Git tutorials if this seems confusing, and I&#39;d encourage you to check them out to become confident using
the versioning tools.&lt;/p&gt;
&lt;p&gt;You can build your site (to the folder &lt;code&gt;_site&lt;/code&gt;) with the &lt;code&gt;jekyll serve&lt;/code&gt; command. This will create a local copy of your site, which is served to &lt;code&gt;localhost:4000&lt;/code&gt; - which you can simply use as a web address
in your favourite browser. Any changes to your local files will be reflected in this dynamically-built site (they&#39;ll take a second or two to rebuild), which makes this a great option for developing offline
before you go live.&lt;/p&gt;
&lt;p&gt;Another feature that I&#39;d recommend is using a &lt;code&gt;_drafts&lt;/code&gt; folder (depending on how you set up you may have to create this manually). Here you can work on your posts prior to publishing them in &lt;code&gt;_posts&lt;/code&gt;,
but they can be served up with the rest of your site using &lt;code&gt;jekyll serve --drafts&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id=&quot;yet-to-come&quot;&gt;Yet to Come&lt;a class=&quot;zola-anchor&quot; href=&quot;#yet-to-come&quot; aria-label=&quot;Anchor link for: yet-to-come&quot; style=&quot;visibility: hidden;&quot;&gt;&lt;/a&gt;
&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Customizing Your Page
&lt;ul&gt;
&lt;li&gt;General Configuration&lt;/li&gt;
&lt;li&gt;Page layouts&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Buying and Managing a Domain&lt;/li&gt;
&lt;/ul&gt;
&lt;!--
## Customizing Your Page

### General Configuration

### Layout

### Adding Posts and Pages

I&#39;ve found the simplest way to create new posts is to do so manually (creating a new file in your text editor) or copying another post and updating the new details.

Pages are typically created once



# Buying and Managing a Domain

While having a personal site on Github is great in itself, the `USERNAME.github.io` domain isn&#39;t the cleanest way to get your message across.
Also, outside of the tech sector GitHub isn&#39;t so well known and it gets a bit confusing.
If you&#39;re aiming to create a brand or an image, remember that the domain is what people will see first.
Your domain can make a great first impression.

So how do you get your own custom domain? Its surprisingly simple.
There are many domain name registrars available for you to buy a domain from, but based on my experience I&#39;d be happy to recommend [Namecheap](https://www.namecheap.com/).
You can register both generic top-level domains (gTLD&#39;s, e.g. `.io`, `.xyz`, `.science`) and country code top-level domains (ccTLD&#39;s, e.g. `.com.au`) on Namecheap,
and the price will vary between each TLD - try to find something both relevant and within budget! You can then check whether your desired full domain (e.g. `rocket.science` or `edwardthatch.id`)
is available.

&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;: WhosisGuard.&lt;/p&gt;{: .notice--info}

For ease of management and a bunch of useful tools, I&#39;d suggest transferring your Domain Name Servers to Cloudflare. It will also speed up your website by distributing it across the web, allow a
cached copy to be presented in case your server goes down and help you deal with bots/spammers/attacks should they come your way.

1. Add a CNAME file with your domain (no extension) to your repository on GitHub: `&lt;yourdomain&gt;.com.au`
2. Sign up to CloudFlare and add your site&#39;s domain name. CloudFlare will import your DNS records.
3. Change your nameservers on Namecheap to those provided by CloudFlare.
4. You should find an A-record, and perhaps a few others. You&#39;ll want to add two CNAME records with both `@` and `www` pointing to your GitHub domain.

&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;: DNS changes can take up to a day to occur, so be patient with this step.&lt;/p&gt;{: .notice--warning}


[//]: # # Domain-Specific Email

[//]: # If you intend on having any interaction with the people who visit your site, one of the more professional methods to do this is have a domain-linked email (e.g. `morgan@fluids.rocks`). One issue you&#39;ll likely have if you&#39;ve set up with Cloudflare as above is that it will not be straightfoward to configure (as opposed to having DNS managed by Namecheap, which is a bit easier). There are ways around this, one of which I found (using Mailgun and a Gmail alias) has been detailed by Chris Tarwater.

[//]: # Overall what we&#39;ll do is connect emails sent to `me@domain.com` to point to something you can access and read (e.g. `me@gmail.com`), and then reply from.

[//]: # We&#39;ll be using Mailgun to achieve this. Firstly, you&#39;ll need to direct your DNS pointers on Cloudflare to a Mailgun account. Secondly, we&#39;ll direct Mailgun to Gmail. Last of all we&#39;ll set up Gmail such that we can reply with an alias matching our domain-specific address (`me@domain.com`). Additionally, it would be great to be able to reply to someone&#39;s email with the same &#39;from&#39; address (`me@domain.com`). You can set this up through Gmail by creating an alias.

[//]: # ## Mail Forwarding (MX: Back to DNS)

[//]: # ## Replying: Configure an Alias
--&gt;
&lt;hr /&gt;
&lt;p&gt;A short list of those who provided useful material, who I wish to acknowledge here:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Michael Rose (Made Mistakes, Minimal Mistakes)&lt;/li&gt;
&lt;li&gt;Tania Rascia (www.taniarascia.com)&lt;/li&gt;
&lt;li&gt;Christopher Tarwater (ctarwater)&lt;/li&gt;
&lt;li&gt;Dave Cole (DevelopmentSeed)&lt;/li&gt;
&lt;li&gt;David Burela (BURELA&#39;S HOUSE-O-BLOG)&lt;/li&gt;
&lt;/ul&gt;
</description>
      </item>
      <item>
          <title>IODP Expedition 357: Atlantis Massif</title>
          <pubDate>Sat, 16 Apr 2016 00:00:00 +0000</pubDate>
          <author>Morgan Williams</author>
          <link>https://fluids.rocks/posts/iodp-357-atlantis-massif/</link>
          <guid>https://fluids.rocks/posts/iodp-357-atlantis-massif/</guid>
          <description xml:base="https://fluids.rocks/posts/iodp-357-atlantis-massif/">&lt;p&gt;Over the past 6 months or so I&#39;ve become involved with the International Oceanic
Discovery Program&#39;s (&lt;a rel=&quot;nofollow noreferrer external&quot; title=&quot;IODP Website&quot; href=&quot;http://www.iodp.org/&quot;&gt;IODP&lt;/a&gt;) expedition to the Atlantis
Massif, on the Mid-Atlantic Ridge (MAR). In collaboration with the
European Consortium for Ocean Research Drilling (&lt;a rel=&quot;nofollow noreferrer external&quot; title=&quot;ECORD Website&quot; href=&quot;http://www.ecord.org/&quot;&gt;ECORD&lt;/a&gt;),
a 47-day drilling expedition aboard the Royal Research Vessel James Cook was
conducted in late 2015. In January I travelled to Germany to participate
in the onshore phase of the expedition, which was conducted at the Centre
for Marine Environmental Sciences (&lt;a rel=&quot;nofollow noreferrer external&quot; title=&quot;MARUM Website&quot; href=&quot;https://www.marum.de/en/&quot;&gt;MARUM&lt;/a&gt;) in Bremen.&lt;/p&gt;
&lt;figure&gt;
&lt;img src=&quot;/img/group-photo-VolkerDiekamp.jpg&quot; height=&quot;400&quot;&gt;&lt;figcaption&gt;&lt;p&gt;Image Credit: &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://www.marum.de/en/Volker_Diekamp.html&quot;&gt;V. Diekamp, MARUM&lt;/a&gt;&lt;/p&gt;&lt;/figcaption&gt;&lt;/figure&gt;
&lt;h2 id=&quot;the-expedition&quot;&gt;The Expedition&lt;a class=&quot;zola-anchor&quot; href=&quot;#the-expedition&quot; aria-label=&quot;Anchor link for: the-expedition&quot; style=&quot;visibility: hidden;&quot;&gt;&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;The Atlantis Massif is the surface expression of an oceanic detachment fault
adjacent to the MAR at 30°N; the serpentinite-dominated footwall of the fault
protrudes 4000m from the seafloor and at its summit hosts the
Lost City Hydrothermal Field (LCHF). The LCHF is a low-temperature,
high-pH hydrothermal vent system which hosts unique microbial communities and
large carbonate chimneys. A number of expeditions have visited the site since
its initial discovery in late 2000, seeking to understand the stark contrast to
magmatically-driven hydrothermal systems (black smokers), the potential for
abiogenetic processes and the actively serpentinizing system. Drilling on the
Massif was conducted (IODP Expedition 304/305, see Hole U1309D, below) with the aim of investigating the
interplay of magmatic and tectonic processes during exhumation, spurred by the
newfound significance of oceanic core complexes as features of spreading centres
globally. Expedition 357 aims to further investigate the exhumation of mantle
rocks to the seafloor at slow-spreading ridges, the physiochemical systematics
of serpentinization and the biosphere interaction within the active
ultramafic-hosted hydrothermal system.&lt;/p&gt;
&lt;figure&gt;
&lt;img src=&quot;/img/Schoolmeesters_et_al_2012_AM.png&quot; height=&quot;400&quot;&gt;&lt;figcaption&gt;&lt;p&gt;Bathymetry of the Atlantis Massif (Schoolmesters et al., 2012).&lt;/p&gt;&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;To investigate these processes, we needed to recover intact sequences of
hydrothermally altered lithologies (e.g. serpentinites and talc-schist)
interspersed with more robust magmatic rocks (basalts, dolerites and gabbros).
Drilling in this material is difficult, and typically non-continuous cores
would be recovered - most of the softer material abrades away, the surface
lithologies are not preserved, and competence contrasts make
recovering intact contacts difficult. One way to maximise the recovery is to use
smaller diameter coring drills. For our expedition we utilized two seafloor rock
drills (MeBo and Rock Drill 2, from MARUM, Germany and the
&lt;a rel=&quot;nofollow noreferrer external&quot; title=&quot;BGS Website&quot; href=&quot;http://www.bgs.ac.uk/&quot;&gt;British Geological Survey&lt;/a&gt;,
UK respectively) to retrieve shallow continuous
cores from a transect across the surface of the massif.&lt;/p&gt;
&lt;h2 id=&quot;the-science-party&quot;&gt;The Science Party&lt;a class=&quot;zola-anchor&quot; href=&quot;#the-science-party&quot; aria-label=&quot;Anchor link for: the-science-party&quot; style=&quot;visibility: hidden;&quot;&gt;&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;The science party for the expedition is led by Co-Chief Scientists Prof. Gretchen
Früh-Green (ETH Zürich, Switzerland) and Dr. Beth Orcutt (Bigelow Laboratory for
Ocean Sciences, Maine, USA), and is distinctly multinational and multidisciplinary.
Notably, the expedition is the first to have a female-dominated science party
and one of the first to have two female Co-Chiefs. The 31 scientists conducting
research as part of the expedition are from 13 different countries and include
PhD students, post-doctoral fellows and tenured professors.&lt;/p&gt;
&lt;h2 id=&quot;the-onshore-party&quot;&gt;The Onshore Party&lt;a class=&quot;zola-anchor&quot; href=&quot;#the-onshore-party&quot; aria-label=&quot;Anchor link for: the-onshore-party&quot; style=&quot;visibility: hidden;&quot;&gt;&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;My role in Bremen was as an igneous petrologist with the core description team.
The lithologies recovered from the Massif were challenging to process and
thoroughly describe during the onshore phase, but as the saying goes,
‘the best geologist is the one who has seen the most rocks’,
and the opportunity to closely examine core was certainly a worthwhile one
(even if I’m more of a geochemist!).
Spending two weeks with the core description team was the highlight of my experience –
both in the core lab and out in Bremen.&lt;/p&gt;
&lt;figure&gt;
&lt;img src=&quot;/img/marum_snow.jpg&quot; height=&quot;200&quot;&gt;&lt;figcaption&gt;&lt;p&gt;Snow in Bremen.&lt;/p&gt;&lt;/figcaption&gt;&lt;/figure&gt;
&lt;figure&gt;
&lt;img src=&quot;/img/DW_DSC01062_small.jpg&quot; height=&quot;200&quot;&gt;&lt;figcaption&gt;&lt;p&gt;A few thousand metres of core in the MARUM core repository. Image Credit: Prof. Dominique Weis, UBC.&lt;/p&gt;&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;Working with a wide variety of scientists, and venturing beyond my local
academic ‘bubble’ has been both a revealing and an encouraging experience.
Beyond a set of valuable samples, my stay in Bremen built numerous friendships,
resulted in current and potential collaborators and also a clearer,
more confident outlook regarding my future career.&lt;/p&gt;
&lt;figure&gt;
&lt;img src=&quot;/img/JE_P1311942_.jpg&quot; height=&quot;200&quot;&gt;&lt;figcaption&gt;&lt;p&gt;In the core lab. Image Credit: Dr. Javier Escartin, IPGP.&lt;/p&gt;&lt;/figcaption&gt;&lt;/figure&gt;
&lt;figure&gt;
&lt;img src=&quot;/img/DW_DSC01309.jpg&quot; height=&quot;200&quot;&gt;&lt;/figure&gt;
&lt;figure&gt;
&lt;img src=&quot;/img/DW_DSC01352.jpg&quot; height=&quot;200&quot;&gt;&lt;figcaption&gt;&lt;p&gt;My igpet dream team - Nori Akizawa (Kyoto) and Kirsten Hesse (Geo-Zentrum Nordbayern). Image Credit: Prof. Dominique Weis, UBC.&lt;/p&gt;&lt;/figcaption&gt;&lt;/figure&gt;
&lt;h2 id=&quot;so-we-have-rocks-what-next&quot;&gt;So we have rocks, what next?&lt;a class=&quot;zola-anchor&quot; href=&quot;#so-we-have-rocks-what-next&quot; aria-label=&quot;Anchor link for: so-we-have-rocks-what-next&quot; style=&quot;visibility: hidden;&quot;&gt;&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;A wide range of post-cruise research has been proposed by the multidisciplinary
science party to investigate the biological, geochemical, petrological and
structural features of the recovered cores in the coming months. I’ll be working
on a variety of serpentinite samples to constrain the evolution of
serpentinization processes within the massif, principally using a novel
combination of halogen ratio and in-situ oxygen isotope measurements.&lt;/p&gt;
</description>
      </item>
    </channel>
</rss>
