Last active
March 16, 2025 02:23
-
-
Save ActuallyFro/cfa1983375fec9204d53fd3a8ef7d13f to your computer and use it in GitHub Desktop.
Using Ubuntu 24.04 -- setting up MERN stack with an example application/hello-world.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/bin/bash | |
| # MERN-Stack Provisioning Script for Ubuntu | |
| set -e | |
| # Colors for output | |
| green='\e[32m' | |
| red='\e[31m' | |
| yellow='\e[33m' | |
| reset='\e[0m' | |
| #Default directory | |
| MERNApp_INSTALL_DIR="/opt/mernapp" | |
| echo -e "${green}[SETUP MERN] Starting installation and provisioning...${reset}" | |
| # Database Server Setup -- based on: https://www.mongodb.com/docs/manual/tutorial/install-mongodb-on-ubuntu/ | |
| install_mongodb() { | |
| echo -e "${green}[SETUP MERN] Installing MongoDB...${reset}" | |
| sudo apt-get update | |
| sudo apt-get install -y gnupg curl git | |
| # Setup keys/sources | |
| if [ ! -f /usr/share/keyrings/mongodb-server-8.0.gpg ]; then | |
| curl -fsSL https://www.mongodb.org/static/pgp/server-8.0.asc | \ | |
| sudo gpg --batch --yes -o /usr/share/keyrings/mongodb-server-8.0.gpg --dearmor | |
| else | |
| echo "GPG key already exists: /usr/share/keyrings/mongodb-server-8.0.gpg" | |
| fi | |
| echo "deb [ arch=amd64,arm64 signed-by=/usr/share/keyrings/mongodb-server-8.0.gpg ] https://repo.mongodb.org/apt/ubuntu noble/mongodb-org/8.0 multiverse" | \ | |
| sudo tee /etc/apt/sources.list.d/mongodb-org-8.0.list > /dev/null | |
| sudo apt-get update | |
| # Install MongoDB Community Server | |
| sudo apt-get install -y mongodb-org=8.0.4 mongodb-org-database=8.0.4 mongodb-org-server=8.0.4 mongodb-mongosh mongodb-org-mongos=8.0.4 mongodb-org-tools=8.0.4 | |
| # Prevent automatic updates | |
| echo "mongodb-org hold" | sudo dpkg --set-selections | |
| echo "mongodb-org-database hold" | sudo dpkg --set-selections | |
| echo "mongodb-org-server hold" | sudo dpkg --set-selections | |
| echo "mongodb-mongosh hold" | sudo dpkg --set-selections | |
| echo "mongodb-org-mongos hold" | sudo dpkg --set-selections | |
| echo "mongodb-org-tools hold" | sudo dpkg --set-selections | |
| echo -e "${green}[SETUP MERN] MongoDB installed.${reset}" | |
| } | |
| configure_mongodb() { | |
| echo -e "${green}[SETUP MERN] Configuring MongoDB...${reset}" | |
| config_file="/etc/mongod.conf" | |
| # Ensure the MongoDB configuration file exists | |
| if [ ! -f "$config_file" ]; then | |
| echo -e "${red}[SETUP MERN][ERROR] MongoDB configuration file not found: $config_file.${reset}" | |
| exit 1 | |
| fi | |
| # Update the bindIp to allow external connections | |
| if grep -q "^ bindIp: 127.0.0.1" "$config_file"; then | |
| sudo sed -i 's/^ bindIp: 127.0.0.1/ bindIp: 0.0.0.0/' "$config_file" | |
| echo -e "${green}[SETUP MERN] Updated bindIp to 0.0.0.0 in mongod.conf.${reset}" | |
| else | |
| echo -e "${yellow}[SETUP MERN][WARN] bindIp is already set to 0.0.0.0 or missing.${reset}" | |
| fi | |
| # Handle security block configuration | |
| if grep -q "^#security:" "$config_file"; then | |
| # Uncomment the security block and add authorization | |
| sudo sed -i 's/^#security:.*/security:\n authorization: enabled/' "$config_file" | |
| echo -e "${green}[SETUP MERN] Uncommented and updated 'security' configuration in mongod.conf.${reset}" | |
| elif grep -q "^security:" "$config_file"; then | |
| # Ensure authorization is enabled under the existing security block | |
| if ! grep -q "^ authorization: enabled" "$config_file"; then | |
| sudo sed -i '/^security:/a \ authorization: enabled' "$config_file" | |
| echo -e "${green}[SETUP MERN] Added 'authorization: enabled' under existing 'security' configuration.${reset}" | |
| else | |
| echo -e "${yellow}[SETUP MERN][WARN] 'authorization: enabled' is already set in mongod.conf.${reset}" | |
| fi | |
| else | |
| # Append the security block if it doesn't exist | |
| echo -e "\nsecurity:\n authorization: enabled" | sudo tee -a "$config_file" > /dev/null | |
| echo -e "${green}[SETUP MERN] Added 'security' block to mongod.conf.${reset}" | |
| fi | |
| # Restart MongoDB to apply changes | |
| echo -e "${green}[SETUP MERN] Restarting MongoDB to apply configuration changes...${reset}" | |
| sudo systemctl enable mongod | |
| sudo systemctl restart mongod | |
| echo -e "${green}[SETUP MERN] MongoDB configured and restarted.${reset}" | |
| } | |
| check_install_mongodb() { | |
| echo -e "${green}[SETUP MERN] Checking MongoDB installation...${reset}" | |
| # Check if data and log directories exist | |
| if [[ -d "/var/lib/mongodb" && -d "/var/log/mongodb" ]]; then | |
| echo -e "${green}[SETUP MERN] MongoDB directories found.${reset}" | |
| else | |
| echo -e "${red}[SETUP MERN] MongoDB directories not found. Installation might be incomplete.${reset}" | |
| exit 1 | |
| fi | |
| # Verify configuration | |
| config_file="/etc/mongod.conf" | |
| if grep -q "bindIp: 0.0.0.0" "$config_file"; then | |
| echo -e "${green}[SETUP MERN] MongoDB configuration verified.${reset}" | |
| else | |
| echo -e "${red}[SETUP MERN] MongoDB configuration update failed. Please check $config_file.${reset}" | |
| exit 1 | |
| fi | |
| # Check MongoDB binary presence and execute health check | |
| if ! command -v mongod &>/dev/null; then | |
| echo -e "${red}[SETUP MERN] MongoDB binary not found. Installation might be incomplete.${reset}" | |
| exit 1 | |
| fi | |
| # Check MongoDB version (handles crashes like "Illegal instruction") | |
| version_check=$(mongod --version 2>&1 || true) | |
| if [[ $version_check == *"Illegal instruction"* ]]; then | |
| echo -e "${red}[SETUP MERN] MongoDB version check failed: Illegal instruction.${reset}" | |
| exit 1 | |
| elif [[ $version_check == *"db version"* ]]; then | |
| echo -e "${green}[SETUP MERN] MongoDB version: $(echo "$version_check" | head -n 1).${reset}" | |
| else | |
| echo -e "${red}[SETUP MERN] MongoDB version check failed. Output: $version_check${reset}" | |
| exit 1 | |
| fi | |
| # Check if MongoDB service is running | |
| if sudo systemctl is-active --quiet mongod; then | |
| echo -e "${green}[SETUP MERN] MongoDB service is running.${reset}" | |
| else | |
| echo -e "${red}[SETUP MERN] MongoDB service is not running. Check logs for errors.${reset}" | |
| # exit 1 | |
| fi | |
| } | |
| setup_mongodb_auth() { | |
| echo -e "${green}[SETUP MERN] Setting up MongoDB authentication...${reset}" | |
| # Wait for MongoDB to become fully operational | |
| echo -e "${green}[SETUP MERN] Waiting for MongoDB to start...${reset}" | |
| for i in {1..10}; do | |
| if mongosh --eval "db.runCommand({ connectionStatus: 1 })" &>/dev/null; then | |
| echo -e "${green}[SETUP MERN] MongoDB is operational.${reset}" | |
| break | |
| fi | |
| echo -e "${yellow}[SETUP MERN][WARN] MongoDB is not ready. Retrying in 20 seconds...${reset}" | |
| sleep 20 | |
| done | |
| # Check if admin user already exists | |
| echo -e "${green}[SETUP MERN] Checking if admin user already exists...${reset}" | |
| if mongosh --eval "db.getSiblingDB('admin').getUser('admin')" &>/dev/null; then | |
| echo -e "${yellow}[SETUP MERN][WARN] Admin user already exists. Skipping creation.${reset}" | |
| return | |
| fi | |
| # Create admin user | |
| mongosh <<EOF | |
| use admin | |
| db.createUser({ | |
| user: "admin", | |
| pwd: "SecureAdminPass123!", | |
| roles: [ | |
| { role: "userAdminAnyDatabase", db: "admin" }, | |
| { role: "readWriteAnyDatabase", db: "admin" } | |
| ] | |
| }) | |
| EOF | |
| echo -e "${green}[SETUP MERN] Admin user created successfully.${reset}" | |
| # Restart MongoDB to apply authentication settings | |
| echo -e "${green}[SETUP MERN] Restarting MongoDB to apply security settings...${reset}" | |
| sudo systemctl restart mongod | |
| echo -e "${green}[SETUP MERN] MongoDB authentication setup completed.${reset}" | |
| } | |
| create_web_user() { | |
| echo -e "${green}[SETUP MERN] Creating web server user...${reset}" | |
| # Wait for MongoDB to become fully operational | |
| echo -e "${green}[SETUP MERN] Waiting for MongoDB to start...${reset}" | |
| for i in {1..10}; do | |
| if mongosh -u admin -p "SecureAdminPass123!" --authenticationDatabase admin --eval "db.runCommand({ connectionStatus: 1 })" &>/dev/null; then | |
| echo -e "${green}[SETUP MERN] MongoDB is operational.${reset}" | |
| break | |
| fi | |
| echo -e "${yellow}[SETUP MERN][WARN] MongoDB is not ready for web user creation. Retrying in 10 seconds...${reset}" | |
| sleep 20 | |
| done | |
| # # Check if web user already exists | |
| # echo -e "${green}[SETUP MERN] Checking if web server user already exists...${reset}" | |
| # if mongosh -u admin -p "SecureAdminPass123!" --authenticationDatabase admin --eval "db.getSiblingDB('mernapp').getUser('web-server')" &>/dev/null; then | |
| # echo -e "${yellow}[SETUP MERN][WARN] Web server user already exists. Skipping creation.${reset}" | |
| # return | |
| # fi | |
| # Create web server user | |
| mongosh -u admin -p "SecureAdminPass123!" --authenticationDatabase admin <<EOF | |
| use mernapp | |
| db.createUser({ | |
| user: "web-server", | |
| pwd: "Pass123!", | |
| roles: [ { role: "readWrite", db: "mernapp" } ] | |
| }) | |
| EOF | |
| echo -e "${green}[SETUP MERN] Web server user created successfully.${reset}" | |
| } | |
| # Web Server Setup | |
| install_nodejs() { | |
| echo -e "${green}[SETUP MERN] Installing NodeJS...${reset}" | |
| curl -sL https://deb.nodesource.com/setup_20.x | sudo -E bash - | |
| sudo apt install -y nodejs | |
| echo -e "${green}[SETUP MERN] NodeJS installed.${reset}" | |
| } | |
| setup_mernapp() { | |
| echo -e "${green}[SETUP MERN] Setting up MERNApp...${reset}" | |
| if [ -d "$MERNApp_INSTALL_DIR" ]; then | |
| echo -e "${yellow}[SETUP MERN][WARN] Existing MERNApp installation detected in $MERNApp_INSTALL_DIR. Skipping creation...${reset}" | |
| else | |
| # Set directory permissions for all users | |
| sudo mkdir -p "$MERNApp_INSTALL_DIR" | |
| echo -e "${green}[SETUP MERN] Setting permissions for $MERNApp_INSTALL_DIR...${reset}" | |
| sudo chmod -R 755 "$MERNApp_INSTALL_DIR" | |
| sudo chown -R $USER:$USER "$MERNApp_INSTALL_DIR" | |
| ################################################################################ | |
| # Uncomment for CLONE-repo approach | |
| # # Check SSH key permissions | |
| # sudo chmod 600 ~/.ssh/id_rsa | |
| # | |
| # # Ensure Git does not prompt for confirmation of the host key | |
| # export GIT_SSH_COMMAND='ssh -o StrictHostKeyChecking=no' | |
| # | |
| # # Clone the repository if it doesn't exist | |
| # echo -e "${green}[SETUP MERN] Cloning MERNApp repository to $MERNApp_INSTALL_DIR...${reset}" | |
| # git clone [email protected]:username-here/mernapp.git "$MERNApp_INSTALL_DIR" || { | |
| # echo "[ERROR] Failed to clone repository" >&2 | |
| # exit 1 | |
| # } | |
| ################################################################### | |
| # REMOVE this section if using the CLONE-repo approach (START) | |
| # Create package.json | |
| echo -e "${green}[SETUP MERN] Creating package.json...${reset}" | |
| cat <<EOF > "$MERNApp_INSTALL_DIR/package.json" | |
| { | |
| "name": "mern-hello-world", | |
| "version": "1.0.0", | |
| "description": "A simple MERN stack hello world application", | |
| "main": "server.js", | |
| "scripts": { | |
| "start": "node server.js", | |
| "dev": "nodemon server.js", | |
| "prod": "NODE_ENV=production node server.js", | |
| "client": "cd client && npm start", | |
| "client-build": "cd client && npm run build", | |
| "install-client": "cd client && npm install", | |
| "setup": "npm install && npm run install-client && npm run client-build" | |
| }, | |
| "dependencies": { | |
| "express": "^4.18.2", | |
| "mongoose": "^8.0.0" | |
| }, | |
| "devDependencies": { | |
| "nodemon": "^3.0.1" | |
| } | |
| } | |
| EOF | |
| # Create server.js | |
| echo -e "${green}[SETUP MERN] Creating server.js...${reset}" | |
| cat <<EOF > "$MERNApp_INSTALL_DIR/server.js" | |
| const express = require('express'); | |
| const mongoose = require('mongoose'); | |
| const path = require('path'); | |
| const fs = require('fs'); | |
| // Load environment configuration | |
| const envFile = path.join(__dirname, 'environment.json'); | |
| const config = JSON.parse(fs.readFileSync(envFile, 'utf8')); | |
| // Initialize Express app | |
| const app = express(); | |
| const PORT = config.port || 8080; | |
| // Middleware | |
| app.use(express.json()); | |
| app.use(express.urlencoded({ extended: true })); | |
| // Serve static files from the React build | |
| app.use(express.static(path.join(__dirname, 'client/build'))); | |
| // MongoDB Connection | |
| const mongoURI = \`mongodb://\${config.mongoUsername}:\${encodeURIComponent(config.mongoPassword)}@\${config.mongoHost}:\${config.mongoPort}/\${config.mongoDB}?authSource=\${config.mongoDB}\`; | |
| mongoose.connect(mongoURI) | |
| .then(() => console.log('MongoDB connected successfully')) | |
| .catch(err => console.error('MongoDB connection error:', err)); | |
| // Define a simple schema | |
| const MessageSchema = new mongoose.Schema({ | |
| text: String, | |
| createdAt: { type: Date, default: Date.now } | |
| }); | |
| const Message = mongoose.model('Message', MessageSchema); | |
| // API Routes | |
| app.get('/api/hello', async (req, res) => { | |
| try { | |
| // Save a new message to the database | |
| const message = new Message({ text: "Hello World from MongoDB!" }); | |
| await message.save(); | |
| // Get all messages | |
| const messages = await Message.find().sort({ createdAt: -1 }); | |
| res.json({ | |
| message: "Hello from MERN Stack!", | |
| savedMessages: messages | |
| }); | |
| } catch (err) { | |
| res.status(500).json({ error: err.message }); | |
| } | |
| }); | |
| // Simple route for basic testing | |
| app.get('/ping', (req, res) => { | |
| res.send('pong'); | |
| }); | |
| // Start the server | |
| app.listen(PORT, () => { | |
| console.log(\`Server running on port \${PORT}\`); | |
| }); | |
| // Handle process termination | |
| process.on('SIGINT', () => { | |
| mongoose.connection.close(() => { | |
| console.log('MongoDB connection closed'); | |
| process.exit(0); | |
| }); | |
| }); | |
| EOF | |
| # Create minimal client directory and index.html for testing | |
| echo -e "${green}[SETUP MERN] Creating minimal client...${reset}" | |
| mkdir -p "$MERNApp_INSTALL_DIR/client/build" | |
| cat <<EOF > "$MERNApp_INSTALL_DIR/client/build/index.html" | |
| <!DOCTYPE html> | |
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>MERN Hello World</title> | |
| <style> | |
| body { | |
| font-family: Arial, sans-serif; | |
| max-width: 800px; | |
| margin: 0 auto; | |
| padding: 20px; | |
| text-align: center; | |
| } | |
| .container { | |
| background-color: #f5f5f5; | |
| border-radius: 8px; | |
| padding: 20px; | |
| box-shadow: 0 2px 4px rgba(0,0,0,0.1); | |
| } | |
| #messages { | |
| text-align: left; | |
| margin-top: 20px; | |
| padding: 10px; | |
| background-color: #fff; | |
| border-radius: 4px; | |
| min-height: 200px; | |
| } | |
| .message-item { | |
| padding: 8px; | |
| margin: 8px 0; | |
| border-bottom: 1px solid #eee; | |
| } | |
| button { | |
| background-color: #4CAF50; | |
| color: white; | |
| padding: 10px 15px; | |
| border: none; | |
| border-radius: 4px; | |
| cursor: pointer; | |
| } | |
| button:hover { | |
| background-color: #45a049; | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <div class="container"> | |
| <h1>MERN Stack Hello World</h1> | |
| <p>This is a simple MERN application deployed by the provisioning script.</p> | |
| <button id="hello-btn">Click to load data from MongoDB</button> | |
| <div id="messages"> | |
| <p>Messages will appear here after clicking the button...</p> | |
| </div> | |
| </div> | |
| <script> | |
| document.getElementById('hello-btn').addEventListener('click', async () => { | |
| try { | |
| const response = await fetch('/api/hello'); | |
| const data = await response.json(); | |
| const messagesDiv = document.getElementById('messages'); | |
| messagesDiv.innerHTML = '<h3>' + data.message + '</h3>'; | |
| if (data.savedMessages && data.savedMessages.length) { | |
| messagesDiv.innerHTML += '<h4>Messages from MongoDB:</h4>'; | |
| const messagesList = document.createElement('div'); | |
| data.savedMessages.forEach(msg => { | |
| const messageItem = document.createElement('div'); | |
| messageItem.className = 'message-item'; | |
| const date = new Date(msg.createdAt).toLocaleString(); | |
| messageItem.textContent = \`\${msg.text} (created: \${date})\`; | |
| messagesList.appendChild(messageItem); | |
| }); | |
| messagesDiv.appendChild(messagesList); | |
| } | |
| } catch (error) { | |
| console.error('Error:', error); | |
| document.getElementById('messages').innerHTML = | |
| '<p style="color: red">Error loading data. Check console for details.</p>'; | |
| } | |
| }); | |
| </script> | |
| </body> | |
| </html> | |
| EOF | |
| # REMOVE this section if using the CLONE-repo approach (END) | |
| ################################################################### | |
| cd "$MERNApp_INSTALL_DIR" || exit 1 | |
| fi | |
| # Uncomment for CLONE-repo approach | |
| # # Install dependencies and build the application | |
| # echo -e "${green}[SETUP MERN] Installing dependencies and building the application...${reset}" | |
| # npm install | |
| # #npm run build | |
| # npm run prod | |
| echo -e "${green}[SETUP MERN] MERNApp setup completed.${reset}" | |
| } | |
| configure_mernapp_env() { | |
| echo -e "${green}[SETUP MERN] Configuring MERNApp environment...${reset}" | |
| cat <<EOF > "$MERNApp_INSTALL_DIR/environment.json" | |
| { | |
| "port": 8080, | |
| "mongoDB": "mernapp", | |
| "mongoHost": "localhost", | |
| "mongoPort": 27017, | |
| "mongoUsername": "web-server", | |
| "mongoPassword": "Pass123!", | |
| "fileStoreDir": "./files/store", | |
| "httpRateLimit": 25, | |
| "wsRateLimit": 25 | |
| } | |
| EOF | |
| echo -e "${green}[SETUP MERN] Environment configuration saved.${reset}" | |
| } | |
| run_mernapp() { | |
| echo -e "${green}[SETUP MERN] Starting MERNApp...${reset}" | |
| cd "$MERNApp_INSTALL_DIR" || exit 1 | |
| #GitHub repo approach: | |
| #npm run prod | |
| npm install | |
| npm start | |
| } | |
| # Provision steps/execution | |
| # ========================= | |
| install_mongodb | |
| configure_mongodb | |
| check_install_mongodb | |
| setup_mongodb_auth | |
| create_web_user | |
| install_nodejs | |
| setup_mernapp | |
| configure_mernapp_env | |
| run_mernapp | |
| echo -e "${green}[SETUP MERN] Installation and provisioning completed!${reset}" | |
| #NOTES | |
| # mongod will NOT start -- Q: Are you using a VM? | |
| # Symptom: `mongod.service: Main process exited, code=dumped, status=4/ILL` <--- AVX flag and Windows "CPU protections" block passthrough of the VM to the CPU hardware... | |
| #Consideration -- security/lockdown of code | |
| # --Starting w/ disabled features: mongosh --nodb --eval "disableTelemetry()" |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment