<?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 debugging-beyond-the-debugger, sense-api, thinking-more-about-backups but not how-i-get-things-done, why-you-should-be-using-https</title>
    <subtitle>Personal homepage of Chris Smith</subtitle>
    <link href="https://chameth.com/feeds/posts/like/debugging-beyond-the-debugger,sense-api,thinking-more-about-backups/unlike/how-i-get-things-done,why-you-should-be-using-https/" 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>Debugging beyond the debugger</title>
        <link href="https://chameth.com/debugging-beyond-the-debugger/"/>
        <updated>2019-05-08T00:00:00Z</updated>
        <id>https://chameth.com/debugging-beyond-the-debugger/</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/debugging-beyond-the-debugger/tools.avif&#34; type=&#34;image/avif&#34;/&gt;
      &lt;source srcset=&#34;https://chameth.com/debugging-beyond-the-debugger/tools.webp&#34; type=&#34;image/webp&#34;/&gt;
      &lt;img src=&#34;https://chameth.com/debugging-beyond-the-debugger/tools.jpg&#34; alt=&#34;Collection of tools hanging on a wall&#34; loading=&#34;lazy&#34; width=&#34;300&#34; height=&#34;396&#34;/&gt;
  &lt;/picture&gt;
  &lt;figcaption&gt;&lt;p&gt;Real-life debugging tools&lt;/p&gt;
&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;Most programming — and sysadmin — problems can be debugged in a
fairly straight forward manner using logs, print statements,
educated guesses, or an actual debugger. Sometimes, though, the
problem is more elusive. There’s a wider box of tricks that can
be employed in these cases but I’ve not managed to find a nice
overview of them, so here’s mine. I’m mainly focusing on Linux
and similar systems, but there tend to be alternatives available
for other Operating Systems or VMs if you seek them out.&lt;/p&gt;
&lt;h3 id=&#34;networking&#34;&gt;Networking&lt;/h3&gt;
&lt;h4 id=&#34;tcpdump&#34;&gt;tcpdump&lt;/h4&gt;
&lt;p&gt;&lt;code&gt;tcpdump&lt;/code&gt; prints out descriptions of packets on a network interface. You can
apply filters to limit which packets are displayed, chose to dump the entire
content of the packet, and so forth.&lt;/p&gt;
&lt;!--more--&gt;
&lt;p&gt;Typical usage might look something like:&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;# tcpdump -nSi eth0 port 80
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;listening on eth0, link-type EN10MB (Ethernet), capture size 262144 bytes
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;16:03:35.577781 IP6 2001:db8::1.54742 &amp;gt; 2001:db8::2.80: Flags [S], seq 2815779044, win 64800, options [mss 1440,sackOK,TS val 2378811665 ecr 0,nop,wscale 7], length 0
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;16:03:35.586853 IP6 2001:db8::2.80 &amp;gt; 2001:db8::1.54742: Flags [S.], seq 1522609102, ack 2815779045, win 28560, options [mss 1440,sackOK,TS val 3063610173 ecr 2378811665,nop,wscale 7], length 0
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;16:03:35.586877 IP6 2001:db8::1.54742 &amp;gt; 2001:db8::2.80: Flags [.], ack 1522609103, win 507, options [nop,nop,TS val 2378811674 ecr 3063610173], length 0
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;16:03:35.620678 IP6 2001:db8::1.54742 &amp;gt; 2001:db8::2.80: Flags [P.], seq 2815779045:2815779399, ack 1522609103, win 507, options [nop,nop,TS val 2378811708 ecr 3063610173], length 354: HTTP: GET / HTTP/1.1
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Here you can see the start of a plaintext HTTP request: the three-way
handshake as the TCP connection is established followed by a GET request.
Even if the data is encrypted as it will be in most cases, it’s often useful
to see the “shape” of the transmissions: did the client start sending data
when it connected, did the server ever respond, etc.&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://danielmiessler.com/study/tcpdump/&#34;&gt;Daniel Miessler has a good tutorial on tcpdump&lt;/a&gt;
if you’re not familiar with it and don’t want to jump straight into the man
page.&lt;/p&gt;
&lt;h5 id=&#34;-with-docker&#34;&gt;… with Docker&lt;/h5&gt;
&lt;p&gt;Docker sets up separate network namespaces for each container. To see the
traffic across the interfaces of a single container you can &lt;code&gt;nsenter&lt;/code&gt; the
container’s network namespace:&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;# nsenter -t $(docker inspect --format &amp;#39;{{.State.Pid}}&amp;#39; my_container) -n tcpdump -nS port 80
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This retrieves the PID for the container, and tells &lt;code&gt;nsenter&lt;/code&gt; to enter the
network (&lt;code&gt;-n&lt;/code&gt;) namespace from the given target (&lt;code&gt;-t&lt;/code&gt;) PID, and then run the
given command (in this case &lt;code&gt;tcpdump ...&lt;/code&gt;).&lt;/p&gt;
&lt;h4 id=&#34;openssl-s-client--s-server&#34;&gt;openssl s_client / s_server&lt;/h4&gt;
&lt;p&gt;When a connection is using TLS it’s often useful to try connecting to the
server and see what certificate it presents, algorithms it negotiates, and
so forth. OpenSSL offers two useful subcommands which can help with this:
&lt;code&gt;s_client&lt;/code&gt; for connecting as a client, and &lt;code&gt;s_server&lt;/code&gt; for listening to
connections.&lt;/p&gt;
&lt;p&gt;For example, using &lt;code&gt;s_client&lt;/code&gt; to connect to &lt;code&gt;google.com&lt;/code&gt; on the standard
HTTPS port shows us details about the server cert and its verification
status:&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;$ openssl s_client -connect google.com:443
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;CONNECTED(00000003)
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;depth=2 OU = GlobalSign Root CA - R2, O = GlobalSign, CN = GlobalSign
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;verify return:1
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;depth=1 C = US, O = Google Trust Services, CN = Google Internet Authority G3
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;verify return:1
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;depth=0 C = US, ST = California, L = Mountain View, O = Google LLC, CN = *.google.com
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;verify return:1
&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;Certificate chain
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt; 0 s:C = US, ST = California, L = Mountain View, O = Google LLC, CN = *.google.com
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;   i:C = US, O = Google Trust Services, CN = Google Internet Authority G3
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt; 1 s:C = US, O = Google Trust Services, CN = Google Internet Authority G3
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;   i:OU = GlobalSign Root CA - R2, O = GlobalSign, CN = GlobalSign
&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&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Whereas connecting to my webserver and providing an unknown host in the SNI
field results in an SSL alert 112 (“The server name sent was not recognized”)
and no server certificate is sent:&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;$ openssl s_client -connect chameth.com:443 -servername example.com
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;CONNECTED(00000003)
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;140384831313024:error:14094458:SSL routines:ssl3_read_bytes:tlsv1 unrecognized name:../ssl/record/rec_layer_s3.c:1536:SSL alert number 112
&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;no peer certificate available
&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&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Often if you hit this kind of alert in an application the exact error will be
lost somewhere in the many layers between the SSL library and the logs, so
being able to directly connect and test can help diagnose a lot of issues.&lt;/p&gt;
&lt;p&gt;Once a connection is established you can read and write plain text and it
will be encrypted and decrypted automatically.&lt;/p&gt;
&lt;h4 id=&#34;java-apps&#34;&gt;Java apps&lt;/h4&gt;
&lt;p&gt;If a Java app is involved in the connection, you can enable a lot of built-in
debugging with a simple JVM property: &lt;code&gt;javax.net.debug&lt;/code&gt;. You can tweak
what exactly gets logged, but the easiest thing to do is just set the property
to &lt;code&gt;all&lt;/code&gt; and you’ll see information about certificate chains, verification,
and packet dumps:&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;$ java -Djavax.net.debug=all -jar ....
&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;found key for : duke
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;chain [0] = [
&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;  Version: V1
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;  Subject: CN=Duke, OU=Java Software, O=&amp;#34;Sun Microsystems, Inc.&amp;#34;,
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;  L=Cupertino, ST=CA, C=US
&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;/code&gt;&lt;/pre&gt;&lt;p&gt;More information about Java’s debugging options is available on
&lt;a href=&#34;https://docs.oracle.com/javase/7/docs/technotes/guides/security/jsse/ReadDebug.html&#34;&gt;docs.oracle.com&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id=&#34;thread-and-core-dumps&#34;&gt;Thread and core dumps&lt;/h3&gt;
&lt;p&gt;Higher-level languages frequently provide an interactive way to dump the
current execution state of all of their threads (a “thread dump”). This
is useful to spot deadlocks, some types of race conditions, and as a
quick and dirty method of investigating hangs or excessive CPU usage.&lt;/p&gt;
&lt;p&gt;With both Java and Go applications you can send a QUIT signal to have a
thread dump printed out; Go applications will quit after doing so, Java
ones will carry on running. At most terminals you can hit &lt;code&gt;Ctrl&lt;/code&gt; and &lt;code&gt;\&lt;/code&gt; to
send a QUIT signal.&lt;/p&gt;
&lt;p&gt;For Java you can also use the &lt;code&gt;jstack&lt;/code&gt; tool from the JDK to dump threads
by PID; this can be useful if the application is running in the background
or has redirected sysout:&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;$ jstack 8321
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;Attaching to process ID 8321, please wait...
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;Debugger attached successfully.
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;Client compiler detected.
&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;Thread t@5: (state = BLOCKED)
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt; - java.lang.Object.wait(long) @bci=-1107318896 (Interpreted frame)
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt; - java.lang.Object.wait(long) @bci=0 (Interpreted frame)
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt; - java.lang.ref.ReferenceQueue.remove(long) @bci=44, line=116 (Interpreted frame)
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt; - java.lang.ref.ReferenceQueue.remove() @bci=2, line=132 (Interpreted frame)
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt; - java.lang.ref.Finalizer$FinalizerThread.run() @bci=3, line=159 (Interpreted frame)
&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&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;A core dump provides more complete information about the state of a process,
but is often more complex to interpret. The &lt;code&gt;gcore&lt;/code&gt; utility from GDB will
create a core dump of a process with a given PID. You can then generally
load the core file using your normal debugger, depending on the language
in question.&lt;/p&gt;
&lt;h3 id=&#34;system-calls&#34;&gt;System calls&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;strace&lt;/code&gt; is the swiss army knife for seeing what a process is doing. It
details each system call made by a program (you can filter them down, of
course). For example:&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;$ strace -e read curl https://google.com/
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;read(3, &amp;#34;\177ELF\2\1\1\0\0\0\0\0\0\0\0\0\3\0&amp;gt;\0\1\0\0\0 \236\0\0\0\0\0\0&amp;#34;..., 832) = 832
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;read(3, &amp;#34;\177ELF\2\1\1\0\0\0\0\0\0\0\0\0\3\0&amp;gt;\0\1\0\0\0P!\0\0\0\0\0\0&amp;#34;..., 832) = 832
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;read(3, &amp;#34;\177ELF\2\1\1\3\0\0\0\0\0\0\0\0\3\0&amp;gt;\0\1\0\0\0\200l\2\0\0\0\0\0&amp;#34;..., 832) = 832
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;read(3, &amp;#34;\177ELF\2\1\1\0\0\0\0\0\0\0\0\0\3\0&amp;gt;\0\1\0\0\0\20Q\0\0\0\0\0\0&amp;#34;..., 832) = 832
&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;read(3, &amp;#34;\0\0\0\0\0\0\0\4\25\345\366\302\273sE6\365wI\225\321|\3435Z\362\216\372\215\251aO&amp;#34;..., 253) = 253
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;&amp;lt;HTML&amp;gt;&amp;lt;HEAD&amp;gt;&amp;lt;meta http-equiv=&amp;#34;content-type&amp;#34; content=&amp;#34;text/html;charset=utf-8&amp;#34;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;&amp;lt;TITLE&amp;gt;301 Moved&amp;lt;/TITLE&amp;gt;&amp;lt;/HEAD&amp;gt;&amp;lt;BODY&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;&amp;lt;H1&amp;gt;301 Moved&amp;lt;/H1&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;The document has moved
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;&amp;lt;A HREF=&amp;#34;https://www.google.com/&amp;#34;&amp;gt;here&amp;lt;/A&amp;gt;.
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;&amp;lt;/BODY&amp;gt;&amp;lt;/HTML&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;read(3, &amp;#34;\27\3\3\0!&amp;#34;, 5)                = 5
&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;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;a href=&#34;http://www.brendangregg.com/blog/2014-05-12/strace-wow-much-syscall.html&#34;&gt;Brendan Gregg&lt;/a&gt;
has a nice guide on &lt;code&gt;strace&lt;/code&gt; and alternatives.&lt;/p&gt;
&lt;h4 id=&#34;-with-docker-1&#34;&gt;… with docker&lt;/h4&gt;
&lt;p&gt;When the application is running in docker you can usually just &lt;code&gt;strace&lt;/code&gt; it
from the host with the correct PID
(from e.g. &lt;code&gt;docker inspect --format &amp;#39;{{.State.Pid}}&amp;#39; my_container&lt;/code&gt;).
Sometimes you may need to trace the startup of an application though, which is
a bit trickier. Instead you can run a new container using the same PID
namespace as your target, and the permissions needed to &lt;code&gt;strace&lt;/code&gt;:&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;$ docker run --rm -it --pid=container:my_container \
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;  --net=container:my_container \
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;  --cap-add sys_admin \
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;  --cap-add sys_ptrace \
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;  alpine
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;From within the new container you can install strace, and trace any running
program within the target container using &lt;code&gt;strace -p&lt;/code&gt; as normal. To start a
new program you need access to the target container’s filesystem, which you
can get to via &lt;code&gt;/proc/1/root&lt;/code&gt; (PID &lt;code&gt;1&lt;/code&gt; being the main process that docker
started in the target container).&lt;/p&gt;
&lt;h3 id=&#34;files&#34;&gt;Files&lt;/h3&gt;
&lt;p&gt;Sometimes the problem might relate to file access. There are a couple of
straight forward — but nonetheless useful — tools which might help here.
&lt;code&gt;inotifywait&lt;/code&gt; uses the Linux &lt;code&gt;inotify&lt;/code&gt; subsystem to watch files or directories
for operations. For example:&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;$ inotifywait -mr site/content
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;Setting up watches.  Beware: since -r was given, this may take a while!
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;Watches established.
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;site/content/post/ MODIFY 2019-05-08-debugging-beyond-the-debugger.md
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;site/content/post/ OPEN 2019-05-08-debugging-beyond-the-debugger.md
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;site/content/post/ MODIFY 2019-05-08-debugging-beyond-the-debugger.md
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;site/content/post/ MODIFY 2019-05-08-debugging-beyond-the-debugger.md
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;site/content/post/ CLOSE_WRITE,CLOSE 2019-05-08-debugging-beyond-the-debugger.md
&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;/code&gt;&lt;/pre&gt;&lt;p&gt;Here the &lt;code&gt;-m&lt;/code&gt; switch makes &lt;code&gt;inotifywait&lt;/code&gt; monitor the files forever (instead
of exiting on the first modification, which is the normal behaviour) and &lt;code&gt;r&lt;/code&gt;
makes it recurse into the directory and monitor each file and subdirectory in
there.&lt;/p&gt;
&lt;p&gt;If you want to see what processes currently have a file open, &lt;code&gt;fuser&lt;/code&gt; is the
go-to tool. For example:&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;$ fuser -v /
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;                     USER PID ACCESS COMMAND
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;/:                   root     kernel mount /
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;                     chris      2961 .rc.. systemd
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;                     chris      2986 .r... gdm-x-session
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;                     chris      2994 .r... dbus-daemon
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;chroma-line&#34;&gt;&lt;span class=&#34;chroma-cl&#34;&gt;                     chris      3001 .r... gnome-session-b
&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;/code&gt;&lt;/pre&gt;&lt;h3 id=&#34;honourable-mentions&#34;&gt;Honourable mentions&lt;/h3&gt;
&lt;p&gt;These aren’t really debugging tools, but I feel it’s worth mentioning as
they often feature somewhere along the debugging-of-weird-problems journey.&lt;/p&gt;
&lt;p&gt;I’ve seen some weird and wonderful problems happen
because a disk is full, so a quick &lt;code&gt;df&lt;/code&gt; early on in the debugging process
never hurts. Some apps may hang, some may corrupt their config, some may
fall over and die; sometimes the manner in which they fail doesn’t obviously
point to a disk space issue.&lt;/p&gt;
&lt;p&gt;Another issue that comes up now and then — especially inside VMs or
other environment that don’t have a decent amount of “noise” happening —
is entropy exhaustion. A quick look at &lt;code&gt;/proc/sys/kernel/random/entropy_avail&lt;/code&gt;
should be enough to confirm that everything is ticking along nicely. If it’s
exceedingly low then you may find that anything involving random number
generation stalls (TLS connections for example).&lt;/p&gt;
</content>
    </entry>
</feed>
