Adventures in PHP

I wanted to make a minor update to this site today and it turned out to be a bit of an ordeal. My objective was to add code syntax highlighting using highlight.js. Easy, right? Almost. Actually adding highlight.js was extremely easy.

But verifying that it was working was an ordeal. My dev environment is my Mac mini and I've upgraded to macOS 12.1 (Monterey) since the last time I worked on this site. Apple dropped support for PHP with macOS 12.1. But they didn't just drop support for PHP, they made it a pain to install because Apache modules need to be code-signed. The PHP modules installed via Homebrew are not code-signed, sadly. After I figured out that this was the issue (which took way too long), I went through the many-step process to set up a Certificate Authority, trust it, issue a code-signing certificate, and use that cert to sign libphp.so. Done, right?

Not so fast. During the "what the heck is broken?!" phase I uninstalled and reinstalled PHP, upgrading to PHP 8.1 and wiping out my config files in the process. Step one: downgrading to PHP 7.4 and redoing the signing. With my PHP configuration, however, it turns out that I have been running a non-standard config...

short_open_tag = On
error_reporting = E_ALL & ~E_NOTICE

I knew that short-open tags were non-standard, so that didn't surprise me. But I didn't realize that I was suppressing certain error messages, particularly those related to accessing undefined variables. Oops! Apparently accessing undefined variables is a bad idea. (I mean, yeah I know it is a bad idea. But I didn't realize that PHP would pump out errors for it unless you suppress them.)

On top of that, my hostname changed (which broke a bunch of things) and it took me a few minutes to remember how to start Docker so I could run local DynamoDB.

Add it all up and this minor change ate a large chunk of my evening. But look at that pretty syntax highlighting!

Jan 2nd, 2022

Running an ASP.NET Core site on an EC2 Instance

I've been working on the Porter Photos project for a bit now, running it locally on my Mac mini. The only wrinkle with getting that working was that I installed the arm64 version of the .NET SDK alongside the x64 version and that was causing problems. I could run the site from the command line just fine but when Visual Studio Code tried to run it, it just didn't. No helpful errors at all. I eventually edited /etc/dotnet/install_location to point to the x64 install and that solved it.

My next challenge was getting the site running in a test environment, on an EC2 instance. First I created a build artifact to deploy to my test instance. This was easily accomplished.

dotnet publish --output BuildArtifacts --sc --os linux
cd BuildArtifacts
tar -acf PorterPhotos.tgz --exclude="PorterPhotos.tgz" *
aws s3 cp PorterPhotos.tgz {s3-URI-to-deploy-bucket-and-object-name} --no-progress

I wanted to publish as a single file, so I also added the following to my .csproj file.

<PropertyGroup>
  <PublishSingleFile>true</PublishSingleFile>
</PropertyGroup>

Next, I created an EC2 instance through the AWS Console and sshed to it. I installed the .NET runtime, downloaded my build artifact, configured Apache and a service to start Kestrel. I validated that this all worked and then wrote a shell script to do those steps.

ec2-cloud-init.sh:

#!/bin/bash
export PORTERPHOTOS_DEPLOY_S3_URI={s3-URI-to-deploy-bucket-and-object-name}
yum -y install httpd2.4 mod_ssl mod_rewrite mod_headers
wget https://dot.net/v1/dotnet-install.sh
chmod u+x ./dotnet-install.sh
./dotnet-install.sh -c 6.0 --runtime aspnetcore
ln -s /root/.dotnet/dotnet /usr/local/bin/dotnet
CAT << EOF >> .bash_profile
export PATH="$PATH:/root/.dotnet"
EOF
source .bash_profile
cat << EOF >> /etc/httpd/conf.d/porterphotos.conf
<VirtualHost *:*>
    RequestHeader set "X-Forwarded-Proto" expr=%{REQUEST_SCHEME}
</VirtualHost>
<VirtualHost *:80>
    ProxyPreserveHost On
    ProxyPass / http://127.0.0.1:5000/
    ProxyPassReverse / http://127.0.0.1:5000/
#    ServerName www.example.com
#    ServerAlias *.example.com
    ErrorLog ${APACHE_LOG_DIR}porterphotos-error.log
    CustomLog ${APACHE_LOG_DIR}porterphotos-access.log common
</VirtualHost>
EOF
systemctl restart httpd
systemctl enable httpd
mkdir /var/www/porterphotos
aws s3 cp $PORTERPHOTOS_DEPLOY_S3_URI PorterPhotos.tgz --no-progress
tar -xvf PorterPhotos.tgz -C /var/www/porterphotos --no-same-owner
cat << EOF >> /etc/systemd/system/kestrel-porterphotos.service
[Unit]
Description=Porter Photos ASP.NET website (Kestrel)

[Service]
WorkingDirectory=/var/www/porterphotos
# ExecStart=/usr/local/bin/dotnet /var/www/porterphotos/PorterPhotos.dll
ExecStart=/var/www/porterphotos/PorterPhotos
Restart=always
# Restart service after 10 seconds if the dotnet service crashes:
RestartSec=10
KillSignal=SIGINT
SyslogIdentifier=dotnet-porterphotos
User=apache
Environment=ASPNETCORE_ENVIRONMENT=Production 

[Install]
WantedBy=multi-user.target
EOF
systemctl enable kestrel-porterphotos.service 
systemctl start kestrel-porterphotos.service

I then used the AWS CLI to create the instance and then wrote a wrapper-script to make that easy to do. The end result is that I just have to run a single command to start a test instance.

ec2-run-instances.sh:

#!/bin/bash
# not shown: a bunch of stuff to pull in some configuration settings
aws ec2 run-instances \
  --image-id ami-0ed9277fb7eb570c9 \
  --instance-type $INSTANCE_TYPE \
  --key-name $KEY_NAME \
  --user-data file://$CLOUD_INIT_PATH \
  --iam-instance-profile Name="$IAM_INSTANCE_PROFILE_NAME" \
  --security-group-ids $SECURITY_GROUP_IDS \
  --tag-specifications "ResourceType=instance,Tags=[{Key=Name,Value=$TAG_NAME}]" "ResourceType=volume,Tags=[{Key=Name,Value=$TAG_NAME}]"

I paired that with another script to terminate these instances...

ec2-terminate-instances.sh:

#!/bin/bash
# not shown: a bunch of stuff to pull in some configuration settings
# this script can be dangerous; remove --dry-run to do it for real
aws ec2 describe-instances \
  --query 'Reservations[*].Instances[*].{Instance:InstanceId,State:State.Name}' \
  --filters "Name=tag-value,Values=$TAG_NAME" \
  --output text | \
  grep -v terminated | \
  awk '{print $1}' | \
  while read line; do aws ec2 terminate-instances --instance-ids $line --dry-run; done

And now I can fire up and tear down test instances with a single command. How convenient.

(I plan to eventually deploy using CloudFormation. I haven't quite got that far yet.)

Porter Photos

I've started a project with my father to make an S3-backed web photo album app. Key requirements:

  • photos stored in a private S3 bucket, organized in some arbitrary manner
  • minimal fixed costs
  • web interface to add/edit/remove albums and photos-in-albums
  • no management of the private S3 bucket
  • albums are abstractions not directly related to how the photos are organized in the S3 bucket
  • albums can be accessed by the public but may be hidden (not listed on an auto-generated list of albums)
  • photos in albums can have titles and/or descriptions (which may be different if placed in a different album)
  • support different view modes (thumbnails, slideshow, etc.)

My first thought was to do it in PHP. And I really wanted to do MVC. So I gave Symfony a shot. It was a struggle. It was easy to get Symfony installed and a basic test page working, but the more I used it, the more I bumped up against the shortcomings of OO PHP.

Next idea: ASP.NET Core. While I've done a fair bit of ASP.NET development at work, I've never created a site from scratch on my own. And I've never run it on Linux. Also, I wanted to use Visual Studio Code, not full Visual Studio, to force myself to learn how it really works.

Here's the plan:

  • ASP.NET Core web app running on Linux EC2 instance
    • Kestrel with Apache reverse proxy
    • album management and viewing
  • .NET Core Lambda function to resize and move images to separate (public) S3 bucket for viewing
  • DynamoDb to track everything

AWS Lambda for the image resizing because in prior projects I found that resizing large images from a tightly-provisioned EC2 instance can be slow and a bit buggy. (Lambda has cold-start slowness. But I'm thinking that I'll pre-generate images anyways.)

DynamoDb because it is serverless, fast, and basically no cost.

Jan 17th, 2021

A Few New Things

A lot has changed since my last post. I switched from Lightsail to EC2. I finalized switching from Squarespace to AWS for sporterphoto.com. I got a new Mac mini. Oh yeah, and my son was born.

The switch from Lightsail to EC2 was because I wanted to use IAM. The pricing is comparable. There is a bit more complexity to EC2, but a lot more configurability. I was using an "Amazon Linux 2" VM on Lightsail specifically because I was similar to the "Amazon Linux 2 AMI" in EC2, which made the transition easy. I signed up for a Compute Savings Plan shortly after making the switch, saving a few bucks a month.

Switching sporterphoto.com over was simple. I had configured Apache when I brought my EC2 instance up so it was just a matter of DNS changes. I decided to migrate the domain name registration and DNS services to EasyDNS while I was at it, as that is where I register/DNS for all of my domains.

The new Mac mini is still a work in progress. I haven't set it up as a dev environment for my sites yet, as I had with my MacBook Pro. I think I can get Apache/PHP/DynamoDb all running locally, but it just hasn't been a priority. Aside from that, though, I love this new computer. Managing my photo library in Lightroom and Apple Photos is so fast. Web browsing is very smooth. Being able to run iOS apps is quite slick. And the couple games I've tried have run well. I have occasionally battled Bluetooth demons, as is a common issue with these, but it has been working for me recently.

About that other thing-- Conrad James Porter was born back in October. Wow. I can't really summarize the experience except to say wow. Maybe more on that some other time.

Apr 19th, 2020

More Web Development

As the CODVID-19 lockdown continues, I've continued doing more web development tinkering.

I've filled in some gaps in PDBlog's features. I can now login and add/edit/delete posts using via the web. No more posting via command line. I'm gonna work on image management and paging (or maybe infinite scrolling) next.

I'm still getting the hang of PHP. I'm finding a lot of similarities to ColdFusion, which was my jam for a decade starting around 1999. In fact, I haven't yet found anything "better" about PHP, compared to ColdFusion (from a decade ago), except that it is free and much more widely used. In many ways, ColdFusion then (much less now, which I haven't really looked into) was better than PHP is now. (To name a few ways: error handling, Application.cfm, shared variable scopes, CFCACHE.)

My next project is pulling my sporterphoto.com website off Squarespace and hosting it on my Lightsail box. I've been satisfied with Squarespace over the years-- it is just another fun project (plus it'll save me $80/year). I decided to do a slight redesign as part of that switchover. On this project I've learned more Bootstrap than PHP. I'm 90% done at this point-- I mostly just have to figure out when to switch everything over. My current setup is a bit convoluted: the domain is registered with Squarespace and my website is hosted there but I'm using Google for DNS resolution because I have Google Apps for that domain.. I'll figure something out.

Apr 14th, 2020

Introducing PDBlog

As I started resurrecting this site, I decided I wanted a blog. Sure, I could pick from among dozens of well-built blog apps, but I'd have to also run MySQL or something similar. However, I really want to treat the Lightsail instance that this site is hosted on as ephemeral/replaceable. I could opt to use RDS, but that is another cost that I don't need and not quite what I had in mind. I ended up deciding to build my own simple blog app using PHP and DynamoDB.

Building a full-featured blogging app is a daunting project. That wasn't my objective. I'm shooting for the minimum viable product. I need to be able to post and I need to show the posts. Simple stuff. I came up with the following milestones:

  • database initialization; post/edit via command-line; blog reading
  • UI improvements
  • RSS support
  • login and post/edit/preview via web
  • image upload/embedding

My first challenge was getting my dev environment running. I'm running Mac OS High Sierra, which includes Apache 2.4 and PHP 7.1. Amazon distributes DynamoDB Local, which is a dev version of their serverless NoSQL database and runs on my dev box. Surprisingly easy.

After a couple hours rereading and rewatching some tutorials on DynamoDB NoSQL data modeling, I had a solid plan for organizing my DynamoDB table. Now, to implement it...

Though I'm a novice with PHP and DynamoDB, they're fairly simple. For PHP there are a zillion resources online so any questions I had were easily answered. Helpfully, Amazon's documentation for the DynamoDB functions in the AWS SDK for PHP is pretty good.

After six or eight hours of work, I have accomplished first three milestones-- enough to go live. I've written:

  • PHP - script to initialize DynamoDB table and indexes
  • PHP - functions to add/edit/list/get blog posts
  • PHP - script to accept post info (command-line) and post to DynamoDB
  • HTML/PHP - index and permalink pages
  • XML/PHP - to generate RSS
  • CSS - a color scheme and style tweaks
  • one mod_rewrite rule in .htaccess

Under 500 lines of code and I have a minimally-functional solution in place. Not bad for a few evenings worth of tinkering.