Skip to content

Deploying to NUCs

Setting Github

To ensure secure access to your GitHub repository from a NUC, generate an SSH key pair and add the public key to your GitHub account.

bash
ssh-keygen -t ed25519 -C "your_email@example.com"

Copy the contents of the public key by printing it

bash
cat ~/.ssh/id_ed25519.pub

Please add the above public SSH key to your GitHub account by following these steps:

  1. Go to https://github.com/settings/keys
  2. Click the 'New SSH key' button.
  3. Enter a title for the key and paste the copied public key into the 'Key' field.
  4. Click the 'Add SSH key' button to save the key.

Optionally you can test the connection with GitHub by running:

bash
ssh -T git@github.com

Installing Node

We prefer using NVM to manage Node versions. To install NVM, run the following command:

bash
wget -qO- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.1/install.sh | bash

Update your system's environment: You can source the file with the following command:

bash
source ~/.bashrc

Install Node

bash
nvm install --lts

Optional: Installing git LFS

Sometimes activations have large videos and assets that need to be stored in the repository. To install git LFS, run the following command:

bash
sudo apt-get install git-lfs

Then run the following command to initialize git LFS:

bash
git lfs install

Installing Postgres

Install PostgreSQL

bash
sudo apt-get install -y postgresql postgresql-contrib

Start the PostgreSQL service

bash
sudo systemctl start postgresql

Enable PostgreSQL to start on boot

bash
sudo systemctl enable postgresql

Set the password

This will get you into the PostgreSQL command line interface.

bash
sudo -u postgres psql

Follow the prompt to set a password for the 'postgres' user.

bash
\password postgres

Quit the PostgreSQL CLI.

bash
\q

You can now add the .env var

bash
DATABASE_URL=postgresql://postgres:postgres@localhost:5432/activation?schema=public

Adding PM2 scripts

Install pm2 globally

bash
npm install pm2 -g

Run the pm2 command to initiate a config file

bash
pm2 init

Then change the contents of the generated file ecosystem.config.js.

javascript
module.exports = {
  apps: [
    {
      name: "activation-app",
      script: "./node_modules/next/dist/bin/next", // changes if using sockets
      args: "start", // changes if using sockets
      instances: 2,
      exec_mode: "cluster",
      wait_ready: true,
      log_date_format: "DD-MM HH:mm:ss.SSS",
      listen_timeout: 3000,
      env: {
        NODE_ENV: "production",
      },
    },
  ],
};

Add the following scripts to the package.json file:

bash
"pm2:start": "pm2 start",
"pm2:restart": "pm2 reload all --update-env",
"pm2:kill": "pm2 delete all",
"pm2:deploy": "./deploy-nuc.sh",

Automatic start on boot

To ensure that the app starts automatically on boot, run after starting the app: Ref Link: https://pm2.keymetrics.io/docs/usage/startup/

bash
# you need to run the app before saving
npm run pm2:start
bash
# To automatically generate and configuration a startup script just type the command (without sudo)
pm2 startup
bash
# save the current pm2 list
pm2 save

logging on the nucs

To view the logs on the nucs, run the following command:

bash
pm2 logs activation-app

Near Zero Downtime deployments with pm2

You can use this simple strategy, although it won't guarantee "zero" downtime, but it would be much better than the current experience. Basically, you build your project into a temp folder, and then delete existing .next folder, and rename your temp folder as .next.

Add this to your next.config.js file:

javascript
const nextConfig = {
  distDir: process.env.OUTPUT_DIR || ".next",
};

Create a script with the following contents: ie: ./deploy-nuc.sh

bash
#!/bin/bash
echo "Deployment starting..."

# pull latest changes
git pull origin master

# store latest hash to include in build
# you can use this to track if the latest report, for example you can build a Next.js
# API route to return this
LATEST_COMMIT_HASH=$(git rev-parse --short  HEAD)
export LATEST_COMMIT_HASH

# install dependencies if any
npm ci || exit

# set build folder to `temp` and build
OUTPUT_DIR=temp npm run build || exit

if [ ! -d "temp" ]; then
  echo '\033[31m temp directory does not exist!\033[0m'
  exit 1;
fi

# delete `.next` folder
rm -rf .next

# rename `temp` folder to `.next`
mv temp .next

# run next start
if pm2 describe activation-app > /dev/null 2>&1; then
  echo "activation-app is running, restarting..."
  npm run pm2:restart
else
  echo "activation-app is not running, starting..."
  npm run pm2:start
fi

echo "Deployment done."

Make the file executable

bash
chmod +x ./deploy-nuc.sh

Automatic updates from Github

Set up a workflow which GitHub will prompt you to do. You can start with a simple workflow file like this in .github/workflows/deploy.yml.

yaml
name: Deploy to NUCS
on:
  workflow_dispatch:
  # Uncomment if you want to autodeploy
  # push:
  #   branches:
  #     - master
jobs:
  check-build:
    name: Check Build
    runs-on: ubuntu-latest
    steps:
      - name: Checkout repository
        uses: actions/checkout@v4

      - name: Set up Node.js
        uses: actions/setup-node@v4

      # Uncomment if needed. Adjust to your project needs
      # Creates dotfiles using GitHub's secrets 
      # https://docs.github.com/en/actions/security-for-github-actions/security-guides/using-secrets-in-github-actions

      # - name: Create dotfiles
      #  run: |
      #    echo "${{ secrets.NPMRC }}" > .npmrc 
      #    echo "${{ secrets.ENV_FILE }}" > .env

      - name: Install dependencies
        run: npm ci

      - name: Build project
        run: npm run build

  deploy:
    name: Deploy to NUC
    runs-on: ubuntu-latest
    needs: check-build
    strategy:
      matrix:
        nuc: [NUC1, NUC2] # add as many as needed [NUC1, NUC2, ...]
    steps:
      - uses: actions/checkout@v4
      - name: Parse NUC credentials
        id: parse_creds
        run: |
          IFS=':' read -r HOST USERNAME PASSWORD PORT <<< "${{ secrets[format('NUC_CREDENTIALS_{0}', matrix.nuc)] }}"
          echo "::add-mask::$HOST"  # Mask the host to avoid it showing in logs
          echo "::add-mask::$USERNAME"  # Mask the username to avoid it showing in logs
          echo "::add-mask::$PASSWORD"  # Mask the password to avoid it showing in logs
          echo "HOST=$HOST" >> $GITHUB_ENV
          echo "USERNAME=$USERNAME" >> $GITHUB_ENV
          echo "PASSWORD=$PASSWORD" >> $GITHUB_ENV
          echo "PORT=$PORT" >> $GITHUB_ENV

      - name: Deploy to server
        uses: appleboy/ssh-action@v1.0.3
        with:
          host: ${{ env.HOST }}
          username: ${{ env.USERNAME }}
          password: ${{ env.PASSWORD }}
          port: ${{ env.PORT }}
          script: |
            cd code/
            export NVM_DIR=~/.nvm # for some reason nvm is not ready
            source ~/.nvm/nvm.sh
            ./deploy-nuc.sh

Create the appropriate secrets in your repository settings. with the following format

bash
NUC_CREDENTIALS_NUC1=host:username:password:port
NUC_CREDENTIALS_NUC2=102.168.3.22:wondersauce:******:22