Skip to content

Commit

Permalink
Merge pull request #3 from j4ys0n/nginx-config
Browse files Browse the repository at this point in the history
update nginx config handling
  • Loading branch information
j4ys0n authored Jul 12, 2024
2 parents 6c73097 + 5f4d068 commit 8d0b242
Show file tree
Hide file tree
Showing 7 changed files with 182 additions and 39 deletions.
21 changes: 14 additions & 7 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,18 +1,28 @@
# Use an official Node.js runtime as the base image
FROM node:18.20.2-buster
FROM node:18.20.4-bullseye

# Install Nginx
RUN apt-get update && apt-get install -y nginx certbot python3-certbot-nginx

# Create directory for Nginx configuration files
RUN mkdir -p /etc/nginx/conf.d/

# Create directory for Let's Encrypt certificates
RUN mkdir -p /etc/letsencrypt/

# Make sure Nginx has the right permissions to run
RUN chown -R www-data:www-data /var/lib/nginx

# Create app directory
WORKDIR /usr/src/app
WORKDIR /app

# Bundle app source
COPY . .

# Install dependencies & build
RUN yarn && yarn build

# Expose port 80 & 443
# Expose port 8080 & 443
EXPOSE 8080 443

# Remove the default Nginx configuration file
Expand All @@ -21,11 +31,8 @@ RUN rm /etc/nginx/sites-enabled/default
# Copy a custom Nginx configuration file
COPY nginx.conf /etc/nginx/nginx.conf

# Make sure Nginx has the right permissions to run
RUN chown -R www-data:www-data /var/lib/nginx

# Create directory for Let's Encrypt certificates
RUN mkdir -p /etc/letsencrypt


# Start Node.js app
CMD ["node", "dist/index.js"]
8 changes: 7 additions & 1 deletion docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,19 @@ version: '3.6'

services:
llmp:
image: ghcr.io/j4ys0n/llm-proxy:1.0.0
image: ghcr.io/j4ys0n/llm-proxy:1.1.0
container_name: llmp
hostname: llmp
restart: unless-stopped
ports:
- 8080:8080
- 443:443
volumes:
- .env:/app/.env
- ./data:/app/data
- ./cloudflare_credentials:/opt/cloudflare/credentials
- ./nginx:/etc/nginx/conf.d # nginx configs
- ./certs:/etc/letsencrypt # tsl certificates
logging:
driver: 'json-file'
options:
Expand Down
31 changes: 17 additions & 14 deletions nginx.conf
Original file line number Diff line number Diff line change
Expand Up @@ -9,30 +9,33 @@ events {

http {
sendfile on;
tcp_nopush on;
# tcp_nopush on;
types_hash_max_size 2048;
include /etc/nginx/mime.types;
default_type application/octet-stream;

ssl_protocols TLSv1 TLSv1.1 TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers on;

keepalive_timeout 65;

access_log /var/log/nginx/access.log;
error_log /var/log/nginx/error.log;

gzip on;

server {
listen 80;
server_name localhost;

location / {
proxy_pass http://localhost:3000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade;
}
}
include /etc/nginx/conf.d/*.conf;
# server {
# listen 80;
# server_name localhost;

# location / {
# proxy_pass http://localhost:3000;
# proxy_http_version 1.1;
# proxy_set_header Upgrade $http_upgrade;
# proxy_set_header Connection 'upgrade';
# proxy_set_header Host $host;
# proxy_cache_bypass $http_upgrade;
# }
# }
}
16 changes: 11 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "llm-proxy",
"version": "1.0.2",
"description": "A Node.js application that manages Nginx",
"version": "1.1.0",
"description": "Manages Nginx for reverse proxy to multiple LLMs, with TLS & Bearer Auth tokens",
"main": "dist/index.js",
"scripts": {
"start": "node dist/index.js",
Expand All @@ -12,10 +12,16 @@
"keywords": [
"node",
"nginx",
"typescript"
"typescript",
"reverse proxy",
"llm",
"openai",
"certificate",
"bearer auth",
"tls"
],
"author": "",
"license": "ISC",
"author": "Jayson Jacobs",
"license": "Apache-2.0",
"dependencies": {
"axios": "^1.7.2",
"dotenv": "^10.0.0",
Expand Down
28 changes: 24 additions & 4 deletions src/controllers/nginx.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,12 @@ export class NginxController {

public registerRoutes(): void {
this.app.post('/nginx/reload', ...this.requestHandlers, this.reloadNginx.bind(this))
this.app.post('/nginx/update-config', ...this.requestHandlers, this.updateConfig.bind(this))
this.app.get('/nginx/config', ...this.requestHandlers, this.getConfig.bind(this))
this.app.get('/nginx/obtain-certificates', ...this.requestHandlers, this.obtainCertificates.bind(this))
this.app.get('/nginx/renew-certificates', ...this.requestHandlers, this.renewCertificates.bind(this))
this.app.post('/nginx/config/update', ...this.requestHandlers, this.updateConfig.bind(this))
this.app.get('/nginx/config/get', ...this.requestHandlers, this.getConfig.bind(this))
this.app.get('/nginx/config/get-default', ...this.requestHandlers, this.getDefaultConfig.bind(this))
this.app.get('/nginx/config/write-default', ...this.requestHandlers, this.writeDefaultConfig.bind(this))
this.app.get('/nginx/certificates/obtain', ...this.requestHandlers, this.obtainCertificates.bind(this))
this.app.get('/nginx/certificates/renew', ...this.requestHandlers, this.renewCertificates.bind(this))
log('info', 'NginxController initialized')
}

Expand All @@ -43,6 +45,24 @@ export class NginxController {
res.status(status).json({ success, message, config })
}

private async getDefaultConfig(req: Request, res: Response): Promise<void> {
const { success, message, config } = await this.nginxManager.getTemplate()
if (success && config) {
res.json({ success, config })
} else {
res.status(500).json({ success, message })
}
}

private async writeDefaultConfig(req: Request, res: Response): Promise<void> {
const { success, message } = await this.nginxManager.writeDefaultTemplate()
if (success) {
res.json({ success, message: 'Default config written successfully' })
} else {
res.status(500).json({ success, message })
}
}

private async obtainCertificates(req: Request, res: Response): Promise<void> {
const { success, message } = await this.nginxManager.obtainCertificates(true)
const status = success ? 200 : 500
Expand Down
80 changes: 80 additions & 0 deletions src/static/nginx-server-template.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
server {
listen 443 ssl;

add_header 'Access-Control-Allow-Origin' '*';
add_header 'Add-Control-Allow-Methods' 'GET, POST';

server_name {{ domainName }};
server_tokens off;

real_ip_header X-Forwarded-For;

ssl_certificate /etc/letsencrypt/live/{{ domainName }}/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/{{ domainName }}/privkey.pem;
# include /etc/letsencrypt/options-ssl-nginx.conf;
# ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;

location ~ ^/v1/(.*) {
proxy_pass_header Authorization;
proxy_pass http://localhost:8080;
proxy_redirect off;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade;
proxy_buffering off;
client_max_body_size 0;
proxy_read_timeout 36000s;
}

location ~ /(auth|nginx)/(.*) {
proxy_pass_header Authorization;
proxy_pass http://localhost:8080;
proxy_redirect off;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade;
proxy_buffering off;
client_max_body_size 0;
proxy_read_timeout 36000s;
allow 10.1.0.0/16;
allow 10.6.0.0/24;
allow 10.9.9.0/24;
allow 10.99.10.0/24;
deny all;
}

# location ~ (.*) {
# proxy_pass_header Authorization;
# proxy_pass http://localhost:8080;
# proxy_redirect off;
# proxy_set_header X-Real-IP $remote_addr;
# proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
# proxy_http_version 1.1;
# proxy_set_header Upgrade $http_upgrade;
# proxy_set_header Connection 'upgrade';
# proxy_set_header Host $host;
# proxy_cache_bypass $http_upgrade;
# proxy_buffering off;
# client_max_body_size 0;
# proxy_read_timeout 36000s;
# }
location ~ (.*) {
return 404;
}
}

server {
listen 443 ssl;

server_name ${public_ip};
server_tokens off;
return 444; # "Connection closed without response"
}
37 changes: 29 additions & 8 deletions src/utils/nginx.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { exec } from 'child_process'
import { readFile, writeFile } from 'fs-extra'
import { join } from 'path'
import { promisify } from 'util'
import { log } from './general'

Expand All @@ -16,11 +17,13 @@ export interface NginxConfigResponse {
message?: string
}

const CONFIG_TEMPLATE_PATH = join(__dirname, '/src/static/nginx-server-template.conf')

export class NginxManager {
private configPath: string
private domains: string[]

constructor(configPath: string = '/etc/nginx/nginx.conf', domains: string[] = []) {
constructor(configPath: string = '/etc/nginx/conf.d/server.conf', domains: string[] = []) {
this.configPath = configPath
this.domains = domains
}
Expand Down Expand Up @@ -56,41 +59,59 @@ export class NginxManager {
}

async updateConfig(newConfig: string): Promise<NginxResponse> {
return this.putFile(this.configPath, newConfig)
}

async writeDefaultTemplate(): Promise<NginxResponse> {
const templateContent = await readFile(CONFIG_TEMPLATE_PATH, 'utf-8')
return this.putFile(this.configPath, templateContent)
}

async getConfig(): Promise<NginxConfigResponse> {
return this.getFile(this.configPath)
}

async getTemplate(): Promise<NginxConfigResponse> {
return this.getFile(CONFIG_TEMPLATE_PATH)
}

async putFile(path: string, content: string): Promise<NginxResponse> {
let message: string | undefined
let success = false
try {
await writeFile(this.configPath, newConfig)
await writeFile(path, content)
message = 'Nginx configuration updated.'
success = true
log('info', message)
await this.reload()
} catch (error) {
log('error', 'Failed to update Nginx configuration.', error)
log('error', `Failed to update Nginx configuration. ${path}`, error)
message = error != null ? (error as any).message ?? 'Error' : 'Unknown error'
}
return { success, message }
}

async getConfig(): Promise<NginxConfigResponse> {
private async getFile(path: string): Promise<NginxConfigResponse> {
let config: string = ''
let message: string | undefined
let success = false
try {
config = await readFile(this.configPath, 'utf-8')
config = await readFile(path, 'utf-8')
success = true
} catch (error) {
log('error', 'Failed to read Nginx configuration.', error)
log('error', `Failed to read configuration file. ${path}`, error)
message = error != null ? (error as any).message ?? 'Error' : 'Unknown error'
}
return { success, config, message }
}

async obtainCertificates(cloudflare?: boolean): Promise<NginxResponse> {
const domainArgs = this.domains.map(domain => `-d ${domain}`).join(' ')
const domainArgs = this.domains.map((domain) => `-d ${domain}`).join(' ')
let message: string | undefined
let success = false
const cloudflareFlags = cloudflare
? ' --dns-cloudflare --dns-cloudflare-credentials /opt/cloudflare/credentials' : ''
? ' --dns-cloudflare --dns-cloudflare-credentials /opt/cloudflare/credentials'
: ''
const command = `certbot --nginx -n --agree-tos --issuance-timeout 120 ${cloudflareFlags}${domainArgs} --preferred-challenges dns-01`
try {
await execAsync(command)
Expand Down

0 comments on commit 8d0b242

Please sign in to comment.