Server Maintenance

Tonight, I decided it was time to give my web server some lovin’. I’ve been happily using Linode for 1.7 years (613 days to be exact) and have enjoyed 100% uptime in that time. Holy crap.

613 days uptimeNot long ago, Linode announced that they were giving everybody 8 cores. Wow, that’s awesome!.. but unfortunately means I’d have to restart the web server to take advantage of the awesomeness. Bye-bye uptime. I’ve been thinking about this for a couple weeks. At first, I didn’t think I’d restart the server until I absolutely had to, but there are some really cool things I want to do like install node.js (and passenger + gitlab).

Anyway, today I decided it was time to not only upgrade the web server’s hardware, but also the software. Might as well do it all at the same time, right?

Backing Up the Server

Obviously before attempting an upgrade such as this, I wanted to be sure I had a solid backup.

I’m slightly embarrassed to say this… actually, really embarrassed… but I don’t take regular backups of anything on my web server.

I host a couple of freebies and I take backups of those every couple of months, but that’s not nearly enough! I’m going to be adding Linode’s backup service for $5/mo. As they say when describing the backup features, I can “set it and forget it” — and I plan to.

I decided the easiest thing to do would be to take a dump of my databases and an archive of my www directory.

Backing up Databases

cd /tmp
mysqldump -uroot -p --all-databases > dump.sql

Easy.

Backing up Files

tar zcf www.tar.gz  /var/www/

This is where I ran into issues. This ran for about 10 minutes and during so, I thought, “I didn’t realize I had that much content in my web folder!” When it finally finished, the tarball was 4.95gb… WTF?!

Remember how I said I don’t take regular backups of anything on my web server? I was obviously wrong because my blog’s backup plugin was running a daily, and there were 250 backups. Oops.

I used the web interface to delete the backups so that it would delete the associated database records as well. I listed the backups, clicked the “Check all” checkbox, hit delete, aaaaannnnd…. Boom, HTTP 414 error. Request URI too Long… Huh? I figured I must have too many backups displaying per page, so I lowered it from 30 to 10. Same thing. Lowered it to five. Same thing. Then I noticed that my scrollbar was much longer than it should’ve been for 10 backups.

Turns out my backup plugin’s pagination was broken and all 250 backups were being listed, each time. I didn’t have the time or want to debug someone else’s “award-winning” plugin so I wrote some jQuery that when entered in the console, would select some of the checkboxes and click the delete button. Close enough to automated.

jQuery('input[name*=backupfiles]').slice(0, 25).each(function() {jQuery(this).prop('checked', true)});jQuery('select[name=action]').find('option[value=delete]').prop('selected', true);jQuery('#doaction').trigger('click');

After I got those all deleted, I took another backup and it was 1.32gb. Much more aligned to my expectations.

Taking the Server Offline

I really hesitated for a good minute before typing “reboot.” 100% uptime is insane… but so is having 8 cores without having to pay any extra…

Rebooting Linode Server

It took about 45 seconds for everything to come back online. Not bad. Bye-bye awesome uptime, but heeelllloooooooooo insane computing power!

Updating the Software

I wasn’t nearly as worried about this part of the process. I love APT.

apt-get update
apt-get upgrade

Notable upgrades include various networking utilities, MySQL, nginx, perl, PHP (and 10+ dependencies) and Redis. Updated without a hitch. (Although I did need to update some config files.)

What to Do Now!

Now that the upgrade is completed, I plan on tearing through the web server and essentially starting the config from scratch. I want to get a node.js + nginx setup going, install Passenger because I’m becoming increasingly interested in Python and Ruby and switch MySQL to MariaDB. Probably some other things too, but not at this point.

WordPress Drop-down Menus with a Custom Walker

One feature of WordPress that I’ve never quite been happy with is the wp_nav_menu function. It has a lot of customizable options but one thing I think could be improved is its support for multiple levels (drop-downs.)

While working on a website for a client, we needed to add an arrow next to top-level nav that had children. We needed to:

  1. add a “dropdown” class to the top-level nav, and
  2. add <b class=”caret”></b> inside the link (yes, that’s bootstrap-inspired)

wp_nav_menu doesn’t support either. After some brainstorming, I had three possible ideas:

  1. Buffer the wp_nav_menu output (aka set the “echo” flag to false) and use regex to parse the HTML and add what we need
  2. Make use of a PHP class like DOMDocument to traverse the HTML and add what we need
  3. Write a custom walker and add what we need before we ever call wp_nav_menu

I’ve been looking for an excuse to get into further WordPress development so I decided to use a custom walker.

Modifying the Walker

I couldn’t find muchuseful information pertaining to my need so I dug into the WordPress core and found two files in /wp-includes, class-wp-walker.php and nav-menu-template.php.

Through some trial and error I found that the display_element function in class-wp-walker.php would allow me to append the class I wanted. Here’s what that code looked like (placed in my theme’s functions.php):

class Dropdown_Walker_Nav_Menu extends Walker_Nav_Menu {
	function display_element($element, &$children_elements, $max_depth, $depth=0, $args, &$output) {
		$id_field = $this->db_fields['id'];
		if(!empty($children_elements[$element->$id_field])) {
			$element->classes[] = 'dropdown';
		}
		Walker_Nav_Menu::display_element($element, $children_elements, $max_depth, $depth, $args, $output);

	}
}

That solved the first part of my problem, but the HTML modification issue had yet to be tackled. While experimenting, I came across the start_el function that looked like I could append some HTML. I copied the entire start_el function to functions.php and added the following just before the closing </a> tag:

if(in_array('dropdown', $classes)) $item_output .= ' <b class="caret"></b>';

It’s possible to search for the “dropdown” class because display_element is called before start_el.

Anyway, this worked like a charm but was uber bulky – I love simplicity. I finally had my “D’OH” moment when I realized that it’s possible to append my HTML directly in the display_element function. I was looking at start_el and noticed it calling $item->title. $item is the same as $element in display_functions, so I appended

$element->title .= ' <b class="caret"></b>';

to my custom display_element function and BINGO!

Final Code

Here’s the final code (placed in the theme’s functions.php file):

class Dropdown_Walker_Nav_Menu extends Walker_Nav_Menu {
	function display_element($element, &$children_elements, $max_depth, $depth=0, $args, &$output) {
		$id_field = $this->db_fields['id'];
		if(!empty($children_elements[$element->$id_field])) {
			$element->classes[] = 'dropdown';

			$element->title .= ' <b class="caret"></b>';
		}
		Walker_Nav_Menu::display_element($element, $children_elements, $max_depth, $depth, $args, $output);

	}
}

My tasks for the weekend

This is my plan for the coming weekend. In hindsight, I should have done all this over Labor Day weekend, but what do you want me to say – I was busy watching Netflix and eating oreos. This is likely going to be an uber-rough post, so my apologies.

Format / Update

  • Backup computer
    • SQL databases should be backed up separately for easy retrieval…
  • Format
  • Install Lion, upgrade to Mountain Lion (I wish there was a ML thumb drive… Maybe I’ll format, upgrade to Lion, purchase Mountain Lion, roll my own and re-format to keep things super fresh/clean)

Restoration / Rebuilding

  • Restore the bare minimum:
    • Dev Frameworks
    • Standard Apps (ST2, CS6, iTunes, etc.)
  • Install new AMP stack if OS X-default isn’t sufficient
    • I use an NMP stack currently… I’d love to keep using it but all of our client sites run on Apache… Perhaps Apahce on 80 and nginx on 8080.
    • Reminder: Setup music proxy
  • Reinstall Parallels and recreate my VMs (Debian, Ubuntu, Trixbox, Windows via Bootcamp)

Setup Version Control

I’m still thinking about how I want to do this, but what I’ve got so far, and very likely to change…
  1. Most likely git
  2. If possible, mirrored in two locations:
    1. External HDD
    2. My webserver
  3. Again, if possible, it should keep the last 3-5 revisions locally in case I need to revert without access to remote repo.
    1. Somehow this should all sync and not delete older local revisions until synced with the remote repo.
  4. I also want to write a script that will package a site / project when I’m finished… Ideally, it would:
    1. Export all linked databases (I’m fine with providing credentials and db names)
    2. Grab the latest revision
    3. Create a text file with:
      1. the path of the git repo
      2. the command I ran to generate
      3. the date, time, and location (work/home… manual entry)
    4. Tar it all and open a finder window with the file highlighted
I’m not sure if all that is possible because I don’t know nearly enough about git, or applescript / shell scripts, so this will be a good learning experience.

Final Thoughts…

Wish me luck.

Link Shorteners and a Call to Action

Some of you may know that I have a domain I use for link shortening: buze.co. If you hit up the domain root, it redirects you to my blog. But if you go to a shortened URL, such as buze.co/pe4es, it will redirect you to the proper page. I set this up using YOURLS because it seemed like the easiest way to get the domain up and running.

What you probably don’t know is that I have another domain that’s run off the same database. In any normal scenario, you’d probably want to split your databases on a per-service basis, i.e. separate personal from work, which is what this other project is. The reason I serve the same content is because I don’t shorten enough links to require two separate databases. And nobody can see the database anyway, so it doesn’t matter.

To achieve this, I symbolically link the work domain to buze.co. Coincidentally, /www is also a symbolic link.

ln -s /www/work.domain /www/buze.co

My vhost paths are strange, but they get the job done. The actual path for this domain is /var/www/buzelac.com/public_html/ but that’s a different blog post.

/www/buze.co/index.php looks like this:

<?php
$host = $_SERVER['HTTP_HOST'];

if($host == 'work.domain') {
    header('Location: http://work.domain');
} elseif($host == 'buze.co') {
    header('Location: http://buzelac.com');
} else {
    echo 'Nothing to see here.'; // this is a fallback just in case
}
?>

And then nginx handles rewrites for any “directory.” If the path isn’t a directory (!-d) and it isn’t a file (!-f), then it rewrites to the YOURLS script. Note: this can be handled with one step using !-e.

YOURLS isn’t bad. In fact, it’s actually pretty cool. It records how many clicks links get, has a stats page with graphs for hits and traffic location, and there’s even a nifty plugin to make it produce links similar in format to other services, such as “pe4es” instead of “23,” the default format.

…What’s the problem?

While YOURLS is a pretty decent link shortener, there’s something about it not being mine that I don’t really know how to explain. Essentially, I think there are some improvements that could be made (both programmatically and aesthetically), so I might as well just write my own.

YOURLS follows what I would call a “Web 2.0″ approach, a term which I think should have been shot before it was even incepted. Clients always say, “This design isn’t ‘Web 2.0′ enough.” or “Yeah, this is good, but can you make it more… ‘Web 2.0′?” These clients don’t even understand what “Web 2.0″ is, they’re just trying to sound savvy and keep up with the latest “trends.” If crappy gradients / color schemes, extremely rounded corners, and ill-fitting typography is what it takes to fit in, then I want to be as far away from conformity as possible.

I digress. The YOURLS code is messy in my opinion. Understandably so, as I don’t think the project was originally intended to be as big as it is. But still another reason that it will be easier for me to start from scratch. I prefer an object-oriented approach. Plus, this way, I’ll be able to easily integrate the new system with my new CMS I’m working on… but that’s another blog post.

The “Call to Action”?

The world doesn’t have enough URL shorteners. So I’m going to create another. Most likely a PHP / Redis approach, because I’m looking for my first project using Redis. But it could easily end up a PHP / SQL project as well. It really depends on how my experiments with Redis go. (I’m not extremely fond of the idea of everything being in memory; I’m really just looking for a NoSQL approach.) It will have to have an import feature, though, so I can copy all of my YOURLS links into the new system. And some sort of API for plugin creation. I’d also like some sort of module-esque system (plugins) to enable features like public URL submits, a user system, etc.

Closing Irony

Some of you reading this clicked the short link, which is currently produced with YOURLS.

Setting up a chrooted SVN environment

Today, I set up SVN on my server for a friend’s school project. While it’s not the first time I’ve set up SVN, it is the first time I’ve needed to restrict the users to a specific directory. Keeping in mind that I refuse to use Apache on this particular server, there would be no dav_svn.

Installing SVN

  1. Install SVN
  2. Create Repo
  3. ???
  4. Profit
apt-get install subversion
svnadmin create /path/to/svn/repo

…We’re done, right? Well, no. Not close. Remember, you wont find Apache on this server, so somehow, I needed to auth users. Now, I’m sure there’s a way to use SVN over FTP, but again, you’re lucky if you find an FTP server on this box… Cleartext passwords? No thanks.

Locking down an SSH User

I’ve never had the need to chroot a user before. Especially not a shell user. A quick google search brought me to a neat little script called make_chroot_jail. I installed the script in /usr/local/sbin/. Every time I tried to run the script, it would throw errors and never accomplished anything. I googled it and learned that the first line of the script,

#!/bin/sh

sometimes has to be changed to:

#!/bin/bash

depending on the distro used. Once I did this, the errors stopped flowing. But I still wasn’t done. I originally thought I needed to use the full syntax:

make_chroot_jail.sh user /path/to/shell /path/to/home

but the script was including my absolute paths in a variable with paths already set. So /path/to/home became /home/jail/path/to/home. Additionally, it didn’t work properly, as I could still manage everything via ssh with that user. After some trial and error (and reviewing the script source again) I decided that simple is better. The new syntax,

make_chroot_jail.sh user

worked perfectly. It created a home directory based on the user inside of the jail. It also restricted the available applications to a select few standard apps. Unfortunately, this didn’t include the svn applications. This was easy enough to solve by adding the required apps to the APPS var, similar to:

APPS="/bin/bash /bin/cp [...] /usr/bin/svn /usr/bin/svnadmin"

I’m assuming that if you’re reading this and planning on doing what I did, you’ll notice that you need to change the line specific to your distro. If you don’t know what your distro is (or what “distro” means, for that matter), find someone familiar with Linux to do this for you.

I digress; At that point, I tried to connect to the SSH server but couldn’t. I learned that the supported jailed apps are actually copied into the jail. Nothing is updated realtime. I re-ran

make_chroot_jail.sh user

and it updated the jail, adding my svn apps. I still couldn’t connect to the repo, though. I wasted a lot of time on this part. SourceTree offered that it was, in fact, an SVN repo, and asked if I wanted to clone it to git. svnX claimed that it didn’t exist.

Beneficial vs. Detrimental

I like open source. I like free. Free + open source together are like ice cream to me. So I found svnX, a free, open-source SVN client for OS X. But if there’s one word I can use to sum up svnX: crap. Maybe it’s my lack of tolerance (bias) for applications that half-ass GUIs for CLI apps. Or maybe it’s the fact that svnX simply wouldn’t accept that I was using an authenticated user via ssh. I mean, in terms of GUIs, I’ve seen great… and I’ve seen worse than horrible. I could have gotten over the bad UI if the app had done what I wanted, but the fact that this app wouldn’t let me svn+ssh was a complete deal breaker.

What’s next? Versions. I’ve used it before. It’s got a great GUI. It makes sense. But since I primarily use GIT, forking out $60 on the app isn’t going to happen. Luckily, there’s a 30-day trial. Anyway, I downloaded it, plugged in my svn+ssh:// URL, and gave it a password. Boom. Instantly connected. And it worked. *Flawlessly.

Verdict
svnX: Detrimental to my workflow
Versions:  Beneficial to my workflow

*Why wouldn’t there be a “But…”?

I said that Versions worked flawlessly. The app itself did. It even gave me a verbose and understandable error when I tried to commit rev 1 as a test. (Yep. There’s no way I’d be done with this project that easily.) The error was “attempt to write on a readonly database.” This one was actually easily enough to solve. I granted write perms to /path/to/repo/db/rep-cache.db and then it worked. No more hiccups on my end.

One more thing?

In the end, my friend, using Tortoise, told me that he has to enter the pass every time he performs an action. It looks like there’s no password caching in Tortoise. I generated a key, but we haven’t implemented it yet. I’m pretty sure that it will get fairly annoying during dev to have to stop to enter the pass all the time, and that’s where PuTTY / pageant will come in.

Closing Thoughts

All said and done, I definitely prefer GIT.

The Search for an Efficient Form Validator

I’ve been working on a contact form for A6X. My plan was to do inline validation as well as serverside validation. Ideally, everything gets validated on the client end and the server just does a double check, instead of having to output an error message.

My conditions were:

  • the javascript validator needed to be HTML5-compatible
    • recognize html5 form elements, with graceful fallbacks
    • replaces built in html5 validation tooltips (i’m actually surprised at how bad these look in chrome…)
  • validate only after text is written, NOT specifically on blur/focus/click/mouseover(i’ve seen this before. why?!)/whatever
    • smart enough to not validate placeholder text
  • allows the option to either validate on submit or onblur (see above point)
  • ideally uses html5 validation methods whenever possible, with a fallback to inline js regex
  • submit button is disabled with javascript and enabled only when everything validates

Of course, I haven’t been able to find a single plugin which meets all of these conditions. Only one even comes close and I still have a lot of issues with it. For instance, I had to rewrite the phone regex because it was not flexible. The e-mail regex doesn’t even allow uppercase letters. Um, HELLO?!?! Lots of people add uppercase letters to their e-mail addresses to help distinguish specific words.

  • thisisanemail@example.com
  • thisIsAnEmail@example.com

Tell me, which is easier to read? Exactly.

And I really don’t understand why most validation scripts validate directly on blur. Or on focus. Validation needs to work like this:

  1. Disable the form submit button on page load
  2. On input blur (input loses focus), check whether the input is a) blank or b) the placeholder text.
    1. If it’s the default text, do nothing.
    2. If it’s not the default text, validate it.
  3. If an input is determined to be invalid, it gets an invalid CSS class.
    1. You should be able to define how far up the DOM the class is added. I.e. parent or up the DOM until it finds a specific class/element/etc
    2. Any error message is enabled with this class as well.
      1. for example: .invalid .error-message {display: block;}
  4. If an input is determined to be valid, it either receives a valid class or does nothing
  5. Upon determining that everything is valid, the submit button is re-enabled.

No javascript? No problem. Since the submit button is disabled with javascript, any no-js browser will still be able to submit the form. And that’s where the serverside validation comes in.

Check out how the serverside validation would occur:

  1. With JS, you would either add/remove a hidden form element. In this case, we’ll add a hidden element called “uses-js”.
  2. When the form is submitted, serverside detection is used to see if a hidden element called “uses-js” is posted.
  3. If it is, the server assumes that the form is valid.
  4. If it isn’t, the server will validate.
  5. Once validated, the form gets its further processing (stored in database, e-mailed, whatever)

Obviously, this isn’t perfect. Any bot targeting the system directly could inject that input. And the form could be submitted using javascript from the console.

However, as long as you have some sort of anti-bot code and sanitize your data, you can just relax and move on. Note that I’m also a huge advocate for using alternative methods to CAPTCHA, but that’s for a different post.

I did just come across html5ifv which seems to be pretty extendable, but it still requires too much on my part to make it function the way I want.

It’s possible that what I’m asking for is very specific. But regardless, I think that’s essentially how form validation should be designed. I don’t think it’s difficult to achieve. I’ll probably end up creating something and posting it on github.

The (almost) Perfect Phone Regex

While working on an inline form validation feature for A6X, I needed to be able to validate phone numbers. The phone regex with the plugin I was using was crappy and didn’t allow you to enter things such as an extension, or use (###) for the area code. Every validation plugin I used had, in my opinion, bad regex, which only took into account a small fraction of ways to type a phone number.

I wanted to be able to do things like:

  • Use (###) format for the area code
  • Add a +1 for a country code
  • Use . instead of -
  • Add an extension, etc

Yet for some reason, literally no regex that I found would allow for this in its entirety. So, obviously, I handcrafted my own.

This allows you to enter phone numbers in many different formats:

  • +1 is entirely optional, but still allowable
  • You can enter the area code with or without surrounding parentheses (###)
  • Numbers can be spaced with spaces, a . or a – (or nothing) – such as 123-4567, 123.4567, 123 4567
  • You can optionally enter an extension
    • Allowable formats: “ex”, “ext”, “extension” followed by a space, a . or a – (or nothing) and then some numbers

The only reason I say it’s “almost” perfect is because while loosely designed around US numbers, it may/may not validate for other countries. Any country with seven digit numbers should theoretically validate. And of course, there’s probably the case where someone wants to enter a weird character like a tilde (~) or something, which isn’t supported.

Regardless, though, I think this is the most effective regex for a phone number I’ve seen on the internet.

/^\+?\d{0,}?(\s|-|\.){0,}?\(?\d{3}\)?(\s|-|\.){0,}?\d{3}(\s|-|\.){0,}?\d{4}(\s|-|\.){0,}?(ex?t?|extension)?(\s|-|\.){0,}?\d{0,}?$/

Spaghetti and Homemade Meatballs

The other day, I remembered that I’ve had some ground chuck sitting in my freezer for probably at least a month. I wanted to make a fairly fast, delicious meal. At first I thought about burgers, but I didn’t have any buns (or real bread, for that matter). I have about six pounds of spaghetti, bought from Sam’s Club, so I settled on Spaghetti and meatballs.

Protip: If meat is properly packaged, it can be frozen anywhere from six months to one year and still be considered edible. Just be careful of odd tastes from freezer burn after a certain point.

I have to warn you that the pictures below are likely going to be crappy – they were taken from my iPhone 3GS and I apparently don’t have very steady hands.

The original recipe for the meatballs comes from allrecipes.com and has been modified slightly to fit my needs.

The Meatballs

Start the meatballs first because while they are in the oven, you can cook the spaghetti and then everything comes out at the same time.

Ingredients

20 ounces (1.25 lbs) ground beef / chuck / turkey*
1 egg, beaten
1/3 cup italian bread crumbs
Olive oil or PAM

Optional:
Salt / Pepper
Italian Seasoning
Oregano
Basil

* You can use your choice of meat. Turkey tastes great and tends to have very little fat. Using 99% fat-free meat will not work well with this recipe.

Preparation

  1. Preheat oven to around 350 degrees
  2. Wash your hands
  3. In a bowl, hand mix the meat, bread crumbs, beaten egg and any optional ingredients you decided to use.
    1. If you don’t have bread crumbs, you can substitute it for thoroughly crushed croutons. Remember, it should look like crumbs.
    2. Protip: Clip your nails before shoving your hands in the meaty goodness
  4. Get out a pan and either:
    1. Spray it with PAM, or
    2. Pour some olive oil on it and ensure that it’s fully covered
  5. Once everything seems evenly mixed, roll into balls and place onto the pan. You can use an ice cream scooper or large spoon if you want.
  6. Bake the meatballs for 15 minutes.
  7. Carefully (don’t burn yourself) flip the meatballs over and bake for another five minutes.
  8. Turn off oven and remove meatballs.
  9. Transfer the meatballs to a different container if you want

The Spaghetti

Remember to cook the spaghetti while the meatballs are in the oven. If you time it right, they will be done at the same time, and everything will be nice and hot!

Ingredients

Pasta (I used angel hair)

Preparation

  1. Fill a pot with water and bring to a boil.
    1. Protip: Leave at least 1 1/2 – 2″ of space at the top so that your boiling water wont overflow when the pasta is inserted. Always account for added volume.
  2. Once the water is boiling (and don’t cheat – it should be fully boiling), add the pasta.
  3. Stir occasionally to keep the pasta from clumping or attaching to the pot.
  4. The pasta packaging will tell you how long to cook the pasta for, but you can usually get by with 8-10 minutes. If you’re unsure, just do a taste test with a noodle to see.
  5. Once the pasta is ready, shut off the burner and drain the pasta. Be careful of the scalding water.

The Sauce

Hey – I’m in college, I’m not some master Italian chef. If you want to roll your own sauce, feel free, but I just turned towards a jar.

Ingredients

Pasta Sauce

Preparation

  1. Pour some sauce into a pot and heat it on low-medium until it’s bubbling a little.
  2. Make sure to stir it occasionally

You can even add your completed meatballs to the sauce for a couple minutes! Yum!

The Final Product

Hi there.

For a while now, I’ve been wanting to set up a website for myself. Every time I create an account somewhere, there’s an input where I can enter a link to my website, and every time, I leave this field blank. Why? Because I’ve never taken the time to set up a website for myself. This is going to change starting now.

If you click on the picture (or if you have good eyes), you can see that I’ve had this domain since July 3rd, 2007. Over four years, and I still have never gotten this site up and running. I’ve done several concepts, and had even coded a few custom blog/CMS systems for personal use, but never did anything with them. Perhaps I’ll look for all of my old site mockups and post them at some point.

For now, though, I’ll just inform you about the types of posts I’ll likely create on this website. I’ll post about my endeavors with my web server, which includes posts on installing debian, nginx, mysql, php and others, as well as endeavors with web design and development.  As a college student, I try to find new ways to make food less, well, boring. I’m tired of eating Ramen at least once a day, and I like to try to concoct recipes – I’ll probably post those. I also like to post about the things that happen to me – be it related to school, something personal, etc.

I haven’t created my custom template for wordpress yet, but since I’m starting to post, I’ll likely be motivated to create one. I hope.

That’s about it for now, I think. Thanks for reading!