diff --git a/packages/core/nodemon.json b/packages/core/nodemon.json index 55326f5..7708d11 100644 --- a/packages/core/nodemon.json +++ b/packages/core/nodemon.json @@ -2,7 +2,7 @@ "watch": ["src"], "ignore": ["src/**/*.spec.ts"], "ext": "ts,json", - "exec": "pnpm build && pnpm start", + "exec": "yarn build", "legacyWatch": true, "delay": 1000 } diff --git a/packages/core/src/commons/configs.ts b/packages/core/src/commons/configs.ts index eafb887..fdf6d32 100644 --- a/packages/core/src/commons/configs.ts +++ b/packages/core/src/commons/configs.ts @@ -23,12 +23,14 @@ if (!existsSync(dataPath)) { const isMaxOSX = platform === SupportedPlatforms.MacOSX; const isWindows = platform === SupportedPlatforms.Windows; +const isLinux = platform === SupportedPlatforms.Linux; const coreConfigs: CoreConfiguration = { dataPath, platform, macosx: isMaxOSX, windows: isWindows, + linux: isLinux, encoding: isWindows ? 'utf16le' : 'utf8', ipcChannels: { daemon: isWindows diff --git a/packages/core/src/commons/typings.ts b/packages/core/src/commons/typings.ts index 40d2c42..3889617 100644 --- a/packages/core/src/commons/typings.ts +++ b/packages/core/src/commons/typings.ts @@ -1,6 +1,7 @@ export enum SupportedPlatforms { Windows = 'win32', MacOSX = 'darwin', + Linux = 'linux', } export interface IpcChannels { @@ -13,6 +14,7 @@ export interface CoreConfiguration { platform: string; windows: boolean; macosx: boolean; + linux: boolean; ipcChannels: IpcChannels; encoding: BufferEncoding; } diff --git a/packages/core/src/tls/factory.ts b/packages/core/src/tls/factory.ts index 390ae88..f09d067 100644 --- a/packages/core/src/tls/factory.ts +++ b/packages/core/src/tls/factory.ts @@ -86,6 +86,7 @@ export class CertificateFactory { publicKey: keyPair.publicKey, signingKey: keyPair.privateKey, issuer: [...this.issuer], + serialId: await this.store.nextSerialId(), // same as issuer since this is self signed subject: [...this.issuer], extensions: [ @@ -157,6 +158,7 @@ export class CertificateFactory { publicKey: keyPair.publicKey, signingKey: ca.key, issuer: caCert.subject.attributes, + serialId: await this.store.nextSerialId(), subject: [ { name: 'commonName', value: hostname }, { diff --git a/packages/core/src/tls/store.ts b/packages/core/src/tls/store.ts index 8722976..ed62f2b 100644 --- a/packages/core/src/tls/store.ts +++ b/packages/core/src/tls/store.ts @@ -14,6 +14,7 @@ import { CertificateDTO, CertificateStoreConfiguration } from './typings'; export class CertificateStore { private readonly storePath: string; + private readonly serialIdPath: string; private static cachedLookups = new InMemoryCache({ stdTTL: 60 * 5, // 5 minutes maxKeys: 250, @@ -23,6 +24,7 @@ export class CertificateStore { private readonly configuration: CertificateStoreConfiguration ) { this.storePath = resolve(coreConfigs.dataPath, this.configuration.folder); + this.serialIdPath = resolve(this.storePath, 'serial'); } private static maybeGetFromCache(id: string): Certificate | undefined { @@ -43,6 +45,7 @@ export class CertificateStore { private async init(): Promise { createDir(this.storePath); + await this.setupSerialDependency(); } private certificateDtoPath(id: string): string { @@ -113,6 +116,30 @@ export class CertificateStore { CertificateStore.maybeSetInCache(certificate.id, certificate); } + public async setupSerialDependency(): Promise { + if (pathExists(this.serialIdPath)) { + return; + } + + const files = getFiles(this.storePath, ['json', 'cert']); + for (const file of files) { + rmSync(resolve(this.storePath, file), { force: true }); + } + } + + public async nextSerialId(): Promise { + const serial = + (await getFile(this.serialIdPath, { encoding: 'utf-8' })) ?? '00'; + const nextSerial = BigInt(serial) + BigInt(1); + const nextSerialStr = nextSerial.toString(); + + await saveFile(this.serialIdPath, nextSerialStr, { + encoding: 'utf-8', + }); + + return nextSerialStr; + } + public static async create( configuration: CertificateStoreConfiguration ): Promise { diff --git a/packages/core/src/tls/typings.ts b/packages/core/src/tls/typings.ts index be3a6b6..3f41c28 100644 --- a/packages/core/src/tls/typings.ts +++ b/packages/core/src/tls/typings.ts @@ -45,6 +45,7 @@ export interface GenerateCertificateOpts { issuer: pki.CertificateField[]; extensions: object[]; signingKey: pki.PrivateKey; + serialId: string; } export interface CertificateStoreConfiguration { diff --git a/packages/core/src/tls/utils.ts b/packages/core/src/tls/utils.ts index 7196fc8..5eeb834 100644 --- a/packages/core/src/tls/utils.ts +++ b/packages/core/src/tls/utils.ts @@ -22,11 +22,12 @@ export const generateCertificate = async ({ issuer, extensions, signingKey, + serialId, }: GenerateCertificateOpts): Promise => { const certificate = pki.createCertificate(); certificate.publicKey = publicKey; - certificate.serialNumber = '01'; + certificate.serialNumber = serialId; certificate.validity.notBefore = createValidityDate(); certificate.validity.notAfter = createValidityDate(365); diff --git a/packages/daemon/package.json b/packages/daemon/package.json index c011933..863d76b 100644 --- a/packages/daemon/package.json +++ b/packages/daemon/package.json @@ -23,8 +23,11 @@ }, "pkg": { "targets": [ + "node18-macos-arm64", "node18-macos-x64", - "node18-win-x64" + "node18-win-x64", + "node18-linux-arm64", + "node18-linux-x64" ], "compress": "GZip", "outputPath": "bin" diff --git a/packages/daemon/src/platforms/factory.ts b/packages/daemon/src/platforms/factory.ts index d3b0082..c95c112 100644 --- a/packages/daemon/src/platforms/factory.ts +++ b/packages/daemon/src/platforms/factory.ts @@ -5,6 +5,7 @@ import { import { MacPlatform } from './mac'; import { Platform, PlatformBuildConfigs } from './typings'; import { WindowsPlatform } from './windows'; +import { LinuxPlatform } from './linux'; export class PlatformFactory { public static async create(configs: PlatformBuildConfigs): Promise { @@ -21,6 +22,12 @@ export class PlatformFactory { proxy: configs.proxy, pac: configs.pac, }); + case SupportedPlatforms.Linux: + return new LinuxPlatform({ + ca: configs.ca, + proxy: configs.proxy, + pac: configs.pac, + }); default: throw new UnsupportedPlatformError('unknown'); } diff --git a/packages/daemon/src/platforms/linux/index.ts b/packages/daemon/src/platforms/linux/index.ts new file mode 100644 index 0000000..42e61b2 --- /dev/null +++ b/packages/daemon/src/platforms/linux/index.ts @@ -0,0 +1,2 @@ +export * from './linux'; +export * from './typings'; diff --git a/packages/daemon/src/platforms/linux/linux.ts b/packages/daemon/src/platforms/linux/linux.ts new file mode 100644 index 0000000..08e140a --- /dev/null +++ b/packages/daemon/src/platforms/linux/linux.ts @@ -0,0 +1,220 @@ +import { + execAsync, + getDirectories, + getFile, + logger, + pathExists, + saveFile, +} from '@dfinity/http-proxy-core'; +import { Platform, PlatformProxyInfo } from '../typings'; +import { PlatformConfigs } from './typings'; +import { resolve } from 'path'; +import { + BASE_MOZILLA_PATH, + BASE_SNAP_MOZZILA_PATH, + CURL_RC_FILE, + FIREFOX_PROFILES_FOLDER, + MOZILLA_CERTIFICATES_FOLDER, + ROOT_CA_PATH, + findP11KitTrustPath, +} from './utils'; + +export class LinuxPlatform implements Platform { + constructor( + private readonly configs: PlatformConfigs, + private readonly username = String(process.env.LOGNAME ?? 'root') + ) {} + + public async attach(): Promise { + await this.setupDependencies(); + + logger.info( + `attaching proxy to system with: ` + + `host(${this.configs.proxy.host}:${this.configs.proxy.port}), ` + + `capath(${this.configs.ca.path}), ` + + `caname(${this.configs.ca.commonName})` + ); + + await this.trustCertificate(true, this.configs.ca.path); + + await this.configureWebProxy(true, { + host: this.configs.proxy.host, + port: this.configs.proxy.port, + }); + } + + public async detach(): Promise { + await this.setupDependencies(); + + logger.info( + `detaching proxy from system with: ` + + `host(${this.configs.proxy.host}:${this.configs.proxy.port}), ` + + `capath(${this.configs.ca.path}), ` + + `caname(${this.configs.ca.commonName})` + ); + + await this.trustCertificate(false, this.configs.ca.path); + + await this.configureWebProxy(false, { + host: this.configs.proxy.host, + port: this.configs.proxy.port, + }); + } + + public async configureWebProxy( + enable: boolean, + { host, port }: PlatformProxyInfo + ): Promise { + const curlrcPath = resolve(String(process.env.HOME), CURL_RC_FILE); + const curlrc = (await getFile(curlrcPath, { encoding: 'utf-8' })) ?? ''; + const curlrcLines = curlrc + .split('\n') + .filter((line) => !line.startsWith('proxy=')); + if (enable) { + curlrcLines.push(`proxy=http://${host}:${port}`); + } + await saveFile(curlrcPath, curlrcLines.join('\n'), { + encoding: 'utf-8', + }); + + await this.tooggleNetworkWebProxy(enable); + } + + private async tooggleNetworkWebProxy(enable: boolean): Promise { + const pacUrl = `http://${this.configs.pac.host}:${this.configs.pac.port}/proxy.pac`; + + if (enable) { + await execAsync( + [ + `su -l ${this.username} -c "gsettings set org.gnome.system.proxy mode 'auto' && gsettings set org.gnome.system.proxy autoconfig-url '${pacUrl}'"`, + ].join(' && ') + ); + + return; + } + + await execAsync( + [ + `su -l ${this.username} -c "gsettings set org.gnome.system.proxy mode 'none'"`, + ].join(' && ') + ); + } + + private async trustCertificate(trust: boolean, path: string): Promise { + if (trust) { + await execAsync( + `sudo cp "${path}" "${ROOT_CA_PATH}" && sudo update-ca-certificates` + ); + + await this.firefoxTrustCertificate(); + return; + } + + await execAsync( + `sudo rm -rf "${ROOT_CA_PATH}" && sudo update-ca-certificates` + ); + } + + private async firefoxTrustCertificate(): Promise { + await this.setupFirefoxCertificateConfigurations(BASE_MOZILLA_PATH); + await this.setupFirefoxCertificateConfigurations(BASE_SNAP_MOZZILA_PATH); + } + + private async setupFirefoxCertificateConfigurations( + basePath: string + ): Promise { + const homePath = String(process.env.HOME); + const mozillaPathPath = resolve(homePath, basePath); + const certificatesPath = resolve( + mozillaPathPath, + MOZILLA_CERTIFICATES_FOLDER + ); + const profilesPath = resolve(mozillaPathPath, FIREFOX_PROFILES_FOLDER); + + if (!pathExists(mozillaPathPath)) { + // Firefox is not installed. + return; + } + + await this.firefoxSetupCertificates(certificatesPath); + await this.firefoxSetupProfiles(profilesPath); + } + + private async setupDependencies(): Promise { + const p11KitPath = await findP11KitTrustPath(); + + if (!p11KitPath) { + await execAsync( + 'sudo apt install p11-kit p11-kit-modules libnss3-tools -y' + ); + const installed = await findP11KitTrustPath(); + + if (!installed) { + throw new Error('Failed to setup p11-kit dependency'); + } + } + } + + private async firefoxSetupCertificates(profilesPath: string): Promise { + if (!pathExists(profilesPath)) { + return; + } + + const p11KitPath = await findP11KitTrustPath(); + if (!p11KitPath) { + throw new Error('Failed to find certificate store path'); + } + + // firefox profile directories end with .default|.default-release + const profiles = getDirectories(profilesPath).filter( + (dir) => dir.endsWith('.default') || dir.endsWith('.default-release') + ); + + for (const profileFolder of profiles) { + const profilePath = resolve(profilesPath, profileFolder); + + await execAsync( + `modutil -dbdir sql:${profilePath} -add "P11 Kit" -libfile ${p11KitPath}` + ); + } + } + + private async firefoxSetupProfiles(profilesPath: string): Promise { + if (!pathExists(profilesPath)) { + return; + } + + // firefox profile directories end with .default|.default-release + const profiles = getDirectories(profilesPath).filter( + (dir) => dir.endsWith('.default') || dir.endsWith('.default-release') + ); + + for (const profileFolder of profiles) { + const userPreferencesPath = resolve( + profilesPath, + profileFolder, + 'user.js' + ); + + const userPreferences = + (await getFile(userPreferencesPath, { encoding: 'utf8' })) ?? ''; + + const preferences = userPreferences + .split('\n') + .filter((line) => !line.includes('security.enterprise_roots.enabled')); + + preferences.push(`user_pref("security.enterprise_roots.enabled", true);`); + + await saveFile( + userPreferencesPath, + preferences.filter((line) => line.length > 0).join('\n') + '\n', + { + encoding: 'utf-8', + } + ); + await execAsync( + `sudo chown ${this.username}:${this.username} "${userPreferencesPath}"` + ); + } + } +} diff --git a/packages/daemon/src/platforms/linux/typings.ts b/packages/daemon/src/platforms/linux/typings.ts new file mode 100644 index 0000000..66c6a94 --- /dev/null +++ b/packages/daemon/src/platforms/linux/typings.ts @@ -0,0 +1,25 @@ +import { PlatformPacInfo, PlatformRootCA } from '../typings'; + +export interface PlatformProxyInfo { + host: string; + port: number; +} + +export interface PlatformConfigs { + ca: PlatformRootCA; + proxy: PlatformProxyInfo; + pac: PlatformPacInfo; +} + +export interface SystemWebProxyInfo { + enabled: boolean; +} + +export interface WebProxyConfiguration { + https: SystemWebProxyInfo; + http: SystemWebProxyInfo; +} + +export interface NetworkProxySetup { + [networkPort: string]: WebProxyConfiguration; +} diff --git a/packages/daemon/src/platforms/linux/utils.ts b/packages/daemon/src/platforms/linux/utils.ts new file mode 100644 index 0000000..9847dd0 --- /dev/null +++ b/packages/daemon/src/platforms/linux/utils.ts @@ -0,0 +1,15 @@ +import { execAsync } from '@dfinity/http-proxy-core'; + +export const BASE_SNAP_MOZZILA_PATH = 'snap/firefox/common/.mozilla'; +export const BASE_MOZILLA_PATH = '.mozilla'; +export const MOZILLA_CERTIFICATES_FOLDER = 'certificates'; +export const FIREFOX_PROFILES_FOLDER = `firefox`; +export const ROOT_CA_STORE_PATH = '/usr/local/share/ca-certificates'; +export const ROOT_CA_PATH = `${ROOT_CA_STORE_PATH}/ic-http-proxy-root-ca.crt`; +export const CURL_RC_FILE = '.curlrc'; + +export const findP11KitTrustPath = async (): Promise => { + const path = await execAsync('sudo find /usr -name p11-kit-trust.so'); + + return path.length ? path : null; +}; diff --git a/packages/daemon/src/platforms/mac/mac.ts b/packages/daemon/src/platforms/mac/mac.ts index dddb187..e0a70f0 100644 --- a/packages/daemon/src/platforms/mac/mac.ts +++ b/packages/daemon/src/platforms/mac/mac.ts @@ -130,7 +130,7 @@ export class MacPlatform implements Platform { await saveFile( userPreferencesPath, - preferences.filter((line) => line.length > 0).join('\n'), + preferences.filter((line) => line.length > 0).join('\n') + '\n', { encoding: 'utf-8', } diff --git a/packages/server/src/servers/daemon/utils.ts b/packages/server/src/servers/daemon/utils.ts index 16cab08..0b685a2 100644 --- a/packages/server/src/servers/daemon/utils.ts +++ b/packages/server/src/servers/daemon/utils.ts @@ -4,19 +4,41 @@ import { coreConfigs, execAsync, } from '@dfinity/http-proxy-core'; +import { spawnSync } from 'child_process'; export const WAIT_UNTIL_ACTIVE_MS = 10000; export const WAIT_INTERVAL_CHECK_MS = 250; +export const daemonArch = (systemArch = process.arch): string => { + switch (systemArch) { + case 'x64': + return 'x64'; + case 'arm64': + return 'arm64'; + default: + throw new UnsupportedPlatformError(systemArch); + } +}; + export const daemonBinPath = async (platform: string): Promise => { switch (platform) { case SupportedPlatforms.MacOSX: return require - .resolve('@dfinity/http-proxy-daemon/bin/http-proxy-daemon-macos') + .resolve( + `@dfinity/http-proxy-daemon/bin/http-proxy-daemon-macos-${daemonArch()}` + ) .replace('.asar', '.asar.unpacked'); case SupportedPlatforms.Windows: return require - .resolve('@dfinity/http-proxy-daemon/bin/http-proxy-daemon-win.exe') + .resolve( + `@dfinity/http-proxy-daemon/bin/http-proxy-daemon-win-${daemonArch()}.exe` + ) + .replace('.asar', '.asar.unpacked'); + case SupportedPlatforms.Linux: + return require + .resolve( + `@dfinity/http-proxy-daemon/bin/http-proxy-daemon-linux-${daemonArch()}` + ) .replace('.asar', '.asar.unpacked'); default: throw new UnsupportedPlatformError(platform); @@ -52,6 +74,26 @@ const spawnDaemonProcessWindows = async (daemonPath: string): Promise => { await execAsync(spawnCommand); }; +const spawnDaemonProcessUbuntu = (daemonPath: string) => { + const escapedDaemonPath = daemonPath.replace(/ /g, '\\ '); + const command = 'pkexec'; + const args = [ + 'sh', + '-c', + `HOME="${process.env.HOME}" LOGNAME="${process.env.LOGNAME}" nohup ${escapedDaemonPath} &>/dev/null &`, + ]; + + const result = spawnSync(command, args, { + stdio: 'ignore', + env: process.env, + }); + if (result.status !== 0) { + throw new Error( + `Spawn error (err: ${result.status}): ${result.error ?? 'unknown'}` + ); + } +}; + export const spawnDaemonProcess = async (platform: string): Promise => { const daemonPath = await daemonBinPath(platform); @@ -60,6 +102,8 @@ export const spawnDaemonProcess = async (platform: string): Promise => { return await spawnDaemonProcessMacOSX(daemonPath); case SupportedPlatforms.Windows: return await spawnDaemonProcessWindows(daemonPath); + case SupportedPlatforms.Linux: + return await spawnDaemonProcessUbuntu(daemonPath); default: throw new UnsupportedPlatformError(platform); } diff --git a/packages/ui/build.js b/packages/ui/build.js index d21dbc7..b7853a4 100644 --- a/packages/ui/build.js +++ b/packages/ui/build.js @@ -3,6 +3,7 @@ const { platform } = require('node:process'); const macBuild = require('./build/mac'); const winBuild = require('./build/win'); +const linuxBuild = require('./build/linux'); switch (platform) { case 'win32': { @@ -13,6 +14,10 @@ switch (platform) { macBuild(); break; } + case 'linux': { + linuxBuild(); + break; + } default: { throw new Error("Unsupported platform. Only Mac and Windows are currently supported."); } diff --git a/packages/ui/build/linux.js b/packages/ui/build/linux.js new file mode 100644 index 0000000..d0dc60b --- /dev/null +++ b/packages/ui/build/linux.js @@ -0,0 +1,80 @@ +'use strict'; + +const { execSync } = require('child_process'); +const builder = require('electron-builder'); +const { createReleaseHashFile } = require('./utils'); +const Platform = builder.Platform; + +/** + * @type {import('electron-builder').Configuration} + * @see https://www.electron.build/configuration/configuration + */ +const options = { + compression: 'normal', + removePackageScripts: true, + appId: 'com.dfinity.ichttpproxy', + productName: 'IC HTTP Proxy', + executableName: 'ic-http-proxy', + artifactName: 'ic-http-proxy-${os}-${arch}-${version}.${ext}', + nodeVersion: 'current', + nodeGypRebuild: false, + buildDependenciesFromSource: false, + asarUnpack: ['node_modules/@dfinity/http-proxy-daemon/bin/*'], + directories: { + output: 'pkg', + }, + afterPack: (context) => { + execSync( + `find "${context.appOutDir}" -exec touch -mht 202201010000.00 {} +` + ); + }, + linux: { + icon: './src/assets/logo@256x256.icns', + category: "System", + files: [ + '!bin/http-proxy-daemon-win.exe', + '!bin/http-proxy-daemon-win-x64.exe', + '!bin/http-proxy-daemon-win-arm64.exe', + '!bin/http-proxy-daemon-macos', + '!bin/http-proxy-daemon-macos-x64', + '!bin/http-proxy-daemon-macos-arm64', + '!.git/*', + '!tsconfig.json', + '!nodemon.json', + '!.eslintrc.js', + ], + }, +}; + +const build = async () => { + // build for linux arm + await builder + .build({ + targets: Platform.LINUX.createTarget('zip', builder.Arch.arm64), + config: options, + }) + .then(async (builtFiles) => createReleaseHashFile(builtFiles)); + // build for linux x64 + await builder + .build({ + targets: Platform.LINUX.createTarget('zip', builder.Arch.x64), + config: options, + }) + .then(async (builtFiles) => createReleaseHashFile(builtFiles)); + // linux arm installer (non deterministic) + await builder + .build({ + targets: Platform.LINUX.createTarget('deb', builder.Arch.arm64), + config: options, + }) + .then(async (builtFiles) => createReleaseHashFile(builtFiles)); + // linux x64 installer (non deterministic) + await builder + .build({ + targets: Platform.LINUX.createTarget('deb', builder.Arch.x64), + config: options, + }) + .then(async (builtFiles) => createReleaseHashFile(builtFiles)); +}; + +module.exports = build; diff --git a/packages/ui/build/mac.js b/packages/ui/build/mac.js index e659d48..d2003aa 100644 --- a/packages/ui/build/mac.js +++ b/packages/ui/build/mac.js @@ -34,6 +34,11 @@ const options = { identity: null, files: [ '!bin/http-proxy-daemon-win.exe', + '!bin/http-proxy-daemon-win-x64.exe', + '!bin/http-proxy-daemon-win-arm64.exe', + '!bin/http-proxy-daemon-linux', + '!bin/http-proxy-daemon-linux-x64', + '!bin/http-proxy-daemon-linux-arm64', '!.git/*', '!tsconfig.json', '!nodemon.json', diff --git a/packages/ui/build/win.js b/packages/ui/build/win.js index cb50b73..64c47c4 100644 --- a/packages/ui/build/win.js +++ b/packages/ui/build/win.js @@ -38,6 +38,11 @@ const options = { icon: './src/assets/logo@128x128.ico', files: [ '!bin/http-proxy-daemon-macos', + '!bin/http-proxy-daemon-macos-x64', + '!bin/http-proxy-daemon-macos-arm64', + '!bin/http-proxy-daemon-linux', + '!bin/http-proxy-daemon-linux-x64', + '!bin/http-proxy-daemon-linux-arm64', '!.git/*', '!tsconfig.json', '!nodemon.json', diff --git a/packages/ui/package.json b/packages/ui/package.json index 3e5f084..9eb00c7 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -10,7 +10,7 @@ "copy-assets": "mkdirp ./built/assets && copyfiles -f ./src/assets/* ./built/assets", "lint": "eslint --ext ts,js src", "lint:fix": "eslint --ext ts,js --fix src", - "pkg": "node build.js" + "pkg": "USE_SYSTEM_FPM=true node build.js" }, "repository": { "type": "git", diff --git a/packages/ui/src/assets/logo.png b/packages/ui/src/assets/logo.png new file mode 100644 index 0000000..bc58e48 Binary files /dev/null and b/packages/ui/src/assets/logo.png differ diff --git a/packages/ui/src/assets/logo@124x124.png b/packages/ui/src/assets/logo@124x124.png new file mode 100644 index 0000000..a00ed15 Binary files /dev/null and b/packages/ui/src/assets/logo@124x124.png differ diff --git a/packages/ui/src/assets/logo@256x256.icns b/packages/ui/src/assets/logo@256x256.icns new file mode 100644 index 0000000..3ea3089 Binary files /dev/null and b/packages/ui/src/assets/logo@256x256.icns differ diff --git a/packages/ui/src/interface/images.ts b/packages/ui/src/interface/images.ts index 6d6d103..b846d5a 100644 --- a/packages/ui/src/interface/images.ts +++ b/packages/ui/src/interface/images.ts @@ -11,6 +11,10 @@ export class Images { return join(Images.path, 'tray-Template.png'); } + if (process.platform === 'linux') { + return join(Images.path, 'tray-dark.png'); + } + const image = this.isInDarkMode ? 'tray-dark.png' : 'tray-light.png'; return join(Images.path, image); @@ -21,6 +25,10 @@ export class Images { return join(Images.path, 'tray-enabled-Template.png'); } + if (process.platform === 'linux') { + return join(Images.path, 'tray-dark-enabled.png'); + } + const image = this.isInDarkMode ? 'tray-dark-enabled.png' : 'tray-light-enabled.png';