Initial LisgloSIPS V2 implementation
This commit is contained in:
+4
@@ -0,0 +1,4 @@
|
||||
LISGLOSIPS_ENTRYPOINT=/opt/lisglosips/current/server.mjs
|
||||
HOST=127.0.0.1
|
||||
PORT=3000
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
server_tokens off;
|
||||
|
||||
limit_req_zone $binary_remote_addr zone=lisglosips_api_per_ip:10m rate=20r/s;
|
||||
limit_req_zone $binary_remote_addr zone=lisglosips_auth_per_ip:10m rate=5r/m;
|
||||
limit_conn_zone $binary_remote_addr zone=lisglosips_conn_per_ip:10m;
|
||||
limit_req_status 429;
|
||||
limit_conn_status 429;
|
||||
|
||||
map $http_upgrade $lisglosips_connection_upgrade {
|
||||
default upgrade;
|
||||
'' close;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
proxy_http_version 1.1;
|
||||
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_set_header X-Request-ID $request_id;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection $lisglosips_connection_upgrade;
|
||||
proxy_connect_timeout 3s;
|
||||
proxy_send_timeout 30s;
|
||||
proxy_read_timeout 30s;
|
||||
proxy_buffering on;
|
||||
proxy_buffer_size 8k;
|
||||
proxy_buffers 8 16k;
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
add_header Strict-Transport-Security "max-age=300" always;
|
||||
add_header X-Content-Type-Options "nosniff" always;
|
||||
add_header X-Frame-Options "DENY" always;
|
||||
add_header Referrer-Policy "same-origin" always;
|
||||
add_header Permissions-Policy "camera=(), microphone=(), geolocation=()" always;
|
||||
add_header Content-Security-Policy "default-src 'self'; base-uri 'self'; frame-ancestors 'none'; form-action 'self'; object-src 'none'" always;
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
ssl_certificate /etc/lisglosips/pki/certs/server.crt;
|
||||
ssl_certificate_key /etc/lisglosips/pki/private/server.key;
|
||||
ssl_protocols TLSv1.2 TLSv1.3;
|
||||
ssl_prefer_server_ciphers off;
|
||||
ssl_session_cache shared:LisgloSIPSTLS:10m;
|
||||
ssl_session_timeout 1d;
|
||||
ssl_session_tickets off;
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
upstream lisglosips_api {
|
||||
server 127.0.0.1:3000 max_fails=3 fail_timeout=5s;
|
||||
keepalive 32;
|
||||
}
|
||||
|
||||
server {
|
||||
listen 80 default_server;
|
||||
listen [::]:80 default_server;
|
||||
server_name _;
|
||||
return 308 https://$host$request_uri;
|
||||
}
|
||||
|
||||
server {
|
||||
listen 443 ssl default_server;
|
||||
listen [::]:443 ssl default_server;
|
||||
server_name _;
|
||||
|
||||
include /etc/nginx/snippets/lisglosips-tls.conf;
|
||||
|
||||
root /opt/lisglosips/current/public;
|
||||
index index.html;
|
||||
client_max_body_size 2m;
|
||||
max_ranges 1;
|
||||
|
||||
include /etc/nginx/snippets/lisglosips-security-headers.conf;
|
||||
|
||||
limit_conn lisglosips_conn_per_ip 30;
|
||||
|
||||
location = /healthz {
|
||||
include /etc/nginx/snippets/lisglosips-proxy.conf;
|
||||
proxy_pass http://lisglosips_api;
|
||||
access_log off;
|
||||
}
|
||||
|
||||
location = /api/auth/login {
|
||||
include /etc/nginx/snippets/lisglosips-proxy.conf;
|
||||
limit_req zone=lisglosips_auth_per_ip burst=3 nodelay;
|
||||
proxy_pass http://lisglosips_api;
|
||||
}
|
||||
|
||||
location /api/ {
|
||||
include /etc/nginx/snippets/lisglosips-proxy.conf;
|
||||
limit_req zone=lisglosips_api_per_ip burst=40 nodelay;
|
||||
proxy_pass http://lisglosips_api;
|
||||
}
|
||||
|
||||
location /_recordings/ {
|
||||
internal;
|
||||
alias /data/recordings/;
|
||||
autoindex off;
|
||||
include /etc/nginx/snippets/lisglosips-security-headers.conf;
|
||||
add_header Accept-Ranges bytes always;
|
||||
}
|
||||
|
||||
location /assets/ {
|
||||
try_files $uri =404;
|
||||
expires 7d;
|
||||
include /etc/nginx/snippets/lisglosips-security-headers.conf;
|
||||
add_header Cache-Control "public, immutable";
|
||||
}
|
||||
|
||||
location / {
|
||||
try_files $uri $uri/ /index.html;
|
||||
expires -1;
|
||||
include /etc/nginx/snippets/lisglosips-security-headers.conf;
|
||||
add_header Cache-Control "no-store";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
<!doctype html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>LisgloSIPS</title>
|
||||
<style>
|
||||
:root { color-scheme: light; font-family: Arial, sans-serif; background: #f5f6f8; color: #12121a; }
|
||||
body { margin: 0; min-height: 100vh; display: grid; place-items: center; }
|
||||
main { width: min(560px, calc(100% - 48px)); border-top: 4px solid #d9c3a0; padding: 32px 0; }
|
||||
h1 { margin: 0 0 12px; font-size: 32px; letter-spacing: 0; }
|
||||
p { margin: 0; color: #5d606b; line-height: 1.7; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<main>
|
||||
<h1>LisgloSIPS</h1>
|
||||
<p>聆界SIP管理平台服务入口已就绪。</p>
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
import { createServer } from 'node:http';
|
||||
|
||||
const host = process.env.HOST || '127.0.0.1';
|
||||
const port = Number.parseInt(process.env.PORT || '3000', 10);
|
||||
|
||||
const server = createServer((request, response) => {
|
||||
response.setHeader('Content-Type', 'application/json; charset=utf-8');
|
||||
response.setHeader('Cache-Control', 'no-store');
|
||||
response.setHeader('X-Content-Type-Options', 'nosniff');
|
||||
|
||||
if (request.method === 'GET' && (request.url === '/healthz' || request.url === '/api/health')) {
|
||||
response.writeHead(200);
|
||||
response.end(JSON.stringify({ status: 'ok', service: 'lisglosips-api-placeholder' }));
|
||||
return;
|
||||
}
|
||||
|
||||
response.writeHead(404);
|
||||
response.end(JSON.stringify({ status: 'not_found' }));
|
||||
});
|
||||
|
||||
server.listen(port, host);
|
||||
|
||||
function shutdown() {
|
||||
server.close((error) => process.exit(error ? 1 : 0));
|
||||
setTimeout(() => process.exit(1), 10_000).unref();
|
||||
}
|
||||
|
||||
process.on('SIGTERM', shutdown);
|
||||
process.on('SIGINT', shutdown);
|
||||
|
||||
+72
@@ -0,0 +1,72 @@
|
||||
#!/bin/sh
|
||||
set -eu
|
||||
|
||||
PKI_ROOT=/etc/lisglosips/pki
|
||||
CA_DIR="$PKI_ROOT/ca"
|
||||
CERT_DIR="$PKI_ROOT/certs"
|
||||
PRIVATE_DIR="$PKI_ROOT/private"
|
||||
HOST_NAME=${HOST_NAME:-yanzi}
|
||||
TAILSCALE_IP=${TAILSCALE_IP:-100.90.90.91}
|
||||
|
||||
umask 077
|
||||
install -d -m 0700 "$CA_DIR"
|
||||
install -d -m 0755 "$CERT_DIR"
|
||||
install -d -m 0700 "$PRIVATE_DIR"
|
||||
|
||||
if [ ! -s "$CA_DIR/lisglosips-dev-ca.key" ] || [ ! -s "$CA_DIR/lisglosips-dev-ca.crt" ]; then
|
||||
openssl req -x509 -newkey rsa:4096 -sha256 -days 3650 -nodes \
|
||||
-subj '/CN=LisgloSIPS Development CA/O=LisgloSIPS' \
|
||||
-keyout "$CA_DIR/lisglosips-dev-ca.key" \
|
||||
-out "$CA_DIR/lisglosips-dev-ca.crt"
|
||||
fi
|
||||
|
||||
cat >"$PKI_ROOT/server-cert.cnf" <<EOF
|
||||
[req]
|
||||
prompt = no
|
||||
distinguished_name = dn
|
||||
req_extensions = req_ext
|
||||
|
||||
[dn]
|
||||
CN = lisglosips.local
|
||||
O = LisgloSIPS
|
||||
|
||||
[req_ext]
|
||||
subjectAltName = @alt_names
|
||||
|
||||
[alt_names]
|
||||
DNS.1 = lisglosips.local
|
||||
DNS.2 = $HOST_NAME
|
||||
DNS.3 = grafana.lisglosips.local
|
||||
DNS.4 = homer.lisglosips.local
|
||||
IP.1 = $TAILSCALE_IP
|
||||
IP.2 = 127.0.0.1
|
||||
|
||||
[server_ext]
|
||||
basicConstraints = critical,CA:FALSE
|
||||
keyUsage = critical,digitalSignature,keyEncipherment
|
||||
extendedKeyUsage = serverAuth
|
||||
subjectAltName = @alt_names
|
||||
EOF
|
||||
|
||||
openssl req -new -newkey rsa:3072 -nodes -sha256 \
|
||||
-config "$PKI_ROOT/server-cert.cnf" \
|
||||
-keyout "$PRIVATE_DIR/server.key" \
|
||||
-out "$PKI_ROOT/server.csr"
|
||||
|
||||
openssl x509 -req -sha256 -days 397 \
|
||||
-in "$PKI_ROOT/server.csr" \
|
||||
-CA "$CA_DIR/lisglosips-dev-ca.crt" \
|
||||
-CAkey "$CA_DIR/lisglosips-dev-ca.key" \
|
||||
-CAcreateserial \
|
||||
-extfile "$PKI_ROOT/server-cert.cnf" \
|
||||
-extensions server_ext \
|
||||
-out "$CERT_DIR/server.crt"
|
||||
|
||||
chown root:root "$CA_DIR/lisglosips-dev-ca.key" "$PKI_ROOT/server.csr" "$PKI_ROOT/server-cert.cnf"
|
||||
chown root:www-data "$PRIVATE_DIR/server.key"
|
||||
chown root:root "$CA_DIR/lisglosips-dev-ca.crt" "$CERT_DIR/server.crt"
|
||||
chmod 0600 "$CA_DIR/lisglosips-dev-ca.key" "$PKI_ROOT/server.csr" "$PKI_ROOT/server-cert.cnf"
|
||||
chmod 0640 "$PRIVATE_DIR/server.key"
|
||||
chmod 0644 "$CA_DIR/lisglosips-dev-ca.crt" "$CERT_DIR/server.crt"
|
||||
|
||||
openssl verify -CAfile "$CA_DIR/lisglosips-dev-ca.crt" "$CERT_DIR/server.crt"
|
||||
@@ -0,0 +1,42 @@
|
||||
[Unit]
|
||||
Description=LisgloSIPS %i service
|
||||
After=network-online.target mysql.service redis-server.service
|
||||
Wants=network-online.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=lisglosips
|
||||
Group=lisglosips
|
||||
WorkingDirectory=/opt/lisglosips/current
|
||||
Environment=NODE_ENV=production
|
||||
EnvironmentFile=/etc/lisglosips/%i.env
|
||||
ExecStart=/usr/bin/node ${LISGLOSIPS_ENTRYPOINT}
|
||||
Restart=on-failure
|
||||
RestartSec=3s
|
||||
TimeoutStartSec=30s
|
||||
TimeoutStopSec=30s
|
||||
KillSignal=SIGTERM
|
||||
UMask=0027
|
||||
|
||||
NoNewPrivileges=true
|
||||
PrivateTmp=true
|
||||
PrivateDevices=true
|
||||
ProtectSystem=strict
|
||||
ProtectHome=true
|
||||
ProtectKernelTunables=true
|
||||
ProtectKernelModules=true
|
||||
ProtectControlGroups=true
|
||||
ProtectClock=true
|
||||
ProtectHostname=true
|
||||
RestrictSUIDSGID=true
|
||||
RestrictRealtime=true
|
||||
LockPersonality=true
|
||||
CapabilityBoundingSet=
|
||||
AmbientCapabilities=
|
||||
RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6
|
||||
StateDirectory=lisglosips
|
||||
RuntimeDirectory=lisglosips
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
NODEJS_VERSION=22.22.2-1nodesource1
|
||||
NODEJS_RUNTIME=v22.22.2
|
||||
PNPM_VERSION=10.33.0
|
||||
COREPACK_VERSION=0.34.6
|
||||
NGINX_VERSION=1.24.0-2ubuntu7.12
|
||||
|
||||
Reference in New Issue
Block a user