aboutsummaryrefslogtreecommitdiff
path: root/posts
diff options
context:
space:
mode:
Diffstat (limited to 'posts')
-rw-r--r--posts/Building_rbenv_on_OpenBSD_7.5.md47
-rw-r--r--posts/Installing_WordPress_on_NearlyFreeSpeech.md166
-rw-r--r--posts/New_Domain_and_Code_Forge.md62
-rw-r--r--posts/OpenBSD_is_a_Cozy_Operating_System.md32
-rw-r--r--posts/Please_Make_Your_Table_Headings_Sticky.md26
-rw-r--r--posts/Please_Make_Your_Table_Headings_Sticky.md~29
-rw-r--r--posts/Website_Backups_with_Apple_iCloud.md24
-rwxr-xr-xposts/x220.md12
8 files changed, 390 insertions, 8 deletions
diff --git a/posts/Building_rbenv_on_OpenBSD_7.5.md b/posts/Building_rbenv_on_OpenBSD_7.5.md
new file mode 100644
index 0000000..90f5479
--- /dev/null
+++ b/posts/Building_rbenv_on_OpenBSD_7.5.md
@@ -0,0 +1,47 @@
+# Building rbenv on OpenBSD 7.5
+
+2024-06-02
+
+I use Ruby (specifically with Jekyll) for a lot of my clubs/projects while using my personal laptop ([X220 ThinkPad](/posts/x220/)) which is runs OpenBSD. Since I recently upgraded to OpenBSD 7.5 I thought it could be helpful for others if I shared my process of building and using `rbenv` to install different Ruby versions.
+
+## Before We Build
+
+First, be sure to install the required packages in order to build from source, and then clone the core `rbenv` repo:
+
+ pkg_add git gcc gmake libffi libyaml openssl
+ git clone https://github.com/rbenv/rbenv.git ~/.rbenv
+
+## Building `rbenv`
+
+Add `rbenv` to your PATH and initialize it (place this inside your `.bashrc` or `.zshrc` etc):
+
+ export PATH="$HOME/.rbenv/bin:$PATH"
+ export PATH="$HOME/.rbenv/shims:$PATH"
+ export RUBY_CONFIGURE_OPTS="--with-openssl-dir=/usr/local"
+ eval "$(rbenv init -)"
+
+Then reload your shell (zsh in this example):
+
+ source ~/.zshrc
+
+Next we will need to install `ruby-build` as a `rbenv` plugin. Clone the ruby-build repository into the rbenv plugins directory:
+
+ git clone https://github.com/rbenv/ruby-build.git ~/.rbenv/plugins/ruby-build
+
+## Installing Ruby and Setting Our Version
+
+Now use `rbenv` to install the desired version of Ruby (we will get an older version for this example):
+
+ rbenv install 3.3.0
+
+If you run into issues, you may need to specify your openssl directory:
+
+ RUBY_CONFIGURE_OPTS="--with-openssl-dir=/usr/local" rbenv install 3.3.0
+
+If you're on a slower machine (like mine) this might take a little bit. Just grab a coffee or a snack while you wait!
+
+Then navigate to your project of choice and set the `local` Ruby version:
+
+ rbenv local 3.3.0
+
+That's it! If you'd prefer to set this version of Ruby for all projects going forward, simply replace `local` with `global`.
diff --git a/posts/Installing_WordPress_on_NearlyFreeSpeech.md b/posts/Installing_WordPress_on_NearlyFreeSpeech.md
new file mode 100644
index 0000000..4af9a4e
--- /dev/null
+++ b/posts/Installing_WordPress_on_NearlyFreeSpeech.md
@@ -0,0 +1,166 @@
+# Installing WordPress on NearlyFreeSpeech
+
+2024-05-27
+
+I recently went through the process of porting over my wife's small business website (built off of WordPress + Woocommerce) from EasyWP to NearlyFreeSpeech. Although the process was fairly easy-going, I thought I would post my complete process here. That way, others who might wish to make the same switch can avoid running into any minor bumps along the way.
+
+> Some of this information has been lifted from [the official NFS docs](https://members.nearlyfreespeech.net/tdarb/support/wordpress), but these pages require a membership to access
+
+### Download WordPress
+
+We will use the `wp-cli` that comes packaged with NearlyFreeSpeech (NFS):
+
+1. Connect to your site via SSH.
+2. Change to the directory you want to be the base of your blog (e.g. /home/public if WordPress will be running the whole site, or /home/public/blog if you want to share the site with other content).
+
+For help using WP-CLI from the SSH command line, use this command:
+
+ wp help
+
+To download and unpack the latest version of WordPress, enter the following command:
+
+ wp core download
+
+## Create a MySQL Process and Database
+
+Follow the instructions in the NearlyFreeSpeech.NET FAQ to [create a MySQL process](https://members.nearlyfreespeech.net/faq?q=MySQL#MySQL) if you haven't already.
+
+Next, [create a new database](https://members.nearlyfreespeech.net/faq?q=CreateDatabase#CreateDatabase) within that process. Note the name of the process and the name of the database.
+
+**Important**: Do not use your own MySQL credentials to connect WordPress to your database. Instead, create a new user. This will protect your member password in the event that your site becomes compromised.
+
+
+- Click on the [MySQL tab](https://members.nearlyfreespeech.net/mysql) in the member interface
+- Click "[Open phpMyAdmin](https://phpmyadmin.nearlyfreespeech.net/" in the Actions box.
+- Enter the DSN ("Server") of your MySQL Process, MySQL username, and MySQL password. The DSN and username can be found on the Process Information page in our member UI.
+- Click on the "Users" tab.
+- Click "Add user." (It's toward the bottom left of the page.)
+- Give the user a descriptive name. We'll use exampledbuser here, but you should pick something better, like wpuser or something representative of your blog.
+- Make sure to leave the Host: selectbox on "Any host."
+- Click the "Generate" link to generate a nice strong password.
+- Use cut and paste to copy the new password somewhere, you'll need it later. (We'll use dbpassword here.)
+- Grant the following permissions to the new user:
+ - All the permissions except "file" in the "data" box,
+ - Everything in the "structure" box, and
+ - "LOCK TABLES" in the "administration" box.
+- Click the "Go" button in the lower right.
+- Exit phpMyAdmin.
+
+## Generate a WordPress Configuration File
+
+At the SSH command line (replace the examples with the info for the database and user you created above):
+
+ wp core config --dbhost=example.db --dbname=exampledb --dbuser=exampledbuser --dbpass=dbpassword
+ chmod 644 wp-config.php
+
+## Run the WordPress Installation and Setting Permissions
+
+To get your permalinks to work properly, you must set up an .htaccess file.
+
+
+1. Go to the Dashboard for your WordPress site. (e.g. https://www.example.com/wp-admin/index.php)
+2. In the navigation sidebar, find Settings and, under that, Permalinks.
+3. Select your preferred link style under "Common Settings." (We like "Day and name.")
+4. Scroll down and select the "Save Changes" button.
+
+Next, create an `.htaccess` file for your WordPress site. We suggest doing this directly from the SSH command line using the cat shell command:
+
+ cat >.htaccess <<NFSNRULES # This line is a shell command, not part of .htaccess!
+ RewriteEngine On
+ RewriteBase /
+ RewriteRule ^index\.php$ - [L]
+ RewriteCond %{REQUEST_FILENAME} !-f
+ RewriteCond %{REQUEST_FILENAME} !-d
+ RewriteRule . /index.php [L]
+ NFSNRULES
+
+If you prefer, you can copy-paste the text into an editor:
+
+ RewriteEngine On
+ RewriteBase /
+ RewriteRule ^index\.php$ - [L]
+ RewriteCond %{REQUEST_FILENAME} !-f
+ RewriteCond %{REQUEST_FILENAME} !-d
+ RewriteRule . /index.php [L]
+
+We need to run the following commands in order to install, uninstall, activate, and deactivate plugins/themes from within the admin web GUI of WordPress:
+
+ chgrp web .htaccess
+ chmod 664 .htaccess
+ chgrp -R web *
+ find . -type d -exec chmod 775 {} \;
+ find . -type f -exec chmod 664 {} \;
+ wp config set FS_METHOD direct
+
+## Enable File Uploading
+
+In your main WordPress installation folder enter the following SSH commands:
+
+ cd wp-content
+ mkdir -p uploads
+ chgrp -R web uploads
+ chmod -R 775 uploads
+
+## Keeping WordPress Up-To-Date Automatically via Command Line
+
+It is **critically** important to keep your WordPress installation up-to-date including, if applicable, your theme and plugins. PHP, WordPress, and WordPress themes and plugins all have a track record of security problems. Most of those get fixed quickly, but if you don't update, you don't get the fixes.
+
+To help you keep WordPress up-to-date, NFS provides a streamlined script.
+
+This command, which must be run from an interactive ssh session, will set up our system to check your WordPress install (and themes & plugins) for you every day, and update them automatically when needed:
+
+ wp-update.sh -a
+
+By default, it will tell you via email when updates happen. If you don't want that, just add `-q` (for "quiet") to the command. It will work silently unless there is an error.
+
+If you get errors from wp-cron (not wp-update) about failed automatic updates, you may also wish to add this to your wp-config.php file:
+
+ define( 'AUTOMATIC_UPDATER_DISABLED', true );
+
+This disables the insecure automatic updater bundled with recent versions of WordPress.
+
+**Do not attempt** to use WordPress's built-in ability to update itself through its admin panel. That insecure, outdated approach should not still exist. It does not work on NFS, which is a feature, not a bug. Getting it to work requires almost completely destroying your site's security, thereby ensuring that any undiscovered security flaw can enable hackers to completely obliterate your site. The update methods described here are faster, easier, and safer.
+
+## Logging In
+
+You should be able to navigate to `yourwebsite.com/wp-admin`, login and have everything work as expected. Congrats - enjoy your blog!
+
+Below you can find more advanced configuration to get the most out of your WordPress project.
+
+---
+
+## Backing up WordPress
+
+NFS recommends backing up your WordPress setup on a regular basis.
+
+This is a two-step process. You must back up both the files and the database.
+
+### Backing up WordPress Files
+
+To back up your WordPress files directly from a Unix-like system of your own, you can use a command like:
+
+ ssh yourmembername_siteshortname@ssh.phx.nearlyfreespeech.net tar -C /home/public -cvf - . | gzip >name-of-wordpress-backup.tar.gz
+
+To back up WordPress to a file on our system that you can transfer via SFTP to your own computer, you can use a command like:
+
+ tar -C /home/public -cvzf /home/tmp/name-of-wordpress-backup.tar.gz .
+
+This assumes that your WordPress install is in the default location (`/home/public`). It will put your backup file in your `/home/tmp` directory.
+
+Do not try to back up your WordPress folder into your WordPress folder, as that occasionally results in attempts to use infinite disk space by trying to back up the backup of the backup of the backup of the...
+
+### Backing up the WordPress Database
+
+If you have a Unix-like system of your own (e.g. macOS or Linux), you can do the backup directly from there using your local command prompt using the MySQL username and password you created for WordPress:
+
+ ssh yourmembername_siteshortname@ssh.phx.nearlyfreespeech.net wp db export - | gzip >wordpress-backup.sql.gz
+
+Or you can do it from the SSH command line via WP-CLI:
+
+ wp db export /home/tmp/wordpress-backup.sql
+
+The `wordpress-backup.sql` file this generates will be stored in your site's `/home/tmp` directory. Download it from there to have a local copy.
+
+## That's It!
+
+Your WordPress site should be up-and-running now. Further customization or extra plugins/services can be freely added if so desired. Enjoy your site!
diff --git a/posts/New_Domain_and_Code_Forge.md b/posts/New_Domain_and_Code_Forge.md
new file mode 100644
index 0000000..f02db18
--- /dev/null
+++ b/posts/New_Domain_and_Code_Forge.md
@@ -0,0 +1,62 @@
+# New Domain and Code Forge
+
+2024-01-29
+
+As you can clearly see, my site's domain has switched over to **btxx.org**. This post will go into details about the reason for this URL swap (spoilers: I'm a cheapskate) - but that isn't all. I have moved my personal git repositories over to my own hosting. I will explain the reasoning for that switch as well.
+
+But enough introductions, let's get into it!
+
+## bt.ht is No More
+
+I've abandoned `bt.ht`. Well, kind of. That domain doesn't expire until 2025, which works out nicely since I can keep a complete web forward active for the entire year. This will avoid such a radical switch, similar to what I did years ago with my "uglyduck" domain[^1]. Hopefully anyone who follows my dumb ramblings will have more than enough time to become familiar with the new URL.
+
+For reference, those interested in updating their RSS will need to use the latest URLs:
+
+- [RSS](https://btxx.org/index.rss)
+- [Atom](https://btxx.org/index.atom)
+
+You have roughly a year to do so, since new posts should still automatically appear even with defaulting to the older URL (hooray for 301 redirects!).
+
+There were two core reasons for switching domains. The first was based off a change in ownership with my previous domain registrar, Gandi. You can read more details [here](https://news.ycombinator.com/item?id=35080777), but they were essentially bought out, then decided to cancel their free email service and raised their prices. Not many registrars support the `.ht` TLD, so moving to another provider was already proving to be difficult. Once my mind was made up that I didn't want to support such shady actions from a company, I thought even more about the concept of spending *so much* on a domain name in the first place. It was a novelty to have such a "short" domain, but that seems silly in hindsight.
+
+Just take a look at the differences in domain costs and email services below:
+
+|URL|Domain/year|Email/year|Total|
+|---|-----------|----------|-----|
+|bt.ht|$172|$60|$232|
+|btxx.org|$17|$19|$36|
+
+By making this switch (for both my domain and email service) I would save a yearly total of **$196 USD**. This was a no-brainer. The minute I did the math I thought, "Hell, I'm already moving everything to a different registrar *and* I need to look for a separate email provider...why not just start fresh with a new, *cheaper* domain?"
+
+So that's what I've done.
+
+## My Own Code Forge
+
+As some people in the open source community might already be aware, [sourcehut](https://sourcehut.org) had a major outage a couple weeks back. It lasted a few days and all services were impacted. This meant that all publicly hosted websites, build processes and `git` repositories were unavailable. It was no fault of sourcehut of course, they were viciously attacked for no *real* reason.
+
+But this outage did get me thinking about what it means to "control" my own code. I always liked the idea of hosting my own git projects but relied on third-party services since they were more convenient. The problem with entrusting anything, not just code storage, to third-party services is lack of oversight. You really have no idea what is happening behind the scenes. You don't control your own backups. You don't have the freedom to tweak UI or user flow of your project views (which I understand is certainly an edge-case).
+
+These thoughts lead me to research some "self-hosted" code forge options. My main contenders were:
+
+- GitWeb
+- cgit
+- Gitea
+
+I'll save you the suspense: I went with cgit. Getting GitWeb configured properly was giving me a lot of issues and Gitea seemed overkill for my person needs. I host through NearlyFreeSpeech (running FreeBSD) and they had a decent tutorial for setting cgit up on their servers. I've updated my own wiki for those interested in doing something similar: [read the full step-by-step instructions for setting up cgit](/wiki/cgit).
+
+I still need to go through most of the existing projects on sourcehut and update their READMEs and purge the contents. The last thing I want to do is have users confused about which repo is the "real" one.
+
+Fore reference the my repos are now located here: [git.btxx.org](https://git.btxx.org)
+
+(I plan to place this in the main navigation soon...)
+
+## Mirror, Mirror on the Wall...
+
+I'm aware that to have extra protection from "downtime" that I should also mirror my code on separate forges. I plan to do this sometime in the future, but this isn't a major priority for me currently. When the time comes I'll be sure to update my repos referring to the mirrors (whatever platform that is I choose).
+
+## Room for Improvement
+
+My code forge is far from perfect. Mobile view is lacking, there is no dark mode support and things could be slightly more intuitive. But I love it. The beauty of hosting everything on my own is that I can improve these things myself. For now, I'm happy with the outcome!
+
+
+[^1]: Someone oddly picked up that domain and piggybacked off the back-links. They happen to also be a designer...Guess the naming was that cool?
diff --git a/posts/OpenBSD_is_a_Cozy_Operating_System.md b/posts/OpenBSD_is_a_Cozy_Operating_System.md
new file mode 100644
index 0000000..93d70ec
--- /dev/null
+++ b/posts/OpenBSD_is_a_Cozy_Operating_System.md
@@ -0,0 +1,32 @@
+# OpenBSD is a Cozy Operating System
+
+2024-04-11
+
+<figure>
+<img src="https://btxx.org/posts/OpenBSD_is_a_Cozy_Operating_System/open-suck-75.png" alt="Screenshot of OpenBSD 7.5 running dwm">
+<figcaption>OpenBSD 7.5 running dwm on my X220</figcaption>
+</figure>
+
+With the recent release of OpenBSD 7.5, I decided to run through my [personal OpenBSD "installer"](https://git.btxx.org/open-suck/about/) for laptop/desktop devices. The project is built off of the `dwm` tiling window manager and only installs a few basic packages. The last time I updated it was with the release of 7.3, so it's been due for an minor rework.
+
+While making these minor changes, I remembered how incredibly easy the entire install process for OpenBSD is and how *cozy* the entire operating system feels. All the core systems just work out the box. Yes, you need to "patch" in WiFi with a firmware update, so you'll need an Ethernet connection during the initial setup. Yes, the default desktop environment is not intuitive or ideal for newcomers.
+
+But the positives heavily outweigh the negatives (in my opinion):
+
+- Incredibly secure operating system (No `xz` drama here...)
+- Detailed documentation. Probably one of the best of any OS
+- Much smaller codebase and small core team
+- *Complete* operating system (kernel, userland utilities, libraries, applications combined)
+
+These points might not seem important to others, but they are to me. Maybe you're possibly interested in checking it out yourself? If you are, then read on...
+
+## Try it Yourself!
+
+I've tried my best to write up a simplified, noob-friendly guide on both setting up the core OpenBSD system, along with installing a simple `dwm` based desktop environment. These are both focused on personal devices (laptops/computers), so if you're looking for server-specific setups you won't find that here!
+
+You can find both of those wiki-pages below:
+
+- [Installing OpenBSD](https://btxx.org/wiki/openbsd/installation/)
+- [Setting up a Desktop Environment for OpenBSD](https://btxx.org/wiki/openbsd/desktop_environment/)
+
+Some additional reading I highly recommend is: [c0ffee.net/blog/openbsd-on-a-laptop](https://www.c0ffee.net/blog/openbsd-on-a-laptop)
diff --git a/posts/Please_Make_Your_Table_Headings_Sticky.md b/posts/Please_Make_Your_Table_Headings_Sticky.md
new file mode 100644
index 0000000..b64928d
--- /dev/null
+++ b/posts/Please_Make_Your_Table_Headings_Sticky.md
@@ -0,0 +1,26 @@
+# Please Make Your Table Headings Sticky
+
+2024-02-23
+
+I often stumble upon large data sets or table layouts across the web. When these tables contain hundreds of rows of content, things become problematic once you start to scroll...
+
+[Link to video example of standard table header](/public/videos/not-fixed-header-tables.mp4)
+
+Look at that table header disappear! Now, if I scroll all the way down to item #300 (for example) will I remember what each column's data is associated with? If this is my first time looking at this table - probably not. Luckily we can fix this (no pun intended!) with a tiny amount of CSS.
+
+## Sticky Header
+
+Check it out:
+
+[Link to video example of fixed table header](/public/videos/fixed-header-tables.mp4)
+
+Pretty awesome, right? It might look like magic but it's actually very easy to implement. You only need to add 2 CSS properties on your `thead`:
+
+ position: sticky;
+ top: 0;
+
+That's it! Best of all, `sticky` has [~96% global support](https://caniuse.com/?search=sticky) which means this isn't some "bleeding-edge" property and can safely support a ton of browsers. Not to mention the improved experience for your end-users!
+
+You can view a live demo of this table on the [CodePen example pen](https://codepen.io/bradleytaunt/pen/bGZyJBj).
+
+If you found this interesting, feel free to check out my other table-focused post: [Making Tables Responsive With Minimal CSS](https://btxx.org/posts/tables/)
diff --git a/posts/Please_Make_Your_Table_Headings_Sticky.md~ b/posts/Please_Make_Your_Table_Headings_Sticky.md~
new file mode 100644
index 0000000..1e7a602
--- /dev/null
+++ b/posts/Please_Make_Your_Table_Headings_Sticky.md~
@@ -0,0 +1,29 @@
+# Please Make Your Table Headings Sticky
+
+2024-02-23
+
+I often stumble upon large data sets or table layouts across the web. When these tables contain hundreds of rows of content, things become problematic once you start to scroll...
+
+[Link to video example](/public/videos/not-fixed-header-tables.mp4)
+
+Look at that table header disappear! Now, if I scroll all the way down to item #300 (for example) will I remember what each column's data is associated with? If this is my first time looking at this table - probably not. Luckily we can fix this (no pun intended!) with a tiny amount of CSS.
+
+## Sticky Header
+
+Check it out:
+
+ <video width="100%" controls>
+ <source src="/public/videos/fixed-header-tables.mp4" type="video/mp4">
+Your browser does not support the video tag.
+</video>
+
+Pretty awesome, right? It might look like magic but it's actually very easy to implement. You only need to add 2 CSS properties on your `thead`:
+
+ position: sticky;
+ top: 0;
+
+That's it! Best of all, `sticky` has [~96% global support](https://caniuse.com/?search=sticky) which means this isn't some "bleeding-edge" property and can safely support a ton of browsers. Not to mention the improved experience for your end-users!
+
+You can view a live demo of this table on the [CodePen example pen](https://codepen.io/bradleytaunt/pen/bGZyJBj).
+
+If you found this interesting, feel free to check out my other table-focused post: [Making Tables Responsive With Minimal CSS](https://btxx.org/posts/tables/)
diff --git a/posts/Website_Backups_with_Apple_iCloud.md b/posts/Website_Backups_with_Apple_iCloud.md
new file mode 100644
index 0000000..e045bfd
--- /dev/null
+++ b/posts/Website_Backups_with_Apple_iCloud.md
@@ -0,0 +1,24 @@
+# Website Backups with Apple iCloud
+
+2024-02-16
+
+My main work machine, an M2 MacBook Air, meshes really well with my iPhone SE (they are in the same ecosystem after all - duh!). Since both of these devices are Apple products, it makes sense that I pay for the optional iCloud service for extra storage. 50GB to be exact. I only need to bare minimum which costs just $1.68 a month, making this storage option cheaper than most cups of coffee these days.
+
+Recently I've been using iCloud as my "middle-man" backup system. I still have local, offline storage for most personal data but having additional off-site backups is never a bad thing. I make things easier for myself by taking advantage of `rsync`. You'll need to make sure you have that program installed before trying this yourself:
+
+ # This assumes you have homebrew installed first
+ brew install rsync
+
+Then, whenever I feel like backing up an existing project or website I simply run:
+
+ rsync -a user_name@ssh.webserver.domain:/home/var/www/ /Users/username/Library/Mobile\ Documents/com\~apple\~CloudDocs/Backups/site-backup
+
+> Note: The `-a` option tells `rsync` to sync directories recursively, transfer special and block devices, preserve symbolic links, modification times, groups, ownership, and permissions.
+
+The beautiful magic of `rsync`! Obviously, you'd want to properly name your directories (ie. `/Backups/site-backup`) for a cleaner structure and ensure that your iCloud directory is set correctly. (remember to read code before just copy-pasting!). With this approach you can backup entire server directories or be specific with each individual project folder. I would also recommend setting up some alias in your `.bashrc` or `.zshrc` etc. to make things more streamlined when running backups manually:
+
+ alias site-backup="rsync -a user_name@ssh.webserver.domain:/home/var/www/ /Users/username/Library/Mobile\ Documents/com\~apple\~CloudDocs/Backups/site-backup"
+ # Then you simply run the following for a manual backup:
+ site-backup
+
+You can take this further by automating things via cron jobs, but for my use case that is a little overkill. Hopefully this helps anyone looking for a quick and dirty backup system, especially one that can piggyback of your existing iCloud that you might be paying for already.
diff --git a/posts/x220.md b/posts/x220.md
index 77c77c1..ef70da7 100755
--- a/posts/x220.md
+++ b/posts/x220.md
@@ -32,10 +32,7 @@ Overall, this laptop is a device you can snatch up off your desk, whip into your
## Ports
-<figure>
- <img src="/public/images/ports-everywhere.jpg" alt="Buzz Lightyear and Woody meme: 'Ports, Ports Everywhere'">
- <figcaption>Ports. Ports Everywhere.</figcaption>
-</figure>
+![Buzz Lightyear and Woody meme: 'Ports, Ports Everywhere'](/public/images/ports-everywhere.jpg)
I don't think I need to explain how valuable it is to have functional ports on a laptop. Needing to carrying around a bunch of dongles for ports that should already be *on the device* just seems silly.
@@ -74,10 +71,9 @@ The ability to completely disassemble and replace almost everything on the X220
Best of all, Lenovo provides a very detailed [hardware maintenance manual](https://download.lenovo.com/pccbbs/mobiles_pdf/0a60739_01.pdf) to help guide you through the entire process.
-<figure>
- <img src="/public/images/x220-pieces.jpeg" alt="My disassembled X220 laptop">
- <figcaption>My disassembled X220 when I was reapplying the CPU thermal paste.</figcaption>
-</figure>
+My disassembled X220 when I was reapplying the CPU thermal paste:
+
+![My disassembled X220 laptop](/public/images/x220-pieces.jpeg)
## Bonus Round: Price