1427 lines
66 KiB
Org Mode
1427 lines
66 KiB
Org Mode
#+HUGO_BASE_DIR: ./
|
||
#+HUGO_SECTION: posts
|
||
#+HUGO_AUTO_SET_LASTMOD: t
|
||
|
||
* Meta :@Meta:
|
||
* Technology :@Technology:
|
||
** TODO Audacity and the telemetry pull request :Open__source__culture:Audio__editing:Music:Drama:
|
||
:PROPERTIES:
|
||
:EXPORT_FILE_NAME: audacity-and-the-telemetry-pull-request
|
||
:EXPORT_HUGO_CUSTOM_FRONT_MATTER: :toc true
|
||
:END:
|
||
|
||
Five days ago at the time of writing, [[https://github.com/crsib][Dmitry Vedenko]] opened a Pull
|
||
Request (PR) in [[https://github.com/audacity/audacity/pull/835][Audacity's GitHub repository]] entitled [[https://github.com/audacity/audacity/pull/835][/Basic telemetry
|
||
for the Audacity/.]] About two days later, all hell broke loose. That PR
|
||
now has over 3.3 thousand downvotes and more than one thousand comments
|
||
from nearly 400 individuals. I started reading the posts shortly after
|
||
they began and kept up with them over the following days, reading every
|
||
single new post. I recognise that few people are going to feel like
|
||
wading through over 1k comments so this is my attempt to provide a
|
||
summary of the PR itself using the community's code reviews along with a
|
||
summary of the various opinions conveyed in the comments.
|
||
|
||
When I reference comments, I'll provide a footnote that includes a link
|
||
to the comment and a link to a screenshot just in case it's removed or
|
||
edited in the future.
|
||
|
||
*** Audacity's acquisition
|
||
|
||
I haven't been able to find /much/ information in this area so forgive me
|
||
if I'm scant on details.
|
||
|
||
On 30 April, a company called [[https://mu.se/][Muse Group]] acquired [[https://www.audacityteam.org/][Audacity.]] According to
|
||
[[https://mu.se][their website]], Muse is the parent company behind many musical
|
||
applications and tools. It was founded by Eugeny Naidenov just days
|
||
before it acquired Audacity. Before all of this, Eugeny Naidenov founded
|
||
[[https://www.ultimate-guitar.com/][Ultimate Guitar]] (UG) in 1998. The service grew rather quickly and now
|
||
has over 300 million users. UG acquired [[https://deanzelinsky.com/][Dean Zelinsky Guitars]] in 2012,
|
||
[[http://agilepartners.com/][Agile Partners]] in 2013, [[https://musescore.org/][MuseScore]] in 2017, and [[http://trycrescendo.com/][Crescendo]] in 2018. Muse
|
||
Group was established in 2021 and it seems as if all of the services UG
|
||
acquired were (or will be) transferred to Muse Group, as well as UG
|
||
itself. Immediately following its establishment, Muse not only acquired
|
||
Audacity but also [[https://www.staffpad.net/][StaffPad.]]
|
||
|
||
I say 30 April because that's when Muse published their [[https://mu.se/newsroom/tpost/6dhedma301-muse-group-acquires-audacity-expanding-c][press release]]
|
||
and when Martin Keary (Tantacrul) published a video entitled [[https://www.youtube.com/watch?v=RMWNvwLiXIQ][/I’m now in
|
||
charge of Audacity. Seriously./]] According to his comment,[fn:17] Martin
|
||
will help with proposing Audacity's roadmap and many of its future
|
||
features as well as working with the community. This has been his role
|
||
with MuseScore since he joined that project and he will be continuing it
|
||
here.
|
||
~-----BEGIN PERSONAL OPINION-----~
|
||
|
||
Looking at [[https://www.martinkeary.com/][his website,]] I also suspect he will play a large role in
|
||
redesigning Audacity's interface. Considering that he was instrumental
|
||
in designing [[https://www.martinkeary.com/#/ubuntu-touch-os/][the best mobile interface I've ever had the absolute
|
||
pleasure of experiencing,]] I have high hopes that this is the case.
|
||
~------END PERSONAL OPINION------~
|
||
|
||
*** Telemetry implementation
|
||
**** Implementation Basics
|
||
|
||
A few days after the acquisition, a PR was opened that adds /Basic
|
||
telemetry for the Audacity/. This implementation collects "application
|
||
opened" events and sends those to Yandex to estimate the number of
|
||
Audacity users. It also collects session start and end events, errors
|
||
for debugging, file used for import and export, OS and Audacity
|
||
versions, and the use of effects, generators, and analysis tools so they
|
||
can prioritise future improvements. Sending this data would be optional
|
||
and the user would be presented with a dialogue the first time they
|
||
launch the application after installation or after they update to the
|
||
including release. This description was mostly copied directly from [[https://github.com/audacity/audacity/pull/835#issue-629891447][the
|
||
PR description itself.]]
|
||
|
||
**** Frontend Implementation
|
||
This is fairly straightforward and a pretty standard UI for prompting
|
||
users to consent to analytics and crash logging. This section is
|
||
included because the community has strong opinions regarding the
|
||
language used and its design, but that will be discussed later. The
|
||
screenshot below is copied directly from the PR.
|
||
|
||
[[/assets/pngs/audacity-pr/consentdialogue.png]]
|
||
|
||
**** Backend Implementation
|
||
Many of the code reviews include the reviewer's personal opinion so I
|
||
will summarise the comment, provide the code block in question, and link
|
||
directly to the comment in a footnote.[fn:9]
|
||
|
||
#+BEGIN_SRC c
|
||
if (!inputFile.Write (wxString::FromUTF8 (ClientID + "\n")))
|
||
return false;
|
||
#+END_SRC
|
||
[[https://github.com/crsib/audacity/blob/c9264d2478fe2af82aeb6e2a0295b00b3a27ce53/libraries/lib-telemetry/TelemetryManager.cpp#L199-L200][Lines 199-200 of TelemetryManager.cpp]] save the user's unique client ID
|
||
to a file.[fn:8] This allows the analytics tool (in this case, Google
|
||
Analytics) to aggregate data produced by a single user.
|
||
|
||
#+BEGIN_SRC c
|
||
def_vars()
|
||
|
||
set( CURL_DIR "${_INTDIR}/libcurl" )
|
||
set( CURL_TAG "curl-7_76_0")
|
||
#+END_SRC
|
||
[[https://github.com/crsib/audacity/blob/c9264d2478fe2af82aeb6e2a0295b00b3a27ce53/cmake-proxies/libcurl/CMakeLists.txt#L3-L6][Lines 3-6 of CMakeLists.txt]] "vendor in" libcurl.[fn:10] This is when an
|
||
application directly includes sources for a utility rather than making
|
||
use utilities provided by the system itself.
|
||
|
||
#+BEGIN_SRC c
|
||
ExternalProject_Add(curl
|
||
PREFIX "${CURL_DIR}"
|
||
INSTALL_DIR "${CURL_DIR}"
|
||
GIT_REPOSITORY https://github.com/curl/curl
|
||
GIT_TAG ${CURL_TAG}
|
||
GIT_SHALLOW Yes
|
||
CMAKE_CACHE_ARGS ${CURL_CMAKE_ARGS}
|
||
)
|
||
#+END_SRC
|
||
[[https://github.com/crsib/audacity/blob/c9264d2478fe2af82aeb6e2a0295b00b3a27ce53/cmake-proxies/libcurl/CMakeLists.txt#L29-L36][Lines 29-36 of CMakeLists.txt]] add curl as a remote dependency.[fn:11]
|
||
This means that the machine building Audacity from its source code has
|
||
to download curl during that build.
|
||
|
||
#+BEGIN_SRC c
|
||
S.Id (wxID_NO).AddButton (rejectButtonTitle);
|
||
S.Id (wxID_YES).AddButton (acceptButtonTitle)->SetDefault ();
|
||
#+END_SRC
|
||
[[https://github.com/crsib/audacity/blob/c9264d2478fe2af82aeb6e2a0295b00b3a27ce53/src/telemetry/TelemetryDialog.cpp#L93-L94][Lines 93-94 of TelemetryDialog.cpp]] add buttons to the dialogue asking
|
||
the user whether they consent to data collection.[fn:12] ~SetDefault~
|
||
focuses the button indicating that the user does consent. This means
|
||
that if the user doesn't really look at the dialogue and presses
|
||
Spacebar or Enter, or if they do so accidentally by simply bumping the
|
||
key, they unintentionally consent to data collection. If the user
|
||
desires, this can later be changed in the settings menu. However, if
|
||
they weren't aware what they were consenting to /or that they did
|
||
consent/, they won't know to go back and opt out.
|
||
|
||
There are other problems with the code that include [[https://github.com/audacity/audacity/pull/835#discussion_r628816050][simple mistakes,]]
|
||
[[https://github.https://github.com/audacity/audacity/pull/835#discussion_r628774985][styling that's inconsistent with the rest of the project,]] [[https://github.com/audacity/audacity/pull/835#discussion_r628500849][unhandled
|
||
return values resulting in skewed data,]] [[https://github.com/audacity/audacity/pull/835#discussion_r628792423][use of inappropriate functions,]]
|
||
and [[https://github.com/audacity/audacity/pull/835#discussion_r628818054][spelling errors in the comments.]] I believe these are less important
|
||
than those above so they won't be discussed.
|
||
|
||
*** Community opinions
|
||
There were many strong opinions regarding both the frontend and backend
|
||
implementations of this PR, from the wording of the dialogue and
|
||
highlighting the consent button to devices running something other than
|
||
Windows and macOS not being able to send telemetry and thus skewing the
|
||
data that /was/ collected.
|
||
|
||
**** Opinions on the frontend
|
||
|
||
Really, the only frontend here is the consent dialogue. However, there
|
||
are /many/ comments about it, the most common of which is probably that
|
||
the wording is not only too vague[fn:13] but also inaccurate.[fn:14] The
|
||
assertion that Google Analytics are not anonymous and any data sent can
|
||
be trivially de-anonymised (or de-pseudonymised) is repeated many times
|
||
over. Below are a few links to comments stating such. I searched for the
|
||
term /"anonymous"/, copied relevant links, and stopped when my scrollbar
|
||
reached halfway down the page.
|
||
|
||
- [[https://github.com/audacity/audacity/pull/835#discussion_r628156527][r628156527]]
|
||
- [[https://github.com/audacity/audacity/pull/835#issuecomment-833969780][833969780]]
|
||
- [[https://github.com/audacity/audacity/pull/835#issuecomment-833969933][833969933]]
|
||
- [[https://github.com/audacity/audacity/pull/835#discussion_r627995927][r627995927]]
|
||
- [[https://github.com/audacity/audacity/pull/835#issuecomment-834358022][834358022]]
|
||
- [[https://github.com/audacity/audacity/pull/835#issuecomment-834377549][834377549]]
|
||
- [[https://github.com/audacity/audacity/pull/835#issuecomment-834382007][834382007]]
|
||
- [[https://github.com/audacity/audacity/pull/835#issuecomment-834385463][834385463]]
|
||
- [[https://github.com/audacity/audacity/pull/835#issuecomment-834405825][834405825]]
|
||
- [[https://github.com/audacity/audacity/pull/835#issuecomment-834531779][834531779]]
|
||
- [[https://github.com/audacity/audacity/pull/835#issuecomment-834546874][834546874]]
|
||
- [[https://github.com/audacity/audacity/pull/835#issuecomment-834638000][834638000]]
|
||
|
||
The next most pervasive comment is regarding the consent buttons at the
|
||
bottom of the dialogue where users opt in or out.[fn:15] Many individuals call
|
||
this design a /dark pattern/. Harry Brignull, a UX specialist focusing on
|
||
deceptive interface practises, describes dark patterns as [[https://www.darkpatterns.org/][/tricks used
|
||
in websites and apps that make you do things that you didn't mean to/.]]
|
||
The dark pattern in this situation is the opt-in button being
|
||
highlighted. Many community members assert that users will see the big
|
||
blue button and click it without actually reading the dialogue's
|
||
contents. They just want to record their audio and this window is a
|
||
distraction that prevents them from doing so; it needs to get out of the
|
||
way and the quickest way to dismiss it is clicking that blue button.
|
||
Below is a list of some comments criticising this design.
|
||
|
||
- [[https://github.com/audacity/audacity/pull/835#issuecomment-834286641][834286641]]
|
||
- [[https://github.com/audacity/audacity/pull/835#issuecomment-834358022][834358022]]
|
||
- [[https://github.com/audacity/audacity/pull/835#issuecomment-834399813][834399813]]
|
||
- [[https://github.com/audacity/audacity/pull/835#issuecomment-834479968][834479968]]
|
||
- [[https://github.com/audacity/audacity/pull/835#issuecomment-835250737][835250737]]
|
||
- [[https://github.com/audacity/audacity/pull/835#issuecomment-835253882][835253882]]
|
||
- [[https://github.com/audacity/audacity/pull/835#issuecomment-835291066][835291066]]
|
||
- [[https://github.com/audacity/audacity/pull/835#issuecomment-835445481][835445481]]
|
||
|
||
Another issue that was brought up by a couple of individuals was the
|
||
lack of a privacy policy.[fn:16] The consent dialogue links to one, but, at the
|
||
time of writing, one does not exist at [[https://www.audacityteam.org/contact/privacy-policy/][the provided URL.]] I have [[https://web.archive.org/web/20210510012924/https://www.audacityteam.org/contact/privacy-policy/][archived
|
||
the state of the page]] in case that changes in the future.
|
||
|
||
**** Opinions on the backend
|
||
|
||
#+BEGIN_SRC c
|
||
if (!inputFile.Write (wxString::FromUTF8 (ClientID + "\n")))
|
||
return false;
|
||
#+END_SRC
|
||
|
||
The issue many individuals take with this snippet is saving the
|
||
~ClientID~. Say an individual has an odd file that causes Audacity to
|
||
crash any time they try to open it. Say they attempt to open it a
|
||
hundred times. Without giving the client a unique ID, it could look like
|
||
there are 100 people having an issue opening a file instead of just the
|
||
one. However, by virtue of each installation having an entirely unique
|
||
ID, this telemetry /is not anonymous/. Anonymity would be sending
|
||
statistics in such a way that connecting those failed attempts to a
|
||
single user would be impossible. At best, this implementation is
|
||
/pseudonymous/ because the client is given a random ID, you don't have to
|
||
sign in with an account or something.
|
||
|
||
#+BEGIN_SRC c
|
||
def_vars()
|
||
|
||
set( CURL_DIR "${_INTDIR}/libcurl" )
|
||
set( CURL_TAG "curl-7_76_0")
|
||
#+END_SRC
|
||
|
||
Timothe Litt's comment gives a good description of why "vendoring in"
|
||
libcurl is a bad idea[fn:19] and Tyler True's comment gives a good
|
||
overview of the pros and cons of doing so.[fn:18] Many people take issue
|
||
with this /specifically/ because it's libcurl. Security flaws in it are
|
||
/very/ common and Audacity's copy would need to be /manually/ kept up to
|
||
date with every upstream release to ensure none of its vulnerabilities
|
||
can be leveraged to compromise users. If the Audacity team was going to
|
||
stay on top of all of the security fixes, they would need to release a
|
||
new version every week or so.
|
||
|
||
#+BEGIN_SRC c
|
||
ExternalProject_Add(curl
|
||
PREFIX "${CURL_DIR}"
|
||
INSTALL_DIR "${CURL_DIR}"
|
||
GIT_REPOSITORY https://github.com/curl/curl
|
||
GIT_TAG ${CURL_TAG}
|
||
GIT_SHALLOW Yes
|
||
CMAKE_CACHE_ARGS ${CURL_CMAKE_ARGS}
|
||
)
|
||
#+END_SRC
|
||
The problem with downloading curl at build-time is that it's simply
|
||
disallowed for many Linux- and BSD-based operation systems. When a
|
||
distribution builds an application from source, its build dependencies
|
||
are often downloaded ahead of time and, as a security measure, the build
|
||
machine is cut off from the internet to prevent any interference.
|
||
Because this is disallowed, the build will fail and the application
|
||
won't be available on those operation systems.
|
||
|
||
Note, however, that these build machines would have the option to
|
||
disable telemetry at build-time. This means the machine wouldn't attempt
|
||
to download curl from GitHub and the build would succeed but, again,
|
||
telemetry would be disabled for anyone not on Windows or macOS. This
|
||
defeats the whole purpose of adding telemetry in the first place.
|
||
|
||
#+BEGIN_SRC c
|
||
S.Id (wxID_NO).AddButton (rejectButtonTitle);
|
||
S.Id (wxID_YES).AddButton (acceptButtonTitle)->SetDefault ();
|
||
#+END_SRC
|
||
|
||
There was a lot of feedback about the decision to highlight the consent
|
||
button but that was mentioned up in the frontend section; I won't rehash
|
||
it here.
|
||
|
||
**** Broader and particularly well-structured comments
|
||
These are simply some comments I feel deserve particular attention.
|
||
|
||
From SndChaser...
|
||
- [[https://github.com/audacity/audacity/pull/835#issuecomment-834037351][834037351]]
|
||
-
|
||
|
||
*** The Audacity team's response
|
||
|
||
|
||
-----------------
|
||
|
||
*** The privacy policy modification
|
||
|
||
https://github.com/audacity/audacity/issues/1213#issuecomment-875274890
|
||
|
||
** TODO Catchy title about Supernote being "the new paper" :Supernote:Writing:Productivity:Organisation:
|
||
:PROPERTIES:
|
||
:EXPORT_FILE_NAME: something-about-supernote
|
||
:EXPORT_HUGO_CUSTOM_FRONT_MATTER: :toc true
|
||
:END:
|
||
|
||
I like writing things down. I like the feel of the pen (preferably a
|
||
fountain pen) gliding smoothly over the paper, that nice solid feeling
|
||
of the tip against the table, seeing the ink dry as it flows from the
|
||
nib, accidentally swiping my hand through it before it's finished and
|
||
smearing a bit of ink across the page, then cursing under my breath as I
|
||
dab it up with a handkerchief or a napkin or something else nearby. I
|
||
also love that writing things by hand [[https://journals.sagepub.com/doi/abs/10.1177/0956797614524581][has an impact on memory and
|
||
improves retention.]]
|
||
|
||
*** The problem
|
||
Unfortunately, I don't love keeping up with that paper. Across many
|
||
different classes, even with dedicated folders for each one, something
|
||
important inevitably gets lost. Notebooks are also bulky and can take up
|
||
a lot of space. I tried [[https://bulletjournal.com/][bullet journalling]] for about a month earlier
|
||
this year and, while the process was enjoyable, the maintenance was not.
|
||
My brain moves faster than my pen (even though I have terrible
|
||
handwriting) and I inevitably forget letters or even whole words. This
|
||
is a problem while writing in pen because white-out looks ugly and I
|
||
dislike wasting whole pages because of a couple mistakes.
|
||
|
||
The obvious solution here is to get an iPad with an Apple Pen, right?
|
||
Right??
|
||
|
||
Wrong because Apple bad.[fn:2]
|
||
|
||
*** The solution
|
||
Enter the world of ... what are they even called? E-ink notebooks? Paper
|
||
tablets? E-R/W?[fn:1] Do they even have a "device category" yet? I don't
|
||
know, but they solve my problem in a wonderful way.
|
||
|
||
As the names suggest, these are devices that can /usually/ open and read
|
||
e-books (EPUBs, PDFs, etc.), annotate them, and create standalone pages
|
||
of notes as if they were full notebooks. The most well-known of these
|
||
devices is likely the [[https://remarkable.com/][reMarkable.]] They had a [[https://venturebeat.com/2019/10/08/remarkable-raises-15-million-to-bring-its-e-paper-tablets-to-more-scribblers/][hugely successful
|
||
crowdfunding campaign]] and produced the reMarkable 1, followed by [[https://blog.remarkable.com/remarkable-2-the-next-generation-paper-tablet-91b47d0080cb][the
|
||
reMarkable 2 in 2020.]] There are a few devices like these by now but
|
||
we'll look at the reMarkable first.
|
||
|
||
*** The reMarkable
|
||
This device boasts all of the features I was looking for. It renders
|
||
digital content, from books and manuals to comics and manga, allows you
|
||
to mark those documents up as you would if they were physical media,
|
||
create full notebooks of hand written text, organise them, search, and,
|
||
if your handwriting is legible enough (mine certainly is not), perform
|
||
OCR on your notes and email a transcription to yourself. It even runs
|
||
Linux and the developers have opened SSH up so you can remote in and
|
||
tinker with it as much as you like. Because of this, there's a pretty
|
||
awesome [[https://github.com/reHackable/awesome-reMarkable][community of people creating third-party tools and integrations]]
|
||
that add even further functionality. My favourite is probably [[https://github.com/bordaigorl/rmview][rMview,]] a
|
||
really fast VNC client for the reMarkable that allows you to view your
|
||
device's screen on any computer.
|
||
|
||
After watching all of [[https://www.youtube.com/c/MyDeepGuide][MyDeepGuide's]] [[https://www.youtube.com/playlist?list=PLsSI9-gaSSmiXwb7Vjk5Vb-nB41UTnrXd][extensive playlist on the
|
||
reMarkable,]] however, I decided to go with a different product.
|
||
|
||
*** Enter the Supernote A5X
|
||
The [[https://www.supernote.com/#/product?type=SN-A5-X][Supernote A5X]] has all of the basic features the reMarkable has:
|
||
reading documents, writing notes, and organising your content. Its
|
||
implementation, on the other hand, seems to be much more polished. It
|
||
also lacks some features from the reMarkable while adding others.
|
||
|
||
*** Operating System
|
||
While the reMarkable runs Codex,[fn:3] a /"custom Linux-based OS
|
||
optimised for low-latency e-paper"/, the Supernote just runs Android.
|
||
There are both benefits and detriments to this; on one hand, they're
|
||
running all of Android, bloated that it is, on a very lightweight
|
||
tablet. On the other, they don't have to develop and maintain a custom
|
||
operating system. This allows them to focus on other aspects that are
|
||
arguably more important so I don't actually mind that it runs Android.
|
||
|
||
The only place that Android stands out is in system operations; file
|
||
transfer uses MTP and, when you swipe down from the top of the device, a
|
||
small bar appears similar to what was in early Android. This lets you
|
||
change WiFi networks, sync with the Supernote Cloud, take a screenshot,
|
||
search, and access the system settings. Nothing else about the device
|
||
really screams Android to me.
|
||
|
||
*** Community
|
||
I don't usually browse Reddit but [[https://old.reddit.com/r/Supernote/][the Supernote community]] there is
|
||
fascinating. I haven't looked around enough to know exactly what his
|
||
relationship is with the company, but one of the members, [[https://old.reddit.com/user/hex2asc][u/hex2asc,]]
|
||
seems to represent Supernote in something of an official capacity. He's
|
||
incredibly active and usually responds to posts and questions within a
|
||
day or two.
|
||
|
||
Before I purchased a Supernote, [[https://old.reddit.com/r/Supernote/comments/lhffyd/sync_targets_open_document_formats_and_crossnote/][I wrote a post]] asking about a couple of
|
||
things that concerned me: sync targets, open document formats, and
|
||
cross-note links. I don't really plan to write full documents on the
|
||
device but having the option to do so would still be nice. The other
|
||
features are absolutely killer for me as I would like to maintain a
|
||
Zettelkasten (I wrote about [[/vim-as-a-markdown-editor/][using Vim to do so]] last year but didn't end
|
||
up sticking with it) and manage document synchronisation with my own
|
||
Nextcloud server. The community was quick to respond and confirm that
|
||
Zettelkasten functionality would be implemented soon™. u/hex2asc
|
||
responded /the day after/ and said that WebDAV would be supported but not
|
||
earlier than May (September update: it's still not supported), ODF would
|
||
likely not be supported, and cross-note links were definitely a
|
||
possibility. Another community member has been avidly following the
|
||
subreddit and even put together an [[https://app-rm.roadmunk.com/publish/03e6dca3d769e2b7015f7f48a649cb3f75f44d9e][unofficial roadmap.]]
|
||
|
||
*** Interfaces
|
||
|
||
**** Home & Organisation
|
||
|
||
***** TODO Record very short videos
|
||
|
||
**** Settings
|
||
|
||
***** TODO Record very short videos
|
||
|
||
**** Writing & Annotating
|
||
|
||
The following images are screenshots of the full page above with the
|
||
possible UI variations while reading a book. This first one is default,
|
||
with the editing bar at the top. It is exactly the same as what's
|
||
displayed on the blank pages for hand writing full notes. From left to
|
||
right is the Table of Contents toggle, the pen tools (fineliner,
|
||
"fountain" pen,[fn:5] and highlighter), the erasers, lasso select tool,
|
||
undo/redo, context menu, palm rejection toggle, previous page, goto
|
||
page, next page, and exit.
|
||
|
||
[[/assets/pngs/supernote-reader-default.png]]
|
||
|
||
You can hold your finger on that bar and drag it down to detach it from
|
||
the top. The default width exposes all the tools without whitespace. You
|
||
can move it around the screen by dragging the circle with a straight
|
||
line through the middle on the far left.
|
||
|
||
[[/assets/pngs/supernote-reader-medium.png]]
|
||
|
||
If you tap that circle, the width shrinks and everything except the
|
||
pens, erasers, and undo/redo buttons are hidden. It can be dragged the
|
||
same was as in the previous image and tapping that circle will expand
|
||
the bar again.
|
||
|
||
[[/assets/pngs/supernote-reader-small.png]]
|
||
|
||
The last mode is with the bar completely hidden. You achieve this just
|
||
by dragging it to the right edge of the screen. Once hidden, you can
|
||
swipe right to left from the edge and it will be revealed flush with the
|
||
right edge.
|
||
|
||
[[/assets/pngs/supernote-reader-minimal.png]]
|
||
|
||
*** Experience
|
||
**** Reading content
|
||
I love e-ink. I think it looks beautiful and would love to have an e-ink
|
||
monitor.[fn:4] That said, the Supernote has an especially nice display
|
||
with 226 PPI (pixels per inch). The image below was taken with my
|
||
phone's camera so it's not very good. However, if you zoom in a bit, you
|
||
can see that the curved edges of some letters are /slightly/ pixellated.
|
||
Viewing with my naked eye at a comfortable distance, it does look better
|
||
/to me/ than some of my print books, however.
|
||
|
||
[[/assets/pngs/supernote-resolution.png]]
|
||
/At the moment,/ I am pretty disappointed with Table of Contents detection
|
||
for ePUBs. A great many of my books seem to use a legacy ToC format that
|
||
the Supernote sees and tries/fails to read before attempting to read the
|
||
more up-to-date one. This is easily remedied by editing the ePUB in
|
||
[[https://calibre-ebook.com/][Calibre,]] going to Tools → Upgrade Book Internals → Remove the legacy
|
||
Table of Contents in NCX format. You might need to make a small change
|
||
to one of the HTML files and revert it before the save button is
|
||
enabled. After that, just copy it back over to the Supernote and
|
||
everything should work properly.
|
||
|
||
**** Writing notes
|
||
I write notes as often if not /more/ often than I read and annotate books.
|
||
It's the main reason I purchased the device and I love the experience.
|
||
The Supernote doesn't /really/ feel like paper despite what their
|
||
marketing materials claim, though it doesn't feel /bad/ either. It's hard
|
||
to describe but I would say it's something like writing with a
|
||
rollerball pen on high-quality paper with a marble counter underneath:
|
||
incredibly smooth with but a little bit of texture so it doesn't feel
|
||
like writing on a glass display.
|
||
|
||
While writing latency[fn:6] is noticeable, I really don't have a huge
|
||
issue with it. I write very quickly but find that the slight latency
|
||
actually makes writing /more/ enjoyable. It sounds weird and I'm not sure
|
||
why, but I /really/ like writing on the Supernote; it's wonderfully
|
||
smooth, pressure-sensitive, the latency makes things interesting, and
|
||
[[https://supernote.com/#/part?id=SP-04][the Heart of Metal pen]] feels good in my hand.
|
||
|
||
**** Surfacing Content
|
||
While organisation is done using a regular filesystem hierarchy, the
|
||
Supernote does have other ways to search for and surface your notes. As
|
||
you're writing, you can use the lasso select tool and encircle a word. A
|
||
little dialogue pops up and gives you a few buttons for things you can
|
||
do with that selection: copy, move to another page, cut, add it to the
|
||
Table of Contents, or mark it as a key word. If you select the key word
|
||
icon, the Supernote does some incredible OCR[fn:7] on it and displays a
|
||
dialogue where you can add it to the note file as a tag. This dialogue
|
||
allows you to edit the word before adding it just in case the OCR was
|
||
wonky. Even with my terrible handwriting, I've found that it works very
|
||
well and I rarely have to make edits.
|
||
|
||
*** TODO Ping Isi and Volpeon when finished
|
||
** TODO Migrating repositories between git hosts
|
||
** TODO A perfect email setup (for me) :Email:Workflow:
|
||
:PROPERTIES:
|
||
:EXPORT_FILE_NAME: a-perfect-email-setup-for-me
|
||
:EXPORT_HUGO_CUSTOM_FRONT_MATTER: :toc true
|
||
:END:
|
||
|
||
I've never been satisfied with any of the email clients most people use.
|
||
I've tried [[https://www.thunderbird.net/en-GB/][Thunderbird,]] [[https://wiki.gnome.org/Apps/Evolution][Evolution,]] [[https://getmailspring.com/][Mailspring,]] [[https://support.apple.com/mail][Mail.app,]] [[https://roundcube.net/][Roundcube,]]
|
||
[[https://sogo.nu/][SOGo,]] [[https://wiki.gnome.org/Apps/Geary][Geary,]] and /many/ more. /None/ of them handle multiple accounts
|
||
particularly well because all of the emails associated with that account
|
||
are bound within it. Sure, you can make a new folder somewhere called
|
||
~TODO~ and move all of your actionable emails to that folder but, when you
|
||
go to move actionable emails from /another/ account into that folder,
|
||
you'll likely find that the client simply doesn't let you. If it does,
|
||
when you reply, it will likely be sent from the wrong account. This is a
|
||
limitation of the IMAP protocol; everything is /managed/ locally but
|
||
changes are pushed to the remote server and mixing things the way I want
|
||
leads to broken setups.
|
||
|
||
Before I go any further, these are a few characteristics of my ideal
|
||
email tool.
|
||
|
||
- Support for multiple accounts (obviously)
|
||
- /Native desktop application/ (*not* [[https://github.com/electron/electron][Electron]])
|
||
- Has stellar keyboard shortcuts
|
||
- Doesn't require internet connectivity (other than downloading and
|
||
sending of course)
|
||
- Organisation can be done with tags
|
||
|
||
*** Why tags?
|
||
Because they're better. Hierarchies are useful for prose and code but
|
||
not for files, emails, notes, or anything where an item may fit within
|
||
multiple categories. Imagine you get an email from your Computer Science
|
||
professor that includes test dates, homework, and information about
|
||
another assignment. In that same email, he asks every student to reply
|
||
with something they learned from the previous class as a form of
|
||
attendance. In a hierarchy, the best place for this might just be a ~TODO~
|
||
folder /even though/ it would also fit under ~School~, ~CS~, ~Dates~, ~To read~,
|
||
and ~Homework~. Maybe you have a few minutes and want to clear out some
|
||
emails that don't require any interaction. In a tag-based workflow, this
|
||
would be a good time to open ~To read~, get that email out of the way, and
|
||
remove the ~To read~ tag. It would still show up under the other tags so
|
||
you can find it later and take the time to fully answer the professor's
|
||
question, add those dates to your calendar, and add the homework
|
||
assignments to your ~TODO~ list. Hierarchies can be quite cumbersome to
|
||
work with, especially when one folder ends up getting all the data. Tags
|
||
ensure that you only see what you want when you want it. Tags are more
|
||
efficient and they will remain my organisation system of choice.
|
||
|
||
*** The tools
|
||
In short, the tools we will be using are...
|
||
+ [[https://isync.sourceforge.io/mbsync.html][~mbsync~]] to download our emails
|
||
+ [[https://notmuchmail.org/][~notmuch~,]] the primary way emails will be organised
|
||
+ [[https://afew.readthedocs.io/en/latest/][~afew~]] to apply initial ~notmuch~ tags based on subject, sender, recipient, etc.
|
||
+ [[https://neomutt.org/][NeoMutt]] to interact with those emails, reply, compose, add/remove
|
||
tags, etc.
|
||
+ [[https://marlam.de/msmtp/][~msmtp~]] for relaying our replies and compositions to our mail provider
|
||
|
||
Yes, it's a lot. Yes, it's time-consuming to set up. Yes, it's worth it
|
||
(in my opinion).
|
||
|
||
*** ~mbsync~
|
||
As I said above, IMAP is limiting; we need to use some other method of
|
||
downloading our emails. There's an awesome piece of software called
|
||
[[https://isync.sourceforge.io/mbsync.html][mbsync]] which is built for exactly this purpose. Its configuration
|
||
can be rather daunting if you have as many accounts as I do (19) but
|
||
it's not /terrible/.
|
||
|
||
The following sections are named *Near*, *Far*, and *Sync*. Near and Far are
|
||
terms mbsync uses to profile /how/ your emails are stored, /where/ they're
|
||
stored, and how to interact with them. In this guide, Far will our mail
|
||
provider's IMAP server and Near will be our local Maildir.
|
||
|
||
**** Far
|
||
#+BEGIN_SRC text
|
||
IMAPAccount amo_ema
|
||
Host imap.nixnet.email
|
||
CertificateFile /etc/ssl/certs/ca-certificates.crt
|
||
SSLType STARTTLS
|
||
User amolith@nixnet.email
|
||
PassCmd "secret-tool lookup Title amolith@nixnet.email"
|
||
|
||
IMAPStore amo_ema-remote
|
||
Account amo_ema
|
||
#+END_SRC
|
||
|
||
**** Near
|
||
#+BEGIN_SRC text
|
||
MaildirStore amo_ema-local
|
||
SubFolders Verbatim
|
||
Path ~/new-mail/amo_ema/
|
||
Inbox ~/new-mail/amo_ema/INBOX/
|
||
#+END_SRC
|
||
|
||
In the first block, ~localrepository~ and ~remoterepository~ tell OfflineIMAP where
|
||
to look for your emails. ~use_exa-local~ is an arbitrary naming scheme I use to
|
||
differentiate between the various local and remote accounts. It can easily be
|
||
swapped with something else.
|
||
|
||
**** Sync
|
||
#+BEGIN_SRC text
|
||
Channel amo_ema
|
||
Far :amo_ema-remote:
|
||
Near :amo_ema-local:
|
||
SyncState *
|
||
Patterns *
|
||
Create Both
|
||
#+END_SRC
|
||
|
||
The repository sections describe how the emails are stored or retrieved.
|
||
In the ~local~ block, you'll notice that the type is ~Maildir~. In this
|
||
format, each email is given a unique filename and stored in a hierarchy
|
||
of folders within your account. This is often how your emails are stored
|
||
on your provider's mail server.
|
||
~pythonfile~ is used here to authenticate with the remote server. This can
|
||
be complicated and depends /entirely/ on how you manage your passwords. I
|
||
use [[https://keepassxc.org/][KeePassXC]] and love it. When I set OfflineIMAP up, however, it didn't
|
||
have ~libsecret~ compatibility. This would have made setup significantly
|
||
easier but, as it already just works™, I don't really see a reason to
|
||
change it.
|
||
|
||
This new feature allows ~libresecret~-based applications to query
|
||
KeePassXC for your passwords or store them there on your behalf. CLI/TUI
|
||
applications that need a secure mechanism for background authentication
|
||
can use ~secret-tool lookup Title "TITLE_OF_PASSWORD"~ as the password
|
||
command. See [[https://github.com/keepassxreboot/keepassxc/pull/2726][the pull request]] for more details. Because this wasn't a
|
||
feature when I first set it up, I put my passwords in plaintext files
|
||
and encrypted them with the GPG key stored on my YubiKey. As long as my
|
||
key is plugged in, OfflineIMAP can authenticate and download all my
|
||
emails just fine. The process for using a GPG key /not/ stored on a
|
||
hardware token is pretty much the same and I'll talk about that process
|
||
instead.
|
||
|
||
These are the contents of my ~~/.offlineimap.py~.
|
||
|
||
#+BEGIN_SRC python
|
||
#! /usr/bin/env python2
|
||
from subprocess import check_output
|
||
def get_pass(account):
|
||
return check_output(["gpg", "-dq", f" ~/.mail_pass/{account}.gpg"]).strip("\n")
|
||
#+END_SRC
|
||
|
||
This runs ~gpg -dq ~/.mail_pass/use_exa.gpg~ then strips the newline
|
||
character before returning it to OfflineIMAP. ~-d~ tells GPG that you're
|
||
passing it a file you want decrypted and ~-q~ tells it not to give any
|
||
output other than the file's contents. For a setup that works with this
|
||
Python script, put your passwords in plaintext files with the account
|
||
name as the file name (e.g. ~use_exa~). You'll then encrypt it with ~gpg
|
||
-er <YOUR_KEY_ID> use_exa~. Running ~gpg -dq use_exa.gpg~ should display
|
||
your password. Repeat for every account and store the resulting files in
|
||
~~/.mail_pass/~.
|
||
|
||
The other option, ~sync_deletes~, is whether or not to delete remote
|
||
emails that have been deleted locally. I enabled that because I want to
|
||
have easy control over how much remote storage is used.
|
||
|
||
Here's the next block again so you don't have to scroll up:
|
||
|
||
#+BEGIN_SRC text
|
||
[Repository use_exa-remote]
|
||
type = IMAP
|
||
remotehost = imap.example.com
|
||
starttls = yes
|
||
ssl = no
|
||
remoteport = 143
|
||
remoteuser = user@example.com
|
||
remotepasseval = get_pass("use_exa")
|
||
auth_mechanisms = GSSAPI, XOAUTH2, CRAM-MD5, PLAIN, LOGIN
|
||
maxconnections = 1
|
||
createfolders = True
|
||
sync_deletes = yes
|
||
#+END_SRC
|
||
|
||
This one's pretty self-explanatory. ~type~, ~remotehost~, ~starttls~, ~ssl~, and
|
||
~remoteport~ should all be somewhere in your provider's documentation.
|
||
~remoteuser~ is your email address and ~remotepasseval~ is the function that
|
||
will return your password and allow OfflineIMAP to authenticate. You'll
|
||
want enter the name of your password file without the ~.gpg~ extension;
|
||
the script takes care of adding that. Leave ~auth_mechanisms~ alone and
|
||
the same for ~maxconnections~ unless you know your provider won't rate
|
||
limit you or something for opening multiple connections. ~sync_deletes~ is
|
||
the same as in the previous block.
|
||
|
||
Copy those three blocks for as many accounts as you want emails
|
||
downloaded from. I have 510 lines just for ~Account~ and ~Repository~ blocks
|
||
due to the number of address I'm keeping track of.
|
||
|
||
*** ~notmuch~
|
||
[[https://notmuchmail.org/][~notmuch~]] is /a fast, global-search, and tag-based email system/. This
|
||
what does all of our organisation as well as what provides the "virtual"
|
||
mailboxes NeoMutt will display later on. Configuration is incredibly
|
||
simple. This file goes in ~~/.notmuch-config~.
|
||
|
||
#+BEGIN_SRC text
|
||
[database]
|
||
path=/home/user/mail/
|
||
|
||
[user]
|
||
name=Amolith
|
||
primary_email=user@example.com
|
||
|
||
[new]
|
||
tags=unread;new;
|
||
ignore=Trash;
|
||
|
||
[search]
|
||
exclude_tags=deleted;spam;
|
||
|
||
[maildir]
|
||
synchronize_flags=true
|
||
#+END_SRC
|
||
|
||
First section is the path to where all of your archives are, the ~[user]~
|
||
section is where you list all of your accounts, ~[new]~ adds ~tags~ to mail
|
||
notmuch hasn't indexed yet and ignores indexing the ~Trash~ folder, and
|
||
~[search]~ ignores mail tagged with ~deleted~ or ~spam~. The final section
|
||
tells ~notmuch~ to add maildir flags which correspond with ~notmuch~ tags.
|
||
These flags will be synced to the remote server the next time
|
||
OfflineIMAP runs and things will be somewhat organised in your webmail
|
||
interface.
|
||
|
||
After creating the configuration file, run ~notmuch new~ and wait for all
|
||
of your mail to be indexed. This could take a short amount of time or it
|
||
could take minutes up to an hour, depending on how many emails you have.
|
||
After it's finished, you'll be able to run queries and see matching
|
||
emails:
|
||
|
||
#+BEGIN_SRC text
|
||
$ notmuch search from:user@example.com
|
||
thread:0000000000002e9d December 28 [1/1] Example User; Random subject that means nothing
|
||
#+END_SRC
|
||
|
||
This is not terribly useful in and of itself because you can't read it
|
||
or reply to it or anything. That's where the Mail User Agent (MUA) comes
|
||
in.
|
||
|
||
*** ~afew~
|
||
[[https://afew.readthedocs.io/en/latest/][~afew~]] is /an initial tagging script for notmuch/. After calling ~notmuch
|
||
new~, ~afew~ will add tags based on headers such as ~From:~, ~To:~, ~Subject:~,
|
||
etc. as well as handle killed threads and spam. The official [[https://afew.readthedocs.io/en/latest/quickstart.html][quickstart
|
||
guide]] is probably the best resource on getting started but I'll include
|
||
a few tips here as well.
|
||
|
||
*** NeoMutt
|
||
*** ~msmtp~
|
||
~msmtp~ is what's known as a /Mail Transfer Agent/ (MTA). You throw it an
|
||
email and it will relay that to your mail provider's SMTP server so it
|
||
can have the proper headers attached for authentication, it can be sent
|
||
from the proper domain, etc. All the necessary security measures can be
|
||
applied that prevent your email from going directly to spam or from
|
||
being rejected outright.
|
||
~msmtp~'s configuration is also fairly simple if a bit long, just like
|
||
OfflineIMAP's.
|
||
|
||
#+BEGIN_SRC text
|
||
# Set default values for all following accounts.
|
||
defaults
|
||
|
||
# Use the mail submission port 587 instead of the SMTP port 25.
|
||
port 587
|
||
|
||
# Always use TLS.
|
||
tls on
|
||
#+END_SRC
|
||
|
||
This section just sets the defaults. It uses port 587 (STARTTLS) for all
|
||
SMTP servers unless otherwise specified and enables TLS.
|
||
|
||
#+BEGIN_SRC text
|
||
account user@example.com
|
||
host smtp.example.com
|
||
from user@example.com
|
||
auth on
|
||
user user@example.com
|
||
passwordeval secret-tool lookup Title "user@example.com"
|
||
#+END_SRC
|
||
|
||
This section is where things get tedious. When passing an email to
|
||
~msmtp~, it looks at the ~From:~ header and searches for a block with a
|
||
matching ~from~ line. If it finds one, it will use those configuration
|
||
options to relay the email. ~host~ is simply the SMTP server of your mail
|
||
provider, sometimes this is ~mail.example.com~, ~smtp.example.com~, etc.
|
||
I've already explained ~from~, ~auth~ simply says that a username and
|
||
password will have to be provided, ~user~ is that username, and
|
||
~passwordeval~ is a method to obtain the password.
|
||
|
||
When I got to configuring ~msmtp~, [[https://keepassxc.org/][KeePassXC]] had just released their
|
||
~libsecret~ integration and I wanted to try it. ~secret-tool~ is a command
|
||
line tool used to store and retrieve passwords from whatever keyring
|
||
you're using. I think KDE has ~kwallet~ and GNOME has ~gnome-keyring~ if
|
||
you already have those set up and want to use them; the process should
|
||
be quite similar regardless.
|
||
|
||
As mentioned above ~secret-tool~ stores and retrieves passwords. For
|
||
retrieval, it expects the command to look like this.
|
||
|
||
#+BEGIN_SRC text
|
||
secret-tool lookup {attribute} {value} ...
|
||
#+END_SRC
|
||
|
||
I don't know what ~kwallet~ and ~gnome-keyring~'s attributes are but
|
||
this can be used with KeePassXC by specifying the ~Title~ attribute. If
|
||
the password to your email account is stored in KeePassXC with the
|
||
address as the entry title, you can retrieve it by simply running...
|
||
|
||
#+BEGIN_SRC text
|
||
secret-tool lookup Title "user@example.com"
|
||
#+END_SRC
|
||
|
||
If you have a different naming system, you'll have to experiment and try
|
||
different things; I don't know what KeePassXC's other attributes are so
|
||
I can't give other examples.
|
||
|
||
#+BEGIN_SRC text
|
||
passwordeval gpg -dq ~/.mail_pass/use_exa.gpg
|
||
#+END_SRC
|
||
|
||
Now that the whole block is assembled, copy/paste/edit for as many
|
||
accounts as you want to send email from.
|
||
|
||
*** Summary
|
||
|
||
*** TODO Pong fluffy when finished
|
||
|
||
** TODO Making yourself overly available
|
||
|
||
*** References
|
||
[[https://www.nytimes.com/2021/03/05/opinion/ezra-klein-podcast-cal-newport.html?showTranscript=1][Stop. Breathe. We Can’t Keep Working Like This.]]
|
||
|
||
*** Notes
|
||
**** Get rid of information that isn't important
|
||
**** Escalate the info that is
|
||
**** Set /clear/ boundaries for when you are available
|
||
**** Enforce those with automatic DnD rules or use timers
|
||
**** With groups...
|
||
***** Specialisation is /good/ and should be /encouraged/
|
||
***** /All/ of the above points apply with coworkers as well
|
||
|
||
*** TODO Pong Jake when finished
|
||
|
||
** TODO Setting LXC up for local "cloud" development
|
||
** DONE (Ab)using mesh networks for easy remote support :Mesh__networking:Open__source:Remote__support:
|
||
CLOSED: [2021-11-01 Mon 02:51]
|
||
:PROPERTIES:
|
||
:EXPORT_FILE_NAME: abusing-mesh-networks-remote-support
|
||
:EXPORT_HUGO_CUSTOM_FRONT_MATTER: :toc true
|
||
:END:
|
||
One of the things many of us struggle with when setting friends and
|
||
family up with Linux is remote support. Commercial solutions like
|
||
[[https://www.realvnc.com/][RealVNC]] and [[https://rustdesk.com/][RustDesk]] do exist and function very well, but are often more
|
||
expensive than we would like for answering the odd "I can't get Facebook
|
||
open!" support call. I've been on the lookout for suitable alternatives
|
||
for a couple years but nothing has been satisfying. Because of this, I
|
||
have held off on setting others up with any Linux distribution, even the
|
||
particularly user-friendly options such as [[https://linuxmint.com/][Linux Mint]] and [[https://elementary.io/][elementary OS;]]
|
||
if I'm going drop someone in an unfamiliar environment, I want to be
|
||
able to help with any issue within a couple hours, not days and
|
||
/certainly/ not weeks.
|
||
|
||
[[https://linuxunplugged.com/421][Episode 421 of LINUX Unplugged]] gave me an awesome idea to use [[https://github.com/slackhq/nebula][Nebula,]] a
|
||
networking tool created by Slack, [[https://libvnc.github.io/][X11vnc,]] a very minimal VNC server, and
|
||
[[https://remmina.org/][Remmina,]] a libre remote access tool available in pretty much every Linux
|
||
distribution, to set up a scalable, secure, and simple setup reminiscent
|
||
of products like RealVNC.
|
||
|
||
*** Nebula
|
||
The first part of our stack is Nebula, the tool that creates a network
|
||
between all of our devices. With traditional VPNs, you have a client
|
||
with a persistent connection to a central VPN server and other clients
|
||
can communicate with the first by going through that central server.
|
||
This works wonderfully in most situations, but there are a lot of
|
||
latency and bandwidth restrictions that would make remote support an
|
||
unpleasant experience. Instead of this model, what we want is a /mesh/
|
||
network, where each client can connect directly to one another /without/
|
||
going through a central system and slowing things down. This is where
|
||
Nebula comes in.
|
||
|
||
In Nebula's terminology, clients are referred to as /nodes/ and central
|
||
servers are referred to as /lighthouses/, so those are the terms I'll use
|
||
going forward.
|
||
|
||
Mesh networks are usually only possible when dealing with devices that
|
||
have static IP addresses. Each node has to know /how/ to connect with the
|
||
other nodes; John can't meet up with Bob when Bob moves every other day
|
||
without notifying anyone of his new address. This wouldn't be a problem
|
||
if Bob phoned Jill and told her where he was moving; John would call
|
||
Jill, Jill would tell him where Bob is, and the two would be able to
|
||
find each other
|
||
|
||
With Nebula, nodes are Bob and John and Jill is a lighthouse. Each node
|
||
connects to a lighthouse and the lighthouse tells the nodes how to
|
||
connect with one another when they ask. It /facilitates/ the P2P
|
||
connection then /backs out of the way/ so the two nodes can communicate
|
||
directly with each other.
|
||
|
||
It allows any node to connect with any other node on any network from
|
||
anywhere in the world, as long as one lighthouse is accessible that
|
||
knows the connection details for both peers.
|
||
|
||
**** Getting started
|
||
The /best/ resource is [[https://github.com/slackhq/nebula][the official documentation,]] but I'll describe the
|
||
process here as well.
|
||
|
||
After [[https://github.com/slackhq/nebula#1-the-nebula-binaries-or-distribution-packages-for-your-specific-platform-specifically-youll-need-nebula-cert-and-the-specific-nebula-binary-for-each-platform-you-use][installing the required packages,]] make sure you have a VPS with a
|
||
static IP address to use as a lighthouse. If you want something dirt
|
||
cheap, I would recommend one of the small plans from [[https://buyvm.net][BuyVM.]] I do have a
|
||
[[https://my.frantech.ca/aff.php?aff=3783][referral link]] if you want them to kick me a few dollars for your
|
||
purchase. [[https://www.hetzner.com/cloud][Hetzner]] (referral: ~ckGrk4J45WdN~) or [[https://www.netcup.eu/][netcup]] (referral:
|
||
~36nc15758387844~) would also be very good options; I've used them all and
|
||
am very comfortable recommending them.
|
||
|
||
**** Creating a Certificate Authority
|
||
After picking a device with a static IP address, it needs to be set up
|
||
as a lighthouse. This is done by first creating a Certificate Authority
|
||
(CA) that will be used for signing keys and certificates that allow our
|
||
other devices into the network. The ~.key~ file produced by the following
|
||
command is incredibly sensitive; with it, anyone can authorise a new
|
||
device and give it access to your network. Store it in a safe,
|
||
preferably encrypted location.
|
||
|
||
#+BEGIN_SRC bash
|
||
nebula-cert ca -name "nebula.example.com"
|
||
#+END_SRC
|
||
|
||
I'll explain why we used a Fully-Qualified Domain Name (FQDN) as the
|
||
CA's name in a later section. If you have your own domain, feel free to
|
||
use that instead; it doesn't really matter what domain is used as long
|
||
as the format is valid.
|
||
|
||
**** Generating lighthouse credentials
|
||
Now that we have the CA's ~.crt~ and ~.key~ files, we can create and sign
|
||
keys and certificates for the lighthouse.
|
||
|
||
#+BEGIN_SRC bash
|
||
nebula-cert sign -name "buyvm.lh.nebula.example.com" -ip "192.168.100.1/24"
|
||
#+END_SRC
|
||
|
||
Here, we're using a FQDN for the same reason as we did in the CA. You
|
||
can use whatever naming scheme you like, I just prefer
|
||
~<vps-host>.lh.nebula...~ for my lighthouses. The IP address can be on any
|
||
of the following private IP ranges, I just happened to use ~192.168.100.X~
|
||
for my network.
|
||
|
||
| IP Range | Number of addresses |
|
||
|-------------------------------+---------------------|
|
||
| 10.0.0.0 – 10.255.255.255 | 16 777 216 |
|
||
| 172.16.0.0 – 172.31.255.255 | 10 48 576 |
|
||
| 192.168.0.0 – 192.168.255.255 | 65 536 |
|
||
|
||
**** Creating a config file
|
||
The next step is creating our lighthouse's config file. The reference
|
||
config can be found in [[https://github.com/slackhq/nebula/blob/master/examples/config.yml][Nebula's repo.]] We only need to change a few of
|
||
the lines for the lighthouse to work properly. If I don't mention a
|
||
specific section here, I've left the default values.
|
||
|
||
The section below is where we'll define certificates and keys. ~ca.crt~
|
||
will remain ~ca.crt~ when we copy it over but I like to leave the node's
|
||
cert and key files named as they were when generated; this makes it easy
|
||
to identify nodes by their configs. Once we copy everything over to the
|
||
server, we'll add the proper paths to the ~cert~ and ~key~ fields.
|
||
|
||
#+BEGIN_SRC yaml
|
||
pki:
|
||
ca: /etc/nebula/ca.crt
|
||
cert: /etc/nebula/
|
||
key: /etc/nebula/
|
||
#+END_SRC
|
||
|
||
The next section is for identifying and mapping your lighthouses. This
|
||
needs to be present in /all/ of the configs on /all/ nodes, otherwise they
|
||
won't know how to reach the lighthouses and will never actually join the
|
||
network. Make sure you replace ~XX.XX.XX.XX~ with whatever your VPS's
|
||
public IP address is. If you've used a different private network range,
|
||
those changes need to be reflected here as well.
|
||
|
||
#+BEGIN_SRC yaml
|
||
static_host_map:
|
||
"192.168.100.1": ["XX.XX.XX.XX:4242"]
|
||
#+END_SRC
|
||
|
||
Below, we're specifying how the node should behave. It is a lighthouse,
|
||
it should answer DNS requests, the DNS server should listen on all
|
||
interfaces on port 53, it sends its IP address to lighthouses every 60
|
||
seconds (this option doesn't actually have any effect when ~am_lighthouse~
|
||
is set to ~true~ though), and this lighthouse should not send reports to
|
||
other lighthouses. The bit about DNS will be discussed later.
|
||
|
||
#+BEGIN_SRC yaml
|
||
lighthouse:
|
||
am_lighthouse: true
|
||
serve_dns: true
|
||
dns:
|
||
host: 0.0.0.0
|
||
port: 53
|
||
interval: 60
|
||
hosts:
|
||
#+END_SRC
|
||
|
||
The next bit is about [[https://en.wikipedia.org/wiki/Hole_punching_%28networking%29][hole punching]], also called /NAT punching/, /NAT
|
||
busting/, and a few other variations. Make sure you read the comments for
|
||
better explanations than I'll give here. ~punch: true~ enables hole
|
||
punching. I also like to enable ~respond~ just in case nodes are on
|
||
particularly troublesome networks; because we're using this as a support
|
||
system, we have no idea what networks our nodes will actually be
|
||
connected to. We want to make sure devices are available no matter where
|
||
they are.
|
||
|
||
#+BEGIN_SRC yaml
|
||
punchy:
|
||
punch: true
|
||
respond: true
|
||
delay: 1s
|
||
#+END_SRC
|
||
~cipher~ is a big one. The value /must/ be identical on /all/ nodes /and/
|
||
lighthouses. ~chachapoly~ is more compatible so it's used by default. The
|
||
devices /I/ want to connect to are all x86 Linux, so I can switch to ~aes~
|
||
and benefit from [[https://www.reddit.com/r/networking/comments/iksyuu/comment/g3ra5cv/?utm_source=share&utm_medium=web2x&context=3][a small performance boost.]] Unless you know /for sure/
|
||
that you won't need to work with /anything/ else, I recommend leaving it
|
||
set to ~chachapoly~.
|
||
|
||
#+BEGIN_SRC yaml
|
||
cipher: chachapoly
|
||
#+END_SRC
|
||
|
||
The last bit I modify is the firewall section. I leave most everything
|
||
default but /remove/ the bits after ~port: 443~. I don't /need/ the ~laptop~ and
|
||
~home~ groups (groups will be explained later) to access port ~443~ on this
|
||
node, so I shouldn't include the statement. If you have different needs,
|
||
take a look at the comment explaining how the firewall portion works and
|
||
make those changes.
|
||
|
||
Again, I /remove/ the following bit from the config.
|
||
|
||
#+BEGIN_SRC yaml
|
||
- port: 443
|
||
proto: tcp
|
||
groups:
|
||
- laptop
|
||
- home
|
||
#+END_SRC
|
||
|
||
**** Setting the lighthouse up
|
||
We've got the config, the certificates, and the keys. Now we're ready to
|
||
actually set it up. After SSHing into the server, grab the [[https://github.com/slackhq/nebula/releases/latest][latest
|
||
release of Nebula for your platform,]] unpack it, make the ~nebula~ binary
|
||
executable, then move it to ~/usr/local/bin~ (or some other location
|
||
fitting for your platform).
|
||
|
||
#+BEGIN_SRC bash
|
||
wget https://github.com/slackhq/nebula/releases/download/vX.X.X/nebula-PLATFORM-ARCH.tar.gz
|
||
tar -xvf nebula-*
|
||
chmod +x nebula
|
||
mv nebula /usr/local/bin/
|
||
rm nebula-*
|
||
#+END_SRC
|
||
|
||
Now we need a place to store our config file, keys, and certificates.
|
||
|
||
#+BEGIN_SRC bash
|
||
mkdir /etc/nebula/
|
||
#+END_SRC
|
||
|
||
The next step is copying the config, keys, and certificates to the
|
||
server. I use ~rsync~ but you can use whatever you're comfortable with.
|
||
The following four files need to be uploaded to the server.
|
||
|
||
- ~config.yml~
|
||
- ~ca.crt~
|
||
- ~buyvm.lh.nebula.example.com.crt~
|
||
- ~buyvm.lh.nebula.example.com.key~
|
||
|
||
With ~rsync~, that would look something like this. Make sure ~rsync~ is also
|
||
installed on the VPS before attempting to run the commands though;
|
||
you'll get an error otherwise.
|
||
|
||
#+BEGIN_SRC bash
|
||
rsync -avmzz ca.crt user@example.com:
|
||
rsync -avmzz config.yml user@example.com:
|
||
rsync -avmzz buyvm.lh.* user@example.com:
|
||
#+END_SRC
|
||
|
||
SSH back into the server and move everything to ~/etc/nebula/~.
|
||
|
||
#+BEGIN_SRC bash
|
||
mv ca.crt /etc/nebula/
|
||
mv config.yml /etc/nebula/
|
||
mv buyvm.lh* /etc/nebula/
|
||
#+END_SRC
|
||
|
||
Edit the config file and ensure the ~pki:~ section looks something like
|
||
this, modified to match your hostnames of course.
|
||
|
||
#+BEGIN_SRC yaml
|
||
pki:
|
||
ca: /etc/nebula/ca.crt
|
||
cert: /etc/nebula/buyvm.lh.nebula.example.com.crt
|
||
key: /etc/nebula/buyvm.lh.nebula.example.com.key
|
||
#+END_SRC
|
||
|
||
Run the following command to make sure everything works properly.
|
||
|
||
#+BEGIN_SRC bash
|
||
nebula -config /etc/nebula/config.yml
|
||
#+END_SRC
|
||
|
||
The last step is daemonizing Nebula so it runs every time the server
|
||
boots. If you're on a machine using systemd, dropping the following
|
||
snippet into ~/etc/systemd/system/nebula.service~ should be sufficient. If
|
||
you're using something else, check the [[https://github.com/slackhq/nebula/tree/master/examples/][the examples directory]] for more
|
||
options.
|
||
|
||
#+BEGIN_SRC text
|
||
[Unit]
|
||
Description=nebula
|
||
Wants=basic.target
|
||
After=basic.target network.target
|
||
Before=sshd.service
|
||
|
||
[Service]
|
||
SyslogIdentifier=nebula
|
||
ExecReload=/bin/kill -HUP $MAINPID
|
||
ExecStart=/usr/local/bin/nebula -config /etc/nebula/config.yml
|
||
Restart=always
|
||
|
||
[Install]
|
||
WantedBy=multi-user.target
|
||
#+END_SRC
|
||
|
||
We're almost done!
|
||
|
||
**** Setting individual nodes up
|
||
This process is almost exactly the same as setting lighthouses up. All
|
||
you'll need to do is generate a couple of certs and keys then tweak the
|
||
configs a bit.
|
||
|
||
The following command creates a new cert/key for USER's node with the IP
|
||
address ~192.168.100.2~. The resulting files would go on the /remote/ node
|
||
not yours. Replace ~HOST~ and ~USER~ with fitting values.
|
||
|
||
#+BEGIN_SRC bash
|
||
nebula-cert sign -name "HOST.USER.nebula.example.com" -ip "192.168.100.2/24"
|
||
#+END_SRC
|
||
|
||
The following command will create a /similar/ cert/key but it will be part
|
||
of the ~support~ group. The files resulting from this should go on /your/
|
||
nodes. With the config we'll create next, nodes in the ~support~ group
|
||
will be able to VNC and SSH into other nodes. Your nodes need to be in
|
||
the ~support~ group so you'll have access to the others.
|
||
|
||
#+BEGIN_SRC bash
|
||
nebula-cert sign -name "HOST.USER.nebula.example.com" -ip "192.168.100.2/24" -groups "support"
|
||
#+END_SRC
|
||
|
||
On to the config now. This tells the node that it is /not/ a lighthouse,
|
||
it should /not/ resolve DNS requests, it /should/ ping the lighthouses and
|
||
tell them its IP address every 60 seconds, and the node at ~192.168.100.1~
|
||
is one of the lighthouses it should report to and query from. If you
|
||
have more than one lighthouse, add them to the list as well.
|
||
|
||
#+BEGIN_SRC yaml
|
||
lighthouse:
|
||
am_lighthouse: false
|
||
#serve_dns: false
|
||
#dns:
|
||
#host: 0.0.0.0
|
||
#port: 53
|
||
interval: 60
|
||
hosts:
|
||
- "192.168.100.1"
|
||
#+END_SRC
|
||
|
||
The other bit that should be modified is the ~firewall:~ section and this
|
||
is where the groups we created earlier are important. Review its
|
||
comments and make sure you understand how it works before proceeding.
|
||
|
||
We want to allow inbound connections on ports 5900, the standard port
|
||
for VNC, and 22, the standard for SSH. Additionally, we /only/ want to
|
||
allow connections from nodes in the ~support~ group. Any /other/ nodes
|
||
should be denied access.
|
||
|
||
Note that including this section is not necessary on /your/ nodes, those
|
||
in the ~support~ group. It's only necessary on the remote nodes that
|
||
you'll be connecting to. As long as the ~outbound:~ section in the config
|
||
on /your/ node allows any outbound connection, you'll be able to access
|
||
other nodes.
|
||
|
||
#+BEGIN_SRC yaml
|
||
- port: 5900
|
||
proto: tcp
|
||
groups:
|
||
- support
|
||
|
||
- port: 22
|
||
proto: tcp
|
||
groups:
|
||
- support
|
||
#+END_SRC
|
||
|
||
The certs, key, config, binary, and systemd service should all be copied
|
||
to the same places on all of these nodes as on the lighthouse.
|
||
|
||
*** X11vnc
|
||
/Alright./ The hardest part is finished. Now on to setting ~x11vnc~ up on
|
||
the nodes you'll be supporting.
|
||
|
||
All you should need to do is install ~x11vnc~ using the package manager
|
||
your distro ships with, generate a 20 character password with ~pwgen -s
|
||
20 1~, run the following command, paste the password, wait for ~x11vnc~ to
|
||
start up, make sure it's running correctly, press ~Ctrl~ + ~C~, then add the
|
||
command to the DE's startup applications!
|
||
|
||
#+BEGIN_SRC bash
|
||
x11vnc --loop -usepw -listen <nebula-ip> -display :0
|
||
#+END_SRC
|
||
~--loop~ tells ~x11vnc~ to restart once you disconnect from the session.
|
||
~-usepw~ is pretty self-explanatory. ~-listen <nebula-ip>~ is important; it
|
||
tells ~x11vnc~ to only listen on the node's Nebula IP address. This
|
||
prevents randos in a coffee shop from seeing an open VNC port and trying
|
||
to brute-force the credentials. ~-display :0~ just defines which X11
|
||
server display to connect to.
|
||
|
||
Some distributions like elementaryOS and those that use KDE and GNOME
|
||
will surface a dialogue for managing startup applications if you just
|
||
press the Windows (Super) key and type ~startup~. If that doesn't work,
|
||
you'll have to root around in the settings menus, consult the
|
||
distribution's documentation, or ask someone else that might know.
|
||
|
||
After adding it to the startup application, log out and back in to make
|
||
sure it's running in the background.
|
||
|
||
*** Remmina
|
||
Now that our network is functioning properly and the VNC server is set
|
||
up, we need something that connects to the VNC server over the fancy
|
||
mesh network. Enter [[https://remmina.org/][Remmina.]] This one goes on /your/ nodes.
|
||
|
||
Remmina is a multi-protocol remote access tool available in pretty much
|
||
ever distribution's package archive as ~remmina~. Install it, launch it,
|
||
add a new connection profile in the top left, give the profile a
|
||
friendly name (I like to use the name of the person I'll be supporting),
|
||
assign it to a group, such as ~Family~ or ~Friends~, set the Protocol to
|
||
~Remmina VNC Plugin~, enter the node's Nebula IP address in the Server
|
||
field, then enter their username and the 20 character password you
|
||
generated earlier. I recommend setting the quality to Poor, but Nebula
|
||
is generally performant enough that any of the options are suitable. I
|
||
just don't want to have to disconnect and reconnect with a lower quality
|
||
if the other person happens to be on a slow network.
|
||
|
||
Save and test the connection!
|
||
|
||
If all goes well and you see the other device's desktop, you're done
|
||
with the VNC section! Now on to SSH.
|
||
|
||
*** SSH
|
||
First off, make sure ~openssh-server~ is installed on the remote node;
|
||
~openssh-client~ would also be good to have, but from what I can tell,
|
||
it's not strictly necessary. You /will/ need ~openssh-client~ on /your/ node,
|
||
however. If you already have an SSH key, copy it over to
|
||
~~/.ssh/authorized_keys~ on the remote node. If you don't, generate one
|
||
with ~ssh-keygen -t ed25519~. This will create an Ed25519 SSH key pair.
|
||
Ed25519 keys are shorter and faster than RSA and more secure than ECDSA
|
||
or DSA. If that means nothing to you, don't worry about it. Just note
|
||
than this key might not interact well with older SSH servers; you'll
|
||
know if you need to stick with the default RSA. Otherwise, Ed25519 is
|
||
the better option. After key generation has finished, copy
|
||
~~/.ssh/id_ed25519.pub~ (note the ~.pub~ extension) from your node to
|
||
~~/.ssh/authorized_keys~ on the remote node. The file /without/ ~.pub~ is your
|
||
/private/ key. Like the Nebula CA certificate we generated earlier, this
|
||
is extremely sensitive and should never be shared with anyone else.
|
||
|
||
Next is configuring SSH to only listen on Nebula's interface; as with
|
||
~x11vnc~, this prevents randos in a coffee shop from seeing an open SSH
|
||
port and trying to brute-force their way in. Set the ~ListenAddress~
|
||
option in ~/etc/ssh/sshd_config~ to the remote node's Nebula IP address.
|
||
If you want to take security a step further, search for
|
||
~PasswordAuthentication~ and set it to ~no~. This means your SSH key is
|
||
/required/ for gaining access via SSH. If you mess up Nebula's firewall
|
||
rules and accidentally give other Nebula devices access to this machine,
|
||
they still won't be able to get in unless they have your SSH key. I
|
||
/personally/ recommend disabling password authentication, but it's not
|
||
absolutely necessary. After making these changes, run ~systemctl restart
|
||
sshd~ to apply them.
|
||
|
||
Now that the SSH server is listening on Nebula's interface, it will
|
||
actually fail to start when the machine (re)boots. The SSH server starts
|
||
faster than Nebula does, so it will look for the interface before Nebula
|
||
has even had a chance to connect. We need to make sure systemd waits for
|
||
Nebula to start up and connect before it tells SSH to start; run
|
||
~systemctl edit --full sshd~ and add the following line in the ~[Unit]~
|
||
section, above ~[Service]~.
|
||
|
||
#+BEGIN_SRC text
|
||
After=nebula.service
|
||
#+END_SRC
|
||
|
||
Even now, there's still a bit of a hiccup. Systemd won't start SSH until
|
||
Nebula is up and running, which is good. Unfortunately, even after
|
||
Nebula has started, it still takes a minute to bring the interface up,
|
||
causing SSH to crash. To fix /this/, add the following line directly below
|
||
~[Service]~.
|
||
|
||
#+BEGIN_SRC text
|
||
ExecStartPre=/usr/bin/sleep 30
|
||
#+END_SRC
|
||
|
||
If the ~sleep~ executable is stored in a different location, make sure you
|
||
use that path instead. You can check by running ~which sleep~.
|
||
|
||
When the SSH /service/ starts up, it will now wait an additional 30
|
||
seconds before actually starting the SSH /daemon/. It's a bit of a hacky
|
||
solution but it works™. If you come up with something better, please
|
||
send it to me and I'll include it in the post! My contact information is
|
||
at the bottom of [[/][this site's home page.]]
|
||
|
||
After you've made these changes, run ~systemctl daemon-reload~ to make
|
||
sure systemd picks up on the modified service file, then run ~systemctl
|
||
restart sshd~. You should be able to connect to the remote node from your
|
||
node using the following command.
|
||
|
||
#+BEGIN_SRC bash
|
||
ssh USER@<nebula-ip>
|
||
#+END_SRC
|
||
|
||
If you want to make the command a little simpler so you don't have to
|
||
remember the IP every time, create ~~/.ssh/config~ on your node and add
|
||
these lines to it.
|
||
|
||
#+BEGIN_SRC text
|
||
Host USER
|
||
Hostname <nebula-ip>
|
||
User USER
|
||
#+END_SRC
|
||
|
||
Now you can just run ~ssh USER~ to get in. If you duplicate the above
|
||
block for all of the remote nodes you need to support, you'll only have
|
||
to remember the person's username to SSH into their machine.
|
||
|
||
*** Going further with Nebula
|
||
This section explains why we used FQDNs in the certs and why the DNS
|
||
resolver is enabled on the lighthouse.
|
||
|
||
Nebula ships with a built-in resolver meant specifically for mapping
|
||
Nebula node hostnames to their Nebula IP addresses. Running a public DNS
|
||
resolver is very much discouraged because it can be abused in terrible
|
||
ways. However, the Nebula resolver mitigates this risk because it /only/
|
||
answers queries for Nebula nodes. It doesn't forward requests to any
|
||
other servers nor does it attempt to resolve any domain other than what
|
||
was defined in its certificate. If you use the example I gave above,
|
||
that would be ~nebula.example.com~; the lighthouse will attempt to resolve
|
||
any subdomain of ~nebula.example.com~ but it will just ignore ~example.com~,
|
||
~nebula.duckduckgo.com~, ~live.secluded.site~, etc.
|
||
|
||
Taking advantage of this resolver requires setting it as your secondary
|
||
resolver on any device you want to be able to resolve hostnames from.
|
||
If you were to add the lighthouse's IP address as your secondary
|
||
resolver on your PC, you could enter ~host.user.nebula.example.com~ in
|
||
Remmina's server settings /instead of/ ~192.168.1.2~.
|
||
|
||
But how you do so is beyond the scope of this post!
|
||
|
||
If you're up for some /more/ shenanigans later on down the line, you could
|
||
set up a Pi-Hole instance backed by Unbound and configure Nebula as
|
||
Unbound's secondary resolver. With this setup, you'd get DNS-level ad
|
||
blocking /and/ the ability to resolve Nebula hostname. Pi-Hole would query
|
||
Unbound for ~host.user.nebula.example.com~, Unbound would receive no
|
||
answer from the root servers because the domain doesn't exist outside of
|
||
your VPN, Unbound would fall back to Nebula, Nebula would give it an
|
||
answer, Unbound would cache the answer, tell Pi-Hole, Pi-Hole would
|
||
cache the answer, tell your device, then your device would cache the
|
||
answer, and you can now resolve any Nebula host!
|
||
|
||
Exactly how you do /that/ is */definitely/* beyond the scope of this post :P
|
||
|
||
If you set any of this up, I would be interested to hear how it goes! As
|
||
stated earlier, my contact information is at the bottom of the site's
|
||
home page :)
|
||
|
||
* Education :@Education:
|
||
** TODO Homeschooling
|
||
* Music :@Music:
|
||
* Pipe Smoking :@Pipe__Smoking:
|
||
* Dungeons & Dragons :@Dungeons__and__Dragons:
|
||
|
||
* Footnotes
|
||
|
||
[fn:19] [[https://github.com/audacity/audacity/pull/835#issuecomment-834451187][Link to the comment]] and [[/assets/pngs/audacity-pr/privatelibcurl.png][link to the screenshot]]
|
||
|
||
[fn:18] [[https://github.com/audacity/audacity/pull/835#issuecomment-834010117][Link to the comment]] and [[/assets/pngs/audacity-pr/vendorproscons.png][link to the screenshot]]
|
||
|
||
[fn:17] [[https://github.com/audacity/audacity/pull/835#issuecomment-836069326][Link to the comment]] and [[/assets/pngs/audacity-pr/tantacrulrole.png][link to the screenshot]]
|
||
|
||
[fn:16] [[https://github.com/audacity/audacity/pull/835#discussion_r627762185][Link to the comment]] and [[/assets/pngs/audacity-pr/missingprivacypolicy.png][link to the screenshot]]
|
||
|
||
[fn:15] [[https://github.com/audacity/audacity/pull/835#issuecomment-834286641][Link to the comment]] and [[/assets/pngs/audacity-pr/darkpattern.png][link to the screenshot]]
|
||
|
||
[fn:14] [[https://github.com/audacity/audacity/pull/835#discussion_r627764300][Link to the comment]] and the screenshot is the same as previous
|
||
|
||
[fn:13] [[https://github.com/audacity/audacity/pull/835#discussion_r627756976][Link to the comment]] and [[/assets/pngs/audacity-pr/vaguedialogue.png][link to the screenshot]]
|
||
|
||
[fn:12] [[https://github.com/audacity/audacity/pull/835#discussion_r628124998][Link to the review]] and [[/assets/pngs/audacity-pr/defaultconsentbutton.png][link to the screenshot]]
|
||
|
||
[fn:11] [[https://github.com/audacity/audacity/pull/835#discussion_r628008821][Link to the review]] and [[/assets/pngs/audacity-pr/externaldependency.png][link to the screenshot]]
|
||
|
||
[fn:10] [[https://github.com/audacity/audacity/pull/835#discussion_r628005925][Link to the review]] and [[/assets/pngs/audacity-pr/vendorcurl.png][link to the screenshot]]
|
||
|
||
[fn:9] Note that because I am not a C programmer, these reviews might
|
||
not be entirely accurate and I wouldn't be able to catch the reviewer's
|
||
error. I am relying on other community members to catch issues and
|
||
comment on them; none of the reviews I link to have such comments so I'm
|
||
assuming they are correct.
|
||
|
||
[fn:8] [[https://github.com/audacity/audacity/pull/835#discussion_r627993755][Link to the review]] and [[/assets/pngs/audacity-pr/writeanalyticsid.png][link to the screenshot]]
|
||
|
||
[fn:7] /Optical Character Recognition/: the program looks at your
|
||
handwriting and tries to turn it into text.
|
||
|
||
[fn:6] In this situation, latency refers to how long it takes for "ink"
|
||
to show up on the "page" after writing something.
|
||
|
||
[fn:5] It's not really a fountain pen even though that's what they call
|
||
it; it's just pressure-sensitive.
|
||
|
||
[fn:4] There does seem to be a group of people interested in just such a
|
||
thing: /[[https://alexsoto.dev/challenges-building-an-open-source-eink-laptop.html][Challenges Building an Open-Source E Ink Laptop]]/
|
||
|
||
[fn:3]Taken from their [[https://support.remarkable.com/hc/en-us/articles/360006699537-About-reMarkable-2-][support page about the reMarkable 2;]] search the
|
||
page for /operating system/ and it should show up.
|
||
|
||
[fn:2]I dislike Apple's operating system, their hardware, business
|
||
model, privacy practises, and much of what they stand for as a company.
|
||
Don't @ me.
|
||
|
||
[fn:1]E-R/W is a play on media commonly being labelled as R/W when you
|
||
can read from it and write to it.
|