Yearly Archives: 2023

Battery replacement on a NiteRider Swift 500

1 minute, 24 seconds

A while ago the battery on my NiteRider Swift 500 headlight stopped taking a charge. I looked at NiteRider’s FAQ page and saw no mention of the batteries being user serviceable. Further, when I searched online, I didn’t find any guides or replacement parts for the light. Time to grab a screwdriver and DIY!

I started by removing the strap mount – a single Phillips head on the bottom:

Then I removed the 4 allen screws around the base of the head:

The lens assembly should come off – be careful as the rubber battery cover will fall free now. Be sure to keep track of all the parts!

Remove the two Philips head screws at the top of the LED plate:

You should now be able to slide the LED plate out which is attached to the battery and the main circuit board. You’ll note the battery is both soldered on an aggressively affixed with double sided sticky tape. Peel the battery off, cut the two wires (one red, one black) half way between the battery and the circuit board. Rub the tape residue off enough so you can see the specs of the battery:

There’s no direct replacement part for this, but I found this “CaoDuRen Rechargeable 3.7V Li Lipo Lithium” on Amazon was close enough to work. Only $9 at the time – what a deal!

Cut the JST connector off of the new battery, cutting half way between the battery and connector. Solder the black to black and red to red wires, and seal up the solder connection. I used heat-shrink tubing and then affixed it with sticky Velcro:

Reassemble your light by following the steps above in reverse order. Careful when working with the light as it is quite bright and I had it accidentally turn on while assembling it – yikes!

Now enjoy your light and drop me a line if you have any other tips or succeed in replacing your battery!

Simple Single Page Site with Secure Log Access

3 minutes, 36 seconds

And image of the sticker with a ".xyz" TLD

A friend of mine created some fun stickers for use at the most recent DEF CON. They were sly commentary about how corporate a lot of the stickers are and how maybe we should get back to our DIY roots. But…what’s this? There’s a .xyz in there…is that a TLD…is there domain I could go to?! IS THIS STICKER AN AD ITSELF?!?!?!?!1!

(Sticker image is marked with CC0 1.0)

It’s all of those things and none of those things – that’s why I love it so much. Best of all, when you go to website, you get just what you deserve ;)

The website was initially setup on a free hosting provider, but they didn’t provide any logs – something my friend was curious about to see how much traffic the non-ad ad was generating. I have a VERY cheap VPS server that already had Ubuntu server and Caddy on it, and I figured I could help by hosting a wee single file static web site and be able to easily offer the logs. Let’s see what we can do!

Step 1: One HTML file + Four Caddy config lines = Web server

I frickin’ love Caddy! I made a single index.html file and then added these 4 lines of config:

the-domain-goes-here.xyz {
        root * /var/www/the-domain-goes-here.xyz
        file_server
}

After I restarted Caddy (systemctl restart caddy) – I was all set! As DNS had already been changed to point to the IP of my lil’ server, Caddy auto-provisioned a free Let’s Encrypt cert, redirected all traffic from port 80 -> 443 and the site worked perfectly!

By default Caddy has logs turned off – let’s fix that!

Step 2: Turn up the (log) volume

Unsurprisingly, Caddy makes enabling logs very straight forward. Just add these three lines

  log {
    output file /var/log/caddy/the-domain-goes-here.xyz-access.log
  }

I reloaded the config in Caddy (systemctl reload caddy) and checked for log activity in /var/log/caddy/. It was there! Oh…it was there in full repetitive, verbose JSON…OK, cool, I guess that’s a sane default in our new cloud init all JSON/YAML all the time world. However, how about common log format though?

This was the first time Caddy surprised me! While it was easy enough to do (huge props to “keen” on Stack Overflow), it was a bit more convoluted and verbose than I expected. You have to change the log deceleration to be log access-formatted and then specify both a format and a transform. The final full server config looks like this:

the-domain-goes-here.xyz {
	root * /var/www/the-domain-goes-here.xyz
	file_server
	log access-formatted {
    		output file /var/log/caddy/the-domain-goes-here.xyz-access.log
		# OMG - thank you!! https://stackoverflow.com/a/76988109
		format transform `{request>remote_ip} - {request>user_id} [{ts}] "{request>method} {request>uri} {request>proto}" {status} {size} "{request>headers>Referer>[0]}" "{request>headers>User-Agent>[0]}" "host:{request>host}"` {
          time_format "02/Jan/2006:15:04:05 -0700"
       		}
  	}
}

Now let’s figure how to to add secure access to download those logs.

Step 3: Rsync via Authorized Keys FTW!

A straight forward way to give access to the logs would be to create a new user (adduser username) and then update the user to be able to read the files created by the Caddy process by adding them to the right group (usermod -a -G caddy username). This indeed worked well enough, but it also gave the user a full shell account on the web server. While they’re a friend and I trust them, I also wanted see if there was a more secure way of granting access.

From prior projects, I knew you could force an SSH session to immediately execute a command upon login, and only that command, by prepending this to the entry in the authorized_key file:

command="SOME_COMMAND",no-port-forwarding,no-user-rc SSH-KEY-HERE

If I had SOME_COMMAND be /usr/bin/rsync then this would be great! The user could easily sync the updates to their access log file at /var/log/caddy/the-domain-goes-here.xyz-access.log. but then I realized they could also rsync off ANY file that they had read access too. That’s not ideal.

The final piece to this Simple Single Page Site with Secure Log Access is rrsync. This is a python script developed specifically for the use case of allowing users to rsync only specific files via the Authorized Keys trick. The full array of security flags now looks like this:

restrict,command="/usr/bin/rrsync -ro /var/log/caddy/",no-agent-forwarding,no-port-forwarding,no-pty,no-user-rc,no-X11-forwarding SSH-KEY-HERE

As there’s no other logs in /var/log/caddy – this works great! The user just needs to call:

rsync -axv username@the-domain-goes-here.xyz: .

Because of the magic of rrsync (two rs) on the server forcing them into a specific remote directory, the rsync (one r) on client is none the wiser and happily syncs away.

Happy web serving and secure log access syncing and Happy Halloween!

Blog Theme Improvements

2 minutes, 38 seconds

Most of the time when I’m reading articles online, I very often switch my browser to Reader View. This gets rid of all fluff of display and theme that a site’s have and, most importantly, fixes the low contrast text trend. Reader View is white text on black background (but this is configurable) and also adds a “Time to Read” at the top of the article. The latter prevents me from clicking on a “quick read” which is actually 30 min!

I noticed some times I visit a site and don’t flip in Reader View because they’ve done it for me already! While I know not everyone is like me, so they may prefer miles of white space with a nice, thin, light gray font on an off-white background. However, as this is my blog, I’ve just converted to be just as I like those sites where I don’t turn on Reader View!

Referencing the image above, here’s’ the changes with the “before” on the right and the “after” on the left:

  1. All code blocks are now numbered. They’re still zebra striped, but they’re higher contrast
  2. Text is now larger and easier to read – straight up white on black
  3. White background is now black background*
  4. Items that are variables or code snippets have a different colored background, like this
  5. The read time is shown at the top of every post (not pictured)
  6. Removed “Share This” icons at the bottom of each post (also not pictured)

* – I actually don’t force the black background! All the changes are live based on the users’ OS preference via the prefers-color-scheme CSS selector. You can pick! Here’s a video showing the two flipping back and forth:

I’m still tweaking a few of the CSS colors and what not as I find things not quite right, but please send along any questions or comments. I’d love to hear what y’all think!

Addendum

The “Share This” plugin mentioned above was not only adding some extra clutter of icons I no longer thought too helpful, but was also including an external CSS or JavaScript or something file that didn’t feel right given I don’t prefer to share my HTTP traffic with any other sites.

As well, I removed two extensions (Share This and Code Highlighter syntax) which I then implemented in my own wee plugin. Less 3rd party code means less to update means less security concerns for my ol’ blog here. As well, I greatly reduced the feature set and amount of PHP – I think the plugin is about 5 active lines of PHP.

Finally, I’m using the Twenty Twelve theme with this additional CSS (added via “appearance” section of prefs):

#site-navigation, .comments-link { 
    display:none;
}
.wp-block-quote {
    border-left: .25em solid;
    border-left-width: 0.25em;
    border-left-style: solid;
    border-left-color: cyan;
}
body .site {
    margin-top: 0;
    margin-bottom: 0;
}
body, * ,.site {
    font-size: 1.15rem;
}
body {
    background-color: white;
    color: black;
}
.wp-block-code ol li:nth-child(odd), code  {
    background: lightcyan;
}
code {
    padding: 4px 6px 4px 6px;
}
@media screen and (prefers-color-scheme: dark) {
     *,.site  {
        background-color: black;
        color: white;
    }
    .widget-area .widget a, .entry-content a, .comment-content a {
        color: #51c9ff;
    }
   .widget-area .widget a:visited,.widget-area .widget a,.entry-content a:visited, .comment-content a:visited {
        color: lightgray;
    }
    .widget-area .widget a:hover ,.widget-area .widget a,.entry-content a:hover, .comment-content a:hover {
        color: white;
    }
    body {
        background-color: #010149;
    }
    .wp-block-code ol li:nth-child(odd), .wp-block-code ol li:nth-child(odd) {
        background: #000030;
        color:white;
    }
    code {
        background: #000065;
    }
    .entry-content img, .comment-content img, .widget img, img.header-image, .author-avatar img, img.wp-post-image, .wp-block-embed {
        border-radius: 3px;
        box-shadow: 1px 2px 5px rgba(255, 255, 255, 0.84);
    }
}

With all this you should able able to reproduce these settings on your own blog if you so desire!

Dead Simple Continuous Deployment

3 minutes, 54 seconds

At work we’ve written some monitoring and alerting software. We’ve also automated releases and we wanted to take it to the next level so that the instant there was a new release, we’d deployed to production. This automated releasing of software upon a new release is called Continuous Deployment, or “CD” for short. This post documents the simple yet effective approach we took to achieve CD using just 20 lines of bash.

The 20 lines of bash

Super impatient? I got you. This code will mean a lot more if you read the post below, but I know a lot of engineers want more copypasta and less preachy blog posts:

#!/usr/bin/env bash

# Checks for local version (current) and then remote version on GH (latest)
# and if they're not the same, run update script
#
# uses lasttversion:  https://github.com/dvershinin/lastversion

current=$(cd /root/cht-monitoring;/usr/bin/git describe --tags)
latest=$(/usr/local/bin/lastversion https://github.com/medic/cht-watchdog)

update(){
	cd /root/cht-monitoring
	git fetch
	git -c advice.detachedHead=false checkout "$latest"
	/root/down-up.sh	
}

announce(){
	/usr/bin/curl -sX POST --data-urlencode "payload={\"channel\": \"#channel-name-here\", \"username\": \"upgrade-bot\", \"text\": \"Watchdog has been updated from "$current" to "$latest". Check it out at https://WATCHDOG-URL-HERE\", \"icon_emoji\": \":dog:\"}" https://hooks.slack.com/services/-SECRET-TOKEN-HERE-
}


if [ ! "$current" = "$latest" ];then
	$(update)
	$(announce)
	echo "New version found, upgraded from $current to $latest"
else
	echo "No new version found, staying on $current."
fi

Why use CD?

There’s been a lot written about CD, like this quote:

Engineering teams can push code changes to the main branch and quickly see it being used in production, often within minutes. This approach to software development [allows] continuous value delivery to end users.

rando GitHub blog post

I’ll add to this and say, as an engineer, it’s also extremely gratifying to have a pull request get merged and then minutes later see a public announcement in Slack that a new release has just automatically gone live. There’s no waiting even to do the release manually yourself, it’s just done for you, every time, all the time! Often when engineers are bogged down by a lengthy release process and slow customer adoption where it can take weeks or months (or years?!) to see their code go live, CD is the antidote to the poison of slowness.

Tools Used

The script uses just one custom binary, but otherwise leans on some pretty bog standard tools:

  • git – Since our app is in a local checked out git repository, we can use trivially find out what version is running locally with git describe --tags
  • curl – We use curl to do the POST to slack using their webhook API. It’s dead simple and requires just a bearer token
  • lastversion – I found this when searching for an easy and reliable way to resolve what the latest release is for our product. It’s on GitHub and it just made my day! It solved the exact problem I had perfectly and it really Does One Thing Well (well, admittedly also downloads)
  • down-up.sh – this is the cheating part of this solution ;) This is an external bash script that makes it easy tokeep track of which docker compose files we’re using. Every time we create a new compose file, we add it to the compose down and compose up calls in the script. This ensures we don’t exclude one by accident. It’s just two lines which are something like:

    docker compose -f docker-compose.yml -f ../docker-compose-extra.yml down
    docker compose -f docker-compose.yml -f ../docker-compose-extra.yml up --remove-orphans -d

  • Inside the repository itself, we’ve hooked up Semantic Release which automatically cuts a new release based on Semantic Versioning (aka “SemVer”)

New release process

With all the tools in place, here’s how we cut a new release, assuming we’re on version 1.10.0:

  1. An engineer will open a pull request (PR) with some code changes.
  2. The PR will be reviewed by another engineer and any feedback will be addressed.
  3. Once approved, the PR is merged to main with an appropriate commit message.
  4. A release is automatically created: A fix commit will release version 1.10.1. A feat (feature) commit will release version 1.11.0. A commit citing BREAKING CHANGE will release version 2.0.0
  5. Every 5 minutes a cronjob runs on the production server to compare the current local version verses the latest version on GitHub
  6. If a new version is found, git is run to check out the latest code
  7. The down-up.sh script is called to restart the docker services. We even support updates to compose files citing a newer tag of a docker image in which case docker downloads new releases of upstream. Easy-peasy!
  8. A curl command is run to make a POST to the Slack API so an announcement is made in our team’s public channel:

Wrap up

You may become overwhelmed in the face of needing to store SSH private keys as a GitHub secret or having a more complex CD pipeline with a custom GitHub Runner. Don’t be overwhelmed! Sometimes the best solution is just 20 lines of bash you copied off a rando blog post (that’s me!) which you tweaked to work for your deployment. You can always get more complex at a later date as needed, but getting CD up and running today can be incredibly empowering for the engineers working on your code!

Timekpr-nExT Remote

3 minutes, 45 seconds

tl;dr – Timekpr-Next Remote is an easy to use web app to add or remove time to users of linux login time tracking app, Timekpr-nExT

Recently, one of my kids got a drawing tablet and wanted to use it with Krita. Given they were on a Chromebook, we decided to repurpose an old Intel NUC i3 server with a clean Ubuntu 22.04 Desktop. Not only would this allow Krita to run well, but it would also enable video editing via KDEnlive and other hard-to-run on ChromeOS softwares.

For some time now, we’ve been happily running Legba to track computer usage. However, we wanted something with a bit more teeth, so we settled on Timekpr-nExT (henceforth just “Timekpr”, but it’s the “nExT” one, not this out of date one or this waaay out of date one, k?). This is a great app that allows for a finite amount of time to be used per day, and it is relatively easy to add more time. Well, easy if you’re on a desktop. And you have SSH installed. And you know the login and password to each computer you want to control. So, not at all easy if you’re a busy parent who’s juggling managing kids and helping with school work and cooking dinner. So, not at all easy if you’re a parent, amiright?!

Enter Timekpr-Next Remote! This is a Dockerized Python app that allows you to easily update update your kids’ computer time right from the nearest parental phone or desktop device:

As you can see, for any given user (only one sample user, “Muhammad”, is shown here) you can easily add more time (or remove time if you fat fingered the add time). Given how ubiquitous phones are, having a self hosted, non-cloud way to easily control time has been a win for us. Video chat with grandma after you’ve done your homework and used all your time allotment? Add -> 30 min -> Save, it only takes 3 seconds \o/

SSH FTW

Let’s have a look under the covers at how all this works.

I should start out by saying that Timekpr is licensed GNU GPL v3 and they post the code online. I did consider adding an HTTP server to handle REST requests to core package up stream. Then I realized I’d have to do the securely and that I’d have to deal with certificates and such. A greenfield approach would be quicker (grok only my code, not someone else’s) and more secure, but not as clean (I used SSH vs REST). With that out of the way…

Timekpr Remote uses SSH to communicate! This is clunky, indeed. But, Python has a wonderful SSH library in the form of Fabric (which turn uses the awesome Paramiko) which took a lot of the really clunky parts out and made them pretty elegant.

Here’s the flow of data when we load the page and want to get the current usage of Mumammad:

This “handwritten” diagram compliments of JS Sequence Diagrams – thank you!

All data flows this way, and there’s three AJAX endpoints which the web client sends via this flow:

  • Get all Users and IPs
  • Get usage for a user and IP pair
  • Add/Remove time for a user and IP pair

This isn’t a perfect REST API, but it’s OK enough. It was my first time writing an app in Flask, so it was fun to figure how to do different URL handling and JSON returning and such, even if my REST uses some GETs instead of POSTs/PUTs

“s” in Timekpr-next Remote is for “Security”

While we an trust SSH between Timekpr Remote and Server, you may note a lack of authentication between the mobile handset and Timekpr Remote. Indeed, there is none. Here’s what I recommend:

Run something like Traefik or Caddy in another docker container. From there you can bind the timekpr-next remote server to the host docker IP with something like TIMEKPR_IP=172.17.0.1 docker compose up -d. It will no longer be available on the network, only only via the revers proxy you set up.

You can then either use basicauth (eg in Caddy) or what I did is make a host name that is un-guessable like https://user-time-8957446623432192758492038.domain.com. Everyone just bookmarks this. Even if your kids see the URL, they won’t be able to remember it.

For those that give SSH the stink eye (smells like an injection attack, eh?), you can harden this too. Ensure the SSH user on the server can not do anything more than run timekpra by restricting it in the authorized_keys file on each client. This will ensure if extra variables are passed (though we do explicitly protect against this), they won’t do any more harm.

Like and Subscribe that video

Here’s a 9 second video demonstrating it in situ!


Jokes on you though, there’s no like and subscribe because it’s not actually YouTube (though GitHub is pretty close to a social media network…)