Le chat

I’m sharing this animation my 12 year old daughter made for her big elementary school graduation ceremony. It was supposed to be projected in front of a 150+ audience. Imagine how thrilled she was. Unfortunately…

Because of a technical problem it was never shown. She was gutted. She self taught herself OpenToonz. She spent many countless hours drawing. She even lost a few weeks of work because she forgot to save the file at one point, but she restarted the project to completion. It’s a short & sweet 35 seconds. Enjoy? Please share? Maybe she can reach more than 150.

I am very proud of her. (In Québec highschool starts in grade 7. Bonne chance pour la prochaine étape!)

Install Upgraded GNU Bash on a Macbook Pro

When you get a new Macbook Pro with M1 chip you get an old version of Bash.

Bash isn’t old. It’s still being maintained. It has millions of Linux and Windows users. Here’s how to put the newest version of Bash back inside your MacOS box.

Prerequisite: Install Homebrew. (Install iTerm2 for good measure.)

Install Bash and the bare minimum of things things you would expect it to have:

brew install bash bash-completion lesspipe 

Verify that it’s installed:

which -a bash
There are now different Bash versions on your Mac, choose the newest one.

Add it to your /etc/shells file:

sudo nano /etc/shells
List of acceptable shells.

Change your default shell to the new Bash:

chsh -s /opt/homebrew/bin/bash

Create a .profile and .bashrc that resembles what you would see in Ubuntu 22.04’s /etc/skel folder:

From here, create your .bash_aliases and make all the other adjustments you’ve been doing since 1989… Bash not dead!

A Book of Generative Poetry

reiterations is an electronic-book containing 24 sonnets. Depending on the e-book reader that is used, each time reiterations is opened, or each time a page in the book is turned, a JavaScript software integrated into the book recomposes the book’s 24 sonnets. In this way, the book automatically generates a new and unique reading experience with each reading. Each time the poems are generated the previous versions are lost and cannot be retrieved. The number of unique sonnets produced by the software is inhumanly vast.

(I have a software credit on this project.)


Compile a Custom Roadrunner Plugin for Laravel Octane

Laravel Octane lets you use Roadrunner.

But, instead of using the provided rr binary why not compile your own? Compiling Roadrunner lets you extend your app by writing Go plugins and middleware.

The conventions on how to compile Roadrunner have changed in version 2. It’s now done with a tool called Velox.


In an existing Laravel application, install Octane:

composer require laravel/octane
php artisan octane:install

Pick Roadrunner as the application server and install the dependency. This will download rr into the Laravel folder.

Tip: If Roadrunner fails to download make sure you don’t already have another version in your path. If you do, delete that stray version and try again.

At this point the default Roadrunner configuration should work. Try it out to make sure.

php artisan octane:start

Next, we’re ready to replace the downloaded rr binary with our own compiled version. To build it we need Go 1.18+ There are many ways to install Go. Here’s one way for MacOS:

brew install go

I also have this in my .bash_profile file:

export GOPATH="${HOME}/go"
export GOROOT="$(brew --prefix golang)/libexec"
export PATH="$PATH:${GOPATH}/bin:${GOROOT}/bin"
export GO111MODULE="on"

After installing Go, install Velox:

go install github.com/roadrunner-server/velox/vx@latest

Next, create a new plugins.toml file in your Laravel folder. This file lists all the Roadrunner plugins to compile. There are many plugins! This example compiles all of them.

build_args = ['-trimpath', '-ldflags', '-s -X github.com/roadrunner-server/roadrunner/v2/internal/meta.version=v2.10.2 -X github.com/roadrunner-server/roadrunner/v2/internal/meta.buildTime=10:00:00']

ref = "v2.10.2"

    token = "__REPLACE_ME__"

    # ref → master, commit or tag
    logger = { ref = "master", owner = "roadrunner-server", repository = "logger" }
    temporal = { ref = "master", owner = "temporalio", repository = "roadrunner-temporal" }
    metrics = { ref = "master", owner = "roadrunner-server", repository = "metrics" }
    cache = { ref = "master", owner = "roadrunner-server", repository = "cache" }
    reload = { ref = "master", owner = "roadrunner-server", repository = "reload" }
    otel = { ref = "master", owner = "roadrunner-server", repository = "otel" }
    server = { ref = "master", owner = "roadrunner-server", repository = "server" }
    service = { ref = "master", owner = "roadrunner-server", repository = "service" }
    amqp = { ref = "master", owner = "roadrunner-server", repository = "amqp" }
    beanstalk = { ref = "master", owner = "roadrunner-server", repository = "beanstalk" }
    boltdb = { ref = "master", owner = "roadrunner-server", repository = "boltdb" }
    broadcast = { ref = "master", owner = "roadrunner-server", repository = "broadcast" }
    fileserver = { ref = "master", owner = "roadrunner-server", repository = "fileserver" }
    grpc = { ref = "master", owner = "roadrunner-server", repository = "grpc" }
    gzip = { ref = "master", owner = "roadrunner-server", repository = "gzip" }
    headers = { ref = "master", owner = "roadrunner-server", repository = "headers" }
    http = { ref = "master", owner = "roadrunner-server", repository = "http" }
    jobs = { ref = "master", owner = "roadrunner-server", repository = "jobs" }
    memory = { ref = "master", owner = "roadrunner-server", repository = "memory" }
    nats = { ref = "master", owner = "roadrunner-server", repository = "nats" }
    new_relic = { ref = "master", owner = "roadrunner-server", repository = "new_relic" }
    prometheus = { ref = "master", owner = "roadrunner-server", repository = "prometheus" }
    redis = { ref = "master", owner = "roadrunner-server", repository = "redis" }
    sqs = { ref = "master", owner = "roadrunner-server", repository = "sqs" }
    static = { ref = "master", owner = "roadrunner-server", repository = "static" }
    status = { ref = "master", owner = "roadrunner-server", repository = "status" }
    kv = { ref = "master", owner = "roadrunner-server", repository = "kv" }
    memcached = { ref = "master", owner = "roadrunner-server", repository = "memcached" }
    tcp = { ref = "master", owner = "roadrunner-server", repository = "tcp" }
    rpc = { ref = "master", owner = "roadrunner-server", repository = "rpc" }
    uuid = { ref = "master", owner = "connerbw", repository = "uuid" }

level = "debug"
mode = "development"

Tip: Make sure to replace the github.token or you will get access denied errors. If your repos are public you don’t need any special permissions.

Near the end of the plugins section pay special attention to this line.

uuid = { ref = "master", owner = "connerbw", repository = "uuid" }

This line represents a custom RPC plugin written in Go. The code comments of the plugin act as a sort of guide if ever you want to write your own. Study it.


Next, cd to your Laravel folder and compile your plugins.toml with Velox.

vx build -c plugins.toml -o ~/path/to/your/laravel/app

Tip: Replace ~/path/to/your/laravel/app with your own. Delete the rr binary before compiling to avoid a write permission error.

After it compiles, add this code to routes/web.php to test it out.

Route::get('/uuid', function () {
  $rpc = Spiral\Goridge\RPC\RPC::create(Spiral\RoadRunner\Environment::fromGlobals()->getRPCAddress());
  return $rpc->call('uuid.Generate', 'not-used');

Restart Laravel Octane and navigate to the new route.

If it all works then PHP is communicating to the new Go plugin over RPC bus. Good times!

Ubuntu 22.04 LTS Inside Windows 11

Running Ubuntu 22.04 LTS on Windows 11 is a breeze.

First, install Windows Terminal and Ubuntu from the Microsoft store. Next, in Ubuntu, setup your .bash_aliases hacks and sudo apt installs. Finally, windows_98_tada.wav

Ubuntu WSL
Tip 1: Make sure the line endings in your .bash_aliases file are LF and not CRLF or you’ll get weird “not found” errors. Tip 2: cd-home is my alias to cd /mnt/c/Users/ME

If you’re like me, a developer who wants Ubuntu not Windows to be the main environment when working, create a /etc/wsl.conf file with:

options = "metadata,umask=022,fmask=111,case=off"

This makes file permissions in /mnt/c behave like how you would expect in Linux. More info.

After this change all files in /mnt/c/Program Files/ and /mnt/c/Program Files (x86)/ requires the WSL terminal to be started as administrator to be able to modify permissions (aka chmod +x)  and it is not currently possible to change permissions in /mnt/c/Windows/

To make Git in Windows more compatible with your workflow (not Git in Ubuntu leave it alone) add this to your .gitconfig

autocrlf = false
fileMode = false

More in depth info available here.

PHP and Go, Together at Last!

PHP and Go, Together At Last!
PHP and Golang, together at last!

This blog post is about Spiral Framework and Roadrunner Server. I’ll briefly talk about what they are, then show how to compile a custom Roadrunner server, start developing with Spiral, using Docker.

Explain Like I’m 5 PHP Developers

Roadrunner works by creating a HTTP server with Golang’s excellent net/http package, and using Goridge as a bridge to pass PSR7 Request and Responses between PHP and Go. The PHP application is then a long-running, already bootstrapped PSR7-capable application that received the already parsed PSR7 request, dispatches it, and collects the response to give back into Go. [1]

Roadrunner offloads unnecessary operations from PHP to a more optimized server, and effectively swaps out the classic setup of Nginx+FPM with a PHP/Golang application that boosts flexibility and performance. [2]

Roadrunner can serve static files without the presence of Nginx, therefore, simplifying the creation of Docker containers. [3]

You can extend your PHP application by including Go libraries, [4], writing Go HTTP middleware, [5], or tweaking and extending the Roadrunner server. [6]

Spiral is a PHP Framework with a customized Roadrunner server. The main difference is that, when you use Spiral’s version of Roadrunner, it comes with more out-of-the-box solutions for PHP developers, Ie.

It’s possible to download the server pre-compiled, but that takes away our power of writing Go code. In this tutorial we start from scratch.

Let’s Go!

The instructions are for Mac, and assume you already have Docker Desktop installed, but the same concepts should work for Linux, and probably Windows.

Step 1

Create a directory for your project.
(If you want, replace hello-spiral with some other name.)

mkdir ~/hello-spiral

Step 2

Create a subdirectory called ./server/ and copy these files into it:


cd ~/hello-spiral
mkdir server
cd server
wget https://raw.githubusercontent.com/spiral/framework/master/main.go
wget https://raw.githubusercontent.com/spiral/framework/master/go.mod

Step 3

Download this Dockerfile into the root dir of ~/hello-spiral

The first stage compiles the app server, the second stage installs the Spiral skeleton app.

Step 4

Your file tree should look like this:

cd ~/hello-spiral; tree

Build a new Docker image:

cd ~/hello-spiral
docker build -t hello-spiral .

Step 5

Run the new Docker image:

docker run -it -p 8080:8080 -p 2112:2112 hello-spiral

Step 6

Go to http://localhost:8080 and verify that it works.


Tada! It runs, but how do we develop?

Step 7

Let’s copy all the PHP files that were successfully installed in the container to our host, then mount them.

While the container is still running from Step 5, in another shell, do:

cd ~/hello-spiral
docker ps

This command will output your container ID:

docker ps

Use your ID in the next command (replace 1f057ae4e473 with your own id):

docker cp 1f057ae4e473:/var/www/app/. src

In the shell tab that is still running the server, stop the server (ctrl-c), then restart with a slight variation of the command from Step 5:

docker run -it -p 8080:8080 -p 2112:2112 -v "$(pwd)"/src:/var/www/app:cached hello-spiral

Keep Going!

Your local PHP files are now mounted in ~/hello-spiral/src, start developing! Change your Dockerfile:

# Setup Spiral
# RUN composer create-project spiral/app . --no-scripts
# Or comment above, uncomment below, and copy Spiral 
COPY ./src/ .

If you make changes in ~/hello-spiral/server, rebuild!

Git Squash at the Command Line with a Bash Script

Useful if the commits on your branch are sloppy, and you want to clean them up, and you don’t mind that your new commits will be file based instead of time based.

What does that mean exactly? Let’s say you worked two days on 3 files. (One of the files was a class, another a config, and the third a unit test.) Over the course of two days you made 13 commits. Sometimes a commit was to one file, sometimes a commit was to both, sometimes all three… 13 times! (Sloppy! You should be ashamed.) After running the script below you will lose 13 commits but can restage and create either a single commit message, or up to 3 new commit messages: One for each file. (Clean! Dick Grune approved.)

Useful if you’re too lazy to use rebase with fixup. Instead, just run one cheap and barely good enough bash script and you’re done. So lazy in fact you’re scrolling through all these words not even reading them, get to the script already.

Useful in scenarios where GitHub’s Squash & Merge interface is unavailable.

Prerequisites before running the script below, where feature/foo is your branch:

  • The script is saved somewhere in your path.
  • You are working on branch feature/foo. You want to create a single squashed commit, or restage in batches, up to as many commits as you have files.
  • You have merged “destination” into feature/foo and resolved conflicts.
  • You are the author of all the commits on feature/foo (Or want to be the author? You will be rewriting…)
  • You are currently on feature/foo.
set -e
branch=$(git rev-parse --abbrev-ref HEAD)
git checkout ${destination}
git pull
git checkout ${branch}
git checkout -b ${branch}-backup
git checkout ${destination}
git branch -D ${branch}
git checkout -b ${branch}
git merge --squash ${branch}-backup
Everything seems fine. Next steps:
    git status
    git commit # Or re-stage and do multiple commits
    git push --force-with-lease --set-upstream origin ${branch}

To undo:
    git reset HEAD .
    git checkout -- .
    git checkout ${destination}
    git branch -d ${branch}
    # If branch exists on GiHub:
    git branch -D ${branch}-backup
    git fetch
    git checkout ${branch}
    # Else:
    git branch -m ${branch}-backup ${branch}
echo "$_done"

Source code.

After running this script your files will be staged. You can either rewrite a nice single commit message for all the files, restage and commit in batches, or undo.

Inspired by this Gist.

Machine Learning and AI using PHP

Montrealer’s who won’t let PHP go (pun intended), get in on that sweet venture capital? Here are two fantastic PHP options for doing machine learning (ML), artificial intelligence (AI), and unprecedented memorization (Singularity), in a dev stack we’re already pretty good at:

Rubix ML

Amazing tutorials like Iris Flower Classifier, Credit Risk Predictor, and Sentiment Analysis make all that buzzword soup turn into real code you can learn from and build on.


Winner of the prestigious Eastern European guy handing out money to projects he likes award, this library is solid clean code.

Other camps may have their sensible conventions, consistent syntax, math fundamentals… But we have PHP! Let’s go!