revert docker details for now, add root ca gen

This commit is contained in:
Jeff Caldwell
2025-07-29 15:14:26 -04:00
parent 46ac271afb
commit 9ca2bb0306
6 changed files with 591 additions and 92 deletions
+8
View File
@@ -18,12 +18,20 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Health check configuration for container monitoring
- Non-root user security implementation in containers
- Environment variable support for all configuration options
- Automatic Root CA generation when none exists
- Manual Root CA generation option with user-friendly interface
- Visual indicators for auto-generated Root CAs
- New API endpoint `/api/generate-ca` for manual CA creation
### Changed
- Updated .gitignore to exclude Docker-related build files
- Enhanced package.json with Docker-related scripts
- Optimized .dockerignore for efficient Docker builds
- Cleaned up unused backup and development files
- **Docker**: Added OpenSSL to container for full certificate functionality
### Fixed
- **Docker**: OpenSSL now included in container for certificate analysis and operations
### Removed
- Unused backup files
+254 -90
View File
@@ -4,36 +4,91 @@ This document provides comprehensive instructions for running mkcert Web UI usin
## Quick Start
### Option 1: Docker Run (Simple)
### Recommended Method: Docker Compose
Run the application with default settings:
The repository includes a pre-configured `docker-compose.yml` file for easy deployment:
```bash
# Clone the repository
git clone https://github.com/jeffcaldwellca/mkcertWeb.git
cd mkcertWeb
# Start the application using the included docker-compose.yml
docker-compose up -d
```
That's it! The application will be available at:
- **HTTP**: http://localhost:3000
- **HTTPS**: http://localhost:3443 (if enabled)
### Alternative: Manual Docker Run
If you prefer to run Docker commands manually:
```bash
# Clone the repository
git clone https://github.com/jeffcaldwellca/mkcertWeb.git
cd mkcertWeb
# Build the image
docker build -t mkcert-web-ui .
# Run the application
docker run -d \
--name mkcert-web-ui \
-p 3000:3000 \
-v mkcert_certificates:/app/certificates \
-v mkcert_data:/app/data \
jeffcaldwellca/mkcert-web-ui:latest
```
### Option 2: Docker Compose (Recommended)
1. Download the docker-compose.yml file:
```bash
wget https://raw.githubusercontent.com/jeffcaldwellca/mkcertWeb/main/docker-compose.yml
```
2. Start the application:
```bash
docker-compose up -d
mkcert-web-ui
```
## Configuration Options
### Environment Variables
The included `docker-compose.yml` file provides sensible defaults and can be customized for your needs.
You can customize the application behavior using environment variables:
### Using the Included Docker Compose File
#### Default Configuration
The included configuration provides:
- HTTP server on port 3000
- HTTPS server on port 3443 (disabled by default)
- Dark theme as default
- Authentication disabled (for easy development)
- Persistent volumes for certificates and data
- Automatic restart on failure
- Health monitoring
#### Customizing Environment Variables
You can override any environment variable by creating a `.env` file in the repository root:
```bash
# Create a .env file to customize settings
cat > .env << EOF
ENABLE_AUTH=true
AUTH_USERNAME=myuser
AUTH_PASSWORD=mysecurepassword
DEFAULT_THEME=light
ENABLE_HTTPS=true
SSL_DOMAIN=myapp.local
EOF
# Start with custom configuration
docker-compose up -d
```
#### Direct Environment Variable Override
You can also override environment variables directly:
```bash
# Override specific settings
ENABLE_AUTH=true AUTH_USERNAME=admin docker-compose up -d
```
### Manual Docker Run Examples
If you prefer not to use the included docker-compose.yml file:
#### Basic Configuration
```bash
@@ -43,7 +98,7 @@ docker run -d \
-e "DEFAULT_THEME=light" \
-e "NODE_ENV=production" \
-v mkcert_certificates:/app/certificates \
jeffcaldwellca/mkcert-web-ui:latest
mkcert-web-ui
```
#### With Authentication
@@ -56,7 +111,7 @@ docker run -d \
-e "AUTH_PASSWORD=mysecurepassword" \
-e "SESSION_SECRET=your-very-long-random-secret-key" \
-v mkcert_certificates:/app/certificates \
jeffcaldwellca/mkcert-web-ui:latest
mkcert-web-ui
```
#### With HTTPS
@@ -68,7 +123,7 @@ docker run -d \
-e "ENABLE_HTTPS=true" \
-e "SSL_DOMAIN=your-domain.com" \
-v mkcert_certificates:/app/certificates \
jeffcaldwellca/mkcert-web-ui:latest
mkcert-web-ui
```
### Available Environment Variables
@@ -87,75 +142,97 @@ docker run -d \
| `AUTH_PASSWORD` | `admin` | Password for authentication |
| `SESSION_SECRET` | `mkcert-web-ui-secret-key-change-in-production` | Session secret |
## Volume Mounts
## Docker Compose Management
### Required Volumes
### Basic Commands
- **Certificates**: `/app/certificates` - Stores generated SSL certificates
- **Data**: `/app/data` - Stores application data and configuration
### Example with Custom Directories
```bash
docker run -d \
--name mkcert-web-ui \
-p 3000:3000 \
-v /host/path/to/certificates:/app/certificates \
-v /host/path/to/data:/app/data \
jeffcaldwellca/mkcert-web-ui:latest
# Start the application
docker-compose up -d
# View logs
docker-compose logs -f
# Stop the application
docker-compose down
# Restart the application
docker-compose restart
# Update and rebuild
docker-compose down
docker-compose up -d --build
# View status
docker-compose ps
```
### Data Persistence
The included docker-compose.yml automatically creates and manages:
- **mkcert_certificates**: Stores all generated SSL certificates
- **mkcert_data**: Stores application data and configuration
```bash
# View volume information
docker volume ls | grep mkcert
# Backup certificates
docker run --rm -v mkcert_certificates:/data -v $(pwd):/backup alpine tar czf /backup/certificates-backup.tar.gz -C /data .
# Restore certificates
docker run --rm -v mkcert_certificates:/data -v $(pwd):/backup alpine tar xzf /backup/certificates-backup.tar.gz -C /data
```
## Production Deployment
### Recommended Production Setup
```bash
docker run -d \
--name mkcert-web-ui \
--restart unless-stopped \
-p 3000:3000 \
-p 3443:3443 \
-e "NODE_ENV=production" \
-e "ENABLE_HTTPS=true" \
-e "FORCE_HTTPS=true" \
-e "SSL_DOMAIN=your-domain.com" \
-e "ENABLE_AUTH=true" \
-e "AUTH_USERNAME=yourusername" \
-e "AUTH_PASSWORD=yoursecurepassword" \
-e "SESSION_SECRET=$(openssl rand -base64 32)" \
-e "DEFAULT_THEME=light" \
-v mkcert_certificates:/app/certificates \
-v mkcert_data:/app/data \
jeffcaldwellca/mkcert-web-ui:latest
```
### Using Docker Compose for Production
Create a `.env` file:
The included docker-compose.yml can be easily configured for production:
```bash
# Production Configuration
# Create a production .env file
cat > .env << EOF
NODE_ENV=production
ENABLE_HTTPS=true
FORCE_HTTPS=true
SSL_DOMAIN=your-domain.com
# Authentication
ENABLE_AUTH=true
AUTH_USERNAME=yourusername
AUTH_PASSWORD=yoursecurepassword
SESSION_SECRET=your-very-long-random-secret-key
# Theme
SESSION_SECRET=$(openssl rand -base64 32)
DEFAULT_THEME=light
EOF
# Deploy to production
docker-compose up -d
```
Then run:
### Production Checklist
- ✅ Set strong `AUTH_USERNAME` and `AUTH_PASSWORD`
- ✅ Generate secure `SESSION_SECRET`
- ✅ Enable HTTPS with your domain
- ✅ Configure proper SSL_DOMAIN
- ✅ Set NODE_ENV=production
- ✅ Enable authentication
- ✅ Configure reverse proxy if needed
## Building and Running
### Using the Included Docker Compose (Recommended)
The repository includes everything needed:
```bash
docker-compose --env-file .env up -d
git clone https://github.com/jeffcaldwellca/mkcertWeb.git
cd mkcertWeb
docker-compose up -d
```
## Building from Source
### Manual Build Process
If you want to build the Docker image yourself:
If you need to build manually:
```bash
# Clone the repository
@@ -165,7 +242,7 @@ cd mkcertWeb
# Build the image
docker build -t mkcert-web-ui .
# Run your custom build
# Run your build
docker run -d \
--name mkcert-web-ui \
-p 3000:3000 \
@@ -175,39 +252,97 @@ docker run -d \
## Troubleshooting
### Check Container Logs
### Docker Compose Commands
```bash
docker logs mkcert-web-ui
# Check container status
docker-compose ps
# View application logs
docker-compose logs -f mkcert-web-ui
# Access container shell
docker-compose exec mkcert-web-ui /bin/sh
# Restart the service
docker-compose restart mkcert-web-ui
# Stop and remove everything
docker-compose down
```
### Access Container Shell
### Manual Docker Commands
```bash
# Check container logs
docker logs mkcert-web-ui
# Access container shell
docker exec -it mkcert-web-ui /bin/sh
```
### Health Check
The container includes a health check that verifies the application is responding:
The included docker-compose.yml has health monitoring built-in:
```bash
docker inspect --format='{{.State.Health.Status}}' mkcert-web-ui
# Check health status
docker-compose ps
# View detailed health information
docker inspect mkcertWeb_mkcert-web-ui_1 | grep -A 5 Health
```
### Port Conflicts
If port 3000 is already in use:
If port 3000 is already in use, modify the docker-compose.yml or use environment override:
```bash
# Edit docker-compose.yml ports section, or:
# Override ports with environment variable
PORT=8080 docker-compose up -d
# Or edit the docker-compose.yml file to change:
# ports:
# - "8080:3000" # HTTP port
```
For manual docker run:
```bash
docker run -d \
--name mkcert-web-ui \
-p 8080:3000 \
jeffcaldwellca/mkcert-web-ui:latest
mkcert-web-ui
```
### Persistence Issues
Ensure volumes are properly mounted to persist certificates and data:
The docker-compose.yml automatically handles volume persistence. To verify:
```bash
# Check volume mounts
docker inspect mkcert-web-ui | grep -A 10 "Mounts"
docker-compose config
# List volumes
docker volume ls | grep mkcert
# Inspect volume details
docker volume inspect mkcertWeb_mkcert_certificates
docker volume inspect mkcertWeb_mkcert_data
```
### Missing Dependencies
The Docker image includes all required dependencies:
- **mkcert**: Pre-installed for certificate generation
- **OpenSSL**: Included for certificate analysis and operations
- **Node.js**: Runtime environment
- **Alpine Linux**: Minimal base image
If you encounter issues, verify the container has the required tools:
```bash
# Check mkcert (using docker-compose)
docker-compose exec mkcert-web-ui mkcert -help
# Check OpenSSL (using docker-compose)
docker-compose exec mkcert-web-ui openssl version
# For manual docker run:
docker exec mkcert-web-ui mkcert -help
docker exec mkcert-web-ui openssl version
```
## Security Considerations
@@ -220,26 +355,55 @@ docker volume ls | grep mkcert
## Examples
### Development Setup
### Quick Development Setup
```bash
docker run -d \
--name mkcert-web-ui-dev \
-p 3000:3000 \
-e "NODE_ENV=development" \
-e "DEFAULT_THEME=dark" \
-v mkcert_certificates:/app/certificates \
jeffcaldwellca/mkcert-web-ui:latest
git clone https://github.com/jeffcaldwellca/mkcertWeb.git
cd mkcertWeb
docker-compose up -d
```
### Production Setup with Authentication
```bash
git clone https://github.com/jeffcaldwellca/mkcertWeb.git
cd mkcertWeb
# Create production configuration
cat > .env << EOF
ENABLE_AUTH=true
AUTH_USERNAME=admin
AUTH_PASSWORD=$(openssl rand -base64 12)
SESSION_SECRET=$(openssl rand -base64 32)
DEFAULT_THEME=light
NODE_ENV=production
EOF
docker-compose up -d
```
### Development with Custom Theme
```bash
# Override just the theme
DEFAULT_THEME=light docker-compose up -d
```
### Reverse Proxy Setup (nginx)
```bash
docker run -d \
--name mkcert-web-ui \
--network nginx-proxy \
-e "VIRTUAL_HOST=certs.yourdomain.com" \
-e "LETSENCRYPT_HOST=certs.yourdomain.com" \
-v mkcert_certificates:/app/certificates \
jeffcaldwellca/mkcert-web-ui:latest
# For use with nginx-proxy, modify docker-compose.yml or create override:
version: '3.8'
services:
mkcert-web-ui:
extends:
file: docker-compose.yml
service: mkcert-web-ui
networks:
- nginx-proxy
environment:
- VIRTUAL_HOST=certs.yourdomain.com
- LETSENCRYPT_HOST=certs.yourdomain.com
networks:
nginx-proxy:
external: true
```
For more information, see the main [README.md](README.md) file.
+1
View File
@@ -4,6 +4,7 @@ FROM node:18-alpine
# Install mkcert and other required tools
RUN apk add --no-cache \
ca-certificates \
openssl \
wget \
&& wget -O /usr/local/bin/mkcert https://github.com/FiloSottile/mkcert/releases/latest/download/mkcert-v1.4.4-linux-amd64 \
&& chmod +x /usr/local/bin/mkcert
+109
View File
@@ -159,6 +159,16 @@ async function loadSystemStatus() {
try {
const status = await apiRequest('/status');
// Show notification if CA was auto-generated
if (status.autoGenerated) {
showAlert(
'<strong>Root CA Auto-Generated!</strong><br>' +
'A new Root Certificate Authority was automatically created and installed.<br>' +
'<em>Location:</em> ' + (status.caRoot || 'Default location'),
'success'
);
}
// Create status indicators HTML
const statusHtml =
'<div class="status-info">' +
@@ -169,6 +179,7 @@ async function loadSystemStatus() {
'<div class="status-item">' +
'<i class="fas fa-' + (status.caExists ? 'shield-alt' : 'exclamation-triangle') + '"></i>' +
'<span id="ca-status" class="status-indicator ' + (status.caExists ? 'status-success' : 'status-error') + '">Root CA ' + (status.caExists ? 'exists' : 'missing') + '</span>' +
(status.autoGenerated ? '<span class="auto-generated-badge"><i class="fas fa-magic"></i> Auto-generated</span>' : '') +
'</div>' +
'<div class="status-item">' +
'<i class="fas fa-' + (status.opensslAvailable ? 'key' : 'times-circle') + '"></i>' +
@@ -190,6 +201,9 @@ async function loadSystemStatus() {
// Load Root CA information if CA exists
if (status.caExists) {
await loadRootCAInfo();
} else {
// Show manual generation option if auto-generation failed
showManualCAGeneration();
}
} catch (error) {
@@ -275,6 +289,11 @@ async function loadRootCAInfo() {
const rootCAInfo = document.getElementById('rootca-info');
if (rootCAInfo) {
rootCAInfo.innerHTML = rootCAHtml;
// Add highlight effect to show the section was updated
rootCAInfo.classList.add('ca-updated');
setTimeout(() => {
rootCAInfo.classList.remove('ca-updated');
}, 3000);
}
// Show the Root CA section
@@ -568,6 +587,96 @@ async function restoreCertificate(folder, certName) {
}
}
// Show manual CA generation option
function showManualCAGeneration() {
const rootCASection = document.getElementById('rootca-section');
if (rootCASection) {
rootCASection.style.display = 'block';
const rootCAInfo = document.getElementById('rootca-info');
if (rootCAInfo) {
rootCAInfo.innerHTML =
'<div class="ca-missing-info">' +
'<div class="warning-message">' +
'<i class="fas fa-exclamation-triangle"></i>' +
'<h3>Root CA Not Found</h3>' +
'<p>A Root Certificate Authority (CA) is required to generate SSL certificates. You can generate one now.</p>' +
'</div>' +
'<div class="ca-generation-actions">' +
'<button id="generate-ca-btn" class="btn btn-primary">' +
'<i class="fas fa-magic"></i> Generate Root CA' +
'</button>' +
'<div class="ca-info-text">' +
'<p><strong>What this does:</strong></p>' +
'<ul>' +
'<li>Creates a new Root Certificate Authority</li>' +
'<li>Installs it in your system trust store</li>' +
'<li>Enables certificate generation for local development</li>' +
'</ul>' +
'</div>' +
'</div>' +
'</div>';
// Attach event listener for generate CA button
const generateBtn = document.getElementById('generate-ca-btn');
if (generateBtn) {
generateBtn.addEventListener('click', handleGenerateCA);
}
}
}
}
// Handle manual CA generation
async function handleGenerateCA() {
const generateBtn = document.getElementById('generate-ca-btn');
if (generateBtn) {
generateBtn.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Generating Root CA...';
generateBtn.disabled = true;
}
try {
const result = await apiRequest('/generate-ca', {
method: 'POST'
});
if (result.success) {
// Show success notification
showNotification(result.message, 'success');
// Add a small delay to ensure the CA is fully created
setTimeout(async () => {
// Force reload the entire system status and CA information
await loadSystemStatus();
// Show a more detailed success message
showAlert(
'<strong>Root CA Generated Successfully!</strong><br>' +
'Your new Root Certificate Authority is now ready to generate SSL certificates.<br>' +
'<em>CA Root Path:</em> ' + (result.caRoot || 'Default location') +
(result.caInfo && result.caInfo.expiry ? '<br><em>Valid Until:</em> ' + new Date(result.caInfo.expiry).toLocaleDateString() : ''),
'success'
);
// Scroll to the Root CA section to show the new information
const rootCASection = document.getElementById('rootca-section');
if (rootCASection) {
rootCASection.scrollIntoView({ behavior: 'smooth', block: 'start' });
}
}, 1000); // 1 second delay to ensure backend processing is complete
} else {
showAlert('Failed to generate CA: ' + result.error, 'error');
}
} catch (error) {
showAlert('Failed to generate CA: ' + error.message, 'error');
} finally {
if (generateBtn) {
generateBtn.innerHTML = '<i class="fas fa-magic"></i> Generate Root CA';
generateBtn.disabled = false;
}
}
}
// CA Installation
async function handleInstallCA() {
installCaBtn.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Installing...';
+112
View File
@@ -348,6 +348,118 @@ section h2::before {
grid-template-columns: 1fr;
gap: 1rem;
}
.btn-group {
flex-direction: column;
}
.btn-group .btn {
min-width: unset;
}
}
/* CA Missing and Generation Styles */
.ca-missing-info {
text-align: center;
padding: 2rem;
}
.warning-message {
background: var(--card-bg-secondary);
border: 2px solid var(--warning-color);
border-radius: 8px;
padding: 1.5rem;
margin-bottom: 2rem;
}
.warning-message i {
color: var(--warning-color);
font-size: 2rem;
margin-bottom: 1rem;
text-shadow: 0 0 8px rgba(255, 193, 7, 0.4);
}
.warning-message h3 {
color: var(--warning-color);
margin-bottom: 1rem;
font-size: 1.3rem;
}
.warning-message p {
color: var(--text-color);
margin-bottom: 0;
font-size: 1rem;
line-height: 1.5;
}
.ca-generation-actions {
display: flex;
flex-direction: column;
align-items: center;
gap: 1.5rem;
}
.ca-info-text {
max-width: 500px;
text-align: left;
background: var(--card-bg);
border: 1px solid var(--border-color);
border-radius: 6px;
padding: 1.25rem;
}
.ca-info-text p {
margin-bottom: 0.75rem;
color: var(--success-color);
font-weight: 600;
}
.ca-info-text ul {
margin: 0;
padding-left: 1.5rem;
}
.ca-info-text li {
margin-bottom: 0.5rem;
color: var(--text-color);
line-height: 1.4;
}
/* Auto-generated badge */
.auto-generated-badge {
display: inline-block;
background: linear-gradient(135deg, var(--success-color), var(--primary-color));
color: var(--dark-bg);
padding: 0.25rem 0.5rem;
border-radius: 12px;
font-size: 0.75rem;
font-weight: 600;
margin-left: 0.5rem;
text-shadow: none;
box-shadow: 0 0 8px rgba(64, 224, 208, 0.3);
animation: pulse-glow 2s ease-in-out;
}
.auto-generated-badge i {
margin-right: 0.25rem;
}
/* Animation for newly generated CA */
@keyframes pulse-glow {
0% { box-shadow: 0 0 8px rgba(64, 224, 208, 0.3); }
50% { box-shadow: 0 0 20px rgba(64, 224, 208, 0.8); }
100% { box-shadow: 0 0 8px rgba(64, 224, 208, 0.3); }
}
/* Highlight effect for newly updated sections */
.ca-updated {
animation: section-highlight 3s ease-in-out;
}
@keyframes section-highlight {
0% { background: var(--card-bg); }
20% { background: rgba(64, 224, 208, 0.1); border: 1px solid rgba(64, 224, 208, 0.3); }
100% { background: var(--card-bg); }
}
/* Forms */
+107 -2
View File
@@ -220,7 +220,23 @@ app.get('/api/status', requireAuth, async (req, res) => {
const caKeyPath = path.join(caRoot, 'rootCA-key.pem');
const caCertPath = path.join(caRoot, 'rootCA.pem');
const caExists = await fs.pathExists(caKeyPath) && await fs.pathExists(caCertPath);
let caExists = await fs.pathExists(caKeyPath) && await fs.pathExists(caCertPath);
// Auto-generate CA if it doesn't exist
let autoGenerated = false;
if (!caExists) {
try {
console.log('Root CA not found, attempting to generate...');
await executeCommand('mkcert -install');
caExists = await fs.pathExists(caKeyPath) && await fs.pathExists(caCertPath);
autoGenerated = caExists;
if (autoGenerated) {
console.log('Root CA auto-generated successfully');
}
} catch (generateError) {
console.error('Failed to auto-generate Root CA:', generateError.message);
}
}
// Check if OpenSSL is available
let opensslAvailable = false;
@@ -237,7 +253,8 @@ app.get('/api/status', requireAuth, async (req, res) => {
caExists,
caCertPath: caExists ? caCertPath : null,
mkcertInstalled: true,
opensslAvailable
opensslAvailable,
autoGenerated
});
} catch (error) {
res.json({
@@ -266,6 +283,94 @@ app.post('/api/install-ca', requireAuth, async (req, res) => {
}
});
// Generate new Root CA (mkcert -install creates a new CA if one doesn't exist)
app.post('/api/generate-ca', requireAuth, async (req, res) => {
try {
// First check if mkcert is available
try {
await executeCommand('mkcert -help');
} catch (helpError) {
return res.status(500).json({
success: false,
error: 'mkcert is not installed or not available in PATH'
});
}
// Get current CA root directory
let caRoot;
try {
const caRootResult = await executeCommand('mkcert -CAROOT');
caRoot = caRootResult.stdout.trim();
} catch (caRootError) {
return res.status(500).json({
success: false,
error: 'Failed to get mkcert CA root directory'
});
}
// Check if CA already exists
const caKeyPath = path.join(caRoot, 'rootCA-key.pem');
const caCertPath = path.join(caRoot, 'rootCA.pem');
const caExists = await fs.pathExists(caKeyPath) && await fs.pathExists(caCertPath);
if (caExists) {
return res.json({
success: true,
message: 'Root CA already exists',
caRoot,
caExists: true,
action: 'none'
});
}
// Generate new CA by running mkcert -install
// This will create a new CA if one doesn't exist
const installResult = await executeCommand('mkcert -install');
// Verify CA was created
const newCaExists = await fs.pathExists(caKeyPath) && await fs.pathExists(caCertPath);
if (!newCaExists) {
return res.status(500).json({
success: false,
error: 'Failed to generate Root CA - files not found after installation'
});
}
// Get CA information
let caInfo = {};
try {
const caResult = await executeCommand(`openssl x509 -in "${caCertPath}" -noout -subject -issuer -dates`);
caInfo.details = caResult.stdout;
// Extract expiry date
const expiryMatch = caResult.stdout.match(/notAfter=(.+)/);
if (expiryMatch) {
caInfo.expiry = new Date(expiryMatch[1]);
}
} catch (error) {
console.log('Could not read CA info with OpenSSL (this is optional)');
}
res.json({
success: true,
message: 'Root CA generated and installed successfully',
caRoot,
caExists: true,
caInfo,
action: 'generated',
output: installResult.stdout
});
} catch (error) {
res.status(500).json({
success: false,
error: error.error || error.message,
details: error.stderr
});
}
});
// Download Root CA certificate
app.get('/api/download/rootca', requireAuth, async (req, res) => {
try {