Skip to content

Instantly share code, notes, and snippets.

@webaxones
Last active April 22, 2025 06:58
Show Gist options
  • Save webaxones/54a9aee13bd9152e900ef30a0fcef3ed to your computer and use it in GitHub Desktop.
Save webaxones/54a9aee13bd9152e900ef30a0fcef3ed to your computer and use it in GitHub Desktop.
GitHub workflow to build a WordPress Bedrock site and deploy it to a shared server using SSH
# This GitHub workflow will build a WordPress Bedrock site and deploy it to a shared server (french one: O2Switch, but URLs can be adapted) using SSH
# Actions secrets are used to store sensitive information:
# - SSH_PRIVATE_KEY: The private key used to authenticate with the remote server
# - REMOTE_HOST: The hostname of the remote server
# - REMOTE_USER: The username used to authenticate with the remote server
# - REMOTE_PROD_TARGET: The path on the remote server where the site will be deployed to
# - REMOTE_PREPROD_TARGET: The path on the remote server where the site will be deployed to
# - URL_ENCODED_PASSWORD: The password used to authenticate with the remote server, URL encoded (e.g. using https://www.urlencoder.org/)
# Workflow triggers on pushes to the develop and master branches:
# - On the develop branch, the site is deployed to the preprod target
# - On the master branch, the site is deployed to the prod target
name: Build and Deploy
on:
push:
branches: [ develop, master ]
jobs:
build-deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
# Install composer dependencies
- id: build-php
name: "Install dependencies"
uses: shivammathur/setup-php@v2
with:
php-version: 8.0
- run: composer install --prefer-dist --no-dev -o
# Get the public IP of the runner
- name: Public IP
id: ip
uses: haythem/[email protected]
# Whitelist the public IP of the runner on the remote server by adding it to the SSH whitelist and wait 65 seconds for the IP to be whitelisted
- shell: bash
run: |
curl -sX GET 'https://${{ secrets.REMOTE_USER }}:${{ secrets.URL_ENCODED_PASSWORD }}@${{ secrets.REMOTE_HOST }}:2083/frontend/o2switch/o2switch-ssh-whitelist/index.live.php' | fgrep 'index.live.php' | fgrep 'index.live.php?r=remove&address=' | cut -d '"' -f 2 | while read ipToRemove
do
curl -sX GET 'https://${{ secrets.REMOTE_USER }}:${{ secrets.URL_ENCODED_PASSWORD }}@${{ secrets.REMOTE_HOST }}:2083/frontend/o2switch/o2switch-ssh-whitelist/'$ipToRemove > /dev/null 2>&1
done
curl -X POST \
-d 'whitelist[address]=${{ steps.ip.outputs.ipv4 }}' \
-d 'whitelist[port]=22' \
'https://${{ secrets.REMOTE_USER }}:${{ secrets.URL_ENCODED_PASSWORD }}@${{ secrets.REMOTE_HOST }}:2083/frontend/o2switch/o2switch-ssh-whitelist/index.live.php?r=add' > /dev/null 2>&1
curl -sX GET 'https://${{ secrets.REMOTE_USER }}:${{ secrets.URL_ENCODED_PASSWORD }}@${{ secrets.REMOTE_HOST }}:2083/frontend/o2switch/o2switch-ssh-whitelist/index.live.php' | fgrep -q '${{ steps.ip.outputs.ipv4 }}' && echo "IP whitelisted"
sleep 65
# Deploy the develop branch to the preprod target
- name: 'Deploy on develop branch'
if: ${{ github.ref == 'refs/heads/develop' }}
uses: easingthemes/ssh-deploy@main
with:
SSH_PRIVATE_KEY: ${{ secrets.SSH_PRIVATE_KEY }}
ARGS: "-rlgoDzvc -i --delete-after"
SOURCE: "./"
REMOTE_HOST: ${{ secrets.REMOTE_HOST }}
REMOTE_USER: ${{ secrets.REMOTE_USER }}
TARGET: ${{ secrets.REMOTE_PREPROD_TARGET }}
EXCLUDE: "/dist/, /node_modules/, ./auth.json"
SCRIPT_BEFORE: |
whoami
ls -al
SCRIPT_AFTER: |
whoami
ls -al
echo $RSYNC_STDOUT
# Deploy the master branch to the production target
- name: 'Deploy on master branch'
if: ${{ github.ref == 'refs/heads/master' }}
uses: easingthemes/ssh-deploy@main
with:
SSH_PRIVATE_KEY: ${{ secrets.SSH_PRIVATE_KEY }}
ARGS: "-rlgoDzvc -i --delete-after"
SOURCE: "./"
REMOTE_HOST: ${{ secrets.REMOTE_HOST }}
REMOTE_USER: ${{ secrets.REMOTE_USER }}
TARGET: ${{ secrets.REMOTE_PROD_TARGET }}
EXCLUDE: "/dist/, /node_modules/, ./auth.json"
SCRIPT_BEFORE: |
whoami
ls -al
SCRIPT_AFTER: |
whoami
ls -al
echo $RSYNC_STDOUT
@d9beuD
Copy link

d9beuD commented Mar 20, 2025

✅ Effectivement, c'était bien ça ! Merci pour l'exemple @madrzejewski. Pour y arriver, j'ai dû :

  1. Installer de quoi générer un code OTP sur le runner
- uses: tecolicom/actions-use-apt-tools@v1
  with:
    tools: oathtool gnupg2
  1. Générer un code OTP
OTP=$(oathtool -b --totp '${{ secrets.O2SWITCH_OTP_SECRET }}')
  1. Utiliser une authentification Basic
AUTH_STR="Basic $(echo -n '${{ secrets.SSH_USER }}:${{ secrets.SSH_PASSWORD }}' | base64)"
  1. Utiliser l'authentification sur chaque requête CURL

Il suffit de rajouter -H "Authorization: $AUTH_STR" -H "X-CPANEL-OTP: $OTP" et d'enlever ${{ secrets.SSH_USER }}:${{ secrets.URL_ENCODED_PASSWORD }}@ à chaque requête.

- curl -sX GET "https://${{ secrets.SSH_USER }}:${{ secrets.URL_ENCODED_PASSWORD }}@${{ secrets.SSH_HOST }}:2083/$ENDPOINT?
+ curl -sX GET "https://${{ secrets.SSH_HOST }}:2083/$ENDPOINT?"  -H "Authorization: $AUTH_STR" -H "X-CPANEL-OTP: $OTP"

Solution finale

# Get the public IP of the runner
- name: Public IP
  id: ip
  uses: haythem/[email protected]

- uses: tecolicom/actions-use-apt-tools@v1
  with:
    tools: oathtool gnupg2

# Whitelist the public IP of the runner on the remote server by adding it 
# to the SSH whitelist and wait 65 seconds for the IP to be whitelisted
- name: Whitelist IP on hosting & delete github old ones (o2switch)
  shell: bash
  run: |
    ENDPOINT='frontend/o2switch/o2switch-ssh-whitelist/index.live.php'
    OTP=$(oathtool -b --totp '${{ secrets.O2SWITCH_OTP_SECRET }}')
    AUTH_STR="Basic $(echo -n '${{ secrets.SSH_USER }}:${{ secrets.SSH_PASSWORD }}' | base64)"

    echo "Get actual whitelisted IPs..."
    UNIQUE_IPS=$(curl -H "Authorization: $AUTH_STR" -H "X-CPANEL-OTP: $OTP" -sX GET "https://${{ secrets.SSH_HOST }}:2083/$ENDPOINT?r=list" | jq -r '.data.list[] | .address' | sort -u)
    for address in $UNIQUE_IPS; do
      # if [[ $address == "$#{{ secrets.IP_TO_KEEP }}" ]]; then
      #     echo "Keep this IP, go to the next..."
      #     continue
      # fi
      echo "Delete this github IP: $address (in & out)"
      curl -H "Authorization: $AUTH_STR" -H "X-CPANEL-OTP: $OTP" -sX GET "https://${{ secrets.SSH_HOST }}:2083/$ENDPOINT?r=remove&address=$address&direction=in&port=22" | jq
      sleep 3
      curl -H "Authorization: $AUTH_STR" -H "X-CPANEL-OTP: $OTP" -sX GET "https://${{ secrets.SSH_HOST }}:2083/$ENDPOINT?r=remove&address=$address&direction=out&port=22" | jq
      sleep 3
    done
    echo "All non-whitelisted IPs deleted!"

    echo "Attempt to whitelist IP..."
    curl -H "Authorization: $AUTH_STR" -H "X-CPANEL-OTP: $OTP" -sX POST -d 'whitelist[address]=${{ steps.ip.outputs.ipv4 }}' -d 'whitelist[port]=22' "https://${{ secrets.SSH_HOST }}:2083/$ENDPOINT?r=add" | jq

@d9beuD
Copy link

d9beuD commented Mar 31, 2025

Comme c'est un bout de code assez conséquent et peu pratique à maintenir quand on a plusieurs projets en dépendant, j'ai pris l'initiative de publier une action sur le Marketplace de GitHub Actions.

https://github.com/d9beuD/o2switch-whitelisting

Cela fonctionne très bien avec la 2FA et normalement ça devrait être pareil sans. J'apprécierais vraiment si quelqu'un se dévouait pour tester sans 2FA.

@madrzejewski
Copy link

Un petit message pour dire qu'on est en train de tester une mise à jour de l'outil d'autorisation SSH. Nous avons étendu l'API de cPanel pour y inclure l'outil.

Qu'est-ce que ça change ?

L'outil s'utilise de la même manière que l'API cPanel. Donc il devient compatible avec le système de Token d'API de cPanel. Cela évite de devoir mettre l'identifiant / mot de passe du compte dans les secrets Github.

Ça veut également dire qu'on peut l'utiliser en mode CLI, ça devient un module supplémentaire de la UAPI cPanel.

À cet instant, cela n'est pas publié sur tous les serveurs. C'est en phase de test. Je l'ai utilisé dans le workflow de notre nouvelle documentation et c'est fonctionnel.

Il reste 2-3 choses à revoir, notamment dans le format des réponses, mais ça devrait être disponible partout prochainement.

On va sans doute garder l'ancienne méthode en complément, pour ne pas casser des workflows déjà utilisés.

@d9beuD
Copy link

d9beuD commented Apr 21, 2025

Merci pour l'information, je mettrai à jour mon action pour que ce soit prêt une fois la fonctionnalité disponible sur tous les serveurs. Avez-vous un temps estimé à communiquer @madrzejewski ?

@madrzejewski
Copy link

Je pense que ça sera déployé d'ici une ou deux semaines. L'outil est prêt, il reste juste 2-3 petits détails à corriger, c'est pas grand chose.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment