diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..638900e --- /dev/null +++ b/.dockerignore @@ -0,0 +1,37 @@ +# Git files +.git +.gitignore + +# Documentation +README.md +CLAUDE.md + +# Local development files +user/ +instance_* +local-dev.sh + +# Logs +*.log +logs/ + +# Temporary files +tmp/ +temp/ +.tmp + +# IDE files +.vscode/ +.idea/ +*.swp +*.swo + +# OS files +.DS_Store +Thumbs.db + +# Node.js +node_modules/ +npm-debug.log* +yarn-debug.log* +yarn-error.log* \ No newline at end of file diff --git a/.gitea/workflows/build-push.yaml b/.gitea/workflows/build-push.yaml new file mode 100644 index 0000000..3aa9b3c --- /dev/null +++ b/.gitea/workflows/build-push.yaml @@ -0,0 +1,40 @@ +name: Cloud Node Container +run-name: ${{ gitea.actor }} pushed a change to main +on: + push: + branches: + - main + +jobs: + Build-and-Push: + runs-on: ubuntu-latest + strategy: + matrix: + nodever: [18, 20, 22] + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Set up QEMU + uses: docker/setup-qemu-action@v3 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Login to Gitea + uses: docker/login-action@v3 + with: + registry: repo.anhonesthost.net + username: ${{ secrets.CI_USER }} + password: ${{ secrets.CI_TOKEN }} + + - name: Build and Push Image + uses: docker/build-push-action@v6 + with: + platforms: linux/amd64 + push: true + build-args: | + NODEVER=${{ matrix.nodever }} + tags: | + repo.anhonesthost.net/cloud-hosting-platform/cnc:node${{ matrix.nodever }} + ${{ matrix.nodever == '20' && 'repo.anhonesthost.net/cloud-hosting-platform/cnc:latest' || '' }} \ No newline at end of file diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..3dadc91 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,96 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Project Overview + +Cloud Node Container (CNC) is a Docker-based Node.js hosting environment that supports multiple Node.js versions (18, 20, 22) with Nginx reverse proxy, designed for both local development and production deployment. + +## Common Development Commands + +### Local Development Setup +```bash +# Quick start with automated setup (creates helper scripts and default app) +./local-dev.sh -n local-dev + +# With specific Node.js version +./local-dev.sh -n myproject -a 22 # Node.js 22 + +# Helper scripts created by local-dev.sh: +./instance_start # Start container +./instance_stop # Stop container +./instance_logs # View container logs +./instance_shell # Access container shell +``` + +### Building and Testing +```bash +# Build container locally +docker build -t cloud-node-container:latest . + +# Build with specific Node.js version +docker build --build-arg NODEVER=22 -t cnc:node22 . + +# Run container manually +docker run -d -p 80:80 -p 443:443 \ + -e NODEVER=20 -e environment=DEV \ + -e uid=$(id -u) -e user=$(whoami) -e domain=localhost \ + -v"$(pwd)/user":/home/$(whoami) \ + --name test-container cloud-node-container:latest +``` + +### Server Deployment +- Production git directory: `/root/whp` +- After `git pull`, sync web files: `rsync -av web-files/ /docker/whp/web/` + +## Architecture and Key Components + +### Directory Structure +- `/scripts/` - Container setup scripts (entrypoint, Node.js installers, nginx config) +- `/configs/` - Nginx, PM2, and default application configurations +- User applications go in `/home/$user/app/` + +### Container Behavior +1. **Entrypoint Flow** (`scripts/entrypoint.sh`): + - Creates user with specified UID + - Sets up directory structure (/home/$user/app/, logs/) + - Configures Nginx reverse proxy based on environment variables + - In DEV mode: starts Memcached for session storage + - Starts Nginx and PM2-managed Node.js application + +2. **Environment Modes**: + - **DEV** (`environment=DEV`): Local Memcached, automatic backups, verbose logging + - **PROD** (default): Expects external services, optimized for production + +3. **Node.js Version Management**: + - Controlled via `NODEVER` environment variable (18, 20, 22) + - Each version has dedicated install script in `/scripts/` + - PM2 ecosystem configuration adapts to user paths + +### Key Environment Variables +- `uid` (required): User ID for file permissions +- `user` (required): Username for container user +- `domain` (required): Primary domain for Nginx configuration +- `serveralias`: Additional domains (comma-separated) +- `NODEVER`: Node.js version to use (default: 20) +- `environment`: DEV or PROD mode + +## Important Technical Details + +1. **Health Check**: Available at `/ping` endpoint (Node.js app must implement) +2. **Logs Location**: `/home/$user/logs/nginx/` and `/home/$user/logs/nodejs/` +3. **Application Backups** (DEV mode): Every 30 minutes to `/home/$user/_backups/` +4. **Log Rotation**: Compress after 3 days, delete after 7 days +5. **SSL**: Self-signed certificate auto-generated, Nginx handles SSL termination +6. **Static Files**: Nginx serves from `/home/$user/app/public/` at `/static/` path +7. **Process Management**: PM2 handles Node.js application lifecycle + +## Application Requirements + +User applications in `/home/$user/app/` should include: +- `package.json` with dependencies and scripts +- Main application file (default: `index.js`) +- Optional: `ecosystem.config.js` for PM2 configuration +- Optional: `public/` directory for static assets + +The container provides a default Express.js application with health endpoints and Memcached session support if no user application is found. \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..bbd853d --- /dev/null +++ b/Dockerfile @@ -0,0 +1,41 @@ +FROM almalinux/9-base +ARG NODEVER=20 + +# Install repos, update, install only needed packages, clean up in one layer +RUN dnf install -y \ + https://dl.fedoraproject.org/pub/epel/epel-release-latest-9.noarch.rpm && \ + dnf update -y && \ + dnf install -y wget procps cronie iproute nginx openssl curl && \ + dnf clean all && \ + rm -rf /var/cache/dnf /usr/share/doc /usr/share/man /usr/share/locale/* \ + /var/cache/yum /tmp/* /var/tmp/* + +# Copy scripts into the image and set permissions +COPY ./scripts/ /scripts/ +RUN chmod +x /scripts/* + +# Generate self-signed cert, create needed dirs, install Node.js, clean up +RUN openssl req -newkey rsa:2048 -nodes -keyout /etc/pki/tls/private/localhost.key -x509 -days 3650 -subj "/CN=localhost" -out /etc/pki/tls/certs/localhost.crt && \ + mkdir -p /var/log/nodejs && \ + /scripts/install-node$NODEVER.sh && \ + rm -rf /tmp/* + +# Install PM2 globally for process management with minimal footprint +RUN npm install -g pm2@latest --production && \ + npm cache clean --force && \ + rm -rf /tmp/* + +# Copy configs and web files +COPY ./configs/nginx.conf /etc/nginx/nginx.conf +COPY ./configs/default.conf /etc/nginx/conf.d/default.conf +COPY ./configs/index.js /var/www/html/ +COPY ./configs/package.json /var/www/html/ +COPY ./configs/ecosystem.config.js /var/www/html/ + +# Set up cron job for log rotation +RUN echo "15 */12 * * * root /scripts/log-rotate.sh" >> /etc/crontab + +HEALTHCHECK --interval=30s --timeout=5s --start-period=60s --retries=3 \ + CMD curl -f http://localhost:3000/ping || exit 1 + +ENTRYPOINT [ "/scripts/entrypoint.sh" ] \ No newline at end of file diff --git a/MEMORY-GUIDE.md b/MEMORY-GUIDE.md new file mode 100644 index 0000000..dc4df44 --- /dev/null +++ b/MEMORY-GUIDE.md @@ -0,0 +1,229 @@ +# Memory Usage Guide - Cloud Node Container + +## Memory Requirements & Recommendations + +### Minimum RAM Requirements + +| Configuration | Minimum RAM | Recommended RAM | Use Case | +|---------------|-------------|-----------------|----------| +| **Production (Basic)** | 256MB | 512MB | Simple websites, APIs with <100 concurrent users | +| **Production (Standard)** | 512MB | 1GB | Standard web applications, APIs with moderate traffic | +| **Development** | 512MB | 1GB | Local development with all services (Memcached, logging) | +| **High Traffic** | 1GB | 2GB+ | Applications with >500 concurrent users or heavy processing | + +### Memory Breakdown (Optimized Configuration) + +| Component | Memory Usage | Notes | +|-----------|--------------|-------| +| **Base AlmaLinux 9** | ~80-120MB | Minimal base system | +| **Node.js Runtime** | ~30-50MB | V8 JavaScript engine | +| **PM2 Process Manager** | ~15-25MB | Process monitoring and management | +| **Nginx (Optimized)** | ~8-15MB | Single worker, limited buffers | +| **User Application** | ~50-200MB | Depends on application complexity | +| **Memcached (DEV mode)** | ~10-15MB | 32MB memory limit, actual usage varies | +| **System Overhead** | ~30-50MB | Logging, cron, system processes | + +**Total Typical Usage:** 220-480MB + +## Memory Optimizations Applied + +### Container-Level Optimizations + +1. **Node.js Heap Limit**: Set to 200MB via `--max-old-space-size=200` +2. **PM2 Memory Restart**: Applications restart at 256MB usage +3. **Memcached Limit**: 32MB maximum cache size +4. **Nginx Workers**: Single worker process for memory efficiency +5. **Buffer Limits**: Reduced client buffer sizes +6. **Log Buffering**: 2-minute flush intervals to reduce I/O + +### Application-Level Recommendations + +```javascript +// Memory-efficient application practices +const express = require('express'); +const app = express(); + +// Use compression to reduce memory for responses +app.use(require('compression')()); + +// Limit request size +app.use(express.json({ limit: '1mb' })); +app.use(express.urlencoded({ limit: '1mb', extended: true })); + +// Stream large files instead of loading into memory +const fs = require('fs'); +app.get('/large-file', (req, res) => { + const stream = fs.createReadStream('large-file.pdf'); + stream.pipe(res); +}); +``` + +## Memory Monitoring + +### Built-in Monitoring Scripts + +```bash +# Check current memory usage +docker exec container-name /scripts/memory-info.sh + +# Monitor PM2 processes +docker exec container-name pm2 monit + +# View memory usage over time +docker stats container-name +``` + +### Memory Alerts & Automatic Restarts + +The container includes automatic memory management: + +- **PM2 Restart**: Apps restart if they exceed 256MB +- **Health Checks**: Container monitors `/ping` endpoint +- **Process Recovery**: Failed processes automatically restart +- **Memory Leak Protection**: Regular restarts prevent memory buildup + +## Scaling Guidelines + +### When to Scale UP (More RAM): + +- PM2 shows frequent memory restarts +- Response times increase significantly +- Multiple applications in one container +- Heavy data processing or file handling +- High concurrent user count (>200 simultaneous) + +### When to Scale OUT (More Containers): + +- CPU usage consistently >80% +- Memory usage consistently >80% of allocation +- Need for geographic distribution +- Fault tolerance requirements + +### Memory-Efficient Application Patterns + +**Good Practices:** +```javascript +// Stream processing instead of loading entire files +const stream = require('stream'); + +// Use connection pooling for databases +const mysql = require('mysql2'); +const pool = mysql.createPool({ + host: 'localhost', + user: 'user', + password: 'password', + database: 'db', + connectionLimit: 5, // Limit connections + acquireTimeout: 60000, + timeout: 60000 +}); + +// Clean up intervals and timeouts +const cleanup = () => { + clearInterval(myInterval); + clearTimeout(myTimeout); +}; + +process.on('SIGTERM', cleanup); +process.on('SIGINT', cleanup); +``` + +**Avoid These Memory Patterns:** +```javascript +// DON'T: Store large amounts in memory +let globalCache = {}; // Can grow unbounded + +// DON'T: Create memory leaks with closures +setInterval(() => { + const largeData = new Array(1000000).fill('data'); + // largeData never gets garbage collected +}, 1000); + +// DON'T: Load entire files into memory +const fs = require('fs'); +const hugeFile = fs.readFileSync('huge-file.txt'); // Use streams instead +``` + +## Container Resource Limits + +### Setting Memory Limits + +```bash +# Limit container memory usage +docker run --memory=512m --memory-swap=512m your-container + +# Or in docker-compose.yml +services: + node-app: + mem_limit: 512m + memswap_limit: 512m +``` + +### Monitoring Memory Usage + +```bash +# Real-time memory stats +docker exec container-name cat /proc/meminfo + +# Process-specific memory +docker exec container-name ps aux --sort=-%mem + +# Application memory usage +docker exec container-name pm2 show app-name +``` + +## Troubleshooting Memory Issues + +### Common Memory Problems + +1. **Container OOM (Out of Memory)** + - **Symptom**: Container suddenly stops + - **Solution**: Increase container memory or optimize application + +2. **Application Memory Leaks** + - **Symptom**: Memory usage steadily increases + - **Solution**: Use PM2 memory restart feature, fix code leaks + +3. **Slow Performance** + - **Symptom**: High response times, high memory usage + - **Solution**: Enable compression, optimize database queries + +### Memory Debugging Tools + +```bash +# Inside container - check memory usage +free -h +cat /proc/meminfo +ps aux --sort=-%mem + +# PM2 memory monitoring +pm2 monit +pm2 show app-name + +# Node.js heap snapshots (development) +# Add to your app: +const v8 = require('v8'); +const fs = require('fs'); +const heapSnapshot = v8.writeHeapSnapshot(); +console.log('Heap snapshot written to', heapSnapshot); +``` + +## Production Memory Recommendations + +### Small Applications (< 50 concurrent users) +- **Container RAM**: 512MB +- **Node.js Heap**: 200MB (default) +- **Expected Usage**: 250-350MB + +### Medium Applications (50-200 concurrent users) +- **Container RAM**: 1GB +- **Node.js Heap**: 400MB (`--max-old-space-size=400`) +- **Expected Usage**: 500-700MB + +### Large Applications (200+ concurrent users) +- **Container RAM**: 2GB+ +- **Node.js Heap**: 800MB+ (`--max-old-space-size=800`) +- **Expected Usage**: 1-1.5GB +- **Consider**: Multiple container instances with load balancing + +The container is optimized for efficient memory usage while maintaining good performance and reliability. \ No newline at end of file diff --git a/README.md b/README.md index deecbcc..21536e8 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,246 @@ -# cloud-node-container +# Cloud Node Container +This is a base container for running Node.js applications, supporting multiple Node.js versions (18, 20, 22). The default is Node.js 20. The container is based on AlmaLinux 9 and uses Nginx as a reverse proxy with SSL. It is designed for both development and production use. + +**You must have Docker or compatible containerization software running.** + +--- + +## What's Included? + +- **Multiple Node.js Versions:** 18, 20, 22 (set with `NODEVER` or `-a` flag) +- **PM2 Process Manager:** For production-grade Node.js application management +- **Nginx Reverse Proxy:** SSL-enabled reverse proxy with automatic HTTP to HTTPS redirect +- **Development Features:** Memcached for sessions, automatic backups, log rotation +- **Health Monitoring:** Built-in `/ping` endpoint for health checks + +--- + +## Quick Start: Local Development with `local-dev.sh` + +The easiest way to start a local development environment is with the provided `local-dev.sh` script. This script automates container setup, volume creation, log directories, and creates a sample Node.js application. + +### Usage Example + +```bash +./local-dev.sh -n local-dev +``` + +**Flags:** +- `-n` Name of the container (required) +- `-p` HTTP port (default: 80) +- `-s` HTTPS port (default: 443) +- `-r` Root path for files (default: current directory) +- `-a` Node.js version (default: 20; options: 18, 20, 22) +- `-v` Enable verbose mode +- `-h` Show help + +The script will: +- Create a user directory and log folders +- Create a default Node.js Express application if none exists +- Start the container with the correct environment variables +- Generate helper scripts in your root path: + - `instance_start` – Start the container + - `instance_stop` – Stop the container + - `instance_logs` – View container logs + - `instance_shell` – Access container shell + +--- + +## Manual Docker Usage + +You can also run the container manually: + +```bash +mkdir -p local-development/domain.tld +cd local-development/domain.tld +mkdir user +mkdir -p user/{app,logs/{nginx,nodejs}} +docker run -d -p 80:80 -p 443:443 -e NODEVER=20 -e environment=DEV --mount type=bind,source="$(pwd)"/user,target=/home/$(whoami) -e uid=$(id -u) -e user=$(whoami) -e domain=localhost --name local-dev cloud-node-container:latest +``` + +--- + +## Accessing the Container + +```bash +docker exec -it local-dev /bin/bash +``` + +--- + +## For Users: How to Deploy Your Application + +### Step 1: Prepare Your Node.js Application + +Your Node.js application needs just two files to get started: + +**Required Files:** +- `package.json` - Describes your app and its dependencies +- A main JavaScript file (usually `server.js` or `index.js`) + +**Optional Files:** +- `public/` folder for static files (HTML, CSS, images) +- `ecosystem.config.js` for advanced PM2 configuration + +### Step 2: What Users Need to Do + +**If you're not familiar with Node.js, here's what you need to know:** + +1. **Create a `package.json` file** - This tells Node.js about your app: +```json +{ + "name": "my-website", + "version": "1.0.0", + "main": "server.js", + "scripts": { + "start": "node server.js" + }, + "dependencies": { + "express": "^4.18.2" + } +} +``` + +2. **Create your main server file** - This runs your application: +```javascript +const express = require('express'); +const app = express(); +const port = process.env.PORT || 3000; + +app.use(express.static('public')); // Serve static files + +app.get('/', (req, res) => { + res.send('

My Website is Running!

'); +}); + +app.get('/ping', (req, res) => { + res.json({ status: 'ok' }); // Health check for the container +}); + +app.listen(port, () => { + console.log(`Server running on port ${port}`); +}); +``` + +3. **Put your files in the right place**: + - Copy your `package.json` and `server.js` to `user/app/` + - Put HTML, CSS, images in `user/app/public/` + +4. **Start the container**: +```bash +./local-dev.sh -n my-app +``` + +**That's it!** The container will: +- Install your dependencies automatically +- Start your application with PM2 +- Handle SSL and reverse proxy +- Provide health monitoring + +### Step 3: Example Applications + +See the `examples/` directory for complete working examples: + +**Simple Website** (`examples/simple-website/`): +- Static HTML pages with navigation +- CSS styling +- Express.js server for routing + +**API Server** (`examples/api-server/`): +- REST API with CRUD operations +- JSON responses +- In-memory data storage + +### What Happens Automatically + +When you place your app in `user/app/`, the container automatically: +1. Runs `npm install` to install dependencies +2. Starts your app with PM2 process manager +3. Sets up Nginx reverse proxy with SSL +4. Configures logging and health checks +5. Starts Memcached for session storage (in DEV mode) + +### Troubleshooting for New Users + +- **App won't start?** Check that your `package.json` has a valid "start" script +- **Can't access your site?** Make sure your app listens on port 3000 and has a `/ping` endpoint +- **Dependencies missing?** List all required packages in your `package.json` dependencies section +- **Static files not loading?** Put them in a `public/` folder and use `app.use(express.static('public'))` + +--- + +## Features + +- **Multiple Node.js Versions:** 18, 20, 22 (set with `NODEVER` environment variable) +- **Process Management:** PM2 for production-grade Node.js application management +- **Reverse Proxy:** Nginx handles SSL termination and proxies requests to Node.js +- **Automatic Backups:** Application files backed up every 30 minutes in DEV mode +- **Log Management:** Log rotation compresses logs older than 3 days, deletes after 7 days +- **Session Storage:** Memcached available in DEV mode for session management +- **SSL:** Self-signed certificate enabled by default with automatic HTTP→HTTPS redirect +- **Health Checks:** `/ping`, `/info` endpoints for monitoring +- **Static Files:** Nginx serves static files from `/home/$user/app/public/` + +--- + +## Environment Variables + +**Required:** +- `uid` – User ID for file permissions +- `user` – Username for file permissions +- `domain` – Primary domain for configuration + +**Optional:** +- `environment` – Set to `DEV` to start Redis and enable development features +- `serveralias` – Comma-separated list of alternative hostnames +- `NODEVER` – Node.js version (18, 20, 22) + +--- + +## Default Application Endpoints + +The default Express.js application provides: +- `/` – Basic application info and status +- `/ping` – Health check endpoint (JSON response) +- `/info` – Detailed system information +- `/session` – Session demo endpoint (shows Memcached sessions working) + +--- + +## Helpful Notes + +- To restart the instance: `./instance_start` or `docker start {container-name}` +- To stop: `./instance_stop` or `docker stop {container-name}` +- To view logs: `./instance_logs` or `docker logs -f {container-name}` +- To access shell: `./instance_shell` or `docker exec -it {container-name} /bin/bash` +- To delete a container: `docker rm {container-name}` (does not delete user files) +- Application logs are in `/home/$user/logs/nodejs/` +- Nginx logs are in `/home/$user/logs/nginx/` + +--- + +## System Requirements + +### Memory Requirements +- **Minimum RAM**: 256MB (basic applications) +- **Recommended RAM**: 512MB (standard applications) +- **Development**: 512MB-1GB (with all services enabled) + +The container is optimized for memory efficiency with automatic memory management and process restarts. See `MEMORY-GUIDE.md` for detailed memory optimization information. + +### Performance Features +- Automatic application restart at 256MB memory usage +- V8 heap limited to 200MB by default +- Nginx optimized for single-worker, low-memory operation +- Memcached limited to 32MB cache size + +## Troubleshooting + +- The first run may take a few minutes as Node.js and dependencies are installed +- If you need to change Node.js version, stop and remove the container, then recreate with the desired version +- For custom applications, ensure your `package.json` has a valid start script +- Check `/home/$user/logs/nodejs/error.log` for application errors +- The health check endpoint `/ping` should return a 200 status when the application is running properly +- **Memory issues**: Run `/scripts/memory-info.sh` inside container to check memory usage +- **Process monitoring**: Use `pm2 monit` to watch application performance \ No newline at end of file diff --git a/USER-GUIDE.md b/USER-GUIDE.md new file mode 100644 index 0000000..0996819 --- /dev/null +++ b/USER-GUIDE.md @@ -0,0 +1,275 @@ +# User Guide: Deploying Applications with Cloud Node Container + +## Quick Start (For Non-Node.js Users) + +If you've never used Node.js before, don't worry! This container makes it simple. + +### What You Need to Know + +**Node.js** is like PHP, but for JavaScript. Instead of Apache serving PHP files, your JavaScript code runs as a server application. + +**Express.js** is like a web framework (similar to Laravel for PHP) that makes it easy to handle web requests. + +### Basic Workflow Comparison + +| PHP/Apache Workflow | Node.js Container Workflow | +|---------------------|---------------------------| +| Put `.php` files in web directory | Put `.js` files in `user/app/` | +| Apache serves files directly | Express.js handles requests | +| Database connection in each file | Server runs continuously | +| `` | `res.send("Hello")` | + +## Step-by-Step: Deploy Your First App + +### 1. Create Your Application Files + +Create these two files in your `user/app/` directory: + +**File: `package.json`** +```json +{ + "name": "my-first-app", + "version": "1.0.0", + "main": "server.js", + "scripts": { + "start": "node server.js" + }, + "dependencies": { + "express": "^4.18.2" + } +} +``` + +**File: `server.js`** +```javascript +const express = require('express'); +const app = express(); +const port = process.env.PORT || 3000; + +// Serve static files (HTML, CSS, images) from 'public' folder +app.use(express.static('public')); + +// Route for home page +app.get('/', (req, res) => { + res.send(` +

Welcome to My Website!

+

This is running on Node.js

+ About Page + `); +}); + +// Route for about page +app.get('/about', (req, res) => { + res.send(` +

About Us

+

This website is powered by Node.js and Express

+ Back to Home + `); +}); + +// Health check (required by container) +app.get('/ping', (req, res) => { + res.json({ status: 'ok', timestamp: new Date() }); +}); + +// Start the server +app.listen(port, () => { + console.log(`Server running on port ${port}`); +}); +``` + +### 2. Start Your Container + +```bash +./local-dev.sh -n my-first-app +``` + +### 3. Access Your Site + +Visit `https://localhost` (accept the SSL warning) and you'll see your website! + +## Common Use Cases + +### Static Website (Like HTML/CSS sites) + +Put your HTML files in `user/app/public/` and use this simple server: + +```javascript +const express = require('express'); +const app = express(); + +app.use(express.static('public')); +app.get('/ping', (req, res) => res.json({ status: 'ok' })); + +app.listen(3000, () => console.log('Static site running')); +``` + +### Form Processing (Like PHP forms) + +```javascript +const express = require('express'); +const app = express(); + +app.use(express.urlencoded({ extended: true })); // Process form data +app.use(express.static('public')); + +// Show contact form +app.get('/contact', (req, res) => { + res.send(` +
+ + + + +
+ `); +}); + +// Process form submission +app.post('/contact', (req, res) => { + const { name, email, message } = req.body; + + // Here you would save to database, send email, etc. + console.log('Contact form:', { name, email, message }); + + res.send(`

Thanks ${name}!

We received your message.

`); +}); + +app.get('/ping', (req, res) => res.json({ status: 'ok' })); +app.listen(3000); +``` + +### Database Connection (Like MySQL in PHP) + +```javascript +const express = require('express'); +const app = express(); + +// In package.json dependencies, add: "mysql2": "^3.6.0" +const mysql = require('mysql2'); + +const db = mysql.createConnection({ + host: 'localhost', + user: 'your_user', + password: 'your_password', + database: 'your_database' +}); + +app.get('/users', (req, res) => { + db.query('SELECT * FROM users', (err, results) => { + if (err) { + res.status(500).json({ error: err.message }); + } else { + res.json(results); + } + }); +}); + +app.get('/ping', (req, res) => res.json({ status: 'ok' })); +app.listen(3000); +``` + +## File Structure Examples + +### Simple Website +``` +user/app/ +├── package.json +├── server.js +└── public/ + ├── index.html + ├── about.html + ├── style.css + └── images/ + └── logo.png +``` + +### API Application +``` +user/app/ +├── package.json +├── server.js +├── routes/ +│ ├── users.js +│ └── products.js +└── public/ + └── api-docs.html +``` + +## Key Differences from PHP + +| Concept | PHP | Node.js | +|---------|-----|---------| +| **File serving** | Apache serves .php files directly | Express routes handle requests | +| **Variables** | `$variable` | `let variable` | +| **Arrays** | `$arr = array()` | `let arr = []` | +| **Echo/Print** | `echo "Hello"` | `res.send("Hello")` | +| **Include files** | `include 'file.php'` | `require('./file.js')` | +| **Database** | Connect in each file | Connect once, reuse connection | +| **Sessions** | `$_SESSION` | `req.session` (with middleware) | +| **Forms** | `$_POST` | `req.body` (with middleware) | + +## Troubleshooting + +### Common Issues for PHP Users + +**Issue:** "Cannot GET /" error +**Solution:** Make sure you have an `app.get('/', ...)` route defined + +**Issue:** Static files (CSS/images) not loading +**Solution:** Put them in `public/` folder and add `app.use(express.static('public'))` + +**Issue:** Forms not working +**Solution:** Add `app.use(express.urlencoded({ extended: true }))` before your routes + +**Issue:** App crashes on restart +**Solution:** PM2 will automatically restart it, but check logs in `user/logs/nodejs/` + +### Getting Help + +- Check container logs: `./instance_logs` +- Check your app specifically: `docker exec -it your-container-name pm2 logs` +- Access container shell: `./instance_shell` +- Health check: Visit `/ping` to see if your app is responding + +## Advanced Features + +### Using Sessions (like PHP $_SESSION) + +```javascript +// Add to package.json dependencies: "express-session": "^1.17.3" +const session = require('express-session'); + +app.use(session({ + secret: 'your-secret-key', + resave: false, + saveUninitialized: false +})); + +app.get('/login', (req, res) => { + req.session.user = 'john_doe'; + res.send('Logged in!'); +}); + +app.get('/profile', (req, res) => { + if (req.session.user) { + res.send(`Hello ${req.session.user}!`); + } else { + res.send('Please log in'); + } +}); +``` + +### File Uploads + +```javascript +// Add to package.json: "multer": "^1.4.5-lts.1" +const multer = require('multer'); +const upload = multer({ dest: 'uploads/' }); + +app.post('/upload', upload.single('file'), (req, res) => { + res.send(`File uploaded: ${req.file.originalname}`); +}); +``` + +The container handles all the complex server setup, so you can focus on building your application! \ No newline at end of file diff --git a/configs/ecosystem.config.js b/configs/ecosystem.config.js new file mode 100644 index 0000000..2311a04 --- /dev/null +++ b/configs/ecosystem.config.js @@ -0,0 +1,31 @@ +module.exports = { + apps: [{ + name: 'node-app', + script: 'index.js', + instances: 1, + autorestart: true, + watch: false, + max_memory_restart: '256M', // Restart if app uses more than 256MB + kill_timeout: 3000, + wait_ready: true, + listen_timeout: 3000, + env: { + NODE_ENV: 'development', + PORT: 3000, + NODE_OPTIONS: '--max-old-space-size=200' // Limit V8 heap to 200MB + }, + env_production: { + NODE_ENV: 'production', + PORT: 3000, + NODE_OPTIONS: '--max-old-space-size=200' + }, + log_file: '/home/myuser/logs/nodejs/app.log', + error_file: '/home/myuser/logs/nodejs/error.log', + out_file: '/home/myuser/logs/nodejs/out.log', + log_date_format: 'YYYY-MM-DD HH:mm:ss Z', + log_type: 'json', + merge_logs: true, + max_restarts: 5, + min_uptime: '10s' + }] +}; \ No newline at end of file diff --git a/configs/index.js b/configs/index.js new file mode 100644 index 0000000..63efbb7 --- /dev/null +++ b/configs/index.js @@ -0,0 +1,101 @@ +const express = require('express'); +const session = require('express-session'); +const app = express(); +const port = process.env.PORT || 3000; + +// Middleware +app.use(express.json()); +app.use(express.static('public')); + +// Session configuration with Memcache (only in DEV mode when memcached is available) +if (process.env.NODE_ENV !== 'production') { + try { + const MemcachedStore = require('connect-memcached')(session); + app.use(session({ + store: new MemcachedStore({ + hosts: ['localhost:11211'] + }), + secret: process.env.SESSION_SECRET || 'your-secret-key-change-in-production', + resave: false, + saveUninitialized: false, + cookie: { + secure: false, // Allow HTTP in development + maxAge: 24 * 60 * 60 * 1000 // 24 hours + } + })); + console.log('Memcached session store initialized'); + } catch (err) { + console.log('Memcached not available, using memory store'); + app.use(session({ + secret: process.env.SESSION_SECRET || 'your-secret-key-change-in-production', + resave: false, + saveUninitialized: false, + cookie: { + secure: false, + maxAge: 24 * 60 * 60 * 1000 + } + })); + } +} else { + // Production session configuration (expects external session store) + app.use(session({ + secret: process.env.SESSION_SECRET || 'your-secret-key-change-in-production', + resave: false, + saveUninitialized: false, + cookie: { + secure: true, // HTTPS only in production + maxAge: 24 * 60 * 60 * 1000 + } + })); +} + +// Health check endpoint +app.get('/ping', (req, res) => { + res.json({ + status: 'ok', + timestamp: new Date().toISOString(), + uptime: process.uptime(), + version: process.env.npm_package_version || '1.0.0' + }); +}); + +// Default route +app.get('/', (req, res) => { + res.json({ + message: 'Cloud Node Container is running!', + nodeVersion: process.version, + environment: process.env.NODE_ENV || 'development', + timestamp: new Date().toISOString() + }); +}); + +// Info endpoint +app.get('/info', (req, res) => { + res.json({ + nodeVersion: process.version, + platform: process.platform, + arch: process.arch, + uptime: process.uptime(), + memory: process.memoryUsage(), + env: process.env.NODE_ENV || 'development' + }); +}); + +// Session demo endpoint +app.get('/session', (req, res) => { + if (!req.session.visits) { + req.session.visits = 0; + } + req.session.visits++; + + res.json({ + sessionId: req.sessionID, + visits: req.session.visits, + message: 'Session is working with ' + (process.env.NODE_ENV !== 'production' ? 'Memcached' : 'default store') + }); +}); + +app.listen(port, () => { + console.log(`Server running on port ${port}`); + console.log(`Environment: ${process.env.NODE_ENV || 'development'}`); +}); \ No newline at end of file diff --git a/configs/nginx.conf b/configs/nginx.conf new file mode 100644 index 0000000..b5fc935 --- /dev/null +++ b/configs/nginx.conf @@ -0,0 +1,45 @@ +user nginx; +worker_processes 1; # Single worker for memory efficiency +worker_rlimit_nofile 1024; +error_log /var/log/nginx/error.log warn; # Less verbose logging +pid /run/nginx.pid; + +events { + worker_connections 512; # Reduced for memory efficiency + use epoll; + multi_accept on; +} + +http { + # Memory-optimized settings + client_body_buffer_size 16k; + client_header_buffer_size 1k; + client_max_body_size 8m; + large_client_header_buffers 2 1k; + + log_format main '$remote_addr - $remote_user [$time_local] "$request" ' + '$status $body_bytes_sent "$http_referer" ' + '"$http_user_agent" "$http_x_forwarded_for"'; + + access_log /var/log/nginx/access.log main buffer=16k flush=2m; + + sendfile on; + tcp_nopush on; + tcp_nodelay on; + keepalive_timeout 30; # Reduced from 65 + keepalive_requests 100; + types_hash_max_size 1024; # Reduced from 2048 + server_tokens off; + + include /etc/nginx/mime.types; + default_type application/octet-stream; + + # Optimized gzip compression + gzip on; + gzip_vary on; + gzip_min_length 1024; + gzip_comp_level 2; # Lower compression for less CPU/memory usage + gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript; + + include /etc/nginx/conf.d/*.conf; +} \ No newline at end of file diff --git a/configs/package.json b/configs/package.json new file mode 100644 index 0000000..6c72df8 --- /dev/null +++ b/configs/package.json @@ -0,0 +1,27 @@ +{ + "name": "cloud-node-container", + "version": "1.0.0", + "description": "Default Node.js application for Cloud Node Container", + "main": "index.js", + "scripts": { + "start": "node index.js", + "dev": "nodemon index.js", + "test": "echo \"Error: no test specified\" && exit 1" + }, + "dependencies": { + "express": "^4.18.2", + "express-session": "^1.17.3", + "connect-memcached": "^1.0.0" + }, + "devDependencies": { + "nodemon": "^3.0.1" + }, + "keywords": [ + "nodejs", + "express", + "container", + "docker" + ], + "author": "", + "license": "MIT" +} \ No newline at end of file diff --git a/examples/api-server/package.json b/examples/api-server/package.json new file mode 100644 index 0000000..0cf1623 --- /dev/null +++ b/examples/api-server/package.json @@ -0,0 +1,12 @@ +{ + "name": "simple-api", + "version": "1.0.0", + "description": "A simple REST API server", + "main": "server.js", + "scripts": { + "start": "node server.js" + }, + "dependencies": { + "express": "^4.18.2" + } +} \ No newline at end of file diff --git a/examples/api-server/server.js b/examples/api-server/server.js new file mode 100644 index 0000000..c94a38b --- /dev/null +++ b/examples/api-server/server.js @@ -0,0 +1,83 @@ +const express = require('express'); +const app = express(); +const port = process.env.PORT || 3000; + +// Middleware +app.use(express.json()); + +// In-memory data store (in production, you'd use a database) +let users = [ + { id: 1, name: 'John Doe', email: 'john@example.com' }, + { id: 2, name: 'Jane Smith', email: 'jane@example.com' } +]; +let nextId = 3; + +// Health check endpoint +app.get('/ping', (req, res) => { + res.json({ status: 'ok', timestamp: new Date().toISOString() }); +}); + +// API Routes +app.get('/api/users', (req, res) => { + res.json(users); +}); + +app.get('/api/users/:id', (req, res) => { + const user = users.find(u => u.id === parseInt(req.params.id)); + if (!user) { + return res.status(404).json({ error: 'User not found' }); + } + res.json(user); +}); + +app.post('/api/users', (req, res) => { + const { name, email } = req.body; + if (!name || !email) { + return res.status(400).json({ error: 'Name and email are required' }); + } + + const user = { id: nextId++, name, email }; + users.push(user); + res.status(201).json(user); +}); + +app.put('/api/users/:id', (req, res) => { + const user = users.find(u => u.id === parseInt(req.params.id)); + if (!user) { + return res.status(404).json({ error: 'User not found' }); + } + + const { name, email } = req.body; + if (name) user.name = name; + if (email) user.email = email; + + res.json(user); +}); + +app.delete('/api/users/:id', (req, res) => { + const index = users.findIndex(u => u.id === parseInt(req.params.id)); + if (index === -1) { + return res.status(404).json({ error: 'User not found' }); + } + + users.splice(index, 1); + res.status(204).send(); +}); + +// Basic documentation endpoint +app.get('/', (req, res) => { + res.json({ + message: 'Simple API Server', + endpoints: { + 'GET /api/users': 'Get all users', + 'GET /api/users/:id': 'Get user by ID', + 'POST /api/users': 'Create new user', + 'PUT /api/users/:id': 'Update user by ID', + 'DELETE /api/users/:id': 'Delete user by ID' + } + }); +}); + +app.listen(port, () => { + console.log(`API server running on port ${port}`); +}); \ No newline at end of file diff --git a/examples/simple-website/package.json b/examples/simple-website/package.json new file mode 100644 index 0000000..6ed912c --- /dev/null +++ b/examples/simple-website/package.json @@ -0,0 +1,12 @@ +{ + "name": "simple-website", + "version": "1.0.0", + "description": "A simple website with static pages", + "main": "server.js", + "scripts": { + "start": "node server.js" + }, + "dependencies": { + "express": "^4.18.2" + } +} \ No newline at end of file diff --git a/examples/simple-website/public/about.html b/examples/simple-website/public/about.html new file mode 100644 index 0000000..b54f307 --- /dev/null +++ b/examples/simple-website/public/about.html @@ -0,0 +1,22 @@ + + + + + + About - My Simple Website + + + + + +
+

About Us

+

This website is powered by Node.js and Express, running in a containerized environment.

+

It demonstrates how easy it is to deploy web applications with the Cloud Node Container.

+
+ + \ No newline at end of file diff --git a/examples/simple-website/public/contact.html b/examples/simple-website/public/contact.html new file mode 100644 index 0000000..78b9c2f --- /dev/null +++ b/examples/simple-website/public/contact.html @@ -0,0 +1,33 @@ + + + + + + Contact - My Simple Website + + + + + +
+

Contact Us

+

Get in touch with us!

+
+ + + + + + + + + + +
+
+ + \ No newline at end of file diff --git a/examples/simple-website/public/index.html b/examples/simple-website/public/index.html new file mode 100644 index 0000000..9b81149 --- /dev/null +++ b/examples/simple-website/public/index.html @@ -0,0 +1,26 @@ + + + + + + My Simple Website + + + + + +
+

Welcome to My Website

+

This is a simple website running on Node.js in a Docker container!

+

Current time:

+
+ + + + \ No newline at end of file diff --git a/examples/simple-website/public/style.css b/examples/simple-website/public/style.css new file mode 100644 index 0000000..5e15231 --- /dev/null +++ b/examples/simple-website/public/style.css @@ -0,0 +1,65 @@ +body { + font-family: Arial, sans-serif; + margin: 0; + padding: 0; + background-color: #f4f4f4; +} + +nav { + background-color: #333; + padding: 1rem; +} + +nav a { + color: white; + text-decoration: none; + margin-right: 1rem; + padding: 0.5rem; +} + +nav a:hover { + background-color: #555; + border-radius: 4px; +} + +main { + max-width: 800px; + margin: 2rem auto; + padding: 2rem; + background-color: white; + border-radius: 8px; + box-shadow: 0 2px 4px rgba(0,0,0,0.1); +} + +h1 { + color: #333; +} + +form { + display: flex; + flex-direction: column; + gap: 1rem; +} + +label { + font-weight: bold; +} + +input, textarea { + padding: 0.5rem; + border: 1px solid #ccc; + border-radius: 4px; +} + +button { + padding: 0.75rem; + background-color: #007bff; + color: white; + border: none; + border-radius: 4px; + cursor: pointer; +} + +button:hover { + background-color: #0056b3; +} \ No newline at end of file diff --git a/examples/simple-website/server.js b/examples/simple-website/server.js new file mode 100644 index 0000000..ec50ae9 --- /dev/null +++ b/examples/simple-website/server.js @@ -0,0 +1,29 @@ +const express = require('express'); +const path = require('path'); +const app = express(); +const port = process.env.PORT || 3000; + +// Serve static files from public directory +app.use(express.static('public')); + +// Basic routes +app.get('/', (req, res) => { + res.sendFile(path.join(__dirname, 'public', 'index.html')); +}); + +app.get('/about', (req, res) => { + res.sendFile(path.join(__dirname, 'public', 'about.html')); +}); + +app.get('/contact', (req, res) => { + res.sendFile(path.join(__dirname, 'public', 'contact.html')); +}); + +// Health check for the container +app.get('/ping', (req, res) => { + res.json({ status: 'ok', timestamp: new Date().toISOString() }); +}); + +app.listen(port, () => { + console.log(`Simple website running on port ${port}`); +}); \ No newline at end of file diff --git a/local-dev.sh b/local-dev.sh new file mode 100755 index 0000000..ec17c9e --- /dev/null +++ b/local-dev.sh @@ -0,0 +1,95 @@ +#!/usr/bin/env bash +name='' +http_port='80' +https_port='443' +root_path="$(pwd)" +verbose='false' + +while getopts 'n:p:s:r:a:vh' flag; do + case "${flag}" in + n) name="${OPTARG}" ;; + p) http_port="${OPTARG}" ;; + s) https_port="${OPTARG}" ;; + r) root_path="${OPTARG}" ;; + a) nodever="${OPTARG}" ;; + v) verbose='true' ;; + h) echo "Variables" + echo "-n = Name of Container, Required" + echo "-p = Non-https Port Override, default 80" + echo "-s = Https Port Override, default 443" + echo "-r = Root Path for files, defaults to current working path" + echo "-a = Node.js Version, Default to 20 (options: 18, 20, 22)" + echo "-v = Enable Verbose Mode" + exit 1 ;; + esac +done + +# Check if Docker is installed +echo "Checking if Docker is Installed..." +check_docker=$(command -v docker) +if [ $? != 0 ]; then + echo "Docker must be installed to run this application" + exit 1 +fi +echo "Docker looks to be installed" + +if [ -z "$name" ]; then + echo "Name not set, please set it with -n" + exit 1 +fi + +if [ -z "$nodever" ]; then + nodever=20; +fi + +echo "Building Docker Image..." +user=$(whoami) +uid=$(id -u) +if [ ! -d "$root_path/user" ]; then + mkdir -p "$root_path/user"; + mkdir -p "$root_path/user/logs/{nginx,nodejs}"; + mkdir -p "$root_path/user/app"; +fi + +# Create default Node.js app if it doesn't exist +if [ ! -f "$root_path/user/app/package.json" ]; then + echo "Creating default Node.js application..." + cp configs/package.json "$root_path/user/app/" + cp configs/index.js "$root_path/user/app/" + cp configs/ecosystem.config.js "$root_path/user/app/" + # Update ecosystem config with correct user path + sed -i "s/\/home\/myuser/\/home\/$user/g" "$root_path/user/app/ecosystem.config.js" +fi + +$check_docker run --pull=always -d -p "$http_port":80 -p "$https_port":443 -e NODEVER=$nodever -e environment=DEV --mount type=bind,source="$root_path"/user,target=/home/"$user" --mount type=bind,source="$(pwd)"/user/logs/nginx,target=/var/log/nginx --mount type=bind,source="$(pwd)"/user/logs/nodejs,target=/var/log/nodejs -e uid="$uid" -e user="$user" -e domain="$name-local.dev" --name "$name" cloud-node-container:latest + +echo "Creating management scripts in root directory..." +echo "#!/usr/bin/env bash" > "$root_path/instance_start" +echo "docker start $name" >> "$root_path/instance_start" +echo "#!/usr/bin/env bash" > "$root_path/instance_stop" +echo "docker stop $name" >> "$root_path/instance_stop" +echo "#!/usr/bin/env bash" > "$root_path/instance_logs" +echo "docker logs -f $name" >> "$root_path/instance_logs" +echo "#!/usr/bin/env bash" > "$root_path/instance_shell" +echo "docker exec -it $name /bin/bash" >> "$root_path/instance_shell" +chmod +x $root_path/instance_* + +echo "Waiting 30 seconds for setup to finish..." +sleep 30 + +echo "Local Development Instance Created!" +echo "- Container name: $name" +echo "- HTTP port: $http_port" +echo "- HTTPS port: $https_port" +echo "- Node.js version: $nodever" +echo "" +echo "Management scripts created:" +echo " ./instance_start - Start the container" +echo " ./instance_stop - Stop the container" +echo " ./instance_logs - View container logs" +echo " ./instance_shell - Access container shell" +echo "" +echo "Your application files are in: $root_path/user/app/" +echo "Visit https://localhost:$https_port (accept SSL warning) to see your app" + +exit 0 \ No newline at end of file diff --git a/scripts/backup.sh b/scripts/backup.sh new file mode 100755 index 0000000..c59daee --- /dev/null +++ b/scripts/backup.sh @@ -0,0 +1,17 @@ +#!/usr/bin/env bash + +USER=$1 +BACKUP_DIR="/home/$USER/_backups" +DATE=$(date +%Y%m%d_%H%M%S) + +# Create backup directory if it doesn't exist +mkdir -p $BACKUP_DIR + +# Backup application files +tar -czf $BACKUP_DIR/app_backup_$DATE.tar.gz -C /home/$USER app/ + +# Keep only last 10 backups +cd $BACKUP_DIR +ls -t app_backup_*.tar.gz | tail -n +11 | xargs -r rm + +echo "Backup completed: app_backup_$DATE.tar.gz" \ No newline at end of file diff --git a/scripts/create-nginx-config.sh b/scripts/create-nginx-config.sh new file mode 100755 index 0000000..7adae60 --- /dev/null +++ b/scripts/create-nginx-config.sh @@ -0,0 +1,54 @@ +#!/usr/bin/env bash + +# Create nginx configuration for reverse proxy to Node.js app +cat > /etc/nginx/conf.d/default.conf << EOF +upstream nodejs_backend { + server 127.0.0.1:3000; +} + +server { + listen 80; + server_name $domain $serveralias; + + # Redirect HTTP to HTTPS + return 301 https://\$server_name\$request_uri; +} + +server { + listen 443 ssl http2; + server_name $domain $serveralias; + + ssl_certificate /etc/pki/tls/certs/localhost.crt; + ssl_certificate_key /etc/pki/tls/private/localhost.key; + + ssl_protocols TLSv1.2 TLSv1.3; + ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384; + + access_log /home/$user/logs/nginx/access.log; + error_log /home/$user/logs/nginx/error.log; + + location / { + proxy_pass http://nodejs_backend; + proxy_http_version 1.1; + proxy_set_header Upgrade \$http_upgrade; + proxy_set_header Connection 'upgrade'; + proxy_set_header Host \$host; + proxy_set_header X-Real-IP \$remote_addr; + proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto \$scheme; + proxy_cache_bypass \$http_upgrade; + } + + location /ping { + proxy_pass http://nodejs_backend/ping; + access_log off; + } + + # Static files + location /static/ { + alias /home/$user/app/public/; + expires 30d; + add_header Cache-Control "public, immutable"; + } +} +EOF \ No newline at end of file diff --git a/scripts/entrypoint.sh b/scripts/entrypoint.sh new file mode 100755 index 0000000..199e924 --- /dev/null +++ b/scripts/entrypoint.sh @@ -0,0 +1,68 @@ +#!/usr/bin/env bash + +if [ -z "$NODEVER" ]; then + NODEVER="20"; +fi + +if [ -z "$environment" ]; then + environment="PROD" +fi + +adduser -u $uid $user + +mkdir -p /home/$user/app +mkdir -p /home/$user/logs/{nginx,nodejs} + +# Link log directories +rm -rf /var/log/nginx +ln -s /home/$user/logs/nginx /var/log/nginx +ln -s /home/$user/logs/nodejs /var/log/nodejs + +# Configure nginx for reverse proxy +/scripts/create-nginx-config.sh + +# Set ownership and permissions +chown -R $user:$user /home/$user +chmod -R 755 /home/$user + +# Start nginx +nginx + +if [[ $environment == 'DEV' ]]; then + echo "Starting Dev Deployment" + mkdir -p /home/$user/_backups + + # Ensure microdnf is available for installing additional packages in DEV mode + if ! command -v microdnf &> /dev/null; then + echo "microdnf not found, installing with dnf..." + dnf install -y microdnf && dnf clean all + fi + + # Install Memcached for session storage in DEV mode with memory limit + microdnf install -y memcached + # Start memcached with 32MB memory limit + nohup memcached -d -u $user -p 11211 -m 32 + + # Set up automatic backups + echo "*/30 * * * * root /scripts/backup.sh $user" >> /etc/crontab +fi + +# Start cron for log rotation and backups +/usr/sbin/crond + +# If there's an app in the user directory, start it with PM2 +if [ -f /home/$user/app/package.json ]; then + cd /home/$user/app + su -c "npm install" $user + su -c "pm2 start ecosystem.config.js" $user +else + # Start default app + cd /var/www/html + npm install + su -c "pm2 start ecosystem.config.js" $user +fi + +# Follow logs +tail -f /home/$user/logs/nginx/* /home/$user/logs/nodejs/* + +exit 0 \ No newline at end of file diff --git a/scripts/install-node18.sh b/scripts/install-node18.sh new file mode 100755 index 0000000..c1b1675 --- /dev/null +++ b/scripts/install-node18.sh @@ -0,0 +1,6 @@ +#!/usr/bin/env bash +curl -fsSL https://rpm.nodesource.com/setup_18.x | bash - +dnf install -y nodejs +npm install -g npm@latest +node --version +npm --version \ No newline at end of file diff --git a/scripts/install-node20.sh b/scripts/install-node20.sh new file mode 100755 index 0000000..d2f6070 --- /dev/null +++ b/scripts/install-node20.sh @@ -0,0 +1,6 @@ +#!/usr/bin/env bash +curl -fsSL https://rpm.nodesource.com/setup_20.x | bash - +dnf install -y nodejs +npm install -g npm@latest +node --version +npm --version \ No newline at end of file diff --git a/scripts/install-node22.sh b/scripts/install-node22.sh new file mode 100755 index 0000000..32a4f65 --- /dev/null +++ b/scripts/install-node22.sh @@ -0,0 +1,6 @@ +#!/usr/bin/env bash +curl -fsSL https://rpm.nodesource.com/setup_22.x | bash - +dnf install -y nodejs +npm install -g npm@latest +node --version +npm --version \ No newline at end of file diff --git a/scripts/log-rotate.sh b/scripts/log-rotate.sh new file mode 100755 index 0000000..aa6d6a2 --- /dev/null +++ b/scripts/log-rotate.sh @@ -0,0 +1,18 @@ +#!/usr/bin/env bash + +# Log rotation script for nginx and Node.js logs +LOG_DIR="/home/*/logs" + +# Compress logs older than 3 days +find $LOG_DIR -name "*.log" -type f -mtime +3 -exec gzip {} \; + +# Delete compressed logs older than 7 days +find $LOG_DIR -name "*.gz" -type f -mtime +7 -delete + +# Rotate nginx logs +if [ -f /var/run/nginx.pid ]; then + kill -USR1 $(cat /var/run/nginx.pid) +fi + +# Rotate PM2 logs +pm2 flush \ No newline at end of file diff --git a/scripts/memory-info.sh b/scripts/memory-info.sh new file mode 100755 index 0000000..e38b81f --- /dev/null +++ b/scripts/memory-info.sh @@ -0,0 +1,58 @@ +#!/usr/bin/env bash + +echo "=== Cloud Node Container Memory Usage ===" +echo "Date: $(date)" +echo "" + +echo "=== System Memory ===" +free -h + +echo "" +echo "=== Process Memory Usage ===" +echo "Process PID VSZ RSS %MEM" +echo "----------------------------------------" + +# Nginx +nginx_pid=$(pgrep nginx | head -1) +if [ ! -z "$nginx_pid" ]; then + ps -o pid,vsz,rss,%mem,comm -p $nginx_pid | tail -n +2 | while read pid vsz rss mem comm; do + printf "%-25s %5s %6s %6s %5s\n" "$comm" "$pid" "${vsz}K" "${rss}K" "$mem%" + done +fi + +# Node.js/PM2 +pm2_pids=$(pgrep -f "PM2\|node") +if [ ! -z "$pm2_pids" ]; then + echo "$pm2_pids" | while read pid; do + ps -o pid,vsz,rss,%mem,comm -p $pid 2>/dev/null | tail -n +2 | while read pid vsz rss mem comm; do + printf "%-25s %5s %6s %6s %5s\n" "$comm" "$pid" "${vsz}K" "${rss}K" "$mem%" + done + done +fi + +# Memcached +memcached_pid=$(pgrep memcached) +if [ ! -z "$memcached_pid" ]; then + ps -o pid,vsz,rss,%mem,comm -p $memcached_pid | tail -n +2 | while read pid vsz rss mem comm; do + printf "%-25s %5s %6s %6s %5s\n" "$comm" "$pid" "${vsz}K" "${rss}K" "$mem%" + done +fi + +echo "" +echo "=== Container Resource Limits ===" +if [ -f /sys/fs/cgroup/memory/memory.limit_in_bytes ]; then + limit=$(cat /sys/fs/cgroup/memory/memory.limit_in_bytes) + if [ $limit -lt 9223372036854775807 ]; then + echo "Memory Limit: $((limit / 1024 / 1024))MB" + else + echo "Memory Limit: No limit set" + fi +fi + +echo "" +echo "=== PM2 Process Info ===" +if command -v pm2 &> /dev/null; then + pm2 list 2>/dev/null | grep -E "(App name|online|stopped|errored)" + echo "" + pm2 show 0 2>/dev/null | grep -E "(memory|cpu|uptime)" || echo "No PM2 processes running" +fi \ No newline at end of file