Compare commits

...

24 Commits

Author SHA1 Message Date
Andras Bacsai
004724da55 Merge pull request #119 from coollabsio/next
Unique secrets by applications
2022-02-12 15:31:53 +01:00
Andras Bacsai
97e9b5ffe3 Merge branch 'main' into next 2022-02-12 15:31:45 +01:00
Andras Bacsai
45f920f802 fix: unique secret by application
css: redesign secret page
2022-02-12 15:23:58 +01:00
Andras Bacsai
2b31532d19 Update README.md 2022-02-11 23:04:50 +01:00
Andras Bacsai
e7a6ecf95b fix: Typo 2022-02-11 21:50:47 +01:00
Andras Bacsai
545c98cee0 Merge pull request #117 from coollabsio/next
Update README.md
2022-02-11 21:50:19 +01:00
Andras Bacsai
d29ccbfe37 Update README.md 2022-02-11 21:49:30 +01:00
Andras Bacsai
d0807862e6 Merge pull request #116 from coollabsio/next
v2.0.5
2022-02-11 21:37:01 +01:00
Andras Bacsai
b92616dc14 chore: Version 2022-02-11 21:29:35 +01:00
Andras Bacsai
a1a436300d fix: Check sentry 2022-02-11 21:27:28 +01:00
Andras Bacsai
16a5aeb1ba chore: Version 2022-02-11 21:23:16 +01:00
Andras Bacsai
872095ff7a fix: Local static assets 2022-02-11 21:22:41 +01:00
Andras Bacsai
d88f2ea4c3 fix: More error handling in proxy configuration + cleanups 2022-02-11 21:14:47 +01:00
Andras Bacsai
02e0385ab8 Preserve build image as well 2022-02-11 21:07:31 +01:00
Andras Bacsai
c9751d4cd9 More images to tag 2022-02-11 21:06:48 +01:00
Andras Bacsai
162b637992 fix: Cleanup images 2022-02-11 21:04:48 +01:00
Andras Bacsai
a10ddd4063 fix: Delete all build files 2022-02-11 20:28:56 +01:00
Andras Bacsai
f46ccc63a7 Update staging release (final, I promise) 2022-02-11 15:46:17 +01:00
Andras Bacsai
fc04a45744 Update staging script (again) 2022-02-11 15:35:01 +01:00
Andras Bacsai
90c2b59a51 Update staging script 2022-02-11 15:34:45 +01:00
Andras Bacsai
d6bee99c1b update releast script 2022-02-11 15:31:53 +01:00
Andras Bacsai
0871d47568 feat: VaultWarden service 2022-02-11 15:31:25 +01:00
Andras Bacsai
5c646c1898 fix: Haproxy check should not throw error 2022-02-11 15:11:10 +01:00
Andras Bacsai
8974de165f fix: PreventDefault on a button, thats all 2022-02-11 13:43:45 +01:00
46 changed files with 553 additions and 194 deletions

View File

@@ -8,6 +8,8 @@ RUN yarn build
FROM node:16.14.0-alpine
WORKDIR /app
LABEL coolify.managed true
RUN apk add --no-cache git openssh-client curl jq cmake sqlite
RUN curl -f https://get.pnpm.io/v6.16.js | node - add --global pnpm@6

View File

@@ -2,6 +2,66 @@
An open-source & self-hostable Heroku / Netlify alternative.
## Installation
Installation is automated with the following command:
```bash
/bin/bash -c "$(curl -fsSL https://get.coollabs.io/coolify/install.sh)"
```
## Migration from v1
A fresh installation is necessary. v2 is not compatible with v1.
## Features
### Git Sources
You can use the official ones or your self hosted version!
- Github
- GitLab
- Bitbucket (WIP)
### Destinations
- Local Docker Engine
- Remote Docker Engine (WIP)
- Kubernetes (WIP)
### Applications
- Static sites
- NodeJS
- VueJS
- NuxtJS
- NextJS
- React/Preact
- NextJS
- Gatsby
- Svelte
- PHP
- Rust
- Dockerfile (you can provide it)
### Databases
- MongoDB
- MySQL
- PostgreSQL
- CouchDB
- Redis
### One-click services
- [WordPress](https://wordpress.org)
- [Plausible Analytics](https://plausible.io)
- [NocoDB](https://nocodb.com)
- [VSCode Server](https://github.com/cdr/code-server)
- [MinIO](https://min.io)
- [VaultWarden](https://github.com/dani-garcia/vaultwarden)
## Support
- Twitter: [@andrasbacsai](https://twitter.com/andrasbacsai)
@@ -13,6 +73,8 @@ An open-source & self-hostable Heroku / Netlify alternative.
[See the Roadmap here](https://github.com/coollabsio/coolify/projects/1)
(Will be updated soon!)
## License
This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Please see the [LICENSE](/LICENSE) file in our repository for the full text.

View File

@@ -1,12 +1,12 @@
{
"name": "coolify",
"description": "An open-source & self-hostable Heroku / Netlify alternative.",
"version": "2.0.4",
"version": "2.0.6",
"license": "AGPL-3.0",
"scripts": {
"dev": "docker-compose -f docker-compose-dev.yaml up -d && NODE_ENV=development svelte-kit dev --host 0.0.0.0",
"dev:stop": "docker-compose -f docker-compose-dev.yaml down",
"dev:logs": "docker-compose -f docker-compose-dev.yaml logs -f --tail 10",
"dev": "docker compose -f docker-compose-dev.yaml up -d && NODE_ENV=development svelte-kit dev --host 0.0.0.0",
"dev:stop": "docker compose -f docker-compose-dev.yaml down",
"dev:logs": "docker compose -f docker-compose-dev.yaml logs -f --tail 10",
"studio": "npx prisma studio",
"start": "npx prisma migrate deploy && npx prisma generate && npx prisma db seed && node index.js",
"build": "svelte-kit build",
@@ -16,8 +16,7 @@
"db:generate": "prisma generate",
"db:push": "prisma db push && prisma generate",
"db:seed": "prisma db seed",
"stagrelease": "cross-var docker build -t coollabsio/coolify:$npm_package_version . && docker push coollabsio/coolify:$npm_package_version",
"prerelease": "cross-var docker build -t coollabsio/coolify:$npm_package_version -t coollabsio/coolify:latest .",
"release:staging": "cross-var docker build -t coollabsio/coolify:$npm_package_version . && docker push coollabsio/coolify:$npm_package_version",
"release:coolify": "cross-var yarn prerelease && docker push coollabsio/coolify:$npm_package_version && docker image push coollabsio/coolify:$npm_package_version && docker push coollabsio/coolify:latest",
"release:haproxy": "docker build -f haproxy.Dockerfile -t coollabsio/coolify-haproxy-alpine:1.0.0 -t coollabsio/coolify-haproxy-alpine:latest . && docker image push --all-tags coollabsio/coolify-haproxy-alpine",
"release:haproxy:tcp": "docker build -f haproxy-tcp.Dockerfile -t coollabsio/coolify-haproxy-tcp-alpine:1.0.0 -t coollabsio/coolify-haproxy-tcp-alpine:latest . && docker image push --all-tags coollabsio/coolify-haproxy-tcp-alpine",

View File

@@ -0,0 +1,11 @@
/*
Warnings:
- A unique constraint covering the columns `[name,applicationId]` on the table `Secret` will be added. If there are existing duplicate values, this will fail.
*/
-- DropIndex
DROP INDEX "Secret_name_key";
-- CreateIndex
CREATE UNIQUE INDEX "Secret_name_applicationId_key" ON "Secret"("name", "applicationId");

View File

@@ -105,13 +105,15 @@ model ApplicationSettings {
model Secret {
id String @id @default(cuid())
name String @unique
name String
value String
isBuildSecret Boolean @default(false)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
application Application @relation(fields: [applicationId], references: [id])
applicationId String
@@unique([name, applicationId])
}
model BuildLog {

View File

@@ -4,9 +4,6 @@
<meta charset="utf-8" />
<link rel="icon" href="/favicon.png" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="dns-prefetch" href="https://cdn.coollabs.io/" />
<link rel="preconnect" href="https://cdn.coollabs.io/" crossorigin="" />
<link rel="stylesheet" href="https://cdn.coollabs.io/fonts/poppins/poppins.css" />
<title>Coolify</title>
%svelte.head%
</head>

View File

@@ -5,5 +5,5 @@
<img
alt="minio logo"
class={isAbsolute ? 'w-7 absolute top-0 left-0 -m-3 -mt-5' : 'w-4 mx-auto'}
src="https://cdn.coollabs.io/assets/coolify/services/minio/MINIO_Bird.png"
src="/minio.png"
/>

View File

@@ -5,5 +5,5 @@
<img
alt="nocodb logo"
class={isAbsolute ? 'w-10 absolute top-0 left-0 -m-5' : 'w-8 mx-auto'}
src="https://cdn.coollabs.io/assets/coolify/services/nocodb/nocodb.png"
src="/nocodb.png"
/>

View File

@@ -5,5 +5,5 @@
<img
alt="plausible logo"
class={isAbsolute ? 'w-10 absolute top-0 left-0 -m-5' : 'w-6 mx-auto'}
src="https://cdn.coollabs.io/assets/coolify/services/plausible/logo_sm.png"
src="/plausible.png"
/>

View File

@@ -0,0 +1,35 @@
<script lang="ts">
export let isAbsolute = false;
</script>
<svg
class={isAbsolute ? 'w-10 absolute top-0 left-0 -m-5' : 'w-8 mx-auto'}
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
version="1.1"
id="Icon"
x="0px"
y="0px"
viewBox="0 0 1024 1024"
style="enable-background:new 0 0 1024 1024;"
xml:space="preserve"
>
<style type="text/css">
.st0 {
fill: #175ddc;
}
.st1 {
fill: #ffffff;
}
</style>
<path
id="Background"
class="st0"
d="M1024,864c0,88.4-71.6,160-160,160H160C71.6,1024,0,952.4,0,864V160C0,71.6,71.6,0,160,0h704 c88.4,0,160,71.6,160,160V864z"
/>
<path
id="Identity"
class="st1"
d="M829.8,128.6c-6.5-6.5-14.2-9.7-23-9.7H217.2c-8.9,0-16.5,3.2-23,9.7c-6.5,6.5-9.7,14.2-9.7,23 v393.1c0,29.3,5.7,58.4,17.1,87.3c11.4,28.8,25.6,54.4,42.5,76.8c16.9,22.3,37,44.1,60.4,65.3c23.4,21.2,45,38.7,64.7,52.7 c19.8,14,40.4,27.2,61.9,39.7c21.5,12.5,36.8,20.9,45.8,25.3c9,4.4,16.3,7.9,21.7,10.2c4.1,2,8.5,3.1,13.3,3.1c4.8,0,9.2-1,13.3-3.1 c5.5-2.4,12.7-5.8,21.8-10.2c9-4.4,24.3-12.9,45.8-25.3c21.5-12.5,42.1-25.7,61.9-39.7c19.8-14,41.4-31.6,64.8-52.7 c23.4-21.2,43.5-42.9,60.4-65.3c16.9-22.4,31-47.9,42.5-76.8c11.4-28.8,17.1-57.9,17.1-87.3V151.7 C839.6,142.8,836.3,135.1,829.8,128.6z M753.8,548.4c0,142.3-241.8,264.9-241.8,264.9V203.1h241.8 C753.8,203.1,753.8,406.1,753.8,548.4z"
/>
</svg>

View File

@@ -1,6 +1,6 @@
import { decrypt, encrypt } from '$lib/crypto';
import { removeProxyConfiguration } from '$lib/haproxy';
import { asyncExecShell, getEngine, removeContainer } from '$lib/common';
import { asyncExecShell, getEngine } from '$lib/common';
import { getDomain, removeDestinationDocker } from '$lib/common';
import { prisma } from './common';

View File

@@ -1,5 +1,5 @@
import { getDomain } from '$lib/common';
import { prisma, PrismaErrorHandler } from './common';
import { prisma } from './common';
export async function isBranchAlreadyUsed({ repository, branch, id }) {
const application = await prisma.application.findUnique({

View File

@@ -116,6 +116,12 @@ export const supportedServiceTypesAndVersions = [
fancyName: 'Wordpress',
baseImage: 'wordpress',
versions: ['latest', 'php8.1', 'php8.0', 'php7.4', 'php7.3']
},
{
name: 'vaultwarden',
fancyName: 'Vaultwarden',
baseImage: 'vaultwarden/server',
versions: ['latest']
}
];

View File

@@ -114,15 +114,6 @@ export async function updateDatabase({
});
}
// export async function setDatabaseSettings({ id, isPublic }) {
// try {
// await prisma.databaseSettings.update({ where: { databaseId: id }, data: { isPublic } })
// return { status: 201 }
// } catch (e) {
// throw PrismaErrorHandler(e)
// }
// }
export async function stopDatabase(database) {
let everStarted = false;
const {

View File

@@ -1,8 +1,8 @@
import { asyncExecShell, getEngine } from '$lib/common';
import { dockerInstance } from '$lib/docker';
import { defaultProxyImageHttp, defaultProxyImageTcp, startCoolifyProxy } from '$lib/haproxy';
import { startCoolifyProxy } from '$lib/haproxy';
import { getDatabaseImage } from '.';
import { prisma, PrismaErrorHandler } from './common';
import { prisma } from './common';
export async function listDestinations(teamId) {
return await prisma.destinationDocker.findMany({ where: { teams: { some: { id: teamId } } } });
@@ -37,11 +37,9 @@ export async function configureDestinationForDatabase({ id, destinationId }) {
const host = getEngine(engine);
if (type && version) {
const baseImage = getDatabaseImage(type);
asyncExecShell(`DOCKER_HOST=${host} docker pull ${baseImage}:${version}`);
asyncExecShell(`DOCKER_HOST=${host} docker pull coollabsio/${defaultProxyImageTcp}`);
asyncExecShell(`DOCKER_HOST=${host} docker pull coollabsio/${defaultProxyImageHttp}`);
asyncExecShell(`DOCKER_HOST=${host} docker pull certbot/certbot:latest`);
asyncExecShell(`DOCKER_HOST=${host} docker pull alpine:latest`);
asyncExecShell(
`DOCKER_HOST=${host} docker pull ${baseImage}:${version} && echo "FROM ${baseImage}:${version}" | docker build --label coolify.managed="true" -t "${baseImage}:${version}" -`
);
}
}
}

View File

@@ -1,5 +1,5 @@
import { decrypt, encrypt } from '$lib/crypto';
import { prisma, PrismaErrorHandler } from './common';
import { prisma } from './common';
export async function listSources(teamId) {
return await prisma.gitSource.findMany({

View File

@@ -1,5 +1,5 @@
import { decrypt, encrypt } from '$lib/crypto';
import { prisma, PrismaErrorHandler } from './common';
import { prisma } from './common';
export async function addInstallation({ gitSourceId, installation_id }) {
const source = await prisma.gitSource.findUnique({

View File

@@ -1,5 +1,5 @@
import { encrypt } from '$lib/crypto';
import { generateSshKeyPair, prisma, PrismaErrorHandler } from './common';
import { generateSshKeyPair, prisma } from './common';
export async function updateDeployKey({ id, deployKeyId }) {
const application = await prisma.application.findUnique({

View File

@@ -7,7 +7,7 @@ export async function listLogs({ buildId, last = 0 }) {
orderBy: { time: 'asc' }
});
return [...body];
} catch (e) {
throw PrismaErrorHandler(e);
} catch (error) {
return PrismaErrorHandler(error);
}
}

View File

@@ -1,5 +1,5 @@
import { encrypt } from '$lib/crypto';
import { prisma, PrismaErrorHandler } from './common';
import { prisma } from './common';
export async function listSecrets({ applicationId }) {
return await prisma.secret.findMany({

View File

@@ -1,8 +1,7 @@
import { decrypt, encrypt } from '$lib/crypto';
import { dockerInstance } from '$lib/docker';
import cuid from 'cuid';
import { generatePassword } from '.';
import { prisma, PrismaErrorHandler } from './common';
import { prisma } from './common';
export async function listServices(teamId) {
return await prisma.service.findMany({ where: { teams: { some: { id: teamId } } } });
@@ -99,6 +98,13 @@ export async function configureServiceType({ id, type }) {
wordpress: { create: { mysqlPassword, mysqlRootUserPassword, mysqlRootUser, mysqlUser } }
}
});
} else if (type === 'vaultwarden') {
await prisma.service.update({
where: { id },
data: {
type
}
});
}
}
export async function setService({ id, version }) {
@@ -115,6 +121,9 @@ export async function updatePlausibleAnalyticsService({ id, fqdn, email, usernam
export async function updateNocoDbOrMinioService({ id, fqdn, name }) {
return await prisma.service.update({ where: { id }, data: { fqdn, name } });
}
export async function updateVaultWardenService({ id, fqdn, name }) {
return await prisma.service.update({ where: { id }, data: { fqdn, name } });
}
export async function updateVsCodeServer({ id, fqdn, name }) {
return await prisma.service.update({ where: { id }, data: { fqdn, name } });
}

View File

@@ -1,4 +1,4 @@
import { prisma, PrismaErrorHandler } from './common';
import { prisma } from './common';
export async function listTeams() {
return await prisma.team.findMany();

View File

@@ -1,8 +1,8 @@
import cuid from 'cuid';
import bcrypt from 'bcrypt';
import { prisma, PrismaErrorHandler } from './common';
import { asyncExecShell, removeContainer, uniqueName } from '$lib/common';
import { prisma } from './common';
import { asyncExecShell, uniqueName } from '$lib/common';
import * as db from '$lib/database';
import { startCoolifyProxy } from '$lib/haproxy';
@@ -28,12 +28,12 @@ export async function login({ email, password }) {
console.log('Network created');
})
.catch(() => {
console.log('Network already exists');
console.log('Network already exists.');
});
startCoolifyProxy('/var/run/docker.sock')
.then(() => {
console.log('Coolify Proxy Started');
console.log('Coolify Proxy started.');
})
.catch((err) => {
console.log(err);

View File

@@ -106,7 +106,11 @@ export async function forceSSLOffApplication({ domain }) {
export async function forceSSLOnApplication({ domain }) {
if (!dev) {
const haproxy = await haproxyInstance();
await checkHAProxy(haproxy);
try {
await checkHAProxy(haproxy);
} catch (error) {
return;
}
const transactionId = await getNextTransactionId();
try {
@@ -278,7 +282,11 @@ export async function reloadHaproxy(engine) {
}
export async function configureProxyForApplication({ domain, imageId, applicationId, port }) {
const haproxy = await haproxyInstance();
await checkHAProxy(haproxy);
try {
await checkHAProxy(haproxy);
} catch (error) {
return;
}
let serverConfigured = false;
let backendAvailable: any = null;
@@ -358,7 +366,11 @@ export async function configureProxyForApplication({ domain, imageId, applicatio
export async function configureCoolifyProxyOff({ domain }) {
const haproxy = await haproxyInstance();
await checkHAProxy(haproxy);
try {
await checkHAProxy(haproxy);
} catch (error) {
return;
}
try {
const transactionId = await getNextTransactionId();
@@ -388,7 +400,11 @@ export async function checkHAProxy(haproxy) {
}
export async function configureCoolifyProxyOn({ domain }) {
const haproxy = await haproxyInstance();
await checkHAProxy(haproxy);
try {
await checkHAProxy(haproxy);
} catch (error) {
return;
}
let serverConfigured = false;
let backendAvailable: any = null;
try {
@@ -586,11 +602,7 @@ export async function configureNetworkCoolifyProxy(engine) {
export async function configureSimpleServiceProxyOn({ id, domain, port }) {
const haproxy = await haproxyInstance();
try {
await checkHAProxy(haproxy);
} catch (error) {
return;
}
await checkHAProxy(haproxy);
try {
await haproxy.get(`v2/services/haproxy/configuration/backends/${domain}`).json();
return;
@@ -627,12 +639,15 @@ export async function configureSimpleServiceProxyOn({ id, domain, port }) {
export async function configureSimpleServiceProxyOff({ domain }) {
const haproxy = await haproxyInstance();
await checkHAProxy(haproxy);
try {
await checkHAProxy(haproxy);
} catch (error) {
return;
}
try {
await haproxy.get(`v2/services/haproxy/configuration/backends/${domain}`).json();
const transactionId = await getNextTransactionId();
await haproxy
.delete(`v2/services/haproxy/configuration/backends/${domain}`, {
searchParams: {

View File

@@ -7,6 +7,7 @@ import { asyncExecShell, createDirectories, getDomain, getEngine, saveBuildLog }
import { configureProxyForApplication, reloadHaproxy } from '../haproxy';
import * as db from '$lib/database';
import { decrypt } from '$lib/crypto';
import { sentry } from '$lib/common';
import {
copyBaseConfigurationFiles,
makeLabelForStandaloneApplication,
@@ -246,19 +247,22 @@ export default async function (job) {
} catch (error) {
throw new Error(error);
}
if (destinationDockerId && destinationDocker.isCoolifyProxyUsed) {
saveBuildLog({ line: 'Proxy configuration started!', buildId, applicationId });
await configureProxyForApplication({ domain, imageId, applicationId, port });
if (isHttps) await letsEncrypt({ domain, id: applicationId });
await reloadHaproxy(destinationDocker.engine);
saveBuildLog({ line: 'Proxy configuration successful!', buildId, applicationId });
} else {
saveBuildLog({
line: 'Coolify Proxy is not configured for this destination. Nothing else to do.',
buildId,
applicationId
});
try {
if (destinationDockerId && destinationDocker.isCoolifyProxyUsed) {
saveBuildLog({ line: 'Proxy configuration started!', buildId, applicationId });
await configureProxyForApplication({ domain, imageId, applicationId, port });
if (isHttps) await letsEncrypt({ domain, id: applicationId });
await reloadHaproxy(destinationDocker.engine);
saveBuildLog({ line: 'Proxy configuration successful!', buildId, applicationId });
} else {
saveBuildLog({
line: 'Coolify Proxy is not configured for this destination. Nothing else to do.',
buildId,
applicationId
});
}
} catch (error) {
sentry.captureException(error);
}
}
}

View File

@@ -1,22 +1,43 @@
import { dev } from '$app/env';
import { asyncExecShell, getEngine } from '$lib/common';
import { prisma } from '$lib/database';
import { defaultProxyImageHttp, defaultProxyImageTcp } from '$lib/haproxy';
export default async function () {
if (!dev) {
const destinationDockers = await prisma.destinationDocker.findMany();
for (const destinationDocker of destinationDockers) {
const host = getEngine(destinationDocker.engine);
// Tagging images with labels
try {
// await asyncExecShell(`DOCKER_HOST=${host} docker container prune -f`);
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) {
await asyncExecShell(
`DOCKER_HOST=${host} docker pull ${image} && echo "FROM ${image}" | docker build --label coolify.managed="true" -t "${image}" -`
);
}
} catch (error) {}
try {
await asyncExecShell(`DOCKER_HOST=${host} docker container prune -f`);
} catch (error) {
//
console.log(error);
}
// Cleanup images that are not managed by coolify
try {
// await asyncExecShell(`DOCKER_HOST=${host} docker image prune -f`);
await asyncExecShell(
`DOCKER_HOST=${host} docker image prune --filter 'label!=coolify.managed=true' -a -f`
);
} catch (error) {
//
console.log(error);
}
}

View File

@@ -107,7 +107,7 @@ cron().catch((error) => {
console.log(error);
});
const buildQueueName = dev ? cuid() : 'build_queue';
const buildQueueName = 'build_queue';
const buildQueue = new Queue(buildQueueName, connectionOptions);
const buildWorker = new Worker(buildQueueName, async (job) => await builder(job), {
concurrency: 2,
@@ -120,11 +120,8 @@ buildWorker.on('completed', async (job: Bullmq.Job) => {
} catch (err) {
console.log(err);
} finally {
const workdir = `/tmp/build-sources/${job.data.repository}/${job.data.build_id}`;
const workdir = `/tmp/build-sources/${job.data.repository}/`;
await asyncExecShell(`rm -fr ${workdir}`);
await asyncExecShell(
`test -f /tmp/build-sources/${job.data.repository}/id.rsa && rm /tmp/build-sources/${job.data.repository}/id.rsa`
);
}
return;
});
@@ -136,11 +133,8 @@ buildWorker.on('failed', async (job: Bullmq.Job, failedReason) => {
} catch (error) {
console.log(error);
} finally {
const workdir = `/tmp/build-sources/${job.data.repository}/${job.data.build_id}`;
const workdir = `/tmp/build-sources/${job.data.repository}`;
await asyncExecShell(`rm -fr ${workdir}`);
await asyncExecShell(
`test -f /tmp/build-sources/${job.data.repository}/id.rsa && rm /tmp/build-sources/${job.data.repository}/id.rsa`
);
}
saveBuildLog({ line: 'Failed build!', buildId: job.data.build_id, applicationId: job.data.id });
saveBuildLog({

View File

@@ -6,14 +6,19 @@
import { page } from '$app/stores';
import { del, post } from '$lib/api';
import { errorNotification } from '$lib/form';
import { createEventDispatcher } from 'svelte';
const dispatch = createEventDispatcher();
if (name) value = 'ENCRYPTED';
const { id } = $page.params;
async function removeSecret() {
try {
await del(`/applications/${id}/secrets.json`, { name });
return window.location.reload();
dispatch('refresh');
if (isNewSecret) {
name = '';
value = '';
}
} catch ({ error }) {
return errorNotification(error);
}
@@ -21,7 +26,11 @@
async function saveSecret() {
try {
await post(`/applications/${id}/secrets.json`, { name, value, isBuildSecret });
return window.location.reload();
dispatch('refresh');
if (isNewSecret) {
name = '';
value = '';
}
} catch ({ error }) {
return errorNotification(error);
}
@@ -33,101 +42,85 @@
}
</script>
<div class="mx-auto max-w-3xl pt-4">
<div class="flex space-x-2">
<div class="grid grid-flow-row">
<label for="secretName">Name</label>
<input
id="secretName"
bind:value={name}
placeholder="EXAMPLE_VARIABLE"
class="w-64 border-2 border-transparent"
readonly={!isNewSecret}
class:hover:bg-coolgray-200={!isNewSecret}
class:cursor-not-allowed={!isNewSecret}
/>
</div>
<div class="grid grid-flow-row">
<label for="secretValue">Value (will be encrypted)</label>
<input
id="secretValue"
bind:value
placeholder="J$#@UIO%HO#$U%H"
class="w-64 border-2 border-transparent"
class:hover:bg-coolgray-200={!isNewSecret}
class:cursor-not-allowed={!isNewSecret}
readonly={!isNewSecret}
/>
</div>
<div class="w-32 px-2 text-center">
<div class="text-xs">Is build variable?</div>
<div class="mt-2">
<ul class="divide-y divide-stone-800">
<li>
<div
type="button"
on:click={setSecretValue}
aria-pressed="false"
class="relative inline-flex h-6 w-11 flex-shrink-0 rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out"
class:bg-green-600={isBuildSecret}
class:bg-stone-700={!isBuildSecret}
class:cursor-not-allowed={!isNewSecret}
class:cursor-pointer={isNewSecret}
>
<span class="sr-only">Use isBuildSecret</span>
<span
class="pointer-events-none relative inline-block h-5 w-5 transform rounded-full bg-white shadow transition duration-200 ease-in-out"
class:translate-x-5={isBuildSecret}
class:translate-x-0={!isBuildSecret}
>
<span
class=" absolute inset-0 flex h-full w-full items-center justify-center transition-opacity duration-200 ease-in"
class:opacity-0={isBuildSecret}
class:opacity-100={!isBuildSecret}
aria-hidden="true"
>
<svg class="h-3 w-3 bg-white text-red-600" fill="none" viewBox="0 0 12 12">
<path
d="M4 8l2-2m0 0l2-2M6 6L4 4m2 2l2 2"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
/>
</svg>
</span>
<span
class="absolute inset-0 flex h-full w-full items-center justify-center transition-opacity duration-100 ease-out"
aria-hidden="true"
class:opacity-100={isBuildSecret}
class:opacity-0={!isBuildSecret}
>
<svg
class="h-3 w-3 bg-white text-green-600"
fill="currentColor"
viewBox="0 0 12 12"
>
<path
d="M3.707 5.293a1 1 0 00-1.414 1.414l1.414-1.414zM5 8l-.707.707a1 1 0 001.414 0L5 8zm4.707-3.293a1 1 0 00-1.414-1.414l1.414 1.414zm-7.414 2l2 2 1.414-1.414-2-2-1.414 1.414zm3.414 2l4-4-1.414-1.414-4 4 1.414 1.414z"
/>
</svg>
</span>
</span>
</div>
</li>
</ul>
</div>
</div>
{#if isNewSecret}
<div class="mt-6">
<button class="w-20 bg-green-600 hover:bg-green-500" on:click={saveSecret}>Add</button>
</div>
{:else}
<div class="mt-6">
<button class="w-20 bg-red-600 hover:bg-red-500" on:click={removeSecret}>Remove</button>
</div>
{/if}
<td class="whitespace-nowrap px-6 py-2 text-sm font-medium text-white">
<input
id="secretName"
bind:value={name}
placeholder="EXAMPLE_VARIABLE"
class="-mx-2 w-64 border-2 border-transparent"
readonly={!isNewSecret}
class:bg-transparent={!isNewSecret}
class:cursor-not-allowed={!isNewSecret}
/>
</td>
<td class="whitespace-nowrap px-6 py-2 text-sm font-medium text-white">
<input
id="secretValue"
bind:value
placeholder="J$#@UIO%HO#$U%H"
class="-mx-2 w-64 border-2 border-transparent"
class:bg-transparent={!isNewSecret}
class:cursor-not-allowed={!isNewSecret}
readonly={!isNewSecret}
/>
</td>
<td class="whitespace-nowrap px-6 py-2 text-center text-sm font-medium text-white">
<div
type="button"
on:click={setSecretValue}
aria-pressed="false"
class="relative inline-flex h-6 w-11 flex-shrink-0 rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out"
class:bg-green-600={isBuildSecret}
class:bg-stone-700={!isBuildSecret}
class:opacity-50={!isNewSecret}
class:cursor-not-allowed={!isNewSecret}
class:cursor-pointer={isNewSecret}
>
<span class="sr-only">Use isBuildSecret</span>
<span
class="pointer-events-none relative inline-block h-5 w-5 transform rounded-full bg-white shadow transition duration-200 ease-in-out"
class:translate-x-5={isBuildSecret}
class:translate-x-0={!isBuildSecret}
>
<span
class=" absolute inset-0 flex h-full w-full items-center justify-center transition-opacity duration-200 ease-in"
class:opacity-0={isBuildSecret}
class:opacity-100={!isBuildSecret}
aria-hidden="true"
>
<svg class="h-3 w-3 bg-white text-red-600" fill="none" viewBox="0 0 12 12">
<path
d="M4 8l2-2m0 0l2-2M6 6L4 4m2 2l2 2"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
/>
</svg>
</span>
<span
class="absolute inset-0 flex h-full w-full items-center justify-center transition-opacity duration-100 ease-out"
aria-hidden="true"
class:opacity-100={isBuildSecret}
class:opacity-0={!isBuildSecret}
>
<svg class="h-3 w-3 bg-white text-green-600" fill="currentColor" viewBox="0 0 12 12">
<path
d="M3.707 5.293a1 1 0 00-1.414 1.414l1.414-1.414zM5 8l-.707.707a1 1 0 001.414 0L5 8zm4.707-3.293a1 1 0 00-1.414-1.414l1.414 1.414zm-7.414 2l2 2 1.414-1.414-2-2-1.414 1.414zm3.414 2l4-4-1.414-1.414-4 4 1.414 1.414z"
/>
</svg>
</span>
</span>
</div>
</div>
</td>
<td class="whitespace-nowrap px-6 py-2 text-sm font-medium text-white">
{#if isNewSecret}
<div class="flex items-center justify-center">
<button class="w-24 bg-green-600 hover:bg-green-500" on:click={saveSecret}>Add</button>
</div>
{:else}
<div class="flex justify-center items-end">
<button class="w-24 bg-red-600 hover:bg-red-500" on:click={removeSecret}>Remove</button>
</div>
{/if}
</td>

View File

@@ -33,7 +33,7 @@ export const post: RequestHandler = async (event) => {
const found = await db.isSecretExists({ id, name });
if (found) {
throw {
error: `Secret ${name} already exists`
error: `Secret ${name} already exists.`
};
} else {
await db.createSecret({ id, name, value, isBuildSecret });

View File

@@ -24,6 +24,15 @@
export let application;
import Secret from './_Secret.svelte';
import { getDomain } from '$lib/components/common';
import { page } from '$app/stores';
import { get } from '$lib/api';
const { id } = $page.params;
async function refreshSecrets() {
const data = await get(`/applications/${id}/secrets.json`);
secrets = [...data.secrets];
}
</script>
<div class="flex space-x-1 p-6 font-bold">
@@ -31,11 +40,47 @@
Secrets for <a href={application.fqdn} target="_blank">{getDomain(application.fqdn)}</a>
</div>
</div>
<div class="mx-auto max-w-4xl px-6">
<div class="flex-col justify-start space-y-1">
{#each secrets as secret}
<Secret name={secret.name} value={secret.value} isBuildSecret={secret.isBuildSecret} />
{/each}
<Secret isNewSecret />
</div>
<div class="mx-auto max-w-6xl rounded-xl px-6 pt-4">
<table class="mx-auto">
<thead class=" rounded-xl border-b border-coolgray-500">
<tr>
<th
scope="col"
class="px-6 py-3 text-left text-xs font-bold uppercase tracking-wider text-warmGray-400"
>Name</th
>
<th
scope="col"
class="px-6 py-3 text-left text-xs font-bold uppercase tracking-wider text-warmGray-400"
>Value</th
>
<th
scope="col"
class="px-6 py-3 text-left text-xs font-bold uppercase tracking-wider text-warmGray-400"
>Need during buildtime?</th
>
<th
scope="col"
class="px-6 py-3 text-left text-xs font-bold uppercase tracking-wider text-warmGray-400"
/>
</tr>
</thead>
<tbody class="">
{#each secrets as secret}
{#key secret.id}
<tr class="hover:bg-coolgray-200">
<Secret
name={secret.name}
value={secret.value ? secret.value : 'ENCRYPTED'}
isBuildSecret={secret.isBuildSecret}
on:refresh={refreshSecrets}
/>
</tr>
{/key}
{/each}
<tr>
<Secret isNewSecret on:refresh={refreshSecrets} />
</tr>
</tbody>
</table>
</div>

View File

@@ -12,7 +12,8 @@ export const get: RequestHandler = async (event) => {
try {
const destination = await db.getDestination({ id, teamId });
const settings = await db.listSettings();
const state = await checkContainer(destination.engine, 'coolify-haproxy');
const state =
destination?.engine && (await checkContainer(destination.engine, 'coolify-haproxy'));
return {
status: 200,
body: {

View File

@@ -36,6 +36,7 @@
import Wordpress from '$lib/components/svg/services/Wordpress.svelte';
import { goto } from '$app/navigation';
import { post } from '$lib/api';
import VaultWarden from '$lib/components/svg/services/VaultWarden.svelte';
const { id } = $page.params;
const from = $page.url.searchParams.get('from');
@@ -71,6 +72,8 @@
<VsCodeServer isAbsolute />
{:else if type.name === 'wordpress'}
<Wordpress isAbsolute />
{:else if type.name === 'vaultwarden'}
<VaultWarden isAbsolute />
{/if}{type.fancyName}
</button>
</form>

View File

@@ -36,6 +36,7 @@
import Wordpress from '$lib/components/svg/services/Wordpress.svelte';
import Services from './_Services/_Services.svelte';
import { getDomain } from '$lib/components/common';
import VaultWarden from '$lib/components/svg/services/VaultWarden.svelte';
export let service;
export let isRunning;
@@ -94,6 +95,10 @@
<a href="https://wordpress.org" target="_blank">
<Wordpress />
</a>
{:else if service.type === 'vaultwarden'}
<a href="https://github.com/dani-garcia/vaultwarden" target="_blank">
<VaultWarden />
</a>
{/if}
</div>
</div>

View File

@@ -0,0 +1,20 @@
import { getUserDetails } from '$lib/common';
import * as db from '$lib/database';
import { PrismaErrorHandler } from '$lib/database';
import type { RequestHandler } from '@sveltejs/kit';
export const post: RequestHandler = async (event) => {
const { status, body } = await getUserDetails(event);
if (status === 401) return { status, body };
const { id } = event.params;
let { name, fqdn } = await event.request.json();
if (fqdn) fqdn = fqdn.toLowerCase();
try {
await db.updateVaultWardenService({ id, fqdn, name });
return { status: 201 };
} catch (error) {
return PrismaErrorHandler(error);
}
};

View File

@@ -0,0 +1,83 @@
import { asyncExecShell, createDirectories, getEngine, getUserDetails } from '$lib/common';
import * as db from '$lib/database';
import { promises as fs } from 'fs';
import yaml from 'js-yaml';
import type { RequestHandler } from '@sveltejs/kit';
import { letsEncrypt } from '$lib/letsencrypt';
import { configureSimpleServiceProxyOn, reloadHaproxy } from '$lib/haproxy';
import { getDomain } from '$lib/components/common';
import { getServiceImage, PrismaErrorHandler } from '$lib/database';
export const post: RequestHandler = async (event) => {
const { teamId, status, body } = await getUserDetails(event);
if (status === 401) return { status, body };
const { id } = event.params;
try {
const service = await db.getService({ id, teamId });
const { type, version, fqdn, destinationDockerId, destinationDocker } = service;
const domain = getDomain(fqdn);
const isHttps = fqdn.startsWith('https://');
const network = destinationDockerId && destinationDocker.network;
const host = getEngine(destinationDocker.engine);
const { workdir } = await createDirectories({ repository: type, buildId: id });
const baseImage = getServiceImage(type);
const config = {
image: `${baseImage}:${version}`,
volume: `${id}-vaultwarden-data:/data/`
};
const composeFile = {
version: '3.8',
services: {
[id]: {
container_name: id,
image: config.image,
networks: [network],
volumes: [config.volume],
restart: 'always'
}
},
networks: {
[network]: {
external: true
}
},
volumes: {
[config.volume.split(':')[0]]: {
external: true
}
}
};
const composeFileDestination = `${workdir}/docker-compose.yaml`;
await fs.writeFile(composeFileDestination, yaml.dump(composeFile));
try {
await asyncExecShell(
`DOCKER_HOST=${host} docker volume create ${config.volume.split(':')[0]}`
);
} catch (error) {
console.log(error);
}
try {
await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} up -d`);
await configureSimpleServiceProxyOn({ id, domain, port: 80 });
if (isHttps) {
await letsEncrypt({ domain, id });
}
await reloadHaproxy(destinationDocker.engine);
return {
status: 200
};
} catch (error) {
return PrismaErrorHandler(error);
}
} catch (error) {
return PrismaErrorHandler(error);
}
};

View File

@@ -0,0 +1,39 @@
import { getUserDetails, removeDestinationDocker } from '$lib/common';
import { getDomain } from '$lib/components/common';
import * as db from '$lib/database';
import { PrismaErrorHandler } from '$lib/database';
import { dockerInstance } from '$lib/docker';
import { checkContainer, configureSimpleServiceProxyOff } from '$lib/haproxy';
import type { RequestHandler } from '@sveltejs/kit';
export const post: RequestHandler = async (event) => {
const { teamId, status, body } = await getUserDetails(event);
if (status === 401) return { status, body };
const { id } = event.params;
try {
const service = await db.getService({ id, teamId });
const { destinationDockerId, destinationDocker, fqdn } = service;
const domain = getDomain(fqdn);
if (destinationDockerId) {
const engine = destinationDocker.engine;
try {
const found = await checkContainer(engine, id);
if (found) {
await removeDestinationDocker({ id, engine });
}
} catch (error) {
console.error(error);
}
await configureSimpleServiceProxyOff({ domain });
}
return {
status: 200
};
} catch (error) {
return PrismaErrorHandler(error);
}
};

View File

@@ -25,6 +25,7 @@
import MinIo from '$lib/components/svg/services/MinIO.svelte';
import VsCodeServer from '$lib/components/svg/services/VSCodeServer.svelte';
import Wordpress from '$lib/components/svg/services/Wordpress.svelte';
import VaultWarden from '$lib/components/svg/services/VaultWarden.svelte';
export let services;
</script>
@@ -67,6 +68,8 @@
<VsCodeServer isAbsolute />
{:else if service.type === 'wordpress'}
<Wordpress isAbsolute />
{:else if service.type === 'vaultwarden'}
<VaultWarden isAbsolute />
{/if}
<div class="font-bold text-xl text-center truncate">
{service.name}

View File

@@ -73,7 +73,9 @@
class:hover:bg-orange-500={!loading}
disabled={loading}>{loading ? 'Saving...' : 'Save'}</button
>
<button on:click={() => installRepositories(source)}>Change GitHub App Settings</button>
<button on:click|preventDefault={() => installRepositories(source)}
>Change GitHub App Settings</button
>
{/if}
</div>
<div class="grid grid-flow-row gap-2 px-10">

View File

@@ -2,6 +2,25 @@
@tailwind components;
@tailwind utilities;
/* poppins-regular - latin-ext_latin_devanagari */
@font-face {
font-family: 'Poppins';
font-style: normal;
font-weight: 400;
src: local(''), url('/poppins-v19-latin-ext_latin_devanagari-regular.woff2') format('woff2'),
/* Chrome 26+, Opera 23+, Firefox 39+ */
url('/poppins-v19-latin-ext_latin_devanagari-regular.woff') format('woff'); /* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */
}
/* poppins-500 - latin-ext_latin_devanagari */
@font-face {
font-family: 'Poppins';
font-style: normal;
font-weight: 500;
src: local(''), url('/poppins-v19-latin-ext_latin_devanagari-500.woff2') format('woff2'),
/* Chrome 26+, Opera 23+, Firefox 39+ */ url('/poppins-v19-latin-ext_latin_devanagari-500.woff')
format('woff'); /* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */
}
html {
@apply h-full min-h-full;
}
@@ -16,7 +35,7 @@ main,
}
input {
@apply w-96 select-all rounded border border-transparent bg-transparent bg-coolgray-200 p-2 text-xs tracking-tight text-white placeholder-stone-600 outline-none transition duration-150 hover:bg-coolgray-500 focus:bg-coolgray-500 disabled:bg-transparent md:text-sm;
@apply h-12 w-96 select-all rounded border border-transparent bg-transparent bg-coolgray-200 p-2 text-xs tracking-tight text-white placeholder-stone-600 outline-none transition duration-150 hover:bg-coolgray-500 focus:bg-coolgray-500 disabled:bg-transparent md:text-sm;
}
textarea {
@apply w-96 select-all rounded border border-transparent bg-transparent bg-coolgray-200 p-2 text-xs tracking-tight text-white placeholder-stone-600 outline-none transition duration-150 hover:bg-coolgray-500 focus:bg-coolgray-500 disabled:bg-transparent md:text-sm;
@@ -31,7 +50,7 @@ label {
}
button {
@apply rounded bg-coolgray-200 p-1 px-4 py-2 text-xs font-bold outline-none transition-all duration-100 hover:bg-coolgray-500 disabled:cursor-not-allowed disabled:bg-coolblack disabled:text-stone-600 md:text-sm;
@apply rounded bg-coolgray-200 p-1 px-2 py-1 text-xs font-bold outline-none transition-all duration-100 hover:bg-coolgray-500 disabled:cursor-not-allowed disabled:bg-coolblack disabled:text-stone-600;
}
a {

BIN
static/minio.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.9 KiB

BIN
static/nocodb.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

BIN
static/plausible.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.