Tuesday, September 8, 2020

Flutter: Fixing Firebase header not found with Notification Service Extension

If you follow the FCM tutorial Send an image in the notification payload and encountered this error message:
'FirebaseMessaging/FirebaseMessaging.h' file not found
You are on the right place, I'm going to show you how to fix it.

My app was working fine but one day it stopped compiling. Apparently Flutter 1.20 changed the way it uses CocoaPod so the service extension no longer has the proper library configured. After some tinkering, I came up with this pod config, it has to be added to ios/Podfile below the main Runner target.
target 'FcmImage' do

  require File.expand_path('../.symlinks/plugins/firebase_core/ios/firebase_sdk_version.rb', __FILE__)
  firebase_sdk_version = firebase_sdk_version!
  pod 'Firebase/Messaging', "~> #{firebase_sdk_version}"
  • FcmImage is my extension name, replace it with yours
  • We can use a hardcoded version for Firebase/Messaging pod but doing so may require further modification in the future. In the example above, I extracted the SDK version from firebase_core package.
The full Podfile:

Thursday, July 30, 2020

Making GIF of iOS Simulator without additional apps

This is how I did it.

First, you need to open Terminal and execute this command:

xcrun simctl io booted recordVideo \
--code=h264 \
--force "$( date +%Y%m%d%H%M%S ).mov"


Running this will record the Simulator screen until you press `Control+C`. The filename will be something like `20200730095757.mov`. The next step is to convert this video into GIF, FFMPEG is required for this. You can either install it via Homebrew but I prefer running it in a Docker container, something like this:

docker run --rm -it -v $PWD:/data -w /data --entrypoint /bin/bash jrottenberg/ffmpeg

Once you have FFMPEG installed or are inside the container, execute this command:

ffmpeg -i 20200730095757.mov \
-vf "fps=fps=5,split[s0][s1];[s0]palettegen[p];[s1][p]paletteuse" \


I have played around with the command line arguments and came up with those. Basically it will create a decent GIF to use in issue report etc. Because of the low frame rate, it won't be very smooth but the filesize is acceptable. See these additional links to tweak it further if you want to:


Monday, March 16, 2020

Running phpinfo to test Kubernetes ingress and Let's Encrypt certificate

For new cluster, I usually deploy a simple phpinfo app to test that the happy-path works as expected. The PHP service and deployment is pretty simple:

The Docker image is basically a php:apache image with a single index.php file.

Next, setup the ingress and issuer (I'm using ingress-nginx and cert-manager FYI):

And voila.

Friday, February 28, 2020

Amazon SES notes

Amazon IAM policy to restrict sending email from a single address

  "Version": "2012-10-17",
  "Statement": [
      "Effect": "Allow",
      "Action": "ses:SendRawEmail",
      "Resource": "*",
      "Condition": {
        "StringLike": {
          "ses:FromAddress": "username@domain.com"

Replace `username@domain.com` with `*@domain.com` to allow sending from any addresses.

Generate SMTP password from the secret access key

Official document is available at docs.aws.amazon.com. Usage is like this:

./ses.py --secret DmxxxOY --region us-west-2

Test SMTP credentials

I use the swaks script (source: wiert.me):

brew install swaks

swaks -tls --to xxx@gmail.com \
  --from username@domain.com \
  --server email-smtp.us-west-2.amazonaws.com \
  --auth-user AKxxxOP

If everything is setup correctly, you should receive the test email in your inbox.

Saturday, January 4, 2020

Configure XenForo 2 to use FTP adapter for external and internal data storage

XenForo 1 needs an add-on to use remote storage for data (useful in container environments) but XenForo 2 supports FTP out of the box. Just put something like these in config.php:

$config['fsAdapters']['data'] = function () {
    return new \League\Flysystem\Adapter\Ftp([
        'host' => 'ftp.domain.com',
        'password' => 'password',
        'username' => 'data',
$config['externalDataUrl'] = function ($externalPath, $canonical) {
    return 'https://data.domain.com/' . $externalPath;

$config['fsAdapters']['internal-data'] = function () {
    return new \League\Flysystem\Adapter\Ftp([
        'host' => 'ftp.domain.com',
        'password' => 'password',
        'username' => 'internal_data',