This document describes the internal architecture and module organization of ShipNode.
ShipNode is organized as a modular bash project to improve maintainability, testability, and collaboration. The codebase is split into focused modules, each with a single responsibility.
Total: ~8,350 lines across 26 modules.
shipnode/
├── shipnode # Main entry point (50 lines)
├── lib/
│ ├── core.sh # Core utilities, globals, template rendering (247 lines)
│ ├── pkg-manager.sh # Package manager detection + PM2 template generation (236 lines)
│ ├── release.sh # Release management with metadata tracking (263 lines)
│ ├── database.sh # Database and Redis operations
│ ├── users.sh # User provisioning helpers (352 lines)
│ ├── framework.sh # Framework detection (259 lines)
│ ├── validation.sh # Input validation (287 lines)
│ ├── prompts.sh # Interactive prompts + Gum UI (178 lines)
│ ├── templates.sh # Framework preset configurations (172 lines)
│ └── commands/ # Command implementations
│ ├── config.sh # Configuration loading (110 lines)
│ ├── users-yaml.sh # Users.yml generation (153 lines)
│ ├── user.sh # User management commands (207 lines)
│ ├── mkpasswd.sh # Password generation (36 lines)
│ ├── init.sh # Initialize command (2007 lines)
│ ├── setup.sh # Setup command (108 lines)
│ ├── deploy.sh # Deploy command (819 lines)
│ ├── doctor.sh # Diagnostics command (603 lines)
│ ├── status.sh # Rich status dashboard (336 lines)
│ ├── unlock.sh # Unlock command (40 lines)
│ ├── rollback.sh # Rollback command (86 lines)
│ ├── migrate.sh # Migrate command (92 lines)
│ ├── env.sh # Environment upload (42 lines)
│ ├── eject.sh # Eject PM2/Caddy templates (266 lines)
│ ├── metrics.sh # PM2 resource monitoring (12 lines)
│ ├── config-cmd.sh # Config show/validate/path (137 lines)
│ ├── upgrade.sh # Upgrade command (104 lines)
│ ├── ci.sh # CI/CD commands (362 lines)
│ ├── harden.sh # Security hardening (476 lines)
│ ├── help.sh # Help command (129 lines)
│ └── main.sh # Main dispatcher (143 lines)
├── templates/ # Ejectable template files
│ ├── ecosystem.config.cjs.tmpl # PM2 config template
│ ├── Caddyfile.backend.tmpl # Backend Caddy template
│ ├── Caddyfile.frontend.tmpl # Frontend Caddy template
│ ├── pre-deploy.sh.template # Pre-deploy hook template
│ ├── post-deploy.sh.template # Post-deploy hook template
│ └── shipnodeignore.template # .shipnodeignore template
├── build.sh # Build script for distribution (81 lines)
└── examples/ # Example projects
Modules are loaded in a specific order to ensure dependencies are available:
- core.sh - No dependencies, provides globals, logging, template rendering
- pkg-manager.sh - Depends on core.sh
- release.sh - Depends on core.sh
- database.sh - Depends on core.sh
- users.sh - Depends on core.sh
- framework.sh - Depends on core.sh
- validation.sh - Depends on core.sh
- prompts.sh - Depends on core.sh
- templates.sh - Depends on core.sh
- commands/config.sh - Depends on core.sh
- commands/users-yaml.sh - Depends on core.sh, validation.sh
- commands/user.sh - Depends on core.sh, users.sh, validation.sh
- commands/mkpasswd.sh - Depends on core.sh
- commands/init.sh - Depends on core.sh, framework.sh, validation.sh, prompts.sh
- commands/setup.sh - Depends on core.sh, release.sh, database.sh
- commands/deploy.sh - Depends on core.sh, release.sh, pkg-manager.sh
- commands/doctor.sh - Depends on core.sh
- commands/status.sh - Depends on core.sh
- commands/unlock.sh - Depends on core.sh, release.sh
- commands/rollback.sh - Depends on core.sh, release.sh
- commands/migrate.sh - Depends on core.sh, release.sh
- commands/env.sh - Depends on core.sh
- commands/eject.sh - Depends on core.sh
- commands/metrics.sh - Depends on core.sh
- commands/config-cmd.sh - Depends on core.sh
- commands/upgrade.sh - Depends on core.sh
- commands/ci.sh - Depends on core.sh
- commands/harden.sh - Depends on core.sh
- commands/help.sh - Depends on core.sh
- commands/main.sh - Depends on all other modules
Purpose: Global variables, colors, logging functions, OS detection, Gum installation, template rendering
Key Functions:
error(),success(),info(),warn()- Logging functionshas_gum()- Check if Gum is installeddetect_os()- Detect OS and package managerinstall_gum()- Install Gum UI frameworkrender_template()- Replace{{VAR}}placeholders in template files using sedresolve_template()- Find user template (ejected or project-root) before falling back to built-in
Globals:
RED,GREEN,YELLOW,BLUE,NC- Color codesVERSION- ShipNode versionUSE_GUM- Enhanced UI flag
Purpose: Zero-downtime deployment release management with deployment metadata tracking
Key Functions:
generate_release_timestamp()- Create unique release IDget_release_path()- Get path for a releasesetup_release_structure()- Create release directoriesacquire_deploy_lock()- Prevent concurrent deploymentsrelease_deploy_lock()- Release deployment lockswitch_symlink()- Atomic symlink switchingperform_health_check()- Validate deployment health with timing metricsrecord_release()- Track release history with duration, commit, health check dataget_previous_release()- Find previous releasecleanup_old_releases()- Remove old releasesrollback_to_release()- Rollback to specific release
Purpose: Database setup and management operations
Key Functions:
setup_databases()- Dispatch configured database and Redis setupsetup_postgresql()- Install and configure PostgreSQLsetup_mysql()- Install and configure MySQLsetup_sqlite()- Install SQLite and create database filesetup_redis()- Install and configure Redis
Purpose: User provisioning helper functions
Key Functions:
validate_username()- Validate username formatvalidate_password_hash()- Validate password hashvalidate_ssh_key()- Validate SSH key formatprompt_yes_no()- Yes/no prompt with defaultgenerate_password_hash()- Create password hashvalidate_email()- Validate email formatread_key_file()- Read SSH key from file
Purpose: Framework detection from package.json
Key Functions:
parse_package_json()- Extract dependencies from package.jsonsuggest_app_type()- Determine backend vs frontenddetect_framework()- Identify framework from dependenciessuggest_port()- Auto-detect port from scripts
Supported Frameworks:
- Backend: Express, NestJS, Fastify, Koa, Hono, AdonisJS
- Full-stack: Next.js, Nuxt, Remix, Astro
- Frontend: React, Vue, Svelte, SolidJS, Angular
Purpose: Input validation functions
Key Functions:
validate_ip_or_hostname()- Validate IP or hostnamevalidate_port()- Validate port number (1-65535)validate_domain()- Validate domain namevalidate_pm2_app_name()- Validate PM2 process nametest_ssh_connection()- Test SSH connectivityparse_users_yaml()- Parse users.yml file
Purpose: Interactive prompts with Gum UI support
Key Functions:
prompt_with_default()- Prompt with default valueprompt_with_validation()- Prompt with validation loopgum_input()- Enhanced input with Gum fallbackgum_choose()- Enhanced selection with Gum fallbackgum_confirm()- Enhanced confirmation with Gum fallbackgum_style()- Enhanced styling with Gum fallbackshow_gum_tip()- Show Gum installation tip
Purpose: Framework preset configurations and template management
Key Functions:
load_template()- Load framework preset templatesget_framework_config()- Get preset configuration for framework
Purpose: Configuration file loading
Key Functions:
load_config()- Load and validate shipnode.conf
Purpose: Interactive users.yml generation
Key Functions:
init_users_yaml()- Generate users.yml interactively
Purpose: User management commands
Key Functions:
cmd_user_sync()- Sync users from users.yml to servercmd_user_list()- List provisioned userscmd_user_remove()- Remove user access
Purpose: Password hash generation
Key Functions:
cmd_mkpasswd()- Generate password hash for users.yml
Purpose: Initialize project configuration (largest module)
Key Functions:
cmd_init()- Main init command routercmd_init_interactive()- Interactive wizarddetect_framework()- Detect project frameworkgenerate_config()- Generate shipnode.conf
Purpose: First-time server setup
Key Functions:
cmd_setup()- Setup server (Node, PM2, Caddy, jq)
Purpose: Deploy applications with template-aware PM2 and Caddy config generation
Key Functions:
cmd_deploy()- Main deploy commandcmd_deploy_dry_run()- Preview deployment without executingdeploy_backend()- Deploy backend applicationdeploy_backend_zero_downtime()- Zero-downtime backend deploy with duration/commit trackingdeploy_frontend()- Deploy frontend applicationdeploy_frontend_zero_downtime()- Zero-downtime frontend deployconfigure_caddy_backend()- Configure Caddy for backendconfigure_caddy_frontend()- Configure Caddy for frontend
Purpose: Application status dashboard with rich output
Key Functions:
cmd_status()- Check application statuscmd_status_backend()- Rich PM2 dashboard: status, uptime, CPU, memory, releases, diskcmd_status_frontend()- Frontend status: file count, size, release, Caddy statuscmd_logs()- View application logscmd_restart()- Restart applicationcmd_stop()- Stop application
Purpose: Clear deployment lock
Key Functions:
cmd_unlock()- Clear stuck deployment lock
Purpose: Eject PM2/Caddy config templates for user customization
Key Functions:
cmd_eject()- Eject templates (pm2, caddy, or all)eject_pm2()- Copy PM2 ecosystem template to.shipnode/templates/eject_caddy()- Copy Caddy template to.shipnode/templates/
Purpose: Real-time PM2 resource monitoring
Key Functions:
cmd_metrics()- Open PM2 monit dashboard over SSH
Purpose: Config inspection and validation
Key Functions:
cmd_config()- Route config subcommands (show, validate, path)cmd_config_show()- Display resolved config valuescmd_config_validate()- Validate config file without deploying
Purpose: Rollback to previous releases
Key Functions:
cmd_rollback()- Rollback to previous releasecmd_releases()- List available releases
Purpose: Migrate existing deployments
Key Functions:
cmd_migrate()- Migrate to release structure
Purpose: Environment variable management
Key Functions:
cmd_env()- Upload .env file to server
Purpose: CI/CD integration commands
Key Functions:
cmd_ci()- Route CI subcommandscmd_ci_github()- Generate GitHub Actions workflowcmd_ci_env_sync()- Sync secrets to GitHub
Purpose: Server security hardening
Key Functions:
cmd_harden()- Interactive security hardening wizardharden_ssh()- SSH hardening optionsharden_firewall()- UFW firewall setupharden_fail2ban()- Install and configure fail2ban
Purpose: Diagnostics and pre-flight checks
Key Functions:
cmd_doctor()- Run diagnostic checkscheck_ssh()- Verify SSH connectivitycheck_dependencies()- Check server dependenciescheck_disk_space()- Verify sufficient disk space
Purpose: Display help information
Key Functions:
cmd_help()- Show help message
Purpose: Main entry point and command dispatcher
Key Functions:
main()- Parse arguments and dispatch to commands
Purpose: Self-upgrade ShipNode
Key Functions:
cmd_upgrade()- Upgrade ShipNode to latest version
Templates are located in templates/ and used for ejection or direct deployment:
| File | Purpose |
|---|---|
ecosystem.config.cjs.tmpl |
PM2 process manager configuration |
Caddyfile.backend.tmpl |
Caddy reverse proxy for backend apps |
Caddyfile.frontend.tmpl |
Caddy static file server for frontends |
pre-deploy.sh.template |
Pre-deploy hook script |
post-deploy.sh.template |
Post-deploy hook script |
shipnodeignore.template |
Default exclusions for rsync |
To add a new command:
- Create a new file in
lib/commands/ - Define a function
cmd_<command_name>() - Add the case to
main()incommands/main.sh - Update
commands/help.shwith usage info - Update README.md with documentation
Example:
# lib/commands/mycommand.sh
cmd_mycommand() {
load_config
# Command implementation
}
# commands/main.sh
case "${1:-}" in
mycommand)
cmd_mycommand "$@"
;;
esac
# commands/help.sh
echo " mycommand Description of mycommand"Since modules are sourced independently, you can test them in isolation:
# Test validation module
source lib/core.sh
source lib/validation.sh
# Test functions
validate_port "3000" && echo "Valid port"
validate_port "70000" && echo "Invalid port"To create a single-file distribution:
./build.shThis concatenates all modules into shipnode-bundled in the correct order.
- Single Responsibility - Each module should do one thing well
- Minimal Dependencies - Keep module dependencies shallow
- No Side Effects - Modules should only define functions, not execute code
- Consistent Naming - Use
cmd_<name>()for commands, descriptive names for helpers - Documentation - Add comments for complex functions
- Error Handling - Use
error()for fatal errors,warn()for warnings
Potential areas for modular expansion:
- plugins/ - Plugin system for third-party extensions
- hooks/ - Pre/post deploy hooks with chaining and local hooks
- tests/ - Unit tests for individual modules
- docs/ - Generated API documentation
- Observability - Deployment notification webhooks (Slack, email), uptime monitoring
- Multi-app - Deploy multiple apps from a single shipnode.conf