<?xml version="1.0" encoding="utf-8"?>
<?xml-stylesheet href="/feeds.xsl" type="text/xsl"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:base="https://chameth.com/">
    <title>Chameth.com - posts like sense-api, thinking-more-about-backups, why-you-should-be-using-https but not how-i-get-things-done</title>
    <subtitle>Personal homepage of Chris Smith</subtitle>
    <link href="https://chameth.com/feeds/posts/like/sense-api,thinking-more-about-backups,why-you-should-be-using-https/unlike/how-i-get-things-done/" rel="self"/>
    <link href="https://chameth.com/"/>
    <icon>https://chameth.com/favicon.png</icon>
    <updated>2025-11-01T00:00:00Z</updated>
    <id>https://chameth.com/</id>
    <author>
        <name>Chris Smith</name>
    </author>
    <entry>
        <title>Thinking more about backups</title>
        <link href="https://chameth.com/thinking-more-about-backups/"/>
        <updated>2025-11-01T00:00:00Z</updated>
        <id>https://chameth.com/thinking-more-about-backups/</id>
        <content xml:lang="en" type="html">&lt;figure class=&#34;image right&#34;&gt;
  &lt;picture&gt;
      &lt;source srcset=&#34;https://chameth.com/thinking-more-about-backups/backblaze.webp&#34; type=&#34;image/webp&#34;/&gt;
      &lt;source srcset=&#34;https://chameth.com/thinking-more-about-backups/backblaze.avif&#34; type=&#34;image/avif&#34;/&gt;
      &lt;img src=&#34;https://chameth.com/thinking-more-about-backups/backblaze.png&#34; alt=&#34;The Backblaze logo: a stylised flame above the word Backblaze&#34; loading=&#34;lazy&#34; width=&#34;500&#34; height=&#34;320&#34;/&gt;
  &lt;/picture&gt;
  &lt;figcaption&gt;&lt;p&gt;The Backblaze logo&lt;/p&gt;
&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;Almost a year ago I wrote about &lt;a href=&#34;https://chameth.com/simple-backups-restic-hetzner/&#34;&gt;how I do backups with Restic and Hetzner&lt;/a&gt;.
That system has been ticking along well ever since, but recently I had some… thoughts. These backups are
all well and good if I accidentally delete a file, or a database gets corrupted, or something, but there are
two glaring issues:&lt;/p&gt;
&lt;p&gt;Firstly, I’m backing up my Hetzner server to Hetzner cloud storage. If something happens to Hetzner — or
my Hetzner account — then all my eggs go down with that basket. Obviously Hetzner are a big organisation
and aren’t likely to just vanish overnight, but I’m less confident about my account. Could a false abuse
report get it suspended? What if the UK passes
&lt;a href=&#34;https://www.legislation.gov.uk/ukpga/2023/50/contents&#34;&gt;even more dumb laws&lt;/a&gt; and Hetzner decide it’s easier
just to not do business with people here? This is the same sort of concern I have about Google accounts:
if you have half of your life in Google Drive and Google Mail, what happens if you comment on a YouTube
video, get flagged by an AI moderation process, and your account gets suspended? It’s probably not very
likely, but these are things my brain likes to dwell on.&lt;/p&gt;
&lt;p&gt;Secondly, the credentials to access the backups sit on each machine that is backed up. If someone malicious
gained access to the machine, they’d also have access to delete or tamper with all the backups. It feels
a little silly that the same attack could take down both the originals and the backups. There’s no way to
avoid that with Hetzner’s S3 implementation, as far as I can tell.&lt;/p&gt;
&lt;h3 id=&#34;exploring-options&#34;&gt;Exploring options&lt;/h3&gt;
&lt;p&gt;I toyed with the idea of making local copies of the backups, but the only way to avoid the same problems
would be to keep them offline and do a manual copy every now and then. I didn’t really want to do that,
and was concerned that if I did a monthly offline backup then I stood to lose up to a month of data in
the worst case.&lt;/p&gt;
&lt;p&gt;I then looked around at other S3 providers. &lt;a href=&#34;https://aws.amazon.com/s3/storage-classes/glacier/&#34;&gt;Amazon’s glacier offering&lt;/a&gt;
is tempting due to its very low storage costs, but you pay for that if you ever want to restore anything.
There are also lots of weird pricing edge cases around moving data between storage classes, minimum file
sizes, and so on. A much better option is &lt;a href=&#34;https://www.backblaze.com/cloud-storage&#34;&gt;Backblaze’s B2&lt;/a&gt; product.
Their pricing is much more straight-forward, and they have an interesting feature that’s particularly useful
in this case: &lt;a href=&#34;https://www.backblaze.com/blog/backblaze-b2-lifecycle-rules/&#34;&gt;lifecycle rules&lt;/a&gt;. Coupled with
the ability to create API keys that don’t have access to delete files (just “hide” them), this allows for
what’s effectively an append-only store.&lt;/p&gt;
&lt;p&gt;This works more-or-less out of the box with Restic. &lt;a href=&#34;https://pricey.uk/blog/restic-backups-without-delete/&#34;&gt;Joseph Price has a guide&lt;/a&gt;
that goes into the setup in a bit more depth. Basically, whenever Restic would delete a file (e.g. during
a “forget” or “prune” operation), it instead gets hidden and is only deleted when the B2 lifecycle rules
decide it should be. I’ve kept the existing Hetzner S3 backups for now, and just added an extra step to
the end of my script: a simple &lt;code&gt;restic copy&lt;/code&gt; and a &lt;code&gt;restic forget&lt;/code&gt;. B2 actually works out cheaper than the
Hetzner storage, as they don’t bill you for a minimum of 1TB storage; my current usage is around $3/month.
Not a bad price for some extra peace of mind!&lt;/p&gt;
</content>
    </entry>
    <entry>
        <title>10 Weeks with an Apple Watch 10</title>
        <link href="https://chameth.com/10-weeks-with-an-apple-watch-10/"/>
        <updated>2025-09-09T00:00:00Z</updated>
        <id>https://chameth.com/10-weeks-with-an-apple-watch-10/</id>
        <content xml:lang="en" type="html">&lt;figure class=&#34;image right&#34;&gt;
  &lt;picture&gt;
      &lt;source srcset=&#34;https://chameth.com/10-weeks-with-an-apple-watch-10/watch.avif&#34; type=&#34;image/avif&#34;/&gt;
      &lt;source srcset=&#34;https://chameth.com/10-weeks-with-an-apple-watch-10/watch.webp&#34; type=&#34;image/webp&#34;/&gt;
      &lt;img src=&#34;https://chameth.com/10-weeks-with-an-apple-watch-10/watch.jpg&#34; alt=&#34;An Apple Watch 10 being worn, with a blue analogue clock on the display, and icons/data shown in the corners&#34; loading=&#34;lazy&#34; width=&#34;500&#34; height=&#34;644&#34;/&gt;
  &lt;/picture&gt;
  &lt;figcaption&gt;&lt;p&gt;My watch. Yes, I am available for wrist modelling opportunities.&lt;/p&gt;
&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;Around ten weeks ago&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a class=&#34;footnote-ref&#34; href=&#34;#fn:1&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt; I picked up an Apple Watch 10, and have been wearing
it almost constantly since. It’s not my first Apple Watch — I had a Series 5
for a bit back in 2020 — but it’s the first time I’ve actually stuck with it.
Ten weeks seems like an apt time to reflect on it.&lt;/p&gt;
&lt;p&gt;Firstly, why did I even bother? Well, for a couple of years I’d been wearing
a Xiaomi Smart Band 7, mainly to monitor my sleep stats and set alarms that
won’t wake up everyone else nearby. Its battery life was fantastic — with
notifications and other things turned off, I got about a month of use between
charges — but actually using it felt like trying to order food via the medium
of interpretive dance.&lt;/p&gt;
&lt;p&gt;My biggest gripe was the screen lock. If I didn’t have the screen locked then
I’d periodically trigger it during the night when I moved around
and it came in contact with my chest or leg. With the lock enabled you had to
deliberately swipe up from bottom to top to enable interaction, but it
didn’t work reliably. When I wanted to adjust an alarm, I’d be stood swiping
repeatedly trying to get it to respond. When you finally get it unlocked, the
whole interface is just &lt;em&gt;fiddly&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;The other issue was the data quality. There were some nights when I’d been
woken up, sometimes even getting up and moving around, and it just didn’t show
it in the data. If it can’t even get whether I’m asleep right, can I trust
anything else it says?&lt;/p&gt;
&lt;p&gt;I spent a while researching the best devices for sleep tracking. The Oura ring
came highly recommended, but it was expensive and required a subscription to
do anything useful. No thanks! The Apple Watch was consistently rated pretty
well, and I reasoned I could pick up a refurbished older unit. I’ve been
&lt;a href=&#34;https://chameth.com/apple-google-aligned-incentives/&#34;&gt;using an iPhone as my daily driver&lt;/a&gt; for
a while, so it’d fit right into my begrudged walled garden.&lt;/p&gt;
&lt;p&gt;The Series 10 has a significant advantage, though: it charges much quicker than
all the previous generations. On a 30-minute charge, the Series 10 can go from
0 to 60%; the 9 can only make it to 40%, and my old 5 a measly 30%. Shorter
charge times means I’m far less likely to leave it on charge and wander off
without it. In some ways the daily charging is more convenient than monthly: the
wireless charger sits on my desk, and I plop the watch on it for a little while
in the evening; I don’t need to dig out the weird pogo-pin connector that has
vanished sometime in the last four weeks, then carefully arrange it so it stays
attached.&lt;/p&gt;
&lt;h3 id=&#34;how-a-watch-maybe-saved-my-life&#34;&gt;How a watch maybe saved my life&lt;/h3&gt;
&lt;p&gt;One of the big features of the Apple Watch, like many other wearable devices,
is health and fitness tracking. I didn’t think much about this, beyond the
sleep data I wanted, at first. I’ve never had a particularly good relationship
with exercise&lt;sup id=&#34;fnref:2&#34;&gt;&lt;a class=&#34;footnote-ref&#34; href=&#34;#fn:2&#34; role=&#34;doc-noteref&#34;&gt;2&lt;/a&gt;&lt;/sup&gt;, but I do like some good statistics. I started going for
walks more often to get more data and see the graphs of VO2 max and HR recovery
gradually inch up. That wasn’t the most profound effect on my health, though…&lt;/p&gt;
&lt;!--more--&gt;
&lt;p&gt;The recent versions of the Apple Watch have a feature that monitors for sleep
apnea, a disorder where you don’t breathe properly during your sleep. I knew
I wasn’t sleeping great — that’s why I was paying attention to sleep tracking
data — but was still a bit surprised to get a notification from Apple Health
after wearing the watch for 30 days. It gives you a graph to print and take to
your doctor. So I diligently booked an appointment and a few weeks later went
to see my GP.&lt;/p&gt;
&lt;p&gt;The appointment went about as you’d expect: talking about referral to a sleep
centre for a study, and so on. Towards the end, the doctor took my blood
pressure (sleep apnea can be caused by, and can cause high blood pressure, in
a lovely little vicious cycle). I don’t think either of us expected anything;
it was just one of those standard checks for a related problem. After taking
the reading, the doctor looked at me and said, “I can’t let you leave with a
BP like this”. Yikes!&lt;/p&gt;
&lt;p&gt;Blood pressure readings are split into stages: normal is under 120 mmHg&lt;sup id=&#34;fnref:3&#34;&gt;&lt;a class=&#34;footnote-ref&#34; href=&#34;#fn:3&#34; role=&#34;doc-noteref&#34;&gt;3&lt;/a&gt;&lt;/sup&gt;
over 80 mmHg&lt;sup id=&#34;fnref:4&#34;&gt;&lt;a class=&#34;footnote-ref&#34; href=&#34;#fn:4&#34; role=&#34;doc-noteref&#34;&gt;4&lt;/a&gt;&lt;/sup&gt;, stage 1 is up to 140/90, stage 2 is up to 180/120,
and above is simply called “crisis”. Guess where I was? Also, fun fact:
depending on how exactly you count and attribute things, hypertension comes out
as the single largest cause of death in humans. It doesn’t kill you outright,
but contributes to strokes, coronary artery disease, heart failure, and lots
of other lovely things you don’t want on your CV.&lt;/p&gt;
&lt;p&gt;After doing a few more readings, it settled down to just under the “crisis”
stage and into the “holy crap, start treatment immediately” stage instead.
I won’t labour on much more about this, but things are definitely moving in
the right direction now&lt;sup id=&#34;fnref:5&#34;&gt;&lt;a class=&#34;footnote-ref&#34; href=&#34;#fn:5&#34; role=&#34;doc-noteref&#34;&gt;5&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;p&gt;While the Apple Watch didn’t literally save my life, it triggered the chain of
events that led to this diagnosis and treatment. Who knows what would have
happened had it remained undetected? Probably nothing good. Also, go check your
blood pressure! It’s simple to do and simple to treat if there is an issue,
but so many people are walking around with hypertension and not even realising.&lt;/p&gt;
&lt;aside class=&#34;update raised-box&#34;&gt;
  &lt;h5 class=&#34;plain-header&#34;&gt;Update 2025-09-09:&lt;/h5&gt;
  &lt;p&gt;Just a couple of hours after I published this post, Apple announced that they’re
adding hypertension notifications in the new Series 11 watch. It looks like
it will also be supported in Series 9 and Series 10 watches. They expect it to
notify more than one million people of unknown hypertension in the first year.&lt;/p&gt;
&lt;/aside&gt;
&lt;h3 id=&#34;building-gates-in-the-walled-garden&#34;&gt;Building gates in the walled garden&lt;/h3&gt;
&lt;p&gt;Even though the watch is arguably a life-saver, not everything is rosy.
It’s an Apple product, so you’re firmly locked in a walled garden, jealously
guarded by people wearing black turtlenecks. Fortunately, there are a few ways
to make it less painful.&lt;/p&gt;
&lt;p&gt;All the health and fitness data is stored in Apple Health. You can export data
as a big messy file, but it’s a very manual process to do so and the data format
is gnarly. Luckily, there’s an app for that!
&lt;a href=&#34;https://www.healthyapps.dev/&#34;&gt;Health Auto Export&lt;/a&gt; can, well, automatically
export health data. It does what it says on the tin. It can send the data to
Home Assistant, over MQTT, or dump it in some cloud file services, but I just
have it post it all to a REST endpoint on a service I wrote. Then I dump it all
in a database and can do whatever I want with it!&lt;sup id=&#34;fnref:6&#34;&gt;&lt;a class=&#34;footnote-ref&#34; href=&#34;#fn:6&#34; role=&#34;doc-noteref&#34;&gt;6&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;p&gt;Another tool that’s more useful on the watch itself is Apple Shortcuts. This
is their no-code “if-this-then-that”-type thing. You can make automations or
shortcuts that run a number of tasks. I have a whole slew of them I access
via a complication&lt;sup id=&#34;fnref:7&#34;&gt;&lt;a class=&#34;footnote-ref&#34; href=&#34;#fn:7&#34; role=&#34;doc-noteref&#34;&gt;7&lt;/a&gt;&lt;/sup&gt; on my watch face: one which prompts for input and adds an
item to my to-do list (swiping to write letters is surprisingly not horrible),
one which lets me select from a bunch of pre-written ones (“charge kindle”,
“take laundry out in 1 hour”, type things), one to log my weight into Apple
Health, one which can open and close the blinds in my room, and so on. It’s
a surprisingly robust and easy-to-use system and offers just enough freedom
that I’m not constantly grating on the edge of the walled garden.&lt;/p&gt;
&lt;p&gt;Shortcuts being able to initiate arbitrary web requests is the real killer
feature for me. Anything I can’t do on the watch itself, I can just farm off
to a web server and connect it up with a shortcut. No need to learn Swift or
pay Apple for the privilege of being a developer! For a lot of things, like
controlling the blinds or adding to-do items, I already had a HTTP endpoint
available and exposed over Tailscale&lt;sup id=&#34;fnref:8&#34;&gt;&lt;a class=&#34;footnote-ref&#34; href=&#34;#fn:8&#34; role=&#34;doc-noteref&#34;&gt;8&lt;/a&gt;&lt;/sup&gt;. Adding it to the watch was just a case
of entering the right things in the Shortcuts app.&lt;/p&gt;
&lt;h3 id=&#34;daily-nitty-gritty&#34;&gt;Daily nitty-gritty&lt;/h3&gt;
&lt;p&gt;There are lots of other little bits and pieces that come up when using the watch
daily. I don’t think I can bundle them up into a pleasing narrative arc, so
instead please enjoy some disjointed paragraphs of observations.&lt;/p&gt;
&lt;p&gt;The Apple Watch has a lot of nagging abilities. It can notify you about your
fitness “rings”, prompt you to stand up every hour, count how many seconds
you wash your hands for, etc. I think I’d dislike these just on general
principle, but the way it does them is &lt;em&gt;so&lt;/em&gt; condescending it’s painful. I think
there’s probably a cultural divide issue here, but there is no way in British
English to say “Great job! You washed your hands for 30 seconds!” without
it sounding like you’re being amazingly sarcastic or like you’re talking to a
young child. So I turned all of that nonsense off. It’s meant to be a tool
not a wannabe life coach.&lt;/p&gt;
&lt;p&gt;You can access Maps directly on the watch and even do navigation. It works
really well. The navigation mode has some nice haptic feedback: it does a short
pulse as you’re approaching a turn, and then a long pulse at the actual turn.
I like it a lot more than having to dig out my phone or have the directions
read out. You get one pulse, glance down and see where you need to go, then it
reminds you a little later when it’s time to do it. It’s a delightful user
experience.&lt;/p&gt;
&lt;p&gt;Watchfaces aren’t quite so delightful. There’s a limited number of built-in
ones, and some are “exclusive” to the Ultra Series, and you can’t use them on
a peasant watch like a Series 10. Annoyingly, there’s not one that does
exactly what I want: a plain analogue clock with an inset date and four
complication slots around the outside. Instead, I have to use one of the slots
to show the date. There are third-party watchfaces, but they have issues.
Firstly, there’s no actual API for making watchfaces&lt;sup id=&#34;fnref:9&#34;&gt;&lt;a class=&#34;footnote-ref&#34; href=&#34;#fn:9&#34; role=&#34;doc-noteref&#34;&gt;9&lt;/a&gt;&lt;/sup&gt;, so what they do is
bodge it horribly by using a photo background that has fake widgets on it.
On top of that they’re almost universally subscription-based. Again, no
thanks&lt;sup id=&#34;fnref:10&#34;&gt;&lt;a class=&#34;footnote-ref&#34; href=&#34;#fn:10&#34; role=&#34;doc-noteref&#34;&gt;10&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;p&gt;The issue I had with the Smart Band triggering when I was sleeping is solved
trivially on an Apple Watch, by virtue of it having a physical button in the
crown. When you put it in sleep mode, you have to double-press the crown to
unlock it before it’ll do anything else. It hasn’t misfired once while I’ve
been using it.&lt;/p&gt;
&lt;figure class=&#34;image right&#34;&gt;
  &lt;picture&gt;
      &lt;source srcset=&#34;https://chameth.com/10-weeks-with-an-apple-watch-10/sleep.avif&#34; type=&#34;image/avif&#34;/&gt;
      &lt;source srcset=&#34;https://chameth.com/10-weeks-with-an-apple-watch-10/sleep.webp&#34; type=&#34;image/webp&#34;/&gt;
      &lt;img src=&#34;https://chameth.com/10-weeks-with-an-apple-watch-10/sleep.png&#34; alt=&#34;A graph showing sleep phases over time. There&amp;#39;s a noticeable transition form patchy data to more smooth data.&#34; loading=&#34;lazy&#34; width=&#34;284&#34; height=&#34;597&#34;/&gt;
  &lt;/picture&gt;
  &lt;figcaption&gt;&lt;p&gt;Sleep data; spot when I swapped devices!&lt;/p&gt;
&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;Finally, a special mention for the gesture controls. If you raise the watch
it reliably wakes up (switching from a low refresh rate, dimmed screen to
an active, brighter one); you can then double-tap your index finger and thumb
together and it will scroll down or page through whatever you’re looking at.
The killer feature for this is navigating recipes: you can advance to the next
step in a recipe while your hands are covered in flour. It’s also handy for
reading notifications: when one pops up, you can double-tap to expand it, then,
when it gets to the bottom, it’ll outline the default button (often “Dismiss”)
and you can double-tap again to click it.&lt;/p&gt;
&lt;h3 id=&#34;the-verdict&#34;&gt;The verdict&lt;/h3&gt;
&lt;p&gt;I normally don’t like writing an actual labelled conclusion, but it feels
like one is needed here! Overall, I’m happy with the watch. The daily
charging doesn’t bother me, the data gathered seems reliable, the health
monitoring has obviously paid dividends already, and the walled garden isn’t
&lt;em&gt;too&lt;/em&gt; chafing. It’s a straight upgrade over my old Smart Band, and I think
it was worth the cost.&lt;/p&gt;
&lt;p&gt;The original reason for getting the watch was better sleep tracking, though,
so how well did it do? I’m much happier with the data: it seems to more
accurately represent when I was awake in the night, and overall the sleep
phases just seem to make more sense. You can see in the graph that the old
data switched frequently between phases, and they didn’t quite line up for
some reason; towards the right when the Apple Watch is supplying the data
instead there’s a much more consistent pattern of sleep phases that repeat
over the course of the night.&lt;/p&gt;
&lt;p&gt;I’m not going to advocate that you go out and buy one, though. I know my
requirements and usage aren’t typical, and I’ve also not got experience with
any recent Android Wear alternatives or the new version of the Pebble watch
that’s coming soon. You should definitely get your blood pressure checked,
though!&lt;/p&gt;
&lt;div class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;
&lt;hr/&gt;
&lt;ol&gt;
&lt;li id=&#34;fn:1&#34;&gt;
&lt;p&gt;OK, it’s more like 14 now, it’s taken a while for this post to make its
way from my brain into text. &lt;a class=&#34;footnote-backref&#34; href=&#34;#fnref:1&#34; role=&#34;doc-backlink&#34;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:2&#34;&gt;
&lt;p&gt;Exercise for the sake of exercising just seems so overwhelmingly
tedious and boring to me. And other types of exercise generally require
social interaction, coordinating with people, and so on. &lt;a class=&#34;footnote-backref&#34; href=&#34;#fnref:2&#34; role=&#34;doc-backlink&#34;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:3&#34;&gt;
&lt;p&gt;Who decided to use “millimetres of mercury displaced” as a unit? You
can’t just put random chemical symbols in units! That’s not how this works! &lt;a class=&#34;footnote-backref&#34; href=&#34;#fnref:3&#34; role=&#34;doc-backlink&#34;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:4&#34;&gt;
&lt;p&gt;Blood pressure readings have two parts: systolic (the pressure when the
heart is beating) and diastolic (the pressure between those beats). They’re
generally presented with the systolic reading on top and the diastolic reading
below, and read as “X over Y”. Now you know what some of the random numbers
they shout in medical shows mean! Yay learning! &lt;a class=&#34;footnote-backref&#34; href=&#34;#fnref:4&#34; role=&#34;doc-backlink&#34;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:5&#34;&gt;
&lt;p&gt;No thanks to how much salt is in everything. I’m pretty sure I’ve had at
least twice the recommended daily amount of salt in a single serving before.
Don’t even get me started on the things that are “low salt” but are still full
of sodium from other sources. I don’t have a problem with ionic compounds, I
have a problem with sodium! &lt;a class=&#34;footnote-backref&#34; href=&#34;#fnref:5&#34; role=&#34;doc-backlink&#34;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:6&#34;&gt;
&lt;p&gt;This mainly looks like drawing graphs that are slightly different to
the graphs in the Apple Health graphs, for reasons I’m not sure I can explain.
Making graphs is fun, OK? &lt;a class=&#34;footnote-backref&#34; href=&#34;#fnref:6&#34; role=&#34;doc-backlink&#34;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:7&#34;&gt;
&lt;p&gt;Complications are basically just home screen widgets, but with a fancy
name because they’re on a watch. &lt;a class=&#34;footnote-backref&#34; href=&#34;#fnref:7&#34; role=&#34;doc-backlink&#34;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:8&#34;&gt;
&lt;p&gt;Tailscale actually causes me some problems here: everything works fine
when the watch is connected to my phone, as the phone handles the Tailscale
part, but if I’m not carrying my phone the watch will try to connect over
WiFi directly and doesn’t understand anything about Tailscale. It happens
infrequently enough that I’ll just live with it; it’s not much worse than
having no signal on a phone. &lt;a class=&#34;footnote-backref&#34; href=&#34;#fnref:8&#34; role=&#34;doc-backlink&#34;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:9&#34;&gt;
&lt;p&gt;Yay walled gardens… &lt;a class=&#34;footnote-backref&#34; href=&#34;#fnref:9&#34; role=&#34;doc-backlink&#34;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:10&#34;&gt;
&lt;p&gt;I don’t object to subscribing to things in general, but it has to be
something that’s worth the ongoing cost and offers something in return for
the subscription. A watchface doesn’t need enough ongoing maintenance to
justify subscribing to it, it’s just a cash grab. &lt;a class=&#34;footnote-backref&#34; href=&#34;#fnref:10&#34; role=&#34;doc-backlink&#34;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
</content>
    </entry>
    <entry>
        <title>Simple backups with Restic and Hetzner Cloud</title>
        <link href="https://chameth.com/simple-backups-restic-hetzner/"/>
        <updated>2024-12-06T00:00:00Z</updated>
        <id>https://chameth.com/simple-backups-restic-hetzner/</id>
        <content xml:lang="en" type="html">&lt;figure class=&#34;image right&#34;&gt;
  &lt;picture&gt;
      &lt;source srcset=&#34;https://chameth.com/simple-backups-restic-hetzner/restic.avif&#34; type=&#34;image/avif&#34;/&gt;
      &lt;source srcset=&#34;https://chameth.com/simple-backups-restic-hetzner/restic.webp&#34; type=&#34;image/webp&#34;/&gt;
      &lt;img src=&#34;https://chameth.com/simple-backups-restic-hetzner/restic.png&#34; alt=&#34;The Restic logo — a gopher with two umbrellas.&#34; loading=&#34;lazy&#34; width=&#34;400&#34; height=&#34;400&#34;/&gt;
  &lt;/picture&gt;
  &lt;figcaption&gt;&lt;p&gt;Restic’s mascot, who’s dual-wielding umbrellas to save you from a rainy day.&lt;/p&gt;
&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;I have a confession: for the past few years I’ve not been backing up any of my
computers. Everyone knows that you &lt;em&gt;should&lt;/em&gt; do backups, but actually getting
around to doing it is another story.&lt;/p&gt;
&lt;p&gt;Don’t get me wrong: most of my important things are “backed up” by virtue of
being committed to remote git repositories, or attached to e-mails, or
re-obtainable from the original source, and so on. I don’t think any machine
failing completely would be a disaster for me, but it would certainly be a pain.&lt;/p&gt;
&lt;p&gt;This week I finally got around to doing something, and it ended up being a lot
more straight forward than my previous forays into backup-land.&lt;/p&gt;
&lt;h3 id=&#34;restic&#34;&gt;Restic&lt;/h3&gt;
&lt;p&gt;After soliciting a few opinions, the choice of backup software came down to
either &lt;a href=&#34;https://www.borgbackup.org/&#34;&gt;Borg&lt;/a&gt; or &lt;a href=&#34;https://restic.net/&#34;&gt;Restic&lt;/a&gt;.
I’m pretty sure either would have done what I want, but I leaned towards Restic
for a few reasons: it has a more informative website, it’s written in Go
rather than Python&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a class=&#34;footnote-ref&#34; href=&#34;#fn:1&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt;, and Borg seems to be transitioning between major
releases at the moment&lt;sup id=&#34;fnref:2&#34;&gt;&lt;a class=&#34;footnote-ref&#34; href=&#34;#fn:2&#34; role=&#34;doc-noteref&#34;&gt;2&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;!--more--&gt;
&lt;p&gt;The way Restic works is pretty simple: you initialise a ‘repository’, and can
then call &lt;code&gt;restic backup /some/path&lt;/code&gt; and it’ll get backed up to the repository.
Restic handles keeping different backups separate, and only sending data that’s
changed, and deduplicating, and so on. You basically point it at a thing you
don’t want to lose, and it sorts it out for you. Perfect.&lt;/p&gt;
&lt;p&gt;There are equally straight-forward commands for removing old snapshots
(&lt;code&gt;restic forget&lt;/code&gt;) and verifying backups (&lt;code&gt;restic check&lt;/code&gt;). One of the nice things
about modern backup solutions is they support a whole range of backends. I was
originally going to spin up a small VPS to host my backups, but noticed that
Restic supported S3-compatible stores…&lt;/p&gt;
&lt;h3 id=&#34;hetzner-cloud-object-storage&#34;&gt;Hetzner Cloud Object Storage&lt;/h3&gt;
&lt;p&gt;I host my servers with Hetzner, and was going to use them to spin up a VPS as
well. Despite the “Cloud” branding on a bunch of products, they offer reasonable
prices and good service. A couple of months ago, they started offering
&lt;a href=&#34;https://docs.hetzner.com/storage/object-storage/overview&#34;&gt;S3-compatible object storage&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The pricing isn’t totally straight forward, but for continuous use the “free
quota” amounts to 1TB of storage and 1TB of egress a month. Ingress is free,
as is traffic within the &lt;code&gt;eu-central&lt;/code&gt; region (where all my servers are). That
quota is only awarded when you pay the “base price”, though, which is €4.99 a
month. So basically it’s €5 a month for 1TB of storage and enough egress to
fully restore every single byte. That’s better value than any VPS I can find,
much cheaper than Amazon S3, and about the same as
&lt;a href=&#34;https://www.backblaze.com/cloud-storage&#34;&gt;Backblaze B2&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id=&#34;getting-them-to-work-together&#34;&gt;Getting them to work together&lt;/h3&gt;
&lt;p&gt;Now you’d think making the tool that supports S3-compatible object storage
work with your S3-compatible object storage would be easy, right? Well not
quite. The library Restic uses to deal with S3 backends has some
strange logic for figuring out the bucket name given a URL. It doesn’t quite
seem to work right, though…&lt;/p&gt;
&lt;p&gt;Hetzner buckets have URLs like &lt;code&gt;s3://bucketname.hel1.your-objectstorage.com&lt;/code&gt;&lt;sup id=&#34;fnref:3&#34;&gt;&lt;a class=&#34;footnote-ref&#34; href=&#34;#fn:3&#34; role=&#34;doc-noteref&#34;&gt;3&lt;/a&gt;&lt;/sup&gt;,
but just passing that to Restic gives an error that the bucket is not specified.
The docs mention there’s an advanced option to make it use the virtual host for
the bucket name: &lt;code&gt;-o s3.bucket-lookup=dns&lt;/code&gt;. But that… also doesn’t work.
What I ended up doing was specifying the URL as
&lt;code&gt;s3://hel1.your-objectstorage.com/bucketname&lt;/code&gt;, and also passing in the &lt;code&gt;dns&lt;/code&gt;
option. The library then seems to muddle its way back to a real, working URL.
I’m not sure why it works like this: maybe it’s just a weird aspect of S3 that
I’m oblivious to?&lt;/p&gt;
&lt;p&gt;The next fun part is that you can configure Restic entirely by using environment
variables, except for that &lt;code&gt;-o s3.bucket-lookup=dns&lt;/code&gt; argument. That has to go
on the command line. I ended up making a little wrapper script to invoke Restic
correctly:&lt;/p&gt;
&lt;pre class=&#34;chroma-chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;&lt;span class=&#34;chroma-cp&#34;&gt;#!/bin/sh
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;&lt;span class=&#34;chroma-c1&#34;&gt;# The password restic will use to encrypt your data. You should generate&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;&lt;span class=&#34;chroma-c1&#34;&gt;# something nice and secure.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;&lt;span class=&#34;chroma-nb&#34;&gt;export&lt;/span&gt; &lt;span class=&#34;chroma-nv&#34;&gt;RESTIC_PASSWORD&lt;/span&gt;&lt;span class=&#34;chroma-o&#34;&gt;=&lt;/span&gt;repo-password
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;&lt;span class=&#34;chroma-c1&#34;&gt;# The path to the repository where restic will save the backup. In our case&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;&lt;span class=&#34;chroma-c1&#34;&gt;# this takes the form `s3:&amp;lt;endpoint&amp;gt;/&amp;lt;bucket&amp;gt;`. Your endpoint might be different&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;&lt;span class=&#34;chroma-c1&#34;&gt;# to mine depending on the region your bucket is in.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;&lt;span class=&#34;chroma-nb&#34;&gt;export&lt;/span&gt; &lt;span class=&#34;chroma-nv&#34;&gt;RESTIC_REPOSITORY&lt;/span&gt;&lt;span class=&#34;chroma-o&#34;&gt;=&lt;/span&gt;s3:hel1.your-objectstorage.com/bucket-name
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;&lt;span class=&#34;chroma-c1&#34;&gt;# The access and secret key generated in the &amp;#39;S3 credentials&amp;#39; section of the&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;&lt;span class=&#34;chroma-c1&#34;&gt;# Hetzner Cloud Console&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;&lt;span class=&#34;chroma-nb&#34;&gt;export&lt;/span&gt; &lt;span class=&#34;chroma-nv&#34;&gt;AWS_ACCESS_KEY_ID&lt;/span&gt;&lt;span class=&#34;chroma-o&#34;&gt;=&lt;/span&gt;hetzner-access-key-id
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;&lt;span class=&#34;chroma-nb&#34;&gt;export&lt;/span&gt; &lt;span class=&#34;chroma-nv&#34;&gt;AWS_SECRET_ACCESS_KEY&lt;/span&gt;&lt;span class=&#34;chroma-o&#34;&gt;=&lt;/span&gt;hetzner-access-key
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;&lt;span class=&#34;chroma-c1&#34;&gt;# Pass any arguments on to the restic command, along with the magic&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;&lt;span class=&#34;chroma-c1&#34;&gt;# `s3.bucket-lookup` option we need to resolve the S3 URL properly.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;&lt;span class=&#34;chroma-nb&#34;&gt;exec&lt;/span&gt; restic -o s3.bucket-lookup&lt;span class=&#34;chroma-o&#34;&gt;=&lt;/span&gt;dns &lt;span class=&#34;chroma-s2&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span class=&#34;chroma-nv&#34;&gt;$@&lt;/span&gt;&lt;span class=&#34;chroma-s2&#34;&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Then in the backup script I just alias &lt;code&gt;restic&lt;/code&gt; to use the script:&lt;/p&gt;
&lt;pre class=&#34;chroma-chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;&lt;span class=&#34;chroma-cp&#34;&gt;#!/bin/bash
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;&lt;span class=&#34;chroma-nb&#34;&gt;set&lt;/span&gt; -eu
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;&lt;span class=&#34;chroma-c1&#34;&gt;# This means any time we use `restic` below, we&amp;#39;ll actually execute our special&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;&lt;span class=&#34;chroma-c1&#34;&gt;# script which supplies all the env vars and arguments needed to find the&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;&lt;span class=&#34;chroma-c1&#34;&gt;# repository. Make sure the path matches where you saved the script!&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;&lt;span class=&#34;chroma-nb&#34;&gt;alias&lt;/span&gt; &lt;span class=&#34;chroma-nv&#34;&gt;restic&lt;/span&gt;&lt;span class=&#34;chroma-o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;chroma-s1&#34;&gt;&amp;#39;~/.bin/restic&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;&lt;span class=&#34;chroma-c1&#34;&gt;# Actually do the backup. Each directory in the list below will be backed up&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;&lt;span class=&#34;chroma-c1&#34;&gt;# separately.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;&lt;span class=&#34;chroma-nv&#34;&gt;dirs&lt;/span&gt;&lt;span class=&#34;chroma-o&#34;&gt;=(&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;	&lt;span class=&#34;chroma-s2&#34;&gt;&amp;#34;/some/path/to/backup/&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;	&lt;span class=&#34;chroma-s2&#34;&gt;&amp;#34;/some/other/path/&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;&lt;span class=&#34;chroma-o&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;&lt;span class=&#34;chroma-k&#34;&gt;for&lt;/span&gt; i in &lt;span class=&#34;chroma-s2&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span class=&#34;chroma-si&#34;&gt;${&lt;/span&gt;&lt;span class=&#34;chroma-nv&#34;&gt;dirs&lt;/span&gt;&lt;span class=&#34;chroma-p&#34;&gt;[@]&lt;/span&gt;&lt;span class=&#34;chroma-si&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;chroma-s2&#34;&gt;&amp;#34;&lt;/span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;&lt;span class=&#34;chroma-k&#34;&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;	&lt;span class=&#34;chroma-nb&#34;&gt;echo&lt;/span&gt; &lt;span class=&#34;chroma-nv&#34;&gt;$i&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;	&lt;span class=&#34;chroma-o&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;chroma-nb&#34;&gt;cd&lt;/span&gt; &lt;span class=&#34;chroma-nv&#34;&gt;$i&lt;/span&gt; &lt;span class=&#34;chroma-o&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt; restic --verbose backup .&lt;span class=&#34;chroma-o&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;&lt;span class=&#34;chroma-k&#34;&gt;done&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;&lt;span class=&#34;chroma-c1&#34;&gt;# Prune our snapshots. You can tweak the numbers here. Run with `--dry-run` to&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;&lt;span class=&#34;chroma-c1&#34;&gt;# see what effect any changes would have before actually committing to them. &lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;restic forget --keep-daily &lt;span class=&#34;chroma-m&#34;&gt;7&lt;/span&gt; --keep-weekly &lt;span class=&#34;chroma-m&#34;&gt;10&lt;/span&gt; --keep-monthly &lt;span class=&#34;chroma-m&#34;&gt;24&lt;/span&gt; --keep-yearly &lt;span class=&#34;chroma-m&#34;&gt;10&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The only other notable thing here is that the script changes into the directory
to be backed up. If you give Restic an absolute path, it will create a new
snapshot if the metadata of any folder in the path changes, which is not what
I want.&lt;/p&gt;
&lt;p&gt;If it wasn’t for the S3 URL issues, the whole thing would’ve probably taken
me about half an hour. That’s including setting up the object storage,
installing Restic, and so on. It’s painfully easy. Why didn’t I do this three
years ago?!&lt;/p&gt;
&lt;h3 id=&#34;addendum-a-step-by-step-guide&#34;&gt;Addendum: a step-by-step guide&lt;/h3&gt;
&lt;aside class=&#34;update raised-box&#34;&gt;
  &lt;h5 class=&#34;plain-header&#34;&gt;Update 2025-03-01:&lt;/h5&gt;
  &lt;p&gt;This section was added after the original article was published, following some
helpful feedback. Let me know if you have any problem with these instructions!&lt;/p&gt;
&lt;/aside&gt;
&lt;p&gt;If you want to do this yourself, here’s a quick step-by-step guide:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Log in to the &lt;a href=&#34;https://console.hetzner.cloud&#34;&gt;Hetzner Cloud Console&lt;/a&gt;, and
create a project.&lt;/li&gt;
&lt;li&gt;On the “Object Storage” tab, create a new bucket. Note the name and the
endpoint.&lt;/li&gt;
&lt;li&gt;On the “Security” tab, go to “S3 Credentials” and generate new credentials.
Note down the access key and the secret key.&lt;/li&gt;
&lt;li&gt;Copy the first shell script above, fill in the password (you can pick!),
endpoint, bucket name, access key and secret key.&lt;/li&gt;
&lt;li&gt;Run the script with the &lt;code&gt;init&lt;/code&gt; argument (e.g. &lt;code&gt;~/.bin/restic init&lt;/code&gt;). This
will create a new repository, and only needs to be done once even if you
backup multiple machines.&lt;/li&gt;
&lt;li&gt;Copy the second shell script above, making sure the &lt;code&gt;restic&lt;/code&gt; alias points
at the script you saved in step 4. Change the list of directories to
whatever you want to backup.&lt;/li&gt;
&lt;li&gt;Schedule the script to be run automatically, using crontab or systemd timers
or however you prefer.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;To check everything is working, you can use the &lt;code&gt;snapshots&lt;/code&gt; subcommand to see
a list of saved snapshots. You might also want to try to restore a snapshot
using the &lt;code&gt;restore&lt;/code&gt; subcommand.&lt;/p&gt;
&lt;div class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;
&lt;hr/&gt;
&lt;ol&gt;
&lt;li id=&#34;fn:1&#34;&gt;
&lt;p&gt;I’m not trying to be a language snob, but given the choice between two
otherwise equal projects one in Go and one in Python, I’ll take the Go one.
I know I’m not going to have weird library issues down the line, and I’m much
more comfortable rummaging around the source. &lt;a class=&#34;footnote-backref&#34; href=&#34;#fnref:1&#34; role=&#34;doc-backlink&#34;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:2&#34;&gt;
&lt;p&gt;With a “Don’t use this in production!” notice on the shiny new version. &lt;a class=&#34;footnote-backref&#34; href=&#34;#fnref:2&#34; role=&#34;doc-backlink&#34;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:3&#34;&gt;
&lt;p&gt;Aside: I really hate URLs like that. What is with the trend for completely
generic domains divorced from the service they’re a part of? &lt;a class=&#34;footnote-backref&#34; href=&#34;#fnref:3&#34; role=&#34;doc-backlink&#34;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
</content>
    </entry>
    <entry>
        <title>Reverse engineering an Arctis Pro Wireless Headset</title>
        <link href="https://chameth.com/reverse-engineering-arctis-pro-wireless-headset/"/>
        <updated>2021-06-12T00:00:00Z</updated>
        <id>https://chameth.com/reverse-engineering-arctis-pro-wireless-headset/</id>
        <content xml:lang="en" type="html">&lt;figure class=&#34;image left&#34;&gt;
  &lt;picture&gt;
      &lt;source srcset=&#34;https://chameth.com/reverse-engineering-arctis-pro-wireless-headset/headset.avif&#34; type=&#34;image/avif&#34;/&gt;
      &lt;source srcset=&#34;https://chameth.com/reverse-engineering-arctis-pro-wireless-headset/headset.webp&#34; type=&#34;image/webp&#34;/&gt;
      &lt;img src=&#34;https://chameth.com/reverse-engineering-arctis-pro-wireless-headset/headset.png&#34; alt=&#34;Boxed SteelSeries Arctis Pro Wireless Headset&#34; loading=&#34;lazy&#34; width=&#34;300&#34; height=&#34;380&#34;/&gt;
  &lt;/picture&gt;
  &lt;figcaption&gt;&lt;p&gt;The Arctis Pro Wireless Headset.&lt;/p&gt;
&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;For the last year and a bit, I’ve been using a &lt;a href=&#34;https://steelseries.com/gaming-headsets/arctis-pro-wireless&#34;&gt;SteelSeries Arctis Pro Wireless Headset&lt;/a&gt;
for gaming and talking to friends. It’s a fine headset, but because there’s an always-on receiver there’s no
way to detect if the headset is turned on or not from the desktop.&lt;/p&gt;
&lt;p&gt;Whenever I start using the headset, I set my desktop’s sound to go to the headset, and then when I stop using
the headset I set it to go back to speakers. It doesn’t take more than a second, but some days I might put the
headset on a dozen times as I’m on calls, or if it’s noisy outside, etc. That means it’s probably
&lt;a href=&#34;https://xkcd.com/1205/&#34;&gt;worth at least a few hours of my time&lt;/a&gt; trying to automate it.&lt;/p&gt;
&lt;p&gt;At first, I hoped I’d be able to tell from the state of the USB device whether there was a headset
connected but nothing at all changed when flipping it on and off. Then I went hunting for existing
open source tools that might work with it and found that while people have reverse engineered many
of the older Arctis headsets, no one has done the same for the Pro Wireless. I finished off with
a search to see if anyone had documented the wire protocol even if there was no nice open source
software to go with it; I came up short there, too. Looks like I’d have to do it myself.&lt;/p&gt;
&lt;!--more--&gt;
&lt;h3 id=&#34;capturing-data-with-wireshark&#34;&gt;Capturing data with WireShark&lt;/h3&gt;
&lt;p&gt;The headset exposes a Human Interface Device (HID), and the wire protocols for earlier versions
of the Arctis series looked to be very simple messages passed over the HID connection. It should
therefore be fairly easy to use &lt;a href=&#34;https://www.wireshark.org/&#34;&gt;WireShark&lt;/a&gt; to capture the data
being sent and received by the official SteelSeries application&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a class=&#34;footnote-ref&#34; href=&#34;#fn:1&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;p&gt;After setting up WireShark and making it record only the headset’s HID connection, it quickly
became apparent that the software was sending three different requests each second, over and
over again:&lt;/p&gt;
&lt;figure class=&#34;image full&#34;&gt;
  &lt;picture&gt;
      &lt;source srcset=&#34;https://chameth.com/reverse-engineering-arctis-pro-wireless-headset/wireshark1.avif&#34; type=&#34;image/avif&#34;/&gt;
      &lt;source srcset=&#34;https://chameth.com/reverse-engineering-arctis-pro-wireless-headset/wireshark1.webp&#34; type=&#34;image/webp&#34;/&gt;
      &lt;img src=&#34;https://chameth.com/reverse-engineering-arctis-pro-wireless-headset/wireshark1.png&#34; alt=&#34;Wireshark, showing a packet capture of a SET_REPORT request from the host to the headset&#34; loading=&#34;lazy&#34; width=&#34;1007&#34; height=&#34;824&#34;/&gt;
  &lt;/picture&gt;
  &lt;figcaption&gt;&lt;p&gt;Wireshark, showing a packet capture of a SET_REPORT request from the host to the headset&lt;/p&gt;
&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;The packets we’re interested in are the &lt;code&gt;SET_REPORT Request&lt;/code&gt; frames sent from the host to the
device. Wireshark understands the HID protocol, so it nicely shows us the raw HID data; in the
screenshot this is &lt;code&gt;0x40AA&lt;/code&gt; (ignoring the trailing zero bytes). The other requests sent immediately
after have &lt;code&gt;0x41AA&lt;/code&gt; and &lt;code&gt;0x42AA&lt;/code&gt; payloads — clearly the first byte is indicating which piece
of data is being requested.&lt;/p&gt;
&lt;p&gt;The responses to these requests come back in an &lt;code&gt;URB_INTERRUPT in&lt;/code&gt; frame:&lt;/p&gt;
&lt;figure class=&#34;image full&#34;&gt;
  &lt;picture&gt;
      &lt;source srcset=&#34;https://chameth.com/reverse-engineering-arctis-pro-wireless-headset/wireshark2.avif&#34; type=&#34;image/avif&#34;/&gt;
      &lt;source srcset=&#34;https://chameth.com/reverse-engineering-arctis-pro-wireless-headset/wireshark2.webp&#34; type=&#34;image/webp&#34;/&gt;
      &lt;img src=&#34;https://chameth.com/reverse-engineering-arctis-pro-wireless-headset/wireshark2.png&#34; alt=&#34;Wireshark, showing a packet capture of the headset&amp;#39;s response to the earlier request&#34; loading=&#34;lazy&#34; width=&#34;1007&#34; height=&#34;824&#34;/&gt;
  &lt;/picture&gt;
  &lt;figcaption&gt;&lt;p&gt;Wireshark, showing a packet capture of the headset’s response to the earlier request&lt;/p&gt;
&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;So the answer to our first request appears to be &lt;code&gt;0x04&lt;/code&gt;. The second response is &lt;code&gt;0x0402&lt;/code&gt; and the third is &lt;code&gt;0x04&lt;/code&gt; again.
The first thing I tried doing was popping the spare battery out of the charger. The response to the &lt;code&gt;0x42AA&lt;/code&gt; request
changed from &lt;code&gt;0x04&lt;/code&gt; to &lt;code&gt;0x00&lt;/code&gt; - that’s certainly clear enough! After a bit of waiting around, the battery in my headset
dropped from full on the display to three bars, and at that point the &lt;code&gt;0x40AA&lt;/code&gt; response dropped from &lt;code&gt;0x04&lt;/code&gt; to &lt;code&gt;0x03&lt;/code&gt;.
The display on the receiver shows battery state as four bars, and it appears the wire protocol directly corresponds to
that particular representation.&lt;/p&gt;
&lt;p&gt;That left the &lt;code&gt;0x41AA&lt;/code&gt; request as an unknown. I tried everything I could think of, but it stubbornly kept returning
&lt;code&gt;0x0402&lt;/code&gt;. I enlisted a friend who has the same headset to run some hacky Go code and report his values, and he also
got a &lt;code&gt;0x0402&lt;/code&gt; response. As I explained that I couldn’t figure out what these values are, he reported back that turning
his headset off made the response change to &lt;code&gt;0x0202&lt;/code&gt;. In all my testing, I’d forgotten to try turning the headset off!
That’s the one thing I was actually trying to detect, as well. Thanks, Simon, for helping me get past that bit of
stupidity!&lt;/p&gt;
&lt;p&gt;I still don’t know what the second byte of the response is, or whether there are other values than &lt;code&gt;0x04&lt;/code&gt; for on
and &lt;code&gt;0x02&lt;/code&gt; for off, but I’m happy enough to label it as “device status” and move on.&lt;/p&gt;
&lt;h3 id=&#34;exploring-other-features&#34;&gt;Exploring other features&lt;/h3&gt;
&lt;p&gt;The software allows you to tweak a bunch of different settings:&lt;/p&gt;
&lt;figure class=&#34;image full&#34;&gt;
  &lt;picture&gt;
      &lt;source srcset=&#34;https://chameth.com/reverse-engineering-arctis-pro-wireless-headset/settings2.avif&#34; type=&#34;image/avif&#34;/&gt;
      &lt;source srcset=&#34;https://chameth.com/reverse-engineering-arctis-pro-wireless-headset/settings2.webp&#34; type=&#34;image/webp&#34;/&gt;
      &lt;img src=&#34;https://chameth.com/reverse-engineering-arctis-pro-wireless-headset/settings2.png&#34; alt=&#34;SteelSeries Good Game software, showing the advanced headset settings&#34; loading=&#34;lazy&#34; width=&#34;1133&#34; height=&#34;836&#34;/&gt;
  &lt;/picture&gt;
  &lt;figcaption&gt;&lt;p&gt;SteelSeries Good Game software, showing the advanced headset settings&lt;/p&gt;
&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;I went through each one and fiddled with all the values, recording the requests in WireShark as I did so. It turns
out the protocol is very simplistic - the wire protocol directly corresponds to the UI elements in the software (or
on the receiver, if you navigate through its menus). For example, the software allows you to set the “Headset auto
shutoff” value in increments of 10 minutes. You might usually expect this to be converted to seconds or something
similar before being passed to the device, but on the wire it’s actually sent as &lt;code&gt;0x00&lt;/code&gt; for off, &lt;code&gt;0x01&lt;/code&gt; for 10 minutes,
up through to &lt;code&gt;0x0C&lt;/code&gt; for the maximum of 120 minutes.&lt;/p&gt;
&lt;p&gt;All the dropdown options seem to function this way — if you pick the 6th option then the request payload will be
an &lt;code&gt;0x06&lt;/code&gt; byte. The sliders have fixed positions they snap to and function similarly: for the two brightness sliders
pictured they snap to 11 positions and on the wire these range from &lt;code&gt;0x00&lt;/code&gt; for off to &lt;code&gt;0x0A&lt;/code&gt; for the maximum.&lt;/p&gt;
&lt;p&gt;At this point I was well past my goal of being able to detect whether the headset was on or off, and I was now just
trying to see if I could figure out enough of the protocol that I could reimplement the control software on Linux if
I ever wanted to. Just as I was about to close the app, something caught my eye: there are integrations with games
and other software that can display information on the receiver’s OLED display!&lt;/p&gt;
&lt;p&gt;There’s an &lt;a href=&#34;https://github.com/SteelSeries/gamesense-sdk&#34;&gt;official API&lt;/a&gt; for this, but it involves sending JSON to
a webserver which runs as part of their app&lt;sup id=&#34;fnref:2&#34;&gt;&lt;a class=&#34;footnote-ref&#34; href=&#34;#fn:2&#34; role=&#34;doc-noteref&#34;&gt;2&lt;/a&gt;&lt;/sup&gt;. That doesn’t feel very nice, and definitely won’t work for me on Linux,
so I started a new WireShark session and took some captures while I spoke to myself on Discord.&lt;/p&gt;
&lt;h3 id=&#34;decoding-pixels&#34;&gt;Decoding pixels&lt;/h3&gt;
&lt;p&gt;The frames sent to the device whenever Discord showed a notification had a 1060 byte payload. The display on the
receiver is 140 pixels wide&lt;sup id=&#34;fnref:3&#34;&gt;&lt;a class=&#34;footnote-ref&#34; href=&#34;#fn:3&#34; role=&#34;doc-noteref&#34;&gt;3&lt;/a&gt;&lt;/sup&gt; and each pixel can only seem to be on or off, so I figured each bit in the payload
corresponded to one bit of the output. I exported the data to binary in the hope that I could visually see what was
going on - in theory if I line wrap the data at 140 characters it should look vaguely like the final output.
Unfortunately, it did not. There was roughly the right number of high bits, but no amount of fiddling with them in
a text editor could give me a coherent picture.&lt;/p&gt;
&lt;p&gt;Instead, I wrote some code to write different values to the device. Starting with a payload of all zeroes and gradually
increasing a bit at a time every second. After the first few seconds, I saw a line of pixels being drawn downwards on
the left of the screen; had I just got the axes the wrong way around? After the first eight pixels lit up, though, they
jumped over to the next column. The actual addressing scheme looks something like this:&lt;/p&gt;
&lt;figure class=&#34;image full&#34;&gt;
  &lt;picture&gt;
      &lt;source srcset=&#34;https://chameth.com/reverse-engineering-arctis-pro-wireless-headset/display.avif&#34; type=&#34;image/avif&#34;/&gt;
      &lt;source srcset=&#34;https://chameth.com/reverse-engineering-arctis-pro-wireless-headset/display.webp&#34; type=&#34;image/webp&#34;/&gt;
      &lt;img src=&#34;https://chameth.com/reverse-engineering-arctis-pro-wireless-headset/display.png&#34; alt=&#34;Diagram of how pixels are addressed on the receiver&amp;#39;s OLED display&#34; loading=&#34;lazy&#34; width=&#34;358&#34; height=&#34;647&#34;/&gt;
  &lt;/picture&gt;
  &lt;figcaption&gt;&lt;p&gt;Diagram of how pixels are addressed on the receiver’s OLED display&lt;/p&gt;
&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;So the first 140 bytes given the pixels for the first 8 rows, the next 140 bytes fill in the 8 rows below that, and
so on and so forth. Armed with this information I wrote a simple program to read an image and output it to the
display:&lt;/p&gt;
&lt;figure class=&#34;image full&#34;&gt;
  &lt;picture&gt;
      &lt;source srcset=&#34;https://chameth.com/reverse-engineering-arctis-pro-wireless-headset/helloworld.avif&#34; type=&#34;image/avif&#34;/&gt;
      &lt;source srcset=&#34;https://chameth.com/reverse-engineering-arctis-pro-wireless-headset/helloworld.webp&#34; type=&#34;image/webp&#34;/&gt;
      &lt;img src=&#34;https://chameth.com/reverse-engineering-arctis-pro-wireless-headset/helloworld.png&#34; alt=&#34;Headset receiver displaying a custom Hello World message&#34; loading=&#34;lazy&#34; width=&#34;500&#34; height=&#34;259&#34;/&gt;
  &lt;/picture&gt;
  &lt;figcaption&gt;&lt;p&gt;Headset receiver displaying a custom Hello World message&lt;/p&gt;
&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;At this point I was thinking about trying to get Doom rendering on the screen, but I couldn’t find anything nicely
hackable that would let me grab the output and pass it on to the receiver. Instead, I decided to try a GIF decoder
and before very long had a nice little animated display:&lt;/p&gt;
&lt;figure class=&#34;video full&#34;&gt;
  &lt;video src=&#34;https://chameth.com/reverse-engineering-arctis-pro-wireless-headset/nyan.webm&#34; alt=&#34;Video of receiver playing Nyan cat gif&#34; controls=&#34;&#34;&gt;&lt;/video&gt;
&lt;/figure&gt;
&lt;h3 id=&#34;protocol-reference&#34;&gt;Protocol reference&lt;/h3&gt;
&lt;p&gt;All the HID messages have a single byte that determines the message type, then an &lt;code&gt;0xAA&lt;/code&gt; byte, then any payload
required by the command. These are the ones I’ve figured out:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Byte&lt;/th&gt;
&lt;th&gt;Command&lt;/th&gt;
&lt;th&gt;Payload&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;0x09&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Save changes&lt;/td&gt;
&lt;td&gt;None.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;0x10&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Request firmware version(?)&lt;/td&gt;
&lt;td&gt;None.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;0x27&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Volume limiter&lt;/td&gt;
&lt;td&gt;&lt;code&gt;0x00&lt;/code&gt; for off, &lt;code&gt;0x01&lt;/code&gt; for on.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;0x2E&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Equalizer preset&lt;/td&gt;
&lt;td&gt;ID of the equalizer preset to use.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;0x39&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Sidetone level&lt;/td&gt;
&lt;td&gt;&lt;code&gt;0x00&lt;/code&gt; for lowest to &lt;code&gt;0x09&lt;/code&gt; for highest.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;0x3C&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Set headset timeout&lt;/td&gt;
&lt;td&gt;Timeout, as a number of 10 minutes. &lt;code&gt;0x00&lt;/code&gt; for off to &lt;code&gt;0x0C&lt;/code&gt; for 120 mins.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;0x3E&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Mic mute LED brightness&lt;/td&gt;
&lt;td&gt;&lt;code&gt;0x00&lt;/code&gt; for lowest to &lt;code&gt;0x0A&lt;/code&gt; for highest.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;0x40&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Request headset battery&lt;/td&gt;
&lt;td&gt;None.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;0x41&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Request device status&lt;/td&gt;
&lt;td&gt;None.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;0x42&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Request receiver battery&lt;/td&gt;
&lt;td&gt;None.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;0x51&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Surround sound mode&lt;/td&gt;
&lt;td&gt;&lt;code&gt;0x00&lt;/code&gt; for off, &lt;code&gt;0x01&lt;/code&gt; for on.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;0x62&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Auto-start Bluetooth&lt;/td&gt;
&lt;td&gt;&lt;code&gt;0x00&lt;/code&gt; for off, &lt;code&gt;0x01&lt;/code&gt; for on.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;0x63&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Auto-mute game audio during calls&lt;/td&gt;
&lt;td&gt;&lt;code&gt;0x00&lt;/code&gt; for off, &lt;code&gt;0x01&lt;/code&gt; for on.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;0x85&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;OLED brightness&lt;/td&gt;
&lt;td&gt;&lt;code&gt;0x00&lt;/code&gt; for lowest to &lt;code&gt;0x0A&lt;/code&gt; for highest.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;0x83&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Equalizer&lt;/td&gt;
&lt;td&gt;(Not yet decoded)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;0x89&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Screensaver mode&lt;/td&gt;
&lt;td&gt;&lt;code&gt;0x00&lt;/code&gt; to dim, &lt;code&gt;0x01&lt;/code&gt; for off, &lt;code&gt;0x02&lt;/code&gt; for screensaver.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;0xD2&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Render image&lt;/td&gt;
&lt;td&gt;Pixel array as described above.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Some of these I’ve not dug too much into because they didn’t seem very interesting. If you have an Arctis Pro Wireless
and figure anything more out, let me know, and I’ll update the list.&lt;/p&gt;
&lt;p&gt;If you just want to check device state like I originally did, I’ve contributed support for this headset to the
excellent &lt;a href=&#34;https://github.com/Sapd/HeadsetControl&#34;&gt;HeadsetControl&lt;/a&gt; project. It should be in the next release.&lt;/p&gt;
&lt;div class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;
&lt;hr/&gt;
&lt;ol&gt;
&lt;li id=&#34;fn:1&#34;&gt;
&lt;p&gt;Unfortunately (as you’d expect) their application only runs on Windows, so this process
involved an annoying amount of rebooting to Windows, fleeing back to Linux, and then
realising I hadn’t actually recorded enough to figure it out and repeating. &lt;a class=&#34;footnote-backref&#34; href=&#34;#fnref:1&#34; role=&#34;doc-backlink&#34;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:2&#34;&gt;
&lt;p&gt;You can also send lisp to the webserver, and it will execute it. No comment. &lt;a class=&#34;footnote-backref&#34; href=&#34;#fnref:2&#34; role=&#34;doc-backlink&#34;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:3&#34;&gt;
&lt;p&gt;I know this not because it’s mentioned in the technical specs (it’s not), but because I took a photo and counted
them out one by one. &lt;a class=&#34;footnote-backref&#34; href=&#34;#fnref:3&#34; role=&#34;doc-backlink&#34;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
</content>
    </entry>
    <entry>
        <title>Why you should be using HTTPS</title>
        <link href="https://chameth.com/why-you-should-be-using-https/"/>
        <updated>2016-06-17T00:00:00Z</updated>
        <id>https://chameth.com/why-you-should-be-using-https/</id>
        <content xml:lang="en" type="html">&lt;figure class=&#34;image left&#34;&gt;
  &lt;picture&gt;
      &lt;source srcset=&#34;https://chameth.com/why-you-should-be-using-https/https-everywhere.avif&#34; type=&#34;image/avif&#34;/&gt;
      &lt;source srcset=&#34;https://chameth.com/why-you-should-be-using-https/https-everywhere.webp&#34; type=&#34;image/webp&#34;/&gt;
      &lt;img src=&#34;https://chameth.com/why-you-should-be-using-https/https-everywhere.jpg&#34; alt=&#34;The EFF&amp;#39;s HTTPS Everywhere logo&#34; loading=&#34;lazy&#34; width=&#34;300&#34; height=&#34;260&#34;/&gt;
  &lt;/picture&gt;
  &lt;figcaption&gt;&lt;p&gt;The EFF’s HTTPS Everywhere logo&lt;/p&gt;
&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;One of my favourite hobbyhorses recently has been the use of HTTPS, or lack thereof. HTTPS is the
thing that makes the little padlock appear in your browser, and has existed for over 20 years.
In the past, that little padlock was the exclusive preserve of banks and other ‘high security’
establishments; over time its use has gradually expanded to most (but not all) websites
that handle user information, and the time is now right for it to become ubiquitous.&lt;/p&gt;
&lt;h3 id=&#34;why-use-https&#34;&gt;Why use HTTPS?&lt;/h3&gt;
&lt;p&gt;There are numerous advantages to using HTTPS, both for the users of a website and for the
operator:&lt;/p&gt;
&lt;h4 id=&#34;privacy&#34;&gt;Privacy&lt;/h4&gt;
&lt;p&gt;The most obvious advantage is that HTTPS gives your users additional privacy. An insecure (HTTP)
request can potentially be read by anyone on the same network, or the network operators, or anyone
who happens to operate a network along the path between the user and the server.&lt;/p&gt;
&lt;p&gt;Users on shared WiFi networks (such as those in coffee shops, hotels, or offices) are particularly
vulnerable to passive sniffing by anyone else on that network. If the network is open (as is
frequently the case) then anyone in radio range can see exactly what the user is up to.&lt;/p&gt;
&lt;!--more--&gt;
&lt;h4 id=&#34;integrity&#34;&gt;Integrity&lt;/h4&gt;
&lt;p&gt;HTTPS also helps to maintain the integrity of your site. With a plain HTTP request, there’s nothing
to stop anyone in between the server and the user from modifying the content of the request or the
response. This is a frequent tactic used by annoying WiFi gateways (such as the ones &lt;a href=&#34;http://justinsomnia.org/2012/04/hotel-wifi-javascript-injection/&#34;&gt;you’d find in
a hotel&lt;/a&gt;), dubious ISPs who want
to serve you extra adverts, or just plain old nefarious attackers.&lt;/p&gt;
&lt;p&gt;If you’re trying to convey some kind of information to users (and if you aren’t, why exactly are
you running a website again?) it seems beneficial to both you and them if the information arrives
as you intended, rather than in a modified form due to someone or something tampering with it.&lt;/p&gt;
&lt;h4 id=&#34;security&#34;&gt;Security&lt;/h4&gt;
&lt;p&gt;If your website has any kind of authentication, or session identifiers, it becomes extremely
vulnerable to an attacker monitoring the traffic and stealing the credentials. This was
starkly demonstrated in 2010 when &lt;a href=&#34;https://en.wikipedia.org/wiki/Firesheep&#34;&gt;Firesheep&lt;/a&gt; was
released. This tool allowed anyone to quickly and automatically hijack social media accounts of
anyone on the same network who was using HTTP to access them.&lt;/p&gt;
&lt;p&gt;Even if your login pages are served over HTTPS, if you send a single session ID cookie over HTTP
(such as a page you decided wasn’t particularly ‘important’) then an attacker can probably spoof
the user’s session and gain full access to their account.  Again, in the case of open WiFi networks
that could be anyone in radio range.&lt;/p&gt;
&lt;h4 id=&#34;search-engine-rankings&#34;&gt;Search engine rankings&lt;/h4&gt;
&lt;p&gt;Some search engines use HTTPS as a signal in their ranking algorithms. &lt;a href=&#34;https://security.googleblog.com/2014/08/https-as-ranking-signal_6.html&#34;&gt;Google announced in
2004&lt;/a&gt; that it was using
the presence of HTTPS as a small positive signal, but that it may strengthen that signal over time
as more and more websites switch to using a secure transport. It’s not unthinkable that at some
point in the future there will be HTTPS-only search engines.&lt;/p&gt;
&lt;h3 id=&#34;but-but-but&#34;&gt;But… But… But…&lt;/h3&gt;
&lt;p&gt;There are lots of excuses for not implementing HTTPS. Most of them are either misguided or outdated.&lt;/p&gt;
&lt;h4 id=&#34;its-too-expensive-andor-complicated&#34;&gt;It’s too expensive and/or complicated&lt;/h4&gt;
&lt;p&gt;In the past, getting HTTPS certificates was a pain. A number of free suppliers have existed for
a while but the process for getting their certificates wasn’t particularly straight forward, and
many imposed arbitrary restrictions on the certificate parameters. Even once you had the
certificate, you had to fiddle about with your HTTP server configuration to make it work, remember
to manually get a new certificate when the old one expired, and lots of other annoying busywork.&lt;/p&gt;
&lt;p&gt;With the arrival of &lt;a href=&#34;https://letsencrypt.org/&#34;&gt;Let’s Encrypt&lt;/a&gt;, all that changed. You can retrieve
and deploy a free HTTPS certificate with two or three commands. Renewal can be handled completely
automatically with a single command executed by cron.&lt;/p&gt;
&lt;h4 id=&#34;theres-no-point-nothing-on-my-site-is-sensitive&#34;&gt;There’s no point; nothing on my site is sensitive&lt;/h4&gt;
&lt;p&gt;You might not think your content warrants privacy, but can you speak for everyone who accesses it?
Even content that seems mundane to you — such as travel advice, or technical writing — could be
used to build up a profile of a user. If an attacker is monitoring traffic in a coffee shop and
sees a user looking at travel advice and weather forecasts for a foreign country, he could use that
information to plan a burglary knowing that the user will be away. Similarly, some content which
is perfectly mundane to you may actually be very sensitive in other countries with repressive
governments. HTTPS makes it much harder for these people to snoop on traffic.&lt;/p&gt;
&lt;p&gt;From another angle, if you’re offering any kind of information, instructions, or especially file
downloads, there’s a severe risk to users if the content is modified on its way to them. An evil
sysadmin could rewrite your travel advice to suggest visiting the local drug dealer’s hangout, or
replace your download with a malware-infested version.&lt;/p&gt;
&lt;h4 id=&#34;https-is-slower-uses-more-resources-etc&#34;&gt;HTTPS is slower, uses more resources, etc&lt;/h4&gt;
&lt;p&gt;Back in 1995 this might have been a valid argument. Enabling HTTPS on a modern server will make
an almost negligible difference to performance. If you also enable HTTP/2 (which most
implementations only support over HTTPS), it’s likely to actually use fewer resources, and result
in a faster, smoother experience for your users. HTTP/2 was designed to work with HTTPS, and
designed with modern requirements and networking techniques in mind.&lt;/p&gt;
&lt;p&gt;CloudFlare have an &lt;a href=&#34;https://www.cloudflare.com/http2/&#34;&gt;excellent demonstration&lt;/a&gt; of the benefits of
HTTP/2, and it can show speed improvements of 2-3x in a typical environment. On top of being faster,
HTTP/2 uses fewer connections which results in less resource overhead on both the server and the
client.&lt;/p&gt;
&lt;h3 id=&#34;so-what-are-you-waiting-for&#34;&gt;So what are you waiting for?&lt;/h3&gt;
&lt;p&gt;If you run a website and aren’t using HTTPS, &lt;a href=&#34;https://certbot.eff.org/&#34;&gt;give it a try&lt;/a&gt;.&lt;/p&gt;
</content>
    </entry>
</feed>
