How to Setup CI/CD of Jigsaw Site to Digital Ocean Droplet Using Bitbucket Pipelines
I created a new personal resume site and decided I wanted to build a static site since it wouldn’t be frequently updated. I evaluated Nuxt, Gatsby, and a few others but settled on Jigsaw, a static site generator based on Laravel. I had never used it before and figured this would be a good learning experience while building something I needed. I was pleasantly surprised by how easy it is to use and setup, so kudos to the Tighten team for putting together such an elegant solution.
I wanted to get a CI/CD pipeline configured to handle the site’s deployment but couldn’t find any working tutorials, so I’m sharing my solution in case it helps others. I’m using Bitbucket for this since it’s a personal private repo, so I’m using Bitbucket Pipelines.
Instructions
After you enable pipelines for your project, you’ll need to configure a Pipelines Repository Variable in your project. Go to the settings tab in your repo, and then select Repository variables:

Add 3 variables:
- USER_NAME – The SSH user name you want Bitbucket to use to connect to your server.
- PRODUCTION_HOST – Your domain that Bitbucket should connect to
- FOLDER – Folder Path where the site should be deployed to

Generate an SSH key (or use your own) and add it to your server under the SSH Keys tab in Pipelines:

I generated a new key and then added it to ~/.ssh/authorized_keys for the account.
Add this YAML snippet to your bitbucket-pipelines.yml in your root. This will use PHP 7.4, install rsync, node + npm, composer, and build the production version of the site to deploy to the specified folder.
The -aVP switch for rsync is to give me verbose progress feedback so I can see what’s happening. If you don’t need the detail, switch it to -a.
image: php:7.4-fpm
pipelines:
branches:
master:
- step:
name: Jigsaw Build
script:
- apt-get update && apt-get install -y unzip
- apt-get install rsync openssh-client nodejs npm -y
- curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer
- composer install
- npm install
- npm run production
- rsync -avP build_production/ $USER_NAME@$PRODUCTION_HOST:$FOLDER --exclude=bitbucket-pipelines.yml --chown=www-data:www-data
I received a few errors when rsync ran. In case you run into them as well, here’s the list and fixes. The first was:
rsync: failed to set times on "$FOLDER": Operation not permitted (1)
I added --no-t
to resolve that and then got a new error:
rsync: failed to set permissions on "$FOLDER": Operation not permitted (1)
which was fixed with adding the switch --no-perms
. My final rsync command became:
rsync -avP --no-t --no-perms build_production/ $USER_NAME@$PRODUCTION_HOST:$FOLDER --exclude=bitbucket-pipelines.yml --chown=www-data:www-data
How to Setup a CI/CD Pipeline for Storybook.js using Gitlab
I just spent a few hours setting up a Gitlab pipeline to deploy a Storybook.js site. Of course the end result ended up being much simpler than I made it out to be. Like everything else on my blog, I’m sharing in case anyone else can use the information to save time.
Just put this in your gitlab-ci.yml and it’ll take care of caching the node modules and building your static version of Storybook to deploy.
image: node:latest
cache:
paths:
- node_modules/
stages:
- build
- deploy
build:
stage: build
script:
- npm install
- npm run build-storybook -- -o storybook-static
artifacts:
paths:
- storybook-static
only:
- qa
- develop
- master
deploy:
stage: deploy_to_aws
# add your deploy code here
How to Clear Archive & Read-only flags on Files in Windows in Bulk
I ran into an issue where I had to move files from one system to another and was running into issues because files had been set as read-only, had the archive flag set, or both. It was causing the system to skip files which wasn’t acceptable. Normally you could just use Windows to clear it in bulk, but that could potentially mess up file permissions. I needed a way to automatically just clear all flags but respect permissions.
I did some searching and didn’t find a utility that would do the job and most of the solutions I found required Powershell which wasn’t available on the system I was on. I ended up writing a quick console application in C# to do the trick. I’ve made it free and open sourced it in case anyone wants to use it.
If you need just the app, you can find the release build here with instructions. The app also prompts for input to make things a bit easier to use. There’s no install, no tracking or metrics, or anything else related to privacy concerns in this app. It’s a simple throwaway utility to get the job done and move on.
https://github.com/gregvarghese/clearflags/releases/tag/1.0.0
If you want to see the source code, that is available here:
https://github.com/gregvarghese/clearflags/
Please note that I did this in about 10 minutes for my own use so error handling is pretty much non-existent. I mention this because I did run into one issue where Windows was somehow seeing a folder with files in it as a file and it couldn’t be deleted or renamed and the utility couldn’t get past it until it was resolved. I didn’t spend much time debugging and jut used my Mac to rename the folder and Windows was able to recognize it after the change, so the utility was able to continue processing.
How to execute SSH command with Bitbucket Pipelines
I inherited an old site that someone else setup that is just a basic static HTML, which was deployed using a git pull on the server. I wanted to automate the deployment, and instead of using rsync as the site will be re-built, I realized I could just configure the Bitbucket Pipeline to use SSH and run the pull command. This is probably a fringe case but here’s the bitbucket-pipelines.yml in case anyone finds it useful.
Add the repository variables for $USER, $SERVER, and $FOLDER with the appropriate values and then you should be able to run the deployment.
pipelines:
default:
- step:
script:
- pipe: atlassian/ssh-run:0.2.8
variables:
SSH_USER: '$USER'
MODE: 'command'
SERVER: '$SERVER'
COMMAND: 'cd $FOLDER && git pull'
How to Generate a Page for Each Day of Month in Microsoft Word using VBA
I briefly joined my wife at her practice to help her grow the business and figure out how to make things more efficient. One of the things I learned is that my wife created a sign-in sheet for the office in Microsoft Word. Every week she would open the file and manually enter the date for each day of the week and then print out the documents. I took over the responsibility for a month and it annoyed me due to how inefficient the process was and I decided to automate the entire thing. I couldn’t find a solution to the problem online so I had to roll my own and am sharing the code in case someone else can benefit from it.
Important Details
The script will calculate the first day and last day of the month and then do a loop to append the date in the “Day, Month day, Year” format (i.e. Thursday July 17, 2019) to a text field.
There are a few important steps involved to get the script working as is:
- Create a Word doc with the first page that you want to duplicate.
- Add a text field from the developer tab. To copy and paste the code below as-is, you’ll need to name it txtDate. This is where the date will be added. If you want a different field name, change the name at line 26 and 83. You can also change the date formats to suit your needs here as well.
- Add a second blank page to the document. I was running into issues where the paste was appearing partially on the first. The blank page resolved this and I added code to remove the original page as well as the blank one from the beginning.
How to Use
Open up Word, then open up VBA, and copy and paste this snippet into a module. When you run the function, it’ll create a copy for every day of the month. I also created a function to start at a specific date in case you run it in the middle of the month.
Sub CreateSigninsForMonth()
Dim N As Integer
Dim sCurrentMonth, sCurrentYear As String
Dim sNewDate As String
N = 1
Count = Day(GetLastDayOfMonth)
For CopyNumber = 1 To Count
With Selection
.GoTo wdGoToPage, wdGoToAbsolute, 1
.Bookmarks("\Page").Range.Copy
.Paste
End With
With ActiveSheet
sCurrentMonth = Format(Date, "mmmm")
sCurrentYear = Format(Date, "yyyy")
sNewDate = (CopyNumber & " " & sCurrentMonth & " " & sCurrentYear)
ActiveDocument.FormFields("txtDate").Result = Format(sNewDate, "DDDD MMMM dd, YYYY")
End With
N = N + 1
Next CopyNumber
'Delete template + blank page
For i = 1 To 2
With ActiveDocument
strt = .GoTo(wdGoToPage, wdGoToLast).Start
Set r = .Range(strt - 1, .Range.End)
r.Delete
End With
Next
End Sub
Sub CreateSigninsForMonthStartingDate()
Dim Count As Integer
Dim N As Integer
Dim sCurrentMonth, sCurrentYear As String
Dim sNewDate, sEndDay As String
N = 1
Count = 0
iStartDay = InputBox("Which day do you want to start on?", "Starting Day", "1")
Count = InputBox("Which day do you want to end on?", "Ending Day", Day(GetLastDayOfMonth))
Do While Count > Day(GetLastDayOfMonth)
sEndDay = InputBox("Which day do you want to end on?", "Ending Day", Day(GetLastDayOfMonth))
If iStartDay = vbNullString Or sEndDay = vbNullString Then
MsgBox "You clicked cancel.", vbOKOnly, "Try again later!"
Exit Sub
End If
If IsNumeric(CInt(sEndDay)) Then
Count = CInt(sEndDay)
End If
Loop
For CopyNumber = iStartDay To Count
With Selection
.GoTo wdGoToPage, wdGoToAbsolute, 1
.Bookmarks("\Page").Range.Copy
.Paste
End With
With ActiveSheet
sCurrentMonth = Format(Date, "mmmm")
sCurrentYear = Format(Date, "yyyy")
sNewDate = (CopyNumber & " " & sCurrentMonth & " " & sCurrentYear)
ActiveDocument.FormFields("txtDate").Result = Format(sNewDate, "DDDD MMMM dd, YYYY")
End With
N = N + 1
Next CopyNumber
'Delete template + blank page
For i = 1 To 2
With ActiveDocument
strt = .GoTo(wdGoToPage, wdGoToLast).Start
Set r = .Range(strt - 1, .Range.End)
r.Delete
End With
Next
End Sub
Function GetFirstDayOfMonth(Optional dtmDate As Date = 0) As Date
' Return the first day in the specified month.
If dtmDate = 0 Then
' Use the current date if none was specified
dtmDate = Date
End If
GetFirstDayOfMonth = DateSerial(Year(dtmDate), Month(dtmDate), 1)
End Function
Function GetLastDayOfMonth(Optional dtmDate As Date = 0) As Date
' Return the last day in the specified month.
If dtmDate = 0 Then
' Use the current date if none was specified
dtmDate = Date
End If
GetLastDayOfMonth = DateSerial(Year(dtmDate), Month(dtmDate) + 1, 0)
End Function
How to Get Laravel Debugging to work with PHPStorm and MAMP Pro 5
This has been one of the more aggravating things I’ve had to deal with in setting up software for development. I’ve followed the official documentation from JetBrains, over 30 other blog tutorials, and literally failed in getting any of them to work.
I figured out an easy way to make the setup work so I’m sharing it in case someone else finds it useful and for self-reference since I’ll probably forget how to do this again in 6 months when I start a new project.
MAMP Configuration
- Load MAMP and setup your host. Make note of the host name as you will need it to configure PHPStorm.
- Go to PHP on the left under Languages.
- On the right under Extensions, check Xdebug (Debugger).

PHPStorm Configuration
- Load PHPStorm and load your Laravel project.
- Setup your PHP executable and interpreter as per the official documentation and then resume here.
- On the top right of PHPStorm, select Edit Configurations from the dropdown.
- Click on the Plus Button on the top left of the dialog and then select ‘PHP Web Page’.
- Enter a descriptive name in the textbox. I use the host name from MAMP so it’s easy to identify visually. Click on the 3 dots with next to Server.
- Enter a descriptive name. I use the host name here as well. For the host, omit the http/https and just add the host name from MAMP.
- Click OK
- Now add your breakpoints and click on the Debugger Button on the top right and PHPStorm will load the site into the browser and break when breakpoints are hit.
Happy debugging!
How to get website average latency in BASH
I was working on a project today and wanted to be able to get the average latency for an API that I was working on. Performance is a concern because we’re running the API over a VPN, and then SSH tunneling over to another server. I wanted a quick way to do it and wrote a little bash function that will calculate the average for me. I couldn’t find an example on how to do this online so I’m sharing in case anyone else runs into the same issue.
This is tested on Mac only. Add these two functions to your .bashrc and do a shellupdate in terminal to load the latest, or just grab my dotfiles from my github: https://github.com/gregvarghese/dotfiles
curlb(){
curl -s -o /dev/null -w '%{time_starttransfer}\n' "$@"
}
# Usage:
# latencyavg [# of times to run] [URL]
function latencyavg()
{
time=0.00
for (( c=1; c<=$1; c++ ))
do
num1=$(curlb $2 -H 'Accept-Encoding: gzip, deflate, sdch' -H 'Accept-Language: en-US,en;q=0.8,ja;q=0.6' -H 'Upgrade-Insecure-Requests: 1' -H 'User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/50.0.2661.86 Safari/537.36' -H 'Connection: keep-alive' --compressed)
echo "$c - $num1"
time=$(echo "$time + $num1" | bc -l)
done
echo "Total Time $time"
echo $(echo $time / $1 | bc -l)
}
Usage example:
latencyavg 20 https://www.godaddy.com
How to remove wrapping tags in PHP Storm
How often do you code something and need to delete a wrapping link or div? I was using PHPStorm and had grabbed some code from another file that had links in the tags which weren’t needed in the new file. I didn’t want to manually remove each link and after a quick look around PHPStorm’s menus, discovered that PHPStorm has a really useful command to remove the wrapping element for you.
Select the element, then from the menu, choose Code > Unwrap/Remove… or use the keyboard shortcut, Command + Shift + Backspace and then choose the appropriate wrapping element to remove.

Windows 7 & 10 How to Bulk Unblock Blocked Files
While setting up a new computer, Windows was throwing up warnings that files transferred from a backup drive might be unsafe. The files were text and images, so the warnings were safe to ignore but the directory had over one thousand files. Rather than unblocking each file manually, Windows PowerShell makes it easy to unblock files in bulk.
Load up Windows Powershell:
- Press Win + R on the keyboard to open the Run dialog.
- In the Run box, type powershell
For one folder without subdirectories, this snippet will do the trick.“$env:userprofile\Downloads“ tells PowerShell to use the Download folder for the logged in user and unblocks all files in it. Change this to the folder path you need if it’s not the Downloads folder.
get-childitem “$env:userprofile\Downloads“ | unblock-file
If you have sub-directories and need to unblock everything, use the -Recurse flag:
dir “$env:userprofile\Downloads“ -Recurse | Unblock-File
Want to see a report of files to be unblocked before running it? The -WhatIf flag will show you without executing.
dir “$env:userprofile\Downloads“ -Recurse | Unblock-File -WhatIf
How to Fix ‘Converter Failed to Save File’ with Excel 2016
I recently upgraded to Office 2016 on my Windows 10 desktop and was getting the error “Converter failed to save file” when double clicking on the file along with an “There was a problem sending the command to the program error” every time. I finally had enough with the annoyance to troubleshoot it and figured out a solve.
If you have the same issue, here’s how to fix it:
- Open your Default Programs configuration from the Control Panel. On Windows 10, you can hit start, type Default Programs, and it’ll open the app.
- Scroll down the list until you get to the Excel formats (XLS):
- If you see anything other than Excel as the default, you’ll need to change the default to Excel. For me, the issue was the Open XML Converter not being installed anymore after upgrading to 2016. To change the default, select the format, click the “Change Program” button and select Excel 2016 from the list of apps that pops up and click OK to set the association:
- You’ll need to do this for each format in the list to correct it. The most common formats you’ll use are XLS, XLSX, & XLT.