Merge pull request #741 from coollabsio/next

v3.11.11
This commit is contained in:
Andras Bacsai 2022-11-21 22:03:07 +01:00 committed by GitHub
commit 3bd9f00268
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 355 additions and 133 deletions

View File

@ -0,0 +1,93 @@
name: fluent-bit-release
on:
push:
paths:
- "others/fluentbit"
- ".github/workflows/fluent-bit-release.yml"
branches:
- next
jobs:
arm64:
runs-on: [self-hosted, arm64]
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Set up QEMU
uses: docker/setup-qemu-action@v1
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1
- name: Login to DockerHub
uses: docker/login-action@v1
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Build and push
uses: docker/build-push-action@v2
with:
context: others/fluentbit/
platforms: linux/arm64
push: true
tags: coollabsio/coolify-fluent-bit:1.0.0-arm64
amd64:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Set up QEMU
uses: docker/setup-qemu-action@v2
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
- name: Login to DockerHub
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Build and push
uses: docker/build-push-action@v3
with:
context: others/fluentbit/
platforms: linux/amd64
push: true
tags: coollabsio/coolify-fluent-bit:1.0.0-amd64
aarch64:
runs-on: [self-hosted, arm64]
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Set up QEMU
uses: docker/setup-qemu-action@v1
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1
- name: Login to DockerHub
uses: docker/login-action@v1
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Build and push
uses: docker/build-push-action@v2
with:
context: others/fluentbit/
platforms: linux/aarch64
push: true
tags: coollabsio/coolify-fluent-bit:1.0.0-aarch64
merge-manifest:
runs-on: ubuntu-latest
needs: [amd64, arm64, aarch64]
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Set up QEMU
uses: docker/setup-qemu-action@v2
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
- name: Login to DockerHub
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Create & publish manifest
run: |
docker manifest create coollabsio/coolify-fluent-bit:1.0.0 --amend coollabsio/coolify-fluent-bit:1.0.0-amd64 --amend coollabsio/coolify-fluent-bit:1.0.0-arm64 --amend coollabsio/coolify-fluent-bit:1.0.0-aarch64
docker manifest push coollabsio/coolify-fluent-bit:1.0.0

View File

@ -2,6 +2,10 @@ name: staging-release
on: on:
push: push:
paths:
- '**'
- "!others/fluentbit"
- "!.github/workflows/fluent-bit-release.yml"
branches: branches:
- next - next

File diff suppressed because one or more lines are too long

View File

@ -1,5 +1,73 @@
- templateVersion: 1.0.0 - templateVersion: 1.0.0
defaultVersion: '20.0' ignore: true
defaultVersion: "1.17"
documentation: https://docs.gitea.io
type: gitea
name: Gitea
description: Gitea is a community managed lightweight code hosting solution written in Go.
labels:
- storage
- git
services:
$$id:
name: Gitea
documentation: https://docs.gitea.io
image: gitea/gitea:$$core_version
volumes:
- $$id-data:/data
- /etc/timezone:/etc/timezone:ro
- /etc/localtime:/etc/localtime:ro
environment:
- USER_UID=1000
- USER_GID=1000
- DOMAIN=$$config_domain
- SSH_DOMAIN=$$config_ssh_domain
- ROOT_URL=$$config_root_url
- SECRET_KEY=$$secret_secret_key
- INTERNAL_TOKEN=$$secret_internal_token
- SSH_PORT=$$config_hostport_ssh
ports:
- "3000"
- "22"
proxy:
- port: "22"
hostPort: $$config_hostport_ssh
variables:
- id: $$config_hostport_ssh
name: SSH_PORT
label: SSH Port
defaultValue: "8022"
description: ""
required: true
- id: $$config_domain
name: DOMAIN
label: Domain
defaultValue: $$generate_domain
description: ""
- id: $$config_ssh_domain
name: SSH_DOMAIN
label: SSH Domain
defaultValue: $$generate_domain
description: ""
- id: $$config_root_url
name: ROOT_URL
label: Root URL of Gitea
defaultValue: $$generate_fqdn_slash
description: ""
- id: $$secret_secret_key
name: SECRET_KEY
label: Secret Key
defaultValue: $$generate_hex(32)
description: ""
showOnConfiguration: true
- id: $$secret_internal_token
name: INTERNAL_TOKEN
label: Internal JWT Token
defaultValue: $$generate_token
description: ""
showOnConfiguration: true
- templateVersion: 1.0.0
defaultVersion: "20.0"
documentation: https://www.keycloak.org/documentation documentation: https://www.keycloak.org/documentation
type: keycloak type: keycloak
name: Keycloak name: Keycloak
@ -12,7 +80,7 @@
services: services:
$$id: $$id:
name: Keycloak name: Keycloak
command: start --db=postgres command: start --db=postgres --features=token-exchange --import-realm
depends_on: depends_on:
- $$id-postgresql - $$id-postgresql
image: "quay.io/keycloak/keycloak:$$core_version" image: "quay.io/keycloak/keycloak:$$core_version"
@ -29,7 +97,7 @@
- KC_DB_USERNAME=$$config_postgres_user - KC_DB_USERNAME=$$config_postgres_user
- KC_DB_URL=$$secret_keycloak_database_url - KC_DB_URL=$$secret_keycloak_database_url
ports: ports:
- '8080' - "8080"
$$id-postgresql: $$id-postgresql:
name: PostgreSQL name: PostgreSQL
depends_on: [] depends_on: []
@ -138,7 +206,7 @@
- id: $$config_port - id: $$config_port
name: PORT name: PORT
label: Port label: Port
defaultValue: '2333' defaultValue: "2333"
required: true required: true
- id: $$secret_password - id: $$secret_password
name: PASSWORD name: PASSWORD

View File

@ -182,7 +182,7 @@ const host = '0.0.0.0';
setInterval(async () => { setInterval(async () => {
await migrateServicesToNewTemplate() await migrateServicesToNewTemplate()
}, 60000) }, isDev ? 1000 : 60000)
setInterval(async () => { setInterval(async () => {
await copySSLCertificates(); await copySSLCertificates();

View File

@ -1,5 +1,5 @@
import cuid from "cuid"; import cuid from "cuid";
import { decrypt, encrypt, fixType, generatePassword, prisma } from "./lib/common"; import { decrypt, encrypt, fixType, generatePassword, generateToken, prisma } from "./lib/common";
import { getTemplates } from "./lib/services"; import { getTemplates } from "./lib/services";
export async function migrateApplicationPersistentStorage() { export async function migrateApplicationPersistentStorage() {
@ -83,39 +83,42 @@ export async function migrateServicesToNewTemplate() {
} catch (error) { } catch (error) {
console.log(error) console.log(error)
} }
if (template.variables) {
if (template.variables.length > 0) { if (template.variables.length > 0) {
for (const variable of template.variables) {
const { defaultValue } = variable;
const regex = /^\$\$.*\((\d+)\)$/g;
const length = Number(regex.exec(defaultValue)?.[1]) || undefined
if (variable.defaultValue.startsWith('$$generate_password')) {
variable.value = generatePassword({ length });
} else if (variable.defaultValue.startsWith('$$generate_hex')) {
variable.value = generatePassword({ length, isHex: true });
} else if (variable.defaultValue.startsWith('$$generate_username')) {
variable.value = cuid();
} else if (variable.defaultValue.startsWith('$$generate_token')) {
variable.value = generateToken()
} else {
variable.value = variable.defaultValue || '';
}
}
}
for (const variable of template.variables) { for (const variable of template.variables) {
const { defaultValue } = variable; if (variable.id.startsWith('$$secret_')) {
const regex = /^\$\$.*\((\d+)\)$/g; const found = await prisma.serviceSecret.findFirst({ where: { name: variable.name, serviceId: id } })
const length = Number(regex.exec(defaultValue)?.[1]) || undefined if (!found) {
if (variable.defaultValue.startsWith('$$generate_password')) { await prisma.serviceSecret.create({
variable.value = generatePassword({ length }); data: { name: variable.name, value: encrypt(variable.value) || '', service: { connect: { id } } }
} else if (variable.defaultValue.startsWith('$$generate_hex')) { })
variable.value = generatePassword({ length, isHex: true }); }
} else if (variable.defaultValue.startsWith('$$generate_username')) {
variable.value = cuid();
} else {
variable.value = variable.defaultValue || '';
}
}
}
for (const variable of template.variables) {
if (variable.id.startsWith('$$secret_')) {
const found = await prisma.serviceSecret.findFirst({ where: { name: variable.name, serviceId: id } })
if (!found) {
await prisma.serviceSecret.create({
data: { name: variable.name, value: encrypt(variable.value) || '', service: { connect: { id } } }
})
}
} }
if (variable.id.startsWith('$$config_')) { if (variable.id.startsWith('$$config_')) {
const found = await prisma.serviceSetting.findFirst({ where: { name: variable.name, serviceId: id } }) const found = await prisma.serviceSetting.findFirst({ where: { name: variable.name, serviceId: id } })
if (!found) { if (!found) {
await prisma.serviceSetting.create({ await prisma.serviceSetting.create({
data: { name: variable.name, value: variable.value.toString(), variableName: variable.id, service: { connect: { id } } } data: { name: variable.name, value: variable.value.toString(), variableName: variable.id, service: { connect: { id } } }
}) })
}
} }
} }
} }

View File

@ -11,13 +11,13 @@ import { promises as dns } from 'dns';
import { PrismaClient } from '@prisma/client'; import { PrismaClient } from '@prisma/client';
import os from 'os'; import os from 'os';
import sshConfig from 'ssh-config'; import sshConfig from 'ssh-config';
import jsonwebtoken from 'jsonwebtoken';
import { checkContainer, removeContainer } from './docker'; import { checkContainer, removeContainer } from './docker';
import { day } from './dayjs'; import { day } from './dayjs';
import { saveBuildLog } from './buildPacks/common'; import { saveBuildLog } from './buildPacks/common';
import { scheduler } from './scheduler'; import { scheduler } from './scheduler';
export const version = '3.11.10'; export const version = '3.11.11';
export const isDev = process.env.NODE_ENV === 'development'; export const isDev = process.env.NODE_ENV === 'development';
const algorithm = 'aes-256-ctr'; const algorithm = 'aes-256-ctr';
@ -722,6 +722,11 @@ export async function listSettings(): Promise<any> {
return settings; return settings;
} }
export function generateToken() {
return jsonwebtoken.sign({
nbf: Math.floor(Date.now() / 1000) - 30,
}, process.env['COOLIFY_SECRET_KEY'])
}
export function generatePassword({ export function generatePassword({
length = 24, length = 24,
symbols = false, symbols = false,
@ -1614,7 +1619,7 @@ export function persistentVolumes(id, persistentStorage, config) {
for (const [key, value] of Object.entries(config)) { for (const [key, value] of Object.entries(config)) {
if (value.volumes) { if (value.volumes) {
for (const volume of value.volumes) { for (const volume of value.volumes) {
if (!volume.startsWith('/var/run/docker.sock')) { if (!volume.startsWith('/')) {
volumeSet.add(volume); volumeSet.add(volume);
} }
} }

View File

@ -103,9 +103,19 @@ export async function startService(request: FastifyRequest<ServiceStartStop>, fa
} }
} }
} }
let port = null let ports = []
if (template.services[s].ports?.length > 0) { if (template.services[s].proxy?.length > 0) {
port = template.services[s].ports[0] for (const proxy of template.services[s].proxy) {
if (proxy.hostPort) {
ports.push(`${proxy.hostPort}:${proxy.port}`)
}
}
} else {
if (template.services[s].ports?.length === 1) {
for (const port of template.services[s].ports) {
ports.push(`${exposePort}:${exposePort}`)
}
}
} }
let image = template.services[s].image let image = template.services[s].image
if (arm && template.services[s].imageArm) { if (arm && template.services[s].imageArm) {
@ -118,7 +128,7 @@ export async function startService(request: FastifyRequest<ServiceStartStop>, fa
entrypoint: template.services[s]?.entrypoint, entrypoint: template.services[s]?.entrypoint,
image, image,
expose: template.services[s].ports, expose: template.services[s].ports,
...(exposePort && port ? { ports: [`${exposePort}:${port}`] } : {}), ports,
volumes: Array.from(volumes), volumes: Array.from(volumes),
environment: newEnvironments, environment: newEnvironments,
depends_on: template.services[s]?.depends_on, depends_on: template.services[s]?.depends_on,
@ -128,7 +138,6 @@ export async function startService(request: FastifyRequest<ServiceStartStop>, fa
labels: makeLabelForServices(type), labels: makeLabelForServices(type),
...defaultComposeConfiguration(network), ...defaultComposeConfiguration(network),
} }
// Generate files for builds // Generate files for builds
if (template.services[s]?.files?.length > 0) { if (template.services[s]?.files?.length > 0) {
if (!config[s].build) { if (!config[s].build) {
@ -182,7 +191,6 @@ export async function startService(request: FastifyRequest<ServiceStartStop>, fa
`docker container ls -a --filter 'name=${id}-' --format {{.ID}}|xargs -r -n 1 docker container rm -f` `docker container ls -a --filter 'name=${id}-' --format {{.ID}}|xargs -r -n 1 docker container rm -f`
}); });
} catch (error) { } } catch (error) { }
} }
return {} return {}
} catch ({ status, message }) { } catch ({ status, message }) {

View File

@ -4,7 +4,7 @@ import yaml from 'js-yaml';
import bcrypt from 'bcryptjs'; import bcrypt from 'bcryptjs';
import cuid from 'cuid'; import cuid from 'cuid';
import { prisma, uniqueName, asyncExecShell, getServiceFromDB, getContainerUsage, isDomainConfigured, fixType, decrypt, encrypt, ComposeFile, getFreePublicPort, getDomain, errorHandler, generatePassword, isDev, stopTcpHttpProxy, executeDockerCmd, checkDomainsIsValidInDNS, checkExposedPort, listSettings } from '../../../../lib/common'; import { prisma, uniqueName, asyncExecShell, getServiceFromDB, getContainerUsage, isDomainConfigured, fixType, decrypt, encrypt, ComposeFile, getFreePublicPort, getDomain, errorHandler, generatePassword, isDev, stopTcpHttpProxy, executeDockerCmd, checkDomainsIsValidInDNS, checkExposedPort, listSettings, generateToken } from '../../../../lib/common';
import { day } from '../../../../lib/dayjs'; import { day } from '../../../../lib/dayjs';
import { checkContainer, } from '../../../../lib/docker'; import { checkContainer, } from '../../../../lib/docker';
import { removeService } from '../../../../lib/services/common'; import { removeService } from '../../../../lib/services/common';
@ -82,7 +82,6 @@ export async function getServiceStatus(request: FastifyRequest<OnlyId>) {
if (containersArray.length > 0 && containersArray[0] !== '') { if (containersArray.length > 0 && containersArray[0] !== '') {
const templates = await getTemplates(); const templates = await getTemplates();
let template = templates.find(t => t.type === service.type); let template = templates.find(t => t.type === service.type);
console.log(service.type)
const templateStr = JSON.stringify(template) const templateStr = JSON.stringify(template)
if (templateStr) { if (templateStr) {
template = JSON.parse(templateStr.replaceAll('$$id', service.id)); template = JSON.parse(templateStr.replaceAll('$$id', service.id));
@ -160,13 +159,17 @@ export async function parseAndFindServiceTemplates(service: any, workdir?: strin
files: value?.files, files: value?.files,
environment: [], environment: [],
fqdns: [], fqdns: [],
hostPorts: [],
proxy: {} proxy: {}
} }
if (value.environment?.length > 0) { if (value.environment?.length > 0) {
for (const env of value.environment) { for (const env of value.environment) {
let [envKey, ...envValue] = env.split('=') let [envKey, ...envValue] = env.split('=')
envValue = envValue.join("=") envValue = envValue.join("=")
const variable = foundTemplate.variables.find(v => v.name === envKey) || foundTemplate.variables.find(v => v.id === envValue) let variable = null
if (foundTemplate?.variables) {
variable = foundTemplate?.variables.find(v => v.name === envKey) || foundTemplate?.variables.find(v => v.id === envValue)
}
if (variable) { if (variable) {
const id = variable.id.replaceAll('$$', '') const id = variable.id.replaceAll('$$', '')
const label = variable?.label const label = variable?.label
@ -192,7 +195,7 @@ export async function parseAndFindServiceTemplates(service: any, workdir?: strin
if (value?.proxy && value.proxy.length > 0) { if (value?.proxy && value.proxy.length > 0) {
for (const proxyValue of value.proxy) { for (const proxyValue of value.proxy) {
if (proxyValue.domain) { if (proxyValue.domain) {
const variable = foundTemplate.variables.find(v => v.id === proxyValue.domain) const variable = foundTemplate?.variables.find(v => v.id === proxyValue.domain)
if (variable) { if (variable) {
const { id, name, label, description, defaultValue, required = false } = variable const { id, name, label, description, defaultValue, required = false } = variable
const found = await prisma.serviceSetting.findFirst({ where: { serviceId: service.id, variableName: proxyValue.domain } }) const found = await prisma.serviceSetting.findFirst({ where: { serviceId: service.id, variableName: proxyValue.domain } })
@ -200,7 +203,16 @@ export async function parseAndFindServiceTemplates(service: any, workdir?: strin
{ id, name, value: found?.value || '', label, description, defaultValue, required } { id, name, value: found?.value || '', label, description, defaultValue, required }
) )
} }
}
if (proxyValue.hostPort) {
const variable = foundTemplate?.variables.find(v => v.id === proxyValue.hostPort)
if (variable) {
const { id, name, label, description, defaultValue, required = false } = variable
const found = await prisma.serviceSetting.findFirst({ where: { serviceId: service.id, variableName: proxyValue.hostPort } })
parsedTemplate[realKey].hostPorts.push(
{ id, name, value: found?.value || '', label, description, defaultValue, required }
)
}
} }
} }
} }
@ -223,15 +235,17 @@ export async function parseAndFindServiceTemplates(service: any, workdir?: strin
if (service.serviceSetting.length > 0) { if (service.serviceSetting.length > 0) {
for (const setting of service.serviceSetting) { for (const setting of service.serviceSetting) {
const { value, variableName } = setting const { value, variableName } = setting
const regex = new RegExp(`\\$\\$config_${variableName.replace('$$config_', '')}`, 'gi') const regex = new RegExp(`\\$\\$config_${variableName.replace('$$config_', '')}\"`, 'gi')
if (value === '$$generate_fqdn') { if (value === '$$generate_fqdn') {
strParsedTemplate = strParsedTemplate.replaceAll(regex, service.fqdn || '') strParsedTemplate = strParsedTemplate.replaceAll(regex, service.fqdn + '"' || '' + '"')
} else if (value === '$$generate_fqdn_slash') {
strParsedTemplate = strParsedTemplate.replaceAll(regex, service.fqdn + '/' + '"')
} else if (value === '$$generate_domain') { } else if (value === '$$generate_domain') {
strParsedTemplate = strParsedTemplate.replaceAll(regex, getDomain(service.fqdn)) strParsedTemplate = strParsedTemplate.replaceAll(regex, getDomain(service.fqdn) + '"')
} else if (service.destinationDocker?.network && value === '$$generate_network') { } else if (service.destinationDocker?.network && value === '$$generate_network') {
strParsedTemplate = strParsedTemplate.replaceAll(regex, service.destinationDocker.network) strParsedTemplate = strParsedTemplate.replaceAll(regex, service.destinationDocker.network + '"')
} else { } else {
strParsedTemplate = strParsedTemplate.replaceAll(regex, value) strParsedTemplate = strParsedTemplate.replaceAll(regex, value + '"')
} }
} }
} }
@ -241,14 +255,14 @@ export async function parseAndFindServiceTemplates(service: any, workdir?: strin
for (const secret of service.serviceSecret) { for (const secret of service.serviceSecret) {
let { name, value } = secret let { name, value } = secret
name = name.toLowerCase() name = name.toLowerCase()
const regexHashed = new RegExp(`\\$\\$hashed\\$\\$secret_${name}`, 'gi') const regexHashed = new RegExp(`\\$\\$hashed\\$\\$secret_${name}\"`, 'gi')
const regex = new RegExp(`\\$\\$secret_${name}`, 'gi') const regex = new RegExp(`\\$\\$secret_${name}\"`, 'gi')
if (value) { if (value) {
strParsedTemplate = strParsedTemplate.replaceAll(regexHashed, bcrypt.hashSync(value.replaceAll("\"", "\\\""), 10)) strParsedTemplate = strParsedTemplate.replaceAll(regexHashed, bcrypt.hashSync(value.replaceAll("\"", "\\\""), 10) + '"')
strParsedTemplate = strParsedTemplate.replaceAll(regex, value.replaceAll("\"", "\\\"")) strParsedTemplate = strParsedTemplate.replaceAll(regex, value.replaceAll("\"", "\\\"") + '"')
} else { } else {
strParsedTemplate = strParsedTemplate.replaceAll(regexHashed, '') strParsedTemplate = strParsedTemplate.replaceAll(regexHashed, '' + '"')
strParsedTemplate = strParsedTemplate.replaceAll(regex, '') strParsedTemplate = strParsedTemplate.replaceAll(regex, '' + '"')
} }
} }
} }
@ -298,42 +312,46 @@ export async function saveServiceType(request: FastifyRequest<SaveServiceType>,
let foundTemplate = templates.find(t => fixType(t.type) === fixType(type)) let foundTemplate = templates.find(t => fixType(t.type) === fixType(type))
if (foundTemplate) { if (foundTemplate) {
foundTemplate = JSON.parse(JSON.stringify(foundTemplate).replaceAll('$$id', id)) foundTemplate = JSON.parse(JSON.stringify(foundTemplate).replaceAll('$$id', id))
if (foundTemplate.variables.length > 0) { if (foundTemplate.variables) {
if (foundTemplate.variables.length > 0) {
for (const variable of foundTemplate.variables) {
const { defaultValue } = variable;
const regex = /^\$\$.*\((\d+)\)$/g;
const length = Number(regex.exec(defaultValue)?.[1]) || undefined
if (variable.defaultValue.startsWith('$$generate_password')) {
variable.value = generatePassword({ length });
} else if (variable.defaultValue.startsWith('$$generate_hex')) {
variable.value = generatePassword({ length, isHex: true });
} else if (variable.defaultValue.startsWith('$$generate_username')) {
variable.value = cuid();
} else if (variable.defaultValue.startsWith('$$generate_token')) {
variable.value = generateToken()
} else {
variable.value = variable.defaultValue || '';
}
const foundVariableSomewhereElse = foundTemplate.variables.find(v => v.defaultValue.includes(variable.id))
if (foundVariableSomewhereElse) {
foundVariableSomewhereElse.value = foundVariableSomewhereElse.value.replaceAll(variable.id, variable.value)
}
}
}
for (const variable of foundTemplate.variables) { for (const variable of foundTemplate.variables) {
const { defaultValue } = variable; if (variable.id.startsWith('$$secret_')) {
const regex = /^\$\$.*\((\d+)\)$/g; const found = await prisma.serviceSecret.findFirst({ where: { name: variable.name, serviceId: id } })
const length = Number(regex.exec(defaultValue)?.[1]) || undefined if (!found) {
if (variable.defaultValue.startsWith('$$generate_password')) { await prisma.serviceSecret.create({
variable.value = generatePassword({ length }); data: { name: variable.name, value: encrypt(variable.value) || '', service: { connect: { id } } }
} else if (variable.defaultValue.startsWith('$$generate_hex')) { })
variable.value = generatePassword({ length, isHex: true }); }
} else if (variable.defaultValue.startsWith('$$generate_username')) {
variable.value = cuid();
} else {
variable.value = variable.defaultValue || '';
}
const foundVariableSomewhereElse = foundTemplate.variables.find(v => v.defaultValue.includes(variable.id))
if (foundVariableSomewhereElse) {
foundVariableSomewhereElse.value = foundVariableSomewhereElse.value.replaceAll(variable.id, variable.value)
}
}
}
for (const variable of foundTemplate.variables) {
if (variable.id.startsWith('$$secret_')) {
const found = await prisma.serviceSecret.findFirst({ where: { name: variable.name, serviceId: id } })
if (!found) {
await prisma.serviceSecret.create({
data: { name: variable.name, value: encrypt(variable.value) || '', service: { connect: { id } } }
})
}
} }
if (variable.id.startsWith('$$config_')) { if (variable.id.startsWith('$$config_')) {
const found = await prisma.serviceSetting.findFirst({ where: { name: variable.name, serviceId: id } }) const found = await prisma.serviceSetting.findFirst({ where: { name: variable.name, serviceId: id } })
if (!found) { if (!found) {
await prisma.serviceSetting.create({ await prisma.serviceSetting.create({
data: { name: variable.name, value: variable.value.toString(), variableName: variable.id, service: { connect: { id } } } data: { name: variable.name, value: variable.value.toString(), variableName: variable.id, service: { connect: { id } } }
}) })
}
} }
} }
} }
@ -539,7 +557,7 @@ export async function saveService(request: FastifyRequest<SaveService>, reply: F
} }
if (isNew) { if (isNew) {
if (!variableName) { if (!variableName) {
variableName = foundTemplate.variables.find(v => v.name === name).id variableName = foundTemplate?.variables.find(v => v.name === name).id
} }
await prisma.serviceSetting.create({ data: { name, value, variableName, service: { connect: { id } } } }) await prisma.serviceSetting.create({ data: { name, value, variableName, service: { connect: { id } } } })
} }

View File

@ -395,8 +395,8 @@ export async function proxyConfiguration(request: FastifyRequest<OnlyId>, remote
} }
found = JSON.parse(JSON.stringify(found).replaceAll('$$id', id)); found = JSON.parse(JSON.stringify(found).replaceAll('$$id', id));
for (const oneService of Object.keys(found.services)) { for (const oneService of Object.keys(found.services)) {
const isProxyConfiguration = found.services[oneService].proxy; const isDomainConfiguration = found?.services[oneService]?.proxy?.filter(p => p.domain) ?? [];
if (isProxyConfiguration) { if (isDomainConfiguration.length > 0) {
const { proxy } = found.services[oneService]; const { proxy } = found.services[oneService];
for (let configuration of proxy) { for (let configuration of proxy) {
if (configuration.domain) { if (configuration.domain) {
@ -431,20 +431,24 @@ export async function proxyConfiguration(request: FastifyRequest<OnlyId>, remote
} }
} else { } else {
if (found.services[oneService].ports && found.services[oneService].ports.length > 0) { if (found.services[oneService].ports && found.services[oneService].ports.length > 0) {
let port = found.services[oneService].ports[0] for (let [index, port] of found.services[oneService].ports.entries()) {
const foundPortVariable = serviceSetting.find((a) => a.name.toLowerCase() === 'port') if (port == 22) continue;
if (foundPortVariable) { if (index === 0) {
port = foundPortVariable.value const foundPortVariable = serviceSetting.find((a) => a.name.toLowerCase() === 'port')
if (foundPortVariable) {
port = foundPortVariable.value
}
}
const domain = getDomain(fqdn);
const nakedDomain = domain.replace(/^www\./, '');
const isHttps = fqdn.startsWith('https://');
const isWWW = fqdn.includes('www.');
const pathPrefix = '/'
const isCustomSSL = false
const serviceId = `${oneService}-${port || 'default'}`
traefik.http.routers = { ...traefik.http.routers, ...generateRouters(serviceId, domain, nakedDomain, pathPrefix, isHttps, isWWW, dualCerts, isCustomSSL) }
traefik.http.services = { ...traefik.http.services, ...generateServices(serviceId, id, port) }
} }
const domain = getDomain(fqdn);
const nakedDomain = domain.replace(/^www\./, '');
const isHttps = fqdn.startsWith('https://');
const isWWW = fqdn.includes('www.');
const pathPrefix = '/'
const isCustomSSL = false
const serviceId = `${oneService}-${port || 'default'}`
traefik.http.routers = { ...traefik.http.routers, ...generateRouters(serviceId, domain, nakedDomain, pathPrefix, isHttps, isWWW, dualCerts, isCustomSSL) }
traefik.http.services = { ...traefik.http.services, ...generateServices(serviceId, id, port) }
} }
} }
} }

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -5,6 +5,7 @@
const handleError = (ev: { target: { src: string } }) => (ev.target.src = fallback); const handleError = (ev: { target: { src: string } }) => (ev.target.src = fallback);
let extension = 'png'; let extension = 'png';
let svgs = [ let svgs = [
'gitea',
'languagetool', 'languagetool',
'meilisearch', 'meilisearch',
'n8n', 'n8n',

View File

@ -38,6 +38,11 @@
import Wordpress from './_Services/wordpress.svelte'; import Wordpress from './_Services/wordpress.svelte';
const { id } = $page.params; const { id } = $page.params;
let hostPorts = Object.keys(template).filter((key) => {
if (template[key]?.hostPorts?.length > 0) {
return true;
}
});
$: isDisabled = $: isDisabled =
!$appSession.isAdmin || !$appSession.isAdmin ||
$status.service.overallStatus === 'degraded' || $status.service.overallStatus === 'degraded' ||
@ -291,7 +296,7 @@
/> />
</div> </div>
{:else} {:else}
<input class="w-full border-red-500" disabled placeholder="Error getting tags..."> <input class="w-full border-red-500" disabled placeholder="Error getting tags..." />
{/if} {/if}
</div> </div>
@ -389,22 +394,24 @@
on:click={() => !$status.service.isRunning && changeSettings('dualCerts')} on:click={() => !$status.service.isRunning && changeSettings('dualCerts')}
/> />
</div> </div>
<div class="grid grid-cols-2 items-center"> {#if hostPorts.length === 0}
<label for="exposePort" <div class="grid grid-cols-2 items-center">
>Exposed Port <Explainer <label for="exposePort"
explanation={'You can expose your application to a port on the host system.<br><br>Useful if you would like to use your own reverse proxy or tunnel and also in development mode. Otherwise leave empty.'} >Exposed Port <Explainer
/></label explanation={'You can expose your application to a port on the host system.<br><br>Useful if you would like to use your own reverse proxy or tunnel and also in development mode. Otherwise leave empty.'}
> /></label
<input >
class="w-full" <input
readonly={isDisabled} class="w-full"
disabled={isDisabled} readonly={isDisabled}
name="exposePort" disabled={isDisabled}
id="exposePort" name="exposePort"
bind:value={service.exposePort} id="exposePort"
placeholder="12345" bind:value={service.exposePort}
/> placeholder="12345"
</div> />
</div>
{/if}
</div> </div>
<div class="pt-6"> <div class="pt-6">
{#each Object.keys(template) as oneService} {#each Object.keys(template) as oneService}
@ -444,6 +451,16 @@
placeholder={variable.placeholder} placeholder={variable.placeholder}
required={variable?.required} required={variable?.required}
/> />
{:else if variable.defaultValue === '$$generate_fqdn_slash'}
<CopyPasswordField
disabled
readonly
name={variable.name}
id={variable.name}
value={service.fqdn + '/' || ''}
placeholder={variable.placeholder}
required={variable?.required}
/>
{:else if variable.defaultValue === '$$generate_domain'} {:else if variable.defaultValue === '$$generate_domain'}
<CopyPasswordField <CopyPasswordField
disabled disabled

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 640" style="enable-background:new 0 0 640 640" xml:space="preserve" width="32" height="32"><path style="fill:#fff" d="m395.9 484.2-126.9-61c-12.5-6-17.9-21.2-11.8-33.8l61-126.9c6-12.5 21.2-17.9 33.8-11.8 17.2 8.3 27.1 13 27.1 13l-.1-109.2 16.7-.1.1 117.1s57.4 24.2 83.1 40.1c3.7 2.3 10.2 6.8 12.9 14.4 2.1 6.1 2 13.1-1 19.3l-61 126.9c-6.2 12.7-21.4 18.1-33.9 12z"/><path style="fill:#609926" d="M622.7 149.8c-4.1-4.1-9.6-4-9.6-4s-117.2 6.6-177.9 8c-13.3.3-26.5.6-39.6.7v117.2c-5.5-2.6-11.1-5.3-16.6-7.9 0-36.4-.1-109.2-.1-109.2-29 .4-89.2-2.2-89.2-2.2s-141.4-7.1-156.8-8.5c-9.8-.6-22.5-2.1-39 1.5-8.7 1.8-33.5 7.4-53.8 26.9C-4.9 212.4 6.6 276.2 8 285.8c1.7 11.7 6.9 44.2 31.7 72.5 45.8 56.1 144.4 54.8 144.4 54.8s12.1 28.9 30.6 55.5c25 33.1 50.7 58.9 75.7 62 63 0 188.9-.1 188.9-.1s12 .1 28.3-10.3c14-8.5 26.5-23.4 26.5-23.4S547 483 565 451.5c5.5-9.7 10.1-19.1 14.1-28 0 0 55.2-117.1 55.2-231.1-1.1-34.5-9.6-40.6-11.6-42.6zM125.6 353.9c-25.9-8.5-36.9-18.7-36.9-18.7S69.6 321.8 60 295.4c-16.5-44.2-1.4-71.2-1.4-71.2s8.4-22.5 38.5-30c13.8-3.7 31-3.1 31-3.1s7.1 59.4 15.7 94.2c7.2 29.2 24.8 77.7 24.8 77.7s-26.1-3.1-43-9.1zm300.3 107.6s-6.1 14.5-19.6 15.4c-5.8.4-10.3-1.2-10.3-1.2s-.3-.1-5.3-2.1l-112.9-55s-10.9-5.7-12.8-15.6c-2.2-8.1 2.7-18.1 2.7-18.1L322 273s4.8-9.7 12.2-13c.6-.3 2.3-1 4.5-1.5 8.1-2.1 18 2.8 18 2.8L467.4 315s12.6 5.7 15.3 16.2c1.9 7.4-.5 14-1.8 17.2-6.3 15.4-55 113.1-55 113.1z"/><path style="fill:#609926" d="M326.8 380.1c-8.2.1-15.4 5.8-17.3 13.8-1.9 8 2 16.3 9.1 20 7.7 4 17.5 1.8 22.7-5.4 5.1-7.1 4.3-16.9-1.8-23.1l24-49.1c1.5.1 3.7.2 6.2-.5 4.1-.9 7.1-3.6 7.1-3.6 4.2 1.8 8.6 3.8 13.2 6.1 4.8 2.4 9.3 4.9 13.4 7.3.9.5 1.8 1.1 2.8 1.9 1.6 1.3 3.4 3.1 4.7 5.5 1.9 5.5-1.9 14.9-1.9 14.9-2.3 7.6-18.4 40.6-18.4 40.6-8.1-.2-15.3 5-17.7 12.5-2.6 8.1 1.1 17.3 8.9 21.3 7.8 4 17.4 1.7 22.5-5.3 5-6.8 4.6-16.3-1.1-22.6 1.9-3.7 3.7-7.4 5.6-11.3 5-10.4 13.5-30.4 13.5-30.4.9-1.7 5.7-10.3 2.7-21.3-2.5-11.4-12.6-16.7-12.6-16.7-12.2-7.9-29.2-15.2-29.2-15.2s0-4.1-1.1-7.1c-1.1-3.1-2.8-5.1-3.9-6.3 4.7-9.7 9.4-19.3 14.1-29-4.1-2-8.1-4-12.2-6.1-4.8 9.8-9.7 19.7-14.5 29.5-6.7-.1-12.9 3.5-16.1 9.4-3.4 6.3-2.7 14.1 1.9 19.8l-24.6 50.4z"/></svg>

After

Width:  |  Height:  |  Size: 2.2 KiB

View File

@ -1,7 +1,7 @@
{ {
"name": "coolify", "name": "coolify",
"description": "An open-source & self-hostable Heroku / Netlify alternative.", "description": "An open-source & self-hostable Heroku / Netlify alternative.",
"version": "3.11.10", "version": "3.11.11",
"license": "Apache-2.0", "license": "Apache-2.0",
"repository": "github:coollabsio/coolify", "repository": "github:coollabsio/coolify",
"scripts": { "scripts": {