Compare commits

...

26 Commits

Author SHA1 Message Date
Andras Bacsai
f3beb5d8db Merge pull request #1045 from coollabsio/next
v3.12.31
2023-04-19 08:53:20 +02:00
Andras Bacsai
e86b916415 fix: application logs duplicate 2023-04-19 08:50:55 +02:00
Andras Bacsai
e14cc6f2f0 remove assignment for issues 2023-04-18 14:52:26 +02:00
Andras Bacsai
8c1eb94401 fix: remove git is not necessary for docker bp 2023-04-18 14:51:31 +02:00
Andras Bacsai
29fa421945 feat: add custom version/tag 2023-04-18 14:46:40 +02:00
Andras Bacsai
7cfe98d988 fix: fail build if no application found. 2023-04-18 14:32:29 +02:00
Andras Bacsai
e2314c350b update lock file 2023-04-18 14:27:54 +02:00
Andras Bacsai
3713b33578 Update templates
fix: non string inputs in templates
2023-04-18 14:22:46 +02:00
Andras Bacsai
e007a773fd Merge pull request #1030 from coollabsio/next
v3.12.30
2023-04-03 10:40:27 +02:00
Andras Bacsai
e2821118eb fix. removing git 2023-04-03 10:21:08 +02:00
Andras Bacsai
4c8e73ac86 fix: harder to remove destinations and sources 2023-04-03 09:55:13 +02:00
Andras Bacsai
cb980fb814 fix: docker compose generator 2023-04-03 08:59:48 +02:00
Andras Bacsai
41c84e3642 Merge pull request #1001 from coollabsio/next
v3.12.29
2023-03-20 13:57:01 +01:00
Andras Bacsai
2bad98424f switch back to aarch-runners 2023-03-20 13:49:41 +01:00
Andras Bacsai
bc6b1e2dea fix: remove .git dir from final image 2023-03-20 13:05:53 +01:00
Andras Bacsai
911c15d1be update versions 2023-03-20 12:44:45 +01:00
Andras Bacsai
f79d570870 fix: gitea 2023-03-20 12:28:23 +01:00
Andras Bacsai
7fffa9fba5 Merge branch 'main' into next 2023-03-20 12:05:21 +01:00
Andras Bacsai
cbd634fb99 Update README.md 2023-03-17 15:31:00 +01:00
Andras Bacsai
7ae7436d4f Update staging-release.yml 2023-03-17 15:27:16 +01:00
Andras Bacsai
641bada100 ignore dockerhub releases 2023-03-16 13:54:58 +01:00
Andras Bacsai
3416d8d88e only arm 2023-03-16 13:42:19 +01:00
Andras Bacsai
0bb503368b concurrency 2023-03-16 13:37:49 +01:00
Andras Bacsai
ac3a77c3c7 no qemu 2023-03-16 13:35:04 +01:00
Andras Bacsai
79b4178d76 vcpu increase 2023-03-16 13:32:48 +01:00
Andras Bacsai
42a61296d7 test buildjet 2023-03-16 13:29:13 +01:00
37 changed files with 2251 additions and 1871 deletions

View File

@@ -2,9 +2,6 @@ name: 🐞 Bug report
description: Create a bug report to help us improve coolify
title: "[Bug]: "
labels: [Bug]
assignees:
- andrasbacsai
- vasani-arpit
body:
- type: markdown
attributes:

View File

@@ -2,9 +2,6 @@ name: 🛠️ Feature request
description: Suggest an idea to improve coolify
title: '[Feature]: '
labels: [Enhancement]
assignees:
- andrasbacsai
- vasani-arpit
body:
- type: markdown
attributes:

View File

@@ -1,8 +1,9 @@
name: Production Release to DockerHub
on:
release:
types: [released]
push:
branches:
- "this-branch-does-not-exists"
jobs:
arm64:

View File

@@ -3,7 +3,7 @@ name: Staging Release to DockerHub
on:
push:
branches:
- "next"
- "this-branch-does-not-exists"
jobs:
arm64:

View File

@@ -1,23 +1,24 @@
name: Staging Release to ghcr.io
concurrency:
group: staging_environment
cancel-in-progress: true
on:
push:
branches-ignore:
- "main"
- "v4"
env:
REGISTRY: ghcr.io
IMAGE_NAME: "coollabsio/coolify"
jobs:
amd64:
runs-on: ubuntu-latest
runs-on: ubuntu-22.04
steps:
- name: Checkout
uses: actions/checkout@v3
with:
ref: "next"
- name: Set up QEMU
uses: docker/setup-qemu-action@v2
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
- name: Login to ghcr.io
@@ -40,14 +41,13 @@ jobs:
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
aarch64:
runs-on: [self-hosted, arm64]
runs-on:
group: aarch-runners
steps:
- name: Checkout
uses: actions/checkout@v3
with:
ref: "next"
- name: Set up QEMU
uses: docker/setup-qemu-action@v2
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
- name: Login to ghcr.io

View File

@@ -100,7 +100,7 @@ Deploy your resource to:
- Mastodon: [@andrasbacsai@fosstodon.org](https://fosstodon.org/@andrasbacsai)
- Telegram: [@andrasbacsai](https://t.me/andrasbacsai)
- Twitter: [@andrasbacsai](https://twitter.com/andrasbacsai)
- Twitter: [@andrasbacsai](https://twitter.com/heyandras)
- Email: [andras@coollabs.io](mailto:andras@coollabs.io)
- Discord: [Invitation](https://coollabs.io/discord)

View File

@@ -268,6 +268,13 @@
- DISABLE_REGISTRATION=$$config_disable_registration
- DATABASE_URL=$$secret_database_url
- CLICKHOUSE_DATABASE_URL=$$secret_clickhouse_database_url
- MAILER_EMAIL=$$config_mailer_email
- SMTP_HOST_ADDR=$$config_smtp_host_addr
- SMTP_HOST_PORT=$$config_smtp_host_port
- SMTP_USER_NAME=$$config_smtp_user_name
- SMTP_USER_PWD=$$config_smtp_user_pwd
- SMTP_HOST_SSL_ENABLED=$$config_smtp_host_ssl_enabled
- SMTP_HOST_RETRIES=$$config_smtp_host_retries
ports:
- "8000"
$$id-postgresql:
@@ -375,6 +382,49 @@
label: PostgreSQL Database
defaultValue: plausible
description: ""
- id: $$config_mailer_email
name: MAILER_EMAIL
label: Mailer Email
defaultValue: hello@plausible.local
description: >-
The email id to use for as from address of all communications from Plausible.
- id: $$config_smtp_host_addr
name: SMTP_HOST_ADDR
label: SMTP Host Address
defaultValue: localhost
description: >-
The host address of your smtp server.
- id: $$config_smtp_host_port
name: SMTP_HOST_PORT
label: SMTP Port
defaultValue: "25"
description: >-
The port of your smtp server.
- id: $$config_smtp_user_name
name: SMTP_USER_NAME
label: SMTP Username
defaultValue: ""
description: >-
The username/email in case SMTP auth is enabled.
- id: $$config_smtp_user_pwd
name: SMTP_USER_PWD
label: SMTP Password
defaultValue: ""
description: >-
The password in case SMTP auth is enabled.
showOnConfiguration: true
- id: $$config_smtp_host_ssl_enabled
name: SMTP_HOST_SSL_ENABLED
label: SMTP SSL
defaultValue: "false"
description: >-
If SSL is enabled for SMTP connection.
- id: $$config_smtp_host_retries
name: SMTP_HOST_RETRIES
label: SMTP Retries
defaultValue: "2"
description: >-
Number of retries to make until mailer gives up.
- id: $$config_scriptName
name: SCRIPT_NAME
label: Custom Script Name
@@ -414,6 +464,7 @@
proxy:
- port: "22"
hostPort: $$config_hostport_ssh
- port: "3000"
variables:
- id: $$config_hostport_ssh
name: SSH_PORT
@@ -3388,6 +3439,13 @@
- DISABLE_REGISTRATION=$$config_disable_registration
- DATABASE_URL=$$secret_database_url
- CLICKHOUSE_DATABASE_URL=$$secret_clickhouse_database_url
- MAILER_EMAIL=$$config_mailer_email
- SMTP_HOST_ADDR=$$config_smtp_host_addr
- SMTP_HOST_PORT=$$config_smtp_host_port
- SMTP_USER_NAME=$$config_smtp_user_name
- SMTP_USER_PWD=$$config_smtp_user_pwd
- SMTP_HOST_SSL_ENABLED=$$config_smtp_host_ssl_enabled
- SMTP_HOST_RETRIES=$$config_smtp_host_retries
ports:
- "8000"
$$id-postgresql:
@@ -3495,6 +3553,49 @@
label: PostgreSQL Database
defaultValue: plausible
description: ""
- id: $$config_mailer_email
name: MAILER_EMAIL
label: Mailer Email
defaultValue: hello@plausible.local
description: >-
The email id to use for as from address of all communications from Plausible.
- id: $$config_smtp_host_addr
name: SMTP_HOST_ADDR
label: SMTP Host Address
defaultValue: localhost
description: >-
The host address of your smtp server.
- id: $$config_smtp_host_port
name: SMTP_HOST_PORT
label: SMTP Port
defaultValue: "25"
description: >-
The port of your smtp server.
- id: $$config_smtp_user_name
name: SMTP_USER_NAME
label: SMTP Username
defaultValue: ""
description: >-
The username/email in case SMTP auth is enabled.
- id: $$config_smtp_user_pwd
name: SMTP_USER_PWD
label: SMTP Password
defaultValue: ""
description: >-
The password in case SMTP auth is enabled.
showOnConfiguration: true
- id: $$config_smtp_host_ssl_enabled
name: SMTP_HOST_SSL_ENABLED
label: SMTP SSL
defaultValue: "false"
description: >-
If SSL is enabled for SMTP connection.
- id: $$config_smtp_host_retries
name: SMTP_HOST_RETRIES
label: SMTP Retries
defaultValue: "2"
description: >-
Number of retries to make until mailer gives up.
- id: $$config_scriptName
name: SCRIPT_NAME
label: Custom Script Name

View File

@@ -306,7 +306,7 @@ async function initServer() {
} catch (error) {}
try {
console.log('[003] Cleaning up old build sources under /tmp/build-sources/...');
await fs.rm('/tmp/build-sources', { recursive: true, force: true });
if (!isDev) await fs.rm('/tmp/build-sources', { recursive: true, force: true });
} catch (error) {
console.log(error);
}

View File

@@ -69,7 +69,15 @@ import * as buildpacks from '../lib/buildPacks';
teams: true
}
});
if (!application) {
await prisma.build.update({
where: { id: queueBuild.id },
data: {
status: 'failed'
}
});
throw new Error('Application not found');
}
let {
id: buildId,
type,
@@ -111,7 +119,7 @@ import * as buildpacks from '../lib/buildPacks';
.replace('-app', '')}:${storage.path}`;
}
if (storage.hostPath) {
return `${storage.hostPath}:${storage.path}`
return `${storage.hostPath}:${storage.path}`;
}
return `${applicationId}${storage.path.replace(/\//gi, '-')}:${storage.path}`;
}) || [];
@@ -163,17 +171,24 @@ import * as buildpacks from '../lib/buildPacks';
port: exposePort ? `${exposePort}:${port}` : port
});
try {
const composeVolumes = volumes.filter(v => {
if (!v.startsWith('.') && !v.startsWith('..') && !v.startsWith('/') && !v.startsWith('~')) {
return v;
}
}).map((volume) => {
return {
[`${volume.split(':')[0]}`]: {
name: volume.split(':')[0]
const composeVolumes = volumes
.filter((v) => {
if (
!v.startsWith('.') &&
!v.startsWith('..') &&
!v.startsWith('/') &&
!v.startsWith('~')
) {
return v;
}
};
});
})
.map((volume) => {
return {
[`${volume.split(':')[0]}`]: {
name: volume.split(':')[0]
}
};
});
const composeFile = {
version: '3.8',
services: {
@@ -240,7 +255,7 @@ import * as buildpacks from '../lib/buildPacks';
applicationId: application.id
});
}
await fs.rm(workdir, { recursive: true, force: true });
if (!isDev) await fs.rm(workdir, { recursive: true, force: true });
return;
}
try {
@@ -263,7 +278,7 @@ import * as buildpacks from '../lib/buildPacks';
await saveBuildLog({ line: error.stderr, buildId, applicationId });
}
} finally {
await fs.rm(workdir, { recursive: true, force: true });
if (!isDev) await fs.rm(workdir, { recursive: true, force: true });
await prisma.build.update({
where: { id: buildId },
data: { status: 'success' }
@@ -389,14 +404,14 @@ import * as buildpacks from '../lib/buildPacks';
.replace('-app', '')}:${storage.path}`;
}
if (storage.hostPath) {
return `${storage.hostPath}:${storage.path}`
return `${storage.hostPath}:${storage.path}`;
}
return `${applicationId}${storage.path.replace(/\//gi, '-')}:${storage.path}`;
}) || [];
try {
dockerComposeConfiguration = JSON.parse(dockerComposeConfiguration);
} catch (error) { }
} catch (error) {}
let deployNeeded = true;
let destinationType;
@@ -463,7 +478,7 @@ import * as buildpacks from '../lib/buildPacks';
try {
await prisma.build.update({ where: { id: buildId }, data: { commit } });
} catch (err) { }
} catch (err) {}
if (!pullmergeRequestId) {
if (configHash !== currentHash) {
@@ -504,8 +519,9 @@ import * as buildpacks from '../lib/buildPacks';
try {
await executeCommand({
dockerId: destinationDocker.id,
command: `docker ${location ? `--config ${location}` : ''
} pull ${imageName}:${customTag}`
command: `docker ${
location ? `--config ${location}` : ''
} pull ${imageName}:${customTag}`
});
imageFoundRemotely = true;
} catch (error) {
@@ -668,8 +684,9 @@ import * as buildpacks from '../lib/buildPacks';
try {
const { stdout: containers } = await executeCommand({
dockerId: destinationDockerId,
command: `docker ps -a --filter 'label=com.docker.compose.service=${pullmergeRequestId ? imageId : applicationId
}' --format {{.ID}}`
command: `docker ps -a --filter 'label=com.docker.compose.service=${
pullmergeRequestId ? imageId : applicationId
}' --format {{.ID}}`
});
if (containers) {
const containerArray = containers.split('\n');
@@ -701,17 +718,24 @@ import * as buildpacks from '../lib/buildPacks';
await saveDockerRegistryCredentials({ url, username, password, workdir });
}
try {
const composeVolumes = volumes.filter(v => {
if (!v.startsWith('.') && !v.startsWith('..') && !v.startsWith('/') && !v.startsWith('~')) {
return v;
}
}).map((volume) => {
return {
[`${volume.split(':')[0]}`]: {
name: volume.split(':')[0]
const composeVolumes = volumes
.filter((v) => {
if (
!v.startsWith('.') &&
!v.startsWith('..') &&
!v.startsWith('/') &&
!v.startsWith('~')
) {
return v;
}
};
});
})
.map((volume) => {
return {
[`${volume.split(':')[0]}`]: {
name: volume.split(':')[0]
}
};
});
const composeFile = {
version: '3.8',
services: {
@@ -782,7 +806,7 @@ import * as buildpacks from '../lib/buildPacks';
applicationId: application.id
});
}
await fs.rm(workdir, { recursive: true, force: true });
if (!isDev) await fs.rm(workdir, { recursive: true, force: true });
return;
}
try {
@@ -803,7 +827,7 @@ import * as buildpacks from '../lib/buildPacks';
await saveBuildLog({ line: error.stderr, buildId, applicationId });
}
} finally {
await fs.rm(workdir, { recursive: true, force: true });
if (!isDev) await fs.rm(workdir, { recursive: true, force: true });
await prisma.build.update({ where: { id: buildId }, data: { status: 'success' } });
}
});

View File

@@ -86,19 +86,20 @@ export async function migrateServicesToNewTemplate() {
if (template.variables) {
if (template.variables.length > 0) {
for (const variable of template.variables) {
const { defaultValue } = variable;
let { defaultValue } = variable;
defaultValue = defaultValue.toString();
const regex = /^\$\$.*\((\d+)\)$/g;
const length = Number(regex.exec(defaultValue)?.[1]) || undefined
if (variable.defaultValue.startsWith('$$generate_password')) {
if (defaultValue.startsWith('$$generate_password')) {
variable.value = generatePassword({ length });
} else if (variable.defaultValue.startsWith('$$generate_hex')) {
} else if (defaultValue.startsWith('$$generate_hex')) {
variable.value = generatePassword({ length, isHex: true });
} else if (variable.defaultValue.startsWith('$$generate_username')) {
} else if (defaultValue.startsWith('$$generate_username')) {
variable.value = cuid();
} else if (variable.defaultValue.startsWith('$$generate_token')) {
} else if (defaultValue.startsWith('$$generate_token')) {
variable.value = generateToken()
} else {
variable.value = variable.defaultValue || '';
variable.value = defaultValue || '';
}
}
}

View File

@@ -804,6 +804,7 @@ export async function buildCacheImageWithNode(data, imageForBuild) {
Dockerfile.push(`RUN ${installCommand}`);
}
Dockerfile.push(`RUN ${buildCommand}`);
Dockerfile.push('RUN rm -fr .git');
await fs.writeFile(`${workdir}/Dockerfile-cache`, Dockerfile.join('\n'));
await buildImage({ ...data, isCache: true });
}
@@ -821,6 +822,7 @@ export async function buildCacheImageForLaravel(data, imageForBuild) {
}
Dockerfile.push(`COPY *.json *.mix.js /app/`);
Dockerfile.push(`COPY resources /app/resources`);
Dockerfile.push('RUN rm -fr .git');
Dockerfile.push(`RUN yarn install && yarn production`);
await fs.writeFile(`${workdir}/Dockerfile-cache`, Dockerfile.join('\n'));
await buildImage({ ...data, isCache: true });
@@ -842,6 +844,7 @@ export async function buildCacheImageWithCargo(data, imageForBuild) {
Dockerfile.push('RUN cargo install cargo-chef');
Dockerfile.push(`COPY --from=planner-${applicationId} /app/recipe.json recipe.json`);
Dockerfile.push('RUN cargo chef cook --release --recipe-path recipe.json');
Dockerfile.push('RUN rm -fr .git');
await fs.writeFile(`${workdir}/Dockerfile-cache`, Dockerfile.join('\n'));
await buildImage({ ...data, isCache: true });
}

View File

@@ -47,7 +47,12 @@ export default async function (data) {
for (let [key, value] of Object.entries(dockerComposeYaml.services)) {
value['container_name'] = `${applicationId}-${key}`;
if (value['env_file']) {
delete value['env_file'];
}
let environment = typeof value['environment'] === 'undefined' ? [] : value['environment'];
console.log({ key, environment });
if (Object.keys(environment).length > 0) {
environment = Object.entries(environment).map(([key, value]) => `${key}=${value}`);
}
@@ -60,7 +65,7 @@ export default async function (data) {
const buildArgs = typeof build['args'] === 'undefined' ? [] : build['args'];
let finalArgs = [...buildEnvs];
if (Object.keys(buildArgs).length > 0) {
for (const arg of buildArgs) {
for (const arg of Object.keys(buildArgs)) {
const [key, _] = arg.split('=');
if (finalArgs.filter((env) => env.startsWith(key)).length === 0) {
finalArgs.push(arg);
@@ -87,7 +92,10 @@ export default async function (data) {
v.startsWith('~') ||
v.startsWith('$PWD')
) {
v = v.replace(/^\./, `~`).replace(/^\.\./, '~').replace(/^\$PWD/, '~');
v = v
.replace(/^\./, `~`)
.replace(/^\.\./, '~')
.replace(/^\$PWD/, '~');
} else {
if (!path) {
path = v;
@@ -110,10 +118,11 @@ export default async function (data) {
source.startsWith('~') ||
source.startsWith('$PWD')
) {
source = source.replace(/^\./, `~`).replace(/^\.\./, '~').replace(/^\$PWD/, '~');
console.log({source})
source = source
.replace(/^\./, `~`)
.replace(/^\.\./, '~')
.replace(/^\$PWD/, '~');
console.log({ source });
} else {
if (!target) {
target = source;
@@ -125,7 +134,6 @@ export default async function (data) {
return `${source}:${target}${mode ? ':' + mode : ''}`;
}
});
}
if (volumes.length > 0) {
@@ -136,16 +144,20 @@ export default async function (data) {
if (dockerComposeConfiguration[key]?.port) {
value['expose'] = [dockerComposeConfiguration[key].port];
}
if (value['networks']?.length > 0) {
value['networks'].forEach((network) => {
networks[network] = {
name: network
};
});
value['networks'] = [...(value['networks'] || ''), network];
} else {
value['networks'] = [network];
value['networks'] = [network];
if (value['build']?.network) {
delete value['build']['network'];
}
// if (value['networks']?.length > 0) {
// value['networks'].forEach((network) => {
// networks[network] = {
// name: network
// };
// });
// value['networks'] = [...(value['networks'] || ''), network];
// } else {
// value['networks'] = [network];
// }
dockerComposeYaml.services[key] = {
...dockerComposeYaml.services[key],

View File

@@ -36,6 +36,7 @@ const createDockerfile = async (data, image): Promise<void> => {
Dockerfile.push(`COPY .${baseDirectory || ''} ./`);
Dockerfile.push(`RUN deno cache ${denoMainFile}`);
Dockerfile.push(`ENV NO_COLOR true`);
Dockerfile.push('RUN rm -fr .git');
Dockerfile.push(`EXPOSE ${port}`);
Dockerfile.push(`CMD deno run ${denoOptions || ''} ${denoMainFile}`);
await fs.writeFile(`${workdir}/Dockerfile`, Dockerfile.join('\n'));

View File

@@ -12,6 +12,7 @@ const createDockerfile = async (data, imageforBuild): Promise<void> => {
if (baseImage?.includes('nginx')) {
Dockerfile.push(`COPY /nginx.conf /etc/nginx/nginx.conf`);
}
Dockerfile.push('RUN rm -fr .git');
Dockerfile.push(`EXPOSE ${port}`);
await fs.writeFile(`${workdir}/Dockerfile`, Dockerfile.join('\n'));
};

View File

@@ -30,6 +30,7 @@ const createDockerfile = async (data, image): Promise<void> => {
`COPY --chown=application:application --from=${applicationId}:${tag}-cache /app/mix-manifest.json /app/public/mix-manifest.json`
);
Dockerfile.push(`COPY --chown=application:application . ./`);
Dockerfile.push('RUN rm -fr .git');
Dockerfile.push(`EXPOSE ${port}`);
await fs.writeFile(`${workdir}/Dockerfile`, Dockerfile.join('\n'));
};

View File

@@ -13,7 +13,7 @@ const createDockerfile = async (data, image): Promise<void> => {
Dockerfile.push('RUN curl -f https://get.pnpm.io/v6.16.js | node - add --global pnpm@7');
}
Dockerfile.push(`COPY --from=${applicationId}:${tag}-cache /app${publishDirectory} ./`);
Dockerfile.push('RUN rm -fr .git');
Dockerfile.push(`EXPOSE ${port}`);
Dockerfile.push(`CMD ${startCommand}`);
await fs.writeFile(`${workdir}/Dockerfile`, Dockerfile.join('\n'));

View File

@@ -36,6 +36,7 @@ const createDockerfile = async (data, image): Promise<void> => {
Dockerfile.push(`COPY .${baseDirectory || ''} ./`);
Dockerfile.push(`RUN ${installCommand}`);
Dockerfile.push(`RUN ${buildCommand}`);
Dockerfile.push('RUN rm -fr .git');
Dockerfile.push(`EXPOSE ${port}`);
Dockerfile.push(`CMD ${startCommand}`);
} else if (deploymentType === 'static') {
@@ -43,6 +44,7 @@ const createDockerfile = async (data, image): Promise<void> => {
Dockerfile.push(`COPY /nginx.conf /etc/nginx/nginx.conf`);
}
Dockerfile.push(`COPY --from=${applicationId}:${tag}-cache /app${publishDirectory} ./`);
Dockerfile.push('RUN rm -fr .git');
Dockerfile.push(`EXPOSE 80`);
}

View File

@@ -34,6 +34,7 @@ const createDockerfile = async (data, image): Promise<void> => {
Dockerfile.push(`RUN ${buildCommand}`);
}
Dockerfile.push(`EXPOSE ${port}`);
Dockerfile.push('RUN rm -fr .git');
Dockerfile.push(`CMD ${startCommand}`);
await fs.writeFile(`${workdir}/Dockerfile`, Dockerfile.join('\n'));
};

View File

@@ -36,6 +36,7 @@ const createDockerfile = async (data, image): Promise<void> => {
Dockerfile.push(`COPY .${baseDirectory || ''} ./`);
Dockerfile.push(`RUN ${installCommand}`);
Dockerfile.push(`RUN ${buildCommand}`);
Dockerfile.push('RUN rm -fr .git');
Dockerfile.push(`EXPOSE ${port}`);
Dockerfile.push(`CMD ${startCommand}`);
} else if (deploymentType === 'static') {
@@ -43,6 +44,7 @@ const createDockerfile = async (data, image): Promise<void> => {
Dockerfile.push(`COPY /nginx.conf /etc/nginx/nginx.conf`);
}
Dockerfile.push(`COPY --from=${applicationId}:${tag}-cache /app${publishDirectory} ./`);
Dockerfile.push('RUN rm -fr .git');
Dockerfile.push(`EXPOSE 80`);
}

View File

@@ -28,6 +28,7 @@ const createDockerfile = async (data, image, htaccessFound): Promise<void> => {
}
Dockerfile.push(`COPY /entrypoint.sh /opt/docker/provision/entrypoint.d/30-entrypoint.sh`);
Dockerfile.push('RUN rm -fr .git');
Dockerfile.push(`EXPOSE ${port}`);
await fs.writeFile(`${workdir}/Dockerfile`, Dockerfile.join('\n'));
};

View File

@@ -52,7 +52,7 @@ const createDockerfile = async (data, image): Promise<void> => {
} else {
Dockerfile.push(`CMD python ${pythonModule}`);
}
Dockerfile.push('RUN rm -fr .git');
await fs.writeFile(`${workdir}/Dockerfile`, Dockerfile.join('\n'));
};

View File

@@ -12,6 +12,7 @@ const createDockerfile = async (data, image): Promise<void> => {
if (baseImage?.includes('nginx')) {
Dockerfile.push(`COPY /nginx.conf /etc/nginx/nginx.conf`);
}
Dockerfile.push('RUN rm -fr .git');
Dockerfile.push(`EXPOSE ${port}`);
await fs.writeFile(`${workdir}/Dockerfile`, Dockerfile.join('\n'));
};

View File

@@ -20,6 +20,7 @@ const createDockerfile = async (data, image, name): Promise<void> => {
);
Dockerfile.push(`RUN update-ca-certificates`);
Dockerfile.push(`COPY --from=${applicationId}:${tag}-cache /app/target/release/${name} ${name}`);
Dockerfile.push('RUN rm -fr .git');
Dockerfile.push(`EXPOSE ${port}`);
Dockerfile.push(`CMD ["/app/${name}"]`);
await fs.writeFile(`${workdir}/Dockerfile`, Dockerfile.join('\n'));

View File

@@ -38,6 +38,7 @@ const createDockerfile = async (data, image): Promise<void> => {
if (baseImage?.includes('nginx')) {
Dockerfile.push(`COPY /nginx.conf /etc/nginx/nginx.conf`);
}
Dockerfile.push('RUN rm -fr .git');
Dockerfile.push(`EXPOSE ${port}`);
await fs.writeFile(`${workdir}/Dockerfile`, Dockerfile.join('\n'));
};

View File

@@ -12,6 +12,7 @@ const createDockerfile = async (data, image): Promise<void> => {
if (baseImage?.includes('nginx')) {
Dockerfile.push(`COPY /nginx.conf /etc/nginx/nginx.conf`);
}
Dockerfile.push('RUN rm -fr .git');
Dockerfile.push(`EXPOSE ${port}`);
await fs.writeFile(`${workdir}/Dockerfile`, Dockerfile.join('\n'));
};

View File

@@ -12,6 +12,7 @@ const createDockerfile = async (data, image): Promise<void> => {
if (baseImage?.includes('nginx')) {
Dockerfile.push(`COPY /nginx.conf /etc/nginx/nginx.conf`);
}
Dockerfile.push('RUN rm -fr .git');
Dockerfile.push(`EXPOSE ${port}`);
await fs.writeFile(`${workdir}/Dockerfile`, Dockerfile.join('\n'));
};

View File

@@ -19,7 +19,7 @@ import { saveBuildLog } from './buildPacks/common';
import { scheduler } from './scheduler';
import type { ExecaChildProcess } from 'execa';
export const version = '3.12.28';
export const version = '3.12.31';
export const isDev = process.env.NODE_ENV === 'development';
export const proxyPort = process.env.COOLIFY_PROXY_PORT;
export const proxySecurePort = process.env.COOLIFY_PROXY_SECURE_PORT;
@@ -402,8 +402,8 @@ export const supportedDatabaseTypesAndVersions = [
fancyName: 'MongoDB',
baseImage: 'bitnami/mongodb',
baseImageARM: 'mongo',
versions: ['5.0', '4.4', '4.2'],
versionsARM: ['5.0', '4.4', '4.2']
versions: ['6.0', '5.0', '4.4', '4.2'],
versionsARM: ['6.0', '5.0', '4.4', '4.2']
},
{
name: 'mysql',
@@ -418,16 +418,16 @@ export const supportedDatabaseTypesAndVersions = [
fancyName: 'MariaDB',
baseImage: 'bitnami/mariadb',
baseImageARM: 'mariadb',
versions: ['10.8', '10.7', '10.6', '10.5', '10.4', '10.3', '10.2'],
versionsARM: ['10.8', '10.7', '10.6', '10.5', '10.4', '10.3', '10.2']
versions: ['10.11', '10.10', '10.9', '10.8', '10.7', '10.6', '10.5', '10.4', '10.3', '10.2'],
versionsARM: ['10.11', '10.10', '10.9', '10.8', '10.7', '10.6', '10.5', '10.4', '10.3', '10.2']
},
{
name: 'postgresql',
fancyName: 'PostgreSQL',
baseImage: 'bitnami/postgresql',
baseImageARM: 'postgres',
versions: ['14.5.0', '13.8.0', '12.12.0', '11.17.0', '10.22.0'],
versionsARM: ['14.5', '13.8', '12.12', '11.17', '10.22']
versions: ['15.2.0', '14.7.0', '14.5.0', '13.8.0', '12.12.0', '11.17.0', '10.22.0'],
versionsARM: ['15.2', '14.7', '14.5', '13.8', '12.12', '11.17', '10.22']
},
{
name: 'redis',
@@ -442,14 +442,14 @@ export const supportedDatabaseTypesAndVersions = [
fancyName: 'CouchDB',
baseImage: 'bitnami/couchdb',
baseImageARM: 'couchdb',
versions: ['3.2.2', '3.1.2', '2.3.1'],
versionsARM: ['3.2.2', '3.1.2', '2.3.1']
versions: ['3.3.1', '3.2.2', '3.1.2', '2.3.1'],
versionsARM: ['3.3', '3.2.2', '3.1.2', '2.3.1']
},
{
name: 'edgedb',
fancyName: 'EdgeDB',
baseImage: 'edgedb/edgedb',
versions: ['latest', '2.1', '2.0', '1.4']
versions: ['latest', '2.9', '2.8', '2.7']
}
];
@@ -849,97 +849,97 @@ export function generatePassword({
type DatabaseConfiguration =
| {
volume: string;
image: string;
command?: string;
ulimits: Record<string, unknown>;
privatePort: number;
environmentVariables: {
MYSQL_DATABASE: string;
MYSQL_PASSWORD: string;
MYSQL_ROOT_USER: string;
MYSQL_USER: string;
MYSQL_ROOT_PASSWORD: string;
};
}
volume: string;
image: string;
command?: string;
ulimits: Record<string, unknown>;
privatePort: number;
environmentVariables: {
MYSQL_DATABASE: string;
MYSQL_PASSWORD: string;
MYSQL_ROOT_USER: string;
MYSQL_USER: string;
MYSQL_ROOT_PASSWORD: string;
};
}
| {
volume: string;
image: string;
command?: string;
ulimits: Record<string, unknown>;
privatePort: number;
environmentVariables: {
MONGO_INITDB_ROOT_USERNAME?: string;
MONGO_INITDB_ROOT_PASSWORD?: string;
MONGODB_ROOT_USER?: string;
MONGODB_ROOT_PASSWORD?: string;
};
}
volume: string;
image: string;
command?: string;
ulimits: Record<string, unknown>;
privatePort: number;
environmentVariables: {
MONGO_INITDB_ROOT_USERNAME?: string;
MONGO_INITDB_ROOT_PASSWORD?: string;
MONGODB_ROOT_USER?: string;
MONGODB_ROOT_PASSWORD?: string;
};
}
| {
volume: string;
image: string;
command?: string;
ulimits: Record<string, unknown>;
privatePort: number;
environmentVariables: {
MARIADB_ROOT_USER: string;
MARIADB_ROOT_PASSWORD: string;
MARIADB_USER: string;
MARIADB_PASSWORD: string;
MARIADB_DATABASE: string;
};
}
volume: string;
image: string;
command?: string;
ulimits: Record<string, unknown>;
privatePort: number;
environmentVariables: {
MARIADB_ROOT_USER: string;
MARIADB_ROOT_PASSWORD: string;
MARIADB_USER: string;
MARIADB_PASSWORD: string;
MARIADB_DATABASE: string;
};
}
| {
volume: string;
image: string;
command?: string;
ulimits: Record<string, unknown>;
privatePort: number;
environmentVariables: {
POSTGRES_PASSWORD?: string;
POSTGRES_USER?: string;
POSTGRES_DB?: string;
POSTGRESQL_POSTGRES_PASSWORD?: string;
POSTGRESQL_USERNAME?: string;
POSTGRESQL_PASSWORD?: string;
POSTGRESQL_DATABASE?: string;
};
}
volume: string;
image: string;
command?: string;
ulimits: Record<string, unknown>;
privatePort: number;
environmentVariables: {
POSTGRES_PASSWORD?: string;
POSTGRES_USER?: string;
POSTGRES_DB?: string;
POSTGRESQL_POSTGRES_PASSWORD?: string;
POSTGRESQL_USERNAME?: string;
POSTGRESQL_PASSWORD?: string;
POSTGRESQL_DATABASE?: string;
};
}
| {
volume: string;
image: string;
command?: string;
ulimits: Record<string, unknown>;
privatePort: number;
environmentVariables: {
REDIS_AOF_ENABLED: string;
REDIS_PASSWORD: string;
};
}
volume: string;
image: string;
command?: string;
ulimits: Record<string, unknown>;
privatePort: number;
environmentVariables: {
REDIS_AOF_ENABLED: string;
REDIS_PASSWORD: string;
};
}
| {
volume: string;
image: string;
command?: string;
ulimits: Record<string, unknown>;
privatePort: number;
environmentVariables: {
COUCHDB_PASSWORD: string;
COUCHDB_USER: string;
};
}
volume: string;
image: string;
command?: string;
ulimits: Record<string, unknown>;
privatePort: number;
environmentVariables: {
COUCHDB_PASSWORD: string;
COUCHDB_USER: string;
};
}
| {
volume: string;
image: string;
command?: string;
ulimits: Record<string, unknown>;
privatePort: number;
environmentVariables: {
EDGEDB_SERVER_PASSWORD: string;
EDGEDB_SERVER_USER: string;
EDGEDB_SERVER_DATABASE: string;
EDGEDB_SERVER_TLS_CERT_MODE: string;
};
};
volume: string;
image: string;
command?: string;
ulimits: Record<string, unknown>;
privatePort: number;
environmentVariables: {
EDGEDB_SERVER_PASSWORD: string;
EDGEDB_SERVER_USER: string;
EDGEDB_SERVER_DATABASE: string;
EDGEDB_SERVER_TLS_CERT_MODE: string;
};
};
export function generateDatabaseConfiguration(database: any): DatabaseConfiguration {
const { id, dbUser, dbUserPassword, rootUser, rootUserPassword, defaultDatabase, version, type } =
database;
@@ -1013,7 +1013,7 @@ export function generateDatabaseConfiguration(database: any): DatabaseConfigurat
ulimits: {}
};
if (isARM()) {
configuration.volume = `${id}-${type}-data:/var/lib/postgresql`;
configuration.volume = `${id}-${type}-data:/var/lib/postgresql/data`;
configuration.environmentVariables = {
POSTGRES_PASSWORD: dbUserPassword,
POSTGRES_USER: dbUser,
@@ -1038,8 +1038,9 @@ export function generateDatabaseConfiguration(database: any): DatabaseConfigurat
};
if (isARM()) {
configuration.volume = `${id}-${type}-data:/data`;
configuration.command = `/usr/local/bin/redis-server --appendonly ${appendOnly ? 'yes' : 'no'
} --requirepass ${dbUserPassword}`;
configuration.command = `/usr/local/bin/redis-server --appendonly ${
appendOnly ? 'yes' : 'no'
} --requirepass ${dbUserPassword}`;
}
return configuration;
} else if (type === 'couchdb') {
@@ -1124,12 +1125,12 @@ export type ComposeFileService = {
command?: string;
ports?: string[];
build?:
| {
context: string;
dockerfile: string;
args?: Record<string, unknown>;
}
| string;
| {
context: string;
dockerfile: string;
args?: Record<string, unknown>;
}
| string;
deploy?: {
restart_policy?: {
condition?: string;
@@ -1200,7 +1201,7 @@ export const createDirectories = async ({
let workdirFound = false;
try {
workdirFound = !!(await fs.stat(workdir));
} catch (error) { }
} catch (error) {}
if (workdirFound) {
await executeCommand({ command: `rm -fr ${workdir}` });
}
@@ -1727,7 +1728,7 @@ export async function stopBuild(buildId, applicationId) {
}
}
count++;
} catch (error) { }
} catch (error) {}
}, 100);
});
}
@@ -1750,7 +1751,7 @@ export async function cleanupDockerStorage(dockerId) {
// Cleanup images that are not used by any container
try {
await executeCommand({ dockerId, command: `docker image prune -af` });
} catch (error) { }
} catch (error) {}
// Prune coolify managed containers
try {
@@ -1758,12 +1759,12 @@ export async function cleanupDockerStorage(dockerId) {
dockerId,
command: `docker container prune -f --filter "label=coolify.managed=true"`
});
} catch (error) { }
} catch (error) {}
// Cleanup build caches
try {
await executeCommand({ dockerId, command: `docker builder prune -af` });
} catch (error) { }
} catch (error) {}
}
export function persistentVolumes(id, persistentStorage, config) {

View File

@@ -302,7 +302,7 @@ export async function startDatabase(request: FastifyRequest<OnlyId>) {
databaseSecret
} = database;
const { privatePort, command, environmentVariables, image, volume, ulimits } =
generateDatabaseConfiguration(database, arch);
generateDatabaseConfiguration(database);
const network = destinationDockerId && destinationDocker.network;
const volumeName = volume.split(':')[0];

View File

@@ -1,279 +1,353 @@
import type { FastifyRequest } from 'fastify';
import { FastifyReply } from 'fastify';
import sshConfig from 'ssh-config'
import fs from 'fs/promises'
import os from 'os';
import { createRemoteEngineConfiguration, decrypt, errorHandler, executeCommand, listSettings, prisma, startTraefikProxy, stopTraefikProxy } from '../../../../lib/common';
import {
errorHandler,
executeCommand,
listSettings,
prisma,
startTraefikProxy,
stopTraefikProxy
} from '../../../../lib/common';
import { checkContainer } from '../../../../lib/docker';
import type { OnlyId } from '../../../../types';
import type { CheckDestination, ListDestinations, NewDestination, Proxy, SaveDestinationSettings } from './types';
import type {
CheckDestination,
ListDestinations,
NewDestination,
Proxy,
SaveDestinationSettings
} from './types';
export async function listDestinations(request: FastifyRequest<ListDestinations>) {
try {
const teamId = request.user.teamId;
const { onlyVerified = false } = request.query
let destinations = []
if (teamId === '0') {
destinations = await prisma.destinationDocker.findMany({ include: { teams: true } });
} else {
destinations = await prisma.destinationDocker.findMany({
where: { teams: { some: { id: teamId } } },
include: { teams: true }
});
}
if (onlyVerified) {
destinations = destinations.filter(destination => destination.engine || (destination.remoteEngine && destination.remoteVerified))
}
return {
destinations
}
} catch ({ status, message }) {
return errorHandler({ status, message })
}
try {
const teamId = request.user.teamId;
const { onlyVerified = false } = request.query;
let destinations = [];
if (teamId === '0') {
destinations = await prisma.destinationDocker.findMany({ include: { teams: true } });
} else {
destinations = await prisma.destinationDocker.findMany({
where: { teams: { some: { id: teamId } } },
include: { teams: true }
});
}
if (onlyVerified) {
destinations = destinations.filter(
(destination) =>
destination.engine || (destination.remoteEngine && destination.remoteVerified)
);
}
return {
destinations
};
} catch ({ status, message }) {
return errorHandler({ status, message });
}
}
export async function checkDestination(request: FastifyRequest<CheckDestination>) {
try {
const { network } = request.body;
const found = await prisma.destinationDocker.findFirst({ where: { network } });
if (found) {
throw {
message: `Network already exists: ${network}`
};
}
return {}
} catch ({ status, message }) {
return errorHandler({ status, message })
}
try {
const { network } = request.body;
const found = await prisma.destinationDocker.findFirst({ where: { network } });
if (found) {
throw {
message: `Network already exists: ${network}`
};
}
return {};
} catch ({ status, message }) {
return errorHandler({ status, message });
}
}
export async function getDestination(request: FastifyRequest<OnlyId>) {
try {
const { id } = request.params
const teamId = request.user?.teamId;
const destination = await prisma.destinationDocker.findFirst({
where: { id, teams: { some: { id: teamId === '0' ? undefined : teamId } } },
include: { sshKey: true, application: true, service: true, database: true }
});
if (!destination && id !== 'new') {
throw { status: 404, message: `Destination not found.` };
}
const settings = await listSettings();
const payload = {
destination,
settings
};
return {
...payload
};
} catch ({ status, message }) {
return errorHandler({ status, message })
}
try {
const { id } = request.params;
const teamId = request.user?.teamId;
const destination = await prisma.destinationDocker.findFirst({
where: { id, teams: { some: { id: teamId === '0' ? undefined : teamId } } },
include: { sshKey: true, application: true, service: true, database: true }
});
if (!destination && id !== 'new') {
throw { status: 404, message: `Destination not found.` };
}
const settings = await listSettings();
const payload = {
destination,
settings
};
return {
...payload
};
} catch ({ status, message }) {
return errorHandler({ status, message });
}
}
export async function newDestination(request: FastifyRequest<NewDestination>, reply: FastifyReply) {
try {
const teamId = request.user.teamId;
const { id } = request.params
try {
const teamId = request.user.teamId;
const { id } = request.params;
let { name, network, engine, isCoolifyProxyUsed, remoteIpAddress, remoteUser, remotePort } = request.body
if (id === 'new') {
if (engine) {
const { stdout } = await await executeCommand({ command: `docker network ls --filter 'name=^${network}$' --format '{{json .}}'` });
if (stdout === '') {
await await executeCommand({ command: `docker network create --attachable ${network}` });
}
await prisma.destinationDocker.create({
data: { name, teams: { connect: { id: teamId } }, engine, network, isCoolifyProxyUsed }
});
const destinations = await prisma.destinationDocker.findMany({ where: { engine } });
const destination = destinations.find((destination) => destination.network === network);
if (destinations.length > 0) {
const proxyConfigured = destinations.find(
(destination) => destination.network !== network && destination.isCoolifyProxyUsed === true
);
if (proxyConfigured) {
isCoolifyProxyUsed = !!proxyConfigured.isCoolifyProxyUsed;
}
await prisma.destinationDocker.updateMany({ where: { engine }, data: { isCoolifyProxyUsed } });
}
if (isCoolifyProxyUsed) {
await startTraefikProxy(destination.id);
}
return reply.code(201).send({ id: destination.id });
} else {
const destination = await prisma.destinationDocker.create({
data: { name, teams: { connect: { id: teamId } }, engine, network, isCoolifyProxyUsed, remoteEngine: true, remoteIpAddress, remoteUser, remotePort: Number(remotePort) }
});
return reply.code(201).send({ id: destination.id })
}
} else {
await prisma.destinationDocker.update({ where: { id }, data: { name, engine, network } });
return reply.code(201).send();
}
} catch ({ status, message }) {
return errorHandler({ status, message })
}
let { name, network, engine, isCoolifyProxyUsed, remoteIpAddress, remoteUser, remotePort } =
request.body;
if (id === 'new') {
if (engine) {
const { stdout } = await await executeCommand({
command: `docker network ls --filter 'name=^${network}$' --format '{{json .}}'`
});
if (stdout === '') {
await await executeCommand({ command: `docker network create --attachable ${network}` });
}
await prisma.destinationDocker.create({
data: { name, teams: { connect: { id: teamId } }, engine, network, isCoolifyProxyUsed }
});
const destinations = await prisma.destinationDocker.findMany({ where: { engine } });
const destination = destinations.find((destination) => destination.network === network);
if (destinations.length > 0) {
const proxyConfigured = destinations.find(
(destination) =>
destination.network !== network && destination.isCoolifyProxyUsed === true
);
if (proxyConfigured) {
isCoolifyProxyUsed = !!proxyConfigured.isCoolifyProxyUsed;
}
await prisma.destinationDocker.updateMany({
where: { engine },
data: { isCoolifyProxyUsed }
});
}
if (isCoolifyProxyUsed) {
await startTraefikProxy(destination.id);
}
return reply.code(201).send({ id: destination.id });
} else {
const destination = await prisma.destinationDocker.create({
data: {
name,
teams: { connect: { id: teamId } },
engine,
network,
isCoolifyProxyUsed,
remoteEngine: true,
remoteIpAddress,
remoteUser,
remotePort: Number(remotePort)
}
});
return reply.code(201).send({ id: destination.id });
}
} else {
await prisma.destinationDocker.update({ where: { id }, data: { name, engine, network } });
return reply.code(201).send();
}
} catch ({ status, message }) {
return errorHandler({ status, message });
}
}
export async function deleteDestination(request: FastifyRequest<OnlyId>) {
try {
const { id } = request.params
const { network, remoteVerified, engine, isCoolifyProxyUsed } = await prisma.destinationDocker.findUnique({ where: { id } });
if (isCoolifyProxyUsed) {
if (engine || remoteVerified) {
const { stdout: found } = await executeCommand({
dockerId: id,
command: `docker ps -a --filter network=${network} --filter name=coolify-proxy --format '{{.}}'`
})
if (found) {
await executeCommand({ dockerId: id, command: `docker network disconnect ${network} coolify-proxy` })
await executeCommand({ dockerId: id, command: `docker network rm ${network}` })
}
}
}
await prisma.destinationDocker.delete({ where: { id } });
return {}
} catch ({ status, message }) {
return errorHandler({ status, message })
}
try {
const { id } = request.params;
const appFound = await prisma.application.findFirst({ where: { destinationDockerId: id } });
const serviceFound = await prisma.service.findFirst({ where: { destinationDockerId: id } });
const databaseFound = await prisma.database.findFirst({ where: { destinationDockerId: id } });
if (appFound || serviceFound || databaseFound) {
throw {
message: `Destination is in use.<br>Remove all applications, services and databases using this destination first.`
};
}
const { network, remoteVerified, engine, isCoolifyProxyUsed } =
await prisma.destinationDocker.findUnique({ where: { id } });
if (isCoolifyProxyUsed) {
if (engine || remoteVerified) {
const { stdout: found } = await executeCommand({
dockerId: id,
command: `docker ps -a --filter network=${network} --filter name=coolify-proxy --format '{{.}}'`
});
if (found) {
await executeCommand({
dockerId: id,
command: `docker network disconnect ${network} coolify-proxy`
});
await executeCommand({ dockerId: id, command: `docker network rm ${network}` });
}
}
}
await prisma.destinationDocker.delete({ where: { id } });
return {};
} catch ({ status, message }) {
return errorHandler({ status, message });
}
}
export async function saveDestinationSettings(request: FastifyRequest<SaveDestinationSettings>) {
try {
const { engine, isCoolifyProxyUsed } = request.body;
await prisma.destinationDocker.updateMany({
where: { engine },
data: { isCoolifyProxyUsed }
});
try {
const { engine, isCoolifyProxyUsed } = request.body;
await prisma.destinationDocker.updateMany({
where: { engine },
data: { isCoolifyProxyUsed }
});
return {
status: 202
}
// return reply.code(201).send();
} catch ({ status, message }) {
return errorHandler({ status, message })
}
return {
status: 202
};
// return reply.code(201).send();
} catch ({ status, message }) {
return errorHandler({ status, message });
}
}
export async function startProxy(request: FastifyRequest<Proxy>) {
const { id } = request.params
try {
await startTraefikProxy(id);
return {}
} catch ({ status, message }) {
await stopTraefikProxy(id);
return errorHandler({ status, message })
}
const { id } = request.params;
try {
await startTraefikProxy(id);
return {};
} catch ({ status, message }) {
await stopTraefikProxy(id);
return errorHandler({ status, message });
}
}
export async function stopProxy(request: FastifyRequest<Proxy>) {
const { id } = request.params
try {
await stopTraefikProxy(id);
return {}
} catch ({ status, message }) {
return errorHandler({ status, message })
}
const { id } = request.params;
try {
await stopTraefikProxy(id);
return {};
} catch ({ status, message }) {
return errorHandler({ status, message });
}
}
export async function restartProxy(request: FastifyRequest<Proxy>) {
const { id } = request.params
try {
await stopTraefikProxy(id);
await startTraefikProxy(id);
await prisma.destinationDocker.update({
where: { id },
data: { isCoolifyProxyUsed: true }
});
return {}
} catch ({ status, message }) {
await prisma.destinationDocker.update({
where: { id },
data: { isCoolifyProxyUsed: false }
});
return errorHandler({ status, message })
}
const { id } = request.params;
try {
await stopTraefikProxy(id);
await startTraefikProxy(id);
await prisma.destinationDocker.update({
where: { id },
data: { isCoolifyProxyUsed: true }
});
return {};
} catch ({ status, message }) {
await prisma.destinationDocker.update({
where: { id },
data: { isCoolifyProxyUsed: false }
});
return errorHandler({ status, message });
}
}
export async function assignSSHKey(request: FastifyRequest) {
try {
const { id: sshKeyId } = request.body;
const { id } = request.params;
await prisma.destinationDocker.update({ where: { id }, data: { sshKey: { connect: { id: sshKeyId } } } })
return {}
} catch ({ status, message }) {
return errorHandler({ status, message })
}
try {
const { id: sshKeyId } = request.body;
const { id } = request.params;
await prisma.destinationDocker.update({
where: { id },
data: { sshKey: { connect: { id: sshKeyId } } }
});
return {};
} catch ({ status, message }) {
return errorHandler({ status, message });
}
}
export async function verifyRemoteDockerEngineFn(id: string) {
const { remoteIpAddress, network, isCoolifyProxyUsed } = await prisma.destinationDocker.findFirst({ where: { id } })
const daemonJson = `daemon-${id}.json`
try {
await executeCommand({ sshCommand: true, command: `docker network inspect ${network}`, dockerId: id });
} catch (error) {
await executeCommand({ command: `docker network create --attachable ${network}`, dockerId: id });
}
const { remoteIpAddress, network, isCoolifyProxyUsed } = await prisma.destinationDocker.findFirst(
{ where: { id } }
);
const daemonJson = `daemon-${id}.json`;
try {
await executeCommand({
sshCommand: true,
command: `docker network inspect ${network}`,
dockerId: id
});
} catch (error) {
await executeCommand({
command: `docker network create --attachable ${network}`,
dockerId: id
});
}
try {
await executeCommand({ sshCommand: true, command: `docker network inspect coolify-infra`, dockerId: id });
} catch (error) {
await executeCommand({ command: `docker network create --attachable coolify-infra`, dockerId: id });
}
try {
await executeCommand({
sshCommand: true,
command: `docker network inspect coolify-infra`,
dockerId: id
});
} catch (error) {
await executeCommand({
command: `docker network create --attachable coolify-infra`,
dockerId: id
});
}
if (isCoolifyProxyUsed) await startTraefikProxy(id);
let isUpdated = false;
let daemonJsonParsed = {
"live-restore": true,
"features": {
"buildkit": true
}
};
try {
const { stdout: daemonJson } = await executeCommand({ sshCommand: true, dockerId: id, command: `cat /etc/docker/daemon.json` });
daemonJsonParsed = JSON.parse(daemonJson);
if (!daemonJsonParsed['live-restore'] || daemonJsonParsed['live-restore'] !== true) {
isUpdated = true;
daemonJsonParsed['live-restore'] = true
}
if (!daemonJsonParsed?.features?.buildkit) {
isUpdated = true;
daemonJsonParsed.features = {
buildkit: true
}
}
} catch (error) {
isUpdated = true;
}
try {
if (isUpdated) {
await executeCommand({ shell: true, command: `echo '${JSON.stringify(daemonJsonParsed, null, 2)}' > /tmp/${daemonJson}` })
await executeCommand({ dockerId: id, command: `scp /tmp/${daemonJson} ${remoteIpAddress}-remote:/etc/docker/daemon.json` });
await executeCommand({ command: `rm /tmp/${daemonJson}` })
await executeCommand({ sshCommand: true, dockerId: id, command: `systemctl restart docker` });
}
await prisma.destinationDocker.update({ where: { id }, data: { remoteVerified: true } })
} catch (error) {
throw new Error('Error while verifying remote docker engine')
}
if (isCoolifyProxyUsed) await startTraefikProxy(id);
let isUpdated = false;
let daemonJsonParsed = {
'live-restore': true,
features: {
buildkit: true
}
};
try {
const { stdout: daemonJson } = await executeCommand({
sshCommand: true,
dockerId: id,
command: `cat /etc/docker/daemon.json`
});
daemonJsonParsed = JSON.parse(daemonJson);
if (!daemonJsonParsed['live-restore'] || daemonJsonParsed['live-restore'] !== true) {
isUpdated = true;
daemonJsonParsed['live-restore'] = true;
}
if (!daemonJsonParsed?.features?.buildkit) {
isUpdated = true;
daemonJsonParsed.features = {
buildkit: true
};
}
} catch (error) {
isUpdated = true;
}
try {
if (isUpdated) {
await executeCommand({
shell: true,
command: `echo '${JSON.stringify(daemonJsonParsed, null, 2)}' > /tmp/${daemonJson}`
});
await executeCommand({
dockerId: id,
command: `scp /tmp/${daemonJson} ${remoteIpAddress}-remote:/etc/docker/daemon.json`
});
await executeCommand({ command: `rm /tmp/${daemonJson}` });
await executeCommand({ sshCommand: true, dockerId: id, command: `systemctl restart docker` });
}
await prisma.destinationDocker.update({ where: { id }, data: { remoteVerified: true } });
} catch (error) {
throw new Error('Error while verifying remote docker engine');
}
}
export async function verifyRemoteDockerEngine(request: FastifyRequest<OnlyId>, reply: FastifyReply) {
const { id } = request.params;
try {
await verifyRemoteDockerEngineFn(id);
return reply.code(201).send()
} catch ({ status, message }) {
await prisma.destinationDocker.update({ where: { id }, data: { remoteVerified: false } })
return errorHandler({ status, message })
}
export async function verifyRemoteDockerEngine(
request: FastifyRequest<OnlyId>,
reply: FastifyReply
) {
const { id } = request.params;
try {
await verifyRemoteDockerEngineFn(id);
return reply.code(201).send();
} catch ({ status, message }) {
await prisma.destinationDocker.update({ where: { id }, data: { remoteVerified: false } });
return errorHandler({ status, message });
}
}
export async function getDestinationStatus(request: FastifyRequest<OnlyId>) {
try {
const { id } = request.params
const destination = await prisma.destinationDocker.findUnique({ where: { id } })
const { found: isRunning } = await checkContainer({ dockerId: destination.id, container: 'coolify-proxy', remove: true })
return {
isRunning
}
} catch ({ status, message }) {
return errorHandler({ status, message })
}
try {
const { id } = request.params;
const destination = await prisma.destinationDocker.findUnique({ where: { id } });
const { found: isRunning } = await checkContainer({
dockerId: destination.id,
container: 'coolify-proxy',
remove: true
});
return {
isRunning
};
} catch ({ status, message }) {
return errorHandler({ status, message });
}
}

View File

@@ -412,22 +412,23 @@ export async function saveServiceType(
if (foundTemplate.variables) {
if (foundTemplate.variables.length > 0) {
for (const variable of foundTemplate.variables) {
const { defaultValue } = variable;
let { defaultValue } = variable;
defaultValue = defaultValue.toString();
const regex = /^\$\$.*\((\d+)\)$/g;
const length = Number(regex.exec(defaultValue)?.[1]) || undefined;
if (variable.defaultValue.startsWith('$$generate_password')) {
if (defaultValue.startsWith('$$generate_password')) {
variable.value = generatePassword({ length });
} else if (variable.defaultValue.startsWith('$$generate_hex')) {
} else if (defaultValue.startsWith('$$generate_hex')) {
variable.value = generatePassword({ length, isHex: true });
} else if (variable.defaultValue.startsWith('$$generate_username')) {
} else if (defaultValue.startsWith('$$generate_username')) {
variable.value = cuid();
} else if (variable.defaultValue.startsWith('$$generate_token')) {
} else if (defaultValue.startsWith('$$generate_token')) {
variable.value = generateToken();
} else {
variable.value = variable.defaultValue || '';
variable.value = defaultValue || '';
}
const foundVariableSomewhereElse = foundTemplate.variables.find((v) =>
v.defaultValue.includes(variable.id)
v.defaultValue.toString().includes(variable.id)
);
if (foundVariableSomewhereElse) {
foundVariableSomewhereElse.value = foundVariableSomewhereElse.value.replaceAll(
@@ -746,7 +747,10 @@ export async function saveService(request: FastifyRequest<SaveService>, reply: F
let { id: settingId, name, value, changed = false, isNew = false, variableName } = setting;
if (value) {
if (changed) {
await prisma.serviceSetting.update({ where: { id: settingId }, data: { value: value.replace(/\n/, "\\n") } });
await prisma.serviceSetting.update({
where: { id: settingId },
data: { value: value.replace(/\n/, '\\n') }
});
}
if (isNew) {
if (!variableName) {
@@ -1101,14 +1105,17 @@ export async function activateWordpressFtp(
shell: true
});
}
} catch (error) { }
} catch (error) {}
const volumes = [
`${id}-wordpress-data:/home/${ftpUser}/wordpress`,
`${isDev ? hostkeyDir : '/var/lib/docker/volumes/coolify-ssl-certs/_data/hostkeys'
`${
isDev ? hostkeyDir : '/var/lib/docker/volumes/coolify-ssl-certs/_data/hostkeys'
}/${id}.ed25519:/etc/ssh/ssh_host_ed25519_key`,
`${isDev ? hostkeyDir : '/var/lib/docker/volumes/coolify-ssl-certs/_data/hostkeys'
`${
isDev ? hostkeyDir : '/var/lib/docker/volumes/coolify-ssl-certs/_data/hostkeys'
}/${id}.rsa:/etc/ssh/ssh_host_rsa_key`,
`${isDev ? hostkeyDir : '/var/lib/docker/volumes/coolify-ssl-certs/_data/hostkeys'
`${
isDev ? hostkeyDir : '/var/lib/docker/volumes/coolify-ssl-certs/_data/hostkeys'
}/${id}.sh:/etc/sftp.d/chmod.sh`
];
@@ -1178,6 +1185,6 @@ export async function activateWordpressFtp(
await executeCommand({
command: `rm -fr ${hostkeyDir}/${id}-docker-compose.yml ${hostkeyDir}/${id}.ed25519 ${hostkeyDir}/${id}.ed25519.pub ${hostkeyDir}/${id}.rsa ${hostkeyDir}/${id}.rsa.pub ${hostkeyDir}/${id}.sh`
});
} catch (error) { }
} catch (error) {}
}
}

View File

@@ -1,191 +1,231 @@
import cuid from 'cuid';
import type { FastifyRequest } from 'fastify';
import { FastifyReply } from 'fastify';
import { decrypt, encrypt, errorHandler, prisma } from '../../../../lib/common';
import { OnlyId } from '../../../../types';
import { CheckGitLabOAuthId, SaveGitHubSource, SaveGitLabSource } from './types';
export async function listSources(request: FastifyRequest) {
try {
const teamId = request.user?.teamId;
const sources = await prisma.gitSource.findMany({
where: { OR: [{ teams: { some: { id: teamId === "0" ? undefined : teamId } } }, { isSystemWide: true }] },
include: { teams: true, githubApp: true, gitlabApp: true }
});
return {
sources
}
} catch ({ status, message }) {
return errorHandler({ status, message })
}
try {
const teamId = request.user?.teamId;
const sources = await prisma.gitSource.findMany({
where: {
OR: [
{ teams: { some: { id: teamId === '0' ? undefined : teamId } } },
{ isSystemWide: true }
]
},
include: { teams: true, githubApp: true, gitlabApp: true }
});
return {
sources
};
} catch ({ status, message }) {
return errorHandler({ status, message });
}
}
export async function saveSource(request, reply) {
try {
const { id } = request.params
let { name, htmlUrl, apiUrl, customPort, customUser, isSystemWide } = request.body
if (customPort) customPort = Number(customPort)
await prisma.gitSource.update({
where: { id },
data: { name, htmlUrl, apiUrl, customPort, customUser, isSystemWide }
});
return reply.code(201).send()
} catch ({ status, message }) {
return errorHandler({ status, message })
}
try {
const { id } = request.params;
let { name, htmlUrl, apiUrl, customPort, customUser, isSystemWide } = request.body;
if (customPort) customPort = Number(customPort);
await prisma.gitSource.update({
where: { id },
data: { name, htmlUrl, apiUrl, customPort, customUser, isSystemWide }
});
return reply.code(201).send();
} catch ({ status, message }) {
return errorHandler({ status, message });
}
}
export async function getSource(request: FastifyRequest<OnlyId>) {
try {
const { id } = request.params
const { teamId } = request.user
const settings = await prisma.setting.findFirst({});
try {
const { id } = request.params;
const { teamId } = request.user;
const settings = await prisma.setting.findFirst({});
if (id === 'new') {
return {
source: {
name: null,
type: null,
htmlUrl: null,
apiUrl: null,
organization: null,
customPort: 22,
customUser: 'git',
},
settings
}
}
if (id === 'new') {
return {
source: {
name: null,
type: null,
htmlUrl: null,
apiUrl: null,
organization: null,
customPort: 22,
customUser: 'git'
},
settings
};
}
const source = await prisma.gitSource.findFirst({
where: { id, OR: [{ teams: { some: { id: teamId === "0" ? undefined : teamId } } }, { isSystemWide: true }] },
include: { githubApp: true, gitlabApp: true }
});
if (!source) {
throw { status: 404, message: 'Source not found.' }
}
const source = await prisma.gitSource.findFirst({
where: {
id,
OR: [
{ teams: { some: { id: teamId === '0' ? undefined : teamId } } },
{ isSystemWide: true }
]
},
include: { githubApp: true, gitlabApp: true }
});
if (!source) {
throw { status: 404, message: 'Source not found.' };
}
if (source?.githubApp?.clientSecret)
source.githubApp.clientSecret = decrypt(source.githubApp.clientSecret);
if (source?.githubApp?.webhookSecret)
source.githubApp.webhookSecret = decrypt(source.githubApp.webhookSecret);
if (source?.githubApp?.privateKey) source.githubApp.privateKey = decrypt(source.githubApp.privateKey);
if (source?.gitlabApp?.appSecret) source.gitlabApp.appSecret = decrypt(source.gitlabApp.appSecret);
if (source?.githubApp?.clientSecret)
source.githubApp.clientSecret = decrypt(source.githubApp.clientSecret);
if (source?.githubApp?.webhookSecret)
source.githubApp.webhookSecret = decrypt(source.githubApp.webhookSecret);
if (source?.githubApp?.privateKey)
source.githubApp.privateKey = decrypt(source.githubApp.privateKey);
if (source?.gitlabApp?.appSecret)
source.gitlabApp.appSecret = decrypt(source.gitlabApp.appSecret);
return {
source,
settings
};
} catch ({ status, message }) {
return errorHandler({ status, message })
}
return {
source,
settings
};
} catch ({ status, message }) {
return errorHandler({ status, message });
}
}
export async function deleteSource(request) {
try {
const { id } = request.params
const source = await prisma.gitSource.delete({
where: { id },
include: { githubApp: true, gitlabApp: true }
});
if (source.githubAppId) {
await prisma.githubApp.delete({ where: { id: source.githubAppId } });
}
if (source.gitlabAppId) {
await prisma.gitlabApp.delete({ where: { id: source.gitlabAppId } });
}
return {}
} catch ({ status, message }) {
return errorHandler({ status, message })
}
try {
const { id } = request.params;
const gitAppFound = await prisma.application.findFirst({ where: { gitSourceId: id } });
if (gitAppFound) {
throw {
status: 400,
message: 'This source is used by an application. Please remove the application first.'
};
}
const source = await prisma.gitSource.delete({
where: { id },
include: { githubApp: true, gitlabApp: true }
});
if (source.githubAppId) {
await prisma.githubApp.delete({ where: { id: source.githubAppId } });
}
if (source.gitlabAppId) {
await prisma.gitlabApp.delete({ where: { id: source.gitlabAppId } });
}
return {};
} catch ({ status, message }) {
return errorHandler({ status, message });
}
}
export async function saveGitHubSource(request: FastifyRequest<SaveGitHubSource>) {
try {
const { teamId } = request.user
try {
const { teamId } = request.user;
const { id } = request.params
let { name, htmlUrl, apiUrl, organization, customPort, isSystemWide } = request.body
const { id } = request.params;
let { name, htmlUrl, apiUrl, organization, customPort, isSystemWide } = request.body;
if (customPort) customPort = Number(customPort)
if (id === 'new') {
const newId = cuid()
await prisma.gitSource.create({
data: {
id: newId,
name,
htmlUrl,
apiUrl,
organization,
customPort,
isSystemWide,
type: 'github',
teams: { connect: { id: teamId } }
}
});
return {
id: newId
}
}
throw { status: 500, message: 'Wrong request.' }
} catch ({ status, message }) {
return errorHandler({ status, message })
}
if (customPort) customPort = Number(customPort);
if (id === 'new') {
const newId = cuid();
await prisma.gitSource.create({
data: {
id: newId,
name,
htmlUrl,
apiUrl,
organization,
customPort,
isSystemWide,
type: 'github',
teams: { connect: { id: teamId } }
}
});
return {
id: newId
};
}
throw { status: 500, message: 'Wrong request.' };
} catch ({ status, message }) {
return errorHandler({ status, message });
}
}
export async function saveGitLabSource(request: FastifyRequest<SaveGitLabSource>) {
try {
const { id } = request.params
const { teamId } = request.user
let { type, name, htmlUrl, apiUrl, oauthId, appId, appSecret, groupName, customPort, customUser } =
request.body
try {
const { id } = request.params;
const { teamId } = request.user;
let {
type,
name,
htmlUrl,
apiUrl,
oauthId,
appId,
appSecret,
groupName,
customPort,
customUser
} = request.body;
if (oauthId) oauthId = Number(oauthId);
if (customPort) customPort = Number(customPort)
const encryptedAppSecret = encrypt(appSecret);
if (oauthId) oauthId = Number(oauthId);
if (customPort) customPort = Number(customPort);
const encryptedAppSecret = encrypt(appSecret);
if (id === 'new') {
const newId = cuid()
await prisma.gitSource.create({ data: { id: newId, type, apiUrl, htmlUrl, name, customPort, customUser, teams: { connect: { id: teamId } } } });
await prisma.gitlabApp.create({
data: {
teams: { connect: { id: teamId } },
appId,
oauthId,
groupName,
appSecret: encryptedAppSecret,
gitSource: { connect: { id: newId } }
}
});
return {
status: 201,
id: newId
}
} else {
await prisma.gitSource.update({ where: { id }, data: { type, apiUrl, htmlUrl, name, customPort, customUser } });
await prisma.gitlabApp.update({
where: { id },
data: {
appId,
oauthId,
groupName,
appSecret: encryptedAppSecret,
}
});
}
return { status: 201 };
} catch ({ status, message }) {
return errorHandler({ status, message })
}
if (id === 'new') {
const newId = cuid();
await prisma.gitSource.create({
data: {
id: newId,
type,
apiUrl,
htmlUrl,
name,
customPort,
customUser,
teams: { connect: { id: teamId } }
}
});
await prisma.gitlabApp.create({
data: {
teams: { connect: { id: teamId } },
appId,
oauthId,
groupName,
appSecret: encryptedAppSecret,
gitSource: { connect: { id: newId } }
}
});
return {
status: 201,
id: newId
};
} else {
await prisma.gitSource.update({
where: { id },
data: { type, apiUrl, htmlUrl, name, customPort, customUser }
});
await prisma.gitlabApp.update({
where: { id },
data: {
appId,
oauthId,
groupName,
appSecret: encryptedAppSecret
}
});
}
return { status: 201 };
} catch ({ status, message }) {
return errorHandler({ status, message });
}
}
export async function checkGitLabOAuthID(request: FastifyRequest<CheckGitLabOAuthId>) {
try {
const { oauthId } = request.body
const found = await prisma.gitlabApp.findFirst({ where: { oauthId: Number(oauthId) } });
if (found) {
throw { status: 500, message: 'OAuthID already configured in Coolify.' }
}
return {}
} catch ({ status, message }) {
return errorHandler({ status, message })
}
}
try {
const { oauthId } = request.body;
const found = await prisma.gitlabApp.findFirst({ where: { oauthId: Number(oauthId) } });
if (found) {
throw { status: 500, message: 'OAuthID already configured in Coolify.' };
}
return {};
} catch ({ status, message }) {
return errorHandler({ status, message });
}
}

View File

@@ -543,6 +543,9 @@ export async function proxyConfiguration(request: FastifyRequest<OnlyId>, remote
const template: any = await parseAndFindServiceTemplates(service, null, true);
const { proxy } = template.services[oneService] || found.services[oneService];
for (let configuration of proxy) {
if (configuration.hostPort) {
continue;
}
if (configuration.domain) {
const setting = serviceSetting.find(
(a) => a.variableName === configuration.domain

File diff suppressed because one or more lines are too long

View File

@@ -51,16 +51,22 @@
async function loadLogs() {
if (logsLoading) return;
try {
const newLogs: any = await get(
`/applications/${id}/logs/${selectedService}?since=${lastLog?.split(' ')[0] || 0}`
);
const since = lastLog?.split(' ')[0] || 0;
const newLogs: any = await get(`/applications/${id}/logs/${selectedService}?since=${since}`);
if (newLogs.noContainer) {
noContainer = true;
} else {
noContainer = false;
}
if (newLogs?.logs && newLogs.logs[newLogs.logs.length - 1] !== logs[logs.length - 1]) {
logs = logs.concat(newLogs.logs);
if (since === 0) {
logs = logs.concat(newLogs.logs);
} else {
const newParsedLogs = newLogs.logs.filter((log: any) => {
return log !== logs[logs.length - 1];
});
logs = logs.concat(newParsedLogs);
}
lastLog = newLogs.logs[newLogs.logs.length - 1];
}
} catch (error) {
@@ -135,7 +141,7 @@
{:else}
<div class="relative w-full">
<div class="flex justify-start sticky space-x-2 pb-2">
<button on:click={followBuild} class="btn btn-sm " class:bg-coollabs={followingLogs}>
<button on:click={followBuild} class="btn btn-sm" class:bg-coollabs={followingLogs}>
<svg
xmlns="http://www.w3.org/2000/svg"
class="w-6 h-6 mr-2"

View File

@@ -43,6 +43,7 @@
return true;
}
});
let customVersion: string;
$: isDisabled =
!$appSession.isAdmin ||
$status.service.overallStatus === 'degraded' ||
@@ -111,6 +112,7 @@
setLocation(service);
forceSave = false;
$isDeploymentEnabled = checkIfDeploymentEnabledServices(service);
customVersion = null;
return addToast({
message: 'Configuration saved.',
type: 'success'
@@ -196,26 +198,12 @@
async function selectTag(event: any) {
service.version = event.detail.value;
}
async function setCustomVersion() {
service.version = customVersion;
}
onMount(async () => {
if (browser && window.location.hostname === 'demo.coolify.io' && !service.fqdn) {
service.fqdn = `http://${cuid()}.demo.coolify.io`;
// if (service.type === 'wordpress') {
// service.wordpress.mysqlDatabase = 'db';
// }
// if (service.type === 'plausibleanalytics') {
// service.plausibleAnalytics.email = 'noreply@demo.com';
// service.plausibleAnalytics.username = 'admin';
// }
// if (service.type === 'minio') {
// service.minio.apiFqdn = `http://${cuid()}.demo.coolify.io`;
// }
// if (service.type === 'ghost') {
// service.ghost.mariadbDatabase = 'db';
// }
// if (service.type === 'fider') {
// service.fider.emailNoreply = 'noreply@demo.com';
// }
// await handleSubmit();
}
});
</script>
@@ -224,7 +212,7 @@
<form id="saveForm" on:submit|preventDefault={handleSubmit}>
<div class="mx-auto w-full">
<div class="flex flex-row border-b border-coolgray-500 mb-6 space-x-2">
<div class="title font-bold pb-3 ">General</div>
<div class="title font-bold pb-3">General</div>
{#if $appSession.isAdmin}
<button
type="submit"
@@ -289,6 +277,7 @@
</div>
<div class="grid grid-cols-2 items-center">
<label for="version">Version / Tag</label>
<div class="flex gap-2">
{#if tags.tags?.length > 0}
<div class="custom-select-wrapper w-full">
<Select
@@ -303,9 +292,11 @@
isClearable={false}
/>
</div>
{:else}
{:else}
<input class="w-full border-red-500" disabled placeholder="Error getting tags..." />
{/if}
{/if}
<input class="w-full" placeholder="Custom version" on:change={setCustomVersion} bind:value={customVersion} />
</div>
</div>
<div class="grid grid-cols-2 items-center">

View File

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

2592
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff