<?xml version="1.0" encoding="UTF-8"?><?xml-stylesheet href="/assets/atom.xsl" type="text/xsl"?><feed
	xmlns="http://www.w3.org/2005/Atom"
	xmlns:thr="http://purl.org/syndication/thread/1.0"
	xml:lang="en-US"
	><title>Sacha Chua - category - tech</title>
	<subtitle>Emacs, sketches, and life</subtitle>
	<link rel="self" type="application/atom+xml" href="https://sachachua.com/blog/category/tech/feed/atom/index.xml" />
  <link rel="alternate" type="text/html" href="https://sachachua.com/blog/category/tech" />
  <id>https://sachachua.com/blog/category/tech/feed/atom/index.xml</id>
  <generator uri="https://11ty.dev">11ty</generator>
	<updated>2025-08-31T18:05:44Z</updated>
<entry>
		<title type="html">Nginx maps: How I redirect sketch IDs to blog posts or sketches</title>
		<link rel="alternate" type="text/html" href="https://sachachua.com/blog/2025/08/nginx-maps-how-i-redirect-sketch-ids-to-blog-posts-or-sketches/"/>
		<author><name><![CDATA[Sacha Chua]]></name></author>
		<updated>2025-08-31T18:05:44Z</updated>
    <published>2025-08-31T18:05:44Z</published>
    <category term="geek" />
<category term="tech" />
		<id>https://sachachua.com/blog/2025/08/nginx-maps-how-i-redirect-sketch-ids-to-blog-posts-or-sketches/</id>
		<content type="html"><![CDATA[<div class="assumed_audience" id="org51679bf">
<p>
Assumed audience: I'm mostly writing these notes
for myself, because I'd forgotten how this
actually works. Maybe it might be interesting for
people who also like identifying their posts or
sketches and who use Nginx.
</p>

</div>

<p>
Since August 2022, I've been identifying sketches
with YYYY-MM-DD-NN, where NN is a number derived
from my journaling system. I used to use just
YYYY-MM-DD and the title, but sometimes I renamed
sketches. Then I used YYYY-MM-DDa (ex:
<a href="https://sketches.sachachua.com/filename/2022-08-01f%20What%20changes%20with%20the%20Supernote%20%23supernote%20%23kaizen.png">2022-08-01f</a>). Sometimes optical character
recognition had a hard time distinguishing my
hand-drawn "d" from "a", though. Numbers were
generally more reliable. And besides, that way,
there's room for growth in case I have more than
26 journal entries or sketches in a day. (Not that
I've gone anywhere near that volume.)
</p>

<p>
Anyway, I want to have short URLs like
<a href="https://sachachua.com/2025-07-31-10">sachachua.com/2025-07-31-10</a> or
<a href="https://sach.ac/2025-07-31-10">sach.ac/2025-07-31-10</a> go to a blog post if one's
available, or just the sketch if I haven't written
about it yet, like <a href="https://sach.ac/2025-01-16-01">sach.ac/2025-01-16-01</a>. Here's
how I do it.
</p>

<p>
To publish my site, I export HTML files from Org Mode and I use the 11ty static site generator to put them together in a blog.
</p>

<p>
One of my 11ty files is <code>map.11ty.cjs</code>, which has the following code:
</p>

<details><summary>map.11ty.cjs</summary>
<div class="org-src-container">
<pre class="src src-js"><code>module.exports = <span class="org-keyword">class</span> AllPosts {
  data() {
    <span class="org-keyword">return</span> {
      permalink: <span class="org-string">'/conf/nginx.map'</span>,
      eleventyExcludeFromCollections: <span class="org-constant">true</span>
    };
  }
  <span class="org-keyword">async</span> render (data) {
    <span class="org-keyword">let</span> <span class="org-variable-name">list</span> = data.collections._posts;
    <span class="org-keyword">let</span> <span class="org-variable-name">cats</span> = data.siteCategoriesWithParents;
    <span class="org-keyword">let</span> <span class="org-variable-name">nested</span> = cats.filter(o =&gt; o.parent);
    <span class="org-keyword">let</span> <span class="org-variable-name">hash</span> = {};
    <span class="org-keyword">await</span> list.reduce(<span class="org-keyword">async</span> (prev, o) =&gt; {
      <span class="org-keyword">await</span> prev;
      <span class="org-keyword">if</span> (o.data?.zid) {
        hash[o.data.zid] = o.url;
      }
      <span class="org-keyword">let</span> <span class="org-variable-name">matches</span> = (<span class="org-keyword">await</span> o.template.inputContent).matchAll(<span class="org-string">/{% +sketchFull +\"([0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9](-[0-9][0-9]|[a-z]))?/</span>g);
      <span class="org-keyword">for</span> (<span class="org-keyword">const</span> <span class="org-variable-name">m</span> <span class="org-keyword">of</span> matches) {
        <span class="org-keyword">if</span> (m[1] &amp;&amp; !hash[m[1]]) {
          hash[m[1]] = o.url;
        }
      }
    });
    <span class="org-keyword">let</span> <span class="org-variable-name">byZIDs</span> = Object.entries(hash).map((o) =&gt; <span class="org-string">`/${o[0]} ${o[1]};\n`</span>).join(<span class="org-string">''</span>);
    <span class="org-keyword">let</span> <span class="org-variable-name">byBlogID</span> = list.filter(o =&gt; o.data.id).map((o) =&gt; <span class="org-string">`/blog/p/${o.data.id} ${o.url};`</span>).join(<span class="org-string">"\n"</span>) + <span class="org-string">"\n"</span>;
    <span class="org-keyword">let</span> <span class="org-variable-name">nestedCategories</span> = nested.map((o) =&gt; {
      <span class="org-keyword">let</span> <span class="org-variable-name">slugPath</span> = o.parentPath.map((p) =&gt; p.slug).join(<span class="org-string">'/'</span>) + <span class="org-string">'/'</span> + o.slug;
      <span class="org-keyword">let</span> <span class="org-variable-name">catPage</span> = <span class="org-string">`/blog/category/${slugPath} /blog/category/${o.slug};\n`</span>;
      <span class="org-keyword">let</span> <span class="org-variable-name">catFeed</span> = <span class="org-string">`/blog/category/${slugPath}/feed/ /blog/category/${o.slug}/feed/;\n`</span>;
      <span class="org-keyword">let</span> <span class="org-variable-name">catAtom</span> = <span class="org-string">`/blog/category/${slugPath}/feed/atom/ /blog/category/${o.slug}/feed/atom/;\n`</span>;
      <span class="org-keyword">return</span> catAtom + catFeed + catPage;
    }).join(<span class="org-string">''</span>);
    <span class="org-keyword">const</span> <span class="org-variable-name">result</span> = byBlogID + nestedCategories + byZIDs;
    <span class="org-keyword">return</span> result;
  }
};
</code></pre>
</div>

</details>

<p>
The code analyzes each of my blog posts and looks
for full-sized sketches. If it finds a full-size
sketch, it writes a rule that redirects that ID to
that blog post. This is what part of the resulting
<code>nginx.map</code> looks like:
</p>

<pre class="example" id="org4d727a8">
/2025-07-31-10 /blog/2025/08/monthly-review-july-2025/;
/2025-08-04-01 /blog/2025/08/turning-42-life-as-a-41-year-old/;
/2025-08-03-08 /blog/2025/08/turning-42-life-as-a-41-year-old/;
/2025-08-31-01 /blog/2025/08/emacs-elevator-pitch-tinkerers-unite/;
</pre>

<p>
In my Nginx web server config (<code>nginx.conf</code>), I have:
</p>


<div class="org-src-container">
<pre class="src src-conf"><code><span class="org-type">http</span> {
  <span class="org-comment-delimiter"># </span><span class="org-comment">...</span>
  include /etc/nginx/redirections.map;
  <span class="org-comment-delimiter"># </span><span class="org-comment">...</span>
}
</code></pre>
</div>


<p>
And that <code>redirections.map</code> uses the <a href="https://nginx.org/en/docs/http/ngx_http_map_module.html">map module</a> and looks like this:
</p>


<div class="org-src-container">
<pre class="src src-conf"><code><span class="org-type">map $request_uri $new_uri</span> {
   default <span class="org-string">""</span>;
   include /var/www/static-blog/conf/nginx.map;
   / /blog;
   <span class="org-comment-delimiter"># </span><span class="org-comment">...</span>
}
</code></pre>
</div>


<p>
Then in my Nginx site configuration, I redirect to that <code>$new_uri</code> if available. As a fallback, I also detect yyyy-mm-dd-nn URLs and redirect them to my sketchbook.
</p>


<div class="org-src-container">
<pre class="src src-conf"><code><span class="org-type">server</span> {
   <span class="org-comment-delimiter"># </span><span class="org-comment">... other rules</span>
   <span class="org-type">if ($new_uri)</span> {
       return 302 $new_uri;
   }
   <span class="org-type">location ~ ^/([0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9](a-z|-[0-9][0-9])?)$</span> {
      return 302 https://sketches.sachachua.com/$1;
   }
}
</code></pre>
</div>


<p>
The Node app that runs sketches.sachachua.com searches by ID if it gets one. Here's the code in the Express router:
</p>


<div class="org-src-container">
<pre class="src src-js"><code>router.get(<span class="org-string">'/:filename'</span>, controller.serveImagePageByFilename);
</code></pre>
</div>


<p>
which calls this controller:
</p>


<div class="org-src-container">
<pre class="src src-js"><code>exports.serveImagePageByFilename = <span class="org-keyword">async</span> (req, res) =&gt; {
  <span class="org-keyword">if</span> (req.params.filename) {
    <span class="org-keyword">let</span> <span class="org-variable-name">f</span> = <span class="org-keyword">await</span> findImageByFilename(req.params.filename);
    <span class="org-keyword">if</span> (f) {
      renderSingle(req, res, f);
    } <span class="org-keyword">else</span> {
      res.sendStatus(404);
    }
  } <span class="org-keyword">else</span> {
    res.sendStatus(404);
  }
};
</code></pre>
</div>


<p>
which uses this function:
</p>


<div class="org-src-container">
<pre class="src src-js"><code><span class="org-keyword">const</span> <span class="org-variable-name">findImageByFilename</span> = (filename) =&gt; {
  <span class="org-keyword">return</span> getSketches().then((sketches) =&gt; {
    <span class="org-keyword">let</span> <span class="org-variable-name">id</span> = filename.match(<span class="org-string">/^[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]([a-z]|-[0-9][0-9])/</span>);
    <span class="org-keyword">let</span> <span class="org-variable-name">m</span> = filename.match(<span class="org-string">/^[^#]+/</span>);
    <span class="org-keyword">let</span> <span class="org-variable-name">file</span> = sketches.find((x) =&gt; path.parse(x.filename).name == filename);
    <span class="org-keyword">if</span> (!file) {
      file = sketches.find((x) =&gt; x.filename.startsWith(id));
    }
    <span class="org-keyword">if</span> (!file &amp;&amp; m) {
      file = sketches.find((x) =&gt; x.filename.startsWith(m[0]));
    }
    <span class="org-keyword">if</span> (!file) {
      file = sketches.find((x) =&gt; path.parse(x.filename).name.replace(<span class="org-string">' #.*'</span>) == filename);
    }
    <span class="org-keyword">if</span> (!file) {
      file = sketches.find((x) =&gt; x.filename.startsWith(filename));
    }
    <span class="org-keyword">return</span> file;
  });
};
</code></pre>
</div>


<p>
(getSketches just reads the JSON I've saved.)
</p>

<p>
So my workflow for a public sketch is:
</p>

<ol class="org-ol">
<li>Draw the sketch.</li>
<li>In my journaling system, write the title and tags for the sketch. Get an ID.</li>
<li>Write the ID on the sketch.</li>
<li>Export the sketch from the iPad as a JPEG.</li>
<li>Wait for things to sync in the background.</li>
<li>Process the sketch. I can use <code>my-image-rename-current-image-based-on-id</code> to rename it manually, or I can use <code>my-sketch-process</code> to recognize the ID and process the text. (<a href="https://sachachua.com/dotemacs/">My Emacs configuration</a>)</li>
<li>Wait for things to sync in the background. In the meantime, edit the text version of the sketch.</li>
<li>Reload my <a href="https://sketches.sachachua.com">public sketches</a>, which regenerates the sketches.json that lists all the sketches.</li>
<li>Use <code>my-write-about-sketch</code> or <code>my-insert-sketch-and-text</code> to include the sketch and the text in a draft blog post. (<a href="https://sachachua.com/dotemacs/">My Emacs configuration</a>) Do my usual blogging process.</li>
</ol>
<div><a href="https://sachachua.com/blog/2025/08/nginx-maps-how-i-redirect-sketch-ids-to-blog-posts-or-sketches/index.org">View org source for this post</a></div><p>You can <a href="https://social.sachachua.com/@sacha/statuses/01K40NNQ6T6Q59CHGM7A99BZ2R" target="_blank" rel="noopener noreferrer">comment on Mastodon</a> or <a href="mailto:sacha@sachachua.com?subject=Comment%20on%20https%3A%2F%2Fsachachua.com%2Fblog%2F2025%2F08%2Fnginx-maps-how-i-redirect-sketch-ids-to-blog-posts-or-sketches%2F&body=Name%20you%20want%20to%20be%20credited%20by%20(if%20any)%3A%20%0AMessage%3A%20%0ACan%20I%20share%20your%20comment%20so%20other%20people%20can%20learn%20from%20it%3F%20Yes%2FNo%0A">e-mail me at sacha@sachachua.com</a>.</p>]]></content>
		</entry><entry>
		<title type="html">Two weeks with the iPad Air (+ SuperNote A5X and Lenovo P52)</title>
		<link rel="alternate" type="text/html" href="https://sachachua.com/blog/2025/02/two-weeks-with-the-ipad-air-supernote-a5x-and-lenovo-p52/"/>
		<author><name><![CDATA[Sacha Chua]]></name></author>
		<updated>2025-03-01T14:55:09Z</updated>
    <published>2025-02-28T20:42:01Z</published>
    <category term="geek" />
<category term="tech" />
<category term="ipad" />
		<id>https://sachachua.com/blog/2025/02/two-weeks-with-the-ipad-air-supernote-a5x-and-lenovo-p52/</id>
		<content type="html"><![CDATA[<div class="update" id="orgdd950f7">
<p>
<span class="timestamp-wrapper"><span class="timestamp">[2025-03-01 Sat]</span></span>: Genaro suggested <a href="https://www.beorgapp.com/">beorg</a>, so now I've
got that on my iPad and it seems to be doing fine
for browsing my Org Mode files. I think I'll set
up WebDAV on our network-attached storage (NAS)
and see what that's like, too.
</p>

<p>
Also, apparently, it's been about three weeks, whoops!
</p>

</div>

<p>
I've had this iPad Air 13" for about <del>two</del> three weeks
now, and I'm slowly settling into how the
different pieces of tech can work together and how
things can flow.
</p>

<p>
</p><div class="sketch-full"><a class="photoswipe" href="https://sketches.sachachua.com/filename/2025-02-28-01%20Getting%20used%20to%20SuperNote%20A5X%20and%20iPad%20Air%2013%20&#45;&#45;%20drawing%20supernote%20ipad%20tech.png" data-src="https://sketches.sachachua.com/static/2025-02-28-01%20Getting%20used%20to%20SuperNote%20A5X%20and%20iPad%20Air%2013%20&#45;&#45;%20drawing%20supernote%20ipad%20tech.png" data-title="2025-02-28-01 Getting used to SuperNote A5X and iPad Air 13 &#45;&#45; drawing supernote ipad tech" data-w="2648" data-h="3524"><picture>
      <img src="https://sketches.sachachua.com/static/2025-02-28-01%20Getting%20used%20to%20SuperNote%20A5X%20and%20iPad%20Air%2013%20&#45;&#45;%20drawing%20supernote%20ipad%20tech.png" width="2648" height="3524" alt="2025-02-28-01 Getting used to SuperNote A5X and iPad Air 13 &#45;&#45; drawing supernote ipad tech" loading="lazy" style="max-height: 90vw; height: auto; width: auto" decoding="async">
      <figcaption>2025-02-28-01 Getting used to SuperNote A5X and iPad Air 13 &#45;&#45; drawing supernote ipad tech</figcaption>
    </picture></a></div>
<p></p>

<details class="code-details" style="padding: 1em;
                 border-radius: 15px;
                 font-size: 0.9em;
                 box-shadow: 0.05em 0.1em 5px 0.01em  #00000057;">
                  <summary><strong>Text from sketch</strong></summary>
<p>
Getting used to SuperNote A5X + iPad Air 13"
</p>

<p>
SuperNote A5X:
</p>

<ul class="org-ul">
<li>I prefer it for writing, sketchnotes
<ul class="org-ul">
<li>pen feel</li>
<li>strokes: exactly as I expect, no hooks or smoothing</li>
<li>lighter weight</li>
<li>no backlight</li>
</ul></li>
<li>Also nice for reading EPUBs, PDFs</li>
</ul>

<p>
iPad Air:
</p>

<ul class="org-ul">
<li>Libby for library e-books</li>
<li>Procreate, Simply Draw for art</li>
<li>NetNewswire for RSS</li>
<li>Copying &amp; pasting is nice</li>
</ul>

<p>
Procreate replaces spaces with underscores and doesn't overwrite files. Instead, it adds 2 to the filename.
</p>

<p>
I want to start building up thought maps so I can see quick summaries
and open questions. The laptop is probably the best place to do it.
</p>

<p>
Org Mode files with sketches, text, and links
</p>

<ul class="org-ul">
<li>Export to HTML</li>
<li>Also, add export to HTML gallery view?</li>
</ul>

<p>
Private sketches: Available through DS
</p>

<p>
Private web server on the NAS? I can just run my sketch viewer.
</p>

<p>
DS: Synology NAS drive
</p>

<ul class="org-ul">
<li>Program the server to consolidate files?</li>
<li>Consider moving metadata to subdir for easier flipping?</li>
</ul>

<p>
Ideas for next steps:
</p>

<ul class="org-ul">
<li>Figure out easier doodling from iPad: resample, autocrop, insert with HTML markup</li>
<li>Automate recolour/rename from Dropbox (phone? server?)</li>
</ul>


</details>

<p>
On the iPad, I've been mostly using:
</p>

<ul class="org-ul">
<li><a href="https://procreate.com/">Procreate</a>: drawing, painting</li>
<li><a href="https://www.getnoteful.com/">Noteful</a>: sketchnotes and thinking, although I'm not completely happy with the pen setup</li>
<li><a href="http://www.zoom-notes.com/">ZoomNotes</a>: also experimenting with this one, might be better for relating several sketches together</li>
<li><a href="https://hellosimply.com/">Simply Piano / Simply Sing / Simply Draw</a>: tutorials</li>
<li><a href="https://netnewswire.com/">NetNewsWire</a>: RSS feeds</li>
<li><a href="https://libbyapp.com/">Libby</a>: reading</li>
</ul>

<p>
Moving data around is a bit of a hodgepodge:
internal webserver or Dropbox for the Supernote,
Dropbox and Synology's DS Drive for the iPad, and
Syncthing for the server. I'm sure that'll settle
down eventually as I figure out a better flow.
</p>

<div class="right-doodle" id="orgeffc4ba">

<figure id="orgfae1c74">
<img src="https://sachachua.com/blog/2025/02/two-weeks-with-the-ipad-air-supernote-a5x-and-lenovo-p52/Untitled_Artwork.jpg" alt="Untitled_Artwork.jpg" title="Trying to sing">

</figure>

</div>

<p>
I like how drawing, singing, and playing
the piano give me a way to distract myself from
the urge to nag A+. Still definitely just starting
out, but it's fun anyway. It's nice to be able to
breeze through library e-books again, too.
</p>

<p>
I notice I've been missing the occasional calendar
reminder from my phone, so that's probably a sign
that I need to (a) set up the iPad for calendar
access as well, (b) have more distinct
notifications on my phone, and (c) take the phone
with me as I go from room to room. On the plus
side, that probably means I've been getting in the
zone, yay! It seems a little easier to just take
the whole stack of phone+Supernote+iPad when I
move rooms instead of leaving them in different
places and then having to go find them. The laptop
is a lot heavier, though, so I tend to move that
on an as-needed basis.
</p>

<p>
I set up a copy of <a href="https://sketches.sachachua.com">sketches.sachachua.com</a> server
on the NAS (so it's just on the local network)
pointing to my private sketches directory, so now
I can flip through my private sketches fairly
easily. They're mostly just various thoughts on
parenting and emotions and life. Now I can review
them from either my iPad or my laptop, yay!
</p>

<p>
Ideas for next steps:
</p>

<ul class="org-ul">
<li>Keep distracting myself from fretting about A+
by completing various art/music tutorials.</li>
<li>Have fun doodling.</li>
<li>Contemplate whether I want to read my Org files
on the iPad, and what the best way of doing it
might be. (<a href="https://github.com/200ok-ch/organice">Organice</a>? <a href="https://plainorg.com/">PlainOrg</a>? Just do a
PDF/EPUB export of stuff I'm focusing on? - update 2025-03-01: added <a href="https://www.beorgapp.com/">Beorg</a>)</li>
<li>See if A+ is up for field trips to the art
gallery or museum.</li>
</ul>

<p>
The iPad Air seems like a good addition. Let's see
what I can do with it.</p>
<div><a href="https://sachachua.com/blog/2025/02/two-weeks-with-the-ipad-air-supernote-a5x-and-lenovo-p52/index.org">View org source for this post</a></div><p>You can <a href="https://sachachua.com/blog/2025/02/two-weeks-with-the-ipad-air-supernote-a5x-and-lenovo-p52/#comment">view 1 comment</a> or <a href="mailto:sacha@sachachua.com?subject=Comment%20on%20https%3A%2F%2Fsachachua.com%2Fblog%2F2025%2F02%2Ftwo-weeks-with-the-ipad-air-supernote-a5x-and-lenovo-p52%2F&body=Name%20you%20want%20to%20be%20credited%20by%20(if%20any)%3A%20%0AMessage%3A%20%0ACan%20I%20share%20your%20comment%20so%20other%20people%20can%20learn%20from%20it%3F%20Yes%2FNo%0A">e-mail me at sacha@sachachua.com</a>.</p>]]></content>
		</entry><entry>
		<title type="html">Scaling a BigBlueButton server down to a 1 GB node between uses</title>
		<link rel="alternate" type="text/html" href="https://sachachua.com/blog/2025/01/scaling-a-bigbluebutton-server-down-to-a-1-gb-node-between-uses/"/>
		<author><name><![CDATA[Sacha Chua]]></name></author>
		<updated>2025-01-23T19:53:47Z</updated>
    <published>2025-01-23T19:53:47Z</published>
    <category term="geek" />
<category term="tech" />
<category term="emacsconf" />
<category term="emacs" />
		<id>https://sachachua.com/blog/2025/01/scaling-a-bigbluebutton-server-down-to-a-1-gb-node-between-uses/</id>
		<content type="html"><![CDATA[<p>
Now that we've survived <a href="https://emacsconf.org/">EmacsConf</a>, I've been
looking into running a <a href="https://bigbluebutton.org">BigBlueButton</a> server so
that various Emacs meetups can use it if they like
instead of relying on <a href="https://jitsi.org/">Jitsi</a> or other free
video-conferencing services. (I spent some time
looking into Galene, but I'm not quite sure that's
ready for our uses yet, like this <a href="https://github.com/jech/galene/issues/213">issue that
LibrePlanet ran into with recording</a>.)
</p>

<p>
BigBlueButton requires a server with at least 4
CPU cores and 8 GB of RAM to even start up, and it
doesn't like to share with other services. This
costs about USD 48+tax/month on <a href="https://www.linode.com/pricing/">Linode</a> or USD
576+tax/year, which is not an efficient use of
funds yet. I could delete it after each instance,
but I've been having a hard time properly
restoring it from backup after deploying to a new
IP address. <code>bbb-conf &#45;&#45;setip</code> doesn't seem to
catch everything, so I was still getting curl
errors related to verifying the certificate.
</p>

<p>
A reasonable in-between is to run it on Linode's
lowest plan (1 core, 1GB RAM; USD 60+tax for the
year) in between meetups, and then spin things up
for maybe 6-12 hours around each meetup. If I go
with the 4-core 8 GB setup, that would be an extra
USD 0.43 - 0.86 USD per meetup, which is eminently
doable. I could even go with the recommended
configuration of 8 cores and 16 GB memory on a
dedicated CPU plan (USD 0.216/hour, so USD 1.30 to
2.59 per meetup). This was the approach that we
used while preparing for EmacsConf. Since I didn't
have a lot of programming time, I scaled the node
up to 4 core / 8GB RAM whenever I had time to work
on it, and I scaled it down to 1GB at the end of
each of my working sessions. I scaled it up to
dedicated 8 core / 16 GB RAM for EmacsConf, during
which we used roughly half of the CPU capacity in
order to host a max of 107 simultaneous users over
7 meetings.
</p>

<p>
I reviewed my BigBlueButton setup notes in the
<a href="https://emacsconf.org/organizers-notebook/#bbb">EmacsConf organizers notebook</a> and <a href="https://emacsconf.org/2024/organizers-notebook/#check-emacsconf-infrastructure-bigbluebutton">the 2024
notebook</a> and set up a Linode instance under my
account, so that I can handle the billing and also
so that Amin Bandali doesn't get spammed by all
the notifications (up, down, up, down&#x2026;). And
then I'll be able to just scale it up when
EmacsConf comes around again, which is nice.
</p>

<p>
Anyway, BBB refuses to install on a machine with
fewer than 4 cores or 8 GB RAM, but once you set
it up, it'll valiantly thrash around even on an
underpowered server, which makes working with the
server over ssh a lot slower. Besides, that's not
friendly to other people using the same server. I
wanted to configure the services so that they
would only run on a server of the correct size. It
turns out that systemd will let you specify either
<code>ConditionMemory</code> and <code>ConditionCPUs</code> in the <a href="https://www.freedesktop.org/software/systemd/man/latest/systemd.unit.html">unit
configuration file</a>, and that you can use files
ending in <code>.conf</code> in a directory named like
<code>yourservicename.service.d</code> to override part of
the configuration. Clear examples were hard to
find, so I wanted to share these notes.
</p>

<p>
Since <code>ConditionMemory</code> is
specified in bytes (ex: 8000000000), I found
<code>ConditionCPUs</code> to be easier to read.
</p>

<p>
I used this command to check if I'd gotten the
syntax right:
</p>


<div class="org-src-container">
<pre class="src src-sh">systemd-analyze condition <span class="org-string">'ConditionCPUs=&gt;=4'</span>
</pre>
</div>


<p>
and then I wrote this script to set up the overrides:
</p>


<div class="org-src-container">
<pre class="src src-sh"><span class="org-variable-name">CPUS_REQUIRED</span>=4
<span class="org-keyword">for</span> ID<span class="org-keyword"> in</span> coturn.service redis-server.service bigbluebutton.target multi-user.target bbb-graphql-server.service bbb-rap-resque-worker.service bbb-webrtc-sfu.service bbb-fsesl-akka.service bbb-webrtc-recorder.service bbb-pads.service bbb-export-annotations.service bbb-web.service freeswitch.service etherpad.service bbb-rap-starter.service bbb-rap-caption-inbox.service freeswitch.service bbb-apps-akka.service bbb-graphql-actions postgresql@14-main.service; <span class="org-keyword">do</span>
    mkdir -p /etc/systemd/system/$<span class="org-variable-name">ID</span>.d
    printf <span class="org-string">"[Unit]\nConditionCPUs=&gt;=$CPUS_REQUIRED\n"</span> &gt; /etc/systemd/system/$<span class="org-variable-name">ID</span>.d/require-cpu.conf
<span class="org-keyword">done</span>
systemctl daemon-reload
systemd-analyze verify bigbluebutton.target
</pre>
</div>


<p>
It seems to work. When I use <a href="https://www.linode.com/products/cli/">linode-cli</a> to resize to the testing size, BigBlueButton works:
</p>


<div class="org-src-container">
<pre class="src src-sh"><span class="org-comment-delimiter">#</span><span class="org-comment">!/bin/</span><span class="org-keyword">bash</span>
<span class="org-builtin">source</span> /home/sacha/.profile
<span class="org-variable-name">PATH</span>=/home/sacha/.local/bin/:$<span class="org-variable-name">PATH</span>
linode-cli linodes resize $<span class="org-variable-name">BBB_ID</span> &#45;&#45;type g6-standard-4 &#45;&#45;allow_auto_disk_resize false
sleep 4m
linode-cli linodes boot $<span class="org-variable-name">BBB_ID</span>
sleep 3m
ssh root@bbb.emacsverse.org <span class="org-string">"bbb-conf &#45;&#45;restart; cd ~/greenlight-v3; docker compose restart"</span>
notify-send <span class="org-string">"Should be ready"</span>
</pre>
</div>


<p>
And when I resize it down to a 1 GB nanode,
BigBlueButton doesn't get started and the VPS is
nice and responsive when I SSH in.
</p>


<div class="org-src-container">
<pre class="src src-sh"><span class="org-comment-delimiter">#</span><span class="org-comment">!/bin/</span><span class="org-keyword">bash</span>
<span class="org-builtin">source</span> /home/sacha/.profile
<span class="org-variable-name">PATH</span>=/home/sacha/.local/bin/:$<span class="org-variable-name">PATH</span>
<span class="org-builtin">echo</span> Powering off
linode-cli linodes shutdown $<span class="org-variable-name">BBB_ID</span>
sleep 60
<span class="org-builtin">echo</span> <span class="org-string">"Resizing BBB node to nanode, dormant"</span>
linode-cli linodes resize $<span class="org-variable-name">BBB_ID</span> &#45;&#45;type g6-nanode-1 &#45;&#45;allow_auto_disk_resize false
</pre>
</div>


<p>
So now I'm going to coordinate with Ihor Radchenko
about when he might want to try this out for
<a href="https://orgmode.org/worg/orgmeetup.html">OrgMeetup</a>, and I can talk to other meetup
organizers to figure out times. People will
probably want to test things before announcing it
to their meetup groups, so we just need to
schedule that. It's BigBlueButton 3.0. I'm not
100% confident in the setup. We had some technical
issues with some EmacsConf speakers even though we
did a tech check with them before we went live
with their session. Not sure what happened there.
</p>

<p>
I'm still a little nervous about accidentally
forgetting to downscale the server and running up
a bill, but I've scheduled downscaling with the <a href="https://www.geeksforgeeks.org/at-command-in-linux-with-examples/">at
command</a> before, so that's helpful. If it turns out
to be something we want to do regularly, I might
even be able to use a cronjob from my other server
so that it happens even if my laptop is off, and
maybe set up a backup nginx server with a friendly
message (and maybe a list of upcoming meetups) in
case people connect before it's been scaled up.
Anyway, I think that's a totally good use of part
of the <a href="https://sachachua.com/blog/2023/12/google-open-source-peer-bonus/">Google Open Source Peer Bonus</a> I received
last year.
</p>

<p>
As an aside, you can change a room's <code>friendly_id</code>
to something actually friendly. In the Rails
console (<code>docker exec -it greenlight-v3 bundle
exec rails console</code>), you could do something like
this:
</p>


<div class="org-src-container">
<pre class="src src-ruby"><span class="org-type">Room</span>.find_by(<span class="org-constant">friendly_id:</span> <span class="org-string">"CURRENT_ROOM_ID"</span>).update_attribute(<span class="org-constant">:friendly_id</span>, <span class="org-string">"NEW_CUSTOM_ID"</span>)
</pre>
</div>


<p>
Anyway, let me know if you organize an Emacs meetup and want to give this BigBlueButton instance a try!
</p>
<div><a href="https://sachachua.com/blog/2025/01/scaling-a-bigbluebutton-server-down-to-a-1-gb-node-between-uses/index.org">View org source for this post</a></div><p>You can <a href="mailto:sacha@sachachua.com?subject=Comment%20on%20https%3A%2F%2Fsachachua.com%2Fblog%2F2025%2F01%2Fscaling-a-bigbluebutton-server-down-to-a-1-gb-node-between-uses%2F&body=Name%20you%20want%20to%20be%20credited%20by%20(if%20any)%3A%20%0AMessage%3A%20%0ACan%20I%20share%20your%20comment%20so%20other%20people%20can%20learn%20from%20it%3F%20Yes%2FNo%0A">e-mail me at sacha@sachachua.com</a>.</p>]]></content>
		</entry><entry>
		<title type="html">One month with the SuperNote A5X</title>
		<link rel="alternate" type="text/html" href="https://sachachua.com/blog/2022/08/one-month-with-the-supernote-a5x/"/>
		<author><name><![CDATA[Sacha Chua]]></name></author>
		<updated>2022-08-25T15:44:41Z</updated>
    <published>2022-08-25T15:44:41Z</published>
    <category term="tech" />
<category term="supernote" />
<category term="drawing" />
		<id>https://sachachua.com/blog/2022/08/one-month-with-the-supernote-a5x/</id>
		<content type="html"><![CDATA[<p>
I've had my SuperNote A5X for a month now, and I really like it.
</p>

<details class="code-details" style="padding: 1em;
                 border-radius: 15px;
                 font-size: 0.9em;
                 box-shadow: 0.05em 0.1em 5px 0.01em  #00000057;">
                  <summary><strong>Text from my sketch</strong></summary>
<p>
I use it for:
</p>
<ul class="org-ul">
<li>untangling thoughts</li>
<li>sketchnoting books</li>
<li>planning</li>
<li>drafting blog posts</li>
<li>drawing</li>
</ul>

<p>
A- uses it for: (she's 6 years old)
</p>
<ul class="org-ul">
<li>practising cursive</li>
<li>doing mazes and dot-to-dots</li>
<li>drawing</li>
<li>reading lyrics</li>
</ul>


<p>
Things I'm learning:
</p>

<ul class="org-ul">
<li>Exporting PNGs at 200% works well for my workflow. I rename them in
Dropbox and upload them to <a href="https://sketches.sachachua.com">sketches.sachachua.com</a>.</li>
<li>Carefully copying &amp; deleting pages lets me preserve page numbers. I use lassoed titles for active thoughts and maintain a manual index for other things.</li>
<li>Layouts:
<ul class="org-ul">
<li>Landscape: only easier to review on my laptop</li>
<li>Portrait columns: lots of scrolling up and down</li>
<li>Portrait rows: a little harder to plan, but easier to review</li>
</ul></li>
<li>Many books fit into one page each.</li>
<li>Google Lens does a decent job of converting my handwriting to text (print or cursive, even with a background). Dropbox → Google Photos → Orgzly → Org</li>
<li>Draft blog posts go into new notebooks so that I can delete them once converted.</li>
<li>The Super Note helps me reclaim a lot of the time I spend waiting for A-. A digital notebook is really nice. Easy to erase, rearrange, export&#x2026; It works well for me.</li>
<li>Part of my everyday carry kit</li>
</ul>

<p>
Ideas for growth:
</p>
<ul class="org-ul">
<li>Settle into monthly pages, bullet journaling techniques</li>
<li>Practise drawing; use larger graphic elements &amp; organizers, different shades</li>
<li>Integrate into Zettelkasten</li>
</ul>


</details>

<p>
</p><div class="sketch-full"><a class="photoswipe" href="https://sketches.sachachua.com/filename/2022-08-25-01%20One%20month%20with%20the%20SuperNote%20A5X%20%23supernote%20%23drawing%20%23decision-review%20%23tech.png" data-src="https://sketches.sachachua.com/static/2022-08-25-01%20One%20month%20with%20the%20SuperNote%20A5X%20%23supernote%20%23drawing%20%23decision-review%20%23tech.png" data-title="2022-08-25-01 One month with the SuperNote A5X #supernote #drawing #decision-review #tech.png" data-w="2808" data-h="3744"><picture>
      <img src="https://sketches.sachachua.com/static/2022-08-25-01%20One%20month%20with%20the%20SuperNote%20A5X%20%23supernote%20%23drawing%20%23decision-review%20%23tech.png" width="2808" height="3744" alt="2022-08-25-01 One month with the SuperNote A5X #supernote #drawing #decision-review #tech.png" loading="lazy" style="max-height: 90vw; height: auto; width: auto" decoding="async">
      <figcaption>2022-08-25-01 One month with the SuperNote A5X #supernote #drawing #decision-review #tech.png</figcaption>
    </picture></a></div>
<p></p>

<p>
I put my visual book notes and visual library notes into a <a href="https://www.dropbox.com/sh/z198zr3inib46ms/AADXD1RzW88KFadAYhfnR3MUa?dl=0">Dropbox
shared folder</a> so that you can check them out if you have a
Supernote. If you don't have a Supernote, you can find my visual book
notes at <a href="https://sketches.sachachua.com">sketches.sachachua.com</a>. Enjoy!
</p>
<p>You can <a href="mailto:sacha@sachachua.com?subject=Comment%20on%20https%3A%2F%2Fsachachua.com%2Fblog%2F2022%2F08%2Fone-month-with-the-supernote-a5x%2F&body=Name%20you%20want%20to%20be%20credited%20by%20(if%20any)%3A%20%0AMessage%3A%20%0ACan%20I%20share%20your%20comment%20so%20other%20people%20can%20learn%20from%20it%3F%20Yes%2FNo%0A">e-mail me at sacha@sachachua.com</a>.</p>]]></content>
		</entry><entry>
		<title type="html">Trying out the SuperNote A5X</title>
		<link rel="alternate" type="text/html" href="https://sachachua.com/blog/2022/07/trying-out-the-supernote-a5x/"/>
		<author><name><![CDATA[Sacha Chua]]></name></author>
		<updated>2022-07-29T14:57:34Z</updated>
    <published>2022-07-29T14:57:34Z</published>
    <category term="tech" />
<category term="geek" />
<category term="drawing" />
<category term="supernote" />
		<id>https://sachachua.com/blog/2022/07/trying-out-the-supernote-a5x/</id>
		<content type="html"><![CDATA[<p>
</p><div class="sketch-full"><a class="photoswipe" href="https://sketches.sachachua.com/filename/2022-07-25%20Trying%20the%20Supernote%20A5X.png" data-src="https://sketches.sachachua.com/static/2022-07-25%20Trying%20the%20Supernote%20A5X.png" data-title="2022-07-25 Trying the SuperNote A5X" data-w="3744" data-h="2808"><picture>
      <img src="https://sketches.sachachua.com/static/2022-07-25%20Trying%20the%20Supernote%20A5X.png" width="3744" height="2808" alt="2022-07-25 Trying the SuperNote A5X" loading="lazy" style="max-height: 90vw; height: auto; width: auto" decoding="async">
      <figcaption>2022-07-25 Trying the SuperNote A5X</figcaption>
    </picture></a></div>
<p></p>

<p>
W- was happy with his SuperNote A5X, so I ordered one for myself on
July 18. The company was still doing pre-orders because of the
lockdowns in China, but it shipped out on July 20 and arrived on July
25, which was pretty fast.
</p>

<p>
I noticed that the <code>org-epub</code> export makes <code>verse</code> blocks look
double-spaced on the SuperNote, probably because <code>&lt;br&gt;</code> tags are
getting extra spacing. I couldn't figure out how to fix it with CSS,
so I've been hacking around it by exporting it as a different class
without the <code>&lt;br&gt;</code> tags and just using <code>{ white-space: pre }</code>. I also
ended up redoing the templates I made in Inkscape, since the gray I
used was too light to see on the SuperNote.
</p>

<p>
</p><div class="sketch-full"><a class="photoswipe" href="https://sketches.sachachua.com/filename/2022-07-26b%20SuperNote%20for%20now%20%23sketching%20%23supernote.jpg" data-src="https://sketches.sachachua.com/static/2022-07-26b%20SuperNote%20for%20now%20%23sketching%20%23supernote.jpg" data-title="2022-07-26b SuperNote for now" data-w="2560" data-h="1920"><picture>
      <img src="https://sketches.sachachua.com/static/2022-07-26b%20SuperNote%20for%20now%20%23sketching%20%23supernote.jpg" width="2560" height="1920" alt="2022-07-26b SuperNote for now" loading="lazy" style="max-height: 90vw; height: auto; width: auto" decoding="async">
      <figcaption>2022-07-26b SuperNote for now</figcaption>
    </picture></a></div>
<p></p>

<p>
It was very tempting to dive into the rabbithole of interesting
layouts on <a href="https://reddit.com/r/supernote">/r/supernote</a> and various journaling resources, but I still
don't have much time, so there's no point in getting all fancy about
to-do lists or trackers at the moment. I wanted to focus on just a
couple of things: untangling my thoughts and sketching. Sketchnoting
books would be a nice bonus (and I actually managed to do one on paper
during a recent playdate), but that can also wait until I have more
focused time.
</p>

<p>
</p><div class="sketch-full"><a class="photoswipe" href="https://sketches.sachachua.com/filename/2022-07-29a%20Getting%20the%20hang%20of%20drawing%20on%20the%20Supernote%20A5X%20%23sketching%20%23supernote.jpg" data-src="https://sketches.sachachua.com/static/2022-07-29a%20Getting%20the%20hang%20of%20drawing%20on%20the%20Supernote%20A5X%20%23sketching%20%23supernote.jpg" data-title="2022-07-29a Getting the hang of drawing on the Supernote A5X" data-w="2560" data-h="1920"><picture>
      <img src="https://sketches.sachachua.com/static/2022-07-29a%20Getting%20the%20hang%20of%20drawing%20on%20the%20Supernote%20A5X%20%23sketching%20%23supernote.jpg" width="2560" height="1920" alt="2022-07-29a Getting the hang of drawing on the Supernote A5X" loading="lazy" style="max-height: 90vw; height: auto; width: auto" decoding="async">
      <figcaption>2022-07-29a Getting the hang of drawing on the Supernote A5X</figcaption>
    </picture></a></div>
<p></p>

<p>
I've had the A5X for five days and I really like it. Writing with the
Lamy pen feels like less work than writing with a pencil or regular
pen. It's smooth but not rubbery. I've still been drawing in landscape
form because that feels a little handier for reviewing on my tablet or
writing about on my blog, but I should probably experiment with
portrait form at some point.
</p>

<p>
So far, I've:
</p>
<dl class="org-dl">
<dt>sketched out my thoughts</dt><dd>I used to use folded-over 8x14" to
sketch out two thoughts, but scanning them was a bit of a pain.
Sometimes I used the backs of our writing practice sheets in order
to reduce paper waste, but then scanning wasn't always as clean. I
really like using the SuperNote to sketch out thoughts like this
one. It's neat, and I can get the note into my archive pretty easily.</dd>
<dt>sketched stuff from life</dt><dd>This is easier if I take a quick
reference picture on my phone. I could probably even figure out some
kind of workflow for making that available as a template for tracing.</dd>
<dt>received many kiddo drawings</dt><dd>A- loves being able to use the
eraser and lasso to modify her drawings. Novelty's probably another
key attraction, too. She's made quite a few drawings for me, even
experimenting with drawing faces from the side like the way she's
been seeing me practice doing.</dd>
<dt>received many kiddo requests</dt><dd>A- likes to ask me to draw things.
She enjoys tracing over them in another layer. More drawing practice
for both of us!</dd>
<dt>used it to help A- practise coding, etc.</dt><dd>A- wanted to do some
coding puzzles with her favourite characters. I enjoyed being able
to quickly sketch it up, drawing large versions and then scaling
down as needed.</dd>
<dt>played a game of chess</dt><dd>I drew chess pieces just to see if I
could, and we ended up using those to play chess. I should share
these and maybe add other games as well.</dd>
<dt>referred to EPUBs and PDFs</dt><dd>I put our favourite songs and poems on
it. I've also started using <code>org-chef</code> to keep a cookbook.</dd>
<dt>doodled sketch elements</dt><dd>boxes, borders, little icons, people&#x2026;
Probably should organize these and share them too.</dd>
</dl>

<p>
I've figured out how to publish sketches by using my phone to rotate
them and sync them with my <a href="https://sketches.sachachua.com">online sketches</a>. Now I'm playing around
with my writing workflow to see if I can easily post them to my blog.
At some point, I think I'll experiment with using my phone to record
and automatically transcribe some commentary, which I can pull into
the blog post via some other Emacs Lisp code I've written. Whee!
</p>
<p>You can <a href="mailto:sacha@sachachua.com?subject=Comment%20on%20https%3A%2F%2Fsachachua.com%2Fblog%2F2022%2F07%2Ftrying-out-the-supernote-a5x%2F&body=Name%20you%20want%20to%20be%20credited%20by%20(if%20any)%3A%20%0AMessage%3A%20%0ACan%20I%20share%20your%20comment%20so%20other%20people%20can%20learn%20from%20it%3F%20Yes%2FNo%0A">e-mail me at sacha@sachachua.com</a>.</p>]]></content>
		</entry>
</feed>