mirror of
https://github.com/ershisan99/coolify.git
synced 2025-12-18 12:33:06 +00:00
Compare commits
32 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
88aa620cb4 | ||
|
|
70d3448110 | ||
|
|
09a1a406a6 | ||
|
|
40939d0b7f | ||
|
|
aec1d184c8 | ||
|
|
69d3cb5dd8 | ||
|
|
3deff162bb | ||
|
|
4ed54568d3 | ||
|
|
004724da55 | ||
|
|
97e9b5ffe3 | ||
|
|
45f920f802 | ||
|
|
2b31532d19 | ||
|
|
e7a6ecf95b | ||
|
|
545c98cee0 | ||
|
|
d29ccbfe37 | ||
|
|
d0807862e6 | ||
|
|
b92616dc14 | ||
|
|
a1a436300d | ||
|
|
16a5aeb1ba | ||
|
|
872095ff7a | ||
|
|
d88f2ea4c3 | ||
|
|
02e0385ab8 | ||
|
|
c9751d4cd9 | ||
|
|
162b637992 | ||
|
|
a10ddd4063 | ||
|
|
f46ccc63a7 | ||
|
|
fc04a45744 | ||
|
|
90c2b59a51 | ||
|
|
d6bee99c1b | ||
|
|
0871d47568 | ||
|
|
5c646c1898 | ||
|
|
8974de165f |
@@ -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
|
||||
|
||||
62
README.md
62
README.md
@@ -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.
|
||||
|
||||
14
package.json
14
package.json
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "coolify",
|
||||
"description": "An open-source & self-hostable Heroku / Netlify alternative.",
|
||||
"version": "2.0.4",
|
||||
"version": "2.0.7",
|
||||
"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,9 +16,9 @@
|
||||
"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: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:staging": "cross-var docker build -t coollabsio/coolify:$npm_package_version . && docker push coollabsio/coolify:$npm_package_version",
|
||||
"release:pre": "cross-var docker build -t coollabsio/coolify:$npm_package_version -t coollabsio/coolify:latest .",
|
||||
"release:coolify": "cross-var yarn release:pre && docker 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",
|
||||
"release:haproxy:http": "docker build -f haproxy-http.Dockerfile -t coollabsio/coolify-haproxy-http-alpine:1.0.0 -t coollabsio/coolify-haproxy-http-alpine:latest . && docker image push --all-tags coollabsio/coolify-haproxy-http-alpine",
|
||||
|
||||
@@ -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");
|
||||
@@ -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 {
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -67,7 +67,6 @@ export const getTeam = (event) => {
|
||||
};
|
||||
|
||||
export const getUserDetails = async (event, isAdminRequired = true) => {
|
||||
// try {
|
||||
const teamId = getTeam(event);
|
||||
const userId = event.locals.session.data.uid || null;
|
||||
const { permission = 'read' } = await db.prisma.permission.findFirst({
|
||||
@@ -91,18 +90,6 @@ export const getUserDetails = async (event, isAdminRequired = true) => {
|
||||
}
|
||||
|
||||
return payload;
|
||||
// } catch (err) {
|
||||
// console.log(err);
|
||||
// return {
|
||||
// teamId: null,
|
||||
// userId: null,
|
||||
// permission: 'read',
|
||||
// status: 401,
|
||||
// body: {
|
||||
// message: 'You do not have permission to do this. \nAsk an admin to modify your permissions.'
|
||||
// }
|
||||
// };
|
||||
// }
|
||||
};
|
||||
|
||||
export function getEngine(engine) {
|
||||
|
||||
@@ -20,9 +20,6 @@
|
||||
|
||||
function showActions(value) {
|
||||
actionsShow = value;
|
||||
// if (value === false) {
|
||||
// showPassword = false;
|
||||
// }
|
||||
}
|
||||
function copyToClipboard() {
|
||||
if (isHttps && navigator.clipboard) {
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
export let description;
|
||||
export let isPadding = true;
|
||||
export let disabled = false;
|
||||
// export let disabledReason = '';
|
||||
</script>
|
||||
|
||||
<li class="flex items-center py-4">
|
||||
@@ -14,11 +13,6 @@
|
||||
<p class="text-xs font-bold text-stone-100 md:text-base">{title}</p>
|
||||
<Explainer text={description} />
|
||||
</div>
|
||||
<!-- {#if disabled}
|
||||
<div class="flex" class:px-4={isPadding} class:pr-32={!isPadding}>
|
||||
<p class="text-xs font-bold text-stone-400">{disabledReason}</p>
|
||||
</div>
|
||||
{:else} -->
|
||||
<div
|
||||
type="button"
|
||||
on:click
|
||||
|
||||
@@ -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"
|
||||
/>
|
||||
|
||||
@@ -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"
|
||||
/>
|
||||
|
||||
@@ -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"
|
||||
/>
|
||||
|
||||
35
src/lib/components/svg/services/VaultWarden.svelte
Normal file
35
src/lib/components/svg/services/VaultWarden.svelte
Normal 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>
|
||||
@@ -1,6 +1,6 @@
|
||||
import { decrypt, encrypt } from '$lib/crypto';
|
||||
import { removeProxyConfiguration } from '$lib/haproxy';
|
||||
import { asyncExecShell, getEngine, removeContainer } from '$lib/common';
|
||||
import { removeProxyConfiguration, removeWwwRedirection } from '$lib/haproxy';
|
||||
import { asyncExecShell, getEngine } from '$lib/common';
|
||||
|
||||
import { getDomain, removeDestinationDocker } from '$lib/common';
|
||||
import { prisma } from './common';
|
||||
|
||||
@@ -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({
|
||||
|
||||
@@ -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']
|
||||
}
|
||||
];
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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}" -`
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -111,22 +109,4 @@ export async function setDestinationSettings({ engine, isCoolifyProxyUsed }) {
|
||||
where: { engine },
|
||||
data: { isCoolifyProxyUsed }
|
||||
});
|
||||
|
||||
// if (isCoolifyProxyUsed) {
|
||||
// await installCoolifyProxy(engine)
|
||||
// await configureNetworkCoolifyProxy(engine)
|
||||
// } else {
|
||||
// // TODO: must check if other destination is using the proxy??? or not?
|
||||
// const domain = await prisma.setting.findUnique({ where: { name: 'domain' }, rejectOnNotFound: false })
|
||||
// if (!domain) {
|
||||
// await uninstallCoolifyProxy(engine)
|
||||
// } else {
|
||||
// return {
|
||||
// stastus: 500,
|
||||
// body: {
|
||||
// message: 'You can not disable the Coolify proxy while the domain is set for Coolify itself.'
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
@@ -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({
|
||||
|
||||
@@ -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({
|
||||
|
||||
@@ -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({
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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({
|
||||
|
||||
@@ -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 } });
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { prisma, PrismaErrorHandler } from './common';
|
||||
import { prisma } from './common';
|
||||
|
||||
export async function listTeams() {
|
||||
return await prisma.team.findMany();
|
||||
|
||||
@@ -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);
|
||||
@@ -97,17 +97,6 @@ export async function login({ email, password }) {
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// const token = jsonwebtoken.sign({}, secretKey, {
|
||||
// expiresIn: 15778800,
|
||||
// algorithm: 'HS256',
|
||||
// audience: 'coolify',
|
||||
// issuer: 'coolify',
|
||||
// jwtid: uid,
|
||||
// subject: `User:${uid}`,
|
||||
// notBefore: -1000
|
||||
// });
|
||||
|
||||
return {
|
||||
status: 200,
|
||||
headers: {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { dev } from '$app/env';
|
||||
import { asyncExecShell, getEngine } from '$lib/common';
|
||||
import { asyncExecShell, getDomain, getEngine } from '$lib/common';
|
||||
import got from 'got';
|
||||
import * as db from '$lib/database';
|
||||
import { letsEncrypt } from '$lib/letsencrypt';
|
||||
@@ -51,11 +51,11 @@ export async function completeTransaction(transactionId) {
|
||||
|
||||
export async function removeProxyConfiguration({ domain }) {
|
||||
const haproxy = await haproxyInstance();
|
||||
const transactionId = await getNextTransactionId();
|
||||
const backendFound = await haproxy
|
||||
.get(`v2/services/haproxy/configuration/backends/${domain}`)
|
||||
.json();
|
||||
if (backendFound) {
|
||||
const transactionId = await getNextTransactionId();
|
||||
await haproxy
|
||||
.delete(`v2/services/haproxy/configuration/backends/${domain}`, {
|
||||
searchParams: {
|
||||
@@ -65,6 +65,7 @@ export async function removeProxyConfiguration({ domain }) {
|
||||
.json();
|
||||
await completeTransaction(transactionId);
|
||||
}
|
||||
await removeWwwRedirection(domain);
|
||||
}
|
||||
export async function forceSSLOffApplication({ domain }) {
|
||||
if (!dev) {
|
||||
@@ -106,7 +107,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 {
|
||||
@@ -120,7 +125,9 @@ export async function forceSSLOnApplication({ domain }) {
|
||||
.json();
|
||||
let nextRule = 0;
|
||||
if (rules.data.length > 0) {
|
||||
const rule = rules.data.find((rule) => rule.cond_test.includes(`-i ${domain}`));
|
||||
const rule = rules.data.find((rule) =>
|
||||
rule.cond_test.includes(`{ hdr(host) -i ${domain} } !{ ssl_fc }`)
|
||||
);
|
||||
if (rule) return;
|
||||
nextRule = rules.data[rules.data.length - 1].index + 1;
|
||||
}
|
||||
@@ -134,7 +141,7 @@ export async function forceSSLOnApplication({ domain }) {
|
||||
json: {
|
||||
index: nextRule,
|
||||
cond: 'if',
|
||||
cond_test: `{ hdr(Host) -i ${domain} } !{ ssl_fc }`,
|
||||
cond_test: `{ hdr(host) -i ${domain} } !{ ssl_fc }`,
|
||||
type: 'redirect',
|
||||
redir_type: 'scheme',
|
||||
redir_value: 'https',
|
||||
@@ -184,101 +191,18 @@ export async function deleteProxy({ id }) {
|
||||
await completeTransaction(transactionId);
|
||||
}
|
||||
}
|
||||
// export async function configureProxyForDatabase({ id, port, isPublic, privatePort }) {
|
||||
// const haproxy = await haproxyInstance()
|
||||
// try {
|
||||
// await checkHAProxy()
|
||||
// } catch (error) {
|
||||
// return
|
||||
// }
|
||||
|
||||
// let alreadyConfigured = false
|
||||
// try {
|
||||
// const backend: any = await haproxy.get(`v2/services/haproxy/configuration/backends/${id}`).json()
|
||||
// const server: any = await haproxy.get(`v2/services/haproxy/configuration/servers/${id}`, {
|
||||
// searchParams: {
|
||||
// backend: id
|
||||
// },
|
||||
// }).json()
|
||||
// if (backend.data.name === id) {
|
||||
// if (server.data.port === privatePort) {
|
||||
// if (server.data.check === 'enabled') {
|
||||
// if (server.data.address === id) {
|
||||
// alreadyConfigured = true
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// } catch (error) {
|
||||
// console.log('error getting backend or server', error.response.body)
|
||||
// }
|
||||
// if (alreadyConfigured) return
|
||||
|
||||
// const transactionId = await getNextTransactionId()
|
||||
// try {
|
||||
// await haproxy.post('v2/services/haproxy/configuration/backends', {
|
||||
// searchParams: {
|
||||
// transaction_id: transactionId
|
||||
// },
|
||||
// json: {
|
||||
// "init-addr": "last,libc,none",
|
||||
// "mode": "tcp",
|
||||
// "name": id
|
||||
// }
|
||||
// })
|
||||
// await haproxy.post('v2/services/haproxy/configuration/servers', {
|
||||
// searchParams: {
|
||||
// transaction_id: transactionId,
|
||||
// backend: id
|
||||
// },
|
||||
// json: {
|
||||
// "address": id,
|
||||
// "check": "enabled",
|
||||
// "name": id,
|
||||
// "port": privatePort
|
||||
// }
|
||||
// })
|
||||
// await haproxy.post('v2/services/haproxy/configuration/frontends', {
|
||||
// searchParams: {
|
||||
// transaction_id: transactionId,
|
||||
// backend: id
|
||||
// },
|
||||
// json: {
|
||||
// "default_backend": id,
|
||||
// "mode": "tcp",
|
||||
// "name": id
|
||||
// }
|
||||
// })
|
||||
// await haproxy.post('v2/services/haproxy/configuration/binds', {
|
||||
// searchParams: {
|
||||
// transaction_id: transactionId,
|
||||
// frontend: id
|
||||
// },
|
||||
// json: {
|
||||
// "address": "*",
|
||||
// "name": id,
|
||||
// "port": port
|
||||
// }
|
||||
// })
|
||||
// } catch (error) {
|
||||
// console.log(error.response.body)
|
||||
// throw error.response.body
|
||||
// } finally {
|
||||
// try {
|
||||
// await completeTransaction(transactionId)
|
||||
// } catch (error) {
|
||||
// console.log(error.response.body)
|
||||
// }
|
||||
// }
|
||||
// await configureDatabaseVisibility({ id, isPublic })
|
||||
// }
|
||||
export async function reloadHaproxy(engine) {
|
||||
const host = getEngine(engine);
|
||||
return await asyncExecShell(`DOCKER_HOST=${host} docker exec coolify-haproxy kill -HUP 1`);
|
||||
}
|
||||
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;
|
||||
@@ -356,9 +280,14 @@ export async function configureProxyForApplication({ domain, imageId, applicatio
|
||||
}
|
||||
}
|
||||
|
||||
export async function configureCoolifyProxyOff({ domain }) {
|
||||
export async function configureCoolifyProxyOff(fqdn) {
|
||||
const domain = getDomain(fqdn);
|
||||
const haproxy = await haproxyInstance();
|
||||
await checkHAProxy(haproxy);
|
||||
try {
|
||||
await checkHAProxy(haproxy);
|
||||
} catch (error) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const transactionId = await getNextTransactionId();
|
||||
@@ -374,6 +303,7 @@ export async function configureCoolifyProxyOff({ domain }) {
|
||||
if (!dev) {
|
||||
await forceSSLOffApplication({ domain });
|
||||
}
|
||||
await setWwwRedirection(fqdn);
|
||||
} catch (error) {
|
||||
throw error?.response?.body || error;
|
||||
}
|
||||
@@ -386,9 +316,14 @@ export async function checkHAProxy(haproxy) {
|
||||
throw 'HAProxy is not running, but it should be!';
|
||||
}
|
||||
}
|
||||
export async function configureCoolifyProxyOn({ domain }) {
|
||||
export async function configureCoolifyProxyOn(fqdn) {
|
||||
const domain = getDomain(fqdn);
|
||||
const haproxy = await haproxyInstance();
|
||||
await checkHAProxy(haproxy);
|
||||
try {
|
||||
await checkHAProxy(haproxy);
|
||||
} catch (error) {
|
||||
return;
|
||||
}
|
||||
let serverConfigured = false;
|
||||
let backendAvailable: any = null;
|
||||
try {
|
||||
@@ -586,14 +521,18 @@ 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;
|
||||
const transactionId = await getNextTransactionId();
|
||||
await haproxy
|
||||
.delete(`v2/services/haproxy/configuration/backends/${domain}`, {
|
||||
searchParams: {
|
||||
transaction_id: transactionId
|
||||
}
|
||||
})
|
||||
.json();
|
||||
await completeTransaction(transactionId);
|
||||
} catch (error) {}
|
||||
try {
|
||||
const transactionId = await getNextTransactionId();
|
||||
@@ -619,6 +558,12 @@ export async function configureSimpleServiceProxyOn({ id, domain, port }) {
|
||||
port: port
|
||||
}
|
||||
});
|
||||
console.log({
|
||||
address: id,
|
||||
check: 'enabled',
|
||||
name: id,
|
||||
port: port
|
||||
});
|
||||
await completeTransaction(transactionId);
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
@@ -627,12 +572,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: {
|
||||
@@ -642,5 +590,98 @@ export async function configureSimpleServiceProxyOff({ domain }) {
|
||||
.json();
|
||||
await completeTransaction(transactionId);
|
||||
} catch (error) {}
|
||||
await removeWwwRedirection(domain);
|
||||
return;
|
||||
}
|
||||
|
||||
export async function removeWwwRedirection(domain) {
|
||||
const haproxy = await haproxyInstance();
|
||||
try {
|
||||
await checkHAProxy(haproxy);
|
||||
} catch (error) {
|
||||
return;
|
||||
}
|
||||
|
||||
const rules: any = await haproxy
|
||||
.get(`v2/services/haproxy/configuration/http_request_rules`, {
|
||||
searchParams: {
|
||||
parent_name: 'http',
|
||||
parent_type: 'frontend'
|
||||
}
|
||||
})
|
||||
.json();
|
||||
if (rules.data.length > 0) {
|
||||
const rule = rules.data.find((rule) =>
|
||||
rule.redir_value.includes(`${domain}%[capture.req.uri]`)
|
||||
);
|
||||
if (rule) {
|
||||
const transactionId = await getNextTransactionId();
|
||||
await haproxy
|
||||
.delete(`v2/services/haproxy/configuration/http_request_rules/${rule.index}`, {
|
||||
searchParams: {
|
||||
transaction_id: transactionId,
|
||||
parent_name: 'http',
|
||||
parent_type: 'frontend'
|
||||
}
|
||||
})
|
||||
.json();
|
||||
await completeTransaction(transactionId);
|
||||
}
|
||||
}
|
||||
}
|
||||
export async function setWwwRedirection(fqdn) {
|
||||
const haproxy = await haproxyInstance();
|
||||
try {
|
||||
await checkHAProxy(haproxy);
|
||||
} catch (error) {
|
||||
return;
|
||||
}
|
||||
const transactionId = await getNextTransactionId();
|
||||
|
||||
try {
|
||||
const domain = getDomain(fqdn);
|
||||
const isHttps = fqdn.startsWith('https://');
|
||||
const isWWW = fqdn.includes('www.');
|
||||
const contTest = `{ req.hdr(host) -i ${isWWW ? domain.replace('www.', '') : `www.${domain}`} }`;
|
||||
const rules: any = await haproxy
|
||||
.get(`v2/services/haproxy/configuration/http_request_rules`, {
|
||||
searchParams: {
|
||||
parent_name: 'http',
|
||||
parent_type: 'frontend'
|
||||
}
|
||||
})
|
||||
.json();
|
||||
let nextRule = 0;
|
||||
if (rules.data.length > 0) {
|
||||
const rule = rules.data.find((rule) =>
|
||||
rule.redir_value.includes(`${domain}%[capture.req.uri]`)
|
||||
);
|
||||
if (rule) return;
|
||||
nextRule = rules.data[rules.data.length - 1].index + 1;
|
||||
}
|
||||
const redirectValue = `${isHttps ? 'https://' : 'http://'}${domain}%[capture.req.uri]`;
|
||||
await haproxy
|
||||
.post(`v2/services/haproxy/configuration/http_request_rules`, {
|
||||
searchParams: {
|
||||
transaction_id: transactionId,
|
||||
parent_name: 'http',
|
||||
parent_type: 'frontend'
|
||||
},
|
||||
json: {
|
||||
index: nextRule,
|
||||
cond: 'if',
|
||||
cond_test: contTest,
|
||||
type: 'redirect',
|
||||
redir_type: 'location',
|
||||
redir_value: redirectValue,
|
||||
redir_code: dev ? 302 : 301
|
||||
}
|
||||
})
|
||||
.json();
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
throw error;
|
||||
} finally {
|
||||
await completeTransaction(transactionId);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,9 +4,10 @@ import * as buildpacks from '../buildPacks';
|
||||
import * as importers from '../importers';
|
||||
import { dockerInstance } from '../docker';
|
||||
import { asyncExecShell, createDirectories, getDomain, getEngine, saveBuildLog } from '../common';
|
||||
import { configureProxyForApplication, reloadHaproxy } from '../haproxy';
|
||||
import { configureProxyForApplication, reloadHaproxy, setWwwRedirection } from '../haproxy';
|
||||
import * as db from '$lib/database';
|
||||
import { decrypt } from '$lib/crypto';
|
||||
import { sentry } from '$lib/common';
|
||||
import {
|
||||
copyBaseConfigurationFiles,
|
||||
makeLabelForStandaloneApplication,
|
||||
@@ -63,10 +64,6 @@ export default async function (job) {
|
||||
if (destinationDockerId) {
|
||||
destinationType = 'docker';
|
||||
}
|
||||
// Not implemented yet
|
||||
// if (destinationKubernetesId) {
|
||||
// destinationType = 'kubernetes'
|
||||
// }
|
||||
|
||||
if (destinationType === 'docker') {
|
||||
const docker = dockerInstance({ destinationDocker });
|
||||
@@ -208,9 +205,7 @@ export default async function (job) {
|
||||
const envs = [];
|
||||
if (secrets.length > 0) {
|
||||
secrets.forEach((secret) => {
|
||||
if (!secret.isBuildSecret) {
|
||||
envs.push(`${secret.name}=${secret.value}`);
|
||||
}
|
||||
envs.push(`${secret.name}=${secret.value}`);
|
||||
});
|
||||
}
|
||||
await fs.writeFile(`${workdir}/.env`, envs.join('\n'));
|
||||
@@ -246,19 +241,23 @@ 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 setWwwRedirection(fqdn);
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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({
|
||||
@@ -150,28 +144,6 @@ buildWorker.on('failed', async (job: Bullmq.Job, failedReason) => {
|
||||
});
|
||||
});
|
||||
|
||||
// const letsEncryptQueueName = dev ? cuid() : 'letsencrypt_queue'
|
||||
// const letsEncryptQueue = new Queue(letsEncryptQueueName, connectionOptions)
|
||||
|
||||
// const letsEncryptWorker = new Worker(letsEncryptQueueName, async (job) => await letsencrypt(job), {
|
||||
// concurrency: 1,
|
||||
// ...connectionOptions
|
||||
// })
|
||||
// letsEncryptWorker.on('completed', async () => {
|
||||
// // TODO: Save letsencrypt logs as build logs!
|
||||
// console.log('[DEBUG] Lets Encrypt job completed')
|
||||
// })
|
||||
|
||||
// letsEncryptWorker.on('failed', async (job: Job, failedReason: string) => {
|
||||
// try {
|
||||
// await prisma.applicationSettings.updateMany({ where: { applicationId: job.data.id }, data: { forceSSL: false } })
|
||||
// } catch (error) {
|
||||
// console.log(error)
|
||||
// }
|
||||
// console.log('[DEBUG] Lets Encrypt job failed')
|
||||
// console.log(failedReason)
|
||||
// })
|
||||
|
||||
const buildLogQueueName = dev ? cuid() : 'log_queue';
|
||||
const buildLogQueue = new Queue(buildLogQueueName, connectionOptions);
|
||||
const buildLogWorker = new Worker(buildLogQueueName, async (job) => await logger(job), {
|
||||
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
configureProxyForApplication,
|
||||
forceSSLOnApplication,
|
||||
reloadHaproxy,
|
||||
setWwwRedirection,
|
||||
startCoolifyProxy
|
||||
} from '$lib/haproxy';
|
||||
import * as db from '$lib/database';
|
||||
@@ -40,6 +41,7 @@ export default async function () {
|
||||
});
|
||||
const isHttps = fqdn.startsWith('https://');
|
||||
if (isHttps) await forceSSLOnApplication({ domain });
|
||||
await setWwwRedirection(fqdn);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -52,6 +54,7 @@ export default async function () {
|
||||
const found = await checkContainer('/var/run/docker.sock', 'coolify-haproxy');
|
||||
if (!found) await startCoolifyProxy('/var/run/docker.sock');
|
||||
await configureCoolifyProxyOn({ domain });
|
||||
await setWwwRedirection(fqdn);
|
||||
const isHttps = fqdn.startsWith('https://');
|
||||
if (isHttps) await forceSSLOnApplication({ domain });
|
||||
}
|
||||
|
||||
@@ -124,25 +124,6 @@
|
||||
updateStatus.success = false;
|
||||
updateStatus.loading = false;
|
||||
}
|
||||
// } else {
|
||||
// let reachable = false;
|
||||
// let tries = 0;
|
||||
// do {
|
||||
// await asyncSleep(1000);
|
||||
// try {
|
||||
// await get(`/undead.json`);
|
||||
// reachable = true;
|
||||
// } catch (error) {
|
||||
// console.log(error);
|
||||
// reachable = false;
|
||||
// }
|
||||
// if (reachable) break;
|
||||
// tries++;
|
||||
// } while (!reachable || tries < 120);
|
||||
// toast.push('New version reachable. Reloading...');
|
||||
// await asyncSleep(2000);
|
||||
// window.location.reload();
|
||||
// }
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
@@ -142,14 +142,6 @@
|
||||
return errorNotification('Branch already configured');
|
||||
}
|
||||
}
|
||||
// async function saveDeployKey(deployKeyId: number) {
|
||||
// try {
|
||||
// await post(updateDeployKeyIdUrl, { deployKeyId });
|
||||
// } catch (error) {
|
||||
// errorNotification(error);
|
||||
// throw new Error(error);
|
||||
// }
|
||||
// }
|
||||
async function checkSSHKey(sshkeyUrl) {
|
||||
try {
|
||||
return await post(sshkeyUrl, {});
|
||||
|
||||
@@ -266,7 +266,7 @@
|
||||
required
|
||||
/>
|
||||
<Explainer
|
||||
text="If you specify <span class='text-green-600 font-bold'>https</span>, the application will be accessible only over https. SSL certificate will be generated for you.<br>To modify the domain, you must first stop the application."
|
||||
text="If you specify <span class='text-green-600 font-bold'>https</span>, the application will be accessible only over https. SSL certificate will be generated for you.<br>If you specify <span class='text-green-600 font-bold'>www</span>, the application will be redirected (302) from non-www and vice versa.<br><br>To modify the domain, you must first stop the application."
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -6,14 +6,20 @@
|
||||
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 = '';
|
||||
isBuildSecret = false;
|
||||
}
|
||||
} catch ({ error }) {
|
||||
return errorNotification(error);
|
||||
}
|
||||
@@ -21,7 +27,12 @@
|
||||
async function saveSecret() {
|
||||
try {
|
||||
await post(`/applications/${id}/secrets.json`, { name, value, isBuildSecret });
|
||||
return window.location.reload();
|
||||
dispatch('refresh');
|
||||
if (isNewSecret) {
|
||||
name = '';
|
||||
value = '';
|
||||
isBuildSecret = false;
|
||||
}
|
||||
} catch ({ error }) {
|
||||
return errorNotification(error);
|
||||
}
|
||||
@@ -33,101 +44,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>
|
||||
|
||||
@@ -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 });
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -25,9 +25,7 @@ export const get: RequestHandler = async (event) => {
|
||||
state = 'running';
|
||||
}
|
||||
} catch (error) {
|
||||
// if (!error.stderr.includes('No such object')) {
|
||||
// console.log(error)
|
||||
// }
|
||||
//
|
||||
}
|
||||
}
|
||||
const configuration = generateDatabaseConfiguration(database);
|
||||
|
||||
@@ -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: {
|
||||
|
||||
@@ -110,25 +110,9 @@
|
||||
required
|
||||
/>
|
||||
<Explainer
|
||||
text="If you specify <span class='text-green-600 font-bold'>https</span>, the application will be accessible only over https. SSL certificate will be generated for you."
|
||||
text="If you specify <span class='text-pink-600 font-bold'>https</span>, the application will be accessible only over https. SSL certificate will be generated for you.<br>If you specify <span class='text-pink-600 font-bold'>www</span>, the application will be redirected (302) from non-www and vice versa.<br><br>To modify the domain, you must first stop the application."
|
||||
/>
|
||||
</div>
|
||||
<!-- {:else}
|
||||
<label for="fqdn" class="pt-2">Domain (FQDN)</label>
|
||||
<div class="col-span-2 ">
|
||||
<CopyPasswordField
|
||||
placeholder="eg: https://analytics.coollabs.io"
|
||||
readonly={!$session.isAdmin}
|
||||
name="fqdn"
|
||||
id="fqdn"
|
||||
bind:value={service.fqdn}
|
||||
required
|
||||
/>
|
||||
<Explainer
|
||||
text="If you specify <span class='text-green-600'>https</span>, the application will be accessible only over https. SSL certificate will be generated for you."
|
||||
/>
|
||||
</div>
|
||||
{/if} -->
|
||||
</div>
|
||||
{#if service.type === 'plausibleanalytics'}
|
||||
<PlausibleAnalytics bind:service {readOnly} />
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -46,24 +46,3 @@ export const get: RequestHandler = async (event) => {
|
||||
return PrismaErrorHandler(error);
|
||||
}
|
||||
};
|
||||
|
||||
// export const post: RequestHandler<Locals, FormData> = async (request) => {
|
||||
// const { teamId, status, body } = await getUserDetails(request);
|
||||
// if (status === 401) return { status, body }
|
||||
// const { id } = request.params
|
||||
|
||||
// const name = request.body.get('name')
|
||||
// const defaultDatabase = request.body.get('defaultDatabase')
|
||||
// const dbUser = request.body.get('dbUser')
|
||||
// const dbUserPassword = request.body.get('dbUserPassword')
|
||||
// const rootUser = request.body.get('rootUser')
|
||||
// const rootUserPassword = request.body.get('rootUserPassword')
|
||||
// const version = request.body.get('version')
|
||||
|
||||
// try {
|
||||
// return await db.updateDatabase({ id, name, defaultDatabase, dbUser, dbUserPassword, rootUser, rootUserPassword, version })
|
||||
// } catch (err) {
|
||||
// return err
|
||||
// }
|
||||
|
||||
// }
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -7,6 +7,7 @@ import { letsEncrypt } from '$lib/letsencrypt';
|
||||
import {
|
||||
configureSimpleServiceProxyOn,
|
||||
reloadHaproxy,
|
||||
setWwwRedirection,
|
||||
startHttpProxy,
|
||||
startTcpProxy
|
||||
} from '$lib/haproxy';
|
||||
@@ -86,13 +87,13 @@ export const post: RequestHandler = async (event) => {
|
||||
try {
|
||||
await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} up -d`);
|
||||
await configureSimpleServiceProxyOn({ id, domain, port: consolePort });
|
||||
|
||||
await db.updateMinioService({ id, publicPort });
|
||||
await startHttpProxy(destinationDocker, id, publicPort, apiPort);
|
||||
|
||||
if (isHttps) {
|
||||
await letsEncrypt({ domain, id });
|
||||
}
|
||||
await setWwwRedirection(fqdn);
|
||||
await reloadHaproxy(destinationDocker.engine);
|
||||
return {
|
||||
status: 200
|
||||
|
||||
@@ -4,7 +4,7 @@ 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 { configureSimpleServiceProxyOn, reloadHaproxy, setWwwRedirection } from '$lib/haproxy';
|
||||
import { getDomain } from '$lib/components/common';
|
||||
import { PrismaErrorHandler } from '$lib/database';
|
||||
|
||||
@@ -52,6 +52,7 @@ export const post: RequestHandler = async (event) => {
|
||||
if (isHttps) {
|
||||
await letsEncrypt({ domain, id });
|
||||
}
|
||||
await setWwwRedirection(fqdn);
|
||||
await reloadHaproxy(destinationDocker.engine);
|
||||
return {
|
||||
status: 200
|
||||
|
||||
@@ -4,7 +4,7 @@ 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 { configureSimpleServiceProxyOn, reloadHaproxy, setWwwRedirection } from '$lib/haproxy';
|
||||
import { getDomain } from '$lib/components/common';
|
||||
import { PrismaErrorHandler } from '$lib/database';
|
||||
|
||||
@@ -185,6 +185,7 @@ COPY ./init-db.sh /docker-entrypoint-initdb.d/init-db.sh`;
|
||||
if (isHttps) {
|
||||
await letsEncrypt({ domain, id });
|
||||
}
|
||||
await setWwwRedirection(fqdn);
|
||||
await reloadHaproxy(destinationDocker.engine);
|
||||
return {
|
||||
status: 200
|
||||
|
||||
20
src/routes/services/[id]/vaultwarden/index.json.ts
Normal file
20
src/routes/services/[id]/vaultwarden/index.json.ts
Normal 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);
|
||||
}
|
||||
};
|
||||
84
src/routes/services/[id]/vaultwarden/start.json.ts
Normal file
84
src/routes/services/[id]/vaultwarden/start.json.ts
Normal file
@@ -0,0 +1,84 @@
|
||||
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, setWwwRedirection } 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 setWwwRedirection(fqdn);
|
||||
await reloadHaproxy(destinationDocker.engine);
|
||||
return {
|
||||
status: 200
|
||||
};
|
||||
} catch (error) {
|
||||
return PrismaErrorHandler(error);
|
||||
}
|
||||
} catch (error) {
|
||||
return PrismaErrorHandler(error);
|
||||
}
|
||||
};
|
||||
39
src/routes/services/[id]/vaultwarden/stop.json.ts
Normal file
39
src/routes/services/[id]/vaultwarden/stop.json.ts
Normal 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);
|
||||
}
|
||||
};
|
||||
@@ -4,7 +4,7 @@ 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 { configureSimpleServiceProxyOn, reloadHaproxy, setWwwRedirection } from '$lib/haproxy';
|
||||
import { getDomain } from '$lib/components/common';
|
||||
import { PrismaErrorHandler } from '$lib/database';
|
||||
|
||||
@@ -80,6 +80,7 @@ export const post: RequestHandler = async (event) => {
|
||||
if (isHttps) {
|
||||
await letsEncrypt({ domain, id });
|
||||
}
|
||||
await setWwwRedirection(fqdn);
|
||||
await reloadHaproxy(destinationDocker.engine);
|
||||
return {
|
||||
status: 200
|
||||
|
||||
@@ -4,7 +4,7 @@ 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 { configureSimpleServiceProxyOn, reloadHaproxy, setWwwRedirection } from '$lib/haproxy';
|
||||
import { getDomain } from '$lib/components/common';
|
||||
import { PrismaErrorHandler } from '$lib/database';
|
||||
|
||||
@@ -117,6 +117,7 @@ export const post: RequestHandler = async (event) => {
|
||||
if (isHttps) {
|
||||
await letsEncrypt({ domain, id });
|
||||
}
|
||||
await setWwwRedirection(fqdn);
|
||||
await reloadHaproxy(destinationDocker.engine);
|
||||
return {
|
||||
status: 200
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -9,6 +9,8 @@ import {
|
||||
forceSSLOffApplication,
|
||||
forceSSLOnApplication,
|
||||
reloadHaproxy,
|
||||
removeWwwRedirection,
|
||||
setWwwRedirection,
|
||||
startCoolifyProxy
|
||||
} from '$lib/haproxy';
|
||||
import { letsEncrypt } from '$lib/letsencrypt';
|
||||
@@ -45,9 +47,10 @@ export const del: RequestHandler = async (event) => {
|
||||
const { fqdn } = await event.request.json();
|
||||
|
||||
try {
|
||||
await db.prisma.setting.update({ where: { fqdn }, data: { fqdn: null } });
|
||||
const domain = getDomain(fqdn);
|
||||
await configureCoolifyProxyOff({ domain });
|
||||
await db.prisma.setting.update({ where: { fqdn }, data: { fqdn: null } });
|
||||
await configureCoolifyProxyOff(fqdn);
|
||||
await removeWwwRedirection(domain);
|
||||
return {
|
||||
status: 201
|
||||
};
|
||||
@@ -77,9 +80,10 @@ export const post: RequestHandler = async (event) => {
|
||||
await db.prisma.setting.update({ where: { id }, data: { isRegistrationEnabled } });
|
||||
}
|
||||
if (oldFqdn && oldFqdn !== fqdn) {
|
||||
const oldDomain = getDomain(oldFqdn);
|
||||
if (oldFqdn) {
|
||||
await configureCoolifyProxyOff({ domain: oldDomain });
|
||||
const oldDomain = getDomain(oldFqdn);
|
||||
await configureCoolifyProxyOff(oldFqdn);
|
||||
await removeWwwRedirection(oldDomain);
|
||||
}
|
||||
}
|
||||
if (fqdn) {
|
||||
@@ -88,7 +92,8 @@ export const post: RequestHandler = async (event) => {
|
||||
const domain = getDomain(fqdn);
|
||||
const isHttps = fqdn.startsWith('https://');
|
||||
if (domain) {
|
||||
await configureCoolifyProxyOn({ domain });
|
||||
await configureCoolifyProxyOn(fqdn);
|
||||
await setWwwRedirection(fqdn);
|
||||
if (isHttps && !dev) {
|
||||
await letsEncrypt({ domain, isCoolify: true });
|
||||
await forceSSLOnApplication({ domain });
|
||||
|
||||
@@ -106,7 +106,7 @@
|
||||
<div class="px-4 sm:px-6">
|
||||
<div class="flex space-x-4 py-4 px-4">
|
||||
<p class="pt-2 text-base font-bold text-stone-100">Domain (FQDN)</p>
|
||||
<div class="justify-center text-center">
|
||||
<div class="justify-center">
|
||||
<input
|
||||
bind:value={fqdn}
|
||||
readonly={!$session.isAdmin || isFqdnSet}
|
||||
@@ -118,7 +118,7 @@
|
||||
required
|
||||
/>
|
||||
<Explainer
|
||||
text="Set the fully qualified domain name for your Coolify instance. <br>If you specify <span class='text-green-600 font-bold'>https</span>, it will be accessible only over https. <br>SSL certificate will be generated for you."
|
||||
text="If you specify <span class='text-green-600 font-bold'>https</span>, Coolify will be accessible only over https. SSL certificate will be generated for you.<br>If you specify <span class='text-green-600 font-bold'>www</span>, Coolify will be redirected (302) from non-www and vice versa."
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -71,8 +71,6 @@
|
||||
window.open(`${source.htmlUrl}/groups/${payload.groupName}/-/settings/applications`);
|
||||
break;
|
||||
case 'instance':
|
||||
// TODO: This is not correct
|
||||
// window.location.assign(`${source.htmlUrl}/-/profile/applications`);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
|
||||
@@ -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
BIN
static/minio.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 7.9 KiB |
BIN
static/nocodb.png
Normal file
BIN
static/nocodb.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 13 KiB |
BIN
static/plausible.png
Normal file
BIN
static/plausible.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 4.2 KiB |
BIN
static/poppins-v19-latin-ext_latin_devanagari-500.woff
Normal file
BIN
static/poppins-v19-latin-ext_latin_devanagari-500.woff
Normal file
Binary file not shown.
BIN
static/poppins-v19-latin-ext_latin_devanagari-500.woff2
Normal file
BIN
static/poppins-v19-latin-ext_latin_devanagari-500.woff2
Normal file
Binary file not shown.
BIN
static/poppins-v19-latin-ext_latin_devanagari-regular.woff
Normal file
BIN
static/poppins-v19-latin-ext_latin_devanagari-regular.woff
Normal file
Binary file not shown.
BIN
static/poppins-v19-latin-ext_latin_devanagari-regular.woff2
Normal file
BIN
static/poppins-v19-latin-ext_latin_devanagari-regular.woff2
Normal file
Binary file not shown.
Reference in New Issue
Block a user