DjangoCon US 2024 Talk
At DjangoCon US 2023 I gave a talk, and wrote about my experience preparing for that talk
Well, I spoke again at DjangoCon US this year (2024) and had a similar, but wildly different experience in preparing for my talk.
Last year I lamented that I didn't really track my time (which is weird because I track my time for ALL sorts of things!).
This year, I did track my time and have a much better sense of how much time I prepared for the talk.
Another difference between each year is that in 2023 I gave a 45 minute talk, while this year my talk was 25 minutes.
I've heard that you need about 1 hour of prep time for each 1 minute of talk that you're going to give. That means that, on average, for a 25 minute talk I'd need about 25 hours of prep time.
My time tracking shows that I was a little short of that (19 hours) but my talk ended up being about 20 minutes, so it seems that maybe I was on track for that.
This year, as last year, my general prep technique was to:
- Give the presentation AND record it
- Watch the recording and make notes about what I needed to change
- Make the changes
I would typically do each step on a different day, though towards the end I would do steps 2 and 3 on the same day, and during the last week I would do all of the steps on the same day.
This flow really seems to help me get the most of out practicing my talk and getting a sense of its strengths and weaknesses.
One issue that came up a week before I was to leave for DjangoCon US is that my boss said I couldn't have anything directly related to my employer in the presentation. My initial drafts didn't have specifics, but the examples I used were too close for my comfort on that, so I ended up having to refactor that part of my talk.
Honestly, I think it came out better because of it. During my practice runs I felt like I was kind of dancing around topics, but once I removed them i felt freer to just kind of speak my mind.
Preparing and giving talks like these are truly a ton of work. Yes, you'll (most likely) be given a free ticket to the conference you're speaking at — but unless you're a seasoned public speaker you will have to practice a lot to give a great talk.
One thing I didn't mention in my prep time is that my talk was essentially just a rendition of my series of blog posts I started writing at DjangoCon US 2023 (Error Culture)
So when you add in the time it took for me to brainstorm those articles, write, and edit them, we're probably looking at another 5 - 7 hours of prep.
This puts me closer to the 25 hours of prep time for the 25 minute talk.
I've given 2 talks so far, and after each one I've said, 'Never again!'
It's been a few weeks since I gave my talk, and I have to say, I'm kind of looking forward to trying to give a talk again next year. Now, I just need to figure out what I would talk about that anyone would want to hear. 🤔
Summer of Writing
In keeping with my themes for 2024 this summer was to be 'The Summer of Writing'.
This theme didn't have a specific post or word count, but I knew I wanted to write more1.
I had a few things I needed to do to get this started. One of them included starting a writing cohort. I wasn't sure how I would do that but it turns out in early June Lacey was having similar thoughts. Mario and Trey had some interest as well and so we formed a writing group!
We meet every Wednesday (more or less) for about an hour.
I had really hoped that the forming the cohort would give me the encouragement and accountability I needed ... and it HAS!
But I also quickly realized that all I had calendared (really) was the Wednesday writing session, and so I set out to have a 30 minute daily writing session on my own.
Life has a way of kicking you in the ass though when you least expect it.
Roughly 10 days into my 'Summer of Writing' a work thing came up and kind of consumed all of my thought and energy. I realized quickly that something had to give, and so I looked ahead at my Autumn theme and borrowed from it a bit, while still keeping the spirit of trying to write.
The next theme was going to be 'The Autumn of Mindfulness' which included starting a meditation practice so I dove into that. I also decided that I needed to try to find something to do from a physical activity perspective. I live in the desert of southern california so the summers are brutal ( daily highs that can average 110F+) and being outside isn't something I really like, even in the early morning, before the sunrise, the temps can be mid to high 80s ... sometimes even the low 90s.
I decided that I would pick up swimming and going to the gym to help alleviate some of the stress from work.
That, in addition to the writing, seemed to be a good thing to work on.
During my Summer of Writing I only wrote 5 posts (including this one)
- Summer of Writing
- SSH Keys
- How to ask a question without sounding like a jerk (mentioned in the Django News Newsletter #248)
- Reflections on Djangonaut Space Session 2 (lots of love on socials)
- Mentors (mentioned in Django News Newsletter #251)
The articles had a total of 5237 words and so, from the perspective of writing, I managed to do some writing, but it wasn't really anymore than what I had done during the Spring of Transition where I wrote 4 articles with 3890
- Spring of Transition
- Using justpath to go on a pyrrhic adventure to clean up my PATH
- Trying out pyenv ... again
- Winter of Learning
However, the ability to transition from one idea to another is something that CGP Grey talks about in his themes and so I don't feel too badly about it ... especially because the meditation and swimming have really been something I'm very proud of.
My meditation practice includes a 20 minute daily session first thing in the morning to help clear my mind for the day.
My swimming routine consists of swimming 3 days a week. When I started I could barely do a 20 minute swim. Just before DjangoCon US I swam 1800 yards in 54 minutes and for the most part didn't stop for a break. If you would have told me that in early July when I started I would not have believed you.
In a weird way the meditation and swimming kind of helped with the writing because it allowed me to stop thinking, which then allowed room for deeper thinking about my writing.
The stress of work has alleviated a bit and so I'm hoping that after DjangoCon US I'll be able to dedicate about 15 minutes a day to writing to start, and then ramp up to 30 minutes (similar to what I did with the swimming) and continue to swim and meditate.
One thing that I've found very helpful is to just add a little bit of a good habit and remove a bit of a bad habit. Sooner or later the bad habit is gone and replaced with the new good habit.
In the Autumn of mindfulness, which I will still try to do, I'll focus on eating right (I kind of eat like a 7 year old whose parents left the pantry stocked with a ton of junk food and then left for the weekend) so I'm going to work to get that under control.
All in all, it's been a successful summer of writing, even if it wasn't what I initially envisioned. But that's OK, and part of life.
As Mark Twain said, "Life is what happens when you're busy making other plans."2
Mentors
Having just finished up my second round of Djangonaut.Space (which I wrote about here) I wanted to write a bit about mentors ... how to find one, how to work with one, and how to be one.
Finding a Mentor
One of the best ways to find a mentor is through a program like Djangonaut Space. You're put into a cohort of other Django / Python programmers with a Captain and a Navigator. A program like this offers up ready made mentors in the form of the Captains and Navigators. Even your fellow participants can act as mentors.
The thing about a mentor, and finding one, is that what you're looking for isn't ONE mentor ... you're looking for a mentor in a specific aspect of life, whether personal or professional. In Djangonaut.Space you'll get a couple of mentors in Python / Django, but you may also find that you get a mentor who helps with thinking about / dealing with / finding developer jobs.
Working with a Mentor
Working with a mentor isn't just showing up and hoping that all of their knowledge in the specific aspect of life you're looking to be mentored on will suddenly flow from them to you like a fountain. You need to do a bit of homework too!
Mentors can provide lots of guidance, but like any guide, you kind of need to know where you're going ... even if it's just a vague direction. Having a goal of
I want to be a programmer
is a bit vague and difficult to help on. A mentor can provide some guidance for that, like
Try Python
but a goal like,
I want to learn programming to help automate some of these things
will definitely lead to more focused advice. Now the mentor can say,
That's great! Check out this book, these blogs, and follow this YouTuber ... also, here are 10 people you might find interesting on Mastodon (or your preferred Social Media platform of choice)
When working with a mentor they might provide open ended advice or guidance and expect that you'll have done something with it. Going back to the previous example, if a mentor offers the advice of Books, Blogs, YouTubers, etc at your next interaction they might ask, "So did you have a chance to check out any of those things".
If your answer is no, that's not the end of the world, but it might signal to the mentor that you're not ready for the mentor/mentee relationship. If your answer is a bit more defined, like "No, work and family have really been crazy, but I've set aside 2 hours this weekend to really check them out" will help the mentor know that you're going to actively try and work on the suggestions made.
Something to keep in mind is that this is a relationship with the mentor. They will try and provide helpful tips and guidance to you, and in return they expect that you'll be acting on those tips or guidance. If you're not willing or able to do that ... that's OK, but maybe this isn't the best time for your mentor relationship to start
How to be a mentor
Going back to my comment above, being a mentor isn't about being the ONLY mentor for a person, but a mentor for that person for a specific thing (or set of things) to help them grow. And that's really the point of mentoring. You want to help someone with their growth so that they can get better at a thing. This will have the strange effect of making you better at that thing as well.
It's easy enough to wave your hands when you're thinking about why something works the way it does, but if you're mentoring someone and they ask you a question you don't know, you are going to do yourself a great service by helping to explain and get them to understand the concept as well.
For example, something that really breaks my brain is mocking. It's just never really stuck with me and every time I need to mock something I'm basically learning it over again. If I had a mentee and they asked about mocking I'd probably get a deer-in-the-headlights sort of look and then say,
You know, it's something I struggle with, but let me write down some thoughts and my understanding on it and talk about it next time.
And, here's the key, next time you meet with them talk through what you learned (again) and show them how you learned it. What docs did you reference? What applications of mocking did you try? How did you try and figure it out?
I think so much of problem solving is learning how to learn. Honestly, if you can be presented with a problem and are able to come up with a solution without much thought then you may not understand the problem as well as you think. You might just be applying a previous solution to the current problem .. essentially trying to make a square peg try to fit in a round hole.
But with mentoring you can help people learn how you learned and to guide them on their journey to discovering things.
Something super important to remember is that it's their journey, not yours.
One thing I do, probably too much, is tell stories to try and get people to understand and remember how things work. I find that stories really work for my brain and help me to retain details that are important, or help to remind me of the ways in which problems were solved.
Honestly, every time someone comes to me with a new problem that I've never seen before my imposter syndrome kicks in like nobody's business! I beat myself up for how stupid I am that I can't solve this problem that I've never seen before.
But slowly, as I work through the problem, I start to see connections to other problems that I've solved. Not the same problem, but similar problems. This helps to get me to a solution ... but short walks help too ... and a good night's sleep.
And this is a prime opportunity for you to take what you've learned, how you've learned it and help a mentee with finding an approach that helps them in similar situations.
As a mentor, you don't need to be a WORLD EXPERT, you just need to be an expert on that one thing in comparison to the mentee. I once heard that an expert is just the person in the room who knows more about a topic than anyone else in that room. You don't need to be a Django Expert at DjangoCon to be a Django Expert at work when trying to introduce Django to developers that haven't seen it before.
Wrap up
Finding opportunities to be mentored can be hard, but a potential good place to start are programs like Djangonaut.Space and similar programs. Other places can be contributing to OSS projects1
Being a mentor doesn't mean you need to be a world expert, you just need to help one person find resources to help move them along in their journey. If you can do that, then I'd call you a pretty successful mentor!
- There are some caveats here, like an open and welcoming community ↩︎
Reflections on Djangonaut Space Session 2
A few weeks ago I completed my second session as a Djangonaut.Space Navigator. The Djangonaut.Space program is an opportunity for people to be introduced to contributing to Django and Django adjacent projects.
In this most recent session I was a Navigator for Team Mars with a fantastic Captain Tobe. Our Djangonauts were Andy, Maryam, and Rosana.
Among the 3 of them they took on 7 tickets, pushed 7 PRs and closed 5 tickets.
As part of the program we would meet weekly to talk about any blockers and try and work through them. These meetings also provided a platform to encourage one another.
One week we spoke about being a professional software developer working with Django which was a great conversation.
I really like this program for what it offers both the Djangonauts, and the mentors. I learned so much as part of this program.
As we were coordinating our first meeting I realized that the rest of my team were in time zones that were 7 - 8 hours ahead of mine! I was a bit worried initially that we'd have a hard time finding a common time to meet, but we settled on Wednesdays at noon and this turned out to be pretty perfect for all of us.
Each of our team meetings was similar to a standup where we'd talk about what work had been done the previous week, and any struggles that we were having. The djangonauts on team Mars were absolute Rock Stars. They picked up some pretty gnarly 1 issues and worked them to completion each time.
Working on a project like Django can be daunting and scary and time consuming. However, the amount that you can learn from working on a large project and code base like this is immeasurable.
Working to form a consensus on an issue or idea, whether it's code or documentation, can be challenging! But as Maryam said in her blog post about her experience with picking up a documentation ticket
To start safely, I picked a documentation change ticket just to get myself familiar with the process. One of my tickets involved updating some wordings in the documentation to make it easier for people to differentiate when a pull request needed a Trac ticket or not. Initially, I thought this would be a simple wording change. However, I soon realised that making changes to Django documentation itself requires a lot of thought and consideration.
This experience reminded me of my early days as a Django user. I loved Django for its documentation - detailed, thoughtful, well-organised, and easy to follow. Now, working on documentation changes as a contributor has shown me how Django achieves such clarity. Significant thought and effort go into making it clear and readable, minimising confusion and maximising understanding for readers.
If you don't know this going in then you can be disappointed or disillusioned with how long something might take to be accepted, or whatever, but a program like Djangonaut Space does, I think, help to ease newcomers into contributing and setting realistic expectations and, in general, enjoying the process.
One thing I tried to really emphasize with my team was that it won't be easy, and it will take some time, but that the effort will pay off with a ticket that has been closed ... and in the worst case you've helped to move it forward.
Another point I tried to keep front and center was the idea that this is a volunteer role and that if you're not having fun it's OK to take a step back. I think we need to hear that more and more, especially given the stress that many developers can be under for their $dayJobs.
I hope that this advice helped them in navigating the tickets that they worked. I also hope it helped to put into perspective what they were doing from a time commitment perspective.
One thing that I really love about the Django community in general, and the Djangonaut.Space community in particular, is how welcoming they are. The community strives to welcome you to be part of it.
BUT even with the welcoming nature, it can still be very hard to pick that first ticket, submit that first PR, and receive that first bit of feedback.
A program like Djangonaut.Space really helps to get people more comfortable with the process of picking and working on a ticket. It also helps to develop long term contributors to the project ... which is amazing.
I'm looking forward to the next time I'll be able to participate and would encourage anyone to get involved, either as a participant, or as a mentor.
How to ask why without sounding like a jerk
As technical folks working with non-technical folks sometimes the asks that come through are unclear. In order to get clarity on these we want to ask questions to get clarification on the ask, but it can be challenging to not sound like a jerk when we ask. This can happen even IF we do our best to come across in a positive way.
When trying to ask for more details on a project or request I find it's usually best to get to the source of the issue. I like to ask, "What problem are we trying to solve here?" or something similar.
This helps to put you and the requester on 'the same team' trying to 'solve the problem' and not in a potentially negative 'why are you asking me this stupid question' sort of light.
I can't say that I have 'one weird trick' that will always make this not a problem, but recently at my $dayJob I had an experience that might be helpful in seeing how to navigate this particular process.
The problem
I received an email that went something like this
Please see below. It seems that delivery of paper reports via courrier could be automated by sending them to a portal. What are your thoughts?
My initial thought was, "Yes, if we could automate these reports and send them electronically to a portal that would be more efficient."
However, there are some deeper questions here that need to be asked ... like:
- Why are we sending these reports in the first place?
Just asking this question though puts us into a potential state of conflict, i.e. it's similar to sounding like you're asking, "why would you do this stupid thing". In order to avoid this I reframed the question into 3 deeper questions that tried to frame 'the problem' and put me and the requester 'on the same team' to 'solve the problem'
- What are the reports?
- What are the recipients of the reports supposed to do with them?
- Do the recipients of the reports find them helpful, or do they just put them in the shred bin?
My first response to the sender was
Ideally any reports that are being delivered on printed paper by courrier would be better served to be delivered via some electronic means. Can you tell me, what are these reports and who are the intended recpients?
I wanted to explicitly ask who the intended recipients were (I work in Healthcare and these reports are 'for the doctors' but they might actually be getting delivered to an office manager, a front desk person, or anyone other than the doctor).
The sender responded back
They are reports that show a key metric for outstanding work left to do for a specific population of their membership. Each doctor (or their office) are free to do, or not do, anything with the information in these reports.
Next I asked if the recipients had been surveyed on the usefulness of the reports and that's when the sender indicated:
Actually, no. It's something that we need to do so that we can potentially consilidate reports and/or eliminate unhelpful reports.
The Solution
At the end we decided that before anywork was done to 'automate' the delivery of these reports, that we really needed to address the contents of the reports and determine which parts of them were helpful, and what parts weren't. Once we have a single report, or potentially a suite of reports, the automation and delivery work could actually start.
By working through and trying to determine the actual problem that needed to be solved by asking questions to help both me and the requester better understand what the real ask was, we saved a ton of development time and have a better path forward for making the information we have more relevant and actionable by the doctors' offices.
Will this work in every situation? Maybe not, but I believe it's a good starting point when trying to solve 'real world' problems in a work setting.
Tech folks have a (sometimes deserved) bad wrap, but we can shed this negative impression by showing the people that request solutions from us that we're both working towards the same goal of solving the problem.
SSH Keys
If you want to access a server in a 'passwordless' way, the best approach I know is to use SSH Keys. This is great, but what does that mean and how do you set it up?
I'm going to attempt to write out the steps for getting this done.
Let's assume we have two servers, web1
and web2
. These two servers have 1 non-root user which I'll call user1
.
So we have something like this
user1@web1
user1@web2
Suppose we want to allow user1 from web2 to access web1.
At a high level, we need to allow SSH access to web1 for user1 on web2 we need to:
- Create
user1
onweb1
- Create
user1
onweb2
- Create SSH keys on
web2
foruser1
- Add the public key for
user1
fromweb2
to onto theauthorized_keys
for foruser1
onweb1
OK, let's try this. I am using DigitalOcean and will be taking advantage of their CLI tool doctl
To create a droplet, there are two required arguments.:
- image
- size
I'm also going to include a few other options
- tag
- region
- ssh-keys1
Below is the command to use to create a server called web-t-001
doctl compute droplet create web-t-001 \
--image ubuntu-24-04-x64 \
--size s-1vcpu-1gb \
--enable-monitoring \
--region sfo2 \
--tag-name test \
--ssh-keys $(doctl compute ssh-key list --output json | jq -r 'map(.id) | join(",")')
and to create a server called web-t-002
doctl compute droplet create web-t-002 \
--image ubuntu-24-04-x64 \
--size s-1vcpu-1gb \
--enable-monitoring \
--region sfo2 \
--tag-name test \
--ssh-keys $(doctl compute ssh-key list --output json | jq -r 'map(.id) | join(",")')
The values for the ssh-keys
above will get all of the ssh-keys I have stored at DigitalOcean and add them.
The output looks something like:
--ssh-keys 1234, 4567, 6789, 1222
Now that we've created two droplets called web-t-001
and web-t-002
we can set up user1 on each of the servers.
I'll SSH as root into each of the servers and create user1
on each (I can do this because of the ssh keys that were added as part of the droplet creation)
adduser --disabled-password --gecos "User 1" user1 --home /home/user1
I then switch to user1
su user1
and run this command
ssh-keygen -q -t rsa -b 2048 -f /home/user1/.ssh/id_rsa -N ''
This will generate two files in ~/.ssh without any prompts
:
id_rsa
id_rsa.pub
The id_rsa
identifies the computer. This is the Private Key. It should NOT be shared with anyone!
The id_rsa.pub
. This is the Public Key. It CAN be shared.
The contents of the id_rsa.pub
file will be used for the authorized_keys
file on the computer this user will SSH into.
OK, what does this actually look like?
On web-t-002
, I get the content of ~/.ssh/id_rsa.pub
for user1
and copy it to my clipboard.
Then from web-t-001
as user1
I paste that into the the authorized_keys
in ~/.ssh
. If the file isn't already there, it needs to be created.
This tells web-t-001
that a computer with the private key that matches the public key is allowed to access as user1
The implementation of this is to be on web-t-002
as user1
and then run the command
ssh user1@web-t-001
The first time this is done, a prompt will come up that looks like this:
The authenticity of host 'xxx.xxx.xxx.xxx (xxx.xxx.xxx.xxx)' can't be established.
ED25519 key fingerprint is SHA256:....
This key is not known by any other names.
Are you sure you want to continue connecting (yes/no/[fingerprint])?
This ensure that you know which computer you're connecting to and you want to continue. This helps to prevent potential man-in-the-middle attacks. When you type yes
this creates a file called known_hosts
OK, so where are we at? The table below shows the files, their content, and their servers
Server | id_rsa | id_rsa.pub | authorized_keys | known_hosts |
---|---|---|---|---|
web-t-001 | private key | ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQDFwHs8VKWWSH737fVz4cs+5Eq8OcRJRf2ti0ytaChM1ySh2+olcKokHao3fl5G+ZZv4pQeKfCh8ClFP86g7rZN1evu2EFVlmBo1Ked4IwF4UBY2+rnfZmvxeHd+smtyZgfVZI/6ySfe1D+inAqv7otsMsNRRuE4aG0DNEJ39qwFxukGNcDXk9RNVvmwbCc5zT/HN0yMJ6Y7KtfPZgjl5v854VodZkfxsLpah7Bn64zAQr/xDh2KcWbtDrsvTdjNMPY7oW20VoqDs98mA6xAw9RNMI+xotNmivdWdv3BEYj9JyH61euTBQ27HC4LsOPuCOFKBqOwGXiJhpzvJZbNCcvQEztem3kqQFAPLg+4wBInyxnY2i31QX7+2IJs0a4pYTWRSRcrvwBAvi2GlXGltrZ7V6KOLzwBrXLD7XiO3C5kO5fcpanKlm/RdVAxUTjUq159H+v9om8HAgX/pIpYBpPnRrG7setNQVzDNQsxfR/YC0h+f9LWnnaBV6+51IjbaqAPSSf6KYv0AKO5XNlJsSTXNRBZaRvrfr0qllgXU82f9y8Eb0sgjL71wD9Fv24fV0toFW8PH3yOeePC6d7kNqZkFdSBksChzqagZwPudYnVhMmhMYV7k1v831H8WHdGPVRe9Z3BDnSCzf8o8fRS3mSEAJBiT30bXlGWUNopIpsgw== user1@ahc-web-t-001 | ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQCbx+wTVEcdy2Uu2iB+u6+R8Q0yH9ws92GM6K/XXmAXoUuXylkdJzw9vUeuaZTmGxwGRdp+lLh+vVDmiuzrUPjbkFA7Y1SxfR5lgJu7PviDDZzsFeUo5fqSp6FOC5x75jOjqy6fc68GzOnoxk4WR6EWKWRd+xqdgCTGWiuhfUEl1lw7YN8MUhd1Hi0Ef55ZpH133jCzffWbkLFFInyIwuzG6jaPsobNPRshvg9kUoFwo5WqCx/s8Zk4iVl86yCwoV+pXjiubLylSKF7hb7uDE4Ll8gADOtuXUqmc470yvzSxxI4yaZOFz4Ajo1qZHgscSOxWgb+ZVIOKhGK5ftHPaZ4CCxXuhW5J8L3Aqs0WQeRu9Goof83V/ruZhzgg1vnhmC2511QSS2dL6U7n2JNLtNnXNjeSQ0BGVlY1FuZRczmAxN9nJETmRCdUfiTwKdPS4LdfAwrnckPHKtk1QoFKietLwfbmipU+pGvt6qKpKeRfZ/XGbG+ZiQ7oPiqcYU/eh54IAUxxo9CvVHtn742A4ABqK5+0MJP5VuY3fcDU8dIvA0r4LpxRpG/KSB4yZMUhjf+KR7QUpN3mJIDOKTDAxpGOqpNoD2gTYGpyT13AdrRROpOjOJZJqDiVi6m6r/U+sIgqymsxDqBur5+n4VxvvXbdNd+6vz7AI12WA8I8+0xZw== user1@ahc-web-t-002 | NULL |
web-t-002 | private key | ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQCbx+wTVEcdy2Uu2iB+u6+R8Q0yH9ws92GM6K/XXmAXoUuXylkdJzw9vUeuaZTmGxwGRdp+lLh+vVDmiuzrUPjbkFA7Y1SxfR5lgJu7PviDDZzsFeUo5fqSp6FOC5x75jOjqy6fc68GzOnoxk4WR6EWKWRd+xqdgCTGWiuhfUEl1lw7YN8MUhd1Hi0Ef55ZpH133jCzffWbkLFFInyIwuzG6jaPsobNPRshvg9kUoFwo5WqCx/s8Zk4iVl86yCwoV+pXjiubLylSKF7hb7uDE4Ll8gADOtuXUqmc470yvzSxxI4yaZOFz4Ajo1qZHgscSOxWgb+ZVIOKhGK5ftHPaZ4CCxXuhW5J8L3Aqs0WQeRu9Goof83V/ruZhzgg1vnhmC2511QSS2dL6U7n2JNLtNnXNjeSQ0BGVlY1FuZRczmAxN9nJETmRCdUfiTwKdPS4LdfAwrnckPHKtk1QoFKietLwfbmipU+pGvt6qKpKeRfZ/XGbG+ZiQ7oPiqcYU/eh54IAUxxo9CvVHtn742A4ABqK5+0MJP5VuY3fcDU8dIvA0r4LpxRpG/KSB4yZMUhjf+KR7QUpN3mJIDOKTDAxpGOqpNoD2gTYGpyT13AdrRROpOjOJZJqDiVi6m6r/U+sIgqymsxDqBur5+n4VxvvXbdNd+6vz7AI12WA8I8+0xZw== user1@ahc-web-t-002 | NULL | |1|V6uYGlSiYXpzFAly9RQHybzl07o=|VUkDfRcKGyUgLdJn+iw6RJE+r68= ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILpbPHA1jL0MHzBI8qb2X0mHDx3UlrKCdbz1IspvaJW9 |1|dshOpqJI2zQxEpj1pleDmtkijIY=|ZYV8bCeLNDdyE7STDPaO2TzYUEQ= ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDnGgQUmsCG23b6iYxRHq5MU9xd8Q/p8j3EyZn9hvs4IsBoCgeNXjyXK28x7Mt7tmfjrF/4jLcq4o2TTAwF6eVQZ4KXoBa73dYqYDmYTVKTwzZL9CsJTWHTsSnU8V/J3Tml+hIFrjZzWP34+lL9xyOVin5R0PT/OCG49ecb5tt2FxTZeyWI47B/bCDGXV9g1tjZ8+mnbLXpIdQ9+6GllRZrEGvXWm6z/U3YHO84dcG0IZJ7QsEaAiLSBC/t83So4MDQgdttm+aHZXds4jej5E3QwUex8JkVVn0X7Nr4yKMDkSk7ABD6AFhpa4ESXysqI33CUaSBROAuu4lmfOkLmyRZK2vQ6soiOW8iBgCEl/q8MSOEpZeAi3faYbUnOpLzLDBcCoAuSDoexrTixxlhJmRDeS3PlcXmzvkJl7RRKUYZZcPQOd2w9ipCIAD1PevNlnmmZcfkRe0RRvAyF1mqcO/x5Ovtq9QLbycFHYfh/3LcPuDOWBtT+mVd5FeNUMsZ6+8= |1|HJd+aDFM66x8jJT1zUZV59ceL10=|PfHQu/Yg35QPBKk7FvNO/46b76o= ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBP+XwUozGye03WJ6zC7yoJQaYF8HiUQKmZwnQO0wSxMm9x9nBdPEx1bmyZHHUbMnwQnoeAMmd6hgK6H8hbxzEas= |
OK, so now we have set it up so that user1
on web-t-002
can access web-t-001
as user1
. A reasonable question to ask at this point might be, can I go the other way without any extra steps?
Let's try it and see!
From web-t-001
as user1
lets run
ssh user1@web-t-002
And see what happens
We get the same warning message we got before, and if we enter yes
we then see
user1@yyy.yyy.yyy.yyy: Permission denied (publickey).
What's going on?
We haven't added the public key for user1
from web-t-002
to the authorized_keys
file on web-t-001
.
Let's do that now.
Similar to before we'll get the content of the id_rsa.pub from web-t-001
for user1
and copy it to the authorized_keys
file on web-t-002
for user1
.
When we do this we are now able to connect to web-t-002
from web-t-001
as user1
OK, SSH has 4 main files involved:
id_rsa.pub
(public key): The SSH key pair is used to authenticate the identity of a user or process that wants to access a remote system using the SSH protocol. The public key is used by both the user and the remote server to encrypt messages.id_rsa
(private key): The private key is secret, known only to the user, and should be encrypted and stored safelyknown_hosts
: stores the public keys and fingerprints of the hosts accessed by a userauthorized_keys
: file containing all of the authorized public keys that have been generated. This is what tells the server that it’s Ok to use the key to allow a connection
Using with GHA
ssh-action from AppleBoy
A general way to access your server with GHA (say for CICD) is to use the GitHub action from appleboy called ssh-action
There are 3 key components needed to get this to work:
- host
- username
- key
Each of these can/should be put into repository secrets. Setting those up is outside the scope of this article. For details on how to set repository secrets, see this article.
Using the servers from above we could set up the following secrets
- SSH_HOST: web-t-001 IP Address
- SSH_KEY: the content from /home/user1/.ssh/id_rsa ( from web-t-002 )
- SSH_USERNAME: user1
And then set up an GitHub Action like this
name: Test Workflow
on:
workflow_dispatch:
jobs:
deploy:
runs-on: ubuntu-22.04
steps:
- name: deploy code
uses: appleboy/ssh-action@v0.1.10
with:
host: ${{ secrets.SSH_HOST }}
port: 22
key: ${{ secrets.SSH_KEY }}
username: ${{ secrets.SSH_USERNAME }}
script: |
echo "This is a test" > ~/test.txt
Using this set up we've made the docker image that runs the GHA to be (basically) known as web-t-001
and it has access as user1
in the same way we did in the terminal.
When this action is run it will ssh into web-t-001
as user1
and create a file called test.txt
in the home directory. The content of that file will be "This is a test"
- I'm using these keys so that I can gain access to the server as root ↩︎
Spring of Transition
I've written before about the Theme's that CGP Grey has discussed and I think they're great! I've just recently completed my 'Spring of Transition'.
So what is the Spring of Transition? For me it meant focusing on that last bit of time that my daughter will be living with me and my wife full time.
She just graduated from High School and is getting ready to go off to college in the Fall. I've taken the last quarter to really try and focus on spending quality time with her, and enjoy the last bits of her living here with me and my wife.
One of the things I am eternally grateful for is that when my daughter was a baby/toddler I started a WordPress blog to keep track of all of the adventures we got into. This was 2008 - 2010 and while Facebook was kind of a thing, Instragram was NOT a thing. I used this blog to post pictures with a fun caption of the context of the picture. I would also write a monthly letter to her and recap what fun adventures we had, or what changes I had noticed.
I did this for a couple of years, but then life got in the way and the changes that she was going through were harder and harder to see, and capture, with a camera. This made it very hard to write about as well.
I ended up taking down my site, but I kept a backup of the WordPress XML just in case1 I would want to use it again.
At one point I stumbled upon a journalling app called Day One that I used to journal. I used it for a couple of years and then found a feature that allowed me to import my WordPress blog data.
I played around with this a bit and finally made the plunge to import the data. It may be the best decision I've ever made with respect to tech.
Over the last several years I focused on trying to journal every day. One of the grear features of Day One is 'On This Day'. After I journal I'll click on that tab and look back at what I've written "On This Day".
The best entries are from the blog. Small reminders of the toddler my (now) adult daughter was.
This has been especially great over the last 3 months as she has wrapped up High School and prepared for College. It's really allowed me to focus on the great times we had, and work to create some awesome new memories.
We didn't do anything exotic, or visit any far off places during this 'Spring of Transition' ... there's already sooooo much to be done at the end of a High School career!
But, I have tried to focus on the things that we do like to do.
We have watched a bunch of Star Wars:
We've also tried to watch the Battle Star Galactica TV show from the early 2000s. This didn't go well.
Another thing that we've been doing is trying to cook meals together. My daughter is a vegetarian, and my wife and I are not, so this makes dinner (and other meals) challenging.
To over come this she found several vegetarian dishes that she thought I would like and we've made them together for dinner. It's been a real treat to see what she thinks of some of these recipes, which are mostly Thai and Vietnamese meals which is not something she would typically eat (she's really big into the various combinations of cheese and starch, i.e. Grilled Cheese Sandwiches, Quesadillas, and Cheese Pizza 😊
We've also been trying really hard to get take out from a Thai place that has many vegetarian options, but haven't been able to make it work just yet. This looks to be something that we'll hopefully be able to do over the summer before she leaves for school.
I think the hardest part about all of this has been knowing that each of the things that she's done will be the last. She had her last dance recital (I've been watching her dance for 15 years). She had her last Girl Scout meeting. She had her last High School class.
Soon she'll have her last night sleeping here before she goes to school.
Obviously I always knew this day would come, but I didn't really think it would get here so quickly. The days are long, but the years are short. I never really understood what that meant until these last few months.
There is now this full grown adult living in my home ... at least for the next few months. But just yesterday she was a silly toddler walking around the house claiming that the elves must have left her milk in the pantry!
I know that her leaving for college isn't the last time I'll ever see her. I mean, she'll still have a room at our house, so she'll want to come back at some point, right? Right?
And it's not like she's going to school on the other side of the country. It's just a short 2 hour drive away. But still ... it won't be the same.
It's just all so different. My wife and I are planning to be empty nesters. Like, what does that even mean? For the last 19 years our daughter has been a part of, and influenced, the lives that we've lead.
I'm also a little nervous about my 'little girl'2 going away into the big bad world. I know I shouldn't be though. She is the most thoughtful, capable, intelligent, caring, hard working person I've ever met in my life.
I know she's going to do great in her next chapter.
I just didn't realize that next chapter would come so soon.
Using justpath to go on a pyrrhic adventure to clean up my PATH
A while ago I heard about a project called justpath from Jeff Tripplet on Mastodon. It seemed like a neat project to try and clean up my path and I figured, what the heck, let me give it a try.
I installed it and when I ran it for the first time, the output looked like this
1 /Users/ryan/.cargo/bin
2 /usr/local/opt/ruby/bin (resolves to /usr/local/Cellar/ruby/3.2.2_1/bin)
3 /Users/ryan/google-cloud-sdk/bin
4 /Users/ryan/.pyenv/shims
5 /opt/homebrew/Cellar/pyenv-virtualenv/1.2.3/shims
6 /opt/homebrew/bin
7 /opt/homebrew/sbin
8 /Library/Frameworks/Python.framework/Versions/3.12/bin
9 /usr/local/bin (duplicates: 3)
10 /usr/local/sbin
11 /Library/Frameworks/Python.framework/Versions/3.11/bin
12 /Library/Frameworks/Python.framework/Versions/3.10/bin
13 /usr/local/bin (duplicates: 3)
14 /System/Cryptexes/App/usr/bin (resolves to /System/Volumes/Preboot/Cryptexes/App/usr/bin)
15 /usr/bin
16 /bin
17 /usr/sbin
18 /sbin
19 /var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/local/bin (resolves to /private/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/local/bin, directory does not exist)
20 /var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/bin (resolves to /private/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/bin, directory does not exist)
21 /var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/appleinternal/bin (resolves to /private/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/appleinternal/bin, directory does not exist)
22 /opt/X11/bin
23 /Library/Apple/usr/bin
24 /Library/TeX/texbin (resolves to /usr/local/texlive/2024basic/bin/universal-darwin)
25 /usr/local/share/dotnet
26 ~/.dotnet/tools (directory does not exist)
27 /Library/Frameworks/Mono.framework/Versions/Current/Commands (resolves to /Library/Frameworks/Mono.framework/Versions/6.12.0/Commands)
28 /Applications/Postgres.app/Contents/Versions/latest/bin (resolves to /Applications/Postgres.app/Contents/Versions/14/bin)
29 /Applications/Xamarin Workbooks.app/Contents/SharedSupport/path-bin (directory does not exist)
30 /Users/ryan/.local/bin (duplicates: 2)
31 /usr/local/bin (duplicates: 3)
32 /Users/ryan/.local/bin (duplicates: 2)
That's a lot to look at, but helpfully there are a few flags to get only the 'bad' items
justpath --invalid
justpath --duplicates
Running justpath --invalid
got this
18 /var/run/com.apple.security.cryptexd/codex.system/bootstrap/ usr/local/bin (resolves to /private/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/local/bin, directory does not exist)
19 /var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/bin (resolves to /private/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/bin, directory does not exist)
20 /var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/appleinternal/bin (resolves to /private/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/appleinternal/bin, directory does not exist)
26 ~/.dotnet/tools (directory does not exist)
29 /Applications/Xamarin Workbooks.app/Contents/SharedSupport/path-bin (directory does not exist)
and running justpath --duplicates
got this
8 /usr/local/bin (duplicates: 3)
12 /usr/local/bin (duplicates: 3)
30 /Users/ryan/.local/bin (duplicates: 2)
31 /usr/local/bin (duplicates: 3)
32 /Users/ryan/.local/bin (duplicates: 2)
Great! Now I know what is invalid and what is duplicated. Surely justpath
will have a command to clean this up, right?
Turns out not so much, and for good reasons:
justpath
does not alter the path itself, it provides the corrected version that a user can apply further. A child process the one that Python is running in cannot alter the parent environment PATH. As for reading the PATH -justpath
relies on what is available fromos.environ['PATH']
.
So I posted on Mastodon to see how others may have approached this and Jeff replied back
I ran the tool and copied the output before starting from an empty PATH.
Then I ran justpath and added them back one at a time.
mise helped me cut at least half a dozen weird/duplicate path statements alone. I had quite a bit of tool overlap.
I poked around with trying to find where the invalid values were coming from and found this answer
Cryptexes are used to update parts of macOS quickly, without requiring a full rebuild of the SSV (see this answer for details). I don't know whether removing these paths will break the installation of such cryptexes. But if you want to take the risk, you can remove
/etc/paths.d/10-cryptex
(or move it to a safe place in case you need it later on). PS: Invalid entries in PATH don't really hurt, they primarily slow down (a very little bit) the lookup of new commands run from the shell the first time.
This supported my assumption that the invalid PATH variables were likely due to macOS upgrades (I guessed that based on the existence of invalid PATH variables on other Macs in my house that didn't have a programmer / developer using them)
So, with that I found 2 files that were in /etc/paths.d
and moved them to my desktop. Once that was done I restarted my terminal and got this output
1 /Users/ryan/.cargo/bin
2 /usr/local/opt/ruby/bin (resolves to /usr/local/Cellar/ruby/3.2.2_1/bin)
3 /Users/ryan/google-cloud-sdk/bin
4 /Users/ryan/.pyenv/shims
5 /opt/homebrew/Cellar/pyenv-virtualenv/1.2.3/shims
6 /opt/homebrew/bin
7 /opt/homebrew/sbin
8 /Library/Frameworks/Python.framework/Versions/3.12/bin
9 /usr/local/bin (duplicates: 3)
10 /usr/local/sbin
11 /Library/Frameworks/Python.framework/Versions/3.11/bin
12 /Library/Frameworks/Python.framework/Versions/3.10/bin
13 /usr/local/bin (duplicates: 3)
14 /System/Cryptexes/App/usr/bin (resolves to /System/Volumes/Preboot/Cryptexes/App/usr/bin)
15 /usr/bin
16 /bin
17 /usr/sbin
18 /sbin
19 /opt/X11/bin
20 /Library/Apple/usr/bin
21 /Library/TeX/texbin (resolves to /usr/local/texlive/2024basic/bin/universal-darwin)
22 /usr/local/share/dotnet
23 /Library/Frameworks/Mono.framework/Versions/Current/Commands (resolves to /Library/Frameworks/Mono.framework/Versions/6.12.0/Commands)
24 /Applications/Postgres.app/Contents/Versions/latest/bin (resolves to /Applications/Postgres.app/Contents/Versions/14/bin)
25 /Users/ryan/.local/bin (duplicates: 2)
26 /usr/local/bin (duplicates: 3)
27 /Users/ryan/.local/bin (duplicates: 2)
This just left the duplicates that were being added. There are a few spots where applications (or people!) will add items to PATH and there are MANY opinions on which one is The Right Way TM. I use zshell and looked in each of these files (~/.profile
, ~/.zshrc
, ~/.zprofile
) and found that only my .zshrc
file contained the addition to PATH for the duplicates I was seeing
/usr/local/bin
/Users/ryan/.local/bin
With that, I simply commented them out of my .zshrc
, restarted my terminal and now I am down to only two duplicates duplicate
8 /usr/local/bin (duplicates: 2)
12 /usr/local/bin (duplicates: 2)
I looked everywhere trying to find where this duplicate was coming from. I tried a few variations of find
find . -type f -name '.*' 2>/dev/null -exec grep -H '/usr/local/bin' {} \;
But the results were only the commented out lines from .zshrc
.
I did find this issue on the Homebrew repo and thought I might be onto something, but it was for a different path so that doesn't seem to be the culprit.
I eventually gave up on trying to find the one true source of the duplicate entry (though I suspect that there is something adding it in the same way that Homebrew is can add a duplicate PATH variable) because I found this command
typeset -U path
I added it to my .zshrc
, restarted my terminal and voila, a clean PATH
1 /Users/ryan/.cargo/bin
2 /Users/ryan/google-cloud-sdk/bin
3 /Users/ryan/.pyenv/shims
4 /opt/homebrew/Cellar/pyenv-virtualenv/1.2.3/shims
5 /opt/homebrew/bin
6 /opt/homebrew/sbin
7 /Library/Frameworks/Python.framework/Versions/3.12/bin
8 /usr/local/bin
9 /usr/local/sbin
10 /Library/Frameworks/Python.framework/Versions/3.11/bin
11 /Library/Frameworks/Python.framework/Versions/3.10/bin
12 /System/Cryptexes/App/usr/bin (resolves to /System/Volumes/Preboot/Cryptexes/App/usr/bin)
13 /usr/bin
14 /bin
15 /usr/sbin
16 /sbin
17 /opt/X11/bin
18 /Library/Apple/usr/bin
19 /Library/TeX/texbin (resolves to /usr/local/texlive/2024basic/bin/universal-darwin)
20 /usr/local/share/dotnet
21 /Library/Frameworks/Mono.framework/Versions/Current/Commands (resolves to /Library/Frameworks/Mono.framework/Versions/6.12.0/Commands)
22 /Applications/Postgres.app/Contents/Versions/latest/bin (resolves to /Applications/Postgres.app/Contents/Versions/16/
I still want to try and figure out where that extra /usr/local/bin
is coming from, but for now, I have been able to clean up my PATH.
So, the question is, does this really matter? In the answer I found the last statement really caught my attention
PS: Invalid entries in PATH don't really hurt, they primarily slow down (a very little bit) the lookup of new commands run from the shell the first time.
So the answer is, probably not, BUT just knowing that I had duplicates and invalid entries in my PATH was like a splinter in my brain 1 that needed to be excised.
What are your experiences with your PATH? Have you gone to these lengths to clean it up, figured, "Meh, it's not hurting anything, why bother?"
- Yes, that is a Matrix reference ↩︎
Trying out pyenv ... again
I think I first tried pyenv
probably sometime in late 2022. I saw some recent stuff about it on Mastadon and thought I'd give it another go.
I read through the installation instructions at the ReadMe at the repo and checked to see if it was already installed (spoiler alert it was!)
I noticed that I was not on the current version (2.3.36 at the time of this writing) and decided that I needed to update it.
With the update out of the way I tried to install a version of Python with it, starting at Python 3.10 (because why not?!)
pyenv install 3.10
But when I ran it I got an error like this:
BUILD FAILED (OS X 12.3.1 using python-build 20180424)
Which lead me here. There were some comments people left about deleting directories (which always makes me a bit uneasy ... especially when they're in /Library/)
Reading further down I did come across this comment
I had to uninstall and reinstall Home Brew before it returned to work. It concerned the change from Mac Intel to Mac M1(Silicon). See the article below from Josh Alletto to find out why. https://earthly.dev/blog/homebrew-on-m1/#:~: text=On%20Intel%20Macs %2C%20Homebrew%2C%20and, %2Fusr%2Flocal%2Fbin%20.&text= Homebrew%20chose%20%2Fusr %2Flocal%2F,in%20your %20PATH%20by%20default.
The link in the comment was a bit malformed, but I was able to clean it up and get this link. This is where I re-discovered1 that the way Homebrew is installed changed with the transition to the Apple Silicon.
Now, I got a new M2 MacBook Pro in March 2023 and since I don't use Homebrew a lot AND I didn't really use pyenv for anything, I hadn't noticed that stuff kind of changed.
Following the steps outlined I was able to redo my Homebrew and now have pyenv working. Now, the only question is will it's use 'stick' with me this time?
- earlier in the day I was working through a post by Marijke about Caddy and there was a statement in her write up about how Homebrew on the M1 Macs stored files in a different directory, but when I ran the command to check where Homebrew was pointing I got the Intel location, not the Apple Silicon location ... this really should have been my first clue that some part of my set up was incorrect ↩︎
Winter of Learning
Winter of Learning Retrospective
Have you heard the good word about themes? If you haven't, take a look at this great video by CGP Grey on Themes and how they can work. For the last couple of years I've been doing yearly themes ... with limited success. This lack of success was entirely due to me not actually reviewing the status of my themes until the end of the year ... and by then it's too late!
This last December I decided that I'd do the themes, but this time I'd do seasonal set of themes instead of one BIG annual theme.
My current theme ended yesterday (March 18th) and this time I'm going to actually take stock of where I am and how 'well' I did.
Since my theme started on December 21, 2023 which is the Northern Hemisphere Winter Solstice, I decided to have a seasonal theme of 'Winter of Learning' with the following things I wanted to learn more about:
To help me keep track of this I dusted off my TIL github repo and started to write down some TILs. Over the course of the 88 days of my Seasonal theme I added 28 TILs. I also had 16 other, more personal, TILs that didn't make it into the repo for a total of 44 TILs. With 88 days that's a 50% hit rate on writing down stuff that I learned.
This is much better than I thought I had done. I'd been pretty down on myself because I had meant to write a TIL every night, but I didn't. I over estimated the number of times I didn't write a TIL and thought I had done much worse on it than I had.
Now, just because I wrote a TIL doesn't mean that it was one of the topics above that I had indicated I would WANT to write about, but that's OK! The point of a TIL is to document some stuff that you learned and the topics above were only ever meant to be guides, not directives.
I think the one thing I learned that I'm more proud of is spending a pretty good amount of time one weekend trying to learn Docker better. During one of Jeff Triplett's office hours I had joked that Docker scared me. And it was me actually saying it out loud that drove me to sit down and figure some shit out. I even had a public notes issue about it!
Overall this Winter of Learning isn't what I thought it would be, but I'm glad I did it. I am going to work to try and keep on writing TILs and hopefully I'll be able to get in at least 2 per week!
That being said, it's now time to prepare for my next seasonal theme ... the Spring of Transition. My daughter is a Senior in High School and is getting ready to head off to college. Now seems like a good time to start getting ready for my wife and I to be empty nesters and so we'll be spending the next 92 days figuring out how we can do that.
Page 6 / 24