vscodeserver + minio

This commit is contained in:
Andras Bacsai
2022-10-18 14:34:10 +02:00
parent 12a1aeb0f8
commit f1ea01e709
5 changed files with 161 additions and 144 deletions

View File

@@ -1,4 +1,4 @@
import { decrypt, encrypt, generatePassword, prisma } from "./lib/common"; import { decrypt, encrypt, prisma } from "./lib/common";
import { includeServices } from "./lib/services/common"; import { includeServices } from "./lib/services/common";
@@ -10,36 +10,27 @@ export async function migrateServicesToNewTemplate() {
if (service.type === 'plausibleanalytics' && service.plausibleAnalytics) await plausibleAnalytics(service) if (service.type === 'plausibleanalytics' && service.plausibleAnalytics) await plausibleAnalytics(service)
if (service.type === 'fider' && service.fider) await fider(service) if (service.type === 'fider' && service.fider) await fider(service)
if (service.type === 'minio' && service.minio) await minio(service) if (service.type === 'minio' && service.minio) await minio(service)
if (service.type === 'vscode' && service.vscodeserver) await vscodeserver(service)
} }
} catch (error) { } catch (error) {
console.log(error) console.log(error)
} }
} }
async function migrateSettings(settings: any[], service: any) {
for (const setting of settings) { async function vscodeserver(service: any) {
if (!setting) continue; const { password } = service.minio
const [name, value] = setting.split('@@@')
console.log('Migrating setting', name, value) const secrets = [
await prisma.serviceSetting.findFirst({ where: { name, serviceId: service.id } }) || await prisma.serviceSetting.create({ data: { name, value, service: { connect: { id: service.id } } } }) `PASSWORD@@@${password}`,
} ]
} await migrateSecrets(secrets, service);
async function migrateSecrets(secrets: any[], service: any) {
for (const secret of secrets) { // Remove old service data
if (!secret) continue; // await prisma.service.update({ where: { id: service.id }, data: { vscodeserver: { delete: true } } })
const [name, value] = secret.split('@@@')
console.log('Migrating secret', name, value)
await prisma.serviceSecret.findFirst({ where: { name, serviceId: service.id } }) || await prisma.serviceSecret.create({ data: { name, value, service: { connect: { id: service.id } } } })
}
}
async function createVolumes(volumes: any[], service: any) {
for (const volume of volumes) {
const [volumeName, path, containerId] = volume.split('@@@')
await prisma.servicePersistentStorage.findFirst({ where: { volumeName, serviceId: service.id } }) || await prisma.servicePersistentStorage.create({ data: { volumeName, path, containerId, predefined: true, service: { connect: { id: service.id } } } })
}
} }
async function minio(service: any) { async function minio(service: any) {
const { rootUser, rootUserPassword, apiFqdn } = service.fider const { rootUser, rootUserPassword, apiFqdn } = service.minio
const secrets = [ const secrets = [
`MINIO_ROOT_PASSWORD@@@${rootUserPassword}`, `MINIO_ROOT_PASSWORD@@@${rootUserPassword}`,
@@ -55,7 +46,6 @@ async function minio(service: any) {
// Remove old service data // Remove old service data
// await prisma.service.update({ where: { id: service.id }, data: { minio: { delete: true } } }) // await prisma.service.update({ where: { id: service.id }, data: { minio: { delete: true } } })
} }
async function fider(service: any) { async function fider(service: any) {
const { postgresqlUser, postgresqlPassword, postgresqlDatabase, jwtSecret, emailNoreply, emailMailgunApiKey, emailMailgunDomain, emailMailgunRegion, emailSmtpHost, emailSmtpPort, emailSmtpUser, emailSmtpPassword, emailSmtpEnableStartTls } = service.fider const { postgresqlUser, postgresqlPassword, postgresqlDatabase, jwtSecret, emailNoreply, emailMailgunApiKey, emailMailgunDomain, emailMailgunRegion, emailSmtpHost, emailSmtpPort, emailSmtpUser, emailSmtpPassword, emailSmtpEnableStartTls } = service.fider
@@ -83,7 +73,7 @@ async function fider(service: any) {
await migrateSecrets(secrets, service); await migrateSecrets(secrets, service);
// Remove old service data // Remove old service data
await prisma.service.update({ where: { id: service.id }, data: { fider: { delete: true } } }) // await prisma.service.update({ where: { id: service.id }, data: { fider: { delete: true } } })
} }
async function plausibleAnalytics(service: any) { async function plausibleAnalytics(service: any) {
@@ -114,5 +104,28 @@ async function plausibleAnalytics(service: any) {
await createVolumes(volumes, service); await createVolumes(volumes, service);
// Remove old service data // Remove old service data
await prisma.service.update({ where: { id: service.id }, data: { plausibleAnalytics: { delete: true } } }) // await prisma.service.update({ where: { id: service.id }, data: { plausibleAnalytics: { delete: true } } })
}
async function migrateSettings(settings: any[], service: any) {
for (const setting of settings) {
if (!setting) continue;
const [name, value] = setting.split('@@@')
console.log('Migrating setting', name, value)
await prisma.serviceSetting.findFirst({ where: { name, serviceId: service.id } }) || await prisma.serviceSetting.create({ data: { name, value, service: { connect: { id: service.id } } } })
}
}
async function migrateSecrets(secrets: any[], service: any) {
for (const secret of secrets) {
if (!secret) continue;
const [name, value] = secret.split('@@@')
console.log('Migrating secret', name, value)
await prisma.serviceSecret.findFirst({ where: { name, serviceId: service.id } }) || await prisma.serviceSecret.create({ data: { name, value, service: { connect: { id: service.id } } } })
}
}
async function createVolumes(volumes: any[], service: any) {
for (const volume of volumes) {
const [volumeName, path, containerId] = volume.split('@@@')
await prisma.servicePersistentStorage.findFirst({ where: { volumeName, serviceId: service.id } }) || await prisma.servicePersistentStorage.create({ data: { volumeName, path, containerId, predefined: true, service: { connect: { id: service.id } } } })
}
} }

View File

@@ -1,4 +1,41 @@
export default [ export default [
{
"templateVersion": "1.0.0",
"serviceDefaultVersion": "4.7.1",
"name": "codeserver",
"displayName": "Code Server",
"description": "code-server by Coder is VS Code running on a remote server, accessible through the browser.",
"services": {
"$$id": {
"name": "Code Server",
"documentation": "Taken from https://github.com/coder/code-server/. ",
"depends_on": [],
"image": "codercom/code-server:$$core_version",
"volumes": [
"$$id-config-data:/home/coder/.local/share/code-server",
"$$id-vscodeserver-data:/home/coder",
"$$id-keys-directory:/root/.ssh",
"$$id-theme-and-plugin-directory:/root/.local/share/code-server"
],
"environment": [
"PASSWORD=$$secret_password",
],
"ports": [
"8080"
]
}
},
"variables": [
{
"id": "$$secret_password",
"name": "PASSWORD",
"label": "Password",
"defaultValue": "$$generate_password",
"description": ""
}
]
},
{ {
"templateVersion": "1.0.0", "templateVersion": "1.0.0",
"serviceDefaultVersion": "RELEASE.2022-10-15T19-57-03Z", "serviceDefaultVersion": "RELEASE.2022-10-15T19-57-03Z",
@@ -20,8 +57,7 @@ export default [
"MINIO_BROWSER_REDIRECT_URL=$$config_minio_browser_redirect_url", "MINIO_BROWSER_REDIRECT_URL=$$config_minio_browser_redirect_url",
"MINIO_DOMAIN=$$config_minio_domain", "MINIO_DOMAIN=$$config_minio_domain",
"MINIO_ROOT_USER=$$config_minio_root_user", "MINIO_ROOT_USER=$$config_minio_root_user",
"MINIO_ROOT_PASSWORD=$$secret_minio_root_user_password", "MINIO_ROOT_PASSWORD=$$secret_minio_root_user_password"
"MINIO_REGION_NAME=$$config_minio_region_name",
], ],
"ports": [ "ports": [
"9001", "9001",
@@ -33,9 +69,12 @@ export default [
{ {
"id": "$$config_server_url", "id": "$$config_server_url",
"name": "MINIO_SERVER_URL", "name": "MINIO_SERVER_URL",
"label": "Server URL", "label": "Server/Console URL",
"defaultValue": "", "defaultValue": "",
"description": "", "description": "",
"extras": {
"required": true
}
}, },
{ {
"id": "$$config_browser_redirect_url", "id": "$$config_browser_redirect_url",
@@ -64,14 +103,7 @@ export default [
"label": "Root User Password", "label": "Root User Password",
"defaultValue": "$$generate_password", "defaultValue": "$$generate_password",
"description": "", "description": "",
}, }
{
"id": "$$config_minio_region_name",
"name": "MINIO_REGION_NAME",
"label": "Region Name",
"defaultValue": "us-east-1",
"description": "",
},
] ]
}, },
{ {

View File

@@ -128,10 +128,10 @@ export async function parseAndFindServiceTemplates(service: any, workdir?: strin
const label = foundTemplate.variables.find(v => v.name === envKey)?.label const label = foundTemplate.variables.find(v => v.name === envKey)?.label
const description = foundTemplate.variables.find(v => v.name === envKey)?.description const description = foundTemplate.variables.find(v => v.name === envKey)?.description
const defaultValue = foundTemplate.variables.find(v => v.name === envKey)?.defaultValue const defaultValue = foundTemplate.variables.find(v => v.name === envKey)?.defaultValue
const isVisibleOnUI = foundTemplate.variables.find(v => v.name === envKey)?.extras?.isVisibleOnUI const extras = foundTemplate.variables.find(v => v.name === envKey)?.extras
if (envValue.startsWith('$$config') || isVisibleOnUI) { if (envValue.startsWith('$$config')) {
parsedTemplate[realKey].environment.push( parsedTemplate[realKey].environment.push(
{ name: envKey, value: envValue, label, description, defaultValue } { name: envKey, value: envValue, label, description, defaultValue, extras }
) )
} }
@@ -155,6 +155,8 @@ export async function parseAndFindServiceTemplates(service: any, workdir?: strin
const { name, value } = setting const { name, value } = setting
if (service.fqdn && value === '$$generate_fqdn') { if (service.fqdn && value === '$$generate_fqdn') {
parsedTemplate = JSON.parse(JSON.stringify(parsedTemplate).replaceAll(`$$config_${name.toLowerCase()}`, service.fqdn)) parsedTemplate = JSON.parse(JSON.stringify(parsedTemplate).replaceAll(`$$config_${name.toLowerCase()}`, service.fqdn))
} else if (service.fqdn && value === '$$generate_domain') {
parsedTemplate = JSON.parse(JSON.stringify(parsedTemplate).replaceAll(`$$config_${name.toLowerCase()}`, getDomain(service.fqdn)))
} else { } else {
parsedTemplate = JSON.parse(JSON.stringify(parsedTemplate).replaceAll(`$$config_${name.toLowerCase()}`, value)) parsedTemplate = JSON.parse(JSON.stringify(parsedTemplate).replaceAll(`$$config_${name.toLowerCase()}`, value))

View File

@@ -359,12 +359,12 @@ export async function traefikConfiguration(request, reply) {
let otherNakedDomain = null; let otherNakedDomain = null;
let otherIsHttps = null; let otherIsHttps = null;
let otherIsWWW = null; let otherIsWWW = null;
if (type === 'minio') {
if (type === 'minio' && service.minio.apiFqdn) { const domain = service.serviceSetting.find((a) => a.name === 'MINIO_SERVER_URL')?.value
otherDomain = getDomain(service.minio.apiFqdn); otherDomain = getDomain(domain);
otherNakedDomain = otherDomain.replace(/^www\./, ''); otherNakedDomain = otherDomain.replace(/^www\./, '');
otherIsHttps = service.minio.apiFqdn.startsWith('https://'); otherIsHttps = domain.startsWith('https://');
otherIsWWW = service.minio.apiFqdn.includes('www.'); otherIsWWW = domain.includes('www.');
} }
data.services.push({ data.services.push({
id, id,
@@ -480,46 +480,42 @@ export async function traefikOtherConfiguration(request: FastifyRequest<TraefikO
} else if (type === 'http') { } else if (type === 'http') {
const service = await prisma.service.findFirst({ const service = await prisma.service.findFirst({
where: { id }, where: { id },
include: { minio: true } include: { serviceSetting: true }
}); });
if (service) { if (service) {
if (service.type === 'minio') { if (service.type === 'minio') {
if (service?.minio?.apiFqdn) { const domainSetting = service.serviceSetting.find((a) => a.name === 'MINIO_SERVER_URL')?.value
const { const domain = getDomain(domainSetting);
minio: { apiFqdn } const isHttps = domainSetting.startsWith('https://');
} = service; traefik = {
const domain = getDomain(apiFqdn); [type]: {
const isHttps = apiFqdn.startsWith('https://'); routers: {
traefik = { [id]: {
[type]: { entrypoints: [type],
routers: { rule: `Host(\`${domain}\`)`,
[id]: { service: id
entrypoints: [type], }
rule: `Host(\`${domain}\`)`, },
service: id services: {
} [id]: {
}, loadbalancer: {
services: { servers: [{ url: `http://${id}:${privatePort}` }]
[id]: {
loadbalancer: {
servers: [{ url: `http://${id}:${privatePort}` }]
}
} }
} }
} }
}; }
if (isHttps) { };
if (isDev) { if (isHttps) {
traefik[type].routers[id].tls = { if (isDev) {
domains: { traefik[type].routers[id].tls = {
main: `${domain}` domains: {
} main: `${domain}`
}; }
} else { };
traefik[type].routers[id].tls = { } else {
certresolver: 'letsencrypt' traefik[type].routers[id].tls = {
}; certresolver: 'letsencrypt'
} };
} }
} }
} else { } else {
@@ -758,11 +754,18 @@ export async function remoteTraefikConfiguration(request: FastifyRequest<OnlyId>
let otherIsHttps = null; let otherIsHttps = null;
let otherIsWWW = null; let otherIsWWW = null;
if (type === 'minio' && service.minio.apiFqdn) { // if (type === 'minio' && service.minio.apiFqdn) {
otherDomain = getDomain(service.minio.apiFqdn); // otherDomain = getDomain(service.minio.apiFqdn);
// otherNakedDomain = otherDomain.replace(/^www\./, '');
// otherIsHttps = service.minio.apiFqdn.startsWith('https://');
// otherIsWWW = service.minio.apiFqdn.includes('www.');
// }
if (type === 'minio') {
const domain = service.serviceSetting.find((a) => a.name === 'MINIO_SERVER_URL')?.value
otherDomain = getDomain(domain);
otherNakedDomain = otherDomain.replace(/^www\./, ''); otherNakedDomain = otherDomain.replace(/^www\./, '');
otherIsHttps = service.minio.apiFqdn.startsWith('https://'); otherIsHttps = domain.startsWith('https://');
otherIsWWW = service.minio.apiFqdn.includes('www.'); otherIsWWW = domain.includes('www.');
} }
data.services.push({ data.services.push({
id, id,

View File

@@ -259,15 +259,6 @@
</div> </div>
</div> </div>
{#if service.type === 'minio' && !service.minio.apiFqdn && $status.service.isRunning}
<div class="py-5">
<span class="font-bold text-red-500">IMPORTANT!</span> There was a small modification with Minio
in the latest version of Coolify. Now you can separate the Console URL from the API URL, so you
could use both through SSL. But this proccess cannot be done automatically, so you have to stop
your Minio instance, configure the new domain and start it back. Sorry for any inconvenience.
</div>
{/if}
<div class="grid grid-flow-row gap-2 px-4"> <div class="grid grid-flow-row gap-2 px-4">
<div class="mt-2 grid grid-cols-2 items-center"> <div class="mt-2 grid grid-cols-2 items-center">
<label for="name">{$t('forms.name')}</label> <label for="name">{$t('forms.name')}</label>
@@ -307,58 +298,24 @@
</div> </div>
</div> </div>
{#if service.type === 'minio'} <div class="grid grid-cols-2 items-center">
<div class="grid grid-cols-2 items-center"> <label for="fqdn"
<label for="fqdn" >{$t('application.url_fqdn')}
>Console URL <Explainer explanation={$t('application.https_explainer')} /></label <Explainer explanation={$t('application.https_explainer')} />
> </label>
<CopyPasswordField
<CopyPasswordField placeholder="eg: https://analytics.coollabs.io"
placeholder="eg: https://console.min.io" readonly={!$appSession.isAdmin && !$status.service.isRunning}
readonly={isDisabled} disabled={!$appSession.isAdmin ||
disabled={isDisabled} $status.service.isRunning ||
name="fqdn" $status.service.initialLoading}
id="fqdn" name="fqdn"
pattern="^https?://([a-z0-9]+(-[a-z0-9]+)*\.)+[a-z]{'{'}2,{'}'}$" id="fqdn"
bind:value={service.fqdn} pattern="^https?://([a-z0-9]+(-[a-z0-9]+)*\.)+[a-z]{'{'}2,{'}'}$"
required bind:value={service.fqdn}
/> required
</div> />
<div class="grid grid-cols-2 items-center"> </div>
<label for="apiFqdn"
>API URL <Explainer explanation={$t('application.https_explainer')} /></label
>
<CopyPasswordField
placeholder="eg: https://min.io"
readonly={!$appSession.isAdmin && !$status.service.isRunning}
disabled={isDisabled}
name="apiFqdn"
id="apiFqdn"
pattern="^https?://([a-z0-9]+(-[a-z0-9]+)*\.)+[a-z]{'{'}2,{'}'}$"
bind:value={service.minio.apiFqdn}
required
/>
</div>
{:else}
<div class="grid grid-cols-2 items-center">
<label for="fqdn"
>{$t('application.url_fqdn')}
<Explainer explanation={$t('application.https_explainer')} />
</label>
<CopyPasswordField
placeholder="eg: https://analytics.coollabs.io"
readonly={!$appSession.isAdmin && !$status.service.isRunning}
disabled={!$appSession.isAdmin ||
$status.service.isRunning ||
$status.service.initialLoading}
name="fqdn"
id="fqdn"
pattern="^https?://([a-z0-9]+(-[a-z0-9]+)*\.)+[a-z]{'{'}2,{'}'}$"
bind:value={service.fqdn}
required
/>
</div>
{/if}
</div> </div>
{#if forceSave} {#if forceSave}
<div class="flex-col space-y-2 pt-4 text-center"> <div class="flex-col space-y-2 pt-4 text-center">
@@ -449,6 +406,15 @@
id={variable.name} id={variable.name}
value={service.fqdn} value={service.fqdn}
/> />
{:else if variable.defaultValue === '$$generate_domain'}
<input
class="w-full"
disabled
readonly
name={variable.name}
id={variable.name}
value={getDomain(service.fqdn)}
/>
{:else if variable.defaultValue === 'true' || variable.defaultValue === 'false'} {:else if variable.defaultValue === 'true' || variable.defaultValue === 'false'}
<select <select
class="w-full font-normal" class="w-full font-normal"
@@ -473,6 +439,7 @@
/> />
{:else} {:else}
<CopyPasswordField <CopyPasswordField
required={variable?.extras?.required}
readonly={isDisabled} readonly={isDisabled}
disabled={isDisabled} disabled={isDisabled}
name={variable.name} name={variable.name}