feat: custom certificate

This commit is contained in:
Andras Bacsai 2022-09-21 15:48:32 +02:00
parent 86ac6461d1
commit 90e639f119
30 changed files with 1000 additions and 367 deletions

View File

@ -21,6 +21,7 @@
"@fastify/env": "4.1.0",
"@fastify/jwt": "6.3.2",
"@fastify/static": "6.5.0",
"@fastify/multipart": "7.2.0",
"@iarna/toml": "2.2.5",
"@ladjs/graceful": "3.0.2",
"@prisma/client": "4.3.1",
@ -49,6 +50,7 @@
"p-all": "4.0.0",
"p-throttle": "5.0.0",
"public-ip": "6.0.1",
"pump": "^3.0.0",
"ssh-config": "4.1.6",
"strip-ansi": "7.0.1",
"unique-names-generator": "4.7.1"

View File

@ -8,6 +8,15 @@ datasource db {
url = env("COOLIFY_DATABASE_URL")
}
model Certificate {
id String @id @default(cuid())
key String
cert String
team Team[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
model Setting {
id String @id @default(cuid())
fqdn String? @unique
@ -70,6 +79,7 @@ model Team {
gitLabApps GitlabApp[]
service Service[]
users User[]
certificate Certificate[]
}
model TeamInvitation {

View File

@ -3,6 +3,7 @@ import cors from '@fastify/cors';
import serve from '@fastify/static';
import env from '@fastify/env';
import cookie from '@fastify/cookie';
import multipart from '@fastify/multipart';
import path, { join } from 'path';
import autoLoad from '@fastify/autoload';
import { asyncExecShell, createRemoteEngineConfiguration, getDomain, isDev, listSettings, prisma, version } from './lib/common';
@ -31,6 +32,7 @@ prisma.setting.findFirst().then(async (settings) => {
logger: settings?.isAPIDebuggingEnabled || false,
trustProxy: true
});
const schema = {
type: 'object',
required: ['COOLIFY_SECRET_KEY', 'COOLIFY_DATABASE_URL', 'COOLIFY_IS_ON'],
@ -88,13 +90,13 @@ prisma.setting.findFirst().then(async (settings) => {
return reply.status(200).sendFile('index.html');
});
}
fastify.register(multipart, { limits: { fileSize: 100000 } });
fastify.register(autoLoad, {
dir: join(__dirname, 'plugins')
});
fastify.register(autoLoad, {
dir: join(__dirname, 'routes')
});
fastify.register(cookie)
fastify.register(cors);
fastify.addHook('onRequest', async (request, reply) => {
@ -145,11 +147,15 @@ prisma.setting.findFirst().then(async (settings) => {
scheduler.workers.has('infrastructure') && scheduler.workers.get('infrastructure').postMessage("action:cleanupStorage")
}, isDev ? 6000 : 60000 * 10)
// checkProxies
// checkProxies and checkFluentBit
setInterval(async () => {
scheduler.workers.has('infrastructure') && scheduler.workers.get('infrastructure').postMessage("action:checkProxies")
scheduler.workers.has('infrastructure') && scheduler.workers.get('infrastructure').postMessage("action:checkFluentBit")
}, 10000)
setInterval(async () => {
scheduler.workers.has('infrastructure') && scheduler.workers.get('infrastructure').postMessage("action:copySSLCertificates")
}, 2000)
// cleanupPrismaEngines
// setInterval(async () => {
// scheduler.workers.has('infrastructure') && scheduler.workers.get('infrastructure').postMessage("action:cleanupPrismaEngines")

View File

@ -1,8 +1,9 @@
import { parentPort } from 'node:worker_threads';
import axios from 'axios';
import { compareVersions } from 'compare-versions';
import { asyncExecShell, cleanupDockerStorage, executeDockerCmd, isDev, prisma, startTraefikTCPProxy, generateDatabaseConfiguration, startTraefikProxy, listSettings, version, createRemoteEngineConfiguration } from '../lib/common';
import { asyncExecShell, cleanupDockerStorage, executeDockerCmd, isDev, prisma, startTraefikTCPProxy, generateDatabaseConfiguration, startTraefikProxy, listSettings, version, createRemoteEngineConfiguration, decrypt } from '../lib/common';
import { checkContainer } from '../lib/docker';
import fs from 'fs/promises'
async function autoUpdater() {
try {
const currentVersion = version;
@ -39,6 +40,46 @@ async function autoUpdater() {
}
} catch (error) { }
}
async function checkFluentBit() {
if (!isDev) {
const engine = '/var/run/docker.sock';
const { id } = await prisma.destinationDocker.findFirst({
where: { engine, network: 'coolify' }
});
const { found } = await checkContainer({ dockerId: id, container: 'coolify-fluentbit' });
if (!found) {
await asyncExecShell(`env | grep COOLIFY > .env`);
await asyncExecShell(`docker compose up -d fluent-bit`);
}
}
}
async function copySSLCertificates() {
try {
const certificates = await prisma.certificate.findMany({ include: { team: true } })
const teamIds = certificates.map(c => c.team.map(t => t.id)).flat()
const destinations = await prisma.destinationDocker.findMany({ where: { isCoolifyProxyUsed: true, teams: { some: { id: { in: teamIds } } } } })
for (const destination of destinations) {
if (destination.remoteEngine) {
// TODO: copy certificates to remote engine
} else {
for (const certificate of certificates) {
const { id, key, cert } = certificate
const decryptedKey = decrypt(key)
await asyncExecShell(`docker exec coolify-proxy sh -c 'mkdir -p /etc/traefik/acme/custom/'`)
await fs.writeFile(`/tmp/${id}-key.pem`, decryptedKey)
await fs.writeFile(`/tmp/${id}-cert.pem`, cert)
await asyncExecShell(`docker cp /tmp/${id}-key.pem coolify-proxy:/etc/traefik/acme/custom/`)
await asyncExecShell(`docker cp /tmp/${id}-cert.pem coolify-proxy:/etc/traefik/acme/custom/`)
await fs.rm(`/tmp/${id}-key.pem`)
await fs.rm(`/tmp/${id}-cert.pem`)
}
}
}
} catch (error) {
}
}
async function checkProxies() {
try {
const { default: isReachable } = await import('is-port-reachable');
@ -215,6 +256,14 @@ async function cleanupStorage() {
await checkProxies();
return;
}
if (message === 'action:checkFluentBit') {
await checkFluentBit();
return;
}
if (message === 'action:copySSLCertificates') {
await copySSLCertificates();
return;
}
if (message === 'action:autoUpdater') {
if (!status.cleanupStorage) {
status.autoUpdater = true

View File

@ -1,6 +1,9 @@
import { FastifyPluginAsync } from 'fastify';
import { checkUpdate, login, showDashboard, update, resetQueue, getCurrentUser, cleanupManually, restartCoolify } from './handlers';
import { GetCurrentUser } from './types';
import pump from 'pump'
import fs from 'fs'
import { asyncExecShell, encrypt, errorHandler, prisma } from '../../../lib/common';
export interface Update {
Body: { latestVersion: string }

View File

@ -1,8 +1,9 @@
import { promises as dns } from 'dns';
import { X509Certificate } from 'node:crypto';
import type { FastifyReply, FastifyRequest } from 'fastify';
import { checkDomainsIsValidInDNS, decrypt, encrypt, errorHandler, getDomain, isDNSValid, isDomainConfigured, listSettings, prisma } from '../../../../lib/common';
import { CheckDNS, CheckDomain, DeleteDomain, DeleteSSHKey, SaveSettings, SaveSSHKey } from './types';
import { CheckDNS, CheckDomain, DeleteDomain, DeleteSSHKey, OnlyIdInBody, SaveSettings, SaveSSHKey } from './types';
export async function listAllSettings(request: FastifyRequest) {
@ -16,8 +17,16 @@ export async function listAllSettings(request: FastifyRequest) {
unencryptedKeys.push({ id: key.id, name: key.name, privateKey: decrypt(key.privateKey), createdAt: key.createdAt })
}
}
const certificates = await prisma.certificate.findMany({ where: { team: { every: { id: teamId } } } })
let cns = [];
for (const certificate of certificates) {
const x509 = new X509Certificate(certificate.cert);
cns.push({ commonName: x509.subject.split('\n').find((s) => s.startsWith('CN=')).replace('CN=', ''), id: certificate.id, createdAt: certificate.createdAt })
}
return {
settings,
certificates: cns,
sshKeys: unencryptedKeys
}
} catch ({ status, message }) {
@ -118,7 +127,7 @@ export async function saveSSHKey(request: FastifyRequest<SaveSSHKey>, reply: Fas
return errorHandler({ status, message })
}
}
export async function deleteSSHKey(request: FastifyRequest<DeleteSSHKey>, reply: FastifyReply) {
export async function deleteSSHKey(request: FastifyRequest<OnlyIdInBody>, reply: FastifyReply) {
try {
const { id } = request.body;
await prisma.sshKey.delete({ where: { id } })
@ -126,4 +135,14 @@ export async function deleteSSHKey(request: FastifyRequest<DeleteSSHKey>, reply:
} catch ({ status, message }) {
return errorHandler({ status, message })
}
}
export async function deleteCertificates(request: FastifyRequest<OnlyIdInBody>, reply: FastifyReply) {
try {
const { id } = request.body;
await prisma.certificate.delete({ where: { id } })
return reply.code(201).send()
} catch ({ status, message }) {
return errorHandler({ status, message })
}
}

View File

@ -1,21 +1,58 @@
import { FastifyPluginAsync } from 'fastify';
import { checkDNS, checkDomain, deleteDomain, deleteSSHKey, listAllSettings, saveSettings, saveSSHKey } from './handlers';
import { CheckDNS, CheckDomain, DeleteDomain, DeleteSSHKey, SaveSettings, SaveSSHKey } from './types';
import { X509Certificate } from 'node:crypto';
import { encrypt, errorHandler, prisma } from '../../../../lib/common';
import { checkDNS, checkDomain, deleteCertificates, deleteDomain, deleteSSHKey, getCertificates, listAllSettings, saveSettings, saveSSHKey } from './handlers';
import { CheckDNS, CheckDomain, DeleteDomain, DeleteSSHKey, OnlyIdInBody, SaveSettings, SaveSSHKey } from './types';
const root: FastifyPluginAsync = async (fastify): Promise<void> => {
fastify.addHook('onRequest', async (request) => {
return await request.jwtVerify()
})
fastify.get('/', async (request) => await listAllSettings(request));
fastify.post<SaveSettings>('/', async (request, reply) => await saveSettings(request, reply));
fastify.delete<DeleteDomain>('/', async (request, reply) => await deleteDomain(request, reply));
fastify.addHook('onRequest', async (request) => {
return await request.jwtVerify()
})
fastify.get('/', async (request) => await listAllSettings(request));
fastify.post<SaveSettings>('/', async (request, reply) => await saveSettings(request, reply));
fastify.delete<DeleteDomain>('/', async (request, reply) => await deleteDomain(request, reply));
fastify.get<CheckDNS>('/check', async (request) => await checkDNS(request));
fastify.post<CheckDomain>('/check', async (request) => await checkDomain(request));
fastify.get<CheckDNS>('/check', async (request) => await checkDNS(request));
fastify.post<CheckDomain>('/check', async (request) => await checkDomain(request));
fastify.post<SaveSSHKey>('/sshKey', async (request, reply) => await saveSSHKey(request, reply));
fastify.delete<DeleteSSHKey>('/sshKey', async (request, reply) => await deleteSSHKey(request, reply));
fastify.post<SaveSSHKey>('/sshKey', async (request, reply) => await saveSSHKey(request, reply));
fastify.delete<OnlyIdInBody>('/sshKey', async (request, reply) => await deleteSSHKey(request, reply));
fastify.post('/upload', async (request) => {
try {
const teamId = request.user.teamId;
const certificates = await prisma.certificate.findMany({})
let cns = [];
for (const certificate of certificates) {
const x509 = new X509Certificate(certificate.cert);
cns.push(x509.subject.split('\n').find((s) => s.startsWith('CN=')).replace('CN=', ''))
}
const parts = await request.files()
let key = null
let cert = null
for await (const part of parts) {
const name = part.fieldname
if (name === 'key') key = (await part.toBuffer()).toString()
if (name === 'cert') cert = (await part.toBuffer()).toString()
}
const x509 = new X509Certificate(cert);
const cn = x509.subject.split('\n').find((s) => s.startsWith('CN=')).replace('CN=', '')
if (cns.includes(cn)) {
throw {
message: `A certificate with ${cn} common name already exists.`
}
}
await prisma.certificate.create({ data: { cert, key: encrypt(key), team: { connect: { id: teamId } } } })
return { message: 'Certificated uploaded' }
} catch ({ status, message }) {
return errorHandler({ status, message });
}
});
fastify.delete<OnlyIdInBody>('/certificate', async (request, reply) => await deleteCertificates(request, reply))
// fastify.get('/certificates', async (request) => await getCertificates(request))
};
export default root;

View File

@ -41,4 +41,9 @@ export interface DeleteSSHKey {
Body: {
id: string
}
}
export interface OnlyIdInBody {
Body: {
id: string
}
}

View File

@ -178,7 +178,19 @@ function configureMiddleware(
export async function traefikConfiguration(request, reply) {
try {
const sslpath = '/etc/traefik/acme/custom';
const certificates = await prisma.certificate.findMany()
let parsedCertificates = []
for (const certificate of certificates) {
parsedCertificates.push({
certFile: `${sslpath}/${certificate.id}-cert.pem`,
keyFile: `${sslpath}/${certificate.id}-key.pem`
})
}
const traefik = {
tls: {
certificates: parsedCertificates
},
http: {
routers: {},
services: {},

View File

@ -42,13 +42,14 @@
},
"type": "module",
"dependencies": {
"dayjs": "1.11.5",
"@sveltejs/adapter-static": "1.0.0-next.39",
"@tailwindcss/typography": "^0.5.7",
"cuid": "2.1.8",
"daisyui": "2.24.2",
"dayjs": "1.11.5",
"js-cookie": "3.0.1",
"p-limit": "4.0.0",
"svelte-file-dropzone": "^1.0.0",
"svelte-select": "4.4.7",
"sveltekit-i18n": "2.2.2"
}

View File

@ -39,7 +39,7 @@ export function getWebhookUrl(type: string) {
async function send({
method,
path,
data = {},
data = null,
headers,
timeout = 120000
}: {
@ -53,7 +53,7 @@ async function send({
const controller = new AbortController();
const id = setTimeout(() => controller.abort(), timeout);
const opts: any = { method, headers: {}, body: null, signal: controller.signal };
if (Object.keys(data).length > 0) {
if (data && Object.keys(data).length > 0) {
const parsedData = data;
for (const [key, value] of Object.entries(data)) {
if (value === '') {
@ -85,7 +85,9 @@ async function send({
if (dev && !path.startsWith('https://')) {
path = `${getAPIUrl()}${path}`;
}
if (method === 'POST' && data && !opts.body) {
opts.body = data;
}
const response = await fetch(`${path}`, opts);
clearTimeout(id);
@ -132,7 +134,7 @@ export function del(
export function post(
path: string,
data: Record<string, unknown>,
data: Record<string, unknown> | FormData,
headers?: Record<string, unknown>
): Promise<Record<string, any>> {
return send({ method: 'POST', path, data, headers });

View File

@ -4,7 +4,7 @@
export let type = 'info';
function success() {
if (type === 'success') {
return 'bg-gradient-to-r from-purple-500 via-pink-500 to-red-500';
return 'bg-coollabs';
}
}
</script>

View File

@ -0,0 +1,20 @@
<script lang="ts">
import { post } from '$lib/api';
let cert: any;
let key: any;
async function submitForm() {
const formData = new FormData();
formData.append('cert', cert[0]);
formData.append('key', key[0]);
await post('/upload', formData);
}
</script>
<form on:submit|preventDefault={submitForm}>
<label for="cert">Certificate</label>
<input id="cert" type="file" required name="cert" bind:files={cert} />
<label for="key">Private Key</label>
<input id="key" type="file" required name="key" bind:files={key} />
<br />
<input type="submit" />
</form>

View File

@ -226,7 +226,7 @@
<a
id="settings"
sveltekit:prefetch
href={$appSession.teamId === '0' ? '/settings/global' : '/settings/ssh-keys'}
href={$appSession.teamId === '0' ? '/settings/coolify' : '/settings/ssh'}
class="icons hover:text-settings"
class:text-settings={$page.url.pathname.startsWith('/settings')}
class:bg-coolgray-500={$page.url.pathname.startsWith('/settings')}
@ -393,7 +393,7 @@
<li>
<a
class="no-underline icons hover:text-black hover:bg-settings"
href={$appSession.teamId === '0' ? '/settings/global' : '/settings/ssh-keys'}
href={$appSession.teamId === '0' ? '/settings/coolify' : '/settings/ssh'}
class:bg-settings={$page.url.pathname.startsWith('/settings')}
class:text-black={$page.url.pathname.startsWith('/settings')}
>

View File

@ -218,7 +218,7 @@
id="git"
href="{application.gitSource.htmlUrl}/{application.repository}/tree/{application.branch}"
target="_blank"
class="w-6 h-6"
class="w-6 h-6 lg:w-10 lg:h-10"
>
{#if application.gitSource?.type === 'gitlab'}
<svg viewBox="0 0 128 128" class="icons">

View File

@ -61,7 +61,7 @@
<div class="pb-2 text-center font-bold">No SSH key found</div>
<div class="flex justify-center">
<a
href="/settings/ssh-keys"
href="/settings/ssh"
sveltekit:prefetch
class="add-icon bg-sky-600 hover:bg-sky-500"
>

View File

@ -3,19 +3,29 @@
import { appSession } from '$lib/store';
</script>
<div class="flex flex-col pt-4 space-y-6 px-10">
<ul class="menu bg-coolgray-200 rounded lg:w-52">
{#if $appSession.teamId === '0'}
<a
href="/settings/global"
class="sub-menu no-underline w-full"
class:sub-menu-active={$page.routeId === 'settings/global'}
<li
class="hover:bg-coollabs duration-150"
class:bordered={$page.url.pathname === '/settings/coolify'}
class:bg-coolgray-500={$page.url.pathname === '/settings/coolify'}
>
Global Settings
</a>
<a href="/settings/coolify" class="no-underline w-full">Coolify Settings</a>
</li>
{/if}
<a
href="/settings/ssh-keys"
class="sub-menu no-underline w-full"
class:sub-menu-active={$page.routeId === 'settings/ssh-keys'}>SSH Keys</a
<li
class="hover:bg-coollabs duration-150"
class:bordered={$page.url.pathname === '/settings/ssh'}
class:bg-coolgray-500={$page.url.pathname === '/settings/ssh'}
>
</div>
<a href="/settings/ssh" class="no-underline w-full">SSH Keys</a>
</li>
<li
class="hover:bg-coollabs duration-150"
class:bordered={$page.url.pathname === '/settings/certificates'}
class:bg-coolgray-400={$page.url.pathname === '/settings/certificates'}
>
<a href="/settings/certificates" class="no-underline w-full">SSL Certificates</a>
</li>
</ul>

View File

@ -1,7 +1,8 @@
<script context="module" lang="ts">
import { get } from '$lib/api';
import { page } from '$app/stores';
import type { Load } from '@sveltejs/kit';
import Menu from './_Menu.svelte';
export const load: Load = async () => {
try {
const response = await get(`/settings`);
@ -19,5 +20,12 @@
};
</script>
<slot />
<div class="flex flex-col lg:flex-row ">
<nav class="header flex flex-col w-full lg:w-52">
<div class="title pb-10">Settings</div>
<Menu />
</nav>
<div class="pt-0 lg:pt-24 px-5 lg:px-0 mx-auto">
<slot />
</div>
</div>

View File

@ -0,0 +1,144 @@
<script context="module" lang="ts">
import type { Load } from '@sveltejs/kit';
export const load: Load = async ({ stuff }) => {
try {
return {
props: {
...stuff
}
};
} catch (error: any) {
return {
status: 500,
error: new Error(error)
};
}
};
</script>
<script lang="ts">
export let certificates: any;
import { del, post } from '$lib/api';
import { errorNotification } from '$lib/common';
let loading = {
save: false
};
let isModalActive = false;
let cert: any = null;
let key: any = null;
async function handleSubmit() {
try {
const formData = new FormData();
formData.append('cert', cert[0]);
formData.append('key', key[0]);
await post('/settings/upload', formData);
return window.location.reload();
} catch (error) {
errorNotification(error);
return false;
}
}
async function deleteCertificate(id: string) {
const sure = confirm('Are you sure you would like to delete this SSH key?');
if (sure) {
try {
if (!id) return;
await del(`/settings/certificate`, { id });
return window.location.reload();
} catch (error) {
errorNotification(error);
return false;
}
}
}
</script>
<div class="title font-bold pb-3">SSL Certificates</div>
<div class="w-full lg:w-[50em]">
{#if certificates.length === 0}
<div class="text-sm">No SSL Certificate found</div>
<label
for="my-modal"
class="btn btn-sm bg-settings text-black mt-6"
on:click={() => (isModalActive = true)}>Add SSL Certificate</label
>
{:else}
<div class="mx-auto w-full p-6 bg-coolgray-100 rounded border-coolgray-300 border ">
<table class="table w-full">
<thead>
<tr>
<th>Common Name</th>
<th>CreatedAt</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
{#each certificates as cert}
<tr>
<td>{cert.commonName}</td>
<td>{cert.createdAt}</td>
<td
><button on:click={() => deleteCertificate(cert.id)} class="btn btn-sm bg-error"
>Delete</button
></td
>
</tr>
{/each}
</tbody>
</table>
<label
for="my-modal"
class="btn btn-sm bg-settings text-black mt-6"
on:click={() => (isModalActive = true)}>Add SSL Certificate</label
>
</div>
{/if}
</div>
{#if isModalActive}
<input type="checkbox" id="my-modal" class="modal-toggle" />
<div class="modal modal-bottom sm:modal-middle ">
<div class="modal-box rounded bg-coolgray-300 max-w-2xl">
<h3 class="font-bold text-lg">Add a new SSL Certificate</h3>
<p class="py-4">
SSL Certificates are used to secure your domain and allow you to use HTTPS. <br /><br />Once
you uploaded your certificate, Coolify will automatically configure it for you in the
background.
</p>
<div class="modal-action">
<form on:submit|preventDefault={handleSubmit} class="w-full">
<div class="flex flex-col justify-center">
<label for="cert">Certificate</label>
<div class="flex-1" />
<input
class="w-full bg-coolgray-100"
id="cert"
type="file"
required
name="cert"
bind:files={cert}
/>
<label for="key" class="pt-10">Private Key</label>
<input
class="w-full bg-coolgray-100"
id="key"
type="file"
required
name="key"
bind:files={key}
/>
</div>
<label for="my-modal">
<button type="submit" class="btn btn-sm bg-settings text-black mt-4">Upload</button
></label
>
<button on:click={() => (isModalActive = false)} type="button" class="btn btn-sm"
>Cancel</button
>
</form>
</div>
</div>
</div>
{/if}

View File

@ -18,6 +18,7 @@
<script lang="ts">
export let settings: any;
export let certificates: any;
import Setting from '$lib/components/Setting.svelte';
import { del, get, post } from '$lib/api';
import { browser } from '$app/env';
@ -26,6 +27,7 @@
import { asyncSleep, errorNotification, getDomain } from '$lib/common';
import Menu from './_Menu.svelte';
import Explainer from '$lib/components/Explainer.svelte';
import Upload from '$lib/components/Upload.svelte';
let isAPIDebuggingEnabled = settings.isAPIDebuggingEnabled;
let isRegistrationEnabled = settings.isRegistrationEnabled;
@ -194,180 +196,187 @@
}
</script>
<div class="flex space-x-1 p-6 font-bold">
<div class="mr-4 text-2xl tracking-tight">{$t('index.settings')}</div>
</div>
<div class="mx-auto w-full">
<div class="title font-bold pb-3">Coolify Settings</div>
<div class="mx-auto w-full p-4 bg-coolgray-100 rounded border-coolgray-300 border">
<div class="flex lg:flex-row flex-col">
<Menu />
<form on:submit|preventDefault={handleSubmit}>
<div class="flex flex-col lg:flex-row flex-wrap items-center space-x-3 justify-center lg:justify-start lg:py-0 px-4">
<div class="title font-bold">{$t('index.global_settings')}</div>
<div class="flex lg:flex-row lg:space-x-4 flex-col space-y-2 lg:space-y-0 py-4">
<button
class="btn btn-sm bg-settings text-black"
type="submit"
class:bg-orange-600={forceSave}
class:hover:bg-orange-400={forceSave}
disabled={loading.save}
>{loading.save
? $t('forms.saving')
: forceSave
? $t('forms.confirm_continue')
: $t('forms.save')}</button
>
{#if isFqdnSet}
<button
on:click|preventDefault={removeFqdn}
disabled={loading.remove}
class="btn btn-sm"
>{loading.remove ? $t('forms.removing') : $t('forms.remove_domain')}</button
>
{/if}
<button
on:click={restartCoolify}
class:loading={loading.restart}
class="btn btn-sm bg-red-600 hover:bg-red-500">Restart Coolify</button
>
<!-- <Upload />
{#if certificates.length > 0}
{#each certificates as cert}
<div>{cert.commonName}</div>
{/each}
{/if} -->
<form on:submit|preventDefault={handleSubmit}>
<div class="grid grid-flow-row gap-2 lg:px-10 px-2 pr-5">
<div class="grid grid-cols-2 items-center">
<div>
{$t('application.url_fqdn')}
<Explainer position="dropdown-bottom" explanation={$t('setting.ssl_explainer')} />
</div>
</div>
<div class="grid grid-flow-row gap-2 lg:px-10 px-2 pr-5">
<div class="grid grid-cols-2 items-center">
<div>
{$t('application.url_fqdn')}
<Explainer position="dropdown-bottom" explanation={$t('setting.ssl_explainer')} />
</div>
<input
class="w-full"
bind:value={fqdn}
readonly={!$appSession.isAdmin || isFqdnSet}
disabled={!$appSession.isAdmin || isFqdnSet}
on:input={resetView}
name="fqdn"
id="fqdn"
pattern="^https?://([a-z0-9]+(-[a-z0-9]+)*\.)+[a-z]{'{'}2,{'}'}$"
placeholder="{$t('forms.eg')}: https://coolify.io"
/>
<input
class="w-full"
bind:value={fqdn}
readonly={!$appSession.isAdmin || isFqdnSet}
disabled={!$appSession.isAdmin || isFqdnSet}
on:input={resetView}
name="fqdn"
id="fqdn"
pattern="^https?://([a-z0-9]+(-[a-z0-9]+)*\.)+[a-z]{'{'}2,{'}'}$"
placeholder="{$t('forms.eg')}: https://coolify.io"
/>
{#if forceSave}
<div class="flex-col space-y-2 pt-4 text-center">
{#if isNonWWWDomainOK}
{#if forceSave}
<div class="flex-col space-y-2 pt-4 text-center">
{#if isNonWWWDomainOK}
<button
class="btn btn-sm bg-success"
on:click|preventDefault={() => isDNSValid(getDomain(nonWWWDomain), false)}
>DNS settings for {nonWWWDomain} is OK, click to recheck.</button
>
{:else}
<button
class="btn btn-sm bg-error"
on:click|preventDefault={() => isDNSValid(getDomain(nonWWWDomain), false)}
>DNS settings for {nonWWWDomain} is invalid, click to recheck.</button
>
{/if}
{#if dualCerts}
{#if isWWWDomainOK}
<button
class="btn btn-sm bg-success"
on:click|preventDefault={() => isDNSValid(getDomain(nonWWWDomain), false)}
>DNS settings for {nonWWWDomain} is OK, click to recheck.</button
on:click|preventDefault={() =>
isDNSValid(getDomain(`www.${nonWWWDomain}`), true)}
>DNS settings for www.{nonWWWDomain} is OK, click to recheck.</button
>
{:else}
<button
class="btn btn-sm bg-error"
on:click|preventDefault={() => isDNSValid(getDomain(nonWWWDomain), false)}
>DNS settings for {nonWWWDomain} is invalid, click to recheck.</button
on:click|preventDefault={() =>
isDNSValid(getDomain(`www.${nonWWWDomain}`), true)}
>DNS settings for www.{nonWWWDomain} is invalid, click to recheck.</button
>
{/if}
{#if dualCerts}
{#if isWWWDomainOK}
<button
class="btn btn-sm bg-success"
on:click|preventDefault={() =>
isDNSValid(getDomain(`www.${nonWWWDomain}`), true)}
>DNS settings for www.{nonWWWDomain} is OK, click to recheck.</button
>
{:else}
<button
class="btn btn-sm bg-error"
on:click|preventDefault={() =>
isDNSValid(getDomain(`www.${nonWWWDomain}`), true)}
>DNS settings for www.{nonWWWDomain} is invalid, click to recheck.</button
>
{/if}
{/if}
</div>
{/if}
</div>
<div class="grid grid-cols-2 items-center">
<div>
{$t('forms.public_port_range')}
<Explainer explanation={$t('forms.public_port_range_explainer')} />
</div>
<div class="flex flex-row items-center space-x-2">
<input
class=" w-full px-2"
type="number"
bind:value={minPort}
min="1024"
max={maxPort}
/>
<p>-</p>
<input
class="w-full px-2"
type="number"
bind:value={maxPort}
min={minPort}
max="65543"
/>
</div>
</div>
<div class="grid grid-cols-2 items-center">
<Setting
id="isDNSCheckEnabled"
bind:setting={isDNSCheckEnabled}
title={$t('setting.is_dns_check_enabled')}
description={$t('setting.is_dns_check_enabled_explainer')}
on:click={() => changeSettings('isDNSCheckEnabled')}
/>
</div>
<div class="grid grid-cols-2 items-center">
<div>
Custom DNS servers <Explainer
explanation="You can specify a custom DNS server to verify your domains all over Coolify.<br><br>By default, the OS defined DNS servers are used."
/>
</div>
<input class="w-full" placeholder="1.1.1.1,8.8.8.8" bind:value={DNSServers} />
</div>
<div class="grid grid-cols-2 items-center">
<Setting
id="dualCerts"
dataTooltip={$t('setting.must_remove_domain_before_changing')}
disabled={isFqdnSet}
bind:setting={dualCerts}
title={$t('application.ssl_www_and_non_www')}
description={$t('setting.generate_www_non_www_ssl')}
on:click={() => !isFqdnSet && changeSettings('dualCerts')}
/>
</div>
<div class="grid grid-cols-2 items-center">
<Setting
id="isRegistrationEnabled"
bind:setting={isRegistrationEnabled}
title={$t('setting.registration_allowed')}
description={$t('setting.registration_allowed_explainer')}
on:click={() => changeSettings('isRegistrationEnabled')}
/>
</div>
<div class="grid grid-cols-2 items-center">
<Setting
id="isAPIDebuggingEnabled"
bind:setting={isAPIDebuggingEnabled}
title="API Debugging"
description="Enable API debugging. This will log all API requests and responses.<br><br>You need to restart the Coolify for this to take effect."
on:click={() => changeSettings('isAPIDebuggingEnabled')}
/>
</div>
{#if browser && $features.beta}
<div class="grid grid-cols-2 items-center">
<Setting
id="isAutoUpdateEnabled"
bind:setting={isAutoUpdateEnabled}
title={$t('setting.auto_update_enabled')}
description={$t('setting.auto_update_enabled_explainer')}
on:click={() => changeSettings('isAutoUpdateEnabled')}
/>
{/if}
</div>
{/if}
</div>
</form>
<div class="grid grid-cols-2 items-center">
<Setting
id="dualCerts"
dataTooltip={$t('setting.must_remove_domain_before_changing')}
disabled={isFqdnSet}
bind:setting={dualCerts}
title={$t('application.ssl_www_and_non_www')}
description={$t('setting.generate_www_non_www_ssl')}
on:click={() => !isFqdnSet && changeSettings('dualCerts')}
/>
</div>
<div class="grid grid-cols-2 items-center">
<div>
{$t('forms.public_port_range')}
<Explainer explanation={$t('forms.public_port_range_explainer')} />
</div>
<div class="flex flex-row items-center space-x-2">
<input
class=" w-full px-2"
type="number"
bind:value={minPort}
min="1024"
max={maxPort}
/>
<p>-</p>
<input
class="w-full px-2"
type="number"
bind:value={maxPort}
min={minPort}
max="65543"
/>
</div>
</div>
<div class="grid grid-cols-2 items-center">
<Setting
id="isDNSCheckEnabled"
bind:setting={isDNSCheckEnabled}
title={$t('setting.is_dns_check_enabled')}
description={$t('setting.is_dns_check_enabled_explainer')}
on:click={() => changeSettings('isDNSCheckEnabled')}
/>
</div>
<div class="grid grid-cols-2 items-center">
<div>
Custom DNS servers <Explainer
explanation="You can specify a custom DNS server to verify your domains all over Coolify.<br><br>By default, the OS defined DNS servers are used."
/>
</div>
<input class="w-full" placeholder="1.1.1.1,8.8.8.8" bind:value={DNSServers} />
</div>
<div class="grid grid-cols-2 items-center">
<Setting
id="isRegistrationEnabled"
bind:setting={isRegistrationEnabled}
title={$t('setting.registration_allowed')}
description={$t('setting.registration_allowed_explainer')}
on:click={() => changeSettings('isRegistrationEnabled')}
/>
</div>
<div class="grid grid-cols-2 items-center">
<Setting
id="isAPIDebuggingEnabled"
bind:setting={isAPIDebuggingEnabled}
title="API Debugging"
description="Enable API debugging. This will log all API requests and responses.<br><br>You need to restart the Coolify for this to take effect."
on:click={() => changeSettings('isAPIDebuggingEnabled')}
/>
</div>
{#if browser && $features.beta}
<div class="grid grid-cols-2 items-center">
<Setting
id="isAutoUpdateEnabled"
bind:setting={isAutoUpdateEnabled}
title={$t('setting.auto_update_enabled')}
description={$t('setting.auto_update_enabled_explainer')}
on:click={() => changeSettings('isAutoUpdateEnabled')}
/>
</div>
{/if}
</div>
<div
class="flex flex-col lg:flex-row flex-wrap items-center space-x-3 justify-center lg:justify-start lg:py-4 px-4 pb-4 lg:pb-4"
>
<div class="flex lg:flex-row lg:space-x-4 flex-col space-y-2 lg:space-y-0 px-6">
<button
class="btn btn-sm bg-settings text-black"
type="submit"
class:bg-orange-600={forceSave}
class:hover:bg-orange-400={forceSave}
class:loading={loading.save}
disabled={loading.save}
>{loading.save
? $t('forms.saving')
: forceSave
? $t('forms.confirm_continue')
: $t('forms.save')}</button
>
{#if isFqdnSet}
<button
on:click|preventDefault={removeFqdn}
disabled={loading.remove}
class="btn btn-sm"
>{loading.remove ? $t('forms.removing') : $t('forms.remove_domain')}</button
>
{/if}
<button
on:click={restartCoolify}
class:loading={loading.restart}
class="btn btn-sm bg-red-600 hover:bg-red-500">Restart Coolify</button
>
</div>
</div>
</form>
</div>
</div>

View File

@ -2,7 +2,7 @@
import { goto } from '$app/navigation';
import { appSession } from '$lib/store';
if ($appSession.teamId !== '0') {
goto('/settings/ssh-keys');
goto('/settings/ssh');
}
goto('/settings/global');
goto('/settings/coolify');
</script>

View File

@ -1,155 +0,0 @@
<script context="module" lang="ts">
import type { Load } from '@sveltejs/kit';
export const load: Load = async ({ stuff }) => {
try {
return {
props: {
...stuff
}
};
} catch (error: any) {
return {
status: 500,
error: new Error(error)
};
}
};
</script>
<script lang="ts">
export let sshKeys: any;
import { del, post } from '$lib/api';
import { t } from '$lib/translations';
import { errorNotification } from '$lib/common';
import Menu from './_Menu.svelte';
let loading = {
save: false
};
let isModalActive = false;
let newSSHKey = {
name: null,
privateKey: null
};
async function handleSubmit() {
try {
await post(`/settings/sshKey`, { ...newSSHKey });
return window.location.reload();
} catch (error) {
errorNotification(error);
return false;
}
}
async function deleteSSHKey(id: string) {
const sure = confirm('Are you sure you would like to delete this SSH key?');
if (sure) {
try {
if (!id) return;
await del(`/settings/sshKey`, { id });
return window.location.reload();
} catch (error) {
errorNotification(error);
return false;
}
}
}
</script>
<div class="flex space-x-1 p-6 font-bold">
<div class="mr-4 text-2xl tracking-tight">{$t('index.settings')}</div>
</div>
<div class="mx-auto w-full">
<div class="flex lg:flex-row flex-col">
<Menu />
<div class="flex flex-col mt-5">
<div
class="flex flex-col lg:flex-row flex-wrap items-center space-x-3 justify-center lg:justify-start lg:py-0 px-4 pb-4 lg:pb-4"
style="min-width: 83vw"
>
<div class="title font-bold">SSH Keys</div>
<button
on:click={() => (isModalActive = true)}
class="btn btn-sm bg-settings text-black"
disabled={loading.save}>New SSH Key</button
>
</div>
<div class="grid grid-flow-col gap-2 lg:px-10 px-6">
{#if sshKeys.length === 0}
<div class="text-sm ">No SSH keys found</div>
{:else}
{#each sshKeys as key}
<div class="box-selection group relative">
<div class="text-xl font-bold">{key.name}</div>
<div class="py-3 text-stone-600">Added on {key.createdAt}</div>
<button on:click={() => deleteSSHKey(key.id)} class="btn btn-sm bg-error"
>Delete</button
>
</div>
{/each}
{/if}
</div>
</div>
</div>
</div>
{#if isModalActive}
<div class="relative z-10" aria-labelledby="modal-title" role="dialog" aria-modal="true">
<div class="fixed inset-0 bg-coolgray-500 bg-opacity-75 transition-opacity" />
<div class="fixed z-10 inset-0 overflow-y-auto text-white">
<div class="flex items-end sm:items-center justify-center min-h-full p-4 text-center sm:p-0">
<form
on:submit|preventDefault={handleSubmit}
class="relative bg-coolblack rounded-lg px-4 pt-5 pb-4 text-left overflow-hidden shadow-xl transform transition-all sm:my-8 sm:max-w-lg sm:w-full sm:p-6 border border-coolgray-500"
>
<div class="hidden sm:block absolute top-0 right-0 pt-4 pr-4">
<button
on:click={() => (isModalActive = false)}
type="button"
class=" rounded-md text-white focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
>
<span class="sr-only">Close</span>
<svg
class="h-6 w-6"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width="2"
stroke="currentColor"
aria-hidden="true"
>
<path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12" />
</svg>
</button>
</div>
<div class="sm:flex sm:items-start">
<div class="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left">
<h3 class="text-lg leading-6 font-medium pb-4" id="modal-title">New SSH Key</h3>
<div class="text-xs text-stone-400">Add an SSH key to your Coolify instance.</div>
<div class="mt-2">
<label for="privateKey" class="pb-2">Key</label>
<textarea
id="privateKey"
required
bind:value={newSSHKey.privateKey}
class="w-full"
rows={15}
/>
</div>
<div class="mt-2">
<label for="name" class="pb-2">Name</label>
<input id="name" required bind:value={newSSHKey.name} class="w-full" />
</div>
</div>
</div>
<div class="mt-5 flex space-x-4 justify-end">
<button type="submit" class="btn btn-sm bg-success">Save</button>
<button on:click={() => (isModalActive = false)} type="button" class="btn btn-sm"
>Cancel</button
>
</div>
</form>
</div>
</div>
</div>
{/if}

View File

@ -0,0 +1,145 @@
<script context="module" lang="ts">
import type { Load } from '@sveltejs/kit';
export const load: Load = async ({ stuff }) => {
try {
return {
props: {
...stuff
}
};
} catch (error: any) {
return {
status: 500,
error: new Error(error)
};
}
};
</script>
<script lang="ts">
export let sshKeys: any;
import { del, post } from '$lib/api';
import { t } from '$lib/translations';
import { errorNotification } from '$lib/common';
import Menu from './_Menu.svelte';
let loading = {
save: false
};
let isModalActive = false;
let newSSHKey = {
name: null,
privateKey: null
};
async function handleSubmit() {
try {
await post(`/settings/sshKey`, { ...newSSHKey });
return window.location.reload();
} catch (error) {
errorNotification(error);
return false;
}
}
async function deleteSSHKey(id: string) {
const sure = confirm('Are you sure you would like to delete this SSH key?');
if (sure) {
try {
if (!id) return;
await del(`/settings/sshKey`, { id });
return window.location.reload();
} catch (error) {
errorNotification(error);
return false;
}
}
}
</script>
<div class="title font-bold pb-3">SSH Keys</div>
<div class="w-full lg:w-[50em]">
{#if sshKeys.length === 0}
<div class="text-sm">No SSH keys found</div>
<label
for="my-modal"
class="btn btn-sm bg-settings text-black mt-6"
on:click={() => (isModalActive = true)}>Add SSH Key</label
>
{:else}
<div
class="mx-auto w-full p-6 bg-coolgray-100 rounded border-coolgray-300 border "
>
<table class="table w-full">
<thead>
<tr>
<th>Name</th>
<th>CreatedAt</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
{#each sshKeys as key}
<tr>
<td>{key.name}</td>
<td>{key.createdAt}</td>
<td
><button on:click={() => deleteSSHKey(key.id)} class="btn btn-sm bg-error"
>Delete</button
></td
>
</tr>
{/each}
</tbody>
</table>
<label
for="my-modal"
class="btn btn-sm bg-settings text-black mt-6"
on:click={() => (isModalActive = true)}>Add SSH Key</label
>
</div>
{/if}
</div>
{#if isModalActive}
<input type="checkbox" id="my-modal" class="modal-toggle" />
<div class="modal modal-bottom sm:modal-middle">
<div class="modal-box rounded bg-coolgray-300">
<h3 class="font-bold text-lg">Add a new SSH Key to Coolify</h3>
<p class="py-4">
SSH Keys can be used to authenticate & execute commands on remote servers.
<br /><br />You can generate a new public/private key using the following command:
<br />
<br />
<code class="bg-coolgray-100 p-2 rounded">ssh-keygen -t rsa -b 4096</code>
</p>
<div class="modal-action">
<form on:submit|preventDefault={handleSubmit}>
<label for="name" class="">Name</label>
<input
id="name"
required
bind:value={newSSHKey.name}
class="w-full bg-coolgray-100"
/>
<label for="privateKey" class="pt-4">Private Key</label>
<textarea
id="privateKey"
placeholder="-----BEGIN OPENSSH PRIVATE KEY-----"
required
bind:value={newSSHKey.privateKey}
class="w-full bg-coolgray-100"
rows={15}
/>
<label for="my-modal">
<button type="submit" class="btn btn-sm bg-settings text-black mt-4">Save</button
></label
>
<button on:click={() => (isModalActive = false)} type="button" class="btn btn-sm"
>Cancel</button
>
</form>
</div>
</div>
</div>
{/if}

32
others/cert.pem Normal file
View File

@ -0,0 +1,32 @@
-----BEGIN CERTIFICATE-----
MIIFkTCCA3mgAwIBAgIUIlH6f/UeHodmg2TpNzo3o26MXDIwDQYJKoZIhvcNAQEL
BQAwWDELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM
GEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDERMA8GA1UEAwwIbm9kZS5jb20wHhcN
MjIwOTIxMDcxOTA4WhcNMjMwOTIxMDcxOTA4WjBYMQswCQYDVQQGEwJBVTETMBEG
A1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0cyBQdHkg
THRkMREwDwYDVQQDDAhub2RlLmNvbTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCC
AgoCggIBAJfjCoXtlFRmNqPQtgfvUaqy692IQLDTy0fahf+gS50D1+EN1+J1MJPm
DiVRGO6yA1/7Vl6BHE1hKkrBTsNoZx+SblFJgI3B5yMHAbhNazvE/re/xvjQIRt2
WetNKqbK6dtKot7ZkOR947nt8xacdO/8bXXuynHFXziuxoaJe8gxdD5OEKjyn5Fp
h372CjRERO2saSYqKz5tChWatUii+TG5b3HjeKIazEPVNLq3/XWFPYFumPkeArho
oPOQiRALz1zmzwXl6JOn1NmfQaStEy/ajsWpT89VESiOXFG4QnLrrij9HOk1i/b4
QtINriv0+GGS01uBDPTl3hTtnE2HQ+rgzhHddDDbL0ksDGMojh8k3bSu5V+T7/AG
TriHmF/vfW2jeERfNz4CBdBR41sQhfSb/g385szBKJ1ykqgIxvyWB882UApDBS5b
cuN5nspE4yMtJbznGnFj/UcsaMR1u/uJ1BKPHFS1EN24ESXFG9nUgtRznYGNqsJo
7+d+RldEUO9KdVUlOOhn5Y4+9nPrFpBfjEACJ/rpOhliX28+y1C039RPdH4+SMgF
fhm/JF7xEgAEwoTTi6QA0HkLo/Tm8qanONNjRa08Hz9t7ZPDmTF2bE3uTrS73Qlq
klAZvJ2WUziNGQYIO7rT0tRquD/i1DyW3ki3F82RNYIzDKIXWn1ZAgMBAAGjUzBR
MB0GA1UdDgQWBBS007k1C9m5MnM+UhHJU2QpiDw72zAfBgNVHSMEGDAWgBS007k1
C9m5MnM+UhHJU2QpiDw72zAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUA
A4ICAQBv2M5pBac1LnftEQUF2AMvWInUzpmEO2K2nkUrn7WskPIF7PkkIbef6r+e
Sy4O8rDSnBipbar7iQgllZQ8/ezlRCzAx39RHqTrslrjBTaPjGVumBKuUl8VpgZ+
AN5jcC7r/KrmRvBw+zD3CJDr3H8OvZ8+kBsOzEUrBZ9eTepYI/+4kD6/qRjg4ixx
4kdvwUMaUJKtCaDPiZ3PcIkrfM+XHwWuEWCqAwCUCPnn8/uUUy2Qs7dQqFI3pLDp
55R8yLVGK7hwczNtTbW5vxDjcR8CBNsATKyAK3YDS1KiHm81whz+n4oa6NFIi13d
3IDrsB4cxUU9HNwuUjOIVw+yN7+QTc0yO4RKAtf1j0QrYxIjQHRmCJVv1xdr4gXe
1kFDDCegBE/nKMWySUGZGn6lyZrdDCbCBJPnCY/F1iC9bMzQyixVgznRMfWCP3gg
Q+KgxuFPj3s8SCM9c/paryP7JaWY8MDk/yjDTaH/HZu0gufift9C8BeOZNXmcUAa
9NP24ijfP7/+QaxAYjVSd+N3PJml2+lEM+8kMMd1HKn9PXZ318RjPg+ndhtQt4Kv
svREmFsLQIZikE4slZWdfVaW4DAPXbFcS+vQwh7tB+5NqVTGj0I6mszNmX9kbVJc
yxWQEY2+v+2URYPiun7ql4p+Vh2ZWHDfcmgAyHIeaDRlPfiJRQ==
-----END CERTIFICATE-----

52
others/key.pem Normal file
View File

@ -0,0 +1,52 @@
-----BEGIN PRIVATE KEY-----
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
dVCwMJnAx42wCGRYCQnZtOpWVE40
-----END PRIVATE KEY-----

View File

@ -0,0 +1,33 @@
-----BEGIN CERTIFICATE-----
MIIFrTCCA5WgAwIBAgIUWscetQdy/o3JGjsR1LifMVHSFPYwDQYJKoZIhvcNAQEL
BQAwZTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM
GEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDEeMBwGA1UEAwwVbm9kZS5jb20sd3d3
Lm5vZGUuY29tMCAXDTIyMDkyMTA5MDgzOVoYDzIwNTAwMjA2MDkwODM5WjBlMQsw
CQYDVQQGEwJBVTETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50ZXJu
ZXQgV2lkZ2l0cyBQdHkgTHRkMR4wHAYDVQQDDBVub2RlLmNvbSx3d3cubm9kZS5j
b20wggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC91LQflL+X2YU1oiDl
V9xGKtkUM7KOZgCQSkwDdkxF6fzjOAkjnekWkPRvPijWLuOLuqbMYZe8uEw06DgL
JlwcYBKC8hrBsaT2QpbzDuetzsYeudfBVYfLSHiUOvP+pVf2nurprbHPxdWLmh8v
HFLmTSVAH0sDDRb3gt2S7OnrGjw4fSjfS/5lFzGdV4ic1vFt2bB9Fv+D5pKPO/Zm
O9XIhxZ8/LBNTRhR6TEWn3h0HMVqTMKVm3cjDB+xelivZ8LCV7sO2Y/ZS70ByI2K
op4e7H50uNh4WjhSXNBSabLNw1gC/cvBLn0wypGtKgRt26Nj4FNvpfoXfbwCDkgJ
SFyrKLaL1YraNZOxIWgcQs2dKoVYKQfLBQeL+uSe26OY5eXZUcq4cmQjzeahF0MM
7STR6c14QuR0ppgInpuLGFVmcnCQBhpR7P2Y3ITIGqQto/ZHAWvx+BgkdUhhKoFO
6YUvBiG14CznOVct2TStt/RyW+Z3NwOd8Bh0MA5TFEdv4wfkVBkd7d1ICvF9+hCz
2LbVztZ4lkqjSooAFcz/xm1dXSNwhRwwG6sOAm4XC+7Pt6Oe5pq9ahFQKW+zkLr2
TKQ8Ppr7wQW6IrrWola4MUeQ+DE8l4QKlLusvejPTdRsnnEt0RUPoWcACB1e0CzO
3zOd300xy0ESeeHLLFuaG5FdOwIDAQABo1MwUTAdBgNVHQ4EFgQUhZdMK4poQSOG
55sANxJlTMTCtEcwHwYDVR0jBBgwFoAUhZdMK4poQSOG55sANxJlTMTCtEcwDwYD
VR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAgEAJNPRgCNHbSR+UYKFSet4
XscYjtxb59QxFqN+AqUiEh38SVzWo2nhnDZxiDsTK2nA0qSrseoMTnTmWJuEf+io
n3XmUNgalxzV0QGMhQZPT3hyoPditwc7easnm2Jwd10kkhMdlXnfy3lgHPIMH6/E
0jh3sSuEora9Q3jIdobRQALrHI++lsLdOcJy0A2LKkVWbvftz6B2cDC6M+QhwPGo
QNIuI8Cl2J89mNsDtZRohbA2/iUGE2Y47m9OD8snLOOZmhiCHsq82AYEt0wAqPI1
biPvN/F+S4lMj71ue77BOg6sKKvotSgJQvyUO4iAouHoxxo5luT1nz2i2ofdvHFd
Lm3eth9hEykOEi7AIJHSlXcFEdlEaP+ohOcTDoaoZc4kqYxuEDpC6otaQhHvqS/D
0JGjdVTw7q0msfzG/7a5k4hklgB4JHrzWS++sCFKAu6LYnsVVFKu6P9qQxFv86qi
xpWX9sj+7+qw1qPjSNKx9S6Lx+2vMIG/2l1rsJIKMLC4ndnT3bjwme0YS0/iOEi3
EQT//Mb6Hg1R4r2OlvfYSt+pGOa64UwgqHj8wOLIkb/txsF3mQDbDE16tByih83E
Wl44xDuoTuCZsvwNh9PJtg1XRaIdXwZFauRwV3nyLREeDX3EESmOyXYZZljcS9/K
Gw265N/AwFB26FAC4Mof6FI=
-----END CERTIFICATE-----

View File

@ -0,0 +1,52 @@
-----BEGIN PRIVATE KEY-----
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
oRPGJSpHWgCoMeHba1Q7nGLmwvHg9w==
-----END PRIVATE KEY-----

32
others/wildcard_cert.pem Normal file
View File

@ -0,0 +1,32 @@
-----BEGIN CERTIFICATE-----
MIIFlzCCA3+gAwIBAgIUJQM7MYyRjtvlVYzVqog0JGigp7swDQYJKoZIhvcNAQEL
BQAwWjELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM
GEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDETMBEGA1UEAwwKKi5ub2RlLmNvbTAg
Fw0yMjA5MjEwOTA3MDVaGA8yMDUwMDIwNjA5MDcwNVowWjELMAkGA1UEBhMCQVUx
EzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoMGEludGVybmV0IFdpZGdpdHMg
UHR5IEx0ZDETMBEGA1UEAwwKKi5ub2RlLmNvbTCCAiIwDQYJKoZIhvcNAQEBBQAD
ggIPADCCAgoCggIBAK62Sk9JUSQXv+wk5FztR1L0H8IgK34th5dOADV0zwY0Olnm
ukkjI2cljNaY3DgfhGdv6m+3sozMliRjtmOkLtWoGsK8Bwv6lZrF6GPy2N0IeIQ5
tddyaENm5ua7rG3kgYCBItadOg33A6eUWFYmZi5K7E9fpHUS7JCi+tYAaHqaESfI
z2wAzlLo+nyfn6AzAWkcdtsNtwCJOXbC6gvJIjPwdsM9wJ77m1k32Dwo2ZJ4BAQm
JoHNZaIy3pytZWaoIHnKwioIF/xKALls6A8TQqetPFxUfGQAsKqMFF/nbzn+S4GE
ltufX/E1KECNiircHWC998XqLoaBim6d9+MKcyBZqG+Gr6k4JGLqw+FTVdCKo0hO
L0GWA+TWZbDtCBmR2fEI8UJ3wnmDjzGir/9Yi/ugmovPQ5lpQIhEQWTQ/zGgVP38
xVTYEPAdVVjFRnFhLlmRDedYo552A68KBlZPPotra5OustB6Rop9z4kNxH7mGDQg
GdS6B0XbJUSk4325+7OQZpp30ursh+f4X0dVyC4maPjUC0YZuMbvoTLwwmu7zrtM
NpvlLhcdBaBfKLdU6/bAxd7V8VHEXJOxnftPwmk151TxC3eHvHX85scNZU/nBl3R
VyNgumcjsSzfb3z2NzHlBH5YKOmGWnDRsnMhMf7qO3k2YI0/XiV+Yd4jHGWvAgMB
AAGjUzBRMB0GA1UdDgQWBBS76NBkLueNtmDSt+kF9y3Q75VuUDAfBgNVHSMEGDAW
gBS76NBkLueNtmDSt+kF9y3Q75VuUDAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3
DQEBCwUAA4ICAQAhkFEXcDVFZmHtAJ3KY5t2OOFBKV1JRU2tbcV11qTbXjCPASz7
AejV+rVqHYDnHMo/lJ0l+OvDl/UDU8wIQo/eBqbba96ClvSrEnEi7Ekx5sfPrLBP
Q1Mg3aKv9PMtOME/QAc4JExjHBz2ZGffM9EcF7bv4hIqeGOCmEV6yXSYj3PDuLL+
3F8LF39eKjT5TldrhAkk+sAsSdLLu14oqlbUW61XRrqZaU4PprYdYOGEjUlWTrOH
L60Qy8EsqGzzohU7wljm0U1m90rGZGYdaoRVNPwuvX8Olb72BQZU8OtUlJbFCHIE
nPpHSN9sKtS1SHme3nYo/dOcuHhjXwoBXiWESEVCsJCAwubCKpj8YlB2JQqHKNUc
fLQoueR6gBrsAP7cXAdEUnXX91cLYbetq09DXquV/b/YDctHuhRJO1kHrollQaWe
XCFHpy3wJjxzoOMDrp1wsVidG8ogTkWxAwW1zbH/9tIW8cbvuVvoUXnzCO3CPBU6
uOuOFLNFl2JJcF0Uddsg62LEOJkiVwhqmBFPWU9ywQ5lFrWK13uZjcnpeP7KWt3+
0NqsRLX3uwU6yBd3aNcmps4yUPAjKbHCXm54/VBjuv1GOoBFWGrI0B5RjuZ+PZUH
lYRmOVpgA3EioOvMukDE/p96Her3MISxplyU0loUlgXBbTj7VI212FQefg==
-----END CERTIFICATE-----

52
others/wildcard_key.pem Normal file
View File

@ -0,0 +1,52 @@
-----BEGIN PRIVATE KEY-----
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
SOS2HtRM0ZdpfQqThSDEKSQ0xkt6
-----END PRIVATE KEY-----

65
pnpm-lock.yaml generated
View File

@ -20,6 +20,7 @@ importers:
'@fastify/cors': 8.1.0
'@fastify/env': 4.1.0
'@fastify/jwt': 6.3.2
'@fastify/multipart': 7.2.0
'@fastify/static': 6.5.0
'@iarna/toml': 2.2.5
'@ladjs/graceful': 3.0.2
@ -60,6 +61,7 @@ importers:
prettier: 2.7.1
prisma: 4.3.1
public-ip: 6.0.1
pump: ^3.0.0
rimraf: 3.0.2
ssh-config: 4.1.6
strip-ansi: 7.0.1
@ -73,6 +75,7 @@ importers:
'@fastify/cors': 8.1.0
'@fastify/env': 4.1.0
'@fastify/jwt': 6.3.2
'@fastify/multipart': 7.2.0
'@fastify/static': 6.5.0
'@iarna/toml': 2.2.5
'@ladjs/graceful': 3.0.2
@ -102,6 +105,7 @@ importers:
p-all: 4.0.0
p-throttle: 5.0.0
public-ip: 6.0.1
pump: 3.0.0
ssh-config: 4.1.6
strip-ansi: 7.0.1
unique-names-generator: 4.7.1
@ -161,6 +165,7 @@ importers:
prettier-plugin-svelte: 2.7.0
svelte: 3.50.0
svelte-check: 2.9.0
svelte-file-dropzone: ^1.0.0
svelte-preprocess: 4.10.7
svelte-select: 4.4.7
sveltekit-i18n: 2.2.2
@ -177,6 +182,7 @@ importers:
dayjs: 1.11.5
js-cookie: 3.0.1
p-limit: 4.0.0
svelte-file-dropzone: 1.0.0
svelte-select: 4.4.7
sveltekit-i18n: 2.2.2_svelte@3.50.0
devDependencies:
@ -200,7 +206,7 @@ importers:
svelte: 3.50.0
svelte-check: 2.9.0_shxyscafa2tdzd4z2tgnnrhyyu
svelte-preprocess: 4.10.7_gzukngxpmlbzkiu3cz7vpamp3y
tailwindcss: 3.1.8
tailwindcss: 3.1.8_postcss@8.4.16
tailwindcss-scrollbar: 0.1.0_tailwindcss@3.1.8
tslib: 2.4.0
typescript: 4.8.2
@ -314,6 +320,13 @@ packages:
pkg-up: 3.1.0
dev: false
/@fastify/busboy/1.1.0:
resolution: {integrity: sha512-Fv854f94v0CzIDllbY3i/0NJPNBRNLDawf3BTYVGCe9VrIIs3Wi7AFx24F9NzCxdf0wyx/x0Q9kEVnvDOPnlxA==}
engines: {node: '>=10.17.0'}
dependencies:
text-decoding: 1.0.0
dev: false
/@fastify/cookie/8.1.0:
resolution: {integrity: sha512-+BxpyK4KLAjDpXdWxOjl8yaKtAoqYZR+CE9+cNtdMDoACb8hcpGx9npkrdINl62EpCu06oIPluq8A4NUsi78ZA==}
dependencies:
@ -328,6 +341,10 @@ packages:
mnemonist: 0.39.2
dev: false
/@fastify/deepmerge/1.1.0:
resolution: {integrity: sha512-E8Hfdvs1bG6u0N4vN5Nty6JONUfTdOciyD5rn8KnEsLKIenvOVcr210BQR9t34PRkNyjqnMLGk3e0BsaxRdL+g==}
dev: false
/@fastify/env/4.1.0:
resolution: {integrity: sha512-9l+JTUiFWSwb9dGSeR46aDWBjrAg8lJeqMjbotG5/8Ho90+qzRbt8kdSnVhLm5k6HcqXcBaBAT/6cImRhRq0VQ==}
dependencies:
@ -355,6 +372,19 @@ packages:
steed: 1.1.3
dev: false
/@fastify/multipart/7.2.0:
resolution: {integrity: sha512-LwfwbCLy30Be1pa5q7F8xCTygxJdEWkfkQhD4OWZ13+vMH4tP/6Bu3OkSTlFatxLAmbEl2UpHLf7CU7w7csRIw==}
dependencies:
'@fastify/busboy': 1.1.0
'@fastify/deepmerge': 1.1.0
'@fastify/error': 3.0.0
end-of-stream: 1.4.4
fastify-plugin: 4.2.1
hexoid: 1.0.0
secure-json-parse: 2.4.0
stream-wormhole: 1.1.0
dev: false
/@fastify/static/6.5.0:
resolution: {integrity: sha512-WEk6iqgejA6ivjkvbJ47A+uMci225z5lZwLXCXZS3ZYR/kYje1gzzarkKKGL6TWpBw6smkOzxA7dfEoY0347Nw==}
dependencies:
@ -584,7 +614,7 @@ packages:
lodash.isplainobject: 4.0.6
lodash.merge: 4.6.2
postcss-selector-parser: 6.0.10
tailwindcss: 3.1.8
tailwindcss: 3.1.8_postcss@8.4.16
dev: false
/@tsconfig/node10/1.0.8:
@ -2334,7 +2364,7 @@ packages:
css-selector-tokenizer: 0.8.0
postcss: 8.4.16
postcss-js: 4.0.0_postcss@8.4.16
tailwindcss: 3.1.8
tailwindcss: 3.1.8_postcss@8.4.16
transitivePeerDependencies:
- ts-node
dev: false
@ -3213,6 +3243,13 @@ packages:
flat-cache: 3.0.4
dev: true
/file-selector/0.2.4:
resolution: {integrity: sha512-ZDsQNbrv6qRi1YTDOEWzf5J2KjZ9KMI1Q2SGeTkCJmNNW25Jg4TW4UMcmoqcg4WrAyKRcpBXdbWRxkfrOzVRbA==}
engines: {node: '>= 10'}
dependencies:
tslib: 2.4.0
dev: false
/fill-range/7.0.1:
resolution: {integrity: sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==}
engines: {node: '>=8'}
@ -5554,6 +5591,11 @@ packages:
reusify: 1.0.4
dev: false
/stream-wormhole/1.1.0:
resolution: {integrity: sha512-gHFfL3px0Kctd6Po0M8TzEvt3De/xu6cnRrjlfYNhwbhLPLwigI2t1nc6jrzNuaYg5C4YF78PPFuQPzRiqn9ew==}
engines: {node: '>=4.0.0'}
dev: false
/string-width/4.2.3:
resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==}
engines: {node: '>=8'}
@ -5728,6 +5770,12 @@ packages:
- sugarss
dev: true
/svelte-file-dropzone/1.0.0:
resolution: {integrity: sha512-F2DN+wN2w7bKuUJFYQOFsdtTgaohQ/rNKau5m5n2l3LHJRRIccYS4wpq8f6dz/h5aSxYse3oRclmYdW6FWAfjw==}
dependencies:
file-selector: 0.2.4
dev: false
/svelte-heros/2.3.5:
resolution: {integrity: sha512-08PdccaeRPP1pVa90AGieTwGzrNtXpC1Fry+i95OTvcR3xbGRU/hxK4rnaFYvGgk1Pxj9YT6GKGTEX8uXE9XJQ==}
dev: true
@ -5816,13 +5864,15 @@ packages:
peerDependencies:
tailwindcss: '>= 2.x.x'
dependencies:
tailwindcss: 3.1.8
tailwindcss: 3.1.8_postcss@8.4.16
dev: true
/tailwindcss/3.1.8:
/tailwindcss/3.1.8_postcss@8.4.16:
resolution: {integrity: sha512-YSneUCZSFDYMwk+TGq8qYFdCA3yfBRdBlS7txSq0LUmzyeqRe3a8fBQzbz9M3WS/iFT4BNf/nmw9mEzrnSaC0g==}
engines: {node: '>=12.13.0'}
hasBin: true
peerDependencies:
postcss: ^8.0.9
dependencies:
arg: 5.0.2
chokidar: 3.5.3
@ -5869,6 +5919,10 @@ packages:
readable-stream: 3.6.0
dev: false
/text-decoding/1.0.0:
resolution: {integrity: sha512-/0TJD42KDnVwKmDK6jj3xP7E2MG7SHAOG4tyTgyUCRPdHwvkquYNLEQltmdMa3owq3TkddCVcTsoctJI8VQNKA==}
dev: false
/text-table/0.2.0:
resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==}
dev: true
@ -5986,7 +6040,6 @@ packages:
/tslib/2.4.0:
resolution: {integrity: sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==}
dev: true
/tsutils/3.21.0_typescript@4.8.2:
resolution: {integrity: sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==}