homebridge-config-ui-x
Version:
A web based management, configuration and control platform for Homebridge.
157 lines • 6.44 kB
JavaScript
import { readFile } from 'node:fs/promises';
import { resolve } from 'node:path';
import process from 'node:process';
import helmet from '@fastify/helmet';
import fastifyMultipart from '@fastify/multipart';
import { ValidationPipe } from '@nestjs/common';
import { NestFactory } from '@nestjs/core';
import { FastifyAdapter } from '@nestjs/platform-fastify';
import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger';
import { Bonjour } from 'bonjour-service';
import { AppModule } from './app.module.js';
import { ConfigService } from './core/config/config.service.js';
import { getStartupConfig } from './core/config/config.startup.js';
import { devServerCorsConfig } from './core/cors.config.js';
import { Logger } from './core/logger/logger.service.js';
import { SpaFilter } from './core/spa/spa.filter.js';
import './env-setup.js';
import 'reflect-metadata';
import './self-check.js';
import './global-defaults.js';
export { HomebridgeIpcService } from './core/homebridge-ipc/homebridge-ipc.service.js';
async function bootstrap() {
const startupConfig = await getStartupConfig();
const fAdapter = new FastifyAdapter({
https: startupConfig.httpsOptions,
logger: startupConfig.debug || false,
});
fAdapter.register(fastifyMultipart, {
limits: {
files: 1,
fileSize: globalThis.backup.maxBackupSize,
},
});
fAdapter.register(helmet, {
hsts: false,
frameguard: false,
referrerPolicy: {
policy: 'no-referrer',
},
crossOriginEmbedderPolicy: false,
crossOriginOpenerPolicy: false,
crossOriginResourcePolicy: false,
contentSecurityPolicy: {
directives: {
defaultSrc: ['\'self\''],
scriptSrc: ['\'self\'', '\'unsafe-inline\'', '\'unsafe-eval\''],
styleSrc: ['\'self\'', '\'unsafe-inline\''],
imgSrc: ['\'self\'', 'data:', 'https://raw.githubusercontent.com', 'https://user-images.githubusercontent.com'],
connectSrc: ['\'self\'', 'https://openweathermap.org', 'https://api.openweathermap.org', (req) => {
return `wss://${req.headers.host} ws://${req.headers.host} ${startupConfig.cspWsOverride || ''}`;
}],
frameSrc: ['\'self\'', 'data:', 'https://developers.homebridge.io'],
workerSrc: ['\'self\'', 'blob:'],
fontSrc: ['\'self\'', 'data:'],
scriptSrcAttr: null,
objectSrc: null,
frameAncestors: null,
formAction: null,
baseUri: null,
upgradeInsecureRequests: null,
blockAllMixedContent: null,
},
},
});
const app = await NestFactory.create(AppModule, fAdapter, {
logger: (startupConfig.debug || process.env.UIX_DEVELOPMENT === '1') ? new Logger() : false,
httpsOptions: startupConfig.httpsOptions,
});
const configService = app.get(ConfigService);
const logger = app.get(Logger);
app.getHttpAdapter().get('/', async (req, res) => {
res.type('text/html');
res.header('Cache-Control', 'no-cache, no-store, must-revalidate');
res.header('Pragma', 'no-cache');
res.header('Expires', '0');
res.send(await readFile(resolve(process.env.UIX_BASE_PATH, 'public/index.html')));
});
app.useStaticAssets({
root: resolve(process.env.UIX_BASE_PATH, 'public'),
setHeaders(res) {
res.setHeader('Cache-Control', 'public,max-age=31536000,immutable');
},
});
app.setGlobalPrefix('/api');
app.enableCors({
...devServerCorsConfig,
methods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'],
});
app.useGlobalPipes(new ValidationPipe({
whitelist: true,
skipMissingProperties: true,
}));
const options = new DocumentBuilder()
.setTitle('Homebridge UI API Reference')
.setVersion(configService.package.version)
.addBearerAuth({
type: 'oauth2',
flows: {
password: {
tokenUrl: '/api/auth/login',
scopes: null,
},
},
})
.build();
const document = SwaggerModule.createDocument(app, options);
SwaggerModule.setup('swagger', app, document);
app.useGlobalFilters(new SpaFilter());
logger.warn(`Homebridge UI v${configService.package.version} is listening on ${startupConfig.host} port ${configService.ui.port}.`);
await app.listen(configService.ui.port, startupConfig.host);
let bonjour = null;
if (configService.ui.enableMdnsAdvertise) {
try {
bonjour = new Bonjour();
const serviceName = configService.homebridgeConfig?.bridge?.name
? configService.homebridgeConfig.bridge.name
: 'Homebridge UI';
const service = bonjour.publish({
name: serviceName,
type: 'http',
port: configService.ui.port,
host: startupConfig.host === '0.0.0.0' || startupConfig.host === '::' ? undefined : startupConfig.host,
txt: {
path: '/',
version: configService.package.version,
https: startupConfig.httpsOptions ? 'true' : 'false',
},
});
logger.log(`Homebridge UI HTTP service advertised via mDNS as "${service.name}" on port ${configService.ui.port}`);
}
catch (error) {
logger.error('Failed to advertise mDNS service:', error);
}
}
const handleShutdown = (signal) => {
logger.log(`Received ${signal}, starting graceful shutdown...`);
if (bonjour) {
try {
logger.log('Shutting down mDNS service advertising...');
bonjour.unpublishAll();
bonjour.destroy();
bonjour = null;
}
catch (error) {
logger.error('Error during mDNS cleanup:', error);
}
}
app.close().finally(() => {
process.exit(0);
});
};
process.once('SIGINT', () => handleShutdown('SIGINT'));
process.once('SIGTERM', () => handleShutdown('SIGTERM'));
return app;
}
export const app = bootstrap();
//# sourceMappingURL=main.js.map