Skip to content

Instantly share code, notes, and snippets.

@ActuallyFro
Last active March 16, 2025 02:23
Show Gist options
  • Select an option

  • Save ActuallyFro/cfa1983375fec9204d53fd3a8ef7d13f to your computer and use it in GitHub Desktop.

Select an option

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.
#!/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