init tools repo
This commit is contained in:
16
asf-cloud-server/TBM_devbench/.env.example
Normal file
16
asf-cloud-server/TBM_devbench/.env.example
Normal file
@@ -0,0 +1,16 @@
|
||||
# Server Configuration
|
||||
PORT=3001
|
||||
|
||||
# Database Configuration
|
||||
DB_PATH=./devbench.db
|
||||
|
||||
# Session Configuration
|
||||
SESSION_SECRET=your-secret-key-here
|
||||
|
||||
# SSH Configuration (for provision script)
|
||||
SSH_USER=asf
|
||||
SSH_HOST=asf-tb.duckdns.org
|
||||
SSH_PASS=ASF
|
||||
|
||||
# Provision Script Configuration
|
||||
PROVISION_SCRIPT=./provision_vm.sh
|
||||
52
asf-cloud-server/TBM_devbench/.gitignore
vendored
Normal file
52
asf-cloud-server/TBM_devbench/.gitignore
vendored
Normal file
@@ -0,0 +1,52 @@
|
||||
# Dependencies
|
||||
node_modules/
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
||||
# Database
|
||||
*.db
|
||||
*.sqlite
|
||||
*.sqlite3
|
||||
data/
|
||||
|
||||
# Logs
|
||||
logs/
|
||||
*.log
|
||||
/var/log/
|
||||
|
||||
# Runtime data
|
||||
pids/
|
||||
*.pid
|
||||
*.seed
|
||||
*.pid.lock
|
||||
|
||||
# Environment variables
|
||||
.env
|
||||
.env.local
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
|
||||
# IDE
|
||||
.vscode/
|
||||
.idea/
|
||||
*.swp
|
||||
*.swo
|
||||
*~
|
||||
|
||||
# OS
|
||||
.DS_Store
|
||||
.DS_Store?
|
||||
._*
|
||||
.Spotlight-V100
|
||||
.Trashes
|
||||
ehthumbs.db
|
||||
Thumbs.db
|
||||
|
||||
# Docker
|
||||
.dockerignore
|
||||
|
||||
# Temporary files
|
||||
tmp/
|
||||
temp/
|
||||
121
asf-cloud-server/TBM_devbench/CHANGELOG.md
Normal file
121
asf-cloud-server/TBM_devbench/CHANGELOG.md
Normal file
@@ -0,0 +1,121 @@
|
||||
# Changelog - DevBench Manager Updates
|
||||
|
||||
## Changes Made
|
||||
|
||||
### 1. Updated SSH Access Configuration
|
||||
- **File**: `provision_vm.sh`
|
||||
- **Changes**:
|
||||
- Updated SSH connection to use port 49152
|
||||
- Changed SSH host to `asf@asf-server.duckdns.org -p 49152`
|
||||
- Modified SSH command to include port parameter
|
||||
|
||||
### 2. Updated Output Parsing
|
||||
- **File**: `provision_vm.sh`
|
||||
- **Changes**:
|
||||
- Now extracts SSH Port and VNC Port from script output
|
||||
- Removed IP address extraction
|
||||
- Parses output format: `SSH Port: XXXX` and `VNC Port: XXXX`
|
||||
- Extracts VM name from success message
|
||||
|
||||
### 3. Updated Server-Side Processing
|
||||
- **File**: `server.js`
|
||||
- **Changes**:
|
||||
- Modified DevBench creation to parse new output format (SSH_PORT, VNC_PORT, VM_NAME)
|
||||
- Stores only port numbers in database (ssh_info and vnc_info fields)
|
||||
- Removed vm_ip field from database schema
|
||||
- Added `/help` route for help page
|
||||
|
||||
### 4. Updated User Interface
|
||||
- **File**: `views/dashboard.ejs`
|
||||
- **Changes**:
|
||||
- Display "SSH Port" instead of full SSH command
|
||||
- Display "VNC Port" instead of full VNC info
|
||||
- Added link to help page in connection info section
|
||||
- Improved visual presentation with larger font for ports
|
||||
|
||||
### 5. Updated Admin Interface
|
||||
- **File**: `views/admin.ejs`
|
||||
- **Changes**:
|
||||
- Replaced "IP Address" column with "SSH Port" and "VNC Port" columns
|
||||
- Shows port numbers for each DevBench
|
||||
|
||||
### 6. Added Help Page
|
||||
- **File**: `views/help.ejs` (NEW)
|
||||
- **Features**:
|
||||
- Step-by-step guide for using SSH Config Manager tool
|
||||
- Instructions for configuring SSH access
|
||||
- Download link for SSH Config Manager
|
||||
- Connection information and important notes
|
||||
- Styled with Bootstrap cards and custom CSS
|
||||
|
||||
### 7. Updated Navigation
|
||||
- **File**: `views/layout.ejs`
|
||||
- **Changes**:
|
||||
- Added Help icon/link in navbar
|
||||
- Added TBM icon (tbm-icon.png) in navbar
|
||||
- Added favicon using TBM icon
|
||||
- Help link accessible from all pages
|
||||
|
||||
### 8. Added Downloadable Tool
|
||||
- **Location**: `public/downloads/db_vm_ssh_config_manager.exe`
|
||||
- **Purpose**: SSH configuration management tool
|
||||
- **Access**: Available at `/downloads/db_vm_ssh_config_manager.exe`
|
||||
|
||||
### 9. Added TBM Icon
|
||||
- **Location**: `public/images/tbm-icon.png`
|
||||
- **Usage**:
|
||||
- Favicon for all pages
|
||||
- Icon in navbar next to logo
|
||||
- Branding element
|
||||
|
||||
### 10. Updated Documentation
|
||||
- **File**: `README.md`
|
||||
- **Changes**:
|
||||
- Added SSH Configuration section
|
||||
- Updated database schema documentation
|
||||
- Added help page to user features
|
||||
- Updated project structure
|
||||
- Added help route to API endpoints
|
||||
|
||||
## Summary of User-Facing Changes
|
||||
|
||||
### For Users:
|
||||
1. **Simplified Connection Info**: Now shows only SSH Port and VNC Port numbers
|
||||
2. **Help Page**: Accessible via navbar, provides detailed setup instructions
|
||||
3. **SSH Config Tool**: Downloadable tool to simplify SSH configuration
|
||||
4. **Visual Improvements**: TBM icon added to branding
|
||||
|
||||
### For Administrators:
|
||||
1. **Updated Admin Dashboard**: Shows SSH and VNC ports instead of IP addresses
|
||||
2. **Same management capabilities**: All admin functions remain unchanged
|
||||
|
||||
## Technical Details
|
||||
|
||||
### SSH Connection Format:
|
||||
- **Old**: Full SSH command string
|
||||
- **New**: Port number only (e.g., "6004")
|
||||
|
||||
### VNC Connection Format:
|
||||
- **Old**: Full VNC connection string
|
||||
- **New**: Port number only (e.g., "5004")
|
||||
|
||||
### Database Changes:
|
||||
- Removed: `vm_ip` field
|
||||
- Modified: `ssh_info` now stores SSH port number
|
||||
- Modified: `vnc_info` now stores VNC port number
|
||||
|
||||
## Testing Recommendations
|
||||
|
||||
1. Test DevBench creation with new output parsing
|
||||
2. Verify SSH port and VNC port display correctly
|
||||
3. Test help page accessibility
|
||||
4. Verify SSH Config Manager download
|
||||
5. Check TBM icon display on all pages
|
||||
6. Test on different browsers for favicon display
|
||||
|
||||
## Migration Notes
|
||||
|
||||
If you have existing DevBenches in the database:
|
||||
- Old entries may still have full SSH/VNC strings or IP addresses
|
||||
- New entries will have port numbers only
|
||||
- Consider running a migration script if needed to update old entries
|
||||
27
asf-cloud-server/TBM_devbench/Dockerfile
Normal file
27
asf-cloud-server/TBM_devbench/Dockerfile
Normal file
@@ -0,0 +1,27 @@
|
||||
FROM node:18-alpine
|
||||
|
||||
# Install bash and other dependencies
|
||||
RUN apk add --no-cache bash sshpass openssh-client wget
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# Copy package files
|
||||
COPY package*.json ./
|
||||
|
||||
# Install dependencies
|
||||
RUN npm install
|
||||
|
||||
# Copy application files
|
||||
COPY . .
|
||||
|
||||
# Ensure public directory permissions
|
||||
RUN chmod -R 755 /app/public
|
||||
|
||||
# Create directories for database and logs
|
||||
RUN mkdir -p /app/data /app/logs
|
||||
|
||||
# Expose port
|
||||
EXPOSE 3001
|
||||
|
||||
# Start the application
|
||||
CMD ["npm", "start"]
|
||||
248
asf-cloud-server/TBM_devbench/QUICK_START.md
Normal file
248
asf-cloud-server/TBM_devbench/QUICK_START.md
Normal file
@@ -0,0 +1,248 @@
|
||||
# 🚀 DevBench Manager - Quick Start Guide
|
||||
|
||||
## One-Minute Setup
|
||||
|
||||
```bash
|
||||
# 1. Clone and enter directory
|
||||
git clone <repository-url>
|
||||
cd ASF_devbench
|
||||
|
||||
# 2. Deploy
|
||||
chmod +x deploy.sh
|
||||
./deploy.sh
|
||||
|
||||
# 3. Access
|
||||
# Open: http://localhost:9090
|
||||
# Login: admin / admin123
|
||||
```
|
||||
|
||||
## That's It! 🎉
|
||||
|
||||
---
|
||||
|
||||
## What You Get
|
||||
|
||||
### 🌐 Web Interface
|
||||
- **URL**: http://localhost:9090
|
||||
- **Caddy**: https://tbm.nabd-co.com (if configured)
|
||||
|
||||
### 👤 Default Login
|
||||
- **Username**: `admin`
|
||||
- **Password**: `admin123`
|
||||
- ⚠️ Change this after first login!
|
||||
|
||||
### ✨ Features
|
||||
- ✅ Create and manage VMs
|
||||
- ✅ Real-time log streaming
|
||||
- ✅ SSH/VNC connection info
|
||||
- ✅ Dark/Light theme toggle
|
||||
- ✅ Help page with guides
|
||||
- ✅ SSH Config Manager download
|
||||
|
||||
---
|
||||
|
||||
## Quick Commands
|
||||
|
||||
### Check Status
|
||||
```bash
|
||||
docker ps | grep devbench-manager
|
||||
```
|
||||
|
||||
### View Logs
|
||||
```bash
|
||||
docker-compose logs -f
|
||||
```
|
||||
|
||||
### Health Check
|
||||
```bash
|
||||
curl http://localhost:9090/health
|
||||
```
|
||||
|
||||
### Stop
|
||||
```bash
|
||||
docker-compose down
|
||||
```
|
||||
|
||||
### Restart
|
||||
```bash
|
||||
docker-compose restart
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## First Steps
|
||||
|
||||
### 1. Login
|
||||
- Open http://localhost:9090
|
||||
- Enter: admin / admin123
|
||||
|
||||
### 2. Change Password
|
||||
- Go to Admin Panel
|
||||
- Click "Reset Password"
|
||||
- Enter new secure password
|
||||
|
||||
### 3. Add Users
|
||||
- Click "Add User"
|
||||
- Enter username (letters only)
|
||||
- Enter email and password
|
||||
|
||||
### 4. Create DevBench
|
||||
- User logs in
|
||||
- Click "Create DevBench"
|
||||
- Enter name (alphanumeric, hyphens, underscores)
|
||||
- Watch real-time creation logs
|
||||
|
||||
### 5. Access VM
|
||||
- Copy SSH Port from connection info
|
||||
- Download SSH Config Manager from Help page
|
||||
- Follow setup guide
|
||||
|
||||
---
|
||||
|
||||
## Theme Toggle
|
||||
|
||||
Click the floating button (bottom-right) to switch between:
|
||||
- 🌞 Light Theme
|
||||
- 🌙 Dark Theme
|
||||
|
||||
Your preference is saved automatically!
|
||||
|
||||
---
|
||||
|
||||
## Need Help?
|
||||
|
||||
### In-App
|
||||
- Click the **Help** icon in navbar
|
||||
- Download SSH Config Manager
|
||||
- Follow step-by-step guide
|
||||
|
||||
### Documentation
|
||||
- `README.md` - Overview
|
||||
- `docs/ARCHITECTURE.md` - System design
|
||||
- `docs/STRUCTURE.md` - Code structure
|
||||
- `docs/DEPLOYMENT.md` - Deployment guide
|
||||
- `docs/API.md` - API reference
|
||||
|
||||
### Troubleshooting
|
||||
```bash
|
||||
# Container not running?
|
||||
docker-compose logs
|
||||
|
||||
# Port in use?
|
||||
sudo lsof -i :9090
|
||||
|
||||
# Permission issues?
|
||||
sudo chown -R $USER:$USER data logs
|
||||
|
||||
# Network issues?
|
||||
docker network inspect caddy_network
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Common Tasks
|
||||
|
||||
### Add a User
|
||||
1. Login as admin
|
||||
2. Go to Admin Panel
|
||||
3. Click "Add User"
|
||||
4. Fill in details
|
||||
5. Click "Add User"
|
||||
|
||||
### Create a DevBench
|
||||
1. Login as user
|
||||
2. Click "Create DevBench"
|
||||
3. Enter name
|
||||
4. Watch creation progress
|
||||
5. Copy connection info
|
||||
|
||||
### Access VM via SSH
|
||||
1. Note SSH Port from DevBench
|
||||
2. Download SSH Config Manager
|
||||
3. Add VM to config
|
||||
4. Use: `ssh vm-name`
|
||||
|
||||
### Check VM Status
|
||||
1. Go to Dashboard
|
||||
2. Click "Check Status"
|
||||
3. View current status
|
||||
|
||||
---
|
||||
|
||||
## Configuration
|
||||
|
||||
### Change Port
|
||||
Edit `docker-compose.yml`:
|
||||
```yaml
|
||||
ports:
|
||||
- "8080:3001" # Change 9090 to 8080
|
||||
```
|
||||
|
||||
### Environment Variables
|
||||
Create `.env`:
|
||||
```bash
|
||||
NODE_ENV=production
|
||||
SECRET_KEY=your-secret-key
|
||||
ADMIN_PASSWORD=your-password
|
||||
```
|
||||
|
||||
### Caddy Proxy
|
||||
Add to Caddyfile:
|
||||
```
|
||||
tbm.nabd-co.com {
|
||||
reverse_proxy devbench-manager:3001
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Maintenance
|
||||
|
||||
### Backup Database
|
||||
```bash
|
||||
cp data/devbench.db backups/devbench-$(date +%Y%m%d).db
|
||||
```
|
||||
|
||||
### View Logs
|
||||
```bash
|
||||
docker-compose logs --tail=100
|
||||
```
|
||||
|
||||
### Update Application
|
||||
```bash
|
||||
git pull
|
||||
./deploy.sh
|
||||
```
|
||||
|
||||
### Clean Up
|
||||
```bash
|
||||
./cleanup.sh
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Support
|
||||
|
||||
📧 **Email**: admin@nabd-co.com
|
||||
📚 **Docs**: `/docs` directory
|
||||
🐛 **Issues**: Check logs first
|
||||
|
||||
---
|
||||
|
||||
## Quick Reference
|
||||
|
||||
| Task | Command |
|
||||
|------|---------|
|
||||
| Deploy | `./deploy.sh` |
|
||||
| Start | `docker-compose up -d` |
|
||||
| Stop | `docker-compose down` |
|
||||
| Logs | `docker-compose logs -f` |
|
||||
| Health | `curl http://localhost:9090/health` |
|
||||
| Backup | `cp data/devbench.db backups/` |
|
||||
| Clean | `./cleanup.sh` |
|
||||
|
||||
---
|
||||
|
||||
**Ready to go!** 🚀
|
||||
|
||||
For detailed information, see `README.md` or `/docs` directory.
|
||||
456
asf-cloud-server/TBM_devbench/README.md
Normal file
456
asf-cloud-server/TBM_devbench/README.md
Normal file
@@ -0,0 +1,456 @@
|
||||
# DevBench Manager 🚀
|
||||
|
||||
A modern web application for managing DevBench virtual machines with user authentication, real-time monitoring, and dark theme support.
|
||||
|
||||

|
||||

|
||||

|
||||

|
||||
|
||||
## 📋 Table of Contents
|
||||
|
||||
- [Overview](#overview)
|
||||
- [Features](#features)
|
||||
- [Quick Start](#quick-start)
|
||||
- [Documentation](#documentation)
|
||||
- [Project Structure](#project-structure)
|
||||
- [Configuration](#configuration)
|
||||
- [Usage](#usage)
|
||||
- [API](#api)
|
||||
- [Troubleshooting](#troubleshooting)
|
||||
- [Contributing](#contributing)
|
||||
- [License](#license)
|
||||
|
||||
## 🎯 Overview
|
||||
|
||||
DevBench Manager provides a centralized web interface for creating, managing, and accessing virtual machine development environments. Built with Node.js and Express, it offers real-time monitoring, WebSocket-based updates, and a modern responsive UI with dark theme support.
|
||||
|
||||
## ✨ Features
|
||||
|
||||
### 👤 User Features
|
||||
- 🔐 Secure authentication with session management
|
||||
- 🖥️ Create and manage personal DevBenches
|
||||
- 📊 Real-time status monitoring
|
||||
- 📝 Live log streaming during VM creation
|
||||
- 🔌 Easy access to SSH and VNC connection info
|
||||
- 📚 Comprehensive help page with setup guide
|
||||
- 📥 Download SSH Config Manager tool
|
||||
- 🌓 Dark/Light theme toggle
|
||||
- 📱 Responsive design for mobile and desktop
|
||||
|
||||
### 👨💼 Admin Features
|
||||
- 👥 User management (add, delete, reset passwords)
|
||||
- 🗂️ View all users and their DevBenches
|
||||
- 📈 System-wide DevBench overview
|
||||
- 🔧 Centralized management dashboard
|
||||
- 📊 User activity monitoring
|
||||
|
||||
### 🛠️ Technical Features
|
||||
- ⚡ Real-time WebSocket updates
|
||||
- 🔒 Secure password hashing (bcrypt)
|
||||
- 💾 SQLite database for persistence
|
||||
- 🐳 Docker containerization
|
||||
- 🔄 Automatic status checks (60-second interval)
|
||||
- 🎨 Modern Bootstrap 5 UI
|
||||
- 🌐 Caddy reverse proxy support
|
||||
- 📡 SSH-based VM provisioning
|
||||
- 🔍 Health check endpoint
|
||||
- 📋 Comprehensive logging
|
||||
|
||||
## 🚀 Quick Start
|
||||
|
||||
### Prerequisites
|
||||
|
||||
- **Docker**: Version 20.10+ ([Install Docker](https://docs.docker.com/get-docker/))
|
||||
- **Docker Compose**: Version 2.0+ ([Install Compose](https://docs.docker.com/compose/install/))
|
||||
- **Git**: For cloning the repository
|
||||
- **SSH Access**: To VM host (asf-server.duckdns.org:49152)
|
||||
|
||||
### One-Command Deployment
|
||||
|
||||
```bash
|
||||
# Clone the repository
|
||||
git clone <repository-url>
|
||||
cd ASF_devbench
|
||||
|
||||
# Deploy
|
||||
chmod +x deploy.sh
|
||||
./deploy.sh
|
||||
```
|
||||
|
||||
That's it! 🎉
|
||||
|
||||
### Access the Application
|
||||
|
||||
- **Direct Access**: http://localhost:9090
|
||||
- **Via Caddy Proxy**: https://tbm.nabd-co.com
|
||||
|
||||
### Default Credentials
|
||||
|
||||
```
|
||||
Username: admin
|
||||
Password: admin123
|
||||
```
|
||||
|
||||
⚠️ **Important**: Change the default password after first login!
|
||||
|
||||
### Alternative: Manual Installation
|
||||
|
||||
For development without Docker:
|
||||
|
||||
```bash
|
||||
# Install dependencies
|
||||
npm install
|
||||
|
||||
# Start application
|
||||
npm start
|
||||
|
||||
# Or with auto-reload
|
||||
npm run dev
|
||||
```
|
||||
|
||||
Access at: http://localhost:3001
|
||||
|
||||
## Configuration
|
||||
|
||||
### Caddy Proxy Configuration
|
||||
Add this to your Caddyfile:
|
||||
```
|
||||
tbm.nabd-co.com {
|
||||
reverse_proxy devbench-manager:3001
|
||||
}
|
||||
```
|
||||
|
||||
Make sure your Caddy container is on the same `caddy_network`. If you need to create the network:
|
||||
```bash
|
||||
docker network create caddy_network
|
||||
```
|
||||
|
||||
### Default Admin Account
|
||||
- Username: `admin`
|
||||
- Password: `admin123`
|
||||
- Email: `admin@nabd-co.com`
|
||||
|
||||
**Important:** Change the default admin password after first login!
|
||||
|
||||
## Usage
|
||||
|
||||
### For Administrators
|
||||
1. Login with admin credentials
|
||||
2. Add users through the "Add User" button
|
||||
3. Monitor all DevBenches from the admin dashboard
|
||||
4. Manage users (reset passwords, delete users)
|
||||
|
||||
### For Users
|
||||
1. Login with provided credentials
|
||||
2. Create DevBenches using the "Create DevBench" button
|
||||
3. Monitor DevBench status and connection information
|
||||
4. Activate or delete DevBenches as needed
|
||||
|
||||
### DevBench Naming Rules
|
||||
- DevBench names can only contain letters, numbers, hyphens (-), and underscores (_)
|
||||
- Final DevBench name format: `username_devbenchname`
|
||||
- Example: User "john" creates "test-db" → Final name: "john_test-db"
|
||||
|
||||
### SSH Configuration
|
||||
The application includes a downloadable SSH Config Manager tool (`db_vm_ssh_config_manager.exe`) that helps users configure SSH access to their VMs. The tool:
|
||||
- Configures SSH jump host (asf-jump)
|
||||
- Sets up VM-specific SSH configurations
|
||||
- Generates easy-to-use SSH commands
|
||||
- Available at `/downloads/db_vm_ssh_config_manager.exe`
|
||||
|
||||
Access the help page at `/help` for detailed instructions on using the SSH Config Manager.
|
||||
|
||||
## API Endpoints
|
||||
|
||||
### Authentication
|
||||
- `GET /login` - Login page
|
||||
- `POST /login` - Login submission
|
||||
- `GET /logout` - Logout
|
||||
|
||||
### Admin Routes
|
||||
- `GET /admin` - Admin dashboard
|
||||
- `POST /admin/add-user` - Add new user
|
||||
- `POST /admin/delete-user/:id` - Delete user
|
||||
- `POST /admin/reset-password/:id` - Reset user password
|
||||
|
||||
### User Routes
|
||||
- `GET /dashboard` - User dashboard
|
||||
- `GET /help` - Help page with SSH configuration guide
|
||||
- `POST /create-devbench` - Create new DevBench
|
||||
- `POST /delete-devbench/:id` - Delete DevBench
|
||||
- `POST /activate-devbench/:id` - Activate DevBench
|
||||
- `GET /check-status/:id` - Check DevBench status
|
||||
|
||||
## Database Schema
|
||||
|
||||
### Users Table
|
||||
- `id` - Primary key
|
||||
- `username` - Unique username
|
||||
- `email` - User email
|
||||
- `password` - Hashed password
|
||||
- `is_admin` - Admin flag
|
||||
- `created_at` - Creation timestamp
|
||||
|
||||
### DevBenches Table
|
||||
- `id` - Primary key
|
||||
- `user_id` - Foreign key to users
|
||||
- `name` - DevBench name (user input)
|
||||
- `actual_name` - Actual VM name from script
|
||||
- `status` - Current status (active/inactive/creating)
|
||||
- `ssh_info` - SSH port number
|
||||
- `vnc_info` - VNC port number
|
||||
- `created_at` - Creation timestamp
|
||||
- `updated_at` - Last update timestamp
|
||||
|
||||
## Security Features
|
||||
|
||||
- Password hashing with bcryptjs
|
||||
- Session-based authentication
|
||||
- Input validation and sanitization
|
||||
- Admin-only routes protection
|
||||
- SQL injection prevention with parameterized queries
|
||||
|
||||
## Monitoring
|
||||
|
||||
- Automatic status checking every minute
|
||||
- Real-time WebSocket updates
|
||||
- Live log streaming during DevBench creation
|
||||
- Connection information extraction and display
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Common Issues
|
||||
|
||||
1. **Cannot access via Caddy**:
|
||||
- Ensure `caddy_network` exists: `docker network create caddy_network`
|
||||
- Check if Caddy container is on the same network
|
||||
- Verify Caddyfile configuration points to `devbench-manager:3001`
|
||||
|
||||
2. **Script timeout**: DevBench creation takes up to 30 minutes
|
||||
|
||||
3. **SSH connection issues**: Ensure sshpass is installed and SSH credentials are correct
|
||||
|
||||
4. **Permission issues**: Make sure the provision script is executable
|
||||
|
||||
5. **Database issues**: Check SQLite file permissions
|
||||
|
||||
6. **Container networking**:
|
||||
- Check networks: `docker network ls`
|
||||
- Inspect container: `docker inspect devbench-manager`
|
||||
- Check if both containers are on caddy_network
|
||||
|
||||
### Logs
|
||||
Application logs are available in the container or local environment where the app is running.
|
||||
|
||||
## 📚 Documentation
|
||||
|
||||
Comprehensive documentation is available in the `/docs` directory:
|
||||
|
||||
- **[Architecture](docs/ARCHITECTURE.md)**: System architecture, components, and data flow
|
||||
- **[Structure](docs/STRUCTURE.md)**: Project structure and file descriptions
|
||||
- **[Deployment](docs/DEPLOYMENT.md)**: Detailed deployment guide and troubleshooting
|
||||
- **[API](docs/API.md)**: Complete API reference and examples
|
||||
|
||||
## 📁 Project Structure
|
||||
|
||||
```
|
||||
ASF_devbench/
|
||||
├── docs/ # Documentation
|
||||
│ ├── ARCHITECTURE.md # System architecture
|
||||
│ ├── STRUCTURE.md # Project structure
|
||||
│ ├── DEPLOYMENT.md # Deployment guide
|
||||
│ └── API.md # API documentation
|
||||
├── public/ # Static assets
|
||||
│ ├── css/ # Stylesheets (with dark theme)
|
||||
│ ├── images/ # Logos and icons
|
||||
│ └── downloads/ # SSH Config Manager tool
|
||||
├── views/ # EJS templates
|
||||
│ ├── layout.ejs # Base layout
|
||||
│ ├── login.ejs # Login page
|
||||
│ ├── dashboard.ejs # User dashboard
|
||||
│ ├── admin.ejs # Admin panel
|
||||
│ └── help.ejs # Help page
|
||||
├── data/ # Database (created on deploy)
|
||||
├── logs/ # Application logs
|
||||
├── server.js # Main application
|
||||
├── config.js # Configuration
|
||||
├── provision_vm.sh # VM provisioning script
|
||||
├── deploy.sh # Deployment script
|
||||
├── docker-compose.yml # Container orchestration
|
||||
├── Dockerfile # Container definition
|
||||
└── README.md # This file
|
||||
```
|
||||
|
||||
## 🎨 Theme Support
|
||||
|
||||
DevBench Manager includes a beautiful dark theme:
|
||||
|
||||
- **Toggle**: Click the theme button (bottom-right corner)
|
||||
- **Persistence**: Theme preference saved in browser
|
||||
- **Smooth Transitions**: Animated theme switching
|
||||
- **Full Coverage**: All pages and components themed
|
||||
|
||||
## 🔧 Configuration
|
||||
|
||||
### Environment Variables
|
||||
|
||||
Create a `.env` file:
|
||||
|
||||
```bash
|
||||
NODE_ENV=production
|
||||
PORT=3001
|
||||
SECRET_KEY=your-secure-secret-key
|
||||
ADMIN_EMAIL=admin@yourdomain.com
|
||||
ADMIN_PASSWORD=your-secure-password
|
||||
```
|
||||
|
||||
### Docker Configuration
|
||||
|
||||
Edit `docker-compose.yml` to customize:
|
||||
|
||||
```yaml
|
||||
ports:
|
||||
- "9090:3001" # Change external port
|
||||
environment:
|
||||
- NODE_ENV=production
|
||||
- SECRET_KEY=${SECRET_KEY}
|
||||
volumes:
|
||||
- ./data:/app/data
|
||||
- ./logs:/app/logs
|
||||
```
|
||||
|
||||
### Caddy Reverse Proxy
|
||||
|
||||
Add to your Caddyfile:
|
||||
|
||||
```
|
||||
tbm.nabd-co.com {
|
||||
reverse_proxy devbench-manager:3001
|
||||
}
|
||||
```
|
||||
|
||||
## 📖 Usage
|
||||
|
||||
### For Users
|
||||
|
||||
1. **Login**: Use provided credentials
|
||||
2. **Create DevBench**: Click "Create DevBench" button
|
||||
3. **Monitor Progress**: Watch real-time log output
|
||||
4. **Access VM**: Use displayed SSH/VNC ports
|
||||
5. **Get Help**: Click help icon for setup guide
|
||||
|
||||
### For Administrators
|
||||
|
||||
1. **Add Users**: Click "Add User" in admin panel
|
||||
2. **Manage DevBenches**: View and delete any DevBench
|
||||
3. **Reset Passwords**: Reset user passwords as needed
|
||||
4. **Monitor System**: View all users and their activity
|
||||
|
||||
### SSH Configuration
|
||||
|
||||
Download the SSH Config Manager tool from the help page to easily configure SSH access to your VMs.
|
||||
|
||||
## 🔌 API
|
||||
|
||||
### Health Check
|
||||
|
||||
```bash
|
||||
curl http://localhost:9090/health
|
||||
```
|
||||
|
||||
### Create DevBench
|
||||
|
||||
```bash
|
||||
curl -X POST http://localhost:9090/create-devbench \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"name":"my-vm"}'
|
||||
```
|
||||
|
||||
See [API Documentation](docs/API.md) for complete reference.
|
||||
|
||||
## 🐛 Troubleshooting
|
||||
|
||||
### Container Won't Start
|
||||
|
||||
```bash
|
||||
# Check logs
|
||||
docker-compose logs
|
||||
|
||||
# Verify port availability
|
||||
sudo lsof -i :9090
|
||||
|
||||
# Check permissions
|
||||
sudo chown -R $USER:$USER data logs
|
||||
```
|
||||
|
||||
### Cannot Access Web Interface
|
||||
|
||||
```bash
|
||||
# Verify container is running
|
||||
docker ps | grep devbench-manager
|
||||
|
||||
# Test health endpoint
|
||||
curl http://localhost:9090/health
|
||||
|
||||
# Check firewall
|
||||
sudo ufw allow 9090/tcp
|
||||
```
|
||||
|
||||
### SSH Connection Fails
|
||||
|
||||
```bash
|
||||
# Test SSH manually
|
||||
ssh -p 49152 asf@asf-server.duckdns.org
|
||||
|
||||
# Check from container
|
||||
docker exec devbench-manager ./provision_vm.sh status test_vm
|
||||
```
|
||||
|
||||
See [Deployment Guide](docs/DEPLOYMENT.md) for more troubleshooting steps.
|
||||
|
||||
## 🔄 Updating
|
||||
|
||||
```bash
|
||||
# Pull latest changes
|
||||
git pull origin main
|
||||
|
||||
# Backup database
|
||||
cp data/devbench.db data/devbench.db.backup
|
||||
|
||||
# Redeploy
|
||||
./deploy.sh
|
||||
```
|
||||
|
||||
## 🤝 Contributing
|
||||
|
||||
Contributions are welcome! Please:
|
||||
|
||||
1. Fork the repository
|
||||
2. Create a feature branch
|
||||
3. Make your changes
|
||||
4. Test thoroughly
|
||||
5. Submit a pull request
|
||||
|
||||
## 📝 License
|
||||
|
||||
MIT License - see LICENSE file for details
|
||||
|
||||
## 🙏 Acknowledgments
|
||||
|
||||
- Built with [Express.js](https://expressjs.com/)
|
||||
- UI powered by [Bootstrap 5](https://getbootstrap.com/)
|
||||
- Icons from [Font Awesome](https://fontawesome.com/)
|
||||
- Reverse proxy by [Caddy](https://caddyserver.com/)
|
||||
|
||||
## 📞 Support
|
||||
|
||||
For issues, questions, or contributions:
|
||||
|
||||
- 📧 Email: admin@nabd-co.com
|
||||
- 📚 Documentation: `/docs` directory
|
||||
- 🐛 Issues: GitHub Issues (if applicable)
|
||||
|
||||
---
|
||||
|
||||
Made with ❤️ by NABD Solutions
|
||||
462
asf-cloud-server/TBM_devbench/UPDATES_SUMMARY.md
Normal file
462
asf-cloud-server/TBM_devbench/UPDATES_SUMMARY.md
Normal file
@@ -0,0 +1,462 @@
|
||||
# DevBench Manager - Final Updates Summary
|
||||
|
||||
## 🎉 Completed Updates
|
||||
|
||||
### 1. ✅ Dark Theme Implementation
|
||||
|
||||
**Added Files:**
|
||||
- Enhanced `public/css/style.css` with comprehensive dark theme styles
|
||||
|
||||
**Modified Files:**
|
||||
- `views/layout.ejs`: Added theme toggle button and JavaScript
|
||||
|
||||
**Features:**
|
||||
- 🌓 Floating theme toggle button (bottom-right)
|
||||
- 💾 Theme preference saved in localStorage
|
||||
- 🎨 Complete dark theme for all components
|
||||
- ✨ Smooth transitions between themes
|
||||
- 🌙 Moon/Sun icon toggle
|
||||
|
||||
**Usage:**
|
||||
- Click the floating button in bottom-right corner
|
||||
- Theme preference persists across sessions
|
||||
- Works on all pages (dashboard, admin, help, login)
|
||||
|
||||
---
|
||||
|
||||
### 2. ✅ Script Cleanup
|
||||
|
||||
**Removed Scripts:**
|
||||
- ❌ `check-network.sh` (debug script)
|
||||
- ❌ `debug-deploy.sh` (debug script)
|
||||
- ❌ `deploy.sh` (old version)
|
||||
- ❌ `test-script.sh` (debug script)
|
||||
- ❌ `update-ui.sh` (debug script)
|
||||
- ❌ `fix-deployment.sh` (debug script)
|
||||
- ❌ `DEPLOYMENT.md` (old, replaced with docs/)
|
||||
|
||||
**Renamed Scripts:**
|
||||
- ✅ `deploy-final.sh` → `deploy.sh` (simplified name)
|
||||
|
||||
**Kept Scripts:**
|
||||
- ✅ `deploy.sh` - Main deployment script
|
||||
- ✅ `install.sh` - Local installation
|
||||
- ✅ `start.sh` - Start application
|
||||
- ✅ `cleanup.sh` - Container cleanup
|
||||
- ✅ `provision_vm.sh` - VM provisioning
|
||||
|
||||
**Result:**
|
||||
- Clean, organized project structure
|
||||
- Only essential scripts remain
|
||||
- Clear purpose for each script
|
||||
|
||||
---
|
||||
|
||||
### 3. ✅ Comprehensive Documentation
|
||||
|
||||
**Created `/docs` Folder:**
|
||||
|
||||
#### `docs/ARCHITECTURE.md`
|
||||
- 📐 System architecture diagrams
|
||||
- 🏗️ Component descriptions
|
||||
- 🔄 Data flow diagrams
|
||||
- 🔒 Security architecture
|
||||
- 📊 Technology stack
|
||||
- 🚀 Deployment architecture
|
||||
- ⚡ Performance considerations
|
||||
- 🛠️ Maintenance guidelines
|
||||
|
||||
#### `docs/STRUCTURE.md`
|
||||
- 📁 Complete directory tree
|
||||
- 📄 File descriptions
|
||||
- 🔗 Data flow through files
|
||||
- ⚙️ Configuration hierarchy
|
||||
- 🌐 Network architecture
|
||||
- 🔐 File permissions
|
||||
- 🏗️ Build process
|
||||
- ⚡ Runtime process
|
||||
|
||||
#### `docs/DEPLOYMENT.md`
|
||||
- 📋 Prerequisites checklist
|
||||
- 🚀 Quick start guide
|
||||
- 📝 Detailed deployment steps
|
||||
- ⚙️ Configuration options
|
||||
- ✅ Verification procedures
|
||||
- 🐛 Troubleshooting guide
|
||||
- 🔧 Maintenance tasks
|
||||
- 💾 Backup & recovery
|
||||
- 🔒 Security hardening
|
||||
- 📊 Monitoring setup
|
||||
|
||||
#### `docs/API.md`
|
||||
- 🔌 Complete API reference
|
||||
- 📡 WebSocket documentation
|
||||
- 🔐 Authentication details
|
||||
- 📊 Data models
|
||||
- 💡 Usage examples
|
||||
- 🔒 Security considerations
|
||||
- 📝 Error responses
|
||||
- 🚀 Rate limiting (future)
|
||||
|
||||
**Documentation Features:**
|
||||
- Clear, structured format
|
||||
- Code examples included
|
||||
- Diagrams and visual aids
|
||||
- Troubleshooting sections
|
||||
- Best practices
|
||||
- Security guidelines
|
||||
|
||||
---
|
||||
|
||||
### 4. ✅ Enhanced README.md
|
||||
|
||||
**New Sections:**
|
||||
- 🎯 Overview with badges
|
||||
- 📋 Table of contents
|
||||
- ✨ Feature highlights with emojis
|
||||
- 🚀 Quick start guide
|
||||
- 📚 Documentation links
|
||||
- 📁 Project structure
|
||||
- 🎨 Theme support
|
||||
- 🔧 Configuration guide
|
||||
- 📖 Usage instructions
|
||||
- 🔌 API quick reference
|
||||
- 🐛 Troubleshooting
|
||||
- 🔄 Update instructions
|
||||
- 🤝 Contributing guidelines
|
||||
- 📞 Support information
|
||||
|
||||
**Improvements:**
|
||||
- More visual and engaging
|
||||
- Better organized
|
||||
- Quick access to information
|
||||
- Clear deployment steps
|
||||
- Professional presentation
|
||||
|
||||
---
|
||||
|
||||
## 📊 Project Statistics
|
||||
|
||||
### Files Summary
|
||||
|
||||
**Total Files:** 25 core files
|
||||
|
||||
**By Category:**
|
||||
- Documentation: 5 files (README.md + 4 in docs/)
|
||||
- Source Code: 2 files (server.js, config.js)
|
||||
- Templates: 5 files (views/*.ejs)
|
||||
- Static Assets: 6 files (CSS, images, downloads)
|
||||
- Scripts: 5 files (deploy, install, start, cleanup, provision)
|
||||
- Configuration: 5 files (package.json, Dockerfile, docker-compose.yml, .env.example, .gitignore)
|
||||
|
||||
### Lines of Code
|
||||
|
||||
**Estimated:**
|
||||
- JavaScript: ~1,500 lines
|
||||
- EJS Templates: ~800 lines
|
||||
- CSS: ~500 lines
|
||||
- Shell Scripts: ~400 lines
|
||||
- Documentation: ~3,000 lines
|
||||
- **Total: ~6,200 lines**
|
||||
|
||||
---
|
||||
|
||||
## 🎨 Theme Implementation Details
|
||||
|
||||
### Color Scheme
|
||||
|
||||
**Light Theme:**
|
||||
- Background: #ffffff
|
||||
- Text: #212529
|
||||
- Primary: #1a365d (NABD Blue)
|
||||
- Accent: #f39c12 (NABD Orange)
|
||||
|
||||
**Dark Theme:**
|
||||
- Background: #1a1a1a
|
||||
- Text: #e0e0e0
|
||||
- Cards: #2d2d2d
|
||||
- Primary: #1a365d
|
||||
- Accent: #f39c12
|
||||
|
||||
### Components Themed
|
||||
|
||||
✅ Navbar
|
||||
✅ Cards
|
||||
✅ Tables
|
||||
✅ Forms
|
||||
✅ Modals
|
||||
✅ Buttons
|
||||
✅ Alerts
|
||||
✅ Code blocks
|
||||
✅ Connection info
|
||||
✅ Help page
|
||||
✅ Login page
|
||||
|
||||
---
|
||||
|
||||
## 📁 Final Project Structure
|
||||
|
||||
```
|
||||
ASF_devbench/
|
||||
├── docs/ ✨ NEW
|
||||
│ ├── ARCHITECTURE.md ✨ NEW
|
||||
│ ├── STRUCTURE.md ✨ NEW
|
||||
│ ├── DEPLOYMENT.md ✨ NEW
|
||||
│ └── API.md ✨ NEW
|
||||
├── public/
|
||||
│ ├── css/
|
||||
│ │ └── style.css 🔄 UPDATED (dark theme)
|
||||
│ ├── images/
|
||||
│ │ ├── nabd-logo.svg
|
||||
│ │ ├── nabd-logo-white.svg
|
||||
│ │ └── tbm-icon.png
|
||||
│ └── downloads/
|
||||
│ └── db_vm_ssh_config_manager.exe
|
||||
├── views/
|
||||
│ ├── layout.ejs 🔄 UPDATED (theme toggle)
|
||||
│ ├── login.ejs
|
||||
│ ├── dashboard.ejs
|
||||
│ ├── admin.ejs
|
||||
│ └── help.ejs
|
||||
├── server.js
|
||||
├── config.js
|
||||
├── provision_vm.sh
|
||||
├── deploy.sh 🔄 RENAMED (from deploy-final.sh)
|
||||
├── install.sh
|
||||
├── start.sh
|
||||
├── cleanup.sh
|
||||
├── docker-compose.yml
|
||||
├── Dockerfile
|
||||
├── package.json
|
||||
├── .env.example
|
||||
├── .gitignore
|
||||
├── README.md 🔄 UPDATED (comprehensive)
|
||||
├── CHANGELOG.md
|
||||
└── UPDATES_SUMMARY.md ✨ NEW (this file)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🚀 Deployment Instructions
|
||||
|
||||
### Quick Deploy
|
||||
|
||||
```bash
|
||||
# 1. Navigate to project
|
||||
cd ASF_devbench
|
||||
|
||||
# 2. Deploy
|
||||
./deploy.sh
|
||||
|
||||
# 3. Access
|
||||
# http://localhost:9090
|
||||
# https://tbm.nabd-co.com
|
||||
```
|
||||
|
||||
### What Gets Deployed
|
||||
|
||||
✅ Docker container with Node.js app
|
||||
✅ SQLite database (persistent)
|
||||
✅ Application logs (persistent)
|
||||
✅ Provision script (mounted)
|
||||
✅ Static assets (CSS, images, downloads)
|
||||
✅ All views and templates
|
||||
✅ Dark theme support
|
||||
✅ WebSocket server
|
||||
✅ Health check endpoint
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Key Features Summary
|
||||
|
||||
### User Experience
|
||||
- 🌓 Dark/Light theme toggle
|
||||
- 📱 Responsive design
|
||||
- ⚡ Real-time updates
|
||||
- 📊 Live log streaming
|
||||
- 🔌 Easy connection info
|
||||
- 📚 Comprehensive help
|
||||
|
||||
### Administration
|
||||
- 👥 User management
|
||||
- 🗂️ DevBench overview
|
||||
- 🔧 System monitoring
|
||||
- 📈 Activity tracking
|
||||
|
||||
### Technical
|
||||
- 🐳 Docker containerized
|
||||
- 🔒 Secure authentication
|
||||
- 💾 Persistent storage
|
||||
- 🌐 Reverse proxy ready
|
||||
- 📡 WebSocket support
|
||||
- 🔍 Health monitoring
|
||||
|
||||
---
|
||||
|
||||
## 📚 Documentation Access
|
||||
|
||||
All documentation is in the `/docs` folder:
|
||||
|
||||
1. **Architecture**: `docs/ARCHITECTURE.md`
|
||||
- System design and components
|
||||
- Data flow and security
|
||||
|
||||
2. **Structure**: `docs/STRUCTURE.md`
|
||||
- File organization
|
||||
- Code structure
|
||||
|
||||
3. **Deployment**: `docs/DEPLOYMENT.md`
|
||||
- Installation guide
|
||||
- Troubleshooting
|
||||
|
||||
4. **API**: `docs/API.md`
|
||||
- Endpoint reference
|
||||
- Examples
|
||||
|
||||
---
|
||||
|
||||
## ✅ Verification Checklist
|
||||
|
||||
### Before Deployment
|
||||
- [ ] Docker and Docker Compose installed
|
||||
- [ ] SSH access to VM host verified
|
||||
- [ ] Port 9090 available
|
||||
- [ ] Caddy network created (if using proxy)
|
||||
|
||||
### After Deployment
|
||||
- [ ] Container running: `docker ps | grep devbench-manager`
|
||||
- [ ] Health check: `curl http://localhost:9090/health`
|
||||
- [ ] Web interface accessible
|
||||
- [ ] Login works with default credentials
|
||||
- [ ] Theme toggle works
|
||||
- [ ] Help page accessible
|
||||
- [ ] SSH Config Manager downloadable
|
||||
|
||||
### Testing
|
||||
- [ ] Create test DevBench
|
||||
- [ ] Monitor real-time logs
|
||||
- [ ] Check connection info
|
||||
- [ ] Test dark theme
|
||||
- [ ] Verify admin panel
|
||||
- [ ] Test user management
|
||||
|
||||
---
|
||||
|
||||
## 🔄 Migration Notes
|
||||
|
||||
### From Previous Version
|
||||
|
||||
**No breaking changes!**
|
||||
|
||||
All existing data and functionality preserved:
|
||||
- ✅ Database schema unchanged
|
||||
- ✅ API endpoints unchanged
|
||||
- ✅ Authentication unchanged
|
||||
- ✅ DevBench management unchanged
|
||||
|
||||
**New additions:**
|
||||
- ✨ Dark theme (optional)
|
||||
- 📚 Documentation (reference)
|
||||
- 🧹 Cleaner project structure
|
||||
|
||||
---
|
||||
|
||||
## 🎓 Learning Resources
|
||||
|
||||
### For Developers
|
||||
- Read `docs/ARCHITECTURE.md` for system design
|
||||
- Read `docs/STRUCTURE.md` for code organization
|
||||
- Read `docs/API.md` for endpoint details
|
||||
|
||||
### For Operators
|
||||
- Read `docs/DEPLOYMENT.md` for deployment
|
||||
- Read `README.md` for quick start
|
||||
- Check `CHANGELOG.md` for updates
|
||||
|
||||
### For Users
|
||||
- Click Help icon in navbar
|
||||
- Download SSH Config Manager
|
||||
- Follow step-by-step guide
|
||||
|
||||
---
|
||||
|
||||
## 🎉 What's New
|
||||
|
||||
### Version 1.0.0 (2025-11-23)
|
||||
|
||||
**Major Updates:**
|
||||
1. 🌓 Dark theme support with toggle
|
||||
2. 📚 Comprehensive documentation (4 guides)
|
||||
3. 🧹 Cleaned up debug scripts
|
||||
4. 📖 Enhanced README with quick start
|
||||
5. 🎨 Improved visual design
|
||||
6. 📊 Better project organization
|
||||
|
||||
**Technical Improvements:**
|
||||
- Cleaner codebase
|
||||
- Better documentation
|
||||
- Easier deployment
|
||||
- Professional presentation
|
||||
|
||||
---
|
||||
|
||||
## 🚀 Next Steps
|
||||
|
||||
### Immediate
|
||||
1. Deploy using `./deploy.sh`
|
||||
2. Change default admin password
|
||||
3. Add users
|
||||
4. Create test DevBenches
|
||||
|
||||
### Short Term
|
||||
- Configure Caddy reverse proxy
|
||||
- Set up SSL/TLS
|
||||
- Configure backups
|
||||
- Monitor logs
|
||||
|
||||
### Long Term
|
||||
- Consider PostgreSQL migration
|
||||
- Implement rate limiting
|
||||
- Add CSRF protection
|
||||
- Set up monitoring dashboard
|
||||
|
||||
---
|
||||
|
||||
## 📞 Support
|
||||
|
||||
**Documentation:**
|
||||
- README.md - Quick start
|
||||
- docs/ARCHITECTURE.md - System design
|
||||
- docs/STRUCTURE.md - Code structure
|
||||
- docs/DEPLOYMENT.md - Deployment guide
|
||||
- docs/API.md - API reference
|
||||
|
||||
**Contact:**
|
||||
- Email: admin@nabd-co.com
|
||||
- Help Page: Click help icon in app
|
||||
|
||||
---
|
||||
|
||||
## 🎊 Conclusion
|
||||
|
||||
All requested updates have been successfully implemented:
|
||||
|
||||
✅ **Dark Theme**: Fully functional with toggle button
|
||||
✅ **Script Cleanup**: Removed all debug scripts
|
||||
✅ **Documentation**: 4 comprehensive guides created
|
||||
✅ **README**: Enhanced with quick start and structure
|
||||
✅ **Project Organization**: Clean and professional
|
||||
|
||||
The DevBench Manager is now production-ready with:
|
||||
- Modern UI with theme support
|
||||
- Comprehensive documentation
|
||||
- Clean project structure
|
||||
- Easy deployment process
|
||||
- Professional presentation
|
||||
|
||||
**Ready to deploy!** 🚀
|
||||
|
||||
---
|
||||
|
||||
*Last Updated: November 23, 2025*
|
||||
*Version: 1.0.0*
|
||||
23
asf-cloud-server/TBM_devbench/cleanup.sh
Normal file
23
asf-cloud-server/TBM_devbench/cleanup.sh
Normal file
@@ -0,0 +1,23 @@
|
||||
#!/bin/bash
|
||||
|
||||
echo "Cleaning up DevBench Manager containers and volumes..."
|
||||
|
||||
# Stop and remove containers
|
||||
echo "Stopping containers..."
|
||||
docker-compose down --remove-orphans
|
||||
|
||||
# Remove any existing containers with the same name
|
||||
echo "Removing existing containers..."
|
||||
docker rm -f devbench-manager 2>/dev/null || true
|
||||
|
||||
# Remove dangling containers
|
||||
echo "Removing dangling containers..."
|
||||
docker container prune -f
|
||||
|
||||
# List and optionally remove volumes
|
||||
echo "Current volumes:"
|
||||
docker volume ls | grep devbench
|
||||
|
||||
echo ""
|
||||
echo "Cleanup complete!"
|
||||
echo "You can now run: docker-compose up -d --build"
|
||||
42
asf-cloud-server/TBM_devbench/config.js
Normal file
42
asf-cloud-server/TBM_devbench/config.js
Normal file
@@ -0,0 +1,42 @@
|
||||
module.exports = {
|
||||
// Server configuration
|
||||
port: process.env.PORT || 3001,
|
||||
|
||||
// Database configuration
|
||||
database: {
|
||||
path: process.env.DB_PATH || './data/devbench.db'
|
||||
},
|
||||
|
||||
// Session configuration
|
||||
session: {
|
||||
secret: process.env.SESSION_SECRET || 'devbench-secret-key',
|
||||
maxAge: 24 * 60 * 60 * 1000 // 24 hours
|
||||
},
|
||||
|
||||
// SSH configuration for provision script
|
||||
ssh: {
|
||||
user: process.env.SSH_USER || 'asf',
|
||||
host: process.env.SSH_HOST || 'asf-tb.duckdns.org',
|
||||
password: process.env.SSH_PASS || 'ASF'
|
||||
},
|
||||
|
||||
// Provision script configuration
|
||||
provision: {
|
||||
scriptPath: process.env.PROVISION_SCRIPT || './provision_vm.sh',
|
||||
timeout: 30 * 60 * 1000, // 30 minutes
|
||||
statusCheckInterval: 60 * 1000 // 1 minute
|
||||
},
|
||||
|
||||
// Default admin user
|
||||
defaultAdmin: {
|
||||
username: 'admin',
|
||||
email: process.env.ADMIN_EMAIL || 'admin@nabd-co.com',
|
||||
password: process.env.ADMIN_PASSWORD || 'admin123'
|
||||
},
|
||||
|
||||
// Validation rules
|
||||
validation: {
|
||||
username: /^[a-zA-Z]+$/, // Only letters for admin usernames
|
||||
devbenchName: /^[a-zA-Z0-9_-]+$/ // Letters, numbers, hyphens, underscores for devbench names
|
||||
}
|
||||
};
|
||||
BIN
asf-cloud-server/TBM_devbench/db_vm_ssh_config_manager.exe
Normal file
BIN
asf-cloud-server/TBM_devbench/db_vm_ssh_config_manager.exe
Normal file
Binary file not shown.
81
asf-cloud-server/TBM_devbench/deploy.sh
Normal file
81
asf-cloud-server/TBM_devbench/deploy.sh
Normal file
@@ -0,0 +1,81 @@
|
||||
#!/bin/bash
|
||||
|
||||
echo "🚀 Deploying DevBench Manager with all improvements..."
|
||||
|
||||
# Create directories
|
||||
echo "📁 Creating directories..."
|
||||
mkdir -p data logs public/css public/images public/downloads
|
||||
|
||||
# Make scripts executable
|
||||
echo "🔧 Setting permissions..."
|
||||
chmod +x provision_vm.sh *.sh
|
||||
|
||||
# Clean up containers
|
||||
echo "🧹 Cleaning up existing containers..."
|
||||
docker-compose down --remove-orphans 2>/dev/null || true
|
||||
docker rm -f devbench-manager 2>/dev/null || true
|
||||
|
||||
# Create network
|
||||
if ! docker network ls | grep -q caddy_network; then
|
||||
echo "🌐 Creating caddy_network..."
|
||||
docker network create caddy_network
|
||||
else
|
||||
echo "✅ caddy_network already exists"
|
||||
fi
|
||||
|
||||
# Build and deploy
|
||||
echo "🏗️ Building and starting container..."
|
||||
docker-compose up -d --build
|
||||
|
||||
# Wait for startup
|
||||
echo "⏳ Waiting for container startup..."
|
||||
sleep 15
|
||||
|
||||
# Check status
|
||||
if docker ps | grep -q devbench-manager; then
|
||||
echo ""
|
||||
echo "🎉 SUCCESS! DevBench Manager is running with latest updates:"
|
||||
echo ""
|
||||
echo "✅ Updated SSH access (asf@asf-server.duckdns.org:49152)"
|
||||
echo "✅ Simplified connection info (SSH Port & VNC Port only)"
|
||||
echo "✅ Added Help page with SSH Config Manager guide"
|
||||
echo "✅ Added TBM icon branding"
|
||||
echo "✅ SSH Config Manager tool available for download"
|
||||
echo "✅ Enhanced UI with copy-to-clipboard functionality"
|
||||
echo ""
|
||||
echo "🌐 Access Points:"
|
||||
echo " Direct: http://localhost:9090"
|
||||
echo " Via Caddy: https://tbm.nabd-co.com"
|
||||
echo ""
|
||||
echo "🔐 Default Login:"
|
||||
echo " Username: admin"
|
||||
echo " Password: admin123"
|
||||
echo ""
|
||||
echo "🛠️ Useful Commands:"
|
||||
echo " Check logs: docker-compose logs -f"
|
||||
echo " Check health: curl http://localhost:9090/health"
|
||||
echo " Stop: docker-compose down"
|
||||
|
||||
# Test health endpoint
|
||||
echo ""
|
||||
echo "🏥 Health Check:"
|
||||
if curl -s http://localhost:9090/health > /dev/null; then
|
||||
echo "✅ Application is healthy"
|
||||
else
|
||||
echo "⚠️ Health check failed (may need more time)"
|
||||
fi
|
||||
|
||||
else
|
||||
echo ""
|
||||
echo "❌ DEPLOYMENT FAILED!"
|
||||
echo ""
|
||||
echo "📋 Checking logs:"
|
||||
docker-compose logs --tail=20
|
||||
echo ""
|
||||
echo "🔍 Container status:"
|
||||
docker ps -a | grep devbench-manager
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "🎯 Deployment completed successfully!"
|
||||
33
asf-cloud-server/TBM_devbench/docker-compose.yml
Normal file
33
asf-cloud-server/TBM_devbench/docker-compose.yml
Normal file
@@ -0,0 +1,33 @@
|
||||
version: '3.8'
|
||||
|
||||
services:
|
||||
devbench-manager:
|
||||
build: .
|
||||
container_name: devbench-manager
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "9090:3001"
|
||||
environment:
|
||||
- NODE_ENV=production
|
||||
- PORT=3001
|
||||
- SECRET_KEY=${SECRET_KEY:-dev-secret-key-change-in-production}
|
||||
- ADMIN_EMAIL=${ADMIN_EMAIL:-admin@nabd-co.com}
|
||||
- ADMIN_PASSWORD=${ADMIN_PASSWORD:-admin123}
|
||||
volumes:
|
||||
- ./data:/app/data
|
||||
- ./logs:/app/logs
|
||||
- ./provision_vm.sh:/app/provision_vm.sh:ro
|
||||
healthcheck:
|
||||
test: [ "CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:3001/health" ]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
networks:
|
||||
- app-network
|
||||
- caddy_network
|
||||
|
||||
networks:
|
||||
app-network:
|
||||
driver: bridge
|
||||
caddy_network:
|
||||
external: true
|
||||
649
asf-cloud-server/TBM_devbench/docs/API.md
Normal file
649
asf-cloud-server/TBM_devbench/docs/API.md
Normal file
@@ -0,0 +1,649 @@
|
||||
# DevBench Manager - API Documentation
|
||||
|
||||
## Base URL
|
||||
|
||||
```
|
||||
http://localhost:9090
|
||||
https://tbm.nabd-co.com
|
||||
```
|
||||
|
||||
## Authentication
|
||||
|
||||
All API endpoints (except `/login` and `/health`) require authentication via session cookies.
|
||||
|
||||
### Session Cookie
|
||||
- **Name**: `connect.sid`
|
||||
- **Type**: HTTP-only
|
||||
- **Secure**: true (in production with HTTPS)
|
||||
- **SameSite**: Lax
|
||||
|
||||
## Endpoints
|
||||
|
||||
### Health Check
|
||||
|
||||
#### GET /health
|
||||
|
||||
Check application health status.
|
||||
|
||||
**Authentication**: Not required
|
||||
|
||||
**Response**:
|
||||
```json
|
||||
{
|
||||
"status": "ok",
|
||||
"timestamp": "2025-11-23T12:00:00.000Z",
|
||||
"version": "1.0.0"
|
||||
}
|
||||
```
|
||||
|
||||
**Status Codes**:
|
||||
- `200`: Application is healthy
|
||||
|
||||
---
|
||||
|
||||
### Authentication
|
||||
|
||||
#### GET /login
|
||||
|
||||
Display login page.
|
||||
|
||||
**Authentication**: Not required
|
||||
|
||||
**Response**: HTML page
|
||||
|
||||
---
|
||||
|
||||
#### POST /login
|
||||
|
||||
Authenticate user and create session.
|
||||
|
||||
**Authentication**: Not required
|
||||
|
||||
**Request Body**:
|
||||
```json
|
||||
{
|
||||
"username": "admin",
|
||||
"password": "admin123"
|
||||
}
|
||||
```
|
||||
|
||||
**Response**: Redirect to `/dashboard` or `/admin`
|
||||
|
||||
**Status Codes**:
|
||||
- `302`: Success, redirect to dashboard
|
||||
- `200`: Failed, re-render login with error
|
||||
|
||||
---
|
||||
|
||||
#### GET /logout
|
||||
|
||||
Destroy session and logout user.
|
||||
|
||||
**Authentication**: Required
|
||||
|
||||
**Response**: Redirect to `/login`
|
||||
|
||||
**Status Codes**:
|
||||
- `302`: Success, redirect to login
|
||||
|
||||
---
|
||||
|
||||
### User Dashboard
|
||||
|
||||
#### GET /dashboard
|
||||
|
||||
Display user's DevBench dashboard.
|
||||
|
||||
**Authentication**: Required (User role)
|
||||
|
||||
**Response**: HTML page with user's DevBenches
|
||||
|
||||
**Status Codes**:
|
||||
- `200`: Success
|
||||
- `302`: Redirect to `/admin` if user is admin
|
||||
- `302`: Redirect to `/login` if not authenticated
|
||||
|
||||
---
|
||||
|
||||
#### GET /help
|
||||
|
||||
Display help page with SSH configuration guide.
|
||||
|
||||
**Authentication**: Required
|
||||
|
||||
**Response**: HTML page with documentation
|
||||
|
||||
**Status Codes**:
|
||||
- `200`: Success
|
||||
- `302`: Redirect to `/login` if not authenticated
|
||||
|
||||
---
|
||||
|
||||
### DevBench Management
|
||||
|
||||
#### POST /create-devbench
|
||||
|
||||
Create a new DevBench.
|
||||
|
||||
**Authentication**: Required (User role)
|
||||
|
||||
**Request Body**:
|
||||
```json
|
||||
{
|
||||
"name": "test-vm"
|
||||
}
|
||||
```
|
||||
|
||||
**Validation**:
|
||||
- Name must match pattern: `[a-zA-Z0-9_-]+`
|
||||
- Name must be unique for user
|
||||
|
||||
**Response**:
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"devbenchId": 123
|
||||
}
|
||||
```
|
||||
|
||||
**Error Response**:
|
||||
```json
|
||||
{
|
||||
"error": "DevBench with this name already exists"
|
||||
}
|
||||
```
|
||||
|
||||
**Status Codes**:
|
||||
- `200`: Success
|
||||
- `400`: Validation error
|
||||
- `500`: Database error
|
||||
|
||||
**Side Effects**:
|
||||
- Creates database record with status "creating"
|
||||
- Spawns provision script execution
|
||||
- Sends real-time updates via WebSocket
|
||||
|
||||
---
|
||||
|
||||
#### POST /delete-devbench/:id
|
||||
|
||||
Delete a DevBench.
|
||||
|
||||
**Authentication**: Required (User role, owner only)
|
||||
|
||||
**Parameters**:
|
||||
- `id`: DevBench ID (integer)
|
||||
|
||||
**Response**:
|
||||
```json
|
||||
{
|
||||
"success": true
|
||||
}
|
||||
```
|
||||
|
||||
**Error Response**:
|
||||
```json
|
||||
{
|
||||
"error": "DevBench not found"
|
||||
}
|
||||
```
|
||||
|
||||
**Status Codes**:
|
||||
- `200`: Success
|
||||
- `404`: DevBench not found
|
||||
- `500`: Database error
|
||||
|
||||
**Side Effects**:
|
||||
- Executes delete command on remote host
|
||||
- Removes database record
|
||||
|
||||
---
|
||||
|
||||
#### POST /activate-devbench/:id
|
||||
|
||||
Activate a DevBench.
|
||||
|
||||
**Authentication**: Required (User role, owner only)
|
||||
|
||||
**Parameters**:
|
||||
- `id`: DevBench ID (integer)
|
||||
|
||||
**Response**:
|
||||
```json
|
||||
{
|
||||
"success": true
|
||||
}
|
||||
```
|
||||
|
||||
**Error Response**:
|
||||
```json
|
||||
{
|
||||
"error": "DevBench not found"
|
||||
}
|
||||
```
|
||||
|
||||
**Status Codes**:
|
||||
- `200`: Success
|
||||
- `404`: DevBench not found
|
||||
|
||||
**Side Effects**:
|
||||
- Executes activate command on remote host
|
||||
- Updates status via WebSocket
|
||||
|
||||
---
|
||||
|
||||
#### GET /check-status/:id
|
||||
|
||||
Check DevBench status.
|
||||
|
||||
**Authentication**: Required (User role, owner only)
|
||||
|
||||
**Parameters**:
|
||||
- `id`: DevBench ID (integer)
|
||||
|
||||
**Response**:
|
||||
```json
|
||||
{
|
||||
"status": "active"
|
||||
}
|
||||
```
|
||||
|
||||
**Possible Status Values**:
|
||||
- `active`: VM is running
|
||||
- `inactive`: VM is stopped
|
||||
- `creating`: VM is being created
|
||||
- `error`: VM creation failed
|
||||
- `unknown`: Status cannot be determined
|
||||
|
||||
**Status Codes**:
|
||||
- `200`: Success
|
||||
|
||||
**Side Effects**:
|
||||
- Updates database with current status
|
||||
|
||||
---
|
||||
|
||||
### Admin Panel
|
||||
|
||||
#### GET /admin
|
||||
|
||||
Display admin dashboard.
|
||||
|
||||
**Authentication**: Required (Admin role)
|
||||
|
||||
**Response**: HTML page with users and DevBenches
|
||||
|
||||
**Status Codes**:
|
||||
- `200`: Success
|
||||
- `403`: Access denied (not admin)
|
||||
- `302`: Redirect to `/login` if not authenticated
|
||||
|
||||
---
|
||||
|
||||
#### POST /admin/add-user
|
||||
|
||||
Create a new user.
|
||||
|
||||
**Authentication**: Required (Admin role)
|
||||
|
||||
**Request Body**:
|
||||
```json
|
||||
{
|
||||
"username": "john",
|
||||
"email": "john@example.com",
|
||||
"password": "password123"
|
||||
}
|
||||
```
|
||||
|
||||
**Validation**:
|
||||
- Username must match pattern: `[a-zA-Z]+` (letters only)
|
||||
- Username must be unique
|
||||
- Email must be valid format
|
||||
- Password required
|
||||
|
||||
**Response**:
|
||||
```json
|
||||
{
|
||||
"success": true
|
||||
}
|
||||
```
|
||||
|
||||
**Error Response**:
|
||||
```json
|
||||
{
|
||||
"error": "Username already exists"
|
||||
}
|
||||
```
|
||||
|
||||
**Status Codes**:
|
||||
- `200`: Success
|
||||
- `400`: Validation error
|
||||
- `500`: Database error
|
||||
|
||||
---
|
||||
|
||||
#### POST /admin/delete-user/:id
|
||||
|
||||
Delete a user and all their DevBenches.
|
||||
|
||||
**Authentication**: Required (Admin role)
|
||||
|
||||
**Parameters**:
|
||||
- `id`: User ID (integer)
|
||||
|
||||
**Response**:
|
||||
```json
|
||||
{
|
||||
"success": true
|
||||
}
|
||||
```
|
||||
|
||||
**Error Response**:
|
||||
```json
|
||||
{
|
||||
"error": "Database error"
|
||||
}
|
||||
```
|
||||
|
||||
**Status Codes**:
|
||||
- `200`: Success
|
||||
- `500`: Database error
|
||||
|
||||
**Side Effects**:
|
||||
- Deletes all user's DevBenches
|
||||
- Deletes user record
|
||||
- Cannot delete admin users
|
||||
|
||||
---
|
||||
|
||||
#### POST /admin/reset-password/:id
|
||||
|
||||
Reset user password.
|
||||
|
||||
**Authentication**: Required (Admin role)
|
||||
|
||||
**Parameters**:
|
||||
- `id`: User ID (integer)
|
||||
|
||||
**Request Body**:
|
||||
```json
|
||||
{
|
||||
"newPassword": "newpassword123"
|
||||
}
|
||||
```
|
||||
|
||||
**Response**:
|
||||
```json
|
||||
{
|
||||
"success": true
|
||||
}
|
||||
```
|
||||
|
||||
**Error Response**:
|
||||
```json
|
||||
{
|
||||
"error": "Database error"
|
||||
}
|
||||
```
|
||||
|
||||
**Status Codes**:
|
||||
- `200`: Success
|
||||
- `500`: Database error
|
||||
|
||||
**Side Effects**:
|
||||
- Updates user password (hashed with bcrypt)
|
||||
- Cannot reset admin passwords
|
||||
|
||||
---
|
||||
|
||||
### User Info
|
||||
|
||||
#### GET /api/user-info
|
||||
|
||||
Get current user information for WebSocket registration.
|
||||
|
||||
**Authentication**: Required
|
||||
|
||||
**Response**:
|
||||
```json
|
||||
{
|
||||
"userId": 1,
|
||||
"username": "admin",
|
||||
"isAdmin": true
|
||||
}
|
||||
```
|
||||
|
||||
**Status Codes**:
|
||||
- `200`: Success
|
||||
- `302`: Redirect to `/login` if not authenticated
|
||||
|
||||
---
|
||||
|
||||
## WebSocket API
|
||||
|
||||
### Connection
|
||||
|
||||
```javascript
|
||||
const ws = new WebSocket('ws://localhost:9090');
|
||||
```
|
||||
|
||||
### Registration
|
||||
|
||||
After connection, register with user ID:
|
||||
|
||||
```javascript
|
||||
ws.send(JSON.stringify({
|
||||
type: 'register',
|
||||
userId: 1
|
||||
}));
|
||||
```
|
||||
|
||||
### Message Types
|
||||
|
||||
#### script_output
|
||||
|
||||
Real-time output from provision script.
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "script_output",
|
||||
"devbenchId": 123,
|
||||
"data": "Cloning VM...\n"
|
||||
}
|
||||
```
|
||||
|
||||
#### script_complete
|
||||
|
||||
Script execution completed.
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "script_complete",
|
||||
"devbenchId": 123,
|
||||
"exitCode": 0
|
||||
}
|
||||
```
|
||||
|
||||
#### status_update
|
||||
|
||||
DevBench status changed.
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "status_update",
|
||||
"devbenchId": 123,
|
||||
"status": "active"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Error Responses
|
||||
|
||||
### Standard Error Format
|
||||
|
||||
```json
|
||||
{
|
||||
"error": "Error message description"
|
||||
}
|
||||
```
|
||||
|
||||
### Common HTTP Status Codes
|
||||
|
||||
- `200`: Success
|
||||
- `302`: Redirect
|
||||
- `400`: Bad Request (validation error)
|
||||
- `401`: Unauthorized (not authenticated)
|
||||
- `403`: Forbidden (insufficient permissions)
|
||||
- `404`: Not Found
|
||||
- `500`: Internal Server Error
|
||||
|
||||
---
|
||||
|
||||
## Rate Limiting
|
||||
|
||||
Currently not implemented. Consider adding rate limiting for production:
|
||||
|
||||
- Login attempts: 5 per minute
|
||||
- DevBench creation: 10 per hour per user
|
||||
- API calls: 100 per minute per user
|
||||
|
||||
---
|
||||
|
||||
## Data Models
|
||||
|
||||
### User
|
||||
|
||||
```json
|
||||
{
|
||||
"id": 1,
|
||||
"username": "admin",
|
||||
"email": "admin@example.com",
|
||||
"password": "$2a$10$...", // bcrypt hash
|
||||
"is_admin": 1,
|
||||
"created_at": "2025-11-23T12:00:00.000Z"
|
||||
}
|
||||
```
|
||||
|
||||
### DevBench
|
||||
|
||||
```json
|
||||
{
|
||||
"id": 1,
|
||||
"user_id": 1,
|
||||
"name": "test-vm",
|
||||
"actual_name": "admin_test-vm",
|
||||
"status": "active",
|
||||
"ssh_info": "6004",
|
||||
"vnc_info": "5004",
|
||||
"created_at": "2025-11-23T12:00:00.000Z",
|
||||
"updated_at": "2025-11-23T12:05:00.000Z"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Examples
|
||||
|
||||
### Create DevBench with cURL
|
||||
|
||||
```bash
|
||||
# Login first to get session cookie
|
||||
curl -c cookies.txt -X POST http://localhost:9090/login \
|
||||
-H "Content-Type: application/x-www-form-urlencoded" \
|
||||
-d "username=admin&password=admin123"
|
||||
|
||||
# Create DevBench
|
||||
curl -b cookies.txt -X POST http://localhost:9090/create-devbench \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"name":"test-vm"}'
|
||||
```
|
||||
|
||||
### Check Status with cURL
|
||||
|
||||
```bash
|
||||
curl -b cookies.txt http://localhost:9090/check-status/1
|
||||
```
|
||||
|
||||
### Add User with cURL
|
||||
|
||||
```bash
|
||||
curl -b cookies.txt -X POST http://localhost:9090/admin/add-user \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"username":"john",
|
||||
"email":"john@example.com",
|
||||
"password":"password123"
|
||||
}'
|
||||
```
|
||||
|
||||
### WebSocket with JavaScript
|
||||
|
||||
```javascript
|
||||
// Connect
|
||||
const ws = new WebSocket('ws://localhost:9090');
|
||||
|
||||
// Register
|
||||
ws.onopen = () => {
|
||||
fetch('/api/user-info')
|
||||
.then(res => res.json())
|
||||
.then(data => {
|
||||
ws.send(JSON.stringify({
|
||||
type: 'register',
|
||||
userId: data.userId
|
||||
}));
|
||||
});
|
||||
};
|
||||
|
||||
// Listen for messages
|
||||
ws.onmessage = (event) => {
|
||||
const message = JSON.parse(event.data);
|
||||
console.log('Received:', message);
|
||||
|
||||
if (message.type === 'script_output') {
|
||||
// Update UI with script output
|
||||
document.getElementById('log').textContent += message.data;
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Security Considerations
|
||||
|
||||
### Authentication
|
||||
- Session-based authentication
|
||||
- HTTP-only cookies
|
||||
- Password hashing with bcrypt (10 rounds)
|
||||
|
||||
### Authorization
|
||||
- Role-based access control (Admin/User)
|
||||
- Owner-only access to DevBenches
|
||||
- Admin-only routes protected
|
||||
|
||||
### Input Validation
|
||||
- Username: Letters only
|
||||
- DevBench name: Alphanumeric, hyphens, underscores
|
||||
- SQL injection prevention: Parameterized queries
|
||||
- XSS prevention: EJS auto-escaping
|
||||
|
||||
### Best Practices
|
||||
- Always use HTTPS in production
|
||||
- Change default admin password
|
||||
- Use strong session secret
|
||||
- Implement rate limiting
|
||||
- Add CSRF protection
|
||||
- Enable security headers
|
||||
|
||||
---
|
||||
|
||||
## Changelog
|
||||
|
||||
### Version 1.0.0 (2025-11-23)
|
||||
- Initial API release
|
||||
- User authentication
|
||||
- DevBench management
|
||||
- Admin panel
|
||||
- WebSocket support
|
||||
- Help page
|
||||
- Dark theme support
|
||||
400
asf-cloud-server/TBM_devbench/docs/ARCHITECTURE.md
Normal file
400
asf-cloud-server/TBM_devbench/docs/ARCHITECTURE.md
Normal file
@@ -0,0 +1,400 @@
|
||||
# DevBench Manager - Architecture Documentation
|
||||
|
||||
## System Overview
|
||||
|
||||
DevBench Manager is a web-based application for managing virtual machine (VM) development environments. It provides a centralized interface for users to create, manage, and access their DevBench VMs through a secure web portal.
|
||||
|
||||
## Architecture Diagram
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ User Browser │
|
||||
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
|
||||
│ │ Dashboard │ │ Admin Panel │ │ Help Page │ │
|
||||
│ └──────────────┘ └──────────────┘ └──────────────┘ │
|
||||
└────────────────────────┬────────────────────────────────────────┘
|
||||
│ HTTPS/WSS
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ Caddy Reverse Proxy │
|
||||
│ (tbm.nabd-co.com) │
|
||||
└────────────────────────┬────────────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ DevBench Manager Container │
|
||||
│ ┌──────────────────────────────────────────────────────────┐ │
|
||||
│ │ Express.js Server │ │
|
||||
│ │ ┌────────────┐ ┌────────────┐ ┌────────────┐ │ │
|
||||
│ │ │ Routes │ │ WebSocket │ │ Auth │ │ │
|
||||
│ │ └────────────┘ └────────────┘ └────────────┘ │ │
|
||||
│ └──────────────────────────────────────────────────────────┘ │
|
||||
│ ┌──────────────────────────────────────────────────────────┐ │
|
||||
│ │ SQLite Database │ │
|
||||
│ │ ┌────────────┐ ┌────────────┐ │ │
|
||||
│ │ │ Users │ │ DevBenches │ │ │
|
||||
│ │ └────────────┘ └────────────┘ │ │
|
||||
│ └──────────────────────────────────────────────────────────┘ │
|
||||
│ ┌──────────────────────────────────────────────────────────┐ │
|
||||
│ │ Provision Script Executor │ │
|
||||
│ │ (provision_vm.sh) │ │
|
||||
│ └──────────────────────────────────────────────────────────┘ │
|
||||
└────────────────────────┬────────────────────────────────────────┘
|
||||
│ SSH (Port 49152)
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ Remote VM Host Server │
|
||||
│ (asf-server.duckdns.org) │
|
||||
│ ┌──────────────────────────────────────────────────────────┐ │
|
||||
│ │ VirtualBox Manager │ │
|
||||
│ │ ┌────────┐ ┌────────┐ ┌────────┐ ┌────────┐ │ │
|
||||
│ │ │ VM 1 │ │ VM 2 │ │ VM 3 │ │ VM N │ │ │
|
||||
│ │ │ SSH: │ │ SSH: │ │ SSH: │ │ SSH: │ │ │
|
||||
│ │ │ 6001 │ │ 6002 │ │ 6003 │ │ 600N │ │ │
|
||||
│ │ │ VNC: │ │ VNC: │ │ VNC: │ │ VNC: │ │ │
|
||||
│ │ │ 5001 │ │ 5002 │ │ 5003 │ │ 500N │ │ │
|
||||
│ │ └────────┘ └────────┘ └────────┘ └────────┘ │ │
|
||||
│ └──────────────────────────────────────────────────────────┘ │
|
||||
└─────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
## Component Architecture
|
||||
|
||||
### 1. Frontend Layer
|
||||
|
||||
#### Technologies
|
||||
- **EJS Templates**: Server-side rendering
|
||||
- **Bootstrap 5**: UI framework
|
||||
- **Font Awesome**: Icons
|
||||
- **Custom CSS**: Theming (light/dark mode)
|
||||
- **Vanilla JavaScript**: Client-side interactivity
|
||||
|
||||
#### Components
|
||||
- **Dashboard**: User's DevBench management interface
|
||||
- **Admin Panel**: User and system management
|
||||
- **Help Page**: Documentation and SSH config guide
|
||||
- **Login Page**: Authentication interface
|
||||
|
||||
### 2. Backend Layer
|
||||
|
||||
#### Technologies
|
||||
- **Node.js**: Runtime environment
|
||||
- **Express.js**: Web framework
|
||||
- **WebSocket (ws)**: Real-time communication
|
||||
- **SQLite3**: Database
|
||||
- **bcryptjs**: Password hashing
|
||||
- **express-session**: Session management
|
||||
|
||||
#### Core Modules
|
||||
|
||||
##### Authentication Module
|
||||
```javascript
|
||||
- Session-based authentication
|
||||
- Password hashing with bcrypt
|
||||
- Role-based access control (Admin/User)
|
||||
- Middleware for route protection
|
||||
```
|
||||
|
||||
##### DevBench Management Module
|
||||
```javascript
|
||||
- Create DevBench
|
||||
- Delete DevBench
|
||||
- Activate DevBench
|
||||
- Status monitoring
|
||||
- Real-time log streaming
|
||||
```
|
||||
|
||||
##### WebSocket Module
|
||||
```javascript
|
||||
- Real-time script output
|
||||
- Status updates
|
||||
- User-specific broadcasting
|
||||
- Connection management
|
||||
```
|
||||
|
||||
##### Provision Script Executor
|
||||
```javascript
|
||||
- SSH connection to remote host
|
||||
- Script execution via sshpass
|
||||
- Output parsing
|
||||
- Error handling
|
||||
- Timeout management (30 minutes)
|
||||
```
|
||||
|
||||
### 3. Database Layer
|
||||
|
||||
#### Schema
|
||||
|
||||
**Users Table**
|
||||
```sql
|
||||
CREATE TABLE users (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
username TEXT UNIQUE NOT NULL,
|
||||
email TEXT NOT NULL,
|
||||
password TEXT NOT NULL,
|
||||
is_admin INTEGER DEFAULT 0,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||
)
|
||||
```
|
||||
|
||||
**DevBenches Table**
|
||||
```sql
|
||||
CREATE TABLE devbenches (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
user_id INTEGER NOT NULL,
|
||||
name TEXT NOT NULL,
|
||||
actual_name TEXT,
|
||||
status TEXT DEFAULT 'inactive',
|
||||
ssh_info TEXT,
|
||||
vnc_info TEXT,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (user_id) REFERENCES users (id)
|
||||
)
|
||||
```
|
||||
|
||||
### 4. Infrastructure Layer
|
||||
|
||||
#### Docker Container
|
||||
```yaml
|
||||
- Base Image: Node.js 18
|
||||
- Exposed Port: 3001 (mapped to 9090)
|
||||
- Volumes:
|
||||
- ./data:/app/data (Database persistence)
|
||||
- ./logs:/app/logs (Log files)
|
||||
- ./provision_vm.sh:/app/provision_vm.sh (Script)
|
||||
- Networks:
|
||||
- app-network (Internal)
|
||||
- caddy_network (External proxy)
|
||||
```
|
||||
|
||||
#### Reverse Proxy (Caddy)
|
||||
```
|
||||
- Domain: tbm.nabd-co.com
|
||||
- SSL/TLS: Automatic (Let's Encrypt)
|
||||
- Proxy Target: devbench-manager:3001
|
||||
```
|
||||
|
||||
## Data Flow
|
||||
|
||||
### DevBench Creation Flow
|
||||
|
||||
```
|
||||
1. User clicks "Create DevBench"
|
||||
↓
|
||||
2. Frontend sends POST /create-devbench
|
||||
↓
|
||||
3. Backend validates input
|
||||
↓
|
||||
4. Database record created (status: creating)
|
||||
↓
|
||||
5. WebSocket connection established
|
||||
↓
|
||||
6. provision_vm.sh executed via SSH
|
||||
↓
|
||||
7. Real-time output streamed via WebSocket
|
||||
↓
|
||||
8. Script parses VM info (name, SSH port, VNC port)
|
||||
↓
|
||||
9. Database updated with connection info
|
||||
↓
|
||||
10. Frontend receives completion notification
|
||||
↓
|
||||
11. Page refreshes to show active DevBench
|
||||
```
|
||||
|
||||
### Authentication Flow
|
||||
|
||||
```
|
||||
1. User submits login form
|
||||
↓
|
||||
2. Backend validates credentials
|
||||
↓
|
||||
3. Password compared with bcrypt
|
||||
↓
|
||||
4. Session created on success
|
||||
↓
|
||||
5. User redirected to dashboard/admin
|
||||
↓
|
||||
6. Session cookie stored in browser
|
||||
↓
|
||||
7. Subsequent requests include session
|
||||
↓
|
||||
8. Middleware validates session
|
||||
```
|
||||
|
||||
### Status Monitoring Flow
|
||||
|
||||
```
|
||||
1. Periodic check (every 60 seconds)
|
||||
↓
|
||||
2. For each DevBench in database
|
||||
↓
|
||||
3. Execute provision_vm.sh status <vm_name>
|
||||
↓
|
||||
4. Parse output for status
|
||||
↓
|
||||
5. Update database if changed
|
||||
↓
|
||||
6. Broadcast update via WebSocket
|
||||
↓
|
||||
7. Frontend updates UI in real-time
|
||||
```
|
||||
|
||||
## Security Architecture
|
||||
|
||||
### Authentication & Authorization
|
||||
- **Password Hashing**: bcrypt with salt rounds
|
||||
- **Session Management**: Secure HTTP-only cookies
|
||||
- **Role-Based Access**: Admin vs User permissions
|
||||
- **Route Protection**: Middleware guards
|
||||
|
||||
### Input Validation
|
||||
- **Username**: Letters only, no special characters
|
||||
- **DevBench Name**: Alphanumeric, hyphens, underscores
|
||||
- **SQL Injection**: Parameterized queries
|
||||
- **XSS Prevention**: EJS auto-escaping
|
||||
|
||||
### Network Security
|
||||
- **HTTPS**: Enforced via Caddy
|
||||
- **SSH**: Password-based (sshpass) with timeout
|
||||
- **Container Isolation**: Docker networking
|
||||
- **Port Exposure**: Minimal (only 9090)
|
||||
|
||||
## Scalability Considerations
|
||||
|
||||
### Current Limitations
|
||||
- Single SQLite database (not suitable for high concurrency)
|
||||
- SSH-based provisioning (sequential, not parallel)
|
||||
- In-memory WebSocket connections (lost on restart)
|
||||
- Single container deployment
|
||||
|
||||
### Future Improvements
|
||||
- **Database**: Migrate to PostgreSQL/MySQL
|
||||
- **Queue System**: Redis/RabbitMQ for async jobs
|
||||
- **Load Balancing**: Multiple container instances
|
||||
- **Persistent WebSocket**: Redis pub/sub
|
||||
- **Caching**: Redis for session and status data
|
||||
|
||||
## Monitoring & Logging
|
||||
|
||||
### Application Logs
|
||||
- Location: `/app/logs/`
|
||||
- Format: Timestamped console output
|
||||
- Rotation: Manual (not automated)
|
||||
|
||||
### Provision Script Logs
|
||||
- Location: `/var/log/devbench/`
|
||||
- Format: Timestamped with operation details
|
||||
- Retention: Indefinite
|
||||
|
||||
### Health Checks
|
||||
- Endpoint: `/health`
|
||||
- Interval: 30 seconds
|
||||
- Timeout: 10 seconds
|
||||
- Retries: 3
|
||||
|
||||
## Technology Stack Summary
|
||||
|
||||
| Layer | Technology | Purpose |
|
||||
|-------|-----------|---------|
|
||||
| Frontend | EJS, Bootstrap 5, JavaScript | UI rendering and interactivity |
|
||||
| Backend | Node.js, Express.js | Web server and API |
|
||||
| Real-time | WebSocket (ws) | Live updates |
|
||||
| Database | SQLite3 | Data persistence |
|
||||
| Authentication | bcryptjs, express-session | Security |
|
||||
| Container | Docker, Docker Compose | Deployment |
|
||||
| Proxy | Caddy | SSL/TLS and routing |
|
||||
| VM Management | Bash, SSH, VirtualBox | VM provisioning |
|
||||
|
||||
## Deployment Architecture
|
||||
|
||||
### Production Environment
|
||||
```
|
||||
Internet
|
||||
↓
|
||||
Caddy (Port 443) → SSL Termination
|
||||
↓
|
||||
Docker Network (caddy_network)
|
||||
↓
|
||||
DevBench Manager Container (Port 3001)
|
||||
↓
|
||||
SQLite Database (Volume: ./data)
|
||||
↓
|
||||
SSH Connection (Port 49152)
|
||||
↓
|
||||
Remote VM Host
|
||||
```
|
||||
|
||||
### Development Environment
|
||||
```
|
||||
localhost:9090
|
||||
↓
|
||||
DevBench Manager Container
|
||||
↓
|
||||
Local SQLite Database
|
||||
↓
|
||||
SSH to Remote Host
|
||||
```
|
||||
|
||||
## Configuration Management
|
||||
|
||||
### Environment Variables
|
||||
- `NODE_ENV`: production/development
|
||||
- `PORT`: Application port (default: 3001)
|
||||
- `SECRET_KEY`: Session secret
|
||||
- `ADMIN_EMAIL`: Default admin email
|
||||
- `ADMIN_PASSWORD`: Default admin password
|
||||
|
||||
### Configuration Files
|
||||
- `config.js`: Application configuration
|
||||
- `docker-compose.yml`: Container orchestration
|
||||
- `Dockerfile`: Container build instructions
|
||||
- `.env`: Environment variables (optional)
|
||||
|
||||
## Error Handling
|
||||
|
||||
### Application Errors
|
||||
- Database errors: Logged and returned as 500
|
||||
- Authentication errors: Returned as 401/403
|
||||
- Validation errors: Returned as 400
|
||||
- Script errors: Logged and broadcast via WebSocket
|
||||
|
||||
### Script Errors
|
||||
- Timeout: 30 minutes (1800 seconds)
|
||||
- SSH failures: Logged and status set to 'error'
|
||||
- Parsing failures: Status set to 'error'
|
||||
- Network issues: Retry not implemented
|
||||
|
||||
## Performance Considerations
|
||||
|
||||
### Bottlenecks
|
||||
1. **SSH Connection**: Sequential, blocking
|
||||
2. **Script Execution**: Long-running (up to 30 min)
|
||||
3. **Status Checks**: Every 60 seconds for all VMs
|
||||
4. **SQLite**: Limited concurrent writes
|
||||
|
||||
### Optimizations
|
||||
- WebSocket for real-time updates (no polling)
|
||||
- Session caching in memory
|
||||
- Static asset serving via Express
|
||||
- Minimal database queries
|
||||
|
||||
## Maintenance & Operations
|
||||
|
||||
### Backup Strategy
|
||||
- Database: Copy `./data/devbench.db`
|
||||
- Logs: Archive `./logs/` directory
|
||||
- Configuration: Version control
|
||||
|
||||
### Update Procedure
|
||||
1. Pull latest code
|
||||
2. Run `./deploy.sh`
|
||||
3. Verify health endpoint
|
||||
4. Check logs for errors
|
||||
|
||||
### Troubleshooting
|
||||
- Check container logs: `docker-compose logs -f`
|
||||
- Verify network: `docker network inspect caddy_network`
|
||||
- Test SSH: `ssh -p 49152 asf@asf-server.duckdns.org`
|
||||
- Check database: `sqlite3 data/devbench.db`
|
||||
679
asf-cloud-server/TBM_devbench/docs/DEPLOYMENT.md
Normal file
679
asf-cloud-server/TBM_devbench/docs/DEPLOYMENT.md
Normal file
@@ -0,0 +1,679 @@
|
||||
# DevBench Manager - Deployment Guide
|
||||
|
||||
## Table of Contents
|
||||
1. [Prerequisites](#prerequisites)
|
||||
2. [Quick Start](#quick-start)
|
||||
3. [Detailed Deployment](#detailed-deployment)
|
||||
4. [Configuration](#configuration)
|
||||
5. [Verification](#verification)
|
||||
6. [Troubleshooting](#troubleshooting)
|
||||
7. [Maintenance](#maintenance)
|
||||
8. [Backup & Recovery](#backup--recovery)
|
||||
|
||||
## Prerequisites
|
||||
|
||||
### Required Software
|
||||
- **Docker**: Version 20.10 or higher
|
||||
- **Docker Compose**: Version 2.0 or higher
|
||||
- **Git**: For cloning the repository
|
||||
- **SSH Access**: To remote VM host (asf-server.duckdns.org:49152)
|
||||
|
||||
### System Requirements
|
||||
- **OS**: Linux (Ubuntu 20.04+ recommended), macOS, or Windows with WSL2
|
||||
- **RAM**: Minimum 512MB for container
|
||||
- **Disk**: Minimum 1GB free space
|
||||
- **Network**: Internet connection for Docker images and SSH
|
||||
|
||||
### Access Requirements
|
||||
- SSH credentials for VM host
|
||||
- Domain name configured (optional, for Caddy)
|
||||
- Port 9090 available (or configure different port)
|
||||
|
||||
## Quick Start
|
||||
|
||||
### One-Command Deployment
|
||||
|
||||
```bash
|
||||
# Clone the repository
|
||||
git clone <repository-url>
|
||||
cd ASF_devbench
|
||||
|
||||
# Run deployment script
|
||||
chmod +x deploy.sh
|
||||
./deploy.sh
|
||||
```
|
||||
|
||||
That's it! The application will be available at:
|
||||
- **Direct**: http://localhost:9090
|
||||
- **Via Caddy**: https://tbm.nabd-co.com (if configured)
|
||||
|
||||
### Default Credentials
|
||||
- **Username**: admin
|
||||
- **Password**: admin123
|
||||
|
||||
⚠️ **Important**: Change the default password after first login!
|
||||
|
||||
## Detailed Deployment
|
||||
|
||||
### Step 1: Clone Repository
|
||||
|
||||
```bash
|
||||
git clone <repository-url>
|
||||
cd ASF_devbench
|
||||
```
|
||||
|
||||
### Step 2: Review Configuration
|
||||
|
||||
Check `config.js` for default settings:
|
||||
```javascript
|
||||
{
|
||||
port: 3001,
|
||||
database: { path: './data/devbench.db' },
|
||||
session: { secret: 'your-secret-key' },
|
||||
defaultAdmin: {
|
||||
username: 'admin',
|
||||
password: 'admin123',
|
||||
email: 'admin@nabd-co.com'
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Step 3: Set Environment Variables (Optional)
|
||||
|
||||
Create `.env` file:
|
||||
```bash
|
||||
NODE_ENV=production
|
||||
PORT=3001
|
||||
SECRET_KEY=your-secure-secret-key
|
||||
ADMIN_EMAIL=admin@yourdomain.com
|
||||
ADMIN_PASSWORD=your-secure-password
|
||||
```
|
||||
|
||||
### Step 4: Prepare Directories
|
||||
|
||||
```bash
|
||||
# Create necessary directories
|
||||
mkdir -p data logs public/downloads
|
||||
|
||||
# Set permissions
|
||||
chmod 755 data logs
|
||||
chmod +x provision_vm.sh deploy.sh
|
||||
```
|
||||
|
||||
### Step 5: Configure Docker Network
|
||||
|
||||
```bash
|
||||
# Create Caddy network (if using reverse proxy)
|
||||
docker network create caddy_network
|
||||
```
|
||||
|
||||
### Step 6: Deploy with Docker Compose
|
||||
|
||||
```bash
|
||||
# Build and start container
|
||||
docker-compose up -d --build
|
||||
|
||||
# Check status
|
||||
docker-compose ps
|
||||
|
||||
# View logs
|
||||
docker-compose logs -f
|
||||
```
|
||||
|
||||
### Step 7: Verify Deployment
|
||||
|
||||
```bash
|
||||
# Check health endpoint
|
||||
curl http://localhost:9090/health
|
||||
|
||||
# Expected response:
|
||||
# {"status":"ok","timestamp":"2025-11-23T...","version":"1.0.0"}
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
### Docker Compose Configuration
|
||||
|
||||
Edit `docker-compose.yml`:
|
||||
|
||||
```yaml
|
||||
services:
|
||||
devbench-manager:
|
||||
ports:
|
||||
- "9090:3001" # Change external port here
|
||||
environment:
|
||||
- NODE_ENV=production
|
||||
- SECRET_KEY=${SECRET_KEY:-dev-secret-key}
|
||||
volumes:
|
||||
- ./data:/app/data
|
||||
- ./logs:/app/logs
|
||||
- ./provision_vm.sh:/app/provision_vm.sh:ro
|
||||
```
|
||||
|
||||
### Caddy Reverse Proxy
|
||||
|
||||
Add to your Caddyfile:
|
||||
|
||||
```
|
||||
tbm.nabd-co.com {
|
||||
reverse_proxy devbench-manager:3001
|
||||
}
|
||||
```
|
||||
|
||||
Ensure Caddy container is on `caddy_network`:
|
||||
|
||||
```bash
|
||||
# Check Caddy network
|
||||
docker network inspect caddy_network
|
||||
|
||||
# Connect Caddy to network if needed
|
||||
docker network connect caddy_network caddy-container-name
|
||||
```
|
||||
|
||||
### SSH Configuration
|
||||
|
||||
The application connects to the VM host via SSH. Ensure:
|
||||
|
||||
1. **Host**: asf-server.duckdns.org
|
||||
2. **Port**: 49152
|
||||
3. **User**: asf
|
||||
4. **Password**: ASF (configured in provision_vm.sh)
|
||||
|
||||
Test SSH connection:
|
||||
```bash
|
||||
ssh -p 49152 asf@asf-server.duckdns.org
|
||||
```
|
||||
|
||||
### Firewall Configuration
|
||||
|
||||
Open required ports:
|
||||
|
||||
```bash
|
||||
# For direct access
|
||||
sudo ufw allow 9090/tcp
|
||||
|
||||
# For Caddy (if using)
|
||||
sudo ufw allow 80/tcp
|
||||
sudo ufw allow 443/tcp
|
||||
```
|
||||
|
||||
## Verification
|
||||
|
||||
### 1. Container Status
|
||||
|
||||
```bash
|
||||
# Check if container is running
|
||||
docker ps | grep devbench-manager
|
||||
|
||||
# Expected output:
|
||||
# CONTAINER ID IMAGE STATUS PORTS
|
||||
# abc123... devbench-manager:latest Up 2 minutes 0.0.0.0:9090->3001/tcp
|
||||
```
|
||||
|
||||
### 2. Health Check
|
||||
|
||||
```bash
|
||||
# Test health endpoint
|
||||
curl http://localhost:9090/health
|
||||
|
||||
# Or with jq for formatted output
|
||||
curl -s http://localhost:9090/health | jq
|
||||
```
|
||||
|
||||
### 3. Network Connectivity
|
||||
|
||||
```bash
|
||||
# Check networks
|
||||
docker inspect devbench-manager --format='{{range $k, $v := .NetworkSettings.Networks}}{{$k}} {{end}}'
|
||||
|
||||
# Expected: app-network caddy_network
|
||||
```
|
||||
|
||||
### 4. Database
|
||||
|
||||
```bash
|
||||
# Check if database was created
|
||||
ls -la data/devbench.db
|
||||
|
||||
# Connect to database
|
||||
docker exec -it devbench-manager sqlite3 /app/data/devbench.db
|
||||
|
||||
# Run query
|
||||
sqlite> SELECT * FROM users;
|
||||
```
|
||||
|
||||
### 5. Logs
|
||||
|
||||
```bash
|
||||
# View application logs
|
||||
docker-compose logs -f
|
||||
|
||||
# View last 50 lines
|
||||
docker-compose logs --tail=50
|
||||
|
||||
# View specific service
|
||||
docker-compose logs devbench-manager
|
||||
```
|
||||
|
||||
### 6. Web Interface
|
||||
|
||||
1. Open browser: http://localhost:9090
|
||||
2. Login with admin credentials
|
||||
3. Create a test DevBench
|
||||
4. Verify real-time log output
|
||||
5. Check connection information
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Container Won't Start
|
||||
|
||||
**Problem**: Container exits immediately
|
||||
|
||||
**Solution**:
|
||||
```bash
|
||||
# Check logs
|
||||
docker-compose logs
|
||||
|
||||
# Common issues:
|
||||
# 1. Port already in use
|
||||
sudo lsof -i :9090
|
||||
# Kill process or change port in docker-compose.yml
|
||||
|
||||
# 2. Permission issues
|
||||
sudo chown -R $USER:$USER data logs
|
||||
|
||||
# 3. Missing provision script
|
||||
ls -la provision_vm.sh
|
||||
chmod +x provision_vm.sh
|
||||
```
|
||||
|
||||
### Cannot Access Web Interface
|
||||
|
||||
**Problem**: Connection refused or timeout
|
||||
|
||||
**Solution**:
|
||||
```bash
|
||||
# 1. Check if container is running
|
||||
docker ps | grep devbench-manager
|
||||
|
||||
# 2. Check port mapping
|
||||
docker port devbench-manager
|
||||
|
||||
# 3. Test from inside container
|
||||
docker exec devbench-manager wget -O- http://localhost:3001/health
|
||||
|
||||
# 4. Check firewall
|
||||
sudo ufw status
|
||||
sudo ufw allow 9090/tcp
|
||||
|
||||
# 5. Check if port is listening
|
||||
sudo netstat -tlnp | grep 9090
|
||||
```
|
||||
|
||||
### Caddy Proxy Not Working
|
||||
|
||||
**Problem**: Cannot access via domain
|
||||
|
||||
**Solution**:
|
||||
```bash
|
||||
# 1. Check if both containers are on caddy_network
|
||||
docker network inspect caddy_network
|
||||
|
||||
# 2. Connect devbench-manager to network
|
||||
docker network connect caddy_network devbench-manager
|
||||
|
||||
# 3. Restart Caddy
|
||||
docker restart caddy-container-name
|
||||
|
||||
# 4. Check Caddy logs
|
||||
docker logs caddy-container-name
|
||||
|
||||
# 5. Test from Caddy container
|
||||
docker exec caddy-container-name wget -O- http://devbench-manager:3001/health
|
||||
```
|
||||
|
||||
### SSH Connection Fails
|
||||
|
||||
**Problem**: Cannot connect to VM host
|
||||
|
||||
**Solution**:
|
||||
```bash
|
||||
# 1. Test SSH manually
|
||||
ssh -p 49152 asf@asf-server.duckdns.org
|
||||
|
||||
# 2. Check if sshpass is installed in container
|
||||
docker exec devbench-manager which sshpass
|
||||
|
||||
# 3. Check provision script
|
||||
docker exec devbench-manager cat /app/provision_vm.sh | grep SSH_
|
||||
|
||||
# 4. Test from container
|
||||
docker exec -it devbench-manager bash
|
||||
./provision_vm.sh status test_vm
|
||||
```
|
||||
|
||||
### Database Errors
|
||||
|
||||
**Problem**: Database locked or corrupted
|
||||
|
||||
**Solution**:
|
||||
```bash
|
||||
# 1. Stop container
|
||||
docker-compose down
|
||||
|
||||
# 2. Backup database
|
||||
cp data/devbench.db data/devbench.db.backup
|
||||
|
||||
# 3. Check database integrity
|
||||
sqlite3 data/devbench.db "PRAGMA integrity_check;"
|
||||
|
||||
# 4. If corrupted, restore from backup or recreate
|
||||
rm data/devbench.db
|
||||
docker-compose up -d
|
||||
```
|
||||
|
||||
### WebSocket Not Working
|
||||
|
||||
**Problem**: Real-time updates not appearing
|
||||
|
||||
**Solution**:
|
||||
```bash
|
||||
# 1. Check browser console for errors
|
||||
# Open DevTools → Console
|
||||
|
||||
# 2. Verify WebSocket connection
|
||||
# Look for "WebSocket connected" message
|
||||
|
||||
# 3. Check if behind proxy
|
||||
# Ensure proxy supports WebSocket upgrade
|
||||
|
||||
# 4. Test WebSocket endpoint
|
||||
wscat -c ws://localhost:9090
|
||||
```
|
||||
|
||||
## Maintenance
|
||||
|
||||
### Regular Tasks
|
||||
|
||||
#### Daily
|
||||
- Monitor logs for errors
|
||||
- Check disk space
|
||||
- Verify health endpoint
|
||||
|
||||
#### Weekly
|
||||
- Review user activity
|
||||
- Clean up old logs
|
||||
- Check for updates
|
||||
|
||||
#### Monthly
|
||||
- Backup database
|
||||
- Review security
|
||||
- Update dependencies
|
||||
|
||||
### Updating the Application
|
||||
|
||||
```bash
|
||||
# 1. Pull latest changes
|
||||
git pull origin main
|
||||
|
||||
# 2. Backup database
|
||||
cp data/devbench.db data/devbench.db.$(date +%Y%m%d)
|
||||
|
||||
# 3. Rebuild and restart
|
||||
docker-compose down
|
||||
docker-compose up -d --build
|
||||
|
||||
# 4. Verify
|
||||
curl http://localhost:9090/health
|
||||
```
|
||||
|
||||
### Viewing Logs
|
||||
|
||||
```bash
|
||||
# Real-time logs
|
||||
docker-compose logs -f
|
||||
|
||||
# Last 100 lines
|
||||
docker-compose logs --tail=100
|
||||
|
||||
# Specific time range
|
||||
docker-compose logs --since 2h
|
||||
|
||||
# Save logs to file
|
||||
docker-compose logs > logs/docker-$(date +%Y%m%d).log
|
||||
```
|
||||
|
||||
### Cleaning Up
|
||||
|
||||
```bash
|
||||
# Remove stopped containers
|
||||
docker container prune -f
|
||||
|
||||
# Remove unused images
|
||||
docker image prune -a -f
|
||||
|
||||
# Remove unused volumes
|
||||
docker volume prune -f
|
||||
|
||||
# Clean everything
|
||||
docker system prune -a -f
|
||||
```
|
||||
|
||||
### Scaling (Future)
|
||||
|
||||
For multiple instances:
|
||||
|
||||
```bash
|
||||
# Scale to 3 instances
|
||||
docker-compose up -d --scale devbench-manager=3
|
||||
|
||||
# Use load balancer (nginx/haproxy)
|
||||
# Configure session persistence
|
||||
```
|
||||
|
||||
## Backup & Recovery
|
||||
|
||||
### Backup Strategy
|
||||
|
||||
#### Database Backup
|
||||
|
||||
```bash
|
||||
# Manual backup
|
||||
cp data/devbench.db backups/devbench-$(date +%Y%m%d-%H%M%S).db
|
||||
|
||||
# Automated backup script
|
||||
#!/bin/bash
|
||||
BACKUP_DIR="backups"
|
||||
mkdir -p $BACKUP_DIR
|
||||
cp data/devbench.db $BACKUP_DIR/devbench-$(date +%Y%m%d).db
|
||||
# Keep only last 7 days
|
||||
find $BACKUP_DIR -name "devbench-*.db" -mtime +7 -delete
|
||||
```
|
||||
|
||||
#### Full Backup
|
||||
|
||||
```bash
|
||||
# Backup everything
|
||||
tar -czf devbench-backup-$(date +%Y%m%d).tar.gz \
|
||||
data/ \
|
||||
logs/ \
|
||||
config.js \
|
||||
.env \
|
||||
docker-compose.yml
|
||||
```
|
||||
|
||||
### Recovery
|
||||
|
||||
#### Restore Database
|
||||
|
||||
```bash
|
||||
# 1. Stop container
|
||||
docker-compose down
|
||||
|
||||
# 2. Restore database
|
||||
cp backups/devbench-20251123.db data/devbench.db
|
||||
|
||||
# 3. Start container
|
||||
docker-compose up -d
|
||||
|
||||
# 4. Verify
|
||||
curl http://localhost:9090/health
|
||||
```
|
||||
|
||||
#### Full Recovery
|
||||
|
||||
```bash
|
||||
# 1. Extract backup
|
||||
tar -xzf devbench-backup-20251123.tar.gz
|
||||
|
||||
# 2. Deploy
|
||||
./deploy.sh
|
||||
|
||||
# 3. Verify all services
|
||||
```
|
||||
|
||||
### Disaster Recovery
|
||||
|
||||
#### Complete System Failure
|
||||
|
||||
```bash
|
||||
# 1. Fresh installation
|
||||
git clone <repository-url>
|
||||
cd ASF_devbench
|
||||
|
||||
# 2. Restore backups
|
||||
cp /backup/location/devbench.db data/
|
||||
cp /backup/location/.env .
|
||||
|
||||
# 3. Deploy
|
||||
./deploy.sh
|
||||
|
||||
# 4. Verify users and DevBenches
|
||||
```
|
||||
|
||||
## Security Hardening
|
||||
|
||||
### Change Default Credentials
|
||||
|
||||
```bash
|
||||
# 1. Login as admin
|
||||
# 2. Go to admin panel
|
||||
# 3. Reset admin password
|
||||
# Or use API:
|
||||
curl -X POST http://localhost:9090/admin/reset-password/1 \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"newPassword":"new-secure-password"}'
|
||||
```
|
||||
|
||||
### Update Session Secret
|
||||
|
||||
Edit `.env`:
|
||||
```bash
|
||||
SECRET_KEY=$(openssl rand -base64 32)
|
||||
```
|
||||
|
||||
Restart:
|
||||
```bash
|
||||
docker-compose restart
|
||||
```
|
||||
|
||||
### Enable HTTPS
|
||||
|
||||
Use Caddy for automatic HTTPS:
|
||||
```
|
||||
tbm.nabd-co.com {
|
||||
reverse_proxy devbench-manager:3001
|
||||
}
|
||||
```
|
||||
|
||||
### Restrict Access
|
||||
|
||||
Use firewall:
|
||||
```bash
|
||||
# Allow only specific IPs
|
||||
sudo ufw allow from 192.168.1.0/24 to any port 9090
|
||||
```
|
||||
|
||||
## Monitoring
|
||||
|
||||
### Health Monitoring
|
||||
|
||||
```bash
|
||||
# Simple health check script
|
||||
#!/bin/bash
|
||||
if curl -sf http://localhost:9090/health > /dev/null; then
|
||||
echo "OK"
|
||||
else
|
||||
echo "FAILED"
|
||||
# Send alert
|
||||
fi
|
||||
```
|
||||
|
||||
### Log Monitoring
|
||||
|
||||
```bash
|
||||
# Watch for errors
|
||||
docker-compose logs -f | grep -i error
|
||||
|
||||
# Count errors
|
||||
docker-compose logs | grep -i error | wc -l
|
||||
```
|
||||
|
||||
### Resource Monitoring
|
||||
|
||||
```bash
|
||||
# Container stats
|
||||
docker stats devbench-manager
|
||||
|
||||
# Disk usage
|
||||
du -sh data/ logs/
|
||||
|
||||
# Database size
|
||||
ls -lh data/devbench.db
|
||||
```
|
||||
|
||||
## Performance Tuning
|
||||
|
||||
### Database Optimization
|
||||
|
||||
```bash
|
||||
# Vacuum database
|
||||
docker exec devbench-manager sqlite3 /app/data/devbench.db "VACUUM;"
|
||||
|
||||
# Analyze database
|
||||
docker exec devbench-manager sqlite3 /app/data/devbench.db "ANALYZE;"
|
||||
```
|
||||
|
||||
### Container Resources
|
||||
|
||||
Edit `docker-compose.yml`:
|
||||
```yaml
|
||||
services:
|
||||
devbench-manager:
|
||||
deploy:
|
||||
resources:
|
||||
limits:
|
||||
cpus: '1.0'
|
||||
memory: 512M
|
||||
reservations:
|
||||
cpus: '0.5'
|
||||
memory: 256M
|
||||
```
|
||||
|
||||
## Support
|
||||
|
||||
### Getting Help
|
||||
|
||||
1. Check logs: `docker-compose logs`
|
||||
2. Review documentation in `/docs`
|
||||
3. Check GitHub issues
|
||||
4. Contact system administrator
|
||||
|
||||
### Reporting Issues
|
||||
|
||||
Include:
|
||||
- Docker version: `docker --version`
|
||||
- Docker Compose version: `docker-compose --version`
|
||||
- Container logs: `docker-compose logs`
|
||||
- Error messages
|
||||
- Steps to reproduce
|
||||
530
asf-cloud-server/TBM_devbench/docs/STRUCTURE.md
Normal file
530
asf-cloud-server/TBM_devbench/docs/STRUCTURE.md
Normal file
@@ -0,0 +1,530 @@
|
||||
# DevBench Manager - Project Structure
|
||||
|
||||
## Directory Tree
|
||||
|
||||
```
|
||||
ASF_devbench/
|
||||
├── docs/ # Documentation
|
||||
│ ├── ARCHITECTURE.md # System architecture
|
||||
│ ├── STRUCTURE.md # This file
|
||||
│ ├── DEPLOYMENT.md # Deployment guide
|
||||
│ └── API.md # API documentation
|
||||
│
|
||||
├── public/ # Static assets
|
||||
│ ├── css/
|
||||
│ │ └── style.css # Custom styles + dark theme
|
||||
│ ├── images/
|
||||
│ │ ├── nabd-logo.svg # Company logo (light)
|
||||
│ │ ├── nabd-logo-white.svg # Company logo (dark)
|
||||
│ │ └── tbm-icon.png # TBM branding icon
|
||||
│ └── downloads/
|
||||
│ └── db_vm_ssh_config_manager.exe # SSH config tool
|
||||
│
|
||||
├── views/ # EJS templates
|
||||
│ ├── layout.ejs # Base layout with navbar
|
||||
│ ├── login.ejs # Login page
|
||||
│ ├── dashboard.ejs # User dashboard
|
||||
│ ├── admin.ejs # Admin panel
|
||||
│ └── help.ejs # Help/documentation page
|
||||
│
|
||||
├── data/ # Database storage (created on deploy)
|
||||
│ └── devbench.db # SQLite database
|
||||
│
|
||||
├── logs/ # Application logs (created on deploy)
|
||||
│ └── *.log # Log files
|
||||
│
|
||||
├── server.js # Main application server
|
||||
├── config.js # Configuration settings
|
||||
├── package.json # Node.js dependencies
|
||||
├── provision_vm.sh # VM provisioning script
|
||||
│
|
||||
├── Dockerfile # Docker image definition
|
||||
├── docker-compose.yml # Container orchestration
|
||||
├── .dockerignore # Docker build exclusions
|
||||
│
|
||||
├── deploy.sh # Deployment script
|
||||
├── install.sh # Local installation script
|
||||
├── start.sh # Start script (Docker/Node)
|
||||
├── cleanup.sh # Cleanup script
|
||||
│
|
||||
├── .env.example # Environment variables template
|
||||
├── .gitignore # Git exclusions
|
||||
├── README.md # Main documentation
|
||||
└── CHANGELOG.md # Change history
|
||||
```
|
||||
|
||||
## File Descriptions
|
||||
|
||||
### Core Application Files
|
||||
|
||||
#### `server.js`
|
||||
**Purpose**: Main Express.js application server
|
||||
|
||||
**Key Components**:
|
||||
- Express app initialization
|
||||
- Database setup and schema
|
||||
- Route definitions
|
||||
- WebSocket server
|
||||
- Authentication middleware
|
||||
- DevBench management logic
|
||||
- Provision script executor
|
||||
- Status monitoring (60-second interval)
|
||||
|
||||
**Dependencies**:
|
||||
- express
|
||||
- express-session
|
||||
- sqlite3
|
||||
- bcryptjs
|
||||
- ws (WebSocket)
|
||||
- child_process (for script execution)
|
||||
|
||||
**Key Functions**:
|
||||
```javascript
|
||||
- requireAuth() // Authentication middleware
|
||||
- requireAdmin() // Admin authorization middleware
|
||||
- executeProvisionScript() // Execute VM provisioning
|
||||
- broadcastToUser() // WebSocket messaging
|
||||
```
|
||||
|
||||
#### `config.js`
|
||||
**Purpose**: Centralized configuration
|
||||
|
||||
**Settings**:
|
||||
- Server port
|
||||
- Database path
|
||||
- Session configuration
|
||||
- Default admin credentials
|
||||
- Validation patterns
|
||||
- Provision script settings
|
||||
|
||||
#### `provision_vm.sh`
|
||||
**Purpose**: VM provisioning and management
|
||||
|
||||
**Commands**:
|
||||
- `create <vm_name>`: Create new VM
|
||||
- `status <vm_name>`: Check VM status
|
||||
- `delete <vm_name>`: Delete VM
|
||||
- `activate <vm_name>`: Activate VM
|
||||
- `start <vm_name>`: Start VM
|
||||
- `stop <vm_name>`: Stop VM
|
||||
|
||||
**Features**:
|
||||
- SSH connection to remote host
|
||||
- Output logging
|
||||
- Connection info extraction
|
||||
- Error handling
|
||||
- Timeout management (30 minutes)
|
||||
|
||||
### Frontend Files
|
||||
|
||||
#### `views/layout.ejs`
|
||||
**Purpose**: Base template for all pages
|
||||
|
||||
**Features**:
|
||||
- Navigation bar with logo
|
||||
- Help link
|
||||
- Theme toggle button
|
||||
- WebSocket initialization
|
||||
- Bootstrap and Font Awesome imports
|
||||
- Dark theme JavaScript
|
||||
|
||||
#### `views/dashboard.ejs`
|
||||
**Purpose**: User dashboard
|
||||
|
||||
**Features**:
|
||||
- DevBench list with cards
|
||||
- Create DevBench modal
|
||||
- Connection info display (SSH/VNC ports)
|
||||
- Real-time log output
|
||||
- Status indicators
|
||||
- Copy-to-clipboard functionality
|
||||
|
||||
#### `views/admin.ejs`
|
||||
**Purpose**: Admin panel
|
||||
|
||||
**Features**:
|
||||
- User management table
|
||||
- Add user modal
|
||||
- DevBench overview table
|
||||
- Password reset
|
||||
- User deletion
|
||||
- DevBench deletion
|
||||
|
||||
#### `views/help.ejs`
|
||||
**Purpose**: Help and documentation
|
||||
|
||||
**Features**:
|
||||
- SSH Config Manager guide
|
||||
- Step-by-step instructions
|
||||
- Download link for tool
|
||||
- Connection information
|
||||
- Important notes
|
||||
|
||||
#### `views/login.ejs`
|
||||
**Purpose**: Authentication page
|
||||
|
||||
**Features**:
|
||||
- Login form
|
||||
- Error messages
|
||||
- Branding
|
||||
|
||||
### Static Assets
|
||||
|
||||
#### `public/css/style.css`
|
||||
**Purpose**: Custom styling
|
||||
|
||||
**Features**:
|
||||
- NABD brand colors
|
||||
- Dark theme styles
|
||||
- Component styling
|
||||
- Responsive design
|
||||
- Smooth transitions
|
||||
|
||||
#### `public/images/`
|
||||
**Purpose**: Image assets
|
||||
|
||||
**Files**:
|
||||
- `nabd-logo.svg`: Light theme logo
|
||||
- `nabd-logo-white.svg`: Dark theme logo
|
||||
- `tbm-icon.png`: Branding icon and favicon
|
||||
|
||||
#### `public/downloads/`
|
||||
**Purpose**: Downloadable files
|
||||
|
||||
**Files**:
|
||||
- `db_vm_ssh_config_manager.exe`: SSH configuration tool
|
||||
|
||||
### Deployment Files
|
||||
|
||||
#### `Dockerfile`
|
||||
**Purpose**: Docker image definition
|
||||
|
||||
**Base Image**: node:18-alpine
|
||||
|
||||
**Steps**:
|
||||
1. Set working directory
|
||||
2. Copy package files
|
||||
3. Install dependencies
|
||||
4. Copy application files
|
||||
5. Create data/logs directories
|
||||
6. Expose port 3001
|
||||
7. Set start command
|
||||
|
||||
#### `docker-compose.yml`
|
||||
**Purpose**: Container orchestration
|
||||
|
||||
**Configuration**:
|
||||
- Service: devbench-manager
|
||||
- Port mapping: 9090:3001
|
||||
- Volumes: data, logs, provision script
|
||||
- Networks: app-network, caddy_network
|
||||
- Health check: /health endpoint
|
||||
- Restart policy: unless-stopped
|
||||
|
||||
#### `deploy.sh`
|
||||
**Purpose**: Automated deployment
|
||||
|
||||
**Steps**:
|
||||
1. Create directories
|
||||
2. Set permissions
|
||||
3. Clean up old containers
|
||||
4. Create Docker network
|
||||
5. Build and start container
|
||||
6. Wait for startup
|
||||
7. Verify health
|
||||
8. Display access information
|
||||
|
||||
### Configuration Files
|
||||
|
||||
#### `package.json`
|
||||
**Purpose**: Node.js project configuration
|
||||
|
||||
**Scripts**:
|
||||
- `start`: Run production server
|
||||
- `dev`: Run with nodemon (auto-reload)
|
||||
|
||||
**Dependencies**:
|
||||
- express: Web framework
|
||||
- express-session: Session management
|
||||
- bcryptjs: Password hashing
|
||||
- sqlite3: Database
|
||||
- body-parser: Request parsing
|
||||
- ejs: Template engine
|
||||
- ws: WebSocket
|
||||
- child_process: Script execution
|
||||
|
||||
#### `.env.example`
|
||||
**Purpose**: Environment variables template
|
||||
|
||||
**Variables**:
|
||||
- NODE_ENV
|
||||
- PORT
|
||||
- SECRET_KEY
|
||||
- ADMIN_EMAIL
|
||||
- ADMIN_PASSWORD
|
||||
|
||||
#### `.gitignore`
|
||||
**Purpose**: Git exclusions
|
||||
|
||||
**Excluded**:
|
||||
- node_modules/
|
||||
- data/
|
||||
- logs/
|
||||
- .env
|
||||
- *.log
|
||||
- .DS_Store
|
||||
|
||||
### Utility Scripts
|
||||
|
||||
#### `install.sh`
|
||||
**Purpose**: Local installation
|
||||
|
||||
**Actions**:
|
||||
- Check Node.js version
|
||||
- Install npm dependencies
|
||||
- Display start instructions
|
||||
|
||||
#### `start.sh`
|
||||
**Purpose**: Start application
|
||||
|
||||
**Logic**:
|
||||
- Check for Docker
|
||||
- Start with Docker Compose if available
|
||||
- Fall back to Node.js
|
||||
- Install dependencies if needed
|
||||
|
||||
#### `cleanup.sh`
|
||||
**Purpose**: Clean up containers
|
||||
|
||||
**Actions**:
|
||||
- Stop containers
|
||||
- Remove containers
|
||||
- Prune dangling containers
|
||||
- List volumes
|
||||
|
||||
### Documentation Files
|
||||
|
||||
#### `README.md`
|
||||
**Purpose**: Main project documentation
|
||||
|
||||
**Sections**:
|
||||
- Features overview
|
||||
- Installation instructions
|
||||
- Configuration guide
|
||||
- Usage instructions
|
||||
- API endpoints
|
||||
- Troubleshooting
|
||||
|
||||
#### `CHANGELOG.md`
|
||||
**Purpose**: Change history
|
||||
|
||||
**Content**:
|
||||
- Recent updates
|
||||
- SSH configuration changes
|
||||
- Output format changes
|
||||
- New features
|
||||
- Migration notes
|
||||
|
||||
#### `docs/ARCHITECTURE.md`
|
||||
**Purpose**: System architecture
|
||||
|
||||
**Content**:
|
||||
- Architecture diagrams
|
||||
- Component descriptions
|
||||
- Data flow
|
||||
- Security architecture
|
||||
- Technology stack
|
||||
|
||||
#### `docs/DEPLOYMENT.md`
|
||||
**Purpose**: Deployment guide
|
||||
|
||||
**Content**:
|
||||
- Prerequisites
|
||||
- Deployment steps
|
||||
- Configuration
|
||||
- Troubleshooting
|
||||
- Maintenance
|
||||
|
||||
## Data Flow Through Files
|
||||
|
||||
### DevBench Creation
|
||||
```
|
||||
1. views/dashboard.ejs
|
||||
↓ (User clicks Create)
|
||||
2. server.js (POST /create-devbench)
|
||||
↓ (Validates and creates DB record)
|
||||
3. server.js (executeProvisionScript)
|
||||
↓ (Spawns child process)
|
||||
4. provision_vm.sh
|
||||
↓ (SSH to remote host)
|
||||
5. Remote VM Host
|
||||
↓ (Creates VM, returns info)
|
||||
6. provision_vm.sh
|
||||
↓ (Parses output)
|
||||
7. server.js
|
||||
↓ (Updates database)
|
||||
8. WebSocket
|
||||
↓ (Broadcasts to user)
|
||||
9. views/dashboard.ejs
|
||||
↓ (Updates UI)
|
||||
```
|
||||
|
||||
### Authentication
|
||||
```
|
||||
1. views/login.ejs
|
||||
↓ (User submits form)
|
||||
2. server.js (POST /login)
|
||||
↓ (Validates credentials)
|
||||
3. config.js (validation patterns)
|
||||
↓
|
||||
4. server.js (bcrypt compare)
|
||||
↓ (Creates session)
|
||||
5. express-session
|
||||
↓ (Stores session)
|
||||
6. server.js (redirect)
|
||||
↓
|
||||
7. views/dashboard.ejs or views/admin.ejs
|
||||
```
|
||||
|
||||
### Theme Toggle
|
||||
```
|
||||
1. views/layout.ejs (Theme toggle button)
|
||||
↓ (User clicks)
|
||||
2. JavaScript (theme toggle handler)
|
||||
↓ (Toggles class)
|
||||
3. public/css/style.css (dark-theme styles)
|
||||
↓ (Applies styles)
|
||||
4. localStorage
|
||||
↓ (Saves preference)
|
||||
```
|
||||
|
||||
## Key Directories
|
||||
|
||||
### `/data`
|
||||
- **Created**: On first deployment
|
||||
- **Purpose**: SQLite database storage
|
||||
- **Persistence**: Docker volume
|
||||
- **Backup**: Copy devbench.db file
|
||||
|
||||
### `/logs`
|
||||
- **Created**: On first deployment
|
||||
- **Purpose**: Application logs
|
||||
- **Persistence**: Docker volume
|
||||
- **Rotation**: Manual
|
||||
|
||||
### `/public`
|
||||
- **Purpose**: Static assets served by Express
|
||||
- **Access**: Direct URL paths
|
||||
- **Caching**: Browser caching enabled
|
||||
|
||||
### `/views`
|
||||
- **Purpose**: EJS templates
|
||||
- **Rendering**: Server-side
|
||||
- **Access**: Via routes only
|
||||
|
||||
### `/docs`
|
||||
- **Purpose**: Project documentation
|
||||
- **Format**: Markdown
|
||||
- **Access**: File system only
|
||||
|
||||
## Configuration Hierarchy
|
||||
|
||||
```
|
||||
1. Environment Variables (.env)
|
||||
↓
|
||||
2. config.js (defaults)
|
||||
↓
|
||||
3. docker-compose.yml (container env)
|
||||
↓
|
||||
4. Application runtime
|
||||
```
|
||||
|
||||
## Port Mapping
|
||||
|
||||
```
|
||||
External → Container → Application
|
||||
9090 → 3001 → Express Server
|
||||
```
|
||||
|
||||
## Network Architecture
|
||||
|
||||
```
|
||||
Internet
|
||||
↓
|
||||
Caddy (caddy_network)
|
||||
↓
|
||||
DevBench Manager (caddy_network + app-network)
|
||||
↓
|
||||
SQLite (local file)
|
||||
↓
|
||||
SSH (external)
|
||||
↓
|
||||
Remote VM Host
|
||||
```
|
||||
|
||||
## File Permissions
|
||||
|
||||
### Scripts
|
||||
- `provision_vm.sh`: 755 (executable)
|
||||
- `deploy.sh`: 755 (executable)
|
||||
- `install.sh`: 755 (executable)
|
||||
- `start.sh`: 755 (executable)
|
||||
- `cleanup.sh`: 755 (executable)
|
||||
|
||||
### Directories
|
||||
- `data/`: 755
|
||||
- `logs/`: 755
|
||||
- `public/`: 755
|
||||
|
||||
### Database
|
||||
- `devbench.db`: 644 (read/write for owner)
|
||||
|
||||
## Build Process
|
||||
|
||||
### Docker Build
|
||||
```
|
||||
1. Read Dockerfile
|
||||
2. Pull node:18-alpine
|
||||
3. Copy package.json
|
||||
4. npm install
|
||||
5. Copy application files
|
||||
6. Set permissions
|
||||
7. Create image
|
||||
```
|
||||
|
||||
### Docker Compose
|
||||
```
|
||||
1. Read docker-compose.yml
|
||||
2. Create networks
|
||||
3. Build image (if needed)
|
||||
4. Create container
|
||||
5. Mount volumes
|
||||
6. Start container
|
||||
7. Run health checks
|
||||
```
|
||||
|
||||
## Runtime Process
|
||||
|
||||
### Container Startup
|
||||
```
|
||||
1. Docker starts container
|
||||
2. Node.js starts
|
||||
3. server.js executes
|
||||
4. Database initialized
|
||||
5. Express server starts
|
||||
6. WebSocket server starts
|
||||
7. Health check passes
|
||||
8. Ready for connections
|
||||
```
|
||||
|
||||
### Request Handling
|
||||
```
|
||||
1. Request arrives at Caddy
|
||||
2. Proxied to container
|
||||
3. Express routes request
|
||||
4. Middleware checks auth
|
||||
5. Controller handles logic
|
||||
6. Database query (if needed)
|
||||
7. Template rendered
|
||||
8. Response sent
|
||||
```
|
||||
BIN
asf-cloud-server/TBM_devbench/icon.png
Normal file
BIN
asf-cloud-server/TBM_devbench/icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 76 KiB |
BIN
asf-cloud-server/TBM_devbench/icone.ico
Normal file
BIN
asf-cloud-server/TBM_devbench/icone.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 184 KiB |
38
asf-cloud-server/TBM_devbench/install.sh
Normal file
38
asf-cloud-server/TBM_devbench/install.sh
Normal file
@@ -0,0 +1,38 @@
|
||||
#!/bin/bash
|
||||
|
||||
echo "Installing DevBench Manager..."
|
||||
|
||||
# Check if Node.js is installed
|
||||
if ! command -v node &> /dev/null; then
|
||||
echo "Node.js is not installed. Please install Node.js 18+ first."
|
||||
echo "Visit: https://nodejs.org/"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check Node.js version
|
||||
NODE_VERSION=$(node -v | cut -d'v' -f2 | cut -d'.' -f1)
|
||||
if [ "$NODE_VERSION" -lt 18 ]; then
|
||||
echo "Node.js version 18 or higher is required. Current version: $(node -v)"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Node.js version: $(node -v) ✓"
|
||||
|
||||
# Install npm dependencies
|
||||
echo "Installing npm dependencies..."
|
||||
npm install
|
||||
|
||||
if [ $? -eq 0 ]; then
|
||||
echo "✓ Dependencies installed successfully!"
|
||||
echo ""
|
||||
echo "Setup complete! You can now start the application with:"
|
||||
echo " npm start"
|
||||
echo " or"
|
||||
echo " ./start.sh"
|
||||
echo ""
|
||||
echo "The application will be available at: http://localhost:3001"
|
||||
echo "Default admin login: admin / admin123"
|
||||
else
|
||||
echo "✗ Failed to install dependencies"
|
||||
exit 1
|
||||
fi
|
||||
26
asf-cloud-server/TBM_devbench/package.json
Normal file
26
asf-cloud-server/TBM_devbench/package.json
Normal file
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"name": "devbench-manager",
|
||||
"version": "1.0.0",
|
||||
"description": "DevBench Manager Web Application",
|
||||
"main": "server.js",
|
||||
"scripts": {
|
||||
"start": "node server.js",
|
||||
"dev": "nodemon server.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"express": "^4.18.2",
|
||||
"express-session": "^1.17.3",
|
||||
"bcryptjs": "^2.4.3",
|
||||
"sqlite3": "^5.1.6",
|
||||
"body-parser": "^1.20.2",
|
||||
"ejs": "^3.1.9",
|
||||
"child_process": "^1.0.2",
|
||||
"ws": "^8.14.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"nodemon": "^3.0.1"
|
||||
},
|
||||
"keywords": ["devbench", "vm", "manager"],
|
||||
"author": "",
|
||||
"license": "MIT"
|
||||
}
|
||||
155
asf-cloud-server/TBM_devbench/provision_vm.sh
Normal file
155
asf-cloud-server/TBM_devbench/provision_vm.sh
Normal file
@@ -0,0 +1,155 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Set up logging
|
||||
LOG_DIR="/var/log/devbench"
|
||||
LOG_FILE="$LOG_DIR/provision_vm_$(date +%Y%m%d_%H%M%S).log"
|
||||
|
||||
# Create log directory if it doesn't exist
|
||||
mkdir -p "$LOG_DIR"
|
||||
chmod 777 "$LOG_DIR"
|
||||
|
||||
# Function to log messages
|
||||
log() {
|
||||
local timestamp=$(date '+%Y-%m-%d %H:%M:%S')
|
||||
echo "[$timestamp] $1" | tee -a "$LOG_FILE"
|
||||
}
|
||||
|
||||
log "Starting provision_vm.sh with arguments: $*"
|
||||
|
||||
# Check if enough arguments were provided for the remote script.
|
||||
# The remote 'provision_vm.sh' script expects at least 2 arguments: <command> <vm_name>.
|
||||
if [ "$#" -lt 2 ]; then
|
||||
error_msg="Error: Please provide at least two arguments for the remote command."
|
||||
log "$error_msg"
|
||||
echo "Usage: $0 <command> <vm_name>"
|
||||
echo "Example: $0 create username_vmname"
|
||||
echo " $0 status username_vmname"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# SSH connection details
|
||||
SSH_USER="asf"
|
||||
SSH_HOST="asf-server.duckdns.org"
|
||||
SSH_PORT="49152"
|
||||
SSH_PASS="ASF"
|
||||
|
||||
# The path to the script on the remote server
|
||||
REMOTE_SCRIPT_PATH="./provision_vm.sh"
|
||||
|
||||
# Get command and VM name from arguments
|
||||
COMMAND="$1"
|
||||
VM_NAME="$2"
|
||||
|
||||
# Validate command
|
||||
if [[ ! "$COMMAND" =~ ^(create|status|delete|start|stop|activate)$ ]]; then
|
||||
log "Error: Invalid command '$COMMAND'. Must be one of: create, status, delete, start, stop, activate"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Validate VM name format (username_vmname)
|
||||
if [[ ! "$VM_NAME" =~ ^[a-zA-Z0-9_]+_[a-zA-Z0-9_-]+$ ]]; then
|
||||
log "Error: Invalid VM name format. Must be in format: username_vmname"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Extract username from VM name (everything before first underscore)
|
||||
USERNAME="${VM_NAME%%_*}"
|
||||
|
||||
log "Command: $COMMAND, VM: $VM_NAME, User: $USERNAME"
|
||||
|
||||
# Check if sshpass is available
|
||||
if ! command -v sshpass &> /dev/null; then
|
||||
log "Error: sshpass is not installed. Please install it with 'apt-get install sshpass'"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check if we can resolve the host
|
||||
if ! getent hosts "$SSH_HOST" >/dev/null 2>&1; then
|
||||
log "Error: Cannot resolve host $SSH_HOST"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Create a temporary file to capture output
|
||||
TEMP_OUTPUT=$(mktemp)
|
||||
log "Temporary output file: $TEMP_OUTPUT"
|
||||
|
||||
# Function to clean up temp file
|
||||
cleanup() {
|
||||
if [ -f "$TEMP_OUTPUT" ]; then
|
||||
rm -f "$TEMP_OUTPUT"
|
||||
log "Temporary files cleaned up"
|
||||
fi
|
||||
}
|
||||
# Set up trap to ensure cleanup happens on script exit
|
||||
trap cleanup EXIT
|
||||
|
||||
# Build the full SSH command
|
||||
SSH_CMD="ssh -p $SSH_PORT -o StrictHostKeyChecking=no -o ConnectTimeout=10 -o ServerAliveInterval=60 -o ServerAliveCountMax=30"
|
||||
FULL_CMD="$REMOTE_SCRIPT_PATH $COMMAND $VM_NAME"
|
||||
|
||||
log "Executing remote command: $FULL_CMD"
|
||||
|
||||
# Execute the remote command with sshpass and capture output
|
||||
{
|
||||
# Increase timeout to 1800 seconds (30 minutes) and ensure we capture all output
|
||||
if ! output=$(timeout 1800 sshpass -p "$SSH_PASS" $SSH_CMD "$SSH_USER@$SSH_HOST" "$FULL_CMD" 2>&1); then
|
||||
EXIT_CODE=$?
|
||||
# Capture any partial output even if command failed
|
||||
echo "$output"
|
||||
if [ $EXIT_CODE -eq 124 ]; then
|
||||
log "ERROR: Command timed out after 30 minutes"
|
||||
exit 124
|
||||
else
|
||||
log "ERROR: Command failed with exit code $EXIT_CODE"
|
||||
exit $EXIT_CODE
|
||||
fi
|
||||
else
|
||||
# Command succeeded, output the result
|
||||
echo "$output"
|
||||
fi
|
||||
} | tee "$TEMP_OUTPUT"
|
||||
|
||||
# Process the output to extract connection information
|
||||
SSH_PORT=""
|
||||
VNC_PORT=""
|
||||
VM_NAME_ACTUAL=""
|
||||
|
||||
# Read the output line by line to parse connection info
|
||||
while IFS= read -r line; do
|
||||
# Extract SSH port (e.g., "SSH Port: 6004")
|
||||
if [[ $line == *"SSH Port:"* ]]; then
|
||||
SSH_PORT=$(echo "$line" | grep -oE 'SSH Port: [0-9]+' | cut -d' ' -f3)
|
||||
# Extract VNC port (e.g., "VNC Port: 5004")
|
||||
elif [[ $line == *"VNC Port:"* ]]; then
|
||||
VNC_PORT=$(echo "$line" | grep -oE 'VNC Port: [0-9]+' | cut -d' ' -f3)
|
||||
# Extract VM name from success message
|
||||
elif [[ $line == *"VM"*"Created and Ready"* ]]; then
|
||||
VM_NAME_ACTUAL=$(echo "$line" | sed -n 's/.*VM \([^ ]*\) Created and Ready.*/\1/p')
|
||||
fi
|
||||
|
||||
# Log each line to the log file
|
||||
log "$line"
|
||||
done < "$TEMP_OUTPUT"
|
||||
|
||||
# Output the connection information in a structured format
|
||||
echo "VM_CREATION_COMPLETE"
|
||||
if [ -n "$VM_NAME_ACTUAL" ]; then
|
||||
echo "VM_NAME=$VM_NAME_ACTUAL"
|
||||
fi
|
||||
if [ -n "$SSH_PORT" ]; then
|
||||
echo "SSH_PORT=$SSH_PORT"
|
||||
fi
|
||||
if [ -n "$VNC_PORT" ]; then
|
||||
echo "VNC_PORT=$VNC_PORT"
|
||||
fi
|
||||
|
||||
# Check if the command was successful
|
||||
if [ ${PIPESTATUS[0]} -eq 0 ]; then
|
||||
log "Remote command executed successfully"
|
||||
exit 0
|
||||
else
|
||||
log "Remote command failed"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
log "Provisioning script completed successfully"
|
||||
308
asf-cloud-server/TBM_devbench/public/css/style.css
Normal file
308
asf-cloud-server/TBM_devbench/public/css/style.css
Normal file
@@ -0,0 +1,308 @@
|
||||
/* Custom styles for DevBench Manager */
|
||||
|
||||
.navbar-brand {
|
||||
font-weight: bold;
|
||||
color: #ffffff !important;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.navbar-brand img {
|
||||
height: 40px;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.company-logo {
|
||||
max-height: 40px;
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.status-active {
|
||||
color: #28a745;
|
||||
}
|
||||
|
||||
.status-inactive {
|
||||
color: #dc3545;
|
||||
}
|
||||
|
||||
.status-creating {
|
||||
color: #ffc107;
|
||||
}
|
||||
|
||||
.log-output {
|
||||
background-color: #1e1e1e;
|
||||
color: #ffffff;
|
||||
font-family: 'Courier New', monospace;
|
||||
font-size: 12px;
|
||||
padding: 15px;
|
||||
border-radius: 5px;
|
||||
max-height: 400px;
|
||||
overflow-y: auto;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
.connection-info {
|
||||
background-color: #f8f9fa;
|
||||
border: 1px solid #dee2e6;
|
||||
border-radius: 5px;
|
||||
padding: 10px;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.connection-info code {
|
||||
background-color: #e9ecef;
|
||||
padding: 2px 4px;
|
||||
border-radius: 3px;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.login-card {
|
||||
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.brand-colors {
|
||||
background: linear-gradient(135deg, #f39c12 0%, #1a365d 100%);
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
background-clip: text;
|
||||
}
|
||||
|
||||
/* NABD Solutions brand colors */
|
||||
.nabd-orange {
|
||||
color: #f39c12;
|
||||
}
|
||||
|
||||
.nabd-blue {
|
||||
color: #1a365d;
|
||||
}
|
||||
|
||||
.bg-nabd-gradient {
|
||||
background: linear-gradient(135deg, #f39c12 0%, #1a365d 100%);
|
||||
}
|
||||
|
||||
/* Dark blue theme overrides */
|
||||
.bg-primary {
|
||||
background-color: #1a365d !important;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background-color: #1a365d;
|
||||
border-color: #1a365d;
|
||||
}
|
||||
|
||||
.btn-primary:hover {
|
||||
background-color: #2c5282;
|
||||
border-color: #2c5282;
|
||||
}
|
||||
|
||||
.btn-primary:focus {
|
||||
background-color: #2c5282;
|
||||
border-color: #2c5282;
|
||||
box-shadow: 0 0 0 0.2rem rgba(26, 54, 93, 0.25);
|
||||
}
|
||||
|
||||
.btn-outline-primary {
|
||||
color: #1a365d;
|
||||
border-color: #1a365d;
|
||||
}
|
||||
|
||||
.btn-outline-primary:hover {
|
||||
background-color: #1a365d;
|
||||
border-color: #1a365d;
|
||||
}
|
||||
|
||||
.text-primary {
|
||||
color: #1a365d !important;
|
||||
}
|
||||
|
||||
.card-header.bg-primary {
|
||||
background-color: #1a365d !important;
|
||||
}
|
||||
|
||||
/* Password info styling */
|
||||
.connection-info small {
|
||||
display: block;
|
||||
margin-top: 5px;
|
||||
padding: 3px 8px;
|
||||
background-color: #e8f4fd;
|
||||
border-radius: 3px;
|
||||
border-left: 3px solid #1a365d;
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
DARK THEME
|
||||
============================================ */
|
||||
|
||||
/* Dark theme toggle button */
|
||||
.theme-toggle {
|
||||
position: fixed;
|
||||
bottom: 20px;
|
||||
right: 20px;
|
||||
z-index: 1000;
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
border-radius: 50%;
|
||||
background: linear-gradient(135deg, #f39c12 0%, #1a365d 100%);
|
||||
border: none;
|
||||
color: white;
|
||||
font-size: 20px;
|
||||
cursor: pointer;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
|
||||
transition: transform 0.3s ease, box-shadow 0.3s ease;
|
||||
}
|
||||
|
||||
.theme-toggle:hover {
|
||||
transform: scale(1.1);
|
||||
box-shadow: 0 6px 16px rgba(0, 0, 0, 0.4);
|
||||
}
|
||||
|
||||
/* Dark theme styles */
|
||||
body.dark-theme {
|
||||
background-color: #1a1a1a;
|
||||
color: #e0e0e0;
|
||||
}
|
||||
|
||||
body.dark-theme .navbar {
|
||||
background-color: #0d1b2a !important;
|
||||
}
|
||||
|
||||
body.dark-theme .card {
|
||||
background-color: #2d2d2d;
|
||||
border-color: #404040;
|
||||
color: #e0e0e0;
|
||||
}
|
||||
|
||||
body.dark-theme .card-header {
|
||||
background-color: #1a365d;
|
||||
border-color: #404040;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
body.dark-theme .card-header.bg-info {
|
||||
background-color: #0c5460 !important;
|
||||
}
|
||||
|
||||
body.dark-theme .card-header.bg-warning {
|
||||
background-color: #856404 !important;
|
||||
}
|
||||
|
||||
body.dark-theme .table {
|
||||
color: #e0e0e0;
|
||||
}
|
||||
|
||||
body.dark-theme .table-striped tbody tr:nth-of-type(odd) {
|
||||
background-color: #333333;
|
||||
}
|
||||
|
||||
body.dark-theme .table-striped tbody tr:nth-of-type(even) {
|
||||
background-color: #2d2d2d;
|
||||
}
|
||||
|
||||
body.dark-theme .modal-content {
|
||||
background-color: #2d2d2d;
|
||||
color: #e0e0e0;
|
||||
}
|
||||
|
||||
body.dark-theme .modal-header {
|
||||
border-bottom-color: #404040;
|
||||
}
|
||||
|
||||
body.dark-theme .modal-footer {
|
||||
border-top-color: #404040;
|
||||
}
|
||||
|
||||
body.dark-theme .form-control {
|
||||
background-color: #1a1a1a;
|
||||
border-color: #404040;
|
||||
color: #e0e0e0;
|
||||
}
|
||||
|
||||
body.dark-theme .form-control:focus {
|
||||
background-color: #1a1a1a;
|
||||
border-color: #1a365d;
|
||||
color: #e0e0e0;
|
||||
}
|
||||
|
||||
body.dark-theme .connection-info {
|
||||
background-color: #1a1a1a;
|
||||
border-color: #404040;
|
||||
}
|
||||
|
||||
body.dark-theme .connection-info code {
|
||||
background-color: #0d1b2a;
|
||||
color: #f39c12;
|
||||
}
|
||||
|
||||
body.dark-theme .alert-info {
|
||||
background-color: #0c5460;
|
||||
border-color: #0c5460;
|
||||
color: #d1ecf1;
|
||||
}
|
||||
|
||||
body.dark-theme .alert-info .alert-link {
|
||||
color: #bee5eb;
|
||||
}
|
||||
|
||||
body.dark-theme .text-muted {
|
||||
color: #999999 !important;
|
||||
}
|
||||
|
||||
body.dark-theme .btn-secondary {
|
||||
background-color: #404040;
|
||||
border-color: #404040;
|
||||
}
|
||||
|
||||
body.dark-theme .btn-secondary:hover {
|
||||
background-color: #555555;
|
||||
border-color: #555555;
|
||||
}
|
||||
|
||||
body.dark-theme .btn-outline-primary {
|
||||
color: #f39c12;
|
||||
border-color: #f39c12;
|
||||
}
|
||||
|
||||
body.dark-theme .btn-outline-primary:hover {
|
||||
background-color: #f39c12;
|
||||
border-color: #f39c12;
|
||||
color: #1a1a1a;
|
||||
}
|
||||
|
||||
body.dark-theme .login-card {
|
||||
background-color: #2d2d2d;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
|
||||
body.dark-theme .form-text {
|
||||
color: #999999;
|
||||
}
|
||||
|
||||
body.dark-theme .connection-info small {
|
||||
background-color: #0d1b2a;
|
||||
border-left-color: #f39c12;
|
||||
color: #e0e0e0;
|
||||
}
|
||||
|
||||
body.dark-theme .help-steps code {
|
||||
background-color: #0d1b2a;
|
||||
color: #f39c12;
|
||||
}
|
||||
|
||||
body.dark-theme .help-steps strong {
|
||||
color: #f39c12;
|
||||
}
|
||||
|
||||
body.dark-theme pre {
|
||||
background-color: #0d1b2a !important;
|
||||
color: #e0e0e0;
|
||||
}
|
||||
|
||||
/* Smooth transitions for theme switching */
|
||||
body,
|
||||
.card,
|
||||
.modal-content,
|
||||
.form-control,
|
||||
.connection-info,
|
||||
.table {
|
||||
transition: background-color 0.3s ease, color 0.3s ease, border-color 0.3s ease;
|
||||
}
|
||||
Binary file not shown.
@@ -0,0 +1,13 @@
|
||||
<svg width="200" height="60" viewBox="0 0 200 60" xmlns="http://www.w3.org/2000/svg">
|
||||
<!-- Pulse/Wave lines in orange -->
|
||||
<g stroke="#f39c12" stroke-width="3" fill="none">
|
||||
<!-- Main pulse line -->
|
||||
<path d="M10 30 L25 30 L30 15 L35 45 L40 10 L45 50 L50 30 L65 30"/>
|
||||
</g>
|
||||
|
||||
<!-- NABD text in white for navbar -->
|
||||
<text x="75" y="35" font-family="Arial, sans-serif" font-size="24" font-weight="bold" fill="white">NABD</text>
|
||||
|
||||
<!-- SOLUTIONS text in white -->
|
||||
<text x="75" y="50" font-family="Arial, sans-serif" font-size="10" font-weight="normal" fill="white" letter-spacing="2px">SOLUTIONS</text>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 626 B |
13
asf-cloud-server/TBM_devbench/public/images/nabd-logo.svg
Normal file
13
asf-cloud-server/TBM_devbench/public/images/nabd-logo.svg
Normal file
@@ -0,0 +1,13 @@
|
||||
<svg width="200" height="60" viewBox="0 0 200 60" xmlns="http://www.w3.org/2000/svg">
|
||||
<!-- Pulse/Wave lines in orange -->
|
||||
<g stroke="#f39c12" stroke-width="3" fill="none">
|
||||
<!-- Main pulse line -->
|
||||
<path d="M10 30 L25 30 L30 15 L35 45 L40 10 L45 50 L50 30 L65 30"/>
|
||||
</g>
|
||||
|
||||
<!-- NABD text in blue -->
|
||||
<text x="75" y="35" font-family="Arial, sans-serif" font-size="24" font-weight="bold" fill="#2980b9">NABD</text>
|
||||
|
||||
<!-- SOLUTIONS text in blue -->
|
||||
<text x="75" y="50" font-family="Arial, sans-serif" font-size="10" font-weight="normal" fill="#2980b9" letter-spacing="2px">SOLUTIONS</text>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 617 B |
BIN
asf-cloud-server/TBM_devbench/public/images/tbm-icon.png
Normal file
BIN
asf-cloud-server/TBM_devbench/public/images/tbm-icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 76 KiB |
593
asf-cloud-server/TBM_devbench/server.js
Normal file
593
asf-cloud-server/TBM_devbench/server.js
Normal file
@@ -0,0 +1,593 @@
|
||||
const express = require('express');
|
||||
const session = require('express-session');
|
||||
const bcrypt = require('bcryptjs');
|
||||
const sqlite3 = require('sqlite3').verbose();
|
||||
const bodyParser = require('body-parser');
|
||||
const { spawn } = require('child_process');
|
||||
const WebSocket = require('ws');
|
||||
const http = require('http');
|
||||
const path = require('path');
|
||||
const config = require('./config');
|
||||
|
||||
const app = express();
|
||||
const server = http.createServer(app);
|
||||
const wss = new WebSocket.Server({ server });
|
||||
|
||||
// Database setup
|
||||
const db = new sqlite3.Database(config.database.path);
|
||||
|
||||
// Initialize database tables
|
||||
db.serialize(() => {
|
||||
// Users table
|
||||
db.run(`CREATE TABLE IF NOT EXISTS users (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
username TEXT UNIQUE NOT NULL,
|
||||
email TEXT NOT NULL,
|
||||
password TEXT NOT NULL,
|
||||
is_admin INTEGER DEFAULT 0,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||
)`);
|
||||
|
||||
// DevBenches table
|
||||
db.run(`CREATE TABLE IF NOT EXISTS devbenches (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
user_id INTEGER NOT NULL,
|
||||
name TEXT NOT NULL,
|
||||
actual_name TEXT,
|
||||
status TEXT DEFAULT 'inactive',
|
||||
ssh_info TEXT,
|
||||
vnc_info TEXT,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (user_id) REFERENCES users (id)
|
||||
)`);
|
||||
|
||||
// Create default admin user if not exists
|
||||
const adminPassword = bcrypt.hashSync(config.defaultAdmin.password, 10);
|
||||
db.run(`INSERT OR IGNORE INTO users (username, email, password, is_admin)
|
||||
VALUES (?, ?, ?, 1)`, [config.defaultAdmin.username, config.defaultAdmin.email, adminPassword]);
|
||||
});
|
||||
|
||||
// Middleware
|
||||
app.use(bodyParser.urlencoded({ extended: true }));
|
||||
app.use(bodyParser.json());
|
||||
app.use(express.static('public'));
|
||||
app.set('view engine', 'ejs');
|
||||
|
||||
app.use(session({
|
||||
secret: config.session.secret,
|
||||
resave: false,
|
||||
saveUninitialized: false,
|
||||
cookie: { secure: false, maxAge: config.session.maxAge }
|
||||
}));
|
||||
|
||||
// Authentication middleware
|
||||
const requireAuth = (req, res, next) => {
|
||||
if (req.session.userId) {
|
||||
next();
|
||||
} else {
|
||||
res.redirect('/login');
|
||||
}
|
||||
};
|
||||
|
||||
const requireAdmin = (req, res, next) => {
|
||||
if (req.session.userId && req.session.isAdmin) {
|
||||
next();
|
||||
} else {
|
||||
res.status(403).send('Access denied');
|
||||
}
|
||||
};
|
||||
|
||||
// WebSocket connections for real-time updates
|
||||
const clients = new Map();
|
||||
|
||||
wss.on('connection', (ws, req) => {
|
||||
console.log('WebSocket connection established');
|
||||
|
||||
ws.on('message', (message) => {
|
||||
try {
|
||||
const data = JSON.parse(message);
|
||||
if (data.type === 'register' && data.userId) {
|
||||
clients.set(data.userId, ws);
|
||||
console.log(`User ${data.userId} registered for WebSocket updates`);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('WebSocket message error:', error);
|
||||
}
|
||||
});
|
||||
|
||||
ws.on('close', () => {
|
||||
// Remove this connection from all users
|
||||
for (const [userId, client] of clients.entries()) {
|
||||
if (client === ws) {
|
||||
clients.delete(userId);
|
||||
console.log(`User ${userId} WebSocket disconnected`);
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Broadcast to specific user
|
||||
const broadcastToUser = (userId, message) => {
|
||||
const client = clients.get(userId);
|
||||
if (client && client.readyState === WebSocket.OPEN) {
|
||||
try {
|
||||
client.send(JSON.stringify(message));
|
||||
console.log(`Sent message to user ${userId}:`, message.type);
|
||||
} catch (error) {
|
||||
console.error(`Error sending message to user ${userId}:`, error);
|
||||
clients.delete(userId);
|
||||
}
|
||||
} else {
|
||||
console.log(`No active WebSocket for user ${userId}`);
|
||||
}
|
||||
};
|
||||
|
||||
// Health check endpoint
|
||||
app.get('/health', (req, res) => {
|
||||
res.json({
|
||||
status: 'ok',
|
||||
timestamp: new Date().toISOString(),
|
||||
version: '1.0.0'
|
||||
});
|
||||
});
|
||||
|
||||
// User info endpoint for WebSocket registration
|
||||
app.get('/api/user-info', requireAuth, (req, res) => {
|
||||
res.json({
|
||||
userId: req.session.userId,
|
||||
username: req.session.username,
|
||||
isAdmin: req.session.isAdmin
|
||||
});
|
||||
});
|
||||
|
||||
// Routes
|
||||
app.get('/', (req, res) => {
|
||||
if (req.session.userId) {
|
||||
if (req.session.isAdmin) {
|
||||
res.redirect('/admin');
|
||||
} else {
|
||||
res.redirect('/dashboard');
|
||||
}
|
||||
} else {
|
||||
res.redirect('/login');
|
||||
}
|
||||
});
|
||||
|
||||
app.get('/login', (req, res) => {
|
||||
res.render('login', { error: null });
|
||||
});
|
||||
|
||||
app.post('/login', (req, res) => {
|
||||
const { username, password } = req.body;
|
||||
|
||||
db.get('SELECT * FROM users WHERE username = ?', [username], (err, user) => {
|
||||
if (err || !user || !bcrypt.compareSync(password, user.password)) {
|
||||
return res.render('login', { error: 'Invalid username or password' });
|
||||
}
|
||||
|
||||
req.session.userId = user.id;
|
||||
req.session.username = user.username;
|
||||
req.session.isAdmin = user.is_admin === 1;
|
||||
|
||||
if (user.is_admin) {
|
||||
res.redirect('/admin');
|
||||
} else {
|
||||
res.redirect('/dashboard');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
app.get('/logout', (req, res) => {
|
||||
req.session.destroy();
|
||||
res.redirect('/login');
|
||||
});
|
||||
|
||||
// Admin routes
|
||||
app.get('/admin', requireAuth, requireAdmin, (req, res) => {
|
||||
db.all(`SELECT u.*, COUNT(d.id) as devbench_count
|
||||
FROM users u
|
||||
LEFT JOIN devbenches d ON u.id = d.user_id
|
||||
WHERE u.is_admin = 0
|
||||
GROUP BY u.id`, (err, users) => {
|
||||
if (err) {
|
||||
console.error(err);
|
||||
return res.status(500).send('Database error');
|
||||
}
|
||||
|
||||
db.all(`SELECT d.*, u.username
|
||||
FROM devbenches d
|
||||
JOIN users u ON d.user_id = u.id
|
||||
ORDER BY d.created_at DESC`, (err, devbenches) => {
|
||||
if (err) {
|
||||
console.error(err);
|
||||
return res.status(500).send('Database error');
|
||||
}
|
||||
|
||||
res.render('admin', { users, devbenches });
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
app.post('/admin/add-user', requireAuth, requireAdmin, (req, res) => {
|
||||
const { username, email, password } = req.body;
|
||||
|
||||
// Validate username (no spaces, numbers, or special characters)
|
||||
if (!config.validation.username.test(username)) {
|
||||
return res.status(400).json({ error: 'Username must contain only letters' });
|
||||
}
|
||||
|
||||
const hashedPassword = bcrypt.hashSync(password, 10);
|
||||
|
||||
db.run('INSERT INTO users (username, email, password) VALUES (?, ?, ?)',
|
||||
[username, email, hashedPassword], function(err) {
|
||||
if (err) {
|
||||
if (err.code === 'SQLITE_CONSTRAINT') {
|
||||
return res.status(400).json({ error: 'Username already exists' });
|
||||
}
|
||||
return res.status(500).json({ error: 'Database error' });
|
||||
}
|
||||
res.json({ success: true });
|
||||
});
|
||||
});
|
||||
|
||||
app.post('/admin/delete-user/:id', requireAuth, requireAdmin, (req, res) => {
|
||||
const userId = req.params.id;
|
||||
|
||||
// Delete user's devbenches first
|
||||
db.run('DELETE FROM devbenches WHERE user_id = ?', [userId], (err) => {
|
||||
if (err) {
|
||||
return res.status(500).json({ error: 'Database error' });
|
||||
}
|
||||
|
||||
// Delete user
|
||||
db.run('DELETE FROM users WHERE id = ? AND is_admin = 0', [userId], (err) => {
|
||||
if (err) {
|
||||
return res.status(500).json({ error: 'Database error' });
|
||||
}
|
||||
res.json({ success: true });
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
app.post('/admin/reset-password/:id', requireAuth, requireAdmin, (req, res) => {
|
||||
const userId = req.params.id;
|
||||
const { newPassword } = req.body;
|
||||
|
||||
const hashedPassword = bcrypt.hashSync(newPassword, 10);
|
||||
|
||||
db.run('UPDATE users SET password = ? WHERE id = ? AND is_admin = 0',
|
||||
[hashedPassword, userId], (err) => {
|
||||
if (err) {
|
||||
return res.status(500).json({ error: 'Database error' });
|
||||
}
|
||||
res.json({ success: true });
|
||||
});
|
||||
});
|
||||
|
||||
// Help page
|
||||
app.get('/help', requireAuth, (req, res) => {
|
||||
res.render('help', {
|
||||
username: req.session.username
|
||||
});
|
||||
});
|
||||
|
||||
// User dashboard
|
||||
app.get('/dashboard', requireAuth, (req, res) => {
|
||||
if (req.session.isAdmin) {
|
||||
return res.redirect('/admin');
|
||||
}
|
||||
|
||||
db.all('SELECT * FROM devbenches WHERE user_id = ? ORDER BY created_at DESC',
|
||||
[req.session.userId], (err, devbenches) => {
|
||||
if (err) {
|
||||
console.error(err);
|
||||
return res.status(500).send('Database error');
|
||||
}
|
||||
|
||||
res.render('dashboard', {
|
||||
username: req.session.username,
|
||||
devbenches
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// DevBench operations
|
||||
app.post('/create-devbench', requireAuth, (req, res) => {
|
||||
const { name } = req.body;
|
||||
|
||||
// Validate devbench name (only letters, numbers, hyphens, underscores)
|
||||
if (!config.validation.devbenchName.test(name)) {
|
||||
return res.status(400).json({
|
||||
error: 'DevBench name can only contain letters, numbers, hyphens, and underscores'
|
||||
});
|
||||
}
|
||||
|
||||
const fullName = `${req.session.username}_${name}`;
|
||||
|
||||
// Check if devbench already exists
|
||||
db.get('SELECT * FROM devbenches WHERE user_id = ? AND name = ?',
|
||||
[req.session.userId, name], (err, existing) => {
|
||||
if (err) {
|
||||
return res.status(500).json({ error: 'Database error' });
|
||||
}
|
||||
|
||||
if (existing) {
|
||||
return res.status(400).json({ error: 'DevBench with this name already exists' });
|
||||
}
|
||||
|
||||
// Insert into database first
|
||||
db.run('INSERT INTO devbenches (user_id, name, status) VALUES (?, ?, ?)',
|
||||
[req.session.userId, name, 'creating'], function(err) {
|
||||
if (err) {
|
||||
console.error('Database error:', err);
|
||||
return res.status(500).json({ error: 'Database error' });
|
||||
}
|
||||
|
||||
const devbenchId = this.lastID;
|
||||
console.log(`Created devbench record with ID: ${devbenchId}, full name: ${fullName}`);
|
||||
|
||||
// Start the provision script
|
||||
setTimeout(() => {
|
||||
executeProvisionScript('create', fullName, req.session.userId, devbenchId);
|
||||
}, 1000); // Small delay to ensure WebSocket is ready
|
||||
|
||||
res.json({ success: true, devbenchId });
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
app.post('/delete-devbench/:id', requireAuth, (req, res) => {
|
||||
const devbenchId = req.params.id;
|
||||
|
||||
db.get('SELECT * FROM devbenches WHERE id = ? AND user_id = ?',
|
||||
[devbenchId, req.session.userId], (err, devbench) => {
|
||||
if (err || !devbench) {
|
||||
return res.status(404).json({ error: 'DevBench not found' });
|
||||
}
|
||||
|
||||
if (devbench.actual_name) {
|
||||
executeProvisionScript('delete', devbench.actual_name, req.session.userId, devbenchId);
|
||||
}
|
||||
|
||||
db.run('DELETE FROM devbenches WHERE id = ?', [devbenchId], (err) => {
|
||||
if (err) {
|
||||
return res.status(500).json({ error: 'Database error' });
|
||||
}
|
||||
res.json({ success: true });
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
app.post('/activate-devbench/:id', requireAuth, (req, res) => {
|
||||
const devbenchId = req.params.id;
|
||||
|
||||
db.get('SELECT * FROM devbenches WHERE id = ? AND user_id = ?',
|
||||
[devbenchId, req.session.userId], (err, devbench) => {
|
||||
if (err || !devbench) {
|
||||
return res.status(404).json({ error: 'DevBench not found' });
|
||||
}
|
||||
|
||||
if (devbench.actual_name) {
|
||||
executeProvisionScript('activate', devbench.actual_name, req.session.userId, devbenchId);
|
||||
}
|
||||
|
||||
res.json({ success: true });
|
||||
});
|
||||
});
|
||||
|
||||
// Function to execute provision script
|
||||
function executeProvisionScript(command, vmName, userId, devbenchId) {
|
||||
console.log(`Executing provision script: ${command} ${vmName} for user ${userId}`);
|
||||
|
||||
const scriptPath = config.provision.scriptPath;
|
||||
let output = '';
|
||||
|
||||
// Check if script exists
|
||||
const fs = require('fs');
|
||||
if (!fs.existsSync(scriptPath)) {
|
||||
console.error(`Script not found: ${scriptPath}`);
|
||||
broadcastToUser(userId, {
|
||||
type: 'script_output',
|
||||
devbenchId,
|
||||
data: `Error: Script not found at ${scriptPath}\n`
|
||||
});
|
||||
|
||||
// Update database to reflect error
|
||||
db.run('UPDATE devbenches SET status = ? WHERE id = ?', ['error', devbenchId]);
|
||||
return;
|
||||
}
|
||||
|
||||
// Send initial message
|
||||
broadcastToUser(userId, {
|
||||
type: 'script_output',
|
||||
devbenchId,
|
||||
data: `Starting ${command} operation for ${vmName}...\n`
|
||||
});
|
||||
|
||||
const child = spawn('bash', [scriptPath, command, vmName], {
|
||||
cwd: process.cwd(),
|
||||
env: process.env
|
||||
});
|
||||
|
||||
child.stdout.on('data', (data) => {
|
||||
const chunk = data.toString();
|
||||
output += chunk;
|
||||
console.log(`Script output: ${chunk.trim()}`);
|
||||
|
||||
// Broadcast real-time output to user
|
||||
broadcastToUser(userId, {
|
||||
type: 'script_output',
|
||||
devbenchId,
|
||||
data: chunk
|
||||
});
|
||||
});
|
||||
|
||||
child.stderr.on('data', (data) => {
|
||||
const chunk = data.toString();
|
||||
output += chunk;
|
||||
console.log(`Script error: ${chunk.trim()}`);
|
||||
|
||||
broadcastToUser(userId, {
|
||||
type: 'script_output',
|
||||
devbenchId,
|
||||
data: `ERROR: ${chunk}`
|
||||
});
|
||||
});
|
||||
|
||||
child.on('error', (error) => {
|
||||
console.error(`Script execution error:`, error);
|
||||
broadcastToUser(userId, {
|
||||
type: 'script_output',
|
||||
devbenchId,
|
||||
data: `Execution Error: ${error.message}\n`
|
||||
});
|
||||
|
||||
db.run('UPDATE devbenches SET status = ? WHERE id = ?', ['error', devbenchId]);
|
||||
});
|
||||
|
||||
child.on('close', (code) => {
|
||||
console.log(`Script finished with exit code: ${code}`);
|
||||
|
||||
if (command === 'create') {
|
||||
if (code === 0) {
|
||||
// Parse the output to extract VM name, SSH port, and VNC port
|
||||
const vmNameMatch = output.match(/VM_NAME=(.+)/);
|
||||
const sshPortMatch = output.match(/SSH_PORT=(\d+)/);
|
||||
const vncPortMatch = output.match(/VNC_PORT=(\d+)/);
|
||||
|
||||
if (vmNameMatch && sshPortMatch && vncPortMatch) {
|
||||
const actualName = vmNameMatch[1].trim();
|
||||
const sshPort = sshPortMatch[1];
|
||||
const vncPort = vncPortMatch[1];
|
||||
|
||||
// Generate SSH and VNC info
|
||||
const sshInfo = sshPort;
|
||||
const vncInfo = vncPort;
|
||||
|
||||
console.log(`Updating database: ${actualName}, SSH Port: ${sshPort}, VNC Port: ${vncPort}`);
|
||||
|
||||
db.run(`UPDATE devbenches SET
|
||||
actual_name = ?, status = 'active',
|
||||
ssh_info = ?, vnc_info = ?, updated_at = CURRENT_TIMESTAMP
|
||||
WHERE id = ?`,
|
||||
[actualName, sshInfo, vncInfo, devbenchId], (err) => {
|
||||
if (err) {
|
||||
console.error('Database update error:', err);
|
||||
} else {
|
||||
console.log('Database updated successfully');
|
||||
}
|
||||
});
|
||||
} else {
|
||||
console.log('Could not parse VM info from output');
|
||||
db.run('UPDATE devbenches SET status = ? WHERE id = ?', ['error', devbenchId]);
|
||||
}
|
||||
} else {
|
||||
console.log('Script failed, updating status to error');
|
||||
db.run('UPDATE devbenches SET status = ? WHERE id = ?', ['error', devbenchId]);
|
||||
}
|
||||
}
|
||||
|
||||
broadcastToUser(userId, {
|
||||
type: 'script_complete',
|
||||
devbenchId,
|
||||
exitCode: code
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Status check endpoint
|
||||
app.get('/check-status/:id', requireAuth, (req, res) => {
|
||||
const devbenchId = req.params.id;
|
||||
|
||||
db.get('SELECT * FROM devbenches WHERE id = ? AND user_id = ?',
|
||||
[devbenchId, req.session.userId], (err, devbench) => {
|
||||
if (err || !devbench || !devbench.actual_name) {
|
||||
return res.json({ status: 'unknown' });
|
||||
}
|
||||
|
||||
const child = spawn('bash', [config.provision.scriptPath, 'status', devbench.actual_name]);
|
||||
let output = '';
|
||||
|
||||
child.stdout.on('data', (data) => {
|
||||
output += data.toString();
|
||||
});
|
||||
|
||||
child.stderr.on('data', (data) => {
|
||||
output += data.toString();
|
||||
});
|
||||
|
||||
child.on('close', (code) => {
|
||||
// Parse status from output - look for "active" or "inactive" in the output
|
||||
let status = 'inactive';
|
||||
if (output.includes('active')) {
|
||||
status = 'active';
|
||||
}
|
||||
|
||||
console.log(`Status check for ${devbench.actual_name}: ${status} (exit code: ${code})`);
|
||||
|
||||
db.run('UPDATE devbenches SET status = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ?',
|
||||
[status, devbenchId]);
|
||||
|
||||
res.json({ status });
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// Periodic status check (every minute)
|
||||
setInterval(() => {
|
||||
db.all('SELECT * FROM devbenches WHERE actual_name IS NOT NULL', (err, devbenches) => {
|
||||
if (err) return;
|
||||
|
||||
devbenches.forEach(devbench => {
|
||||
const child = spawn('bash', [config.provision.scriptPath, 'status', devbench.actual_name]);
|
||||
let output = '';
|
||||
|
||||
child.stdout.on('data', (data) => {
|
||||
output += data.toString();
|
||||
});
|
||||
|
||||
child.stderr.on('data', (data) => {
|
||||
output += data.toString();
|
||||
});
|
||||
|
||||
child.on('close', (code) => {
|
||||
// Parse status from output - look for "active" in the output
|
||||
let status = 'inactive';
|
||||
if (output.includes('active')) {
|
||||
status = 'active';
|
||||
}
|
||||
|
||||
if (status !== devbench.status) {
|
||||
console.log(`Status changed for ${devbench.actual_name}: ${devbench.status} -> ${status}`);
|
||||
|
||||
db.run('UPDATE devbenches SET status = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ?',
|
||||
[status, devbench.id]);
|
||||
|
||||
// Notify user of status change
|
||||
broadcastToUser(devbench.user_id, {
|
||||
type: 'status_update',
|
||||
devbenchId: devbench.id,
|
||||
status
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
}, config.provision.statusCheckInterval);
|
||||
|
||||
const PORT = config.port;
|
||||
server.listen(PORT, () => {
|
||||
console.log(`DevBench Manager running on port ${PORT}`);
|
||||
console.log(`Database path: ${config.database.path}`);
|
||||
console.log(`Provision script path: ${config.provision.scriptPath}`);
|
||||
|
||||
// Check if provision script exists
|
||||
const fs = require('fs');
|
||||
if (fs.existsSync(config.provision.scriptPath)) {
|
||||
console.log('✓ Provision script found');
|
||||
} else {
|
||||
console.log('✗ Provision script NOT found');
|
||||
}
|
||||
});
|
||||
31
asf-cloud-server/TBM_devbench/start.sh
Normal file
31
asf-cloud-server/TBM_devbench/start.sh
Normal file
@@ -0,0 +1,31 @@
|
||||
#!/bin/bash
|
||||
|
||||
echo "Starting DevBench Manager..."
|
||||
|
||||
# Check if Docker is available
|
||||
if command -v docker &> /dev/null && command -v docker-compose &> /dev/null; then
|
||||
echo "Using Docker Compose..."
|
||||
docker-compose up -d
|
||||
echo "DevBench Manager started with Docker!"
|
||||
echo "Access it at: http://localhost:3001"
|
||||
echo "Default admin login: admin / admin123"
|
||||
else
|
||||
echo "Docker not found, starting with Node.js..."
|
||||
|
||||
# Check if Node.js is available
|
||||
if ! command -v node &> /dev/null; then
|
||||
echo "Error: Node.js is not installed!"
|
||||
echo "Please install Node.js 18+ or Docker to run this application."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Install dependencies if node_modules doesn't exist
|
||||
if [ ! -d "node_modules" ]; then
|
||||
echo "Installing dependencies..."
|
||||
npm install
|
||||
fi
|
||||
|
||||
# Start the application
|
||||
echo "Starting application..."
|
||||
npm start
|
||||
fi
|
||||
212
asf-cloud-server/TBM_devbench/views/admin.ejs
Normal file
212
asf-cloud-server/TBM_devbench/views/admin.ejs
Normal file
@@ -0,0 +1,212 @@
|
||||
<%- include('layout', { body: `
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<h2><i class="fas fa-cogs"></i> Admin Dashboard</h2>
|
||||
<button class="btn btn-success" data-bs-toggle="modal" data-bs-target="#addUserModal">
|
||||
<i class="fas fa-user-plus"></i> Add User
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Users Section -->
|
||||
<div class="card mb-4">
|
||||
<div class="card-header">
|
||||
<h5><i class="fas fa-users"></i> Users Management</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Username</th>
|
||||
<th>Email</th>
|
||||
<th>DevBenches</th>
|
||||
<th>Created</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
${users.map(user => `
|
||||
<tr>
|
||||
<td>${user.username}</td>
|
||||
<td>${user.email}</td>
|
||||
<td><span class="badge bg-info">${user.devbench_count}</span></td>
|
||||
<td>${new Date(user.created_at).toLocaleDateString()}</td>
|
||||
<td>
|
||||
<button class="btn btn-sm btn-warning" onclick="resetPassword(${user.id})">
|
||||
<i class="fas fa-key"></i> Reset Password
|
||||
</button>
|
||||
<button class="btn btn-sm btn-danger" onclick="deleteUser(${user.id})">
|
||||
<i class="fas fa-trash"></i> Delete
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
`).join('')}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- DevBenches Section -->
|
||||
<div class="card">
|
||||
<div class="card-header d-flex justify-content-between align-items-center">
|
||||
<h5><i class="fas fa-server"></i> All DevBenches</h5>
|
||||
<small class="text-light"><i class="fas fa-info-circle"></i> Default SSH/VNC Password: <strong>ASF</strong></small>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>User</th>
|
||||
<th>DevBench Name</th>
|
||||
<th>Actual Name</th>
|
||||
<th>Status</th>
|
||||
<th>SSH Port</th>
|
||||
<th>VNC Port</th>
|
||||
<th>Created</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
${devbenches.map(db => `
|
||||
<tr>
|
||||
<td>${db.username}</td>
|
||||
<td>${db.name}</td>
|
||||
<td>${db.actual_name || 'N/A'}</td>
|
||||
<td>
|
||||
<span id="status-${db.id}" class="badge bg-${db.status === 'active' ? 'success' : db.status === 'creating' ? 'warning' : 'danger'}">
|
||||
${db.status}
|
||||
</span>
|
||||
</td>
|
||||
<td>${db.ssh_info || 'N/A'}</td>
|
||||
<td>${db.vnc_info || 'N/A'}</td>
|
||||
<td>${new Date(db.created_at).toLocaleDateString()}</td>
|
||||
<td>
|
||||
<button class="btn btn-sm btn-danger" onclick="adminDeleteDevbench(${db.id})">
|
||||
<i class="fas fa-trash"></i> Delete
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
`).join('')}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Add User Modal -->
|
||||
<div class="modal fade" id="addUserModal" tabindex="-1">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">Add New User</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||||
</div>
|
||||
<form id="addUserForm">
|
||||
<div class="modal-body">
|
||||
<div class="mb-3">
|
||||
<label for="newUsername" class="form-label">Username</label>
|
||||
<input type="text" class="form-control" id="newUsername" name="username" required
|
||||
pattern="[a-zA-Z]+" title="Username must contain only letters">
|
||||
<div class="form-text">Username must contain only letters (no spaces, numbers, or special characters)</div>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="newEmail" class="form-label">Email</label>
|
||||
<input type="email" class="form-control" id="newEmail" name="email" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="newPassword" class="form-label">Initial Password</label>
|
||||
<input type="password" class="form-control" id="newPassword" name="password" required>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
|
||||
<button type="submit" class="btn btn-success">Add User</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
document.getElementById('addUserForm').addEventListener('submit', async (e) => {
|
||||
e.preventDefault();
|
||||
const formData = new FormData(e.target);
|
||||
|
||||
try {
|
||||
const response = await fetch('/admin/add-user', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(Object.fromEntries(formData))
|
||||
});
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
if (result.success) {
|
||||
location.reload();
|
||||
} else {
|
||||
alert(result.error);
|
||||
}
|
||||
} catch (error) {
|
||||
alert('Error adding user');
|
||||
}
|
||||
});
|
||||
|
||||
async function deleteUser(userId) {
|
||||
if (confirm('Are you sure you want to delete this user and all their DevBenches?')) {
|
||||
try {
|
||||
const response = await fetch(\`/admin/delete-user/\${userId}\`, { method: 'POST' });
|
||||
const result = await response.json();
|
||||
|
||||
if (result.success) {
|
||||
location.reload();
|
||||
} else {
|
||||
alert(result.error);
|
||||
}
|
||||
} catch (error) {
|
||||
alert('Error deleting user');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function resetPassword(userId) {
|
||||
const newPassword = prompt('Enter new password:');
|
||||
if (newPassword) {
|
||||
try {
|
||||
const response = await fetch(\`/admin/reset-password/\${userId}\`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ newPassword })
|
||||
});
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
if (result.success) {
|
||||
alert('Password reset successfully');
|
||||
} else {
|
||||
alert(result.error);
|
||||
}
|
||||
} catch (error) {
|
||||
alert('Error resetting password');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function adminDeleteDevbench(devbenchId) {
|
||||
if (confirm('Are you sure you want to delete this DevBench?')) {
|
||||
try {
|
||||
const response = await fetch(\`/delete-devbench/\${devbenchId}\`, { method: 'POST' });
|
||||
const result = await response.json();
|
||||
|
||||
if (result.success) {
|
||||
location.reload();
|
||||
} else {
|
||||
alert(result.error);
|
||||
}
|
||||
} catch (error) {
|
||||
alert('Error deleting DevBench');
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
`, username: typeof username !== 'undefined' ? username : 'admin' }) %>
|
||||
208
asf-cloud-server/TBM_devbench/views/dashboard.ejs
Normal file
208
asf-cloud-server/TBM_devbench/views/dashboard.ejs
Normal file
@@ -0,0 +1,208 @@
|
||||
<%- include('layout', { body: `
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<h2><i class="fas fa-tachometer-alt"></i> My DevBenches</h2>
|
||||
<button class="btn btn-success" data-bs-toggle="modal" data-bs-target="#createDevbenchModal">
|
||||
<i class="fas fa-plus"></i> Create DevBench
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
${devbenches.map(db => `
|
||||
<div class="col-md-6 col-lg-4 mb-4">
|
||||
<div class="card h-100">
|
||||
<div class="card-header d-flex justify-content-between align-items-center">
|
||||
<h6 class="mb-0">${db.name}</h6>
|
||||
<span id="status-${db.id}" class="badge bg-${db.status === 'active' ? 'success' : db.status === 'creating' ? 'warning' : 'danger'}">
|
||||
${db.status}
|
||||
</span>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
${db.actual_name ? `
|
||||
<p><strong>VM Name:</strong> ${db.actual_name}</p>
|
||||
|
||||
${db.ssh_info || db.vnc_info ? `
|
||||
<div class="connection-info">
|
||||
<h6><i class="fas fa-plug"></i> Connection Info:</h6>
|
||||
${db.ssh_info ? `
|
||||
<div class="mb-3">
|
||||
<strong><i class="fas fa-terminal"></i> SSH Port:</strong><br>
|
||||
<code class="fs-5">${db.ssh_info}</code>
|
||||
<button class="btn btn-sm btn-outline-primary ms-2" onclick="copyToClipboard('${db.ssh_info}')">
|
||||
<i class="fas fa-copy"></i>
|
||||
</button>
|
||||
</div>
|
||||
` : ''}
|
||||
${db.vnc_info ? `
|
||||
<div class="mb-3">
|
||||
<strong><i class="fas fa-desktop"></i> VNC Port:</strong><br>
|
||||
<code class="fs-5">${db.vnc_info}</code>
|
||||
<button class="btn btn-sm btn-outline-primary ms-2" onclick="copyToClipboard('${db.vnc_info}')">
|
||||
<i class="fas fa-copy"></i>
|
||||
</button>
|
||||
</div>
|
||||
` : ''}
|
||||
<div class="alert alert-info mt-3">
|
||||
<i class="fas fa-info-circle"></i> Need help connecting?
|
||||
<a href="/help" class="alert-link">View setup guide</a>
|
||||
</div>
|
||||
</div>
|
||||
` : ''}
|
||||
` : `
|
||||
<p class="text-muted">DevBench is being created...</p>
|
||||
<div class="log-output" id="log-${db.id}"></div>
|
||||
`}
|
||||
|
||||
<p class="text-muted mt-2">
|
||||
<small>Created: ${new Date(db.created_at).toLocaleString()}</small>
|
||||
</p>
|
||||
</div>
|
||||
<div class="card-footer">
|
||||
<div class="btn-group w-100" role="group">
|
||||
${db.status !== 'creating' ? `
|
||||
<button class="btn btn-sm btn-primary" onclick="activateDevbench(${db.id})"
|
||||
${db.status === 'active' ? 'disabled' : ''}>
|
||||
<i class="fas fa-play"></i> Activate
|
||||
</button>
|
||||
<button class="btn btn-sm btn-info" onclick="checkStatus(${db.id})">
|
||||
<i class="fas fa-sync"></i> Check Status
|
||||
</button>
|
||||
` : ''}
|
||||
<button class="btn btn-sm btn-danger" onclick="deleteDevbench(${db.id})">
|
||||
<i class="fas fa-trash"></i> Delete
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`).join('')}
|
||||
|
||||
${devbenches.length === 0 ? `
|
||||
<div class="col-12">
|
||||
<div class="text-center py-5">
|
||||
<i class="fas fa-server fa-3x text-muted mb-3"></i>
|
||||
<h4 class="text-muted">No DevBenches Yet</h4>
|
||||
<p class="text-muted">Create your first DevBench to get started!</p>
|
||||
</div>
|
||||
</div>
|
||||
` : ''}
|
||||
</div>
|
||||
|
||||
<!-- Create DevBench Modal -->
|
||||
<div class="modal fade" id="createDevbenchModal" tabindex="-1">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">Create New DevBench</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||||
</div>
|
||||
<form id="createDevbenchForm">
|
||||
<div class="modal-body">
|
||||
<div class="mb-3">
|
||||
<label for="devbenchName" class="form-label">DevBench Name</label>
|
||||
<input type="text" class="form-control" id="devbenchName" name="name" required
|
||||
pattern="[a-zA-Z0-9_-]+" title="Only letters, numbers, hyphens, and underscores allowed">
|
||||
<div class="form-text">
|
||||
Only letters, numbers, hyphens (-), and underscores (_) are allowed.<br>
|
||||
Full name will be: <strong>${username}_[your-name]</strong>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
|
||||
<button type="submit" class="btn btn-success">Create DevBench</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
document.getElementById('createDevbenchForm').addEventListener('submit', async (e) => {
|
||||
e.preventDefault();
|
||||
const formData = new FormData(e.target);
|
||||
|
||||
try {
|
||||
const response = await fetch('/create-devbench', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(Object.fromEntries(formData))
|
||||
});
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
if (result.success) {
|
||||
location.reload();
|
||||
} else {
|
||||
alert(result.error);
|
||||
}
|
||||
} catch (error) {
|
||||
alert('Error creating DevBench');
|
||||
}
|
||||
});
|
||||
|
||||
async function deleteDevbench(devbenchId) {
|
||||
if (confirm('Are you sure you want to delete this DevBench?')) {
|
||||
try {
|
||||
const response = await fetch(\`/delete-devbench/\${devbenchId}\`, { method: 'POST' });
|
||||
const result = await response.json();
|
||||
|
||||
if (result.success) {
|
||||
location.reload();
|
||||
} else {
|
||||
alert(result.error);
|
||||
}
|
||||
} catch (error) {
|
||||
alert('Error deleting DevBench');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function activateDevbench(devbenchId) {
|
||||
try {
|
||||
const response = await fetch(\`/activate-devbench/\${devbenchId}\`, { method: 'POST' });
|
||||
const result = await response.json();
|
||||
|
||||
if (result.success) {
|
||||
alert('DevBench activation started');
|
||||
} else {
|
||||
alert(result.error);
|
||||
}
|
||||
} catch (error) {
|
||||
alert('Error activating DevBench');
|
||||
}
|
||||
}
|
||||
|
||||
async function checkStatus(devbenchId) {
|
||||
try {
|
||||
const response = await fetch(\`/check-status/\${devbenchId}\`);
|
||||
const result = await response.json();
|
||||
|
||||
const statusElement = document.getElementById(\`status-\${devbenchId}\`);
|
||||
if (statusElement) {
|
||||
statusElement.className = \`badge bg-\${result.status === 'active' ? 'success' : 'danger'}\`;
|
||||
statusElement.textContent = result.status;
|
||||
}
|
||||
} catch (error) {
|
||||
alert('Error checking status');
|
||||
}
|
||||
}
|
||||
|
||||
function copyToClipboard(text) {
|
||||
navigator.clipboard.writeText(text).then(() => {
|
||||
// Show a temporary success message
|
||||
const toast = document.createElement('div');
|
||||
toast.className = 'alert alert-success position-fixed';
|
||||
toast.style.cssText = 'top: 20px; right: 20px; z-index: 9999; opacity: 0.9;';
|
||||
toast.innerHTML = '<i class="fas fa-check"></i> Copied to clipboard!';
|
||||
document.body.appendChild(toast);
|
||||
|
||||
setTimeout(() => {
|
||||
document.body.removeChild(toast);
|
||||
}, 2000);
|
||||
}).catch(err => {
|
||||
console.error('Failed to copy: ', err);
|
||||
alert('Failed to copy to clipboard');
|
||||
});
|
||||
}
|
||||
</script>
|
||||
`, username }) %>
|
||||
125
asf-cloud-server/TBM_devbench/views/help.ejs
Normal file
125
asf-cloud-server/TBM_devbench/views/help.ejs
Normal file
@@ -0,0 +1,125 @@
|
||||
<%- include('layout', { body: `
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<h2><i class="fas fa-question-circle"></i> Help - How to Use DevBench Manager</h2>
|
||||
<a href="/dashboard" class="btn btn-primary">
|
||||
<i class="fas fa-arrow-left"></i> Back to Dashboard
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="card mb-4">
|
||||
<div class="card-header bg-info text-white">
|
||||
<h5><i class="fas fa-info-circle"></i> SSH Configuration Tool</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<p class="lead">Follow these steps to configure SSH access to your VM:</p>
|
||||
|
||||
<ol class="help-steps">
|
||||
<li>
|
||||
<strong>Download the VM SSH Config Tool</strong>
|
||||
<p>Download the configuration tool from here:</p>
|
||||
<a href="/downloads/db_vm_ssh_config_manager.exe" class="btn btn-success mb-2" download>
|
||||
<i class="fas fa-download"></i> Download SSH Config Manager
|
||||
</a>
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<strong>Open the Tool</strong>
|
||||
<p>Run the <code>db_vm_ssh_config_manager.exe</code> application.</p>
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<strong>Add Friendly Name</strong>
|
||||
<p>In the "Host" field, enter a friendly name for your VM (e.g., <code>vm-test1</code>).</p>
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<strong>Add SSH Port</strong>
|
||||
<p>Enter the SSH port number shown in your DevBench connection info (e.g., <code>6004</code>).</p>
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<strong>Add Username</strong>
|
||||
<p>Enter the username: <code>asf_user</code></p>
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<strong>Keep Jump Proxy</strong>
|
||||
<p>Keep the jump proxy as: <code>asf-jump</code></p>
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<strong>Press Add VM</strong>
|
||||
<p>Click the "Add VM" button to save the configuration.</p>
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<strong>Select VM Host and Show Command</strong>
|
||||
<p>In the "Select VM Host" dropdown, choose your VM name and click "Show Commands".</p>
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<strong>Access Your VM</strong>
|
||||
<p>You will now see the SSH command to access your VM. Copy and use it in your terminal.</p>
|
||||
</li>
|
||||
</ol>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card mb-4">
|
||||
<div class="card-header bg-primary text-white">
|
||||
<h5><i class="fas fa-terminal"></i> Connection Information</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<h6>SSH Connection:</h6>
|
||||
<p>After configuring the SSH tool, you can connect using:</p>
|
||||
<pre class="bg-light p-3 rounded"><code>ssh [your-vm-name]</code></pre>
|
||||
|
||||
<h6 class="mt-4">VNC Connection:</h6>
|
||||
<p>For graphical access, use your VNC client with the port shown in your DevBench info.</p>
|
||||
<p><strong>Default Password:</strong> <code>ASF</code></p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-header bg-warning text-dark">
|
||||
<h5><i class="fas fa-exclamation-triangle"></i> Important Notes</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<ul>
|
||||
<li>The default password for both SSH and VNC is: <strong>ASF</strong></li>
|
||||
<li>Make sure to keep your SSH config tool updated with the correct ports</li>
|
||||
<li>Each DevBench has unique SSH and VNC ports</li>
|
||||
<li>Contact your administrator if you encounter any issues</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.help-steps {
|
||||
font-size: 1.1rem;
|
||||
line-height: 1.8;
|
||||
}
|
||||
|
||||
.help-steps li {
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
.help-steps strong {
|
||||
color: #0d6efd;
|
||||
display: block;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.help-steps p {
|
||||
margin-left: 1rem;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.help-steps code {
|
||||
background-color: #f8f9fa;
|
||||
padding: 2px 6px;
|
||||
border-radius: 3px;
|
||||
color: #d63384;
|
||||
}
|
||||
</style>
|
||||
`, username }) %>
|
||||
134
asf-cloud-server/TBM_devbench/views/layout.ejs
Normal file
134
asf-cloud-server/TBM_devbench/views/layout.ejs
Normal file
@@ -0,0 +1,134 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>DevBench Manager</title>
|
||||
<link rel="icon" type="image/png" href="/images/tbm-icon.png">
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css" rel="stylesheet">
|
||||
<link href="/css/style.css" rel="stylesheet">
|
||||
</head>
|
||||
<body>
|
||||
<nav class="navbar navbar-expand-lg navbar-dark bg-primary">
|
||||
<div class="container">
|
||||
<a class="navbar-brand" href="/">
|
||||
<img src="/images/tbm-icon.png" alt="TBM" class="tbm-icon me-2" style="height: 32px; width: 32px;">
|
||||
<img src="/images/nabd-logo-white.svg" alt="NABD Solutions" class="company-logo">
|
||||
DevBench Manager
|
||||
</a>
|
||||
<div class="navbar-nav ms-auto">
|
||||
<% if (typeof username !== 'undefined') { %>
|
||||
<a class="nav-link" href="/help" title="Help">
|
||||
<i class="fas fa-question-circle"></i> Help
|
||||
</a>
|
||||
<span class="navbar-text me-3">Welcome, <%= username %></span>
|
||||
<a class="nav-link" href="/logout">
|
||||
<i class="fas fa-sign-out-alt"></i> Logout
|
||||
</a>
|
||||
<% } %>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<div class="container mt-4">
|
||||
<%- body %>
|
||||
</div>
|
||||
|
||||
<!-- Theme Toggle Button -->
|
||||
<button class="theme-toggle" id="themeToggle" title="Toggle Dark/Light Theme">
|
||||
<i class="fas fa-moon" id="themeIcon"></i>
|
||||
</button>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"></script>
|
||||
<script>
|
||||
// Theme Toggle Functionality
|
||||
const themeToggle = document.getElementById('themeToggle');
|
||||
const themeIcon = document.getElementById('themeIcon');
|
||||
const body = document.body;
|
||||
|
||||
// Load saved theme preference
|
||||
const savedTheme = localStorage.getItem('theme') || 'light';
|
||||
if (savedTheme === 'dark') {
|
||||
body.classList.add('dark-theme');
|
||||
themeIcon.classList.remove('fa-moon');
|
||||
themeIcon.classList.add('fa-sun');
|
||||
}
|
||||
|
||||
// Toggle theme
|
||||
themeToggle.addEventListener('click', () => {
|
||||
body.classList.toggle('dark-theme');
|
||||
|
||||
if (body.classList.contains('dark-theme')) {
|
||||
themeIcon.classList.remove('fa-moon');
|
||||
themeIcon.classList.add('fa-sun');
|
||||
localStorage.setItem('theme', 'dark');
|
||||
} else {
|
||||
themeIcon.classList.remove('fa-sun');
|
||||
themeIcon.classList.add('fa-moon');
|
||||
localStorage.setItem('theme', 'light');
|
||||
}
|
||||
});
|
||||
</script>
|
||||
<script>
|
||||
// WebSocket connection for real-time updates
|
||||
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
|
||||
const ws = new WebSocket(`${protocol}//${window.location.host}`);
|
||||
|
||||
ws.onopen = function() {
|
||||
console.log('WebSocket connected');
|
||||
// Register this connection with user ID if available
|
||||
<% if (typeof username !== 'undefined') { %>
|
||||
// Get user ID from session (we'll need to pass this from server)
|
||||
fetch('/api/user-info')
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.userId) {
|
||||
ws.send(JSON.stringify({
|
||||
type: 'register',
|
||||
userId: data.userId
|
||||
}));
|
||||
console.log('Registered WebSocket for user:', data.userId);
|
||||
}
|
||||
})
|
||||
.catch(error => console.error('Error getting user info:', error));
|
||||
<% } %>
|
||||
};
|
||||
|
||||
ws.onmessage = function(event) {
|
||||
const message = JSON.parse(event.data);
|
||||
console.log('WebSocket message received:', message);
|
||||
handleWebSocketMessage(message);
|
||||
};
|
||||
|
||||
ws.onerror = function(error) {
|
||||
console.error('WebSocket error:', error);
|
||||
};
|
||||
|
||||
ws.onclose = function() {
|
||||
console.log('WebSocket disconnected');
|
||||
};
|
||||
|
||||
function handleWebSocketMessage(message) {
|
||||
if (message.type === 'script_output') {
|
||||
const logElement = document.getElementById(`log-${message.devbenchId}`);
|
||||
if (logElement) {
|
||||
logElement.textContent += message.data;
|
||||
logElement.scrollTop = logElement.scrollHeight;
|
||||
}
|
||||
} else if (message.type === 'script_complete') {
|
||||
console.log('Script completed with exit code:', message.exitCode);
|
||||
setTimeout(() => {
|
||||
location.reload();
|
||||
}, 3000);
|
||||
} else if (message.type === 'status_update') {
|
||||
const statusElement = document.getElementById(`status-${message.devbenchId}`);
|
||||
if (statusElement) {
|
||||
statusElement.className = `badge bg-${message.status === 'active' ? 'success' : 'danger'}`;
|
||||
statusElement.textContent = message.status;
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
31
asf-cloud-server/TBM_devbench/views/login.ejs
Normal file
31
asf-cloud-server/TBM_devbench/views/login.ejs
Normal file
@@ -0,0 +1,31 @@
|
||||
<%- include('layout', { body: `
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-6 col-lg-4">
|
||||
<div class="card shadow login-card">
|
||||
<div class="card-header bg-primary text-white text-center">
|
||||
<img src="/images/nabd-logo.svg" alt="NABD Solutions" style="height: 40px; margin-bottom: 10px;">
|
||||
<h4><i class="fas fa-sign-in-alt"></i> Login</h4>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
${error ? `<div class="alert alert-danger">${error}</div>` : ''}
|
||||
<form method="POST" action="/login">
|
||||
<div class="mb-3">
|
||||
<label for="username" class="form-label">Username</label>
|
||||
<input type="text" class="form-control" id="username" name="username" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="password" class="form-label">Password</label>
|
||||
<input type="password" class="form-control" id="password" name="password" required>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary w-100">
|
||||
<i class="fas fa-sign-in-alt"></i> Login
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
<div class="card-footer text-center text-muted">
|
||||
<small>DevBench Manager v1.0</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
` }) %>
|
||||
220
asf-dev-tools/ssh_config_manager.py
Normal file
220
asf-dev-tools/ssh_config_manager.py
Normal file
@@ -0,0 +1,220 @@
|
||||
import sys
|
||||
import os
|
||||
import re # We need the regex module for easier parsing
|
||||
|
||||
from PyQt6.QtWidgets import (
|
||||
QApplication, QWidget, QVBoxLayout, QHBoxLayout,
|
||||
QLineEdit, QLabel, QPushButton, QTextEdit, QMessageBox
|
||||
)
|
||||
from PyQt6.QtCore import Qt
|
||||
|
||||
class SSHConfigManager(QWidget):
|
||||
# --- Configuration for the Jump Host ---
|
||||
JUMP_HOST_NAME = "asf-jump"
|
||||
JUMP_HOST_DETAILS = f"""
|
||||
Host {JUMP_HOST_NAME}
|
||||
Hostname asf-server.duckdns.org
|
||||
Port 49152
|
||||
User asf
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.setWindowTitle("SSH Config Manager (PyQt6)")
|
||||
self.setMinimumWidth(500)
|
||||
self.config_path = os.path.expanduser("~/.ssh/config")
|
||||
self.init_ui()
|
||||
|
||||
# Ensure directory and file exist
|
||||
os.makedirs(os.path.dirname(self.config_path), exist_ok=True)
|
||||
if not os.path.exists(self.config_path):
|
||||
with open(self.config_path, 'w') as f:
|
||||
f.write("")
|
||||
|
||||
self.ensure_jump_host_defined() # Check and add jump host immediately
|
||||
self.load_config()
|
||||
|
||||
def init_ui(self):
|
||||
main_layout = QVBoxLayout()
|
||||
|
||||
# --- 1. Input Fields for New VM ---
|
||||
input_group = QVBoxLayout()
|
||||
input_group.addWidget(QLabel("## 🛠️ Add New VM Entry"))
|
||||
|
||||
self.host_input = QLineEdit()
|
||||
self.host_input.setPlaceholderText("Host (e.g., vm-test1)")
|
||||
|
||||
self.port_input = QLineEdit()
|
||||
self.port_input.setPlaceholderText("Port (e.g., 6002)")
|
||||
|
||||
self.user_input = QLineEdit()
|
||||
self.user_input.setPlaceholderText("User (e.g., asf_user)")
|
||||
|
||||
self.proxy_input = QLineEdit()
|
||||
self.proxy_input.setPlaceholderText(f"ProxyJump (Default: {self.JUMP_HOST_NAME})")
|
||||
self.proxy_input.setText(self.JUMP_HOST_NAME) # Pre-fill the jump host name
|
||||
|
||||
input_group.addWidget(self.host_input)
|
||||
input_group.addWidget(self.port_input)
|
||||
input_group.addWidget(self.user_input)
|
||||
input_group.addWidget(self.proxy_input)
|
||||
|
||||
add_button = QPushButton("➕ Add VM & Save Config")
|
||||
add_button.clicked.connect(self.add_vm_entry)
|
||||
input_group.addWidget(add_button)
|
||||
|
||||
main_layout.addLayout(input_group)
|
||||
main_layout.addWidget(QLabel("---"))
|
||||
|
||||
# --- 2. Existing Config Viewer ---
|
||||
main_layout.addWidget(QLabel("## 📜 Current ~/.ssh/config Content"))
|
||||
self.config_viewer = QTextEdit()
|
||||
self.config_viewer.setReadOnly(True)
|
||||
self.config_viewer.setMinimumHeight(150)
|
||||
main_layout.addWidget(self.config_viewer)
|
||||
|
||||
# --- 3. Connection Instructions & Display ---
|
||||
main_layout.addWidget(QLabel("## 🔗 Connection Info"))
|
||||
|
||||
info_layout = QHBoxLayout()
|
||||
info_layout.addWidget(QLabel("Select VM Host:"))
|
||||
self.vm_select = QLineEdit()
|
||||
self.vm_select.setPlaceholderText("Enter Host name (e.g., vm-test1)")
|
||||
info_layout.addWidget(self.vm_select)
|
||||
|
||||
show_button = QPushButton("Show Commands")
|
||||
show_button.clicked.connect(self.show_connection_commands)
|
||||
info_layout.addWidget(show_button)
|
||||
|
||||
main_layout.addLayout(info_layout)
|
||||
|
||||
self.command_output = QTextEdit()
|
||||
self.command_output.setReadOnly(True)
|
||||
self.command_output.setMinimumHeight(100)
|
||||
main_layout.addWidget(self.command_output)
|
||||
|
||||
self.setLayout(main_layout)
|
||||
|
||||
def load_config(self):
|
||||
"""Loads the current config file content into the viewer."""
|
||||
try:
|
||||
with open(self.config_path, 'r') as f:
|
||||
content = f.read()
|
||||
self.config_viewer.setText(content)
|
||||
except Exception as e:
|
||||
self.config_viewer.setText(f"Error loading config: {e}")
|
||||
|
||||
def ensure_jump_host_defined(self):
|
||||
"""Checks if the JUMP_HOST_NAME is defined and adds it if missing."""
|
||||
try:
|
||||
with open(self.config_path, 'r+') as f:
|
||||
content = f.read()
|
||||
|
||||
# Use regex to find if the Host definition already exists
|
||||
if re.search(rf"^Host\s+{re.escape(self.JUMP_HOST_NAME)}\s*$", content, re.MULTILINE | re.IGNORECASE):
|
||||
# print("Jump host already defined.") # For debugging
|
||||
return
|
||||
|
||||
# If not found, append the definition to the beginning of the file
|
||||
f.seek(0)
|
||||
f.write(self.JUMP_HOST_DETAILS + "\n" + content)
|
||||
f.truncate()
|
||||
|
||||
QMessageBox.information(self, "Setup Complete",
|
||||
f"Automatically added the '{self.JUMP_HOST_NAME}' jump host definition to the config file.")
|
||||
|
||||
except Exception as e:
|
||||
QMessageBox.critical(self, "Setup Error", f"Failed to ensure jump host definition: {e}")
|
||||
|
||||
def add_vm_entry(self):
|
||||
"""Validates inputs, formats the entry, and appends it to the config file."""
|
||||
host = self.host_input.text().strip()
|
||||
port = self.port_input.text().strip()
|
||||
user = self.user_input.text().strip()
|
||||
proxy = self.proxy_input.text().strip()
|
||||
|
||||
if not all([host, port, user, proxy]):
|
||||
QMessageBox.warning(self, "Input Error", "All fields must be filled out.")
|
||||
return
|
||||
|
||||
# Ensure jump host is defined before adding dependent VMs
|
||||
self.ensure_jump_host_defined()
|
||||
|
||||
new_entry = f"""
|
||||
|
||||
Host {host}
|
||||
Hostname 127.0.0.1
|
||||
Port {port}
|
||||
User {user}
|
||||
ProxyJump {proxy}
|
||||
"""
|
||||
|
||||
try:
|
||||
with open(self.config_path, 'a') as f:
|
||||
f.write(new_entry)
|
||||
|
||||
QMessageBox.information(self, "Success", f"VM '{host}' successfully added to {self.config_path}!")
|
||||
self.load_config() # Refresh the viewer
|
||||
|
||||
# Clear inputs
|
||||
self.host_input.clear()
|
||||
self.port_input.clear()
|
||||
self.user_input.clear()
|
||||
self.proxy_input.setText(self.JUMP_HOST_NAME) # Reset ProxyJump field
|
||||
|
||||
except Exception as e:
|
||||
QMessageBox.critical(self, "Error", f"Failed to save file: {e}")
|
||||
|
||||
def show_connection_commands(self):
|
||||
"""Generates and displays the connection commands for the selected VM."""
|
||||
host = self.vm_select.text().strip()
|
||||
|
||||
if not host:
|
||||
self.command_output.setText("Please enter a Host name to look up.")
|
||||
return
|
||||
|
||||
ssh_port = None
|
||||
try:
|
||||
with open(self.config_path, 'r') as f:
|
||||
content = f.read().splitlines()
|
||||
|
||||
# Simple line-by-line parser to find the port
|
||||
current_host = None
|
||||
for line in content:
|
||||
line = line.strip()
|
||||
if line.startswith('Host '):
|
||||
current_host = line.split()[1]
|
||||
elif current_host == host and line.startswith('Port '):
|
||||
ssh_port = line.split()[1]
|
||||
break
|
||||
except Exception:
|
||||
self.command_output.setText(f"Error reading config file to find port for {host}.")
|
||||
return
|
||||
|
||||
if not ssh_port:
|
||||
self.command_output.setText(f"Host '{host}' not found or Port not specified in config.")
|
||||
return
|
||||
|
||||
# VNC Port (Assuming VNC Port = SSH Port - 1000, based on your ranges 6002/5002)
|
||||
vnc_port = int(ssh_port) - 1000
|
||||
|
||||
# 2. Generate Commands
|
||||
commands = f"""
|
||||
✅ **SSH Command** (for direct console access via Jump Host):
|
||||
ssh {host}
|
||||
|
||||
🖥️ **VNC Command** (for secure graphical access via SSH Tunnel):
|
||||
1. Establish the tunnel (keep this window open/running):
|
||||
ssh -L 5900:127.0.0.1:{vnc_port} {host} -N &
|
||||
|
||||
2. Connect VNC Client to your local machine:
|
||||
VNC Host/Port: 127.0.0.1:5900
|
||||
"""
|
||||
self.command_output.setText(commands)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
app = QApplication(sys.argv)
|
||||
manager = SSHConfigManager()
|
||||
manager.show()
|
||||
sys.exit(app.exec())
|
||||
271
asf-pc-server/provision_vm.sh
Normal file
271
asf-pc-server/provision_vm.sh
Normal file
@@ -0,0 +1,271 @@
|
||||
#!/bin/bash
|
||||
|
||||
# ======================================================
|
||||
# 🚀 Automated VirtualBox VM Management Script (Updated)
|
||||
# ======================================================
|
||||
|
||||
# --- Configuration ---
|
||||
BASE_VM_NAME="DB_Image" # Name of the template/base VM to clone from
|
||||
VM_USER="asf_user" # User inside the VM (for IP detection and final output)
|
||||
VM_PASSWORD="ASF" # Password for the VM user (for IP detection)
|
||||
|
||||
# Ports Configuration
|
||||
BASE_SSH_PORT=6002 # Starting SSH NAT port
|
||||
BASE_VNC_PORT=5002 # Starting VNC (VRDE) port
|
||||
PORT_DB_FILE="$HOME/.vms_ports_db.json" # JSON file for port persistence
|
||||
|
||||
# VM Resources (50 GB is 51200 MB)
|
||||
VM_MEMORY=51200
|
||||
VM_VRAM=128
|
||||
VM_CPUS=2
|
||||
|
||||
# --- Utility Functions (All functions remain the same) ---
|
||||
|
||||
usage() {
|
||||
echo "Usage:"
|
||||
echo " $0 create <new_vm_name>"
|
||||
echo " $0 status <vm_name>"
|
||||
echo " $0 activate <vm_name>"
|
||||
echo " $0 delete <vm_name>"
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Function to remove VM port entry from JSON
|
||||
remove_port_entry() {
|
||||
local vm_name="$1"
|
||||
init_port_db # Ensure DB exists
|
||||
|
||||
if [[ -f "$PORT_DB_FILE" ]]; then
|
||||
if grep -q "\"$vm_name\"" "$PORT_DB_FILE"; then
|
||||
# Use grep and sed to remove the entry, handling list structure complexities
|
||||
grep -v "\"$VM_NAME\"" "$PORT_DB_FILE" | sed 's/}, /}/' | sed 's/, }/}/' > "${PORT_DB_FILE}.tmp"
|
||||
|
||||
if grep -q "^{.*}$" "${PORT_DB_FILE}.tmp"; then
|
||||
mv "${PORT_DB_FILE}.tmp" "$PORT_DB_FILE"
|
||||
else
|
||||
echo "{}" > "$PORT_DB_FILE"
|
||||
fi
|
||||
# NOTE: Removed the success echo to keep the main flow cleaner
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
# Function to safely create/initialize the JSON port database
|
||||
init_port_db() {
|
||||
if [[ ! -f "$PORT_DB_FILE" ]]; then
|
||||
echo "{}" > "$PORT_DB_FILE"
|
||||
fi
|
||||
}
|
||||
|
||||
# Function to get VM status
|
||||
get_vm_status() {
|
||||
local vm_name="$1"
|
||||
local status
|
||||
status=$(VBoxManage showvminfo "$vm_name" --machinereadable 2>/dev/null | grep -i "^VMState=" | cut -d'"' -f2)
|
||||
|
||||
if [[ "$status" == "running" ]]; then
|
||||
echo "active"
|
||||
else
|
||||
echo "not active"
|
||||
fi
|
||||
}
|
||||
|
||||
# Function to start VM if not running
|
||||
activate_vm() {
|
||||
local vm_name="$1"
|
||||
local status
|
||||
status=$(get_vm_status "$vm_name")
|
||||
if [[ "$status" == "active" ]]; then
|
||||
echo "✅ VM '$vm_name' is already active."
|
||||
return 0
|
||||
fi
|
||||
|
||||
echo "🚀 Starting VM '$vm_name'..."
|
||||
VBoxManage startvm "$vm_name" --type headless
|
||||
if [[ $? -eq 0 ]]; then
|
||||
echo "✅ VM '$vm_name' started successfully."
|
||||
else
|
||||
echo "❌ Failed to start VM '$vm_name'."
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
# Function to read ports from JSON
|
||||
get_vm_ports() {
|
||||
local vm_name="$1"
|
||||
local ports
|
||||
|
||||
# Check if the VM entry exists in the JSON file
|
||||
ports=$(cat "$PORT_DB_FILE" | grep "\"$vm_name\"" | awk -F': ' '{print $2}' | tr -d '",}{')
|
||||
|
||||
if [[ -z "$ports" ]]; then
|
||||
echo "" # Return empty if not found
|
||||
else
|
||||
# Assuming the format in the JSON is {"vm_name": "SSH_PORT VNC_PORT"}
|
||||
echo "$ports"
|
||||
fi
|
||||
}
|
||||
|
||||
# Function to assign unique ports for SSH/VNC per VM using JSON
|
||||
assign_ports() {
|
||||
local vm_name="$1"
|
||||
init_port_db
|
||||
|
||||
# 1. Check if ports already assigned in JSON
|
||||
local assigned_ports
|
||||
assigned_ports=$(get_vm_ports "$vm_name")
|
||||
|
||||
if [[ -n "$assigned_ports" ]]; then
|
||||
echo "$assigned_ports"
|
||||
return 0
|
||||
fi
|
||||
|
||||
# 2. Collect ALL currently used ports from the JSON file
|
||||
local used_ports
|
||||
used_ports=$(grep -oE "[0-9]+" "$PORT_DB_FILE" 2>/dev/null)
|
||||
|
||||
local ssh_port=$BASE_SSH_PORT
|
||||
local vnc_port=$BASE_VNC_PORT
|
||||
|
||||
# Find unique SSH port
|
||||
while grep -qw "$ssh_port" <<<"$used_ports" || netstat -tln 2>/dev/null | grep -q ":$ssh_port"; do
|
||||
ssh_port=$((ssh_port + 1))
|
||||
done
|
||||
|
||||
# Find unique VNC port
|
||||
while grep -qw "$vnc_port" <<<"$used_ports" || netstat -tln 2>/dev/null | grep -q ":$vnc_port"; do
|
||||
vnc_port=$((vnc_port + 1))
|
||||
done
|
||||
|
||||
# 3. Save new ports to JSON using a simple tool for JSON manipulation
|
||||
local new_entry="\"$vm_name\": \"$ssh_port $vnc_port\""
|
||||
|
||||
local current_content
|
||||
current_content=$(cat "$PORT_DB_FILE")
|
||||
|
||||
if [[ "$current_content" == "{}" ]]; then
|
||||
echo "{$new_entry}" > "$PORT_DB_FILE"
|
||||
else
|
||||
sed '$s/}$/, '"$new_entry"'}' "$PORT_DB_FILE" > "${PORT_DB_FILE}.tmp" && mv "${PORT_DB_FILE}.tmp" "$PORT_DB_FILE"
|
||||
fi
|
||||
|
||||
echo "$ssh_port $vnc_port"
|
||||
}
|
||||
|
||||
# --- Main Logic ---
|
||||
|
||||
if [[ $# -lt 2 && "$1" != "create" ]]; then
|
||||
usage
|
||||
fi
|
||||
|
||||
COMMAND="$1"
|
||||
ARG="$2"
|
||||
|
||||
case "$COMMAND" in
|
||||
status)
|
||||
get_vm_status "$ARG"
|
||||
;;
|
||||
|
||||
activate)
|
||||
activate_vm "$ARG"
|
||||
;;
|
||||
|
||||
create)
|
||||
NEW_VM_NAME="$ARG"
|
||||
init_port_db # Ensure DB exists
|
||||
|
||||
if VBoxManage showvminfo "$NEW_VM_NAME" &>/dev/null; then
|
||||
echo "VM '$NEW_VM_NAME' already exists. Please delete it or choose another name."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Assign ports and store them in the JSON
|
||||
read -r SSH_PORT VNC_PORT < <(assign_ports "$NEW_VM_NAME")
|
||||
|
||||
echo "Cloning VM '$BASE_VM_NAME' to '$NEW_VM_NAME' (SSH:$SSH_PORT, VNC:$VNC_PORT)..."
|
||||
VBoxManage clonevm "$BASE_VM_NAME" --name "$NEW_VM_NAME" --register --mode all
|
||||
|
||||
if [[ $? -ne 0 ]]; then
|
||||
echo "❌ Failed to clone VM. Aborting."
|
||||
remove_port_entry "$NEW_VM_NAME"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Configure VM resources
|
||||
echo "Configuring VM resources (Memory: $VM_MEMORY MB, CPUs: $VM_CPUS)..."
|
||||
VBoxManage modifyvm "$NEW_VM_NAME" --memory "$VM_MEMORY" --vram "$VM_VRAM" --cpus "$VM_CPUS"
|
||||
VBoxManage modifyvm "$NEW_VM_NAME" --nic1 nat
|
||||
VBoxManage modifyvm "$NEW_VM_NAME" --cableconnected1 on
|
||||
|
||||
# --- PHASE 1: Initial Boot and Shutdown ---
|
||||
|
||||
echo "Starting VM '$NEW_VM_NAME' for initial boot (1 minute)..."
|
||||
VBoxManage startvm "$NEW_VM_NAME" --type headless
|
||||
|
||||
# Wait for 1 minute (60 seconds) for first boot/OS initialization
|
||||
sleep 60
|
||||
echo "Initial boot complete. Shutting down VM."
|
||||
|
||||
VBoxManage controlvm "$NEW_VM_NAME" poweroff
|
||||
|
||||
# --- PHASE 2: Setting Network Ports ---
|
||||
echo "Setting up network port forwarding (SSH:$SSH_PORT, VNC:$VNC_PORT)..."
|
||||
|
||||
# Remove old (potentially conflicting) rules and add the new one
|
||||
VBoxManage modifyvm "$NEW_VM_NAME" --natpf1 delete ssh 2>/dev/null || true
|
||||
|
||||
# Add the new NAT port forwarding rules
|
||||
VBoxManage modifyvm "$NEW_VM_NAME" --natpf1 "ssh,tcp,,${SSH_PORT},,22"
|
||||
VBoxManage modifyvm "$NEW_VM_NAME" --vrde on --vrdeport "$VNC_PORT"
|
||||
|
||||
# --- PHASE 3: Final Start and Wait ---
|
||||
echo "Starting VM '$NEW_VM_NAME' headless for final usage (Waiting 5 minutes)..."
|
||||
VBoxManage startvm "$NEW_VM_NAME" --type headless
|
||||
|
||||
# Wait for 5 minutes (300 seconds) for the system and services to fully initialize
|
||||
sleep 300
|
||||
echo "Initialization complete. VM should be ready."
|
||||
|
||||
# --- PHASE 4: Final Output ---
|
||||
|
||||
echo "--- ✅ VM $NEW_VM_NAME Created and Ready ---"
|
||||
echo "Host: 127.0.0.1 (use your external DDNS for remote access)"
|
||||
echo "User: $VM_USER"
|
||||
echo "SSH Port: $SSH_PORT"
|
||||
echo "VNC Port: $VNC_PORT"
|
||||
|
||||
;;
|
||||
|
||||
delete)
|
||||
VM_NAME="$ARG"
|
||||
init_port_db # Ensure DB exists
|
||||
|
||||
echo "🛑 Powering off VM '$VM_NAME'..."
|
||||
VBoxManage controlvm "$VM_NAME" poweroff 2>/dev/null || true
|
||||
|
||||
echo "🗑️ Unregistering and deleting VM '$VM_NAME'..."
|
||||
VBoxManage unregistervm "$VM_NAME" --delete
|
||||
|
||||
# Remove entry from port DB
|
||||
if [[ -f "$PORT_DB_FILE" ]]; then
|
||||
|
||||
if grep -q "\"$VM_NAME\"" "$PORT_DB_FILE"; then
|
||||
# Remove the entire line containing the VM entry
|
||||
grep -v "\"$VM_NAME\"" "$PORT_DB_FILE" | sed 's/}, /}/' | sed 's/, }/}/' > "${PORT_DB_FILE}.tmp"
|
||||
|
||||
# Check if the file is now just "{}" and fix it
|
||||
if grep -q "^{.*}$" "${PORT_DB_FILE}.tmp"; then
|
||||
mv "${PORT_DB_FILE}.tmp" "$PORT_DB_FILE"
|
||||
else
|
||||
echo "{}" > "$PORT_DB_FILE"
|
||||
fi
|
||||
|
||||
echo "✅ Removed '$VM_NAME' entry from $PORT_DB_FILE"
|
||||
fi
|
||||
fi
|
||||
;;
|
||||
|
||||
*)
|
||||
usage
|
||||
;;
|
||||
esac
|
||||
Reference in New Issue
Block a user