nginx-manager.ts•8.45 kB
import { SSHService } from '../services/ssh-service.js';
import { logger } from '../utils/logger.js';
export interface NginxConfig {
domain: string;
port: number;
ssl?: boolean;
}
export interface NginxResult {
success: boolean;
message: string;
}
export class NginxManager {
constructor(private sshService: SSHService) {}
async setupNginx(config: NginxConfig): Promise<NginxResult> {
try {
logger.info('Setting up Nginx configuration', { domain: config.domain, port: config.port });
// Create Nginx configuration
const nginxConfigResult = await this.createNginxConfig(config);
if (!nginxConfigResult.success) {
return nginxConfigResult;
}
// Test and reload Nginx
const testResult = await this.sshService.executeCommand('nginx -t');
if (!testResult.success) {
return {
success: false,
message: `Nginx configuration test failed: ${testResult.stderr}`,
};
}
const reloadResult = await this.sshService.executeCommand('systemctl reload nginx');
if (!reloadResult.success) {
return {
success: false,
message: `Failed to reload Nginx: ${reloadResult.stderr}`,
};
}
// Setup SSL if requested
if (config.ssl) {
const sslResult = await this.setupSSL(config.domain);
if (!sslResult.success) {
return sslResult;
}
}
// Configure firewall
await this.configureFirewall();
return {
success: true,
message: `Nginx configured successfully for ${config.domain}${config.ssl ? ' with SSL' : ''}`,
};
} catch (error) {
logger.error('Nginx setup failed', { error, config });
return {
success: false,
message: `Nginx setup failed: ${error instanceof Error ? error.message : 'Unknown error'}`,
};
}
}
private async createNginxConfig(config: NginxConfig): Promise<NginxResult> {
const nginxConfig = this.generateNginxConfig(config);
const configPath = `/etc/nginx/sites-available/${config.domain}`;
const enabledPath = `/etc/nginx/sites-enabled/${config.domain}`;
try {
// Write configuration file
const writeResult = await this.sshService.executeCommand(
`cat > ${configPath} << 'EOF'\n${nginxConfig}\nEOF`
);
if (!writeResult.success) {
return {
success: false,
message: `Failed to write Nginx config: ${writeResult.stderr}`,
};
}
// Enable site
const linkResult = await this.sshService.executeCommand(
`ln -sf ${configPath} ${enabledPath}`
);
if (!linkResult.success) {
return {
success: false,
message: `Failed to enable Nginx site: ${linkResult.stderr}`,
};
}
// Remove default site if it exists
await this.sshService.executeCommand('rm -f /etc/nginx/sites-enabled/default');
return {
success: true,
message: 'Nginx configuration created successfully',
};
} catch (error) {
return {
success: false,
message: `Failed to create Nginx config: ${error instanceof Error ? error.message : 'Unknown error'}`,
};
}
}
private generateNginxConfig(config: NginxConfig): string {
return `server {
listen 80;
server_name ${config.domain} www.${config.domain};
# Security headers
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Referrer-Policy "no-referrer-when-downgrade" always;
add_header Content-Security-Policy "default-src 'self' http: https: data: blob: 'unsafe-inline'" always;
# Gzip compression
gzip on;
gzip_vary on;
gzip_min_length 1024;
gzip_proxied expired no-cache no-store private must-revalidate auth;
gzip_types text/plain text/css text/xml text/javascript application/javascript application/xml+rss application/json;
location / {
proxy_pass http://127.0.0.1:${config.port};
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_cache_bypass $http_upgrade;
proxy_read_timeout 86400;
}
# Deny access to hidden files
location ~ /\\. {
deny all;
}
# Optimize static file serving
location ~* \\.(jpg|jpeg|png|gif|ico|css|js|woff|woff2|ttf|svg)$ {
expires 1y;
add_header Cache-Control "public, immutable";
try_files $uri @proxy;
}
location @proxy {
proxy_pass http://127.0.0.1:${config.port};
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}`;
}
private async setupSSL(domain: string): Promise<NginxResult> {
try {
logger.info('Setting up SSL with Certbot', { domain });
// Install Certbot
const installResult = await this.sshService.executeCommand(
'DEBIAN_FRONTEND=noninteractive apt install -y certbot python3-certbot-nginx'
);
if (!installResult.success) {
return {
success: false,
message: `Failed to install Certbot: ${installResult.stderr}`,
};
}
// Obtain SSL certificate
const certResult = await this.sshService.executeCommand(
`certbot --nginx -d ${domain} -d www.${domain} --non-interactive --agree-tos --email admin@${domain}`
);
if (!certResult.success) {
// Try without www subdomain
const certResultNoWWW = await this.sshService.executeCommand(
`certbot --nginx -d ${domain} --non-interactive --agree-tos --email admin@${domain}`
);
if (!certResultNoWWW.success) {
return {
success: false,
message: `Failed to obtain SSL certificate: ${certResult.stderr}`,
};
}
}
// Setup auto-renewal
const cronResult = await this.sshService.executeCommand(
'echo "0 12 * * * /usr/bin/certbot renew --quiet" | crontab -'
);
if (!cronResult.success) {
logger.warn('Failed to setup SSL auto-renewal', { error: cronResult.stderr });
}
return {
success: true,
message: 'SSL certificate obtained and configured successfully',
};
} catch (error) {
logger.error('SSL setup failed', { error, domain });
return {
success: false,
message: `SSL setup failed: ${error instanceof Error ? error.message : 'Unknown error'}`,
};
}
}
private async configureFirewall(): Promise<void> {
try {
// Enable UFW and configure basic rules
const commands = [
'ufw --force enable',
'ufw default deny incoming',
'ufw default allow outgoing',
'ufw allow ssh',
'ufw allow "Nginx Full"',
'ufw --force reload',
];
for (const command of commands) {
await this.sshService.executeCommand(command);
}
logger.info('Firewall configured successfully');
} catch (error) {
logger.warn('Firewall configuration failed', { error });
}
}
async removeConfig(domain: string): Promise<NginxResult> {
try {
const commands = [
`rm -f /etc/nginx/sites-available/${domain}`,
`rm -f /etc/nginx/sites-enabled/${domain}`,
'nginx -t',
'systemctl reload nginx',
];
for (const command of commands) {
const result = await this.sshService.executeCommand(command);
if (!result.success && command.includes('nginx -t')) {
return {
success: false,
message: `Nginx configuration test failed after removing ${domain}`,
};
}
}
return {
success: true,
message: `Nginx configuration for ${domain} removed successfully`,
};
} catch (error) {
return {
success: false,
message: `Failed to remove Nginx config: ${error instanceof Error ? error.message : 'Unknown error'}`,
};
}
}
}