Skip to content

Latest commit

 

History

79 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Autobuilder

A non-interactive CI/CD pipeline written entirely in Bash. It fetches source code from Bitbucket, packages or compiles it locally, uploads the artifact to a remote server over SSH, extracts it, and runs a service installer on the remote. Build status notifications are sent to Slack.


Table of Contents


Architecture

Entry-point script  (e.g. dev-api.example.com-my-service.sh)
  │
  └── Deployer      (scripts/nodejs-pm2-deployer.sh, etc.)
        │  Defines: AUTOBUILDER_PACKAGER, AUTOBUILDER_INSTALLER,
        │           accepted CLI flags, remote installer flag map
        │
        └── scripts/engine/engine.sh
              │
              ├── helpers/logger.sh           logging, email, Slack
              ├── helpers/dynamic-flags.sh    parse CLI args → variables
              ├── helpers/server-api.sh       SSH connect, remoteCommand, remoteUpload
              │     └── servers/{server}.sh   server credentials
              │
              ├── helpers/fetch-repository.sh git clone/pull from Bitbucket
              │
              ├── packagers/{packager}.sh      compile/zip the payload locally
              │     └── helpers/zip-validator.sh
              │
              ├── [remoteUpload]  payload.zip + payload-extractor.sh + installer.sh
              ├── [remoteCommand] payload-extractor.sh   unzip on remote
              └── [remoteCommand] installers/{installer}.sh   start service
                    └── helpers/post-to-slack.sh   success/failure notification

Prerequisites

The following must be available on the autobuilder host (the machine running the scripts):

Tool Purpose
bash Shell runtime
git Repository fetching and version push-back
ssh / scp Remote server access and file upload
zip / unzip Payload packaging and extraction
nvm Node.js version management (auto-installed if missing)
go Required only for Go binary deployments
mailx Optional — log forwarding via email
dos2unix Normalise line endings on uploaded scripts

SSH keys must be configured:

  • ~/.ssh/repositories — private key with read access to Bitbucket repositories
  • Per-server key defined in each servers/*.sh definition (e.g. ~/.ssh/do-ssh-key)

Quick Start

1. Define a server in servers/my-server.sh:

domain=example.com
serverName=my-server
serverIP=10.0.0.1
user=root
sshKey=~/.ssh/my-key.pem

2. Create an entry-point script in the project root:

#!/bin/bash
DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
branch=${1:-"main"}

cd "$DIR/scripts"

bash nodejs-pm2-deployer.sh \
  -e production \
  -r my-bitbucket-workspace \
  -p my-api \
  -s my-server \
  -m 3000 \
  -b "$branch"

3. Run it:

bash my-api-production.sh
# or with a branch override:
bash my-api-production.sh feature/my-branch

Server Definitions

Each server is defined as a .sh file inside the servers/ directory. The filename (minus .sh) is the value passed to the -s flag of any deployer.

# servers/my-server.sh
domain=example.com
serverName=my-server
serverIP=203.0.113.10
user=root
sshKey=~/.ssh/my-key.pem
Variable Description
domain Domain name of the server
serverName Friendly name (matches the filename)
serverIP IP address — use localhost or 127.0.0.1 for local deployments
user SSH login user
sshKey Path to the SSH private key

Deployers

Deployer scripts live in scripts/. Each one exports engine configuration variables, defines accepted CLI flags, and sources engine/engine.sh to start the pipeline.

Common Flags

Most deployers accept some or all of these flags:

Flag Variable Required Default Description
-e environment Yes Target environment (development, production, etc.)
-r repository Yes Bitbucket workspace/bucket name
-p projectName Yes Repository and project name
-s server Yes Server ID (matches a file in servers/)
-b branch No master Git branch to deploy
-w sitename No $repository Whitelist/site name used in the installation path
-f projectFolder No $projectName Override the remote folder name

Node.js + PM2 Deployer

Script: scripts/nodejs-pm2-deployer.sh

Packages Node.js backend source (no local build), uploads it, runs npm install and starts/restarts the process under PM2 on the remote server.

Additional flags:

Flag Variable Required Default Description
-m port Yes Port the service listens on
-n nodeTargetVersion No remote default Node.js version to use on the remote
-x skipTests No false Skip npm test on the remote

Example:

bash nodejs-pm2-deployer.sh \
  -e development \
  -r my-workspace \
  -p my-api \
  -s dev-01 \
  -m 4000 \
  -n 18.20.0 \
  -b main

Next.js Deployer

Script: scripts/nextjs-deployer.sh

Zips the full source directory and uploads it. The remote installer runs npm install, npm run build, and starts the app with PM2 (npm start). The build happens on the remote server.

No additional required flags beyond the common set.

Example:

bash nextjs-deployer.sh \
  -e production \
  -r my-workspace \
  -p my-nextjs-app \
  -s prod-01

React.js Deployer

Script: scripts/reactjs-deployer.sh

Builds the React app locally (npm run build), zips the build/ output, and deploys it to the web root (/var/www/). Uses Node 18.13.0 by default.

Additional flags:

Flag Variable Required Default Description
-a subDomain No $environment Subdomain prefix for the web root path
-l basepath No URL base path appended to the web root
-d disableSubDomain No false Deploy to domain/ instead of subdomain.domain/

Example:

bash reactjs-deployer.sh \
  -e staging \
  -r my-workspace \
  -p my-react-app \
  -s staging-01 \
  -a staging

Vite Deployer

Script: scripts/vite-deployer.sh

Builds a Vite-based frontend locally, zips the dist/ output, and deploys it to the web root. Uses Node 22.18.0 by default. Tests are enabled by default.

Additional flags:

Flag Variable Required Default Description
-a subDomain No $environment Subdomain prefix
-l basepath No URL base path
-d disableSubDomain No false Deploy to root domain
-x skipTests No false Skip npm run test

Example:

bash vite-deployer.sh \
  -e production \
  -r my-workspace \
  -p my-vite-app \
  -s prod-01 \
  -x

Vue.js Webpack Deployer

Script: scripts/vuejs-webpack-deployer.sh

Builds a Vue.js (webpack) frontend locally, zips the build/ output, and deploys it to the web root. Uses Node 18.13.0. Functionally identical to the React deployer with a full (non-production) npm install.

Flags are the same as the React.js Deployer minus the -x test-skip flag.


Go Binary (systemd) Deployer

Script: scripts/golang-dist-deployer.sh

Compiles a Go binary for linux/amd64 locally, zips it with any YAML config files, uploads it, and installs it as a systemd service on the remote server.

Additional flags:

Flag Variable Required Default Description
-m port Yes Port the service listens on
-x skipTests No false Skip go test ./...

Example:

bash golang-dist-deployer.sh \
  -e production \
  -r my-workspace \
  -p my-go-service \
  -s prod-01 \
  -m 8080

Entry-Point Scripts

Entry-point scripts in the project root are deployment configurations for specific services. They pin all the flags for a given service so deployments can be triggered with a single command.

Example — dev-api.unlimtid.io-authentication-api.sh:

#!/bin/bash
DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
branch=${1:-"development"}
export APP_ROOT_PATH=/opt

cd "$DIR/scripts"
bash nodejs-pm2-deployer.sh \
  -e development \
  -r firmviewdev \
  -w unlimitid \
  -p unlimitid-authentication-server \
  -f unlimitidio-auth-server \
  -n 16.17.1 \
  -b "$branch" \
  -s crypto-01 \
  -m 43003

A -test-skip variant of the same script appends -x to skip the test suite for faster deployments.

Convention: name entry-point scripts as {environment}-{domain}-{service}.sh.


Engine

Script: scripts/engine/engine.sh

The core orchestration script. Sourced (not called) by deployer scripts. It is not intended to be run directly.

What the engine does, in order:

  1. Resolves all paths (ENGINE_DIR, AUTOBUILDER_PATH, TMP_PATH, AUTOBUILDER_BUILD_DIR, etc.)
  2. Sources logger.sh and dynamic-flags.sh to parse CLI flags and call verifyArgs()
  3. Validates required variables and the AUTOBUILDER_PACKAGER value
  4. Sources server-api.sh to establish SSH connectivity to the target server
  5. Computes PAYLOAD_INSTALLATION_TARGET:
    • Web root deployments (REMOTE_PATH_WWW_ROOT=true): /var/www/{subDomain.}domain{/basepath}
    • App deployments: $APP_ROOT_PATH/$sitename/$environment/$projectFolder
  6. Sends a Slack notification that the build has started
  7. Clones or pulls the repository via fetch-repository.sh
  8. Sources the configured packager to produce $PAYLOAD_ZIP
  9. Creates the staging directory on the remote, uploads the zip, extractor, and installer scripts
  10. Executes payload-extractor.sh on the remote to unzip the payload
  11. Executes the installer script on the remote to start the service
  12. On success, calls success() which logs, emails, and sends a Slack notification

Key engine variables:

Variable Description
AUTOBUILDER_PACKAGER Packager to use (golang-binary, nodejs-source, nodejs-websource, source)
AUTOBUILDER_INSTALLER Remote installer to use (pm2-service, pm2-service-nextjs, sysd-service)
REMOTE_PATH_WWW_ROOT Set true to deploy to /var/www/ instead of $APP_ROOT_PATH
REMOTE_INSTALL_ENABLE_ROLLBACK Set true to generate an uninstall.sh on the remote
REMOTE_INSTALL_RUN_TESTS Set true to run tests on the remote after install
APP_ROOT_PATH Override the default app installation root (default: /home/$user/apps)
LOG_LOCAL Set true to print log output to stdout

Helpers

Script Purpose
engine/helpers/dynamic-flags.sh Generic getopts parser — maps flags to variables using the arguments array defined by each deployer
engine/helpers/fetch-repository.sh Clones or hard-resets a Bitbucket repository to the specified branch using ~/.ssh/repositories
engine/helpers/logger.sh Structured logging with levels INFO, DEBUG, ERROR, FATAL, SUCCESS; writes to a log file, emails on fatal/success, and notifies Slack
engine/helpers/server-api.sh Provides remoteCommand() and remoteUpload() that transparently work on both SSH-connected and localhost targets
engine/helpers/payload-extractor.sh Remote script — validates and unzips the payload into the installation directory
engine/helpers/zip-validator.sh Validates the local zip is at least 30 KiB before upload
engine/helpers/post-to-slack.sh Fires a Slack incoming webhook with a plain-text message
engine/helpers/slack_template.json Reference Block Kit JSON template for richer Slack messages (not currently wired up)

Packagers

Packagers run on the autobuilder host and produce a $PAYLOAD_ZIP file.

Script Used by Behaviour
packagers/source.sh Next.js deployer Zips the entire project directory as-is
packagers/nodejs-source.sh Node.js PM2 deployer Zips src/, *.js, *.json, and config files — no build step
packagers/nodejs-websource.sh React, Vite, Vue deployers Installs dependencies, runs optional tests, builds (npm run build), zips only the compiled output directory
packagers/golang-binary.sh Go deployer Runs optional tests, cross-compiles for linux/amd64 (Makefile or go build fallback), zips the binary and YAML configs

Installers

Installers are uploaded to the remote server and executed via SSH after the payload has been extracted.

pm2-service.sh

Installs a Node.js backend under PM2.

  1. Installs/switches nvm and pm2 if needed
  2. Auto-detects the entrypoint file (index.js, main.js, app.js, or src/ variants)
  3. Runs npm install
  4. Optionally runs npm test
  5. Replaces the existing PM2 process and validates online status
  6. Runs pm2 save

pm2-service-nextjs.sh

Installs a Next.js app under PM2. Identical flow to pm2-service.sh but runs npm run build on the remote and starts the app via pm2 start npm -- start.

sysd-service.sh

Installs a Go binary as a systemd service.

  1. Generates an uninstall.sh on the remote
  2. Creates a systemd unit file with Restart=always if one does not already exist
  3. Handles SELinux — temporarily disables enforcement, generates and installs a policy module via audit2allow, then re-enables enforcement
  4. Runs systemctl daemon-reload, enable, and restart

Supports both root-owned (/lib/systemd/system) and user-owned (~/.config/systemd/user) service locations.


SSH Configuration

Run servers/create-ssh-config.sh on the autobuilder host to regenerate ~/.ssh/config from all server definition files:

bash servers/create-ssh-config.sh

This will:

  1. Delete and recreate ~/.ssh/config
  2. Write a Host stanza for every server defined in servers/*.sh
  3. Test connectivity to each server and record OK or FAILED as a comment
  4. Restart sshd via systemctl

Example output in ~/.ssh/config:

# example.com on my-server
Host 203.0.113.10
  StrictHostKeyChecking no
  UserKnownHostsFile ~/.ssh/my-key.pem
  User root

Adding a New Deployment

  1. Add a server definition (if the server is new):

    # servers/my-new-server.sh
    domain=example.com
    serverName=my-new-server
    serverIP=203.0.113.10
    user=deploy
    sshKey=~/.ssh/deploy-key.pem
  2. Create an entry-point script in the project root, choosing the appropriate deployer for your stack.

  3. Regenerate SSH config (if the server is new):

    bash servers/create-ssh-config.sh
  4. Run the deployment:

    bash my-new-service-production.sh
    # or with a branch override:
    bash my-new-service-production.sh feature/my-branch
  5. Monitor — logs are written to $TMP_PATH/logs/{script-name}/{timestamp}.log and build notifications are posted to Slack.

About

A non-interactive CI/CD pipeline written entirely in Bash

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages