sc
Scott avatar
ttwillsey

Manage Multiple GitHub Deploy Keys

Contents

GitHub Deploy Keys

Sometimes I want to be able to pull a personal GitHub repository onto a server in order to keep some files up to date. Things like scripts that I won’t edit on the server and so won’t need to push to GitHub are good candidates for this. In order to accomplish this in a reasonably secure way, I use GitHub Deploy Keys. With a read-only deploy key, if someone ever gets access to my server I’ll suddenly have a billion new problems, but worrying about my repositories being overwritten with malicious garbage won’t be one of them.

Deploy keys are repository specific, however, which means if you have multiple repositories you want to pull from GitHub onto the server, you need a way to specify which deploy key each repo should use.

Let’s use an example where I have two repos, both of which I want to pull onto my server. One is called server-management-scripts and the other is called website-management-scripts.

Create the Deploy Keys

You can use good old ssh-keygen in your account’s .ssh directory on the server to create your keys. In my case, ecdsa keys, named sms-deploy (for server-management-scripts repo) and wms-deploy (for website-management-scripts repo).

~/.ssh/
ssh-keygen -t ecdsa -f sms-deploy

And

~/.ssh/
ssh-keygen -t ecdsa -f wms-deploy

Because these are going to be read-only keys for their repos, I do not use a passphrase on them.

~/.ssh/
scott@dragonfly ~/.ssh $ ll
total 40
drwx------ 2 scott scott 4096 Nov 19 18:23 ./
drwxr-xr-x 8 scott scott 4096 Nov 19 18:21 ../
-rw------- 1 scott scott 194 May 8 2024 authorized_keys
-rw-r--r-- 1 scott scott 133 Mar 12 2021 config
-rw------- 1 scott scott 505 Nov 19 18:22 sms-deploy
-rw-r--r-- 1 scott scott 177 Nov 19 18:22 sms-deploy.pub
-rw------- 1 scott scott 505 Nov 19 18:23 wms-deploy
-rw-r--r-- 1 scott scott 177 Nov 19 18:23 wms-deploy.pub

Add the Public Keys to the GitHub Repositories

Go to your repository on GitHub, go into the repository settings, Click the Deploy Keys link, and click the Add Deploy Key button.

SettingsDeployAdd

Give the key a name in the Title textbox, copy the contents of your public key (the .pub file for the key) that you created on your server into the Key textbox, and leave “Allow write access” unchecked. Click the “Add key” button.

DeployKeysAddNew

Now your new deploy key shows in the repo’s Settings > Deploy keys, and this key can be used to pull the repo.

NewDeployKey

Once you’ve added the public keys to the repos, you can delete them from your server, leaving only the private keys behind.

~/.ssh/
scott@dragonfly ~/.ssh $ ll
total 32
drwx------ 2 scott scott 4096 Nov 19 18:23 ./
drwxr-xr-x 8 scott scott 4096 Nov 19 18:21 ../
-rw------- 1 scott scott 194 May 8 2024 authorized_keys
-rw-r--r-- 1 scott scott 133 Mar 12 2021 config
-rw------- 1 scott scott 505 Nov 19 18:22 sms-deploy
-rw------- 1 scott scott 505 Nov 19 18:23 wms-deploy

Assign the Correct Keys to the Correct Repositories

In order to make sure that a git pull command works correctly in each repo, I can edit my user account ssh config file on the server to contain these entries:

~/.ssh/config
Host serverscripts
HostName github.com
User git
AddKeysToAgent yes
IdentityFile ~/.ssh/sms-deploy
IdentitiesOnly yes
ForwardAgent yes
Host websitescripts
HostName github.com
User git
AddKeysToAgent yes
IdentityFile ~/.ssh/wms-deploy
IdentitiesOnly yes
ForwardAgent yes

Now, in each repo’s .git directory on the server, I can edit the config file to reference the correct entry in ~/.ssh/config :

/usr/local/sbin/scripts/sms/.git/config
[core]
repositoryformatversion = 0
filemode = true
bare = false
logallrefupdates = true
[remote "origin"]
url = git@serverscripts:scottaw66/server-management-scripts.git
fetch = +refs/heads/*:refs/remotes/origin/*
[branch "main"]
remote = origin
merge = refs/heads/main

Notice the highlighted part, which is the URL for the repo to use. It uses serverscripts as the server portion of the URL (the part after the @ symbol and before the :), because that matches the first Host entry in my account’s ~/.ssh/config file on the server.

The second git repo’s config file on the server would look like this, since I gave it a Host name of websitescripts in the ~/.ssh/config file:

/usr/local/sbin/scripts/wms/.git/config
[core]
repositoryformatversion = 0
filemode = true
bare = false
logallrefupdates = true
[remote "origin"]
url = git@websitescripts:scottaw66/website-management-scripts.git
fetch = +refs/heads/*:refs/remotes/origin/*
[branch "main"]
remote = origin
merge = refs/heads/main

Now both repos are linked back to a specific entry in ~/.ssh/config, and therefore to their respective deploy keys, specified on the IdentityFile line. The ssh key file for the server management scripts repo is ~/.ssh/sms-deploy, and the ssh key for the website management scripts repo is ~/.ssh/wms-deploy.

When you do a git pull in either of those repos on the server, it will now use the correct deploy key to authenticate.

Initial SuperDuper Copy

Mac

Part of the Mac series

Last night I mentioned setting up SuperDuper! to clone my Mac’s internal SSD to an external SSD daily. One thing about the initial copy you should know that doesn’t pertain to subsequent incremental copies is don’t let your Mac sleep during the initial copy.

I even thought about this when I started the initial backup, but then thought it shouldn’t matter. I came back to a failed copy with the following errors in the SuperDuper! log:

Terminal window
| Info | Volume replication failed - Resource busy
| Error | ***ERROR OCCURRED: ****FAILED****: result=256 errno=60 (Operation not permitted)

I sent the log to Shirt Pocket support, and Dave Nanian quickly replied with the following (I hope he doesn’t mind me posting it here):

Your Mac has fallen asleep during the copy, even though we asked it not to, or something similar (such as a TRIM operation that takes too long) — as you can see, Apple’s error is vague and unhelpful.

But, in our experience, this usually works. Please reformat the backup drive (which, done this way, should reset all the TRIM operations):

  • Open Disk Utility
  • Choose “Show All Devices” from the View menu
  • Select the destination drive hardware (above the existing volume)
  • Click Erase
  • Choose the “GUID” partition scheme (2nd pop-up), THEN “APFS” formatting (1st pop-up) and name appropriately
  • Click Erase

Then restart your Mac. Install Coca (free) from the App Store and enable it to keep your Mac awake — including the screen. Ensure it’s plugged in if it’s a laptop. Turn Time Machine off temporarily. Then try the erase-then-copy backup again.

Note that none of this should be necessary for future Smart Updates, which are done with our copy engine, and not Apple’s.

Hope that helps!

Dave Nanian
Shirt Pocket

In my case, I use the Raycast Coffee extension now when I want to keep my Mac awake, so I used that to make sure the first copy finished.

Anyway, don’t sleep on that first copy, and do back your drive up to more than one destination. Make at least one local and at least one remote (cloud backup is fine). I highly recommend SuperDuper! for its features, price, and always outstanding support from Dave Nanian.

SuperDuper Invisible

Mac

Part of the Mac series

I’ve certainly used drive cloning software in the past, including SuperDuper!.1 For awhile now, though, I’ve just had Backblaze as my backup method, which does violate my principle of always having local and remote backups. So when I saw Jason Snell’s article on SixColors about using a cloned drive to recover from Mac failures, it gave me the impetus to make a clone again.

I love and like and love SuperDuper! and always have, but I do wonder about this UI choice… SuperDuper Invisible UI Element

If you can’t see the blue progress bar inside the outlined section of that Copy Files section header, you are not the only one. It took me a couple minutes to realize it was there.

Look, I may be the pot calling the kettle black, or not picking the mote out of my own eye, or whatever metaphor from 50,000 years ago for hypocrisy you want to use because of the color of the hyperlinks on my site’s dark theme, but that is some kind of invisible ink level trickery on that progress bar.

Anyway… according to the invisible progress bar, as I write this SuperDuper! is about 75% of the way done cloning my internal drive.

Footnotes

  1. It looks like I’m ending that sentence with two punctuation marks, but SuperDuper! is the name of the product, and the period is the end of the sentence. Really.

Nownownow Oregon Edition

You all know, I hope, that I have a /now page on this site for quick dumps of things I’m into at the moment. It’s ephemeral, as /now pages should be.

The idea of the /now page comes from Derek Sivers, and he has a whole site, nownownow.com, dedicated to spreading the message and the sheer fun of /now pages.

And look! You can see people by region!

nownownow

I’m very pleased to announce that, as of this writing, 37 of us in Oregon are extremely proud of whatever it is we’re doing /now. You should definitely check it out. I’m even listed right beside Justin Miller.

nownownow Oregon

I was a little surprised to see that everyone in Japan touting their /now pages are gaijin1 and not Japanese. Some of those people in Japan need to talk to the people they live amongst and spread the news! Richard Möhn is my hero though – he lives in Kagoshima City, my old stomping grounds!

Here’s your assignment for today:

Footnotes

  1. Foreigners

Mail Weekly Reads Script Command

Raycast

Part of the Raycast series

As I mentioned previously, I have a Weekly Reads page that I set up to make it easier for me to share articles with my dad and brother (and anyone else who cares), complete with RSS feed. But let’s be real, neither of them wants to be bothered with my feed because they have stuff to do and we already have a tradition of emailing links to each other.1

Last night I created a Raycast Script Command that looks at the markdown file for my latest Reads update, parses and reformats it for email, and mails it out to them. It looks like this:

#!/bin/bash
# Required parameters:
# @raycast.schemaVersion 1
# @raycast.title Mail Weekly Reads
# @raycast.mode fullOutput
# Optional parameters:
# @raycast.icon
# @raycast.argument1 { "type": "text", "placeholder": "Subject" }
# @raycast.packageName Website
# Documentation:
# @raycast.author scott_willsey
# @raycast.authorURL https://raycast.com/scott_willsey
subject=$1
# Find the latest file in the specified directory
latest_file=$(ls -t ~/Sites/scottwillsey/src/content/reads | head -n1)
echo "Processing file: $latest_file"
# Read the contents of the file, remove YAML front matter, and modify markdown links
content=$(awk '
BEGIN { in_frontmatter=0; }
/^---$/ { in_frontmatter = !in_frontmatter; next; }
!in_frontmatter { print; }
' "/Users/scott/Sites/scottwillsey/src/content/reads/$latest_file")
content=$(echo "$content" | perl -0pe 's/\*\*\[(.+)\]\((.+)\)\*\*\n\n(.+)/$3 → $1 – $2\n/gm')
echo "Content after regex replacement:"
echo "$content"
# Escape the content for AppleScript
escaped_content=$(echo "$content" | sed -e 's/\\/\\\\/g' -e 's/"/\\"/g' -e 's/`/\\`/g' -e "s/'/\\'/g" -e 's/$/\\n/' | tr -d '\n')
# Pass the escaped content to the AppleScript
osascript <<EOD
on run
set theContent to "$escaped_content"
tell application "Mail"
set theFrom to "me@mine.com"
set theTos to {"dad@dads.com", "brother@brothers.com"}
set theCcs to {}
set theBccs to {}
set theSubject to "$subject"
set theSignature to ""
set theAttachments to {}
set theDelay to 1
set theMessage to make new outgoing message with properties {sender:theFrom, subject:theSubject, content:theContent, visible:false}
tell theMessage
repeat with theTo in theTos
make new recipient at end of to recipients with properties {address:theTo}
end repeat
end tell
send theMessage
end tell
end run
EOD

It takes one parameter when I run the script so that I can give the email a cutesy custom subject (another idea I stole from my brother).

RaycastMailWeeklyReadsScriptCommand

Then it parses the latest file in my web project’s /src/content/reads directory, replaces markdown links with “commentary → link title – link” for each link and following commentary paragraph in the page, and gives that to an AppleScript to send the mail.

From this markdown page that generates the individual HTML reads page:

ReadsMarkdown

To this email that gets sent to my long-suffering family members:

ReadsEmail

It’s simple and effective. You can do this and make your family hate you too.

Footnotes

  1. Fine, it’s my brother’s tradition and I’m stealing it, much to his annoyance I’m sure.

Raycast Extensions I Use

Raycast

Part of the Raycast series

Introductionum

Recently Niléane of MacStories wrote about the Raycast extensions she uses in Adding to My Mac’s Swiss Army Knife: A Raycast Extension Roundup - MacStories. I learned about a couple good ones that I wasn’t aware of, but I also thought I would list Raycast extensions I use here, along with a little bit about why for each of them.

The Listium

So here they are in alphabetical order-ish:

1Password

I actually underutilize this one. It allows for opening items in various ways (in 1P or in a browser), generating passwords, and more, but I’m so familiar with 1Password’s own keyboard shortcuts that I forget about the Raycast extension.

Append Clipboard

This is a really outstanding extension that lets you prepend or append text to whatever’s on your clipboard with a pre-specified separator. It sounds like something I should use all the time. I’d actually forgotten I had this one installed. 🤦‍♂️

Apple Mail

Stop me if you’ve heard this one before – here’s an extension that sounds really useful and I just don’t use it. But I should! Checking for new mail, composing an email, sharing finder items to mail, seeing recent or important mail, these are all possible with this extension. I think with most of these it’s a matter of building muscle memory.

Apple Reminders

I actually have used this extension! The Apple Reminders extension lets you use natural language to set reminds, in addition to allowing you to view and complete them.

Brew

Hi, Homebrew users! Want a quick way to see what you’ve installed with Homebrew, or search for and install new formulae and casks? You can do so from wherever you’re at in macOS with this extension.

Change Case

I use this extension all the time. Capital Case, camelCase, lower case, PascalCase, snake_case, UPPER CASE, and more… select your text and have at it. I love this one.

Clean Keyboard

I just started using this one today. It effectively locks your keyboard until you type ^ U (Ctrl+U) to unlock it, in order to allow you to clean the keyboard without deleting your home folder or sending an obscene message to your boss.

CleanShot X

I’m currently a heavy CleanShot X user and this extension has a ton of useful CleanShot X commands, but once again… the old muscle memory. If you picked up on the fact that I’m primarily writing this article to benefit myself, you win the prize of knowing how insightful you are.

Clipboard Formatter

I use this all the time to clear formatting from items in the clipboard. It comes in handy for certain things I have to enter into excel, for example. Yes, you can tell excel to paste in content only, but this is faster for me, and it’s also useful anywhere you need formatting removed.

ColorSlurp

I use ColorSlurp for color picking and therefore I use this extension to quickly launch it and grab colors, but truthfully I may very well switch to Color Picker instead. In fact, I’m doing it right now.

Copy Path

Oh, I love this one. For example, I just had a finder window with some pdfs in it open and I ran this extension and it gave me /Users/scott/Documents/BlueDragonfly/Sophos/ET15 - Sophos Central Endpoint Protection v5.0 - Engineer/ET15-Sophos-Central-5.0v5-Engineer-EN/01 Sophos Central Overview/ on my clipboard. You can’t beat that!

Cron Description

You ever look in crontab and see something illegible like */15 * * * * python3 /usr/local/sbin/scripts/wanipy/main.py --env prod > /dev/null and you can’t remember what */15 * * * * means? Well, it means run this thing every 15 minutes, and I know that because (well, I already knew that particular one) Cron Description told me!

cURL

I know I installed this for a reason, I just don’t remember what it was. Anyway, it lets you use curl to make an HTTP request, complete with custom headers.

Custom Folder

I haven’t actually used this yet, but this one helps you create custom folder icons and apply them to folders you want to customize. It’s not something you’ll use every day, but it’s pretty cool.

Date Format Converter

Input a date string and then format it into any standard date format you like. It’s that simple.

Dynamic Font Size

If you’ve ever struggled with the CSS clamp function, you’ll like this one. You just enter your parameters, like min and max font sizes and min and max viewport sizes, and it’ll generate your clamp for you.

Easings

Another CSS helper. You can quickly copy an ease-in like cubic-bezier(0.55, 0.085, 0.68, 0.53); or you can create your own custom CSS transitions.

Format JSON

Lets you take JSON that’s not formatted nicely and indent everything properly.

GIF Search

Search for GIFs, favorite them, paste them in various ways to various targets.

Git Commands

Search and learn what different git commands do.

GitHub CLI Manual

Helpful if you need to interact with GitHub using a CLI, this utility lets you search for commands and then opens the GitHub manual page for them.

GitHub

Too many commands to list here, but it lets you work with GitHub in various ways. I guess that’s pretty self-evident.

GitHub Repository Search

This is for searching for repos on GitHub and quickly opening them (and really quickly opening repos you’ve visited before).

Google Chrome

I primarily use this for searching Chrome history, but you can search bookmarks, open windows, search tabs, and more.

Google Translate

I have never used this. Maybe I should uninstall it.

HTTP.cat Status Codes

This reminds you what all those pesky HTTP status codes mean AND gives you a cute cat to go with it. The internet was invented for cats. Don’t ever forget it.

Iconify — Search Icons

Here’s a good one that I should really use more. Search for icons and get the svg source code.

Ingredients Lister

I can see why I installed this, but I can’t see why I haven’t used it yet. This grabs web pages or YouTube videos and gets recipe ingredients from them for you.

Installed Extensions

If you’re thinking “I bet Scott used this extension to list what extensions he has installed”, you win a Ferrari!! I mean, someone else is going to have to provide the Ferrari. But I’ll let you win it if they’re willing to give it. 🤷

Link Cleaner

Clean those dirty links of yours, dammit!! Auto-scrubs tracking and referral crap off URLs for you.

Lorem Ipsum

Occasionally while mocking up some web idea I’ll toss in some Lorem Ipsum text, and this is a super quick way to do it.

Markdown Reference

I kind of installed this one because I could. I don’t think I’ve ever needed to use it. I use Markdown all the time but it’s pretty simple.

Menubar Calendar

I like this one for letting me pop open a tiny calendar quickly and for also showing me upcoming calendar items.

Notion

I use Notion. I use Raycast. You’d think I’d use this extension. Truth is, I keep forgetting about it (again). I should at least use the search feature, that would save me some time getting to it in the Notion app itself.

Open Link in Specific Browser

This is actually kind of cool, but I never use this because I’ve created default browser script commands that I use all the time.

Open Path

Here’s another one I have but never used. It looks like a way to open things in various apps depending on what the source link is, but… I have no clue.

Pins

I like this one. It’s just another way to get to things – I use it to create pins to Finder folders, mostly.

Pipe Commands

This lets you take various input sources and pipe them into a script command. You’d think I would use this all the time.

Placeholder

Grabs placeholder images from Lorem Picsum. Pretty handy.

PromptLab

A super useful extension for creating AI prompts to be used with Raycast AI.

Quick Calendar

Yep, another calendar extension, but this one just pops open a bigger calendar window to look at.

Quick Event

Um, I’m going to let you guess whether or not I’ve ever actually used this, but it’s for quickly creating calendar events.

Random Data Generator

This is a really cool one for quickly generating random dates, CC numbers, text, email addresses, and more.

Raycast Icons

Have not used. There, are you happy? This is for looking at various Raycast-specific icons.

Reddit Search

I might use this more if 1.) I remembered I have it, 2.) I don’t hate Reddit so much. Does everyone there have to write like they’re 13?

Regex Tester

I should use this more and see if it’s any good. I’m spoiled by BBEdit’s RegEx playground feature so I never do.

Remove Paywall

I’m not going to overtly state why I use this one, but I use it quite a bit. I don’t constantly end-run around paywalls on the same sites over and over. It’s for one-offs.

Ruler

This is a cool one for measuring stuff on the screen. I used this the other day to make a screenshot of a certain form item on a web page vs. what the form item plugin thought the minimum width should be. Yes, I could have used the browser developer tools for that, but this was easier for the client to look at and instantly get the point.

Safari

Like its Chrome counterpart, I mostly use this for Safari history search, but it’s also good for searching tabs and the reading list (which I do use).

ScreenOCR

I don’t think I have ever used this because I’ve discovered the OCR reading shortcut in CleanShot X, but it is pretty cool.

Search npm Packages

I don’t really use npm for a lot these days because most stuff I script I now script in Python. I of course use npm with things like Astro, but I don’t need to search for anything for that.

Set Audio Device

This is a great one to use to make sure you’re using your podcast mic instead of the built-in laptop one or whatever else will pick up the cricket in your kitchen.

Shell

I don’t know. It looks cool, but I’ve never used it. I have Warp, what the hell, guys. Get off my back. 😄

Speedtest

A handy way to run the ever popular speedtest.net speed test.

Summarize YouTube Videos with AI

I keep meaning to show this one to Peter. An AI watches videos so you don’t have to. Problem is, AIs are shite at summarizing (imnsho).

System Monitor

I have no clue why I have this installed, because I use and love iStat Menus.

Text Shortcuts

I use this a LOT while writing and programming, and I LIKE IT. Quick and easy text transformations of all types.

Timezone Converter

I use this when I remember to. Mostly I use it before I’ve had coffee for trying to see what people on the east coast might be doing.

Type Snob

VERY specific text utility for curly quotes, real em and en dashes, etc.

Unicode Symbols Search

I don’t always search for unicode symbols, but when I do…

Unix Timestamp

Unix timestamps were created by the devil, so I guess this is really just a conduit to hell. But it’s a pretty cool one.

URL Tools

URL encoding and decoding made fun and easy!!!

UUID Generator

Who does not need a UUID from time to time? No one, that’s who. If you think you don’t, well now you know why your life is falling apart.

Visual Studio Code - Project Manager

I genuinely have no idea why I have this instead of the general purpose Visual Studio Code extension. Maybe I was drinking beer the night I installed this.

Warp

I LOVE WARP!!! I always forget I have this installed though. I know. I’m a loser.

Web Converter

I do webby things a lot. I should probably try to remember I have this installed. “Hey, Scott. You have this installed.”

Wordle

I used to play this, but then I paid for Lex.games and have never looked back.1

World Clock

Somewhere in the world, it’s X o’clock!! Find out where!! Also (and even cooler), put in an IP address and find out what time it is for whatever has that stupid IP.

YouTube

Search YouTube videos and channels like it’s 1999. I don’t know why I said that. I’m not even sure when YouTube was first created.

YouTube Downloader

Speaking of YouTube… download all of YouTube, one video at a time!!!!

Summarium

That’s pretty much it for now. I’m always open to revisiting things and uninstalling some things and installing other things. Most likely in a year from now, not much will have changed though.

Footnotes

  1. I love Lex.games. Don’t even get me started.

Weekly Reads

My brother likes to send emails to dad and I containing links of interesting things he’s read recently, replete with commentary of his own. I used to send one link at a time to them, then I tried his approach, and now I generally send a link at a time again.

Instead of this nonsense, I thought I’d create a Weekly Reads page to add Weekly Reads posts to, complete with its own RSS feed.

I may call it Weekly Reads, but I may add reads more than weekly or much less than weekly. I don’t know. Don’t pressure me! I have a lot going on! 😄

I need to create some links to it so people can find it and know it exists, but it’s there! It works!