#Boy Needs Help On Deployment

85 messages · Page 1 of 1 (latest)

scenic plaza
#

I post below.

broken parcelBOT
#

Hi wobby! I'd love to help, but I need more details to give you useful feedback. Right now, the post is missing some key information:

Please add:

  • What are you deploying? (Node.js app, static site, etc.)
  • Where are you deploying to? (Vercel, Heroku, AWS, etc.)
  • What's the specific problem? (error message, unexpected behavior, etc.)
  • What have you already tried?
  • Show your code (formatted with markdown code blocks)

A clearer title like "Deploying Node.js app to Vercel - getting ENOENT error" would also help. Once you provide these details, you'll get much faster and more accurate help!

#

-# Node.js/Bun JavaScript @vale nest @primal crescent @obtuse birch

scenic plaza
#
// pm2 log when i try to do pm2 start ecosyste.config.js
5|babadelu | 18-03-2026 16:39:42: [18-03-2026 16:39:42] Checking staging...
5|babadelu | 18-03-2026 16:39:42: [18-03-2026 16:39:42] Checking production...
5|babadelu | 18-03-2026 16:39:42: [18-03-2026 16:39:42] [staging] deploy failed: spawn /bin/sh ENOENT
5|babadelu | 18-03-2026 16:39:42: [18-03-2026 16:39:42] [production] deploy failed: spawn /bin/sh ENOENT

boy says no
today no deployment

// ./ecosystem.config.js
export const apps = [
  {
    name: 'babadeluxe-backend-auto-deploy',
    script: './dist/scripts/auto-deploy.js',
    interpreter: 'node',
    cwd: '.',
    node_args: '-r dotenv/config',
    args:
      'dotenv_config_path=./.env',
    log_date_format: 'DD-MM-YYYY HH:mm:ss',
  }
]
clever widget
#

So it failed to spawn /bin/sh, I presume that exists on the hosts, so likely;
your user can't access /bin/sh, or your connection is borked
apperently it can also be a config issue:
spawn sh ENOENT with pm2-deploy can occurred if configuration is wrong - github issues

Were you using this setup and it worked before?

scenic plaza
scenic plaza
#

i just wanna make it working

clever widget
#

fair enough

scenic plaza
#

my goal was to have just something checking my repo regularly and trigger my build and distribute when the master or dev changes

primal crescent
#

depending on the distro you could need /usr/bin/sh.. idk where you set that though

scenic plaza
#

its ubuntu

clever widget
#

How is it supossed to look?

-> dev machine -> server -> bare metal?

scenic plaza
#

dev machine -> server -> staging
-> master

clever widget
#

So both are on the same server and not in a container of some sorts?

scenic plaza
#

yes

#

dude, i'm on budget

#

xD

clever widget
#

Just getting the lay of the land

#

Ill be at a machine in like 20 mins

scenic plaza
#

it's really a bit tricky xD

clever widget
#

Eh well get it sortec

clever widget
#

Alright I'm at an actual machine now lemme give it a propper look over

broken parcelBOT
#

-# 🛑 @clever widget, .zip files are potentially dangerous. Your message has been deleted.

clever widget
#

fucks sake

#

fair enough

#

@scenic plaza

So to confirm:

You (the user executing the script on your machine) has the following:

  • ssh access to the server + (allowed to use shell)
    • this user is the same name as your local user name?
  • ssh key on your git provider
#

also I don't think these scripts are supossed to be in typescript

#

do you have ESM modules enabled or nah btw?

scenic plaza
#

yes sure

#

i have esm enabled

clever widget
#
// ecosystem.config.js
module.exports = {
  apps: [
    {
      name: "babadeluxe-backend-auto-deploy",
      script: "./dist/scripts/auto-deploy.js",
      interpreter: "node",
      cwd: ".",
      node_args: "-r dotenv/config",
      args: "dotenv_config_path=./.env",
      log_date_format: "DD-MM-YYYY HH:mm:ss",
      env_production: {
        NODE_ENV: "production",
      },
      env_staging: {
        NODE_ENV: "staging",
      },
    },
  ],

  deploy: {
    staging: {
      user: "deployuser", // ssh user on the server
      host: "staging.example.com", // server address or array for multiple
      ref: "origin/staging", // git ref to deploy
      repo: "git@github.com:org/repo.git",
      path: "/var/www/babadeluxe-staging", // path on the server
      "pre-deploy-local": "", // run on local before ssh (optional)
      "post-deploy":
        "npm ci && npm run build && pm2 reload ecosystem.config.js --env staging",
      ssh_options: "StrictHostKeyChecking=no",
      env: {
        NODE_ENV: "staging",
      },
    },

    production: {
      user: "deployuser",
      host: "prod.example.com",
      ref: "origin/main",
      repo: "git@github.com:org/repo.git",
      path: "/var/www/babadeluxe-prod",
      "post-deploy":
        "npm ci && npm run build && pm2 reload ecosystem.config.js --env production",
      ssh_options: "StrictHostKeyChecking=no",
      env: {
        NODE_ENV: "production",
      },
    },
  },
};

You can also make the users more explicit if you want, or even do CommonJS, TS won't work cause it don't get compiled.

#
dist/scripts/auto-deploy.js
import process from 'node:process'
import { exec } from 'node:child_process'
import path from 'node:path'
 
const _dirname = import.meta.dirname
 
function formatTimestamp(date = new Date()) {
  const year = date.getFullYear()
  const month = String(date.getMonth() + 1).padStart(2, '0')
  const day = String(date.getDate()).padStart(2, '0')
  const hour = String(date.getHours()).padStart(2, '0')
  const minute = String(date.getMinutes()).padStart(2, '0')
  const second = String(date.getSeconds()).padStart(2, '0')
 
  return `${day}-${month}-${year} ${hour}:${minute}:${second}`
}
 
function log(message) {
  console.log(`[${formatTimestamp()}] ${message}`)
}
 
const deployCheckIntervalMs = Number(process.env['DEPLOY_CHECK_INTERVAL_MS']) || 5 * 60 * 1000
 
const babadeluxePath = path.join(_dirname, '..')
console.log('BabaDeluxe Path:', babadeluxePath)
 
const stagingPath = path.join(babadeluxePath, 'api-staging.babadeluxe.com')
const productionPath = path.join(babadeluxePath, 'api.babadeluxe.com')
 
const repos = [
  {
    name: 'staging',
    dir: stagingPath,
    branch: process.env['STAGING_BRANCH'] ?? 'dev',
    env: process.env['STAGING_PM2_ENV'] ?? 'staging',
  },
  {
    name: 'production',
    dir: productionPath,
    branch: process.env['PROD_BRANCH'] ?? 'master',
    env: process.env['PROD_PM2_ENV'] ?? 'production',
  },
]
 
function run(command, cwd) {
  return new Promise((resolve, reject) => {
    exec(command, { cwd }, (error, stdout) => {
      if (error) {
        reject(error)
        return
      }
 
      resolve(stdout.trim())
    })
  })
}
 
async function deployRepo(repo) {
  try {
    log(`Checking ${repo.name}...`)
 
    await run('git fetch origin', repo.dir)
 
    const local = await run('git rev-parse HEAD', repo.dir)
    const remote = await run(`git rev-parse origin/${repo.branch}`, repo.dir)
 
    if (local === remote) {
      log(`[${repo.name}] up to date`)
      return
    }
 
    log(`[${repo.name}] changes detected, deploying...`)
 
    await run(`git pull --ff-only origin ${repo.branch}`, repo.dir)
    await run('npm install --production', repo.dir)
    await run('npm run build', repo.dir)
 
    await run('pm2 reload ecosystem.config.js --update-env', repo.dir)
    await run('pm2 save', repo.dir)
 
    log(`[${repo.name}] deploy done`)
  } catch (error) {
    const message = error instanceof Error ? error.message : JSON.stringify(error, null, 2)
    log(`[${repo.name}] deploy failed: ${message}`)
  }
}
 
async function runDeployCycle() {
  await Promise.all(repos.map(async (repo) => deployRepo(repo)))
}
 
function scheduleDeploy() {
  void runDeployCycle()
 
  setInterval(() => {
    void runDeployCycle()
  }, deployCheckIntervalMs)
}
 
scheduleDeploy()
#
# Bootstrap .env - fill values before running
STAGING_BRANCH=
STAGING_PM2_ENV=
PROD_BRANCH=
PROD_PM2_ENV=
DEPLOY_CHECK_INTERVAL_MS=
#
#README.MD
This folder contains a PM2 ecosystem and the auto-deploy script for BabaDeluxe.

Files created:
- ecosystem.config.js  (CommonJS PM2 config that points at ./dist/scripts/auto-deploy.js)
- dist/scripts/auto-deploy.js  (ESM script provided by the user)
- .env  (bootstrap with empty values)
- package.json

Notes / next steps:
1. Edit .env and set STAGING_BRANCH, PROD_BRANCH and PM2 env names.
2. Ensure the server deploy user has SSH access to the git repo and the repo is cloned into the paths referenced by the script:
   - <repo-root>/api-staging.babadeluxe.com
   - <repo-root>/api.babadeluxe.com
3. Start with PM2 locally for testing:
   - npm ci
   - npm run build (if applicable)
   - pm2 start ecosystem.config.js --env staging
4. On the server, make sure pm2 is installed globally and the deploy user can run pm2. Configure SSH keys and clone the repo into the expected directories.

If you want, I can also generate a deploy stanza for ecosystem.config.js (pm2 deploy) — tell me your git repo URL, server host(s) and deploy user names and I will add it.
#

package .json example

{
  "name": "babadeluxe-deploy-helper",
  "version": "0.0.1",
  "type": "module",
  "scripts": {
    "deploy": "node ./dist/scripts/auto-deploy.js"
  }
}
clever widget
# scenic plaza yes

so if you do ssh your-username@server.ip
you get a shell where you can do stuff?

scenic plaza
#

i usually dont work much with ssh keys

clever widget
#

pm2 does

scenic plaza
#

i only use ssh keys when i set up git deployment for simple one branch set up

#

with plesk's built-in feat

clever widget
#

most things linux remote stuff works via or with ssh

scenic plaza
#

i use the gh cli

clever widget
#

if you'se gonna manage deployments, might want to grok ssh basics. ;p

scenic plaza
#

i did gh auth

#

and set the gh credential helper globally

clever widget
#

I don't use the gh helper/cli, does it set up ssh for you?

scenic plaza
#

thats why i can run the commands for git now without ssh stuff

clever widget
#

okay

scenic plaza
#

no clue xD

#

maybe the generate ssh in the bg

clever widget
#

but how can you execute command on a remote box that isn't github? ;p

scenic plaza
#

i don't rly know

clever widget
#

(which is what pm2 is doing)

#

no gh is just for github

scenic plaza
#

i only have gh right now

clever widget
#

if you do ls ~/.ssh/

scenic plaza
#

i think the access to the repo is not the problem here

#

xD

clever widget
#

is there stuff there?

#

no access to the server you are deploying to is the issue likely

scenic plaza
#

btw i made some progress wanna see

#

nah

#

i have all accesses

clever widget
#

so you can ssh into the server you are deploying to?

scenic plaza
#

yes

#

thats the only ssh i need

clever widget
#

okay

#

hrm

scenic plaza
#

i switched to sh

clever widget
#

so if you can you should have a shell, if it's the same username it should work

scenic plaza
#

ifg?

clever widget
#

typo

scenic plaza
#

ah xD

#

yeah i can show my sh

clever widget
#

ssh* but okay

scenic plaza
#
#!/usr/bin/env bash
set -euo pipefail

cd /var/www/vhosts/babadeluxe.com/babadeluxe-backend

# --- load .env ---
if [ -f ".env" ]; then
  # basic KEY=VALUE, no spaces parser
  export $(grep -v '^#' .env | grep -E '^[A-Za-z_][A-Za-z0-9_]*=' | xargs)
fi

log() {
  local ts
  ts="$(date '+%d-%m-%Y %H:%M:%S')"
  echo "[$ts] $1"
}

# --- config from env (with defaults) ---
STAGING_BRANCH="${STAGING_BRANCH:-dev}"
PROD_BRANCH="${PROD_BRANCH:-master}"
STAGING_PM2_ENV="${STAGING_PM2_ENV:-staging}"
PROD_PM2_ENV="${PROD_PM2_ENV:-production}"

# if you want per-env behavior later, you can branch on $1 (e.g. "staging"|"production")
TARGET="${1:-both}"

deploy_one() {
  local name="$1"
  local branch="$2"
  local pm2_env="$3"

  log "[$name] checking for changes on branch '$branch'..."

  # fail if working tree is dirty to preserve local hacks
  if [ -n "$(git status --porcelain)" ]; then
    log "[$name] deploy aborted: working tree is dirty in $(pwd)"
    return 1
  fi

  git fetch origin

  local local_ref remote_ref
  local_ref="$(git rev-parse HEAD)"
  remote_ref="$(git rev-parse "origin/$branch")"

  if [ "$local_ref" = "$remote_ref" ]; then
    log "[$name] up to date"
    return 0
  fi

  log "[$name] changes detected, deploying..."

  # fast-forward only – no hard reset
  git pull --ff-only origin "$branch"

  npm install --production
  npm run build

  # reload whole ecosystem with correct env
  pm2 reload ecosystem.config.js --env "$pm2_env"
  pm2 save

  log "[$name] deploy done"
}

case "$TARGET" in
  staging)
    deploy_one "staging" "$STAGING_BRANCH" "$STAGING_PM2_ENV"
    ;;
  production)
    deploy_one "production" "$PROD_BRANCH" "$PROD_PM2_ENV"
    ;;
  both|*)
    deploy_one "staging" "$STAGING_BRANCH" "$STAGING_PM2_ENV" || true
    deploy_one "production" "$PROD_BRANCH" "$PROD_PM2_ENV" || true
    ;;
esac
#

it seems to work i think. i hang on another thing now. package-lock.json needs some love.

#

ty for your help 🙂

clever widget
#

bash works too lel

#

anyhow good stuff hope you get to deploy soon