Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

👷 build: optimize image size under glibc env #4025

Merged
merged 37 commits into from
Sep 27, 2024
Merged
Changes from 1 commit
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
3ecc15c
👷 build: optimize image size under `glibc` env
hezhijie0327 Sep 19, 2024
35ecda3
Merge branch 'main' into dockerfile
hezhijie0327 Sep 19, 2024
1641538
👷 build: add `startServer.js` as server launcher
hezhijie0327 Sep 19, 2024
f5419df
🐛 fix: `PROXY_URL` missing
hezhijie0327 Sep 19, 2024
63d837b
🔨 chore: exit if DB migration failed
hezhijie0327 Sep 19, 2024
0ca15f9
🔨 chore: allow resolve ipv6 address
hezhijie0327 Sep 19, 2024
28f1823
🔨 chore: rollback changes, cleanup code
hezhijie0327 Sep 19, 2024
44d09b8
🔨 chore: improve console log
hezhijie0327 Sep 19, 2024
fb2434b
🔨 chore: improve error print
hezhijie0327 Sep 19, 2024
0e914da
👷 build: add `isValidSSL` function to check SSL cert
hezhijie0327 Sep 19, 2024
ee775ce
🔨 chore: handle `CERT_HAS_EXPIRED` error
hezhijie0327 Sep 19, 2024
1e2e682
🔨 chore: cleanup code
hezhijie0327 Sep 19, 2024
48c9bee
🔨 chore: improve console log
hezhijie0327 Sep 19, 2024
de68ab4
👷 build: check oss & auth issuer ssl connection before running
hezhijie0327 Sep 19, 2024
513dd48
🔨 chore: improve console log
hezhijie0327 Sep 19, 2024
abe57cb
🔨 chore: change `SSL` to `TLS`
hezhijie0327 Sep 19, 2024
5d6559a
🐛 fix: fix `443` port not display in logs
hezhijie0327 Sep 19, 2024
c9aebf7
🔨 chore: improve console output
hezhijie0327 Sep 19, 2024
a640258
Merge branch 'main' into dockerfile
hezhijie0327 Sep 19, 2024
73a6232
🐛 fix: fix error catch
hezhijie0327 Sep 19, 2024
83fa139
🔨 chore: handle corner case
hezhijie0327 Sep 19, 2024
e5b774a
👷 build: support self-signed SSL cert, switch to system-wide CA cert
hezhijie0327 Sep 19, 2024
ddd37ce
🔨 chore: handle `UNABLE_TO_GET_ISSUER_CERT_LOCALLY` error
hezhijie0327 Sep 20, 2024
7bd0a95
🔨 chore: handle when `*_ISSUER` not existed
hezhijie0327 Sep 20, 2024
689b7e9
🔨 chore: handle non-https protocol, skip TLS checking
hezhijie0327 Sep 20, 2024
5fcf2d9
🔨 chore: improve console log
hezhijie0327 Sep 20, 2024
fac469d
🐛 fix: fix proxychains logs not available when host is ip addr
hezhijie0327 Sep 20, 2024
3e1a94c
👷 build: add DNS server self-check support, split DNS resolve function
hezhijie0327 Sep 22, 2024
f5f1858
🔨 chore: improve console.log
hezhijie0327 Sep 22, 2024
dc202ee
🔨 chore: print DNS server before db migration
hezhijie0327 Sep 22, 2024
32800ea
🔨 chore: update `isValidIP` & `resolveHostIP` function, ready for IPv6
hezhijie0327 Sep 22, 2024
d4b1d72
🐛 fix: fix error handle
hezhijie0327 Sep 22, 2024
e89929c
Merge branch 'main' into dockerfile
arvinxx Sep 24, 2024
dc8334a
Merge branch 'lobehub:main' into dockerfile
hezhijie0327 Sep 25, 2024
9e80f40
👷 build: set `ENTRYPOINT` to `/bin/node`
hezhijie0327 Sep 25, 2024
cdf934f
👷 build: set full path for `proxychains` & `node`, ready for distroless
hezhijie0327 Sep 25, 2024
deb3ea8
👷 build: pin node LTS version to 20
hezhijie0327 Sep 26, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
👷 build: add DNS server self-check support, split DNS resolve function
  • Loading branch information
hezhijie0327 committed Sep 22, 2024
commit 3e1a94c0bb6c725d6f2e7be7a48dd55360987da9
239 changes: 103 additions & 136 deletions scripts/serverLauncher/startServer.js
Original file line number Diff line number Diff line change
@@ -1,148 +1,113 @@
const dns = require('dns').promises;
const fs = require('fs');
const tls = require('tls');

const { spawn } = require('child_process');

// Set file paths
const DB_MIGRATION_SCRIPT_PATH = '/app/docker.cjs';
const SERVER_SCRIPT_PATH = '/app/server.js';

const PROXYCHAINS_CONF_PATH = '/etc/proxychains4.conf';

// Read proxy URL from environment variable
const PROXY_URL = process.env.PROXY_URL;

// Function to check if a string is a valid IP address
function isValidIP(ip) {
const IP_REGEX = /^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)(\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/;
return IP_REGEX.test(ip);
}
const isValidIP = (ip) => /^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)(\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/.test(ip);

// Function to check if a URL using a valid TLS certificate
function isValidTLS(url) {
// Function to check TLS validity of a URL
const isValidTLS = (url = '') => {
if (!url) {
console.log('⚠️ TLS Check: No URL provided. Skipping TLS check. Please ensure your ENV has been set up correctly.');
console.log('-------------------------------------');
return Promise.resolve(); // Return a resolved promise to keep the function's return type consistent
return Promise.resolve();
}

let { protocol, host, port } = parseUrl(url);
port = port || 443;

if (protocol !== "https") {
console.log(`⚠️ TLS Check: Protocol is not HTTPS (${protocol}). Skipping TLS check for ${url}.`);
const { protocol, host, port } = parseUrl(url);
if (protocol !== 'https') {
console.log(`⚠️ TLS Check: Non-HTTPS protocol (${protocol}). Skipping TLS check for ${url}.`);
console.log('-------------------------------------');
return Promise.resolve(); // Skip if the protocol is not HTTPS
return Promise.resolve();
}

const options = {
host: host,
port: Number( port ),
servername: host
};

const options = { host, port, servername: host };
return new Promise((resolve, reject) => {
const socket = tls.connect(options, () => {
if (socket.authorized) {
console.log(`✅ TLS Check: Certificate for ${host}:${port} is valid.`);
console.log(`✅ TLS Check: Valid certificate for ${host}:${port}.`);
console.log('-------------------------------------');
resolve();
}

socket.end();
});

socket.on('error', (err) => {
if (err.code === 'DEPTH_ZERO_SELF_SIGNED_CERT' || err.code === 'CERT_HAS_EXPIRED') {
console.error(`❌ TLS Check: Certificate for ${host}:${port} is not valid. You can set NODE_TLS_REJECT_UNAUTHORIZED="0" or map /etc/ssl/certs/ca-certificates.crt to your Docker container. Error details:`);
} else if (err.code === 'UNABLE_TO_GET_ISSUER_CERT_LOCALLY') {
console.error(`❌ TLS Check: Unable to verify the issuer of the certificate for ${host}:${port}. Please ensure /etc/ssl/certs/ca-certificates.crt is correctly mapped in your Docker container. Error details:`);
} else {
console.error(`❌ TLS Check: Unable to connect ${host}:${port}. Please check your network connection or firewall rule. Error details:`);
const errMsg = `❌ TLS Check: Error for ${host}:${port}. Details:`;
switch (err.code) {
case 'CERT_HAS_EXPIRED':
case 'DEPTH_ZERO_SELF_SIGNED_CERT':
console.error(`${errMsg} Certificate is not valid. Consider setting NODE_TLS_REJECT_UNAUTHORIZED="0" or mapping /etc/ssl/certs/ca-certificates.crt.`);
break;
case 'UNABLE_TO_GET_ISSUER_CERT_LOCALLY':
console.error(`${errMsg} Unable to verify issuer. Ensure correct mapping of /etc/ssl/certs/ca-certificates.crt.`);
break;
default:
console.error(`${errMsg} Network issue. Check firewall or DNS.`);
break;
}
reject(err);
});
});
}

// Function to get env vars by keyword
function getEnvVarsByKeyword(keyword) {
const value = Object.keys(process.env)
.filter(key => key.includes(keyword) && process.env[key]) // filter by keywords & exclude empty value
.map(key => process.env[key]) // get matched keys
.shift(); // get the first value

return value || null;
}
};

// Function to check TLS connections for OSS and Auth Issuer
const checkTLSConnections = async () => {
await Promise.all([
isValidTLS(process.env.S3_ENDPOINT),
isValidTLS(process.env.S3_PUBLIC_DOMAIN),
isValidTLS(getEnvVarsByKeyword('_ISSUER')),
]);
};

// Function to get environment variable by keyword
const getEnvVarsByKeyword = (keyword) => {
return Object.entries(process.env)
.filter(([key, value]) => key.includes(keyword) && value)
.map(([, value]) => value)[0] || null;
};

// Function to parse protocol, host and port from a URL
function parseUrl(url) {
const urlObj = new URL(url);
return {
protocol: urlObj.protocol.replace(':', ''),
host: urlObj.hostname,
port: urlObj.port
};
}

// Function to run the DB Migration script
async function runDBMigrationScript() {
return new Promise((resolve, reject) => {
const server = spawn('node', [DB_MIGRATION_SCRIPT_PATH], { stdio: 'inherit' });
const parseUrl = (url) => {
const { protocol, hostname: host, port } = new URL(url);
return { protocol: protocol.replace(':', ''), host, port: port || 443 };
};

// Function to resolve host IP via DNS
const resolveHostIP = async (host) => {
try {
const { address } = await dns.lookup(host, { family: 4 });
if (!isValidIP(address)) console.error(`❌ DNS Error: Invalid resolved IP: ${address}`);
return address;
} catch (err) {
console.error(`❌ DNS Error: Could not resolve ${host}.`, err);
process.exit(1);
}
};

server.on('close', (code) => {
if (code !== 0) {
reject();
} else {
resolve();
}
});
});
}

// Function to run OSS connection checker
async function runOSSConnChecker() {
isValidTLS(process.env.S3_ENDPOINT);
isValidTLS(process.env.S3_PUBLIC_DOMAIN);
}

// Function to run auth issuer connection checker
async function runAuthIssuerConnChecker() {
isValidTLS(getEnvVarsByKeyword("_ISSUER"));
}

// Function to run ProxyChains conf generator
async function runProxyChainsConfGenerator(url) {
// Parse the proxy URL
// Function to generate proxychains configuration
const runProxyChainsConfGenerator = async (url) => {
const { protocol, host, port } = parseUrl(url);

// assume host is an IP address
let ip = host

// If the host is not an IP, resolve it using DNS
if (!isValidIP(host)) {
try {
const result = await dns.lookup(host, { family: 4 });
if (!['http', 'socks4', 'socks5'].includes(protocol)) {
console.error(`❌ ProxyChains: Invalid protocol (${protocol}). Only supported 'http', 'socks4' and 'socks5'.`);
process.exit(1);
}

if (isValidIP(result.address)) {
ip = result.address;
} else {
console.error(`❌ ProxyChains: The host "${host}" resolved to an address "${result.address}", but it is not a valid IPv4 address. Please check your proxy server.`);
process.exit(1);
}
} catch (error) {
console.error(`❌ ProxyChains: Unable to resolve the host "${host}". Please check your DNS configuration. Error details:`);
console.error(error);
process.exit(1);
}
const validPort = parseInt(port, 10);
if (isNaN(validPort) || validPort <= 0 || validPort > 65535) {
console.error(`❌ ProxyChains: Invalid port (${port}). Port must be a number between 1 and 65535.`);
process.exit(1);
}

console.log(`✅ ProxyChains: All outgoing traffic is now routed via ${protocol}://${ip}:${port}.`);
console.log('-------------------------------------');
let ip = isValidIP(host) ? host : await resolveHostIP(host);

// Generate the proxychains configuration file
const proxyChainsConfig = `
const configContent = `
localnet 127.0.0.0/255.0.0.0
localnet ::1/128
proxy_dns
Expand All @@ -152,48 +117,50 @@ tcp_connect_time_out 8000
tcp_read_time_out 15000
[ProxyList]
${protocol} ${ip} ${port}
`;
`.trim();

fs.writeFileSync(PROXYCHAINS_CONF_PATH, configContent);
console.log(`✅ ProxyChains: All outgoing traffic routed via ${protocol}://${ip}:${port}.`);
console.log('-------------------------------------');
};

// Function to execute a script with child process spawn
const runScript = (scriptPath, useProxy = false) => {
const command = useProxy ? ['proxychains', '-q', 'node', scriptPath] : ['node', scriptPath];
return new Promise((resolve, reject) => {
const process = spawn(command.shift(), command, { stdio: 'inherit' });
process.on('close', (code) => (code === 0 ? resolve() : reject(new Error(`Process exited with code ${code}`))));
});
};

// Write configuration to the specified path
fs.writeFileSync(PROXYCHAINS_CONF_PATH, proxyChainsConfig.trim());
}
// Main function to run the server with optional proxy
const runServer = async () => {
console.log('🚀 Starting server...');
console.log('-------------------------------------');
console.log('🌐 DNS Server:', dns.getServers());
console.log('-------------------------------------');

// Function to run the server
async function runServer() {
let server
const PROXY_URL = process.env.PROXY_URL || ''; // Default empty string to avoid undefined errors

if (PROXY_URL) {
// Run ProxyChain Conf Generator first
await runProxyChainsConfGenerator(PROXY_URL);

// Run the server using proxychains
server = spawn('proxychains', ['-q', 'node', SERVER_SCRIPT_PATH], { stdio: 'inherit' });
} else {
// No proxy, run the server directly
server = spawn('node', [SERVER_SCRIPT_PATH], { stdio: 'inherit' });
return runScript(SERVER_SCRIPT_PATH, true);
}
return runScript(SERVER_SCRIPT_PATH);
};

server.on('close', (code) => {
console.log(`Server exited with code ${code}`);
});
}

// Main
// Main execution block
(async () => {
if (process.env.DATABASE_DRIVER) {
// Run the DB Migration script first
await runDBMigrationScript();

// Run OSS Connection Checker
await runOSSConnChecker();

// Run Auth Issuer Connection Checker
await runAuthIssuerConnChecker();

// If successful, proceed to run the server
runServer();
} else {
// Non-database mode: Run server directly
runServer();
try {
await runScript(DB_MIGRATION_SCRIPT_PATH);
await checkTLSConnections();
} catch (err) {
console.error('❌ Error during TLS connection check or DB migration:', err);
process.exit(1);
}
}

// Run the server in either database or non-database mode
await runServer();
})();