Channel: Antarctica Starts Here.
Mark channel Not-Safe-For-Work? cancel confirm NSFW Votes: (0 votes)
Are you the publisher? Claim or contact us about this channel.
Previous Article Next Article

At HOPE 2020...


Wednesday... 29 July 2020... 1300 hours Eastern... the Fediverse takes Manhattan.

Join us... at HOPE 2020.

HOPE 2020 - Saving Hacking from the Zaibatsus: A Memoir

Update from the homestead.


CW: Stuff about medicine, post-surgical care, and wounds.  Feel free to close the tab if you need to.

This won't be easy for me to write, mostly because I'm tired, scatterbrained, and trying to put everything in some kind of order.  I'm pretty stressed out and my allergies aren't helping, either.  It's also been difficult to find ideas to put together right now.

Cancer is a nasty adversary.  It runs you down, robs you of your strength, and tries to steal away your dignity.  The overall supply of dignity in the world right now is starting to run low and I don't want to contribute to that.  I'd be lying if I said that I knew, really knew, what mom was going through right now.  I don't, and I can't.  I can imagine what it's like from being here and watching and helping as best I can but that's not the same thing.  Cancer can also throw you curveballs in the same way that an entire team of pissed off baseball pitchers could.  When there are rogue immortal cells gobbling up the body's resources faster than they can be replenished it really wipes you out.

I mentioned a couple of days ago that my mom was diagnosed with cervical cancer and I flew back to Pittsburgh to help take care of her.  Since that time it's been a whirlwind of activity around the old homestead, picking up, cleaning, throwing things out, fixing stuff, ordering parts and tools, and generally trying to get the house ready for her to come home.  It's been a pretty big job, involving more driving in a day than I've done during the entire covid-19 lockdown (that's not saying much, I don't drive all that much back home) and finding myself on a first-name basis with the staff of our friendly neighborhood chain hardware store because they've been helping me track down the stuff I needed.  Closer to mom's discharge date I had to call in assistance with the house because it just got to be too much for one body to handle, and as of when I write this we've been able to make some pretty serious changes for the better.

My mom was discharged from the hospital on 29 July, after over a week of recovering from a full hysterectomy.  She's got a ten inch surgical incision formerly held shut with 30 staples (they've been removed since I started working on this post), which are nothing to sneeze at.  Unfortunately, edema has set in to complicate matters.  Her legs are very swollen with excess fluid, resulting in the formation of hydrostatic bulla, or very large blisters on her lower legs and feet which are full of excess fluid.  We figured out that they'd put four IV lines running at KVO into her prior to and during preparation for surgery, but when they removed the surgical drain some days later they forgot to remove the extra IV lines.  Long story short, more fluid going in than could be removed naturally.  The thing is, the blisters are not from anything other than edema, which is good - there are no indications of secondary infections, no pressure sores or anything of the sort.  Fluid has to go somewhere and in this (unfortunate) case, that somewhere is in between layers of skin when there's no more room anywhere else.  Like other blisters, they're more or less sterile until they rupture, which they did shortly after returning home.  Her physician says that this is probably the best that could be hoped for due to the prevalence of antibiotic-resistant strep in hospitals these days.  Since I started writing this post some days ago mom's been put on a diuretic, which has been doing quite a job of removing the excess fluid through the usual means, and on top of that she's been getting exercise for her legs and back at the same time.  The hydrostatic bullae are no longer dripping constantly and are just draining as one would expect healing wounds to normally do (with all of the ickiness that one would expect, a positive change from before).

We've arranged for in-home assistance a couple of times a week.  A nurse, aide, and physical therapist come in on a rotation that I've yet to really figure out (due to HOPE, taking care of stuff around the house and checking in at work every couple of days).  I'm relieved by this; while I keep my first aid certs up to date with yearly training, a lot of this is just beyond me.  Especially when it comes to the daily injections of an anticoagulant to prevent deep vein thrombosis (though I do think it's pretty cool that syrettes, or more accurately, pre-loaded single-use injectors are coming back into medical fashion).  I will say, however, that I've learned more about wound care and sanitary measures than I ever expected to learn.

As it turned out when the staples were removed, part of the surgical wound hadn't had a chance to knit closed yet.  So, there is a part of the surgical incision near the middle about as long as one of my fingers that's just sort of hanging open.  It hasn't pulled closed yet.  If you look in there (and I do at least once a day when changing the dressing) you can see all the way down to the fascia.  I could stick a fingertip in there and easily cover the nail (not that I would do such a thing).  Now, let's be clear: It is decidedly not normal to be able to peer through about a quarter inch of skin, subcutaneous fat, and blood vessels (with a bit of bleeding).  There's something primal deep inside the brain that says "That's the insides of another being like myself, and I should not be seeing that under any circumstances."  Thing is, that's also the territory where higher parts of the brain have to step up and say "Hey, wait a minute.  That's skin.  That's not organs or bones and gushing blood, that's just a look inside the upper layers of another person.  Sure, it sucks, but it's easy to take care of.  So, get in there and help."

Or at least that's what a subprocess inside my head tells me when I pull that wad of gauze out and hose the incision down with sterile saline solution.  Wound care, like bong hits and memory corruption vulnerabilities, has a learning curve.

I'm sort of glad that mom didn't tell me right away that the official diagnosis from the hospital is stage 3 ovarian cancer.  I don't think I would have been able to hold together very well without a few days to gear up to it.  For folks who've heard the term but don't know exactly what that entails (like me, I had to look it up), this means that as localized cancers go it was pretty advanced.  Multiple tumors were found and removed and there is evidence that they were getting pretty ambitious in there.  Did it metastasize anywhere other than her abdomen?  No idea.  Hence, chemotherapy.  It'll probably be another couple of weeks before she's strong enough to have a mediport installed.  I fully plan on teasing her about being the second cyborg in the family.

I haven't yet figured out what my schedule is going to be like here.  I was planning on four or five weeks in Pittsburgh, then flying back to take care of some stuff at home (including two weeks of covid-19 quarantine at the house) for another few weeks, then flying back to Pittsburgh.  It's going to require nontrivial coordination with folks out here as well as back home.  Just in case, I'm building up supplies of clothing, tools and toiletries to manage how much mass I'll have to fly with.

One of the things I did was dig out my grandfather's old walker and clean it up a bit.  Mom isn't terribly steady on her feet and she needs extra help to move around.  For what it's worth, while she had considerable difficulty getting around the first day or two of being home, she seems to be getting stronger.  My mom's not leaning on the walker as heavily as before, she's able to walk more quickly and more stably, and her legs seem to be getting stronger.  The edema has made it very difficult for her to lift her legs due to the additional mass of the fluid but she's steadily able to move them higher and higher to reposition herself.  These are all good signs.  In addition to this, Dora the cat has rediscovered her love of jumping up onto the walker and riding as mom works her way through the house.  She used to do that when my grandfather was alive, and would fetch my mom when he was having trouble.  I would be entirely unsurprised if she did the same for my mom.

Due to the complicating factors I mentioned above, plus her core muscles being rearranged during surgery, my mom's not able to spend any time in a bed right now.  Getting her into bed is problematic at best, getting out means not being able to get back in unassisted, plus this is an activity which requires said core muscles to not be surgically compromised.  However, she has found that she's able to sit comfortably in an easy chair, with a minimum of difficulty standing up and sitting down, plus she's able to sleep quite comfortably.  A few days ago I called in help to switch out the chair for my grandfather's recliner (stored in the basement at the time), which resulted in some quick planning, some mental math, and a lot of pushing and shoving as hard as I could to get it up the stairs.  While I'm glad that I've kept up my usual exercise routine during covid lockdown, my back isn't inclined to do me any favors for a couple of days.

It seems like things are changing faster than I can keep up with them, let alone blog about them.  As I update this post (11 August 2020) I took my mom to an appointment with her primary care physician that afternoon (yesterday was an appointment for full-leg sonography to see if there were any complications of edema, such as deep vein thrombosis - nothing was found) to check out the blisters on her feet as well as the surgical incision.  The blisters appear to be healing up nicely.  However, there is now concern that the open part of the surgical incision is infected due to irritation, spreading redness and the appearance of the fluid draining from the wound.  Mom's on antibiotics now and I'll be taking her in to see her surgeon tomorrow (today for you, as this is a timed post) for a closer look.  I really hope this turns out to be nothing (or at least, something trivially easy to treat).

Another eventful couple of weeks.


CW: Stuff about medicine, post-surgical care, and cancer.  Feel free to close the tab if you need to.

It's been a couple of weeks since my last update.  I was working on a different post in my spare time but I'm not entirely pleased with how it's turning out, plus I think it needs a lot more work, so I thought it'd be easier to write about the last week and change.  By "easier," I mean "easier to write," not "easier to handle."

A little over a week ago, on the 21st of August, I was killing time with mom rewatching Twin Peaks (she didn't know there was a third season so of course we had to rewatch the first two beforehand).  Nothing fancy, just the television and me hacking around a bit on a project.  Partway through the episode, around 2230 local time, she said in that quiet voice that in my family means that something is terribly wrong, that she was having trouble breathing and that I needed to call 911.  I'm sorry to say that I was expecting that something like this would occur so I kept my phone within arm's reach and had a go-bag packed and standing by, so it was the work of a few seconds to dial emergency services, give a situation report, request an ambulance, and get my stuff together.  There isn't much to say about the process of paramedics showing up, gathering data, loading my mom onto a stretcher, and heading to the hospital.  There wasn't any room in the ambulance, unfortunately, so I had to call in a favor from a neighbor to follow.

To make a long story short, rather than fluid accumulating in her abdomen (as has happened a number of times before) it started accumulating around her left lung.  Looking at her chest x-ray and comparing it to the pre-op one, her lung had been compressed to around half its normal volume by the fluid.  Thankfully, this isn't a strange or even un-predicted situation for a cancer patient so the solution is about as straightforward as it gets.  Thoracostomy - poke a hole in the chest and slide a tube into the space between the lung and chest wall, and then start pumping.  The procedure was scheduled for the next day, and while running on a fraction of a lung sucks by any stretch of the imagination (doubly so late at night) at least she was in the right place at the right time.

I tried to stick it out all night, I really did.  For some reason, the room they had my mom in was freezing cold, and between that and the coffee I'd had in the emergency room there was no way I was going to get any sleep.  I'm not as young as I used to be, I don't handle sleep deprivation very well anymore, and if I needed to do something the next day I did't want to be running on wiring only.  Unfortunately, rideshare options in outer Pittsburgh are pretty thin on the ground in the middle of the night, so I had to call in another favor to get home.  I didn't get to sleep until around 0500 local time, and what sleep I did get wasn't particularly restful.

They had to drain fluid from mom's chest a couple of times in the last week or so.  At one point they installed a semi-permanent chest drain and hooked it to suction to pull out a couple of liters.  The day before I brought her home (a day ago, as I write this) the surgeons installed a permanent chest drain that the in-home nurse is supposed to take care of.  On the same day they implanted the infusaport that they're going to use for chemotherapy once she gets stronger.  Fun fact: It's radiopaque, and shows the international symbol for a radiopaque implant when x-rayed.  (image source: Patient manual for the Bard Peripheral Vascular PowerPort, used without permission though I did try to find an image online.)  I've been told (and suspected) that chemotherapy would help with the edema problem because ovarian cancer throws the body out of whack just enough to cause fluid to build up wherever it can.  It's also worth noting that the oncology lab found the protein markers for ovarian cancer in the fluid from both her abdomen and chest cavity, which pretty much confirms that mom's cancer and bouts of edema are related.  Take care of the cancer, take care of the edema, too.

In other news, the water blisters on mom's legs have almost completely healed.  She's been on some pretty hardcore diuretics for the last few weeks, and I think the time in the hospital (NOT on four IVs running at KVO, mind you) have done her a lot of good.  The skin's almost completely regenerated and now the dead skin is coming off.  The infection of her surgical incision has fully cleared up and the tissue is almost completely knit.  Mom's also a lot stronger now.  Her voice is much more clear, her appetite appears to have come back, and she's walking much more steadily (albeit she still needs a walker for balance).  Understandably she's not too pleased with the placement of the chest drain (on her back, left side) so there probably isn't any really comfortable way for her to sit or lie down.  The implantation surgery was also rather painful, regardless of local anesthetic.  Whenever a surgeon says that you're going to feel some discomfort, it's going to hurt like a son of a bitch.

I seemed to visit mom in the hospital every time her oncologist paid her a visit.  He was very amenable to discussing mom's cancer diagnosis, and is also very confident that treatment will be successful.  Ovarian cancer, he says, is very well understood these days and treatments for it have come a long, long way in the last 30 years.  He mentioned that he was planning a two-drug cocktail for mom's chemotherapy; doing a little digging it's probably going to be a combination of carboplatin and a taxane of some kind.  Mom also has an ongoing prescription for a fairly high dose of the corticosteroid decadron, which she's supposed to take the night before and prior to a chemo run.  I happened to be talking with a colleague who was successfully treated for a particularly nasty form of cancer some time ago and learned something interesting.  Decadron has the interesting side effect of hastening cellular division, which doesn't make a whole lot of sense in the context of cancer treatment.  However, carboplatin works by damaging cells' DNA as they replicate, which means that the already overly ambitious cancer cells will be damaged and possibly non-viable while they're dividing.  Taxanes function by interfering with cellular replication in a different way, vis a vis, by interfering with the process of chromosomes splitting and pulling into the two new cells during cellular division.  tl;dr - it's like fixing an engine while driving in fourth gear.

A quick and easy way of downloading MP3s from Youtube.


Let's say you find a particularly banging' track on Youtube that you'd like to save for posterity.. what's an easy way of grabbing just the audio so you can listen to it later?  Sure, you can go hunting for a sketchy website that'll download the video, strip out the audio, and give it to you in a download, but those come and go and you can never be sure you're getting what you want.  My personal favorite technique is to use youtube-dl: youtube-dl -x --audio-format mp3 https://www.youtube.com/watch?v=EO2dPcvf1BQ

But I can never remember off the top of my head what combination of command line switches to use, I always have to go through youtube-dl's online help to find it.  Recently I realized that I could set a shell alias for this command and go about my usual business.  Oh, and document it so the next time I have to set up a box, I can just search my blog for what I did...  It was trivial to do, I just added the following to my ~/.bashrc file: alias mp3-dl='youtube-dl -x --audio-format mp3'

Now I just have to do this:

{09:28:27 @ Tue Sep 08} [drwho @ windbringer mp3] () $ mp3-dl https://www.youtube.com/watch?v=EO2dPcvf1BQ [youtube] EO2dPcvf1BQ: Downloading webpage [youtube] EO2dPcvf1BQ: Downloading MPD manifest [dashsegments] Total fragments: 12 [download] Destination: Warren Zevon - Real or Not w_Lyrics-EO2dPcvf1BQ.webm [download] 100% of 1.64MiB in 00:01 [ffmpeg] Destination: Warren Zevon - Real or Not w_Lyrics-EO2dPcvf1BQ.mp3 Deleting original file Warren Zevon - Real or Not w_Lyrics-EO2dPcvf1BQ.webm (pass -k to keep) {09:28:45 @ Tue Sep 08} [drwho @ windbringer mp3] () $

Share and enjoy!

Chemotherapy begins.


Mom had her first round of chemotherapy last Tuesday.  Early that morning I drove her to the Hillman Cancer Center at UPMC, got her checked in, and had to leave as they took her back because, due to the pandemic and generally immunosuppressed state of the other patients in the office I posed a contamination risk.  I spent most of the day puttering around the house, fixing stuff up, cleaning, and getting a bit of dayjob work done after dropping her off.  Mom spent most of the day hooked up to one IV line or another.  Unsurprisingly, it took some time to get the actual procedure started: Mediports can be used for drawing blood samples as well as administering medications.  However, while it was possible to flush her mediport with saline the doctors weren't able to draw any blood samples through it and they couldn't proceed until they were able to.  As I understand the situation, it required three heparin flushes to un-fuck the catheter, which took roughly 90 minutes.

Mom's oncologist says that each run of chemo has to be compounded specifically to the patient's current blood stats, height, and weight, which is why vitals and blood samples need to be taken every time.  Seems like it's pretty tricky stuff to get right and it gets mixed up immediately prior to administration.  Thing is, if the blood sample takes a while to get, the compounding process takes a while, and and and... this is why cancer patients normally bring lunch and things to occupy their time while they're in the office hooked up.  Once they got things going, though, they started the process off with a prophylactic IV antibiotic (probably to minimize the risk of something already in her system getting any ideas while her immune system is suppressed), IV benadryl (because hypersensitivity to chemotheraputic drugs is a known problem), and an IV dose of an anti-nausea drug before the actual chemo drugs went in.

Looking over mom's medical records and treatment plan (she'd asked me to translate them for her), she's on a cocktail of paclitaxel and carboplatin, which is a well understood treatment protocol for stage III ovarian cancer.  It's as well suited to its purpose as a shark and about as nasty.  Paclitaxel is a compound that, to simplify things because I'm not a medical doctor or chemist, interferes with cellular replication at several stages of the process.  In some respects, dividing cells get stuck somewhere in the process, and in other respects they get stuck and have to revert to an earlier stage in the replication process.  When you have a bunch of parts of a very complex system fall out of synch the whole thing collapses; in this context, the dividing cells self-destruct.  The other drug in the protocol she's under is caboplatin, a platinum salt, which seems to interfere with the replication of DNA as cancer cells divide and causes them to fail to complete replication and lyse.

While many of cancerous cells get caught up it's inevitable that other cells (like bone marrow and the lining of the small intestine) will also get caught in crossfire, which leads to some of the classic side effects of chemo, like nausea, vomiting, diarrhea, a suppressed immune system and anemia.  We have to be very careful about sanitation, hygine, and the like at home right now.  Also, and I didn't know this, we have to treat pretty much everything as a potential biohazard.  In the first 24 hours, 70% of the chemotheraputic compounds will be excreted, and we're talking some pretty cytotoxic stuff, so we have to handle everything with gloves, it has to be washed in hot water and detergent, things that can be disposed of have to be wrapped up and taped beforehand... you basically become a walking Superfund site.

The malaise set in on Wednesday.  By Thursday, mom was spending quite a bit of time napping and she started hitting the anti-nausea medication that night.  By Friday (when I started writing this post) mom spent a significant amount of the day sleeping and went to bed unusually early.  The doctors said that it would take 7-10 days before the side effects set in, but natural variation being what it is, sometimes things happen sooner, sometimes they happen later, and sometimes not at all.

In other news, the in-home nurse has been coming over every day or two and hooking mom up to a drainage rig to pump fluid out of her chest cavity.  Sometimes there isn't much, just a couple of CC's, sometimes there's a bit more, sometimes a bit more than that, but nowhere near as much as in previous weeks.  I'm inclined to think that the pleural effusion is pretty much managed at this point, though I do find myself worrying a bit about the edema returning in her lower legs.

Fun fact: The PleurX kits that the nurse uses to drain the fluid?  They cost about a grand each (thus sayeth the in-home nurse) which is why we have to treat them with kid gloves.  Researching the cost of them on the open market, however, they seem to average out to around $58.72us each... which makes me wonder just where in the process she's getting fucked and how many times.

Calculating entropy with Python.


Fun fact: There is more than one kind of entropy out there.

If you've been through high school chemistry or physics, you might have learned about thermodynamic entropy, which is (roughly speaking) the amount of disorder in a closed system.  Alternatively, and a little more precisely, thermodynamic entropy can be defined as the heat in a volume of space equalizing throughout the volume.  But that's not the kind of entropy that I'm talking about.

Information theory has its own concept of entropy.  One way of explaining information theory is that it's the mathematical study of messages as they travel through a communications system (which you won't need to know anything about for the purposes of this article).  In the year 1948.ev Claude Shannon (the father of information theory) wrote a paper called A Mathematical Theory of Communication in which he proposed that the amount of raw information in a message could be thought of as the amount of uncertainty (or perhaps novelty) in a given volume of bits (a message) in a transmission.  So, Shannon entropy could be thought of as asking the question "How much meaningful information is present in this message?"  Flip a coin and there's only one bit - heads or tails, zero or one.  Look at a more complex message and it's not quite so simple.  However, let's consider a computational building block, if you will:

One bit has two states, zero or one, or 21 states.  Two bits have four possible states: 00, 01, 10, and 11, or 22 possible states.  n bits have 2n possible states, which means that they can store up to n bits of information.  Now we bring in logarithms, which we can think of in this case as "what number foo would we need in 2foo to represent the number of bits in a message?"

That said, Claude Shannon came up with a nifty bit of math to calculate the entropy in a message:

(Image credit: Wikipedia, probably CC-BY-SA 3.0 Unported.)

It sorta-kinda makes sense if you know math, sorta-kinda doesn't if math isn't your strong suit (math isn't mine, please don't feel badly).  Let's break the equation down to figure out what it means.

H(X) is the amount of entropy in the message.  To be honest, I don't know why you'd throw a - sign in front of the equation because it doesn't make sense to worry about the amount of negative entropy in a message (I'll probably catch some flak for saying that but I'm not afraid to say when I don't undertstand something, that's part of learning).  P(xi) is the probability mass function, or the probability that a discrete random variable (like a single bit) has a particular value.  log is the logarithm to the base 2 (so that included image should probably say log2, but we'll worry about that in a couple of paragraphs).  The Σ sign means "See the equation after the capital sigma?  Solve that equation once for every value of i between the value below the sigma, until you hit the value of i above the sigma.  Then add all of those answers together to get the final result."

If you've messed around with programming you've probably noticed that I tried to break the explanation down in such a way that you could write some code in the language of your choice to implement this math more easily by implying loops and function calls.  When I was thinking about this a while back I got it into my head to not use any special-purpose libraries to do it, because it seemed.. a bit excessive, to be honest.  Looking at Shannon's equation, it seemed to me that you shouldn't need to install a couple of score of megabytes of NumPy or SciPy libraries to accomplish this.  I posted about this on the Fediverse and heard back from @pra about a page at Rosetta Code that explains the equation in a much more clear fashion.  The thing that really helped is that it replaces the abstract probability mass function P(xi) with something that makes a little more sense, counti/N, or "the number of times some character appears in the message divided by the number of characters in the message."  That gives us enough information to implement the calculation of entropy.  I used as my sample message the sentence "Now is the time for all good men to come to the aid of their country." because that was the first touch typing drill sentence I learned from my mom.

Here's how I did it in Python:

import math string = "Now is the time for all good men to come to the aid of their country." entropy_in_bits = 0 letters_in_string = "".join(set(string)) for i in letters_in_string: x = string.count(i) y = x = (x / len(string)) x = math.log2(x) x = x * y entropy_in_bits = entropy_in_bits + x

The math module is the basic Python mathematics library, which implements all of the math functions in the ANSI C standard.  If you like, this is the bare minimum math library you need for a programming language.  string is my sample sentence and entropy_in_bits is just what it sounds like, a count of the number of bits of entropy in string.  The statement letters_in_string = "".join(set(string)) means, "First, build a list of every character in string the first time it appears, called a set.  (Yes, I linked to the documentation for an old version of Python because I felt it explains what a set is more concisely.)  Then turn that list into a string containing all of the unique characters in the value string."

Now, for every character in letters_in_string, count the number of times it appears in the sample text in string.  Divide the number of times the character appears in the text and divide that by the number of characters in the entire text.  Store it into two variables, x and y.  Take the logarithm to the base 2 of x and store it back into x.  Then multiply the log2 of x by the value in y.  Add that value to the running total in entropy_in_bits.  Move on to the next character.

The amount of Shannon entropy (in bits) I got with the above code and sample text was 3.839559153700935.  To sanity check myself I plugged the sample text into GCHQ's online copy of CyberChef at Github and also got the answer 3.839559153700935.  This double-check strongly implies that the above code I wrote works the same way and does the same thing as the entropy calculation module in CyberChef, written by people a lot smarter than I am.

There you have it.  I sincerely hope that I was able to explain Shannon entropy in a way that made sense (and if not, let me know through the usual channels and I'll do my best to fix that) and I hope that I got a few people curious about what this means.  Maybe the Wikipedia page will make more sense to you (it does to me after this writing this hamster).  If nothing else, it was a fun exercise to take something big and scary and rewrite it in a form that's tiny, compact, and easy to dissect and understand.

Go forth and make shit happen.

Wrestling with mental and physical health.


This isn't easy for me to write because it involves my mental health.  So, if it's not your bag feel free to skip this post.

Helping my mom since her cancer diagnosis has left me in this peculiar state where I don't actually know what I'm feeling.  I call it "running on wires," as in, the silicon I'm connected to is running me, and the organics are off doing... something, maybe.  My therapist calls it alexithymia, and reading about it that's as good a word for it as any.

I've been fighting with clinical depression for most of my life, ever since my grandmother died in 1987 or 1988.ev (somewhen around fourth grade).  I've been in and out of various forms of therapy for most of my life, and while everything seems to help for a while it never lasts.  I've also been fighting with my body's weight (hang on... my weight) for about as long.  When I get depressed my diet goes to hell in a handbasket, and I know that I've put on some weight during the time I was in Pittsburgh.  I don't know how much because I haven't weighed myself, and I haven't wanted to weigh myself because I don't know how I'd react to seeing just how many pounds I've put on.

Under the cut, discussion of eating disorders.  Punch out if you want.

Depression and being overweight have always gone hand-in-hand for me, for as long as I can remember.  When I started high school I joined Weight Watchers because I wanted to do something about my weight and appearance, and by the time I graduated I didn't look half bad, or so people tell me.  I think I was around 165 or 170 at that time, and in those four years I'd lost quite a bit of weight.  But in college I went a little overboard... my freshman year I got sick, probably from something in the dining hall (go ahead and laugh, but it was the only place I'd eaten that week), and between running to the bathroom, not eating, and sleeping with a fever I lost even more weight.  I think I was around 150 pounds by that point.

And for the first time in just about ever, I looked into the mirror and felt pleased with myself.

To say that I was obsessed with my weight by trying to stay between 150 and 155 pounds was an understatement.  I'm pretty sure that I'd crossed into anorexia nervosa at that point, and was eating just enough so I could function in class and do my homework (because I didn't want to fuck up college).  I distinctly remember getting really upset when I'd hit 155 because the clothes I was wearing were too tight (and they were skin tight to begin with).  And I'd starve myself back down.  Over and over and over and over again.

My knees hurt constantly.  So did my back.  I was tired all the time and had very little endurance, which I didn't let stop me when it came to taking aerobics classes to burn off everything I'd eaten.  I had very little body hair at the time, probably because my body was hoarding every scrap of protein it could lay claim to just to not fall apart in a heap.  You could count the old rib fractures through my skin (and a few people did).  I think I was living on 900 to 1000 dietary calories a day by that point, which by even the most liberal perspective was well into WTF.  To say that I was unhealthy would be polite.  I was really hurting myself.

It wasn't until I met Lyssa at a Yule party in 2002.ev and we started dating seriously that this started to change.  She fed me.  I was snacking at parties (something I never did).  Eating decently.  Putting on weight.  My back and legs stopped hurting.  The RST I'd been struggling with for years even mostly went into remission (though stopping MUDding and leaving IRC most certainly helped) because my body started having enough resources available to repair itself.  I even developed body hair (Lyssa likes to joke that she put me through puberty at long last) and my hairline started coming back (though it's gotten fed up with my bullshit in recent years and is packing its bags and leaving, taking my functional hair follicles with it).

Thinking back about my behavior in the last two months I've observed that I've been overeating... Two helpings here, two helpings there, the odd mid-afternoon snack... I don't remember the last time I had ice cream, but I've had more in the last week than in the last ten years.  Two or three bowls, but still.  I know I'm compensating for something.  I know something's wrong inside.  I can't tell what it is, can't feel it, can't isolate it, but I conclude that it's depression, and I'm putting on weight because that's what I do when I'm depressed, I eat.

I don't recognize the person in the mirror any more, and I cringe every time I see myself without a shirt on.  I do want to lose weight, I probably need to (I'm 42, it's probably not healthy for me), but on the other hand there's that little voice in the back of my head saying that I can get back down to where I was in college... svelte.  Pretty.  Amazing looking.  All I have to do is stop.

For those of you saying "For fuck's sake, talk to your therapist about this," I will be.  This post is me trying to process everything going on in my head by making an end run around the feelings that I can't grasp.  Maybe I can figure something out.  Maybe I can convince myself that everything will be okay.

Maybe I can convince myself that I need to accept that I'm not the skinny-ass goth kid I was in college, never will be again because I'm twice as old now, with a whole new life and set of circumstances, and come to terms with it.

Neologism: Leaning Tower of Hangout


Leaning Tower of Hangout - noun phrase - The covid-19 quarantine phenomenon in which one discovers a stack of hangouts on one's work calendar, all for the exact same time, all flagged as required, all scheduled while you were asleep the night before.

ref, Towers of Hanoi.

Previous Article Next Article

Setting up a mail relay server with Postfix, DKIM, and a little Nebula trickery.


Given the proliferation of spam on just about every vaguely workable platform these days it seems sheer insanity to attempt to run your own mail server.  If it's out there, it's ripe for abuse in one way in another.  And yet, e-mail is still probably one of the best ways to get status reports from your machines every day (my SMTP bridge notwithstanding).  It is thus that the default configuration for mail servers these days defaults to "no way in hell will I relay a message for you," which is a net good for the the Internet as a whole, but by and large a huge pain in the ass if you actually want to set up a mail relay for some reason.  In my case, I wanted to set up Leandra (running in my rack at home) to relay outbound mail through another of my servers on the outside.  I further wanted to ensure that Leandra's outbound mail had the same kind of authentication and protection measures the rest of my machines have so that my servers wouldn't wind up on any spam blacklists and would be significantly difficult (because there is no 'impossible') to abuse.

The first thing I had to do was set up an A record in DNS for leandra.virtadpt.net pointing at the same IP address as the server I wanted to relay through.  If this were a sane and reasonable world I'd just set an alias with a CNAME record but it seems like nothing out there plays nicely with aliases anymore.  There is nothing that says at a single IP address can't have more than one hostname associated with it, so this isn't a big deal.

However, DKIM is kind of a big deal.  I don't fully understand it but I'll try explaining it as best I can, at least insofar as it applies to our use case.

The foundation of spamming is spoofing the origin of a message, meaning the sending e-mail address.  There are various and sundry ways of making this difficult, at the very least by preventing servers from relaying mail for anyone and everyone.  One of the latest ways to prevent this from happening is called DKIM (Domain Keys Identified Mail).  Without going into too much detail, DKIM uses public key encryption to help prove that a message came from a particular place.  When you set DKIM up on a mail server you generate a private key (which stays on the box) and a public key (which gets published in DNS as a TXT record).  You also plug some extra software into your mail server which generates and appends a digital signature to every message sent from or through that machine (parts of the message are hashed, the hash is encrypted with the private key on the mail server, and the resulting signature goes into a new SMTP header on the message).  When the signed message hits the receiving server, the mail server makes a couple of DNS queries to dig up the public key of the mail server that sent it and verifies the DKIM signature on the message.

The nice thing about DKIM?  End users don't have to worry about it.  It's all server side.  If a message fails DKIM authentication you might see a warning message when you open the message telling you that it might have been forged.

Okay.  So, what kind of sysadmin crap did I do to make this work?  To keep from messing things up too badly I did all of the steps that wouldn't impact anything else running on Exocortex first by following an excellent tutorial over at Linuxbabe.  I'm typing this process up in part because this blog is a manifestation of my external memory, and in part because there are folks out there (and I don't blame you) who are more comfortable doing something tricky once someone else has done it and explained what happened each step of the way.

For the record, all of these commands are run as the root user.  Also for the record, everything I'm going to show you is the public side of this setup.  All of the information has to be public by definition, because other mail servers on the Net need to be able to look this stuff up.  Anyone out there can go splunking through DNS or run a couple of searches on SHODAN and find it for themselves.

Of course, you'll want to adapt the configuration options and commands to match your own host- and domain names, IP addresses, and so forth.  Don't forget to make backup copies of your config files (you DO make backups, don't you?)

The first step was to install the OpenDKIM daemon on Exocortex.  As described above it ingests outbound e-mail on one side, digitally signs it, and barfs it out the other side by passing it off to Postfix.  This was probably the simplest part: apt-get install opendkim opendkim-tools

One of the things that's not terribly clear and is easy to trip over is that the postfix service account (which the various parts of Postfix run as) has to be added to the opendkim system group so that it can interact with the new parts you just installed.  Also, keep the OpenDKIM daemon out of your way while you work on other stuff: systemctl stop opendkim

I like to make a backup copy of the supplied config file before I edit anything, just in case I screw it up: cp /etc/opendkim.conf /etc/opendkim.conf.orig

While I could reiterate the instructions I followed originally, I've already linked to them so I'll start giving you the lazy way because it's easier to explain.  Edit the /etc/opendkim.conf file in your favorite text editor and add the following lines:

# How strictly to treat reorganization and reformatting of the # headers and message body before considering the signature bad. # Headers can be reformatted, but the message body can't. Canonicalization relaxed/simple # What DKIM functions to fulfill: sign, verify Mode sv # Also sign messages for recognized subdomains? SubDomains no # If the opendkim daemon dies, restart it. AutoRestart yes # If the opendkim daemon needs to be restarted, limit the attempts # to 10 tries every minute. AutoRestartRate 10/1M # Run in the background? Background yes # How long to wait before giving up on DNS (in seconds). DNSTimeout 5 # Algorithm to use for the digital signatures. SignatureAlgorithm rsa-sha256 # Path to the file that contains the mappings between the DNS TXT # record holding the pubkey and the location of the private key on # the machine. KeyTable refile:/etc/opendkim/key.table # Path to the file that contains the mappings of usernames on the # server to DNS records containing the public keys. SigningTable refile:/etc/opendkim/signing.table # Path to a file that contains a list of IP addresses, hostnames, and # other system identifiers whose DKIM signatures won't be checked # (because, by definition, they're known good because they're ours). ExternalIgnoreList /etc/opendkim/trusted.hosts # Path to a file that contains a list of IP addresses, hostnames, and # other system identifiers which may send mail through the server and # get the DKIM signature. If it's not in this list, it gets dropped. InternalHosts /etc/opendkim/trusted.hosts

Now we need to set up some directories to store stuff:

root@exocortex:/etc()# mkdir -p /etc/opendkim/keys root@exocortex:/etc()# chown -R opendkim:opendkim /etc/opendkim

Time to write a few new config files that are specific to your server and domain.  The first file is /etc/opendkim/signing.table, with the following contents:

*@exocortex.virtadpt.net default._domainkey.exocortex.virtadpt.net *@leandra.virtadpt.net default._domainkey.leandra.virtadpt.net

Of course, you'll want to make the contents reflect your situation.  Now create the /etc/opendkim/key.table file.

default._domainkey.exocortex.virtadpt.net exocortex.virtadpt.net:default:/etc/opendkim/keys/exocortex.virtadpt.net/default.private default._domainkey.leandra.virtadpt.net leandra.virtadpt.net:default:/etc/opendkim/keys/leandra.virtadpt.net/default.private

Next file to create: /etc/opendkim/trusted.hosts localhost *.virtadpt.net

The IP address is that of Exocortex, and is listed so that mail originating from the server will be DKIM signed.  You will, of course, put the IP address(es) of your server here instead.  See that IP address at the very bottom (  That is Leandra's Nebula network IP address.  Leandra is going to hit Exocortex over a Nebula connection because mail relaying in Postfix is restricted, in part, by IP address of origin, and Leandra is on a connection with a dynamic IP address.  All things considered, I'd much rather not have to futz with dynamic hostnames, guessing netblocks, or anything like that.

Then I created two directories, one per hostname, to hold the DKIM key material: mkdir -p /etc/opendkim/keys/{exocortex,leandra}.virtadpt.net

Generate the key material for the two hostnames:

root@exocortex:/etc()# opendkim-genkey -b 2048 -d exocortex.virtadpt.net -D /etc/opendkim/keys/exocortex.virtadpt.net -s default -v root@exocortex:/etc()# opendkim-genkey -b 2048 -d leandra.virtadpt.net -D /etc/opendkim/keys/leandra.virtadpt.net -s default -v

In each directory you will find two files, default.private (which contains the private key for the hostname) and default.txt (which contains the text of the entire TXT record that has to be copied into the DNS zone).  Here are the contents of one of mine:

root@exocortex:/etc/opendkim/keys/exocortex.virtadpt.net()# cat default.txt default._domainkey IN TXT ( "v=DKIM1; h=sha256; k=rsa; " "p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAzOQVBHlkoI67vwT5Vh+PSo4UltdIiX+dI3iBTQRw99AseFCVvwVKjRmrfQ8fGCJ7NdMVoiHzZczN0h8oBTNPPS8VIoN5rMJn4sz5c+kSOr16iD/9R7kZBKMnGqSMu5+YMHkMrPJKGubHKz3pSVmBAKsB3ew2JgRpJ0NBPadeK9lI2eu46fe9BooNlYttCfFDNPQvJcNTQmiUEH" "7aPw3v8tdwifoY4EPB/XkWIMxxBxk1bHGN4vsB3NMdFmlStRO8ptVGGccsdBeMZv6s9WCLuIhnPlCxZGY1SD4cTDOc/qzEKSaBt7Tn6Hf3feaKgsSIN01dP6G47ojUT95r4lu6oQIDAQAB" ) ; ----- DKIM key default for exocortex.virtadpt.net

When I installed the record into the DNS zone files, it looked like this:

v=DKIM1; h=sha256; k=rsa; p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAzOQVBHlkoI67vwT5Vh+PSo4UltdIiX+dI3iBTQRw99AseFCVvwVKjRmrfQ8fGCJ7NdMVoiHzZczN0h8oBTNPPS8VIoN5rMJn4sz5c+kSOr16iD/9R7kZBKMnGqSMu5+YMHkMrPJKGubHKz3pSVmBAKsB3ew2JgRpJ0NBPadeK9lI2eu46fe9BooNlYttCfFDNPQvJcNTQmiUEH7aPw3v8tdwifoY4EPB/XkWIMxxBxk1bHGN4vsB3NMdFmlStRO8ptVGGccsdBeMZv6s9WCLuIhnPlCxZGY1SD4cTDOc/qzEKSaBt7Tn6Hf3feaKgsSIN01dP6G47ojUT95r4lu6oQIDAQAB

Note that I spliced together everything inside the double quotes into a single line and dropped it into a TXT record, default._domainkey.exocortex.virtadpt.net.  I don't know why it outputs the DNS record that way, but I suspect it has to do something with defaulting to BIND's zone file format because it's the most commonly used out there.  If you don't know what BIND is, don't worry about it.  It's not relevant to this post unless you run your own DNSes, in which case you know all about this anyway.

Now set file ownership on the private keys so that the opendkim daemon can read them (substitute as necessary, of course): chown opendkim:opendkim /etc/opendkim/keys/*.virtadpt.net/default.private

One of the things about DNS is that it takes time for changes to propagate, doubly so when you use a hosted DNS provider.  It takes both patience and periodic testing to see when the records are available.  I used the following command to look for the above DNS record: opendkim-testkey -d exocortex.virtadpt.net -s default -vvv

Before the DNS zone was reloaded at Dreamhost, the TXT record wasn't yet available, which resulted in the following output:

opendkim-testkey: using default configfile /etc/opendkim.conf opendkim-testkey: checking key 'default._domainkey.exocortex.virtadpt.net' opendkim-testkey: 'default._domainkey.exocortex.virtadpt.net' record not found

When the DNS record had successfully propagated this was the output:

opendkim-testkey: using default configfile /etc/opendkim.conf opendkim-testkey: checking key 'default._domainkey.exocortex.virtadpt.net' opendkim-testkey: key not secure opendkim-testkey: key OK

("key not secure" means "DNSSEC isn't in use here."  Don't worry about it.)

By default, Postfix gets stuck into a chroot in the directory /var/spool/postfix when it starts up to minimize any damage it could do if something goes wrong.  This implies that the socket which Postfix uses to communicate with OpenDKIM must exist somewhere inside that directory structure.

root@exocortex:/etc/opendkim/()# mkdir /var/spool/postfix/opendkim root@exocortex:/etc/opendkim/()# chown opendkim:postfix /var/spool/postfix/opendkim

Now OpenDKIM needs to be configured to find the socket.  There are two files (/etc/opendkim.conf and /etc/default/opendkim) which need to be edited in the same way: Search for the string "socket" (case insensitive) in each file and change its value to read "local:/var/spool/postfix/opendkim/opendkim.sock"

Now the tricky bit, where stuff can break.  Edit Postfix's primary configuration file /etc/postfix/main.cf and append the following lines at the bottom of the file:

# If the OpenDKIM milter isn't available, accept the message anyway. milter_default_action = accept # What milter communication protocol should be used to pass messages # to and from OpenDKIM? Just go with it. milter_protocol = 6 # Where should the OpenDKIM milter be contact through? Note that this # is inside the /var/spool/postfix chroot. smtpd_milters = local:opendkim/opendkim.sock # Send mail that doesn't arrive from the network through the same milter # as outbound mail. non_smtpd_milters = $smtpd_milters

We're in the home stretch.  We can test the Postfix configuration file without having to restart the server and catch any errors early:

root@exocortex:/etc/postfix()# ls dynamicmaps.cf main.cf.bak.1 master.cf.bak.1 postfix-script dynamicmaps.cf.d main.cf.proto master.cf.proto post-install main.cf makedefs.out postfix-files sasl main.cf.bak master.cf postfix-files.d ssl root@exocortex:/etc/postfix()# postfix check postfix: Postfix is running with backwards-compatible default settings postfix: See http://www.postfix.org/COMPATIBILITY_README.html for details postfix: To disable backwards compatibility use "postconf compatibility_level=2" and "postfix reload" root@exocortex:/etc/postfix()# echo $? 0

The last command (echo $?) means "show the exit status that postfix reload sent."  In this case a 0 means "it's all good."  If there were any problems, postfix check would have displayed errors.  The bit about "backwards-compatible default settings" seems pretty harmless - Postfix will run normally but it's telling you that you should probably look into updating your configuration settings a little.  I haven't really dug into this (though I should).

To adhere to best practices I edited /etc/postfix/master.cf to listen on a new port.  By RFC 4409 the port which is supposed to accept messages to relay on behalf of another server or user should be port 587/tcp.  In the /etc/services file this port is named "submission."  That said, the line I added to the /etc/postfix/master.cf file by copying the line above it and making a minor edit:

# ========================================================================== # service type private unpriv chroot wakeup maxproc command + args # (yes) (yes) (yes) (never) (100) # ========================================================================== submission inet n - y - - smtpd

"Open a new port that is called 'submission' in /etc/services.  It is an inet service, meaning that it opens a port on at least one network interface.  Because it is an inet service it is not private (incidentally, Postfix requires this setting on inet services, so don't get creative with it).  It is an unprivileged service (the default), so it will run as the postfix user.  This service runs in the chroot, so apply those restrictions to it.  The service on this port is always running so don't worry about the wakeup feature.  The maximum number of workers servicing this port is 100 processes.  The worker which will handle requests on this port is the smtpd module."

I could have specified a specific port and not the string "submission" but I wanted to get things working and not dork around with hunting down bugs and mismatches.  I know I can cut the number of workers down from 100 but without a reason to do so I'm not worried about it just yet.

Now to kick everything off: Start everything up and send a test message with the command line mail utility while watching the system logs:

root@exocortex:/etc/postfix()# systemctl start opendkim root@exocortex:/etc/postfix()# systemctl restart postfix root@exocortex:/etc/postfix()# mail my-test-account@gmail.com && journalctl -xf Subject: This is a test message. This is a test message for the tutorial I'm writing. --me . Cc: ^D -- Logs begin at Sat 2020-07-25 13:51:49 UTC. -- ... Sep 27 22:42:03 exocortex.virtadpt.net run.sh[1482]: - - [27/Sep/2020 22:42:03] "GET /Jayce HTTP/1.1" 200 - Sep 27 22:43:55 exocortex.virtadpt.net postfix/pickup[5220]: ECE078203E: uid=0 from=<root> Sep 27 22:43:55 exocortex.virtadpt.net postfix/cleanup[8058]: ECE078203E: message-id=<20200927224355.ECE078203E@exocortex.virtadpt.net> Sep 27 22:43:55 exocortex.virtadpt.net postfix/qmgr[25609]: ECE078203E: from=<root@exocortex.virtadpt.net>, size=525, nrcpt=1 (queue active) Sep 27 22:43:56 exocortex.virtadpt.net postfix/smtp[8061]: connect to gmail-smtp-in.l.google.com[2607:f8b0:400e:c09::1b]:25: Cannot assign requested address Sep 27 22:43:56 exocortex.virtadpt.net postfix/smtp[8061]: ECE078203E: to=<my-test-account@gmail.com>, relay=gmail-smtp-in.l.google.com[]:25, delay=0.96, delays=0.02/0.01/0.27/0.66, dsn=2.0.0, status=sent (250 2.0.0 OK 1601246636 t5si5096039pjm.145 - gsmtp) Sep 27 22:43:56 exocortex.virtadpt.net postfix/qmgr[25609]: ECE078203E: removed ^C

^D means "hit control-d to end input" and ^c means "hit control-c to stop following the logfile."

Llet's look at the test message I sent to my Gmail address.  The reason I sent it there is because Gmail makes it really easy to look at the DKIM report for the message.  Open your test message in Gmail, click the three dots menu at the right of the message, and click Show Original.  Among other things, you will see in the report the result of the SPF test (don't worry about this, it's out of scope for this post), the DMARC test (also out of scope), and the test of our DKIM setup.  Which, if it worked, you should see that the Big G gave it a 'PASS'.  Success.

Now for the weird part of my setup, which is probably optional for you: Configure Nebula so that Leandra could contact Postfix over the VPN to send mail.  As mentioned earlier, Leandra's IP address on my Nebula VPN network is and Postfix was configured to accept e-mail relay from that address.  To make this happen I had to add the SMTP submission service we just built to the Nebula configuration file on Exocortex.  This is actually really easy to do by editing /etc/nebula/config.yml and adding the following block:

# Allow 587/tcp from any host in the home group - port: 587 proto: tcp groups: - home

To make the changes take effect I restarted Nebula: systemctl restart nebula

I've had this setup running for a couple of weeks now and it has been stable and doing what I wanted, which is that system reports from root (at) leandra dot virtadpt dot net (spamblocked, and doesn't receive mail anyway) show up in my inbox elsewhere reliably, with the hostname I wanted, and pass anti-spam measures the way they're supposed to.