Merge pull request #341 from coollabsio/v2.4.6

v2.4.6
This commit is contained in:
Andras Bacsai 2022-04-13 08:40:10 +02:00 committed by GitHub
commit 6926975e40
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 277 additions and 98 deletions

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": "2.4.5", "version": "2.4.6",
"license": "AGPL-3.0", "license": "AGPL-3.0",
"scripts": { "scripts": {
"dev": "docker-compose -f docker-compose-dev.yaml up -d && cross-env NODE_ENV=development & svelte-kit dev", "dev": "docker-compose -f docker-compose-dev.yaml up -d && cross-env NODE_ENV=development & svelte-kit dev",

View File

@ -93,11 +93,16 @@ export const getUserDetails = async (
}> => { }> => {
const teamId = getTeam(event); const teamId = getTeam(event);
const userId = event?.locals?.session?.data?.userId || null; const userId = event?.locals?.session?.data?.userId || null;
const { permission = 'read' } = await db.prisma.permission.findFirst({ let permission = 'read';
if (teamId && userId) {
const data = await db.prisma.permission.findFirst({
where: { teamId, userId }, where: { teamId, userId },
select: { permission: true }, select: { permission: true },
rejectOnNotFound: true rejectOnNotFound: true
}); });
if (data.permission) permission = data.permission;
}
const payload = { const payload = {
teamId, teamId,
userId, userId,

View File

@ -219,7 +219,6 @@ export function generateDatabaseConfiguration(database: Database & { settings: D
return { return {
privatePort: 5432, privatePort: 5432,
environmentVariables: { environmentVariables: {
POSTGRESQL_POSTGRES_PASSWORD: rootUserPassword,
POSTGRESQL_PASSWORD: dbUserPassword, POSTGRESQL_PASSWORD: dbUserPassword,
POSTGRESQL_USERNAME: dbUser, POSTGRESQL_USERNAME: dbUser,
POSTGRESQL_DATABASE: defaultDatabase POSTGRESQL_DATABASE: defaultDatabase

View File

@ -215,7 +215,8 @@ export async function configureHAProxy(): Promise<void> {
plausibleAnalytics: true, plausibleAnalytics: true,
vscodeserver: true, vscodeserver: true,
wordpress: true, wordpress: true,
ghost: true ghost: true,
meiliSearch: true
} }
}); });

View File

@ -6,6 +6,7 @@ import cuid from 'cuid';
import fs from 'fs/promises'; import fs from 'fs/promises';
import getPort, { portNumbers } from 'get-port'; import getPort, { portNumbers } from 'get-port';
import { supportedServiceTypesAndVersions } from '$lib/components/common'; import { supportedServiceTypesAndVersions } from '$lib/components/common';
import { promises as dns } from 'dns';
export async function letsEncrypt(domain: string, id?: string, isCoolify = false): Promise<void> { export async function letsEncrypt(domain: string, id?: string, isCoolify = false): Promise<void> {
try { try {
@ -148,7 +149,8 @@ export async function generateSSLCerts(): Promise<void> {
plausibleAnalytics: true, plausibleAnalytics: true,
vscodeserver: true, vscodeserver: true,
wordpress: true, wordpress: true,
ghost: true ghost: true,
meiliSearch: true
}, },
orderBy: { createdAt: 'desc' } orderBy: { createdAt: 'desc' }
}); });
@ -198,6 +200,15 @@ export async function generateSSLCerts(): Promise<void> {
file.endsWith('.pem') && certificates.push(file.replace(/\.pem$/, '')); file.endsWith('.pem') && certificates.push(file.replace(/\.pem$/, ''));
} }
} }
const resolver = new dns.Resolver({ timeout: 2000 });
resolver.setServers(['8.8.8.8', '1.1.1.1']);
let ipv4, ipv6;
try {
ipv4 = await (await asyncExecShell(`curl -4s https://ifconfig.io`)).stdout;
} catch (error) {}
try {
ipv6 = await (await asyncExecShell(`curl -6s https://ifconfig.io`)).stdout;
} catch (error) {}
for (const ssl of ssls) { for (const ssl of ssls) {
if (!dev) { if (!dev) {
if ( if (
@ -206,8 +217,27 @@ export async function generateSSLCerts(): Promise<void> {
) { ) {
console.log(`Certificate for ${ssl.domain} already exists`); console.log(`Certificate for ${ssl.domain} already exists`);
} else { } else {
console.log('Generating SSL for', ssl.domain); // Checking DNS entry before generating certificate
await letsEncrypt(ssl.domain, ssl.id, ssl.isCoolify); if (ipv4 || ipv6) {
let domains4 = [];
let domains6 = [];
try {
domains4 = await resolver.resolve4(ssl.domain);
} catch (error) {}
try {
domains6 = await resolver.resolve6(ssl.domain);
} catch (error) {}
if (domains4.length > 0 || domains6.length > 0) {
if (
(ipv4 && domains4.includes(ipv4.replace('\n', ''))) ||
(ipv6 && domains6.includes(ipv6.replace('\n', '')))
) {
console.log('Generating SSL for', ssl.domain, '.');
return await letsEncrypt(ssl.domain, ssl.id, ssl.isCoolify);
}
}
}
console.log('DNS settings is incorrect for', ssl.domain, 'skipping.');
} }
} else { } else {
if ( if (
@ -216,7 +246,27 @@ export async function generateSSLCerts(): Promise<void> {
) { ) {
console.log(`Certificate for ${ssl.domain} already exists`); console.log(`Certificate for ${ssl.domain} already exists`);
} else { } else {
console.log('Generating SSL for', ssl.domain); // Checking DNS entry before generating certificate
if (ipv4 || ipv6) {
let domains4 = [];
let domains6 = [];
try {
domains4 = await resolver.resolve4(ssl.domain);
} catch (error) {}
try {
domains6 = await resolver.resolve6(ssl.domain);
} catch (error) {}
if (domains4.length > 0 || domains6.length > 0) {
if (
(ipv4 && domains4.includes(ipv4.replace('\n', ''))) ||
(ipv6 && domains6.includes(ipv6.replace('\n', '')))
) {
console.log('Generating SSL for', ssl.domain, '.');
return;
}
}
}
console.log('DNS settings is incorrect for', ssl.domain, 'skipping.');
} }
} }
} }

View File

@ -285,7 +285,15 @@ export default async function (job: Job<BuilderJob, void, string>): Promise<void
networks: [docker.network], networks: [docker.network],
labels, labels,
depends_on: [], depends_on: [],
restart: 'always' restart: 'always',
deploy: {
restart_policy: {
condition: 'on-failure',
delay: '5s',
max_attempts: 3,
window: '120s'
}
}
} }
}, },
networks: { networks: {

View File

@ -14,56 +14,23 @@ export default async function (): Promise<void> {
await asyncExecShell(`DOCKER_HOST=${host} docker rmi -f ${images}`); await asyncExecShell(`DOCKER_HOST=${host} docker rmi -f ${images}`);
} }
} catch (error) { } catch (error) {
console.log(error); //console.log(error);
} }
try { try {
await asyncExecShell(`DOCKER_HOST=${host} docker container prune -f`); await asyncExecShell(`DOCKER_HOST=${host} docker container prune -f`);
} catch (error) { } catch (error) {
console.log(error); //console.log(error);
} }
try { try {
await asyncExecShell(`DOCKER_HOST=${host} docker image prune -f --filter "until=2h"`); await asyncExecShell(`DOCKER_HOST=${host} docker image prune -f --filter "until=2h"`);
} catch (error) { } catch (error) {
console.log(error); //console.log(error);
}
// Cleanup old images older than a day
try {
await asyncExecShell(`DOCKER_HOST=${host} docker image prune --filter "until=24h" -a -f`);
} catch (error) {
//console.log(error);
} }
// Tagging images with labels
// try {
// const images = [
// `coollabsio/${defaultProxyImageTcp}`,
// `coollabsio/${defaultProxyImageHttp}`,
// 'certbot/certbot:latest',
// 'node:16.14.0-alpine',
// 'alpine:latest',
// 'nginx:stable-alpine',
// 'node:lts',
// 'php:apache',
// 'rust:latest'
// ];
// for (const image of images) {
// try {
// await asyncExecShell(`DOCKER_HOST=${host} docker image inspect ${image}`);
// } catch (error) {
// await asyncExecShell(
// `DOCKER_HOST=${host} docker pull ${image} && echo "FROM ${image}" | docker build --label coolify.image="true" -t "${image}" -`
// );
// }
// }
// } catch (error) {}
// if (!dev) {
// // Cleanup images that are not managed by coolify
// try {
// await asyncExecShell(
// `DOCKER_HOST=${host} docker image prune --filter 'label!=coolify.image=true' -a -f`
// );
// } catch (error) {
// console.log(error);
// }
// // Cleanup old images >3 days
// try {
// await asyncExecShell(`DOCKER_HOST=${host} docker image prune --filter "until=72h" -a -f`);
// } catch (error) {
// console.log(error);
// }
// }
} }
} }

View File

@ -23,6 +23,14 @@ export type ComposeFileService = {
dockerfile: string; dockerfile: string;
args?: Record<string, unknown>; args?: Record<string, unknown>;
}; };
deploy?: {
restart_policy?: {
condition?: string;
delay?: string;
max_attempts?: number;
window?: string;
};
};
}; };
export type ComposerFileVersion = export type ComposerFileVersion =

View File

@ -36,8 +36,15 @@
}); });
} }
async function loadBranchesByPage(page = 0) {
return await get(`${apiUrl}/repos/${selected.repository}/branches?per_page=100&page=${page}`, {
Authorization: `token ${$gitTokens.githubToken}`
});
}
let reposSelectOptions; let reposSelectOptions;
let branchSelectOptions; let branchSelectOptions;
async function loadRepositories() { async function loadRepositories() {
let page = 1; let page = 1;
let reposCount = 0; let reposCount = 0;
@ -58,24 +65,28 @@
})); }));
} }
async function loadBranches(event) { async function loadBranches(event) {
branches = [];
selected.repository = event.detail.value; selected.repository = event.detail.value;
loading.branches = true;
selected.branch = undefined;
selected.projectId = repositories.find((repo) => repo.full_name === selected.repository).id; selected.projectId = repositories.find((repo) => repo.full_name === selected.repository).id;
try { let page = 1;
branches = await get(`${apiUrl}/repos/${selected.repository}/branches`, { let branchCount = 0;
Authorization: `token ${$gitTokens.githubToken}` loading.branches = true;
}); const loadedBranches = await loadBranchesByPage();
branches = branches.concat(loadedBranches);
branchCount = branches.length;
if (branchCount === 100) {
while (branchCount === 100) {
page = page + 1;
const nextBranches = await loadBranchesByPage(page);
branches = branches.concat(nextBranches);
branchCount = nextBranches.length;
}
}
loading.branches = false;
branchSelectOptions = branches.map((branch) => ({ branchSelectOptions = branches.map((branch) => ({
value: branch.name, value: branch.name,
label: branch.name label: branch.name
})); }));
return;
} catch ({ error }) {
return errorNotification(error);
} finally {
loading.branches = false;
}
} }
async function isBranchAlreadyUsed(event) { async function isBranchAlreadyUsed(event) {
selected.branch = event.detail.value; selected.branch = event.detail.value;
@ -166,30 +177,36 @@
{:else} {:else}
<form on:submit|preventDefault={handleSubmit} class="flex flex-col justify-center text-center"> <form on:submit|preventDefault={handleSubmit} class="flex flex-col justify-center text-center">
<div class="flex-col space-y-3 md:space-y-0 space-x-1"> <div class="flex-col space-y-3 md:space-y-0 space-x-1">
<div class="flex gap-4"> <div class="flex-col md:flex gap-4">
<div class="custom-select-wrapper"> <div class="custom-select-wrapper">
<Select <Select
placeholder={loading.repositories placeholder={loading.repositories
? 'Loading repositories ...' ? 'Loading repositories...'
: 'Please select a repository'} : 'Please select a repository'}
id="repository" id="repository"
showIndicator={true}
isWaiting={loading.repositories}
on:select={loadBranches} on:select={loadBranches}
items={reposSelectOptions} items={reposSelectOptions}
isDisabled={loading.repositories} isDisabled={loading.repositories}
isClearable={false}
/> />
</div> </div>
<input class="hidden" bind:value={selected.projectId} name="projectId" /> <input class="hidden" bind:value={selected.projectId} name="projectId" />
<div class="custom-select-wrapper"> <div class="custom-select-wrapper">
<Select <Select
placeholder={loading.branches placeholder={loading.branches
? 'Loading branches ...' ? 'Loading branches...'
: !selected.repository : !selected.repository
? 'Please select a repository first' ? 'Please select a repository first'
: 'Please select a branch'} : 'Please select a branch'}
id="repository" isWaiting={loading.branches}
showIndicator={selected.repository}
id="branches"
on:select={isBranchAlreadyUsed} on:select={isBranchAlreadyUsed}
items={branchSelectOptions} items={branchSelectOptions}
isDisabled={loading.branches || !selected.repository} isDisabled={loading.branches || !selected.repository}
isClearable={false}
/> />
</div> </div>
</div> </div>
@ -202,13 +219,6 @@
class:bg-orange-600={showSave} class:bg-orange-600={showSave}
class:hover:bg-orange-500={showSave}>Save</button class:hover:bg-orange-500={showSave}>Save</button
> >
<!-- <button class="w-40"
><a
class="no-underline"
href="{apiUrl}/apps/{application.gitSource.githubApp.name}/installations/new"
>Modify Repositories</a
></button
> -->
</div> </div>
</form> </form>
{/if} {/if}

View File

@ -45,7 +45,15 @@ export const post: RequestHandler = async (event) => {
volumes: [volume], volumes: [volume],
ulimits, ulimits,
labels, labels,
restart: 'always' restart: 'always',
deploy: {
restart_policy: {
condition: 'on-failure',
delay: '5s',
max_attempts: 3,
window: '120s'
}
}
} }
}, },
networks: { networks: {

View File

@ -90,7 +90,15 @@ export const post: RequestHandler = async (event) => {
environment: config.ghost.environmentVariables, environment: config.ghost.environmentVariables,
restart: 'always', restart: 'always',
labels: makeLabelForServices('ghost'), labels: makeLabelForServices('ghost'),
depends_on: [`${id}-mariadb`] depends_on: [`${id}-mariadb`],
deploy: {
restart_policy: {
condition: 'on-failure',
delay: '5s',
max_attempts: 3,
window: '120s'
}
}
}, },
[`${id}-mariadb`]: { [`${id}-mariadb`]: {
container_name: `${id}-mariadb`, container_name: `${id}-mariadb`,
@ -98,7 +106,15 @@ export const post: RequestHandler = async (event) => {
networks: [network], networks: [network],
volumes: [config.mariadb.volume], volumes: [config.mariadb.volume],
environment: config.mariadb.environmentVariables, environment: config.mariadb.environmentVariables,
restart: 'always' restart: 'always',
deploy: {
restart_policy: {
condition: 'on-failure',
delay: '5s',
max_attempts: 3,
window: '120s'
}
}
} }
}, },
networks: { networks: {

View File

@ -43,7 +43,15 @@ export const post: RequestHandler = async (event) => {
environment: config.environmentVariables, environment: config.environmentVariables,
restart: 'always', restart: 'always',
volumes: [config.volume], volumes: [config.volume],
labels: makeLabelForServices('languagetool') labels: makeLabelForServices('languagetool'),
deploy: {
restart_policy: {
condition: 'on-failure',
delay: '5s',
max_attempts: 3,
window: '120s'
}
}
} }
}, },
networks: { networks: {

View File

@ -48,7 +48,15 @@ export const post: RequestHandler = async (event) => {
environment: config.environmentVariables, environment: config.environmentVariables,
restart: 'always', restart: 'always',
volumes: [config.volume], volumes: [config.volume],
labels: makeLabelForServices('meilisearch') labels: makeLabelForServices('meilisearch'),
deploy: {
restart_policy: {
condition: 'on-failure',
delay: '5s',
max_attempts: 3,
window: '120s'
}
}
} }
}, },
networks: { networks: {

View File

@ -67,7 +67,15 @@ export const post: RequestHandler = async (event) => {
networks: [network], networks: [network],
volumes: [config.volume], volumes: [config.volume],
restart: 'always', restart: 'always',
labels: makeLabelForServices('minio') labels: makeLabelForServices('minio'),
deploy: {
restart_policy: {
condition: 'on-failure',
delay: '5s',
max_attempts: 3,
window: '120s'
}
}
} }
}, },
networks: { networks: {

View File

@ -44,7 +44,15 @@ export const post: RequestHandler = async (event) => {
volumes: [config.volume], volumes: [config.volume],
environment: config.environmentVariables, environment: config.environmentVariables,
restart: 'always', restart: 'always',
labels: makeLabelForServices('n8n') labels: makeLabelForServices('n8n'),
deploy: {
restart_policy: {
condition: 'on-failure',
delay: '5s',
max_attempts: 3,
window: '120s'
}
}
} }
}, },
networks: { networks: {

View File

@ -40,7 +40,15 @@ export const post: RequestHandler = async (event) => {
networks: [network], networks: [network],
environment: config.environmentVariables, environment: config.environmentVariables,
restart: 'always', restart: 'always',
labels: makeLabelForServices('nocodb') labels: makeLabelForServices('nocodb'),
deploy: {
restart_policy: {
condition: 'on-failure',
delay: '5s',
max_attempts: 3,
window: '120s'
}
}
} }
}, },
networks: { networks: {

View File

@ -133,7 +133,15 @@ COPY ./init-db.sh /docker-entrypoint-initdb.d/init-db.sh`;
environment: config.plausibleAnalytics.environmentVariables, environment: config.plausibleAnalytics.environmentVariables,
restart: 'always', restart: 'always',
depends_on: [`${id}-postgresql`, `${id}-clickhouse`], depends_on: [`${id}-postgresql`, `${id}-clickhouse`],
labels: makeLabelForServices('plausibleAnalytics') labels: makeLabelForServices('plausibleAnalytics'),
deploy: {
restart_policy: {
condition: 'on-failure',
delay: '10s',
max_attempts: 5,
window: '120s'
}
}
}, },
[`${id}-postgresql`]: { [`${id}-postgresql`]: {
container_name: `${id}-postgresql`, container_name: `${id}-postgresql`,
@ -141,7 +149,15 @@ COPY ./init-db.sh /docker-entrypoint-initdb.d/init-db.sh`;
networks: [network], networks: [network],
environment: config.postgresql.environmentVariables, environment: config.postgresql.environmentVariables,
volumes: [config.postgresql.volume], volumes: [config.postgresql.volume],
restart: 'always' restart: 'always',
deploy: {
restart_policy: {
condition: 'on-failure',
delay: '10s',
max_attempts: 5,
window: '120s'
}
}
}, },
[`${id}-clickhouse`]: { [`${id}-clickhouse`]: {
build: workdir, build: workdir,
@ -149,7 +165,15 @@ COPY ./init-db.sh /docker-entrypoint-initdb.d/init-db.sh`;
networks: [network], networks: [network],
environment: config.clickhouse.environmentVariables, environment: config.clickhouse.environmentVariables,
volumes: [config.clickhouse.volume], volumes: [config.clickhouse.volume],
restart: 'always' restart: 'always',
deploy: {
restart_policy: {
condition: 'on-failure',
delay: '10s',
max_attempts: 5,
window: '120s'
}
}
} }
}, },
networks: { networks: {

View File

@ -42,7 +42,15 @@ export const post: RequestHandler = async (event) => {
volumes: [config.volume], volumes: [config.volume],
environment: config.environmentVariables, environment: config.environmentVariables,
restart: 'always', restart: 'always',
labels: makeLabelForServices('uptimekuma') labels: makeLabelForServices('uptimekuma'),
deploy: {
restart_policy: {
condition: 'on-failure',
delay: '5s',
max_attempts: 3,
window: '120s'
}
}
} }
}, },
networks: { networks: {

View File

@ -43,7 +43,15 @@ export const post: RequestHandler = async (event) => {
networks: [network], networks: [network],
volumes: [config.volume], volumes: [config.volume],
restart: 'always', restart: 'always',
labels: makeLabelForServices('vaultWarden') labels: makeLabelForServices('vaultWarden'),
deploy: {
restart_policy: {
condition: 'on-failure',
delay: '5s',
max_attempts: 3,
window: '120s'
}
}
} }
}, },
networks: { networks: {

View File

@ -52,7 +52,15 @@ export const post: RequestHandler = async (event) => {
networks: [network], networks: [network],
volumes: [config.volume], volumes: [config.volume],
restart: 'always', restart: 'always',
labels: makeLabelForServices('vscodeServer') labels: makeLabelForServices('vscodeServer'),
deploy: {
restart_policy: {
condition: 'on-failure',
delay: '5s',
max_attempts: 3,
window: '120s'
}
}
} }
}, },
networks: { networks: {

View File

@ -77,7 +77,15 @@ export const post: RequestHandler = async (event) => {
networks: [network], networks: [network],
restart: 'always', restart: 'always',
depends_on: [`${id}-mysql`], depends_on: [`${id}-mysql`],
labels: makeLabelForServices('wordpress') labels: makeLabelForServices('wordpress'),
deploy: {
restart_policy: {
condition: 'on-failure',
delay: '5s',
max_attempts: 3,
window: '120s'
}
}
}, },
[`${id}-mysql`]: { [`${id}-mysql`]: {
container_name: `${id}-mysql`, container_name: `${id}-mysql`,
@ -85,7 +93,15 @@ export const post: RequestHandler = async (event) => {
volumes: [config.mysql.volume], volumes: [config.mysql.volume],
environment: config.mysql.environmentVariables, environment: config.mysql.environmentVariables,
networks: [network], networks: [network],
restart: 'always' restart: 'always',
deploy: {
restart_policy: {
condition: 'on-failure',
delay: '5s',
max_attempts: 3,
window: '120s'
}
}
} }
}, },
networks: { networks: {

View File

@ -50,7 +50,10 @@ #svelte .custom-select-wrapper .selectContainer {
} }
#svelte .listContainer { #svelte .listContainer {
@apply bg-coolgray-400 text-white scrollbar-w-2 scrollbar-thumb-coollabs scrollbar-track-coolgray-200; @apply bg-coolgray-400 text-white scrollbar-w-2 scrollbar-thumb-green-500 scrollbar-track-coolgray-200;
}
#svelte .selectedItem {
@apply pl-3;
} }
#svelte .item.hover { #svelte .item.hover {