From b52950d3d7e04b7eef906a9f99a7e6152c213f09 Mon Sep 17 00:00:00 2001 From: biersoeckli Date: Sat, 8 Nov 2025 16:09:12 +0000 Subject: [PATCH] refactor: replace TraefikMeUtils with HostnameDnsProviderUtils and remove related code. Switched to sslip.io as dns-hostname service --- .../utils/domain-dns-provider.utils.test.ts | 79 +++++++++++++++++++ .../shared/utils/traefik-me.utils.test.ts | 25 ------ src/app/auth/actions.ts | 7 -- .../project/app/[appId]/domains/actions.ts | 8 +- .../maintenance/qs-maintenance-settings.tsx | 14 +--- src/app/settings/server/actions.ts | 8 -- src/server.ts | 2 - src/server/adapter/traefik-me.adapter.ts | 34 -------- .../db-tool-services/base-db-tool.service.ts | 27 ++++--- src/server/services/file-browser-service.ts | 5 +- src/server/services/ingress.service.ts | 10 +-- src/server/services/project.service.ts | 2 - .../traefik-me-domain-standalone.service.ts | 40 ---------- .../services/traefik-me-domain.service.ts | 16 ++-- src/shared/utils/domain-dns-provider.utils.ts | 31 ++++++++ src/shared/utils/traefik-me.utils.ts | 10 --- 16 files changed, 145 insertions(+), 173 deletions(-) create mode 100644 src/__tests__/shared/utils/domain-dns-provider.utils.test.ts delete mode 100644 src/__tests__/shared/utils/traefik-me.utils.test.ts delete mode 100644 src/server/adapter/traefik-me.adapter.ts delete mode 100644 src/server/services/standalone-services/traefik-me-domain-standalone.service.ts create mode 100644 src/shared/utils/domain-dns-provider.utils.ts delete mode 100644 src/shared/utils/traefik-me.utils.ts diff --git a/src/__tests__/shared/utils/domain-dns-provider.utils.test.ts b/src/__tests__/shared/utils/domain-dns-provider.utils.test.ts new file mode 100644 index 0000000..d859b89 --- /dev/null +++ b/src/__tests__/shared/utils/domain-dns-provider.utils.test.ts @@ -0,0 +1,79 @@ +import { HostnameDnsProviderUtils } from '../../../shared/utils/domain-dns-provider.utils'; + +describe('DomainDnsProviderUtils', () => { + describe('isValidTraefikMeDomain', () => { + it('should return true for valid sslip.io domain with subdomain', () => { + expect(HostnameDnsProviderUtils.isValidDnsProviderHostname('sub.example.sslip.io')).toBe(true); + }); + + it('should return true for IP-based domain', () => { + expect(HostnameDnsProviderUtils.isValidDnsProviderHostname('192.168.1.1.sslip.io')).toBe(true); + }); + + it('should return false for simple domain ending with .sslip.io', () => { + expect(HostnameDnsProviderUtils.isValidDnsProviderHostname('example.sslip.io')).toBe(false); + }); + + it('should return false for domain not ending with .sslip.io', () => { + expect(HostnameDnsProviderUtils.isValidDnsProviderHostname('example.com')).toBe(false); + }); + + it('should return false for domain with only provider domain', () => { + expect(HostnameDnsProviderUtils.isValidDnsProviderHostname('sslip.io')).toBe(false); + }); + + it('should return false for empty string', () => { + expect(HostnameDnsProviderUtils.isValidDnsProviderHostname('')).toBe(false); + }); + }); + + describe('containesTraefikMeDomain', () => { + it('should return true for domain containing .sslip.io', () => { + expect(HostnameDnsProviderUtils.containsDnsProviderHostname('example.sslip.io')).toBe(true); + }); + + it('should return true for subdomain containing .sslip.io', () => { + expect(HostnameDnsProviderUtils.containsDnsProviderHostname('sub.example.sslip.io')).toBe(true); + }); + + it('should return false for domain not containing .sslip.io', () => { + expect(HostnameDnsProviderUtils.containsDnsProviderHostname('example.com')).toBe(false); + }); + + it('should return false for empty string', () => { + expect(HostnameDnsProviderUtils.containsDnsProviderHostname('')).toBe(false); + }); + }); + + describe('getHostnameForIpAdress', () => { + it('should convert IP address to hostname with dashes', () => { + expect(HostnameDnsProviderUtils.getHostnameForIpAdress('192.168.1.1')).toBe('192-168-1-1.sslip.io'); + }); + + it('should handle another IP address format', () => { + expect(HostnameDnsProviderUtils.getHostnameForIpAdress('10.0.0.1')).toBe('10-0-0-1.sslip.io'); + }); + + it('should handle localhost IP', () => { + expect(HostnameDnsProviderUtils.getHostnameForIpAdress('127.0.0.1')).toBe('127-0-0-1.sslip.io'); + }); + }); + + describe('ipv4ToHex', () => { + it('should convert IPv4 address to hex', () => { + expect(HostnameDnsProviderUtils.ipv4ToHex('192.168.1.1')).toBe('c0a80101'); + }); + + it('should convert another IPv4 address to hex', () => { + expect(HostnameDnsProviderUtils.ipv4ToHex('10.0.0.1')).toBe('0a000001'); + }); + + it('should handle leading zeros correctly', () => { + expect(HostnameDnsProviderUtils.ipv4ToHex('1.2.3.4')).toBe('01020304'); + }); + + it('should handle max values correctly', () => { + expect(HostnameDnsProviderUtils.ipv4ToHex('255.255.255.255')).toBe('ffffffff'); + }); + }); +}); \ No newline at end of file diff --git a/src/__tests__/shared/utils/traefik-me.utils.test.ts b/src/__tests__/shared/utils/traefik-me.utils.test.ts deleted file mode 100644 index 194851a..0000000 --- a/src/__tests__/shared/utils/traefik-me.utils.test.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { TraefikMeUtils } from '../../../shared/utils/traefik-me.utils'; - -describe('TraefikMeUtils', () => { - describe('isValidTraefikMeDomain', () => { - it('should return true for valid traefik.me domain', () => { - expect(TraefikMeUtils.isValidTraefikMeDomain('example.traefik.me')).toBe(true); - }); - - it('should return false for domain not ending with .traefik.me', () => { - expect(TraefikMeUtils.isValidTraefikMeDomain('example.com')).toBe(false); - }); - - it('should return false for domain with more than three parts', () => { - expect(TraefikMeUtils.isValidTraefikMeDomain('sub.example.traefik.me')).toBe(false); - }); - - it('should return false for domain with less than three parts', () => { - expect(TraefikMeUtils.isValidTraefikMeDomain('traefik.me')).toBe(false); - }); - - it('should return false for empty string', () => { - expect(TraefikMeUtils.isValidTraefikMeDomain('')).toBe(false); - }); - }); -}); \ No newline at end of file diff --git a/src/app/auth/actions.ts b/src/app/auth/actions.ts index 544dedc..b660bec 100644 --- a/src/app/auth/actions.ts +++ b/src/app/auth/actions.ts @@ -8,7 +8,6 @@ import quickStackService from "@/server/services/qs.service"; import userService from "@/server/services/user.service"; import { saveFormAction } from "@/server/utils/action-wrapper.utils"; import ipAddressFinderAdapter from "@/server/adapter/ip-adress-finder.adapter"; -import traefikMeDomainStandaloneService from "@/server/services/standalone-services/traefik-me-domain-standalone.service"; import userGroupService from "@/server/services/user-group.service"; @@ -28,12 +27,6 @@ export const registerUser = async (prevState: any, inputData: RegisterFormInputS // ignore console.error('Failes to evaluate public ip address', e); } - try { - await traefikMeDomainStandaloneService.updateTraefikMeCertificate(); - } catch (e) { - // ignore - console.error('Failed to update traefik me certificate', e); - } if (validatedData.qsHostname) { const url = new URL(validatedData.qsHostname.includes('://') ? validatedData.qsHostname : `https://${validatedData.qsHostname}`); await paramService.save({ diff --git a/src/app/project/app/[appId]/domains/actions.ts b/src/app/project/app/[appId]/domains/actions.ts index e9c4661..f23a02d 100644 --- a/src/app/project/app/[appId]/domains/actions.ts +++ b/src/app/project/app/[appId]/domains/actions.ts @@ -6,7 +6,7 @@ import { SuccessActionResult } from "@/shared/model/server-action-error-return.m import appService from "@/server/services/app.service"; import { getAuthUserSession, isAuthorizedWriteForApp, saveFormAction, simpleAction } from "@/server/utils/action-wrapper.utils"; import { z } from "zod"; -import { TraefikMeUtils } from "@/shared/utils/traefik-me.utils"; +import { HostnameDnsProviderUtils } from "@/shared/utils/domain-dns-provider.utils"; import { ServiceException } from "@/shared/model/service.exception.model"; const actionAppDomainEditZodModel = appDomainEditZodModel.merge(z.object({ @@ -23,9 +23,9 @@ export const saveDomain = async (prevState: any, inputData: z.infer Force Update Registry - - - - ; diff --git a/src/app/settings/server/actions.ts b/src/app/settings/server/actions.ts index 24783d9..bfe0577 100644 --- a/src/app/settings/server/actions.ts +++ b/src/app/settings/server/actions.ts @@ -13,7 +13,6 @@ import { QsPublicIpv4SettingsModel, qsPublicIpv4SettingsZodModel } from "@/share import ipAddressFinderAdapter from "@/server/adapter/ip-adress-finder.adapter"; import { KubeSizeConverter } from "@/shared/utils/kubernetes-size-converter.utils"; import buildService from "@/server/services/build.service"; -import traefikMeDomainStandaloneService from "@/server/services/standalone-services/traefik-me-domain-standalone.service"; import standalonePodService from "@/server/services/standalone-services/standalone-pod.service"; import maintenanceService from "@/server/services/standalone-services/maintenance.service"; import appLogsService from "@/server/services/standalone-services/app-logs.service"; @@ -111,13 +110,6 @@ export const updateRegistry = async () => return new SuccessActionResult(undefined, 'Registry will be updated, this might take a few seconds.'); }); -export const updateTraefikMeCertificates = async () => - simpleAction(async () => { - await getAdminUserSession(); - await traefikMeDomainStandaloneService.updateTraefikMeCertificate(); - return new SuccessActionResult(undefined, 'Certificates will be updated, this might take a few seconds.'); - }); - export const deleteAllFailedAndSuccededPods = async () => simpleAction(async () => { await getAdminUserSession(); diff --git a/src/server.ts b/src/server.ts index 1ea3233..938788c 100644 --- a/src/server.ts +++ b/src/server.ts @@ -8,7 +8,6 @@ import dataAccess from './server/adapter/db.client' import { FancyConsoleUtils } from './shared/utils/fancy-console.utils' import { Constants } from './shared/utils/constants' import backupService from './server/services/standalone-services/backup.service' -import traefikMeDomainStandaloneService from './server/services/standalone-services/traefik-me-domain-standalone.service' import maintenanceService from './server/services/standalone-services/maintenance.service' import passwordChangeService from './server/services/standalone-services/password-change.service' import appLogsService from './server/services/standalone-services/app-logs.service' @@ -56,7 +55,6 @@ async function initializeNextJs() { } await backupService.registerAllBackups(); - traefikMeDomainStandaloneService.configureSchedulingForTraefikMeCertificateUpdate(); maintenanceService.configureMaintenanceCronJobs(); appLogsService.configureCronJobs(); diff --git a/src/server/adapter/traefik-me.adapter.ts b/src/server/adapter/traefik-me.adapter.ts deleted file mode 100644 index d6391c0..0000000 --- a/src/server/adapter/traefik-me.adapter.ts +++ /dev/null @@ -1,34 +0,0 @@ - - -class TraefikMeAdapter { - private traefikMeBaseURL = 'https://traefik.me'; - - async getCurrentPrivateKey() { - const result = await fetch(`${this.traefikMeBaseURL}/privkey.pem`, { - method: 'GET', - cache: 'no-store', - }); - - if (!result.ok) { - throw new Error('Failed to get private key from traefik.me'); - } - const privateKeyText = result.text(); - return privateKeyText; - } - - async getFullChainCertificate() { - const result = await fetch(`${this.traefikMeBaseURL}/fullchain.pem`, { - method: 'GET', - cache: 'no-store', - }); - - if (!result.ok) { - throw new Error('Failed to get full chain from traefik.me'); - } - const fullChainText = result.text(); - return fullChainText; - } -} - -const traefikMeAdapter = new TraefikMeAdapter(); -export default traefikMeAdapter; \ No newline at end of file diff --git a/src/server/services/db-tool-services/base-db-tool.service.ts b/src/server/services/db-tool-services/base-db-tool.service.ts index b4c76d3..47f7913 100644 --- a/src/server/services/db-tool-services/base-db-tool.service.ts +++ b/src/server/services/db-tool-services/base-db-tool.service.ts @@ -1,6 +1,6 @@ import { ServiceException } from "@/shared/model/service.exception.model"; import dataAccess from "../../adapter/db.client"; -import traefikMeDomainService from "../traefik-me-domain.service"; +import hostnameDnsProviderService from "../traefik-me-domain.service"; import { KubeObjectNameUtils } from "../../utils/kube-object-name.utils"; import deploymentService from "../deployment.service"; import { V1Deployment, V1Ingress } from "@kubernetes/client-node"; @@ -60,7 +60,7 @@ export class BaseDbToolService { } const { username, password } = searchFunc(existingDeployment, app); - const traefikHostname = await traefikMeDomainService.getDomainForApp(toolAppName); + const traefikHostname = await hostnameDnsProviderService.getDomainForApp(toolAppName); return { url: `https://${traefikHostname}`, username, password }; } @@ -75,7 +75,7 @@ export class BaseDbToolService { const namespace = app.projectId; console.log(`Deploying DB Tool ${toolAppName} for app ${appId}`); - const traefikHostname = await traefikMeDomainService.getDomainForApp(toolAppName); + const hostnameDnsProviderHostname = await hostnameDnsProviderService.getDomainForApp(toolAppName); console.log(`Creating DB Tool ${toolAppName} deployment for app ${appId}`); await this.createOrUpdateDbGateDeployment(app, deplyomentBuilder); @@ -88,7 +88,7 @@ export class BaseDbToolService { }]); console.log(`Creating ingress for DB Tool ${toolAppName} for app ${appId}`); - await this.createOrUpdateIngress(toolAppName, namespace, traefikHostname); + await this.createOrUpdateIngress(toolAppName, namespace, hostnameDnsProviderHostname); const fileBrowserPods = await podService.getPodsForApp(namespace, toolAppName); for (const pod of fileBrowserPods) { @@ -129,24 +129,25 @@ export class BaseDbToolService { } } - private async createOrUpdateIngress(dbGateAppName: string, namespace: string, traefikHostname: string) { + private async createOrUpdateIngress(dbGateAppName: string, namespace: string, hostname: string) { const ingressDefinition: V1Ingress = { apiVersion: 'networking.k8s.io/v1', kind: 'Ingress', metadata: { name: KubeObjectNameUtils.getIngressName(dbGateAppName), namespace: namespace, - // dont annotate, because ingress will be deleted after redeployment of app - /* annotations: { - [Constants.QS_ANNOTATION_APP_ID]: appId, - [Constants.QS_ANNOTATION_PROJECT_ID]: projectId, - },*/ + annotations: { + // dont annotate, because ingress will be deleted after redeployment of app + // [Constants.QS_ANNOTATION_APP_ID]: appId, + // [Constants.QS_ANNOTATION_PROJECT_ID]: projectId, + ...({ 'cert-manager.io/cluster-issuer': 'letsencrypt-production' }), // --> dont start cert-manager for traefik.me domains + } }, spec: { ingressClassName: 'traefik', rules: [ { - host: traefikHostname, + host: hostname, http: { paths: [ { @@ -166,8 +167,8 @@ export class BaseDbToolService { }, ], tls: [{ - hosts: [traefikHostname], - secretName: Constants.TRAEFIK_ME_SECRET_NAME, + hosts: [hostname], + secretName: `secret-tls-${hostname}`.substring(0, 63) }], }, }; diff --git a/src/server/services/file-browser-service.ts b/src/server/services/file-browser-service.ts index 9eb3a70..abfc5f3 100644 --- a/src/server/services/file-browser-service.ts +++ b/src/server/services/file-browser-service.ts @@ -1,6 +1,5 @@ import { V1Deployment, V1Ingress } from "@kubernetes/client-node"; import dataAccess from "../adapter/db.client"; -import traefikMeDomainStandaloneService from "./standalone-services/traefik-me-domain-standalone.service"; import { Constants } from "@/shared/utils/constants"; import { KubeObjectNameUtils } from "../utils/kube-object-name.utils"; import deploymentService from "./deployment.service"; @@ -10,7 +9,7 @@ import svcService from "./svc.service"; import { randomBytes } from "crypto"; import podService from "./pod.service"; import bcrypt from "bcrypt"; -import traefikMeDomainService from "./traefik-me-domain.service"; +import hostnameDnsProviderService from "./traefik-me-domain.service"; import pvcService from "./pvc.service"; class FileBrowserService { @@ -37,7 +36,7 @@ class FileBrowserService { await pvcService.createPvcForVolumeIfNotExists(volume.app.projectId, volume); console.log(`Deploying filebrowser for volume ${volumeId}`); - const traefikHostname = await traefikMeDomainService.getDomainForApp(volume.id); + const traefikHostname = await hostnameDnsProviderService.getDomainForApp(volume.id); const pvcName = KubeObjectNameUtils.toPvcName(volume.id); diff --git a/src/server/services/ingress.service.ts b/src/server/services/ingress.service.ts index 3d98c98..6029714 100644 --- a/src/server/services/ingress.service.ts +++ b/src/server/services/ingress.service.ts @@ -7,7 +7,7 @@ import { Constants } from "../../shared/utils/constants"; import ingressSetupService from "./setup-services/ingress-setup.service"; import { dlog } from "./deployment-logs.service"; import { createHash } from "crypto"; -import { TraefikMeUtils } from "@/shared/utils/traefik-me.utils"; +import { HostnameDnsProviderUtils } from "@/shared/utils/domain-dns-provider.utils"; class IngressService { @@ -68,7 +68,7 @@ class IngressService { const hostname = domain.hostname; const ingressName = KubeObjectNameUtils.getIngressName(domain.id); const existingIngress = await this.getIngressByName(app.projectId, domain.id); - const isATraefikMeDomain = TraefikMeUtils.isValidTraefikMeDomain(hostname); + const isATraefikMeDomain = HostnameDnsProviderUtils.isValidDnsProviderHostname(hostname); const middlewares = [ basicAuthMiddlewareName, @@ -84,7 +84,7 @@ class IngressService { annotations: { [Constants.QS_ANNOTATION_APP_ID]: app.id, [Constants.QS_ANNOTATION_PROJECT_ID]: app.projectId, - ...(!isATraefikMeDomain && domain.useSsl === true && { 'cert-manager.io/cluster-issuer': 'letsencrypt-production' }), + ...(!isATraefikMeDomain && domain.useSsl === true && { 'cert-manager.io/cluster-issuer': 'letsencrypt-production' }), // for traefik.me domains no cert-manager --> uses default traefik self signed certificate ...(middlewares && { 'traefik.ingress.kubernetes.io/router.middlewares': middlewares }), ...(domain.useSsl === false && { 'traefik.ingress.kubernetes.io/router.entrypoints': 'web' }), // disable requests from https --> only http }, @@ -112,11 +112,11 @@ class IngressService { }, }, ], - ...(domain.useSsl === true && { + ...(domain.useSsl === true && !isATraefikMeDomain && { tls: [ { hosts: [hostname], - secretName: isATraefikMeDomain ? Constants.TRAEFIK_ME_SECRET_NAME : `secret-tls-${domain.id}`, + secretName: `secret-tls-${domain.id}`, }, ], }), diff --git a/src/server/services/project.service.ts b/src/server/services/project.service.ts index 0117afd..2f72f58 100644 --- a/src/server/services/project.service.ts +++ b/src/server/services/project.service.ts @@ -6,7 +6,6 @@ import { KubeObjectNameUtils } from "../utils/kube-object-name.utils"; import deploymentService from "./deployment.service"; import namespaceService from "./namespace.service"; import buildService from "./build.service"; -import traefikMeDomainStandaloneService from "./standalone-services/traefik-me-domain-standalone.service"; import { ProjectExtendedModel } from "@/shared/model/project-extended.model"; class ProjectService { @@ -73,7 +72,6 @@ class ProjectService { } finally { revalidateTag(Tags.projects()); } - await traefikMeDomainStandaloneService.updateTraefikMeCertificate(); return savedItem; } } diff --git a/src/server/services/standalone-services/traefik-me-domain-standalone.service.ts b/src/server/services/standalone-services/traefik-me-domain-standalone.service.ts deleted file mode 100644 index 5038f17..0000000 --- a/src/server/services/standalone-services/traefik-me-domain-standalone.service.ts +++ /dev/null @@ -1,40 +0,0 @@ -import traefikMeAdapter from "../../adapter/traefik-me.adapter"; -import { V1Secret } from "@kubernetes/client-node"; -import secretService from "../secret.service"; -import { Constants } from "../../../shared/utils/constants"; -import dataAccess from "../../adapter/db.client"; -import scheduleService from "./schedule.service"; - -class TraefikMeDomainStandaloneService { - - async updateTraefikMeCertificate() { - const fullChainCert = await traefikMeAdapter.getFullChainCertificate(); - const privateKey = await traefikMeAdapter.getCurrentPrivateKey(); - - const projects = await dataAccess.client.project.findMany(); - const secretName = Constants.TRAEFIK_ME_SECRET_NAME; - - for (const project of projects) { - const secretManifest: V1Secret = { - metadata: { - name: secretName, - }, - data: { - 'tls.crt': Buffer.from(fullChainCert).toString('base64'), - 'tls.key': Buffer.from(privateKey).toString('base64'), - }, - type: 'kubernetes.io/tls', - }; - await secretService.saveSecret(project.id, secretName, secretManifest); - } - } - - configureSchedulingForTraefikMeCertificateUpdate() { - scheduleService.scheduleJob('traefik-me-certificate-update', '0 1 * * *', async () => { - await this.updateTraefikMeCertificate(); - }); - } -} - -const traefikMeDomainStandaloneService = new TraefikMeDomainStandaloneService(); -export default traefikMeDomainStandaloneService; \ No newline at end of file diff --git a/src/server/services/traefik-me-domain.service.ts b/src/server/services/traefik-me-domain.service.ts index 6c8d08d..c12ede5 100644 --- a/src/server/services/traefik-me-domain.service.ts +++ b/src/server/services/traefik-me-domain.service.ts @@ -1,21 +1,23 @@ import { ServiceException } from "@/shared/model/service.exception.model"; import paramService, { ParamService } from "./param.service"; +import { HostnameDnsProviderUtils } from "@/shared/utils/domain-dns-provider.utils"; - -class TraefikMeDomainService { +/** + * Service for Domaing DNS providers like traefik.me or sslip.io. + */ +class HostnameDnsProviderService { async getDomainForApp(appId: string, prefix?: string) { const publicIpv4 = await paramService.getString(ParamService.PUBLIC_IPV4_ADDRESS); if (!publicIpv4) { throw new ServiceException('Please set the main public IPv4 address in the QuickStack settings first.'); } - const traefikFriendlyIpv4 = publicIpv4.split('.').join('-'); if (prefix) { - return `${prefix}-${appId}-${traefikFriendlyIpv4}.traefik.me`; + return `${prefix}-${appId}-${HostnameDnsProviderUtils.getHostnameForIpAdress(publicIpv4)}`; } - return `${appId}-${traefikFriendlyIpv4}.traefik.me`; + return `${appId}-${HostnameDnsProviderUtils.getHostnameForIpAdress(publicIpv4)}`; } } -const traefikMeDomainService = new TraefikMeDomainService(); -export default traefikMeDomainService; \ No newline at end of file +const hostnameDnsProviderService = new HostnameDnsProviderService(); +export default hostnameDnsProviderService; \ No newline at end of file diff --git a/src/shared/utils/domain-dns-provider.utils.ts b/src/shared/utils/domain-dns-provider.utils.ts new file mode 100644 index 0000000..19bac75 --- /dev/null +++ b/src/shared/utils/domain-dns-provider.utils.ts @@ -0,0 +1,31 @@ +/** + * Utils for a provider wich supports domains like xip.io or traefik.me. + * In first versions of QuickStack traefik.me was used. Due to availability issues with traefik.me, + * it was replaced with https://sslip.io. + */ +export class HostnameDnsProviderUtils { + + public static readonly PROVIDER_HOSTNAME = 'sslip.io'; + private static readonly PROVIDER_HOSTNAME_SUFFIX = `.${this.PROVIDER_HOSTNAME}`; + + static getHostnameForIpAdress(ipv4Address: string): string { + const traefikFriendlyIpv4 = ipv4Address.split('.').join('-'); + return `${traefikFriendlyIpv4}.${this.PROVIDER_HOSTNAME}`; + } + + static isValidDnsProviderHostname(domain: string): boolean { + return this.containsDnsProviderHostname(domain) && domain.replace(this.PROVIDER_HOSTNAME_SUFFIX, '').includes('.'); + } + + static containsDnsProviderHostname(domain: string): boolean { + return domain.includes(this.PROVIDER_HOSTNAME_SUFFIX); + } + + static ipv4ToHex(ip: string): string { + return ip.split('.') + .map(octet => { + const hex = parseInt(octet, 10).toString(16); + return hex.padStart(2, '0'); + }).join(''); + } +} \ No newline at end of file diff --git a/src/shared/utils/traefik-me.utils.ts b/src/shared/utils/traefik-me.utils.ts deleted file mode 100644 index 6898061..0000000 --- a/src/shared/utils/traefik-me.utils.ts +++ /dev/null @@ -1,10 +0,0 @@ -export class TraefikMeUtils { - - static isValidTraefikMeDomain(domain: string): boolean { - return this.containesTraefikMeDomain(domain) && domain.split('.').length === 3; - } - - static containesTraefikMeDomain(domain: string): boolean { - return domain.includes('.traefik.me'); - } -} \ No newline at end of file