DevLog

DevLog is an attempt to develop a more concise, convenient and informal writing form/style that better reflects a normal «conversation». Let's see what happens. Posts on this page are sorted in descending chronological order.

--

What to do with GA/GA4 ?

I recently migrated 3 micro-sites to Drupal 11. All included the google_analytics module, the "religion" of an era that has become an unconscious reflex. OK, I'll comply, I'm going to reinstall it. Oh! the module is gone, it's been replaced by google_tag. Fine, alright. Ah… I have to create a new account, GTA, changing times require it. Oh no… yuck! I feel like Grumpy Smurf. No no no…

But I'd still like to have some numbers… What are the open source alternatives? Matomo $29/month, Plausible $9/month. In both cases I have to create an account… yuck again! Maybe with a few SQL queries then…?

Ah! The 3 micros are in a cPanel. And what's in the toolbox of every cPanel ? AWstats! Not pretty and rather rudimentary but it'll do the job. Hmm… in the cPanel UI there are a lot of clicks that stand in the way of the passing desire to see these statistics and the display (sigh)…

I feel lazy. Is there something better? Yes! There's the Bawstats module (bit obsolete?) which once configured, displays the same reports directly in the Drupal admin UI. Yippee!

 

Optimizing the deploy script of a Ddev-GitLab-cPanel workflow

In a recent post I talked about a Ddev-GitLab-cPanel workflow that removed most of the pain points and irritants related to the deployment of routine updates or new features to cPanel hosted websites.

In this post, I drill down into key details of the cPanel's repository architecture and deploy script (.cpanel.yml) for which I feel there isn't quite enough information to easily achieve an ideal and secure setup.

When creating a cPanel repo from the UI (cPanel » Tools » Files » Git Version Control), by default the Repository Path is /home/your-cpanel-username/repositories/repo-name. Of course, it's easy to change this to e.g. public_html/drupal1, which is what I did after reading through the docs and not finding any explicit warnings against that. I got it wrong! It took a while before I could fully realized that using a project root as a repository path was not best practice.

This 'wrong' had advantages: no code duplication, I could commit changes from Prod back to the remote repo and getting the deploy script to work was easy, so it seemed. But there was a somewhat invisible downside to this: it meant the .git and .ddev landed in the project root and that's not secure.

As it turns out, the suggested Repository Path i.e. /repositories/repo-name is both best practice and secure because it lies outside of public_html. Here's a before and after comparison:

Directory Structure Comparison

Development vs Production File Organization

❌ Not Best Practice
📁public_html/
├── 📁simple_html_site/
│ ├── 📁.git
│ ├── 📁css/
│ ├── 📁files/
│ ├──.cpanel.yml
│ ├──.gitignore
│ ├──index.html
│ └──
├── 📁drupal_site/
│ ├── 📁.ddev/
│ ├── 📁.git
│ ├── 📁config/
│ ├── 📁vendor/
│ ├── 📁web/
│ │ └──.htaccess
│ ├──.cpanel.yml
│ ├──.gitignore
│ ├──composer.json
│ ├──composer.lock
│ └──
└──

⚠️ Issues:

  • Development files (.git, .ddev) exposed in web directory
  • Security risk - version control accessible via web
  • Mixed development and production environments
✅ Best Practice
📁public_html/
├── 📁simple_html_site/
│ ├── 📁css/
│ ├── 📁files/
│ ├──index.html
├── 📁drupal_site/
│ ├── 📁config/
│ ├── 📁vendor/ (full)
│ ├── 📁web/
│ │ └──.htaccess
│ ├──composer.json
│ ├──composer.lock
└──
📁repositories/
├── 📁simple_html_site/
│ ├── 📁.git
│ ├── 📁css/
│ ├── 📁files/
│ ├──.cpanel.yml
│ ├──.gitignore
│ ├──index.html
│ └──
├── 📁drupal_site/
│ ├── 📁.ddev/
│ ├── 📁.git
│ ├── 📁config/
│ ├── 📁vendor/ (empty)
│ ├── 📁web/
│ │ └──.htaccess
│ ├──.cpanel.yml
│ ├──.gitignore
│ ├──composer.json
│ ├──composer.lock
│ └──
└──

✅ Benefits:

  • Clean separation of development and production
  • No development files exposed to web
  • Enhanced security and organization
  • Full dependencies in production, minimal in development

Going for Best Practices means accepting some code duplication, though with a proper .gitignore strategy this quickly becomes a non-issue, really.

The biggest impact of this architecture change is that the deploy script (.cpanel.yml) becomes a bit more complex because it now has to take into account the fact that the cPanel's repository and the site's root are separate locations. Here's a partial example that illustrates this:

---
deployment:
  tasks:
    # Set variables
    - export REPO_PATH=/home/bisonble/repositories/drupal_site
    - export DEPLOYPATH=/home/your-cpanel-username/public_html/drupal_site
    - export LOG_PATH=/home/your-cpanel-username/public_html
    # Initialize custom deploy.log
    - echo "=== Deployment Started ===" > $LOG_PATH/deploy.log
    - echo "Deployment started at $(date)" >> $LOG_PATH/deploy.log
    - echo "Current directory $(pwd)" >> $LOG_PATH/deploy.log
    - echo "" >> $LOG_PATH/deploy.log
    # Copy files and directories
    - echo "=== Copying visible directories and files ===" >> $LOG_PATH/deploy.log
    - /bin/cp -R config recipes web composer.json composer.lock $DEPLOYPATH/
    - echo "" >> $LOG_PATH/deploy.log
    # Run commands
    - echo "=== Running Composer Install ===" >> $LOG_PATH/deploy.log
    - cd $DEPLOYPATH && /usr/local/bin/composer install --no-interaction --prefer-dist --optimize-autoloader --no-dev >> $LOG_PATH/deploy.log 2>&1 || echo "Composer install failed" >> $LOG_PATH/deploy.log
    - echo "" >> $LOG_PATH/deploy.log
    - echo "=== Restoring Custom .htaccess ===" >> $LOG_PATH/deploy.log
    - echo "Current directory $(pwd)" >> $LOG_PATH/deploy.log
    - cd $REPO_PATH && /bin/cp web/.htaccess $DEPLOYPATH/web/.htaccess >> $LOG_PATH/deploy.log 2>&1 || echo "Failed to restore .htaccess" >> $LOG_PATH/deploy.log
    - echo "" >> $LOG_PATH/deploy.log
    - echo "=== Running Drush Deploy ===" >> $LOG_PATH/deploy.log
    - cd $DEPLOYPATH && vendor/bin/drush deploy >> $LOG_PATH/deploy.log 2>&1 || echo "Drush deploy failed with exit code $?" >> $LOG_PATH/deploy.log
    - echo "" >> $LOG_PATH/deploy.log
    # It's a wrap
    - echo "=== Deployment Complete ===" >> $LOG_PATH/deploy.log
    - echo "Deployment finished at $(date)" >> $LOG_PATH/deploy.log
    - echo "" >> $LOG_PATH/deploy.log

Final Notes

Deploying in the cPanel UI occurs in 2 separate steps, each step has its dedicated button:

  • Update from Remote: clicking this button adds a pull job to a queue; I usually wait 10 minutes just to make sure the system can complete this process;
  • Deploy HEAD Commit: clicking this button adds a deploy job to a queue; stay on the page until you get the green successful system message; this can easily take 90 seconds if e.g. you are deploying a Drupal core minor version update, don't forget that a deployment requires composer install and drush deploy to run in succession;

About debugging tools:

  • By default, the cPanel provides a very useful log @ e.g. /home/your-cpanel-username/.cpanel/logs/vc_1721761233.5389_git_deploy.log, make sure to check it out;
  • Also, the .cpanel.yml example provided above shows how you can create your own log to help you debug and fine-tune your script;

Common errors

  • Error: (XID blabla) “/usr/local/cpanel/3rdparty/bin/git” reported error code “128” when it ended: git@remote-repo.com: Permission denied (publickey). fatal: Could not read from remote repository. Please make sure you have the correct access rights and the repository exists.
  • Solution: The error suggests the SSH key authentication isn't properly set up in the cPanel.
    • On cPanel server, check if private key exists: ls -l ~/.ssh/your-cPanel-key*
    • Verify SSH connection: ssh -Tv git@remote-repo.com -i ~/.ssh/your-cPanel-key
    • Ensure key permissions are correct: chmod 600 ~/.ssh/your-cPanel-key && chmod 700 ~/.ssh
    • Add to SSH agent: eval $(ssh-agent -s) && ssh-add ~/.ssh/your-cPanel-key
    • Create/edit ~/.ssh/config:
       Host remote-repo.com
         IdentityFile ~/.ssh/your-cPanel-key
         User git

AI Bubble, AI Trouble

Ai SAASies are flooding the martech space with news, claims & cheap tricks to convince the industry that they really need to pay the extra dollars for their cutting-edge services if they don't want to quickly become obsolete… Don't be fooled, stay away from the bandwagons, be smart, think it through (they won't help you with that because they badly need your money), learn to read through their bluffs, see the AI revolution for what it really is.

Tags

CI/CD & Upgrading from 8.9.20 to 11.1.6

I'm enjoying a bit of time off after a 27 month mandate at Manitoba Liquor & Lotteries. I decided that I would spend time to resolve all the technical issues and blockers that stand in the way of me writing about the stuff I'm interested in.

Personally, Pantheon.io is a thing of the past, my clients that were using it in the golden age of Drupal 7 have moved on to WordPress or other platforms. Ditto with Lando which had replaced MAMP. Time never stays still. Heraclitus, the ancient Greek philosopher, once famously proclaimed:

There is nothing permanent except change.

OK, but before I can start writing fun stuff, I need a website that is functional and whose software is distraction free, i.e., easy to update and upgrade!

This is a humble attempt to document the processes that helped me get over those blockers and cancel my technical debt.

My Stack

  1. Ddev - Drupal's local web development platform of choice, going with the flow here has its advantages, there's no good reasons not to.
  2. GitLab - Until recently, I had never felt the urge or need to use CI/CD in my personal projects. This time has come.
  3. cPanel - For hosting small websites, this stack is "the" Swiss Army Knife, it is both competent and affordable. Let's make it work.

DDEV - To get a good start, the only trick is to first replicate the production environment (exact same versions of modules and themes) in order to be able to import a database from Prod. For my use case, this is what worked:

  • ddev config --project-type=drupal8 --docroot=web --php-version=7.4
  • ddev composer create "drupal/recommended-project:8.9.20" --no-install
  • ddev composer require drush/drush
  • ddev composer install
  • ddev start

Then, leapfrogging through the 252 core releases that mark out Drupal's evolution from 8.9.20 to 11.1.6 is not exactly a lame undertaking; patience is a required virtue for going through Composer dependency hell a few times. BTW, know that manually upgrading might be easier than a full-fledged migration, but it is also riskier. I got bit. More on this later.

GITLAB - That's the easy part of the workflow, no need to go into details. But if you need a quick refresher this YouTube tutorial, will do just fine.

CPANEL - That was my biggest black box, devops is not really my thing. Anyway, I found some helpful and also some slightly misleading information; sifting through it all is not exactly trivial. In those moments of doubt, when I'm outside my comfort zone and I can't quite see the light at the end of the tunnel, I'm reminded of Zork.

This video by Utshuk and the cPanel Docs helped me figure out how to create the repo on the cPanel and connect it to my private GitLab repo. My initial deploy script only partially worked. But I got some help from Claude.ai. Trial and error is also a good friend.

Between the two of them I was able to fine-tune the CI/CD workflow that now makes it easy to deploy my local changes inside a sub-directory of a cPanel's public_html where one or more websites can run.

All is well that ends well.

This website happily purrs on the latest Drupal release. And now that my technical debt is paid in full, I can freely ponder about what I might want to write next…

Under Construction page - postmortem

The idea of creating an Under Construction home page made me smile because it reminded me of the ubiquitous nature of these pages when the Web was just starting out in the early '90. Under Construction pages were of course everywhere. So I thought it would be fun to try to replicate some of that aesthetic and indulge in a little Electronic Frontier nostalgia. Anyway, in case you're curious, that's where the black & yellow stripes come from.

 

At the end of a day's work I'm pretty happy with the result. It's all done in the Wysiwyg of a very basic Body field - also in the spirit of those long gone days when we created entire websites using raw HTML -- sorry, no custom Twig template here.

The black & yellow stripes are empty DIVs with classes, the CSS is injected on the page with Asset Injector, same for the radial striping separator. The layout is rendered responsive thanks to a few more DIVs and Bootstrap.

Initially, I had planned to create a custom content type with a dedicated Twig template. But I soon realized this was cumbersome and that I could more playfully achieve my retro '90 objectives using the Basic Page content type and a bit of HTML coding.

Which raises the question: are we not too often conditioned to create according to rigid standards and best practices to the detriment of simpler, out-of-the-box, no frills and perfectly legitimate solutions ?

« Complexity is your enemy. », Richard Hipp, creator of SQLite