Compare commits

...

15 Commits

Author SHA1 Message Date
Andras Bacsai
88aa620cb4 Merge pull request #121 from coollabsio/next
v2.0.7
2022-02-13 23:45:49 +01:00
Andras Bacsai
70d3448110 fix: New secret should have default values 2022-02-13 23:31:52 +01:00
Andras Bacsai
09a1a406a6 Bump version 2022-02-13 23:19:08 +01:00
Andras Bacsai
40939d0b7f fix: Build secrets should be visible in runtime 2022-02-13 23:17:50 +01:00
Andras Bacsai
aec1d184c8 feat: www <-> non-www redirection 2022-02-13 22:56:37 +01:00
Andras Bacsai
69d3cb5dd8 feat: www <-> non-www redirection for apps 2022-02-13 15:04:00 +01:00
Andras Bacsai
3deff162bb code cleanup 2022-02-13 13:46:51 +01:00
Andras Bacsai
4ed54568d3 fix: package.json 2022-02-12 15:34:55 +01:00
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
33 changed files with 386 additions and 376 deletions

View File

@@ -2,6 +2,66 @@
An open-source & self-hostable Heroku / Netlify alternative. 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 ## Support
- Twitter: [@andrasbacsai](https://twitter.com/andrasbacsai) - 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) [See the Roadmap here](https://github.com/coollabsio/coolify/projects/1)
(Will be updated soon!)
## License ## 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. 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,7 +1,7 @@
{ {
"name": "coolify", "name": "coolify",
"description": "An open-source & self-hostable Heroku / Netlify alternative.", "description": "An open-source & self-hostable Heroku / Netlify alternative.",
"version": "2.0.5", "version": "2.0.7",
"license": "AGPL-3.0", "license": "AGPL-3.0",
"scripts": { "scripts": {
"dev": "docker compose -f docker-compose-dev.yaml up -d && NODE_ENV=development svelte-kit dev --host 0.0.0.0", "dev": "docker compose -f docker-compose-dev.yaml up -d && NODE_ENV=development svelte-kit dev --host 0.0.0.0",
@@ -17,7 +17,8 @@
"db:push": "prisma db push && prisma generate", "db:push": "prisma db push && prisma generate",
"db:seed": "prisma db seed", "db:seed": "prisma db seed",
"release:staging": "cross-var docker build -t coollabsio/coolify:$npm_package_version . && docker push coollabsio/coolify:$npm_package_version", "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: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": "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: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", "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",

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 { model Secret {
id String @id @default(cuid()) id String @id @default(cuid())
name String @unique name String
value String value String
isBuildSecret Boolean @default(false) isBuildSecret Boolean @default(false)
createdAt DateTime @default(now()) createdAt DateTime @default(now())
updatedAt DateTime @updatedAt updatedAt DateTime @updatedAt
application Application @relation(fields: [applicationId], references: [id]) application Application @relation(fields: [applicationId], references: [id])
applicationId String applicationId String
@@unique([name, applicationId])
} }
model BuildLog { model BuildLog {

View File

@@ -67,7 +67,6 @@ export const getTeam = (event) => {
}; };
export const getUserDetails = async (event, isAdminRequired = true) => { export const getUserDetails = async (event, isAdminRequired = true) => {
// try {
const teamId = getTeam(event); const teamId = getTeam(event);
const userId = event.locals.session.data.uid || null; const userId = event.locals.session.data.uid || null;
const { permission = 'read' } = await db.prisma.permission.findFirst({ const { permission = 'read' } = await db.prisma.permission.findFirst({
@@ -91,18 +90,6 @@ export const getUserDetails = async (event, isAdminRequired = true) => {
} }
return payload; 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) { export function getEngine(engine) {

View File

@@ -20,9 +20,6 @@
function showActions(value) { function showActions(value) {
actionsShow = value; actionsShow = value;
// if (value === false) {
// showPassword = false;
// }
} }
function copyToClipboard() { function copyToClipboard() {
if (isHttps && navigator.clipboard) { if (isHttps && navigator.clipboard) {

View File

@@ -6,7 +6,6 @@
export let description; export let description;
export let isPadding = true; export let isPadding = true;
export let disabled = false; export let disabled = false;
// export let disabledReason = '';
</script> </script>
<li class="flex items-center py-4"> <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> <p class="text-xs font-bold text-stone-100 md:text-base">{title}</p>
<Explainer text={description} /> <Explainer text={description} />
</div> </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 <div
type="button" type="button"
on:click on:click

View File

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

View File

@@ -109,22 +109,4 @@ export async function setDestinationSettings({ engine, isCoolifyProxyUsed }) {
where: { engine }, where: { engine },
data: { isCoolifyProxyUsed } 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.'
// }
// }
// }
// }
} }

View File

@@ -28,12 +28,12 @@ export async function login({ email, password }) {
console.log('Network created'); console.log('Network created');
}) })
.catch(() => { .catch(() => {
console.log('Network already exists'); console.log('Network already exists.');
}); });
startCoolifyProxy('/var/run/docker.sock') startCoolifyProxy('/var/run/docker.sock')
.then(() => { .then(() => {
console.log('Coolify Proxy Started'); console.log('Coolify Proxy started.');
}) })
.catch((err) => { .catch((err) => {
console.log(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 { return {
status: 200, status: 200,
headers: { headers: {

View File

@@ -1,5 +1,5 @@
import { dev } from '$app/env'; import { dev } from '$app/env';
import { asyncExecShell, getEngine } from '$lib/common'; import { asyncExecShell, getDomain, getEngine } from '$lib/common';
import got from 'got'; import got from 'got';
import * as db from '$lib/database'; import * as db from '$lib/database';
import { letsEncrypt } from '$lib/letsencrypt'; import { letsEncrypt } from '$lib/letsencrypt';
@@ -51,11 +51,11 @@ export async function completeTransaction(transactionId) {
export async function removeProxyConfiguration({ domain }) { export async function removeProxyConfiguration({ domain }) {
const haproxy = await haproxyInstance(); const haproxy = await haproxyInstance();
const transactionId = await getNextTransactionId();
const backendFound = await haproxy const backendFound = await haproxy
.get(`v2/services/haproxy/configuration/backends/${domain}`) .get(`v2/services/haproxy/configuration/backends/${domain}`)
.json(); .json();
if (backendFound) { if (backendFound) {
const transactionId = await getNextTransactionId();
await haproxy await haproxy
.delete(`v2/services/haproxy/configuration/backends/${domain}`, { .delete(`v2/services/haproxy/configuration/backends/${domain}`, {
searchParams: { searchParams: {
@@ -65,6 +65,7 @@ export async function removeProxyConfiguration({ domain }) {
.json(); .json();
await completeTransaction(transactionId); await completeTransaction(transactionId);
} }
await removeWwwRedirection(domain);
} }
export async function forceSSLOffApplication({ domain }) { export async function forceSSLOffApplication({ domain }) {
if (!dev) { if (!dev) {
@@ -124,7 +125,9 @@ export async function forceSSLOnApplication({ domain }) {
.json(); .json();
let nextRule = 0; let nextRule = 0;
if (rules.data.length > 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; if (rule) return;
nextRule = rules.data[rules.data.length - 1].index + 1; nextRule = rules.data[rules.data.length - 1].index + 1;
} }
@@ -138,7 +141,7 @@ export async function forceSSLOnApplication({ domain }) {
json: { json: {
index: nextRule, index: nextRule,
cond: 'if', cond: 'if',
cond_test: `{ hdr(Host) -i ${domain} } !{ ssl_fc }`, cond_test: `{ hdr(host) -i ${domain} } !{ ssl_fc }`,
type: 'redirect', type: 'redirect',
redir_type: 'scheme', redir_type: 'scheme',
redir_value: 'https', redir_value: 'https',
@@ -188,94 +191,7 @@ export async function deleteProxy({ id }) {
await completeTransaction(transactionId); 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) { export async function reloadHaproxy(engine) {
const host = getEngine(engine); const host = getEngine(engine);
return await asyncExecShell(`DOCKER_HOST=${host} docker exec coolify-haproxy kill -HUP 1`); return await asyncExecShell(`DOCKER_HOST=${host} docker exec coolify-haproxy kill -HUP 1`);
@@ -364,7 +280,8 @@ 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(); const haproxy = await haproxyInstance();
try { try {
await checkHAProxy(haproxy); await checkHAProxy(haproxy);
@@ -386,6 +303,7 @@ export async function configureCoolifyProxyOff({ domain }) {
if (!dev) { if (!dev) {
await forceSSLOffApplication({ domain }); await forceSSLOffApplication({ domain });
} }
await setWwwRedirection(fqdn);
} catch (error) { } catch (error) {
throw error?.response?.body || error; throw error?.response?.body || error;
} }
@@ -398,7 +316,8 @@ export async function checkHAProxy(haproxy) {
throw 'HAProxy is not running, but it should be!'; 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(); const haproxy = await haproxyInstance();
try { try {
await checkHAProxy(haproxy); await checkHAProxy(haproxy);
@@ -605,7 +524,15 @@ export async function configureSimpleServiceProxyOn({ id, domain, port }) {
await checkHAProxy(haproxy); await checkHAProxy(haproxy);
try { try {
await haproxy.get(`v2/services/haproxy/configuration/backends/${domain}`).json(); 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) {} } catch (error) {}
try { try {
const transactionId = await getNextTransactionId(); const transactionId = await getNextTransactionId();
@@ -631,6 +558,12 @@ export async function configureSimpleServiceProxyOn({ id, domain, port }) {
port: port port: port
} }
}); });
console.log({
address: id,
check: 'enabled',
name: id,
port: port
});
await completeTransaction(transactionId); await completeTransaction(transactionId);
} catch (error) { } catch (error) {
console.log(error); console.log(error);
@@ -657,5 +590,98 @@ export async function configureSimpleServiceProxyOff({ domain }) {
.json(); .json();
await completeTransaction(transactionId); await completeTransaction(transactionId);
} catch (error) {} } catch (error) {}
await removeWwwRedirection(domain);
return; 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);
}
}

View File

@@ -4,7 +4,7 @@ import * as buildpacks from '../buildPacks';
import * as importers from '../importers'; import * as importers from '../importers';
import { dockerInstance } from '../docker'; import { dockerInstance } from '../docker';
import { asyncExecShell, createDirectories, getDomain, getEngine, saveBuildLog } from '../common'; 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 * as db from '$lib/database';
import { decrypt } from '$lib/crypto'; import { decrypt } from '$lib/crypto';
import { sentry } from '$lib/common'; import { sentry } from '$lib/common';
@@ -64,10 +64,6 @@ export default async function (job) {
if (destinationDockerId) { if (destinationDockerId) {
destinationType = 'docker'; destinationType = 'docker';
} }
// Not implemented yet
// if (destinationKubernetesId) {
// destinationType = 'kubernetes'
// }
if (destinationType === 'docker') { if (destinationType === 'docker') {
const docker = dockerInstance({ destinationDocker }); const docker = dockerInstance({ destinationDocker });
@@ -209,9 +205,7 @@ export default async function (job) {
const envs = []; const envs = [];
if (secrets.length > 0) { if (secrets.length > 0) {
secrets.forEach((secret) => { 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')); await fs.writeFile(`${workdir}/.env`, envs.join('\n'));
@@ -252,6 +246,7 @@ export default async function (job) {
saveBuildLog({ line: 'Proxy configuration started!', buildId, applicationId }); saveBuildLog({ line: 'Proxy configuration started!', buildId, applicationId });
await configureProxyForApplication({ domain, imageId, applicationId, port }); await configureProxyForApplication({ domain, imageId, applicationId, port });
if (isHttps) await letsEncrypt({ domain, id: applicationId }); if (isHttps) await letsEncrypt({ domain, id: applicationId });
await setWwwRedirection(fqdn);
await reloadHaproxy(destinationDocker.engine); await reloadHaproxy(destinationDocker.engine);
saveBuildLog({ line: 'Proxy configuration successful!', buildId, applicationId }); saveBuildLog({ line: 'Proxy configuration successful!', buildId, applicationId });
} else { } else {

View File

@@ -144,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 buildLogQueueName = dev ? cuid() : 'log_queue';
const buildLogQueue = new Queue(buildLogQueueName, connectionOptions); const buildLogQueue = new Queue(buildLogQueueName, connectionOptions);
const buildLogWorker = new Worker(buildLogQueueName, async (job) => await logger(job), { const buildLogWorker = new Worker(buildLogQueueName, async (job) => await logger(job), {

View File

@@ -7,6 +7,7 @@ import {
configureProxyForApplication, configureProxyForApplication,
forceSSLOnApplication, forceSSLOnApplication,
reloadHaproxy, reloadHaproxy,
setWwwRedirection,
startCoolifyProxy startCoolifyProxy
} from '$lib/haproxy'; } from '$lib/haproxy';
import * as db from '$lib/database'; import * as db from '$lib/database';
@@ -40,6 +41,7 @@ export default async function () {
}); });
const isHttps = fqdn.startsWith('https://'); const isHttps = fqdn.startsWith('https://');
if (isHttps) await forceSSLOnApplication({ domain }); 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'); const found = await checkContainer('/var/run/docker.sock', 'coolify-haproxy');
if (!found) await startCoolifyProxy('/var/run/docker.sock'); if (!found) await startCoolifyProxy('/var/run/docker.sock');
await configureCoolifyProxyOn({ domain }); await configureCoolifyProxyOn({ domain });
await setWwwRedirection(fqdn);
const isHttps = fqdn.startsWith('https://'); const isHttps = fqdn.startsWith('https://');
if (isHttps) await forceSSLOnApplication({ domain }); if (isHttps) await forceSSLOnApplication({ domain });
} }

View File

@@ -124,25 +124,6 @@
updateStatus.success = false; updateStatus.success = false;
updateStatus.loading = 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> </script>

View File

@@ -142,14 +142,6 @@
return errorNotification('Branch already configured'); 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) { async function checkSSHKey(sshkeyUrl) {
try { try {
return await post(sshkeyUrl, {}); return await post(sshkeyUrl, {});

View File

@@ -266,7 +266,7 @@
required required
/> />
<Explainer <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>
</div> </div>

View File

@@ -6,14 +6,20 @@
import { page } from '$app/stores'; import { page } from '$app/stores';
import { del, post } from '$lib/api'; import { del, post } from '$lib/api';
import { errorNotification } from '$lib/form'; import { errorNotification } from '$lib/form';
import { createEventDispatcher } from 'svelte';
const dispatch = createEventDispatcher();
if (name) value = 'ENCRYPTED';
const { id } = $page.params; const { id } = $page.params;
async function removeSecret() { async function removeSecret() {
try { try {
await del(`/applications/${id}/secrets.json`, { name }); await del(`/applications/${id}/secrets.json`, { name });
return window.location.reload(); dispatch('refresh');
if (isNewSecret) {
name = '';
value = '';
isBuildSecret = false;
}
} catch ({ error }) { } catch ({ error }) {
return errorNotification(error); return errorNotification(error);
} }
@@ -21,7 +27,12 @@
async function saveSecret() { async function saveSecret() {
try { try {
await post(`/applications/${id}/secrets.json`, { name, value, isBuildSecret }); await post(`/applications/${id}/secrets.json`, { name, value, isBuildSecret });
return window.location.reload(); dispatch('refresh');
if (isNewSecret) {
name = '';
value = '';
isBuildSecret = false;
}
} catch ({ error }) { } catch ({ error }) {
return errorNotification(error); return errorNotification(error);
} }
@@ -33,39 +44,29 @@
} }
</script> </script>
<div class="mx-auto max-w-3xl pt-4"> <td class="whitespace-nowrap px-6 py-2 text-sm font-medium text-white">
<div class="flex space-x-2">
<div class="grid grid-flow-row">
<label for="secretName">Name</label>
<input <input
id="secretName" id="secretName"
bind:value={name} bind:value={name}
placeholder="EXAMPLE_VARIABLE" placeholder="EXAMPLE_VARIABLE"
class="w-64 border-2 border-transparent" class="-mx-2 w-64 border-2 border-transparent"
readonly={!isNewSecret} readonly={!isNewSecret}
class:hover:bg-coolgray-200={!isNewSecret} class:bg-transparent={!isNewSecret}
class:cursor-not-allowed={!isNewSecret} class:cursor-not-allowed={!isNewSecret}
/> />
</div> </td>
<div class="grid grid-flow-row"> <td class="whitespace-nowrap px-6 py-2 text-sm font-medium text-white">
<label for="secretValue">Value (will be encrypted)</label>
<input <input
id="secretValue" id="secretValue"
bind:value bind:value
placeholder="J$#@UIO%HO#$U%H" placeholder="J$#@UIO%HO#$U%H"
class="w-64 border-2 border-transparent" class="-mx-2 w-64 border-2 border-transparent"
class:hover:bg-coolgray-200={!isNewSecret} class:bg-transparent={!isNewSecret}
class:cursor-not-allowed={!isNewSecret} class:cursor-not-allowed={!isNewSecret}
readonly={!isNewSecret} readonly={!isNewSecret}
/> />
</div> </td>
<td class="whitespace-nowrap px-6 py-2 text-center text-sm font-medium text-white">
<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 <div
type="button" type="button"
on:click={setSecretValue} on:click={setSecretValue}
@@ -73,6 +74,7 @@
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="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-green-600={isBuildSecret}
class:bg-stone-700={!isBuildSecret} class:bg-stone-700={!isBuildSecret}
class:opacity-50={!isNewSecret}
class:cursor-not-allowed={!isNewSecret} class:cursor-not-allowed={!isNewSecret}
class:cursor-pointer={isNewSecret} class:cursor-pointer={isNewSecret}
> >
@@ -104,11 +106,7 @@
class:opacity-100={isBuildSecret} class:opacity-100={isBuildSecret}
class:opacity-0={!isBuildSecret} class:opacity-0={!isBuildSecret}
> >
<svg <svg class="h-3 w-3 bg-white text-green-600" fill="currentColor" viewBox="0 0 12 12">
class="h-3 w-3 bg-white text-green-600"
fill="currentColor"
viewBox="0 0 12 12"
>
<path <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" 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"
/> />
@@ -116,18 +114,15 @@
</span> </span>
</span> </span>
</div> </div>
</li> </td>
</ul> <td class="whitespace-nowrap px-6 py-2 text-sm font-medium text-white">
</div>
</div>
{#if isNewSecret} {#if isNewSecret}
<div class="mt-6"> <div class="flex items-center justify-center">
<button class="w-20 bg-green-600 hover:bg-green-500" on:click={saveSecret}>Add</button> <button class="w-24 bg-green-600 hover:bg-green-500" on:click={saveSecret}>Add</button>
</div> </div>
{:else} {:else}
<div class="mt-6"> <div class="flex justify-center items-end">
<button class="w-20 bg-red-600 hover:bg-red-500" on:click={removeSecret}>Remove</button> <button class="w-24 bg-red-600 hover:bg-red-500" on:click={removeSecret}>Remove</button>
</div> </div>
{/if} {/if}
</div> </td>
</div>

View File

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

View File

@@ -24,6 +24,15 @@
export let application; export let application;
import Secret from './_Secret.svelte'; import Secret from './_Secret.svelte';
import { getDomain } from '$lib/components/common'; 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> </script>
<div class="flex space-x-1 p-6 font-bold"> <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> Secrets for <a href={application.fqdn} target="_blank">{getDomain(application.fqdn)}</a>
</div> </div>
</div> </div>
<div class="mx-auto max-w-4xl px-6"> <div class="mx-auto max-w-6xl rounded-xl px-6 pt-4">
<div class="flex-col justify-start space-y-1"> <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} {#each secrets as secret}
<Secret name={secret.name} value={secret.value} isBuildSecret={secret.isBuildSecret} /> {#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} {/each}
<Secret isNewSecret /> <tr>
</div> <Secret isNewSecret on:refresh={refreshSecrets} />
</tr>
</tbody>
</table>
</div> </div>

View File

@@ -25,9 +25,7 @@ export const get: RequestHandler = async (event) => {
state = 'running'; state = 'running';
} }
} catch (error) { } catch (error) {
// if (!error.stderr.includes('No such object')) { //
// console.log(error)
// }
} }
} }
const configuration = generateDatabaseConfiguration(database); const configuration = generateDatabaseConfiguration(database);

View File

@@ -110,25 +110,9 @@
required required
/> />
<Explainer <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> </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> </div>
{#if service.type === 'plausibleanalytics'} {#if service.type === 'plausibleanalytics'}
<PlausibleAnalytics bind:service {readOnly} /> <PlausibleAnalytics bind:service {readOnly} />

View File

@@ -46,24 +46,3 @@ export const get: RequestHandler = async (event) => {
return PrismaErrorHandler(error); 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
// }
// }

View File

@@ -7,6 +7,7 @@ import { letsEncrypt } from '$lib/letsencrypt';
import { import {
configureSimpleServiceProxyOn, configureSimpleServiceProxyOn,
reloadHaproxy, reloadHaproxy,
setWwwRedirection,
startHttpProxy, startHttpProxy,
startTcpProxy startTcpProxy
} from '$lib/haproxy'; } from '$lib/haproxy';
@@ -86,13 +87,13 @@ export const post: RequestHandler = async (event) => {
try { try {
await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} up -d`); await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} up -d`);
await configureSimpleServiceProxyOn({ id, domain, port: consolePort }); await configureSimpleServiceProxyOn({ id, domain, port: consolePort });
await db.updateMinioService({ id, publicPort }); await db.updateMinioService({ id, publicPort });
await startHttpProxy(destinationDocker, id, publicPort, apiPort); await startHttpProxy(destinationDocker, id, publicPort, apiPort);
if (isHttps) { if (isHttps) {
await letsEncrypt({ domain, id }); await letsEncrypt({ domain, id });
} }
await setWwwRedirection(fqdn);
await reloadHaproxy(destinationDocker.engine); await reloadHaproxy(destinationDocker.engine);
return { return {
status: 200 status: 200

View File

@@ -4,7 +4,7 @@ import { promises as fs } from 'fs';
import yaml from 'js-yaml'; import yaml from 'js-yaml';
import type { RequestHandler } from '@sveltejs/kit'; import type { RequestHandler } from '@sveltejs/kit';
import { letsEncrypt } from '$lib/letsencrypt'; 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 { getDomain } from '$lib/components/common';
import { PrismaErrorHandler } from '$lib/database'; import { PrismaErrorHandler } from '$lib/database';
@@ -52,6 +52,7 @@ export const post: RequestHandler = async (event) => {
if (isHttps) { if (isHttps) {
await letsEncrypt({ domain, id }); await letsEncrypt({ domain, id });
} }
await setWwwRedirection(fqdn);
await reloadHaproxy(destinationDocker.engine); await reloadHaproxy(destinationDocker.engine);
return { return {
status: 200 status: 200

View File

@@ -4,7 +4,7 @@ import { promises as fs } from 'fs';
import yaml from 'js-yaml'; import yaml from 'js-yaml';
import type { RequestHandler } from '@sveltejs/kit'; import type { RequestHandler } from '@sveltejs/kit';
import { letsEncrypt } from '$lib/letsencrypt'; 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 { getDomain } from '$lib/components/common';
import { PrismaErrorHandler } from '$lib/database'; import { PrismaErrorHandler } from '$lib/database';
@@ -185,6 +185,7 @@ COPY ./init-db.sh /docker-entrypoint-initdb.d/init-db.sh`;
if (isHttps) { if (isHttps) {
await letsEncrypt({ domain, id }); await letsEncrypt({ domain, id });
} }
await setWwwRedirection(fqdn);
await reloadHaproxy(destinationDocker.engine); await reloadHaproxy(destinationDocker.engine);
return { return {
status: 200 status: 200

View File

@@ -4,7 +4,7 @@ import { promises as fs } from 'fs';
import yaml from 'js-yaml'; import yaml from 'js-yaml';
import type { RequestHandler } from '@sveltejs/kit'; import type { RequestHandler } from '@sveltejs/kit';
import { letsEncrypt } from '$lib/letsencrypt'; 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 { getDomain } from '$lib/components/common';
import { getServiceImage, PrismaErrorHandler } from '$lib/database'; import { getServiceImage, PrismaErrorHandler } from '$lib/database';
@@ -70,6 +70,7 @@ export const post: RequestHandler = async (event) => {
if (isHttps) { if (isHttps) {
await letsEncrypt({ domain, id }); await letsEncrypt({ domain, id });
} }
await setWwwRedirection(fqdn);
await reloadHaproxy(destinationDocker.engine); await reloadHaproxy(destinationDocker.engine);
return { return {
status: 200 status: 200

View File

@@ -4,7 +4,7 @@ import { promises as fs } from 'fs';
import yaml from 'js-yaml'; import yaml from 'js-yaml';
import type { RequestHandler } from '@sveltejs/kit'; import type { RequestHandler } from '@sveltejs/kit';
import { letsEncrypt } from '$lib/letsencrypt'; 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 { getDomain } from '$lib/components/common';
import { PrismaErrorHandler } from '$lib/database'; import { PrismaErrorHandler } from '$lib/database';
@@ -80,6 +80,7 @@ export const post: RequestHandler = async (event) => {
if (isHttps) { if (isHttps) {
await letsEncrypt({ domain, id }); await letsEncrypt({ domain, id });
} }
await setWwwRedirection(fqdn);
await reloadHaproxy(destinationDocker.engine); await reloadHaproxy(destinationDocker.engine);
return { return {
status: 200 status: 200

View File

@@ -4,7 +4,7 @@ import { promises as fs } from 'fs';
import yaml from 'js-yaml'; import yaml from 'js-yaml';
import type { RequestHandler } from '@sveltejs/kit'; import type { RequestHandler } from '@sveltejs/kit';
import { letsEncrypt } from '$lib/letsencrypt'; 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 { getDomain } from '$lib/components/common';
import { PrismaErrorHandler } from '$lib/database'; import { PrismaErrorHandler } from '$lib/database';
@@ -117,6 +117,7 @@ export const post: RequestHandler = async (event) => {
if (isHttps) { if (isHttps) {
await letsEncrypt({ domain, id }); await letsEncrypt({ domain, id });
} }
await setWwwRedirection(fqdn);
await reloadHaproxy(destinationDocker.engine); await reloadHaproxy(destinationDocker.engine);
return { return {
status: 200 status: 200

View File

@@ -9,6 +9,8 @@ import {
forceSSLOffApplication, forceSSLOffApplication,
forceSSLOnApplication, forceSSLOnApplication,
reloadHaproxy, reloadHaproxy,
removeWwwRedirection,
setWwwRedirection,
startCoolifyProxy startCoolifyProxy
} from '$lib/haproxy'; } from '$lib/haproxy';
import { letsEncrypt } from '$lib/letsencrypt'; import { letsEncrypt } from '$lib/letsencrypt';
@@ -45,9 +47,10 @@ export const del: RequestHandler = async (event) => {
const { fqdn } = await event.request.json(); const { fqdn } = await event.request.json();
try { try {
await db.prisma.setting.update({ where: { fqdn }, data: { fqdn: null } });
const domain = getDomain(fqdn); const domain = getDomain(fqdn);
await configureCoolifyProxyOff({ domain }); await db.prisma.setting.update({ where: { fqdn }, data: { fqdn: null } });
await configureCoolifyProxyOff(fqdn);
await removeWwwRedirection(domain);
return { return {
status: 201 status: 201
}; };
@@ -77,9 +80,10 @@ export const post: RequestHandler = async (event) => {
await db.prisma.setting.update({ where: { id }, data: { isRegistrationEnabled } }); await db.prisma.setting.update({ where: { id }, data: { isRegistrationEnabled } });
} }
if (oldFqdn && oldFqdn !== fqdn) { if (oldFqdn && oldFqdn !== fqdn) {
const oldDomain = getDomain(oldFqdn);
if (oldFqdn) { if (oldFqdn) {
await configureCoolifyProxyOff({ domain: oldDomain }); const oldDomain = getDomain(oldFqdn);
await configureCoolifyProxyOff(oldFqdn);
await removeWwwRedirection(oldDomain);
} }
} }
if (fqdn) { if (fqdn) {
@@ -88,7 +92,8 @@ export const post: RequestHandler = async (event) => {
const domain = getDomain(fqdn); const domain = getDomain(fqdn);
const isHttps = fqdn.startsWith('https://'); const isHttps = fqdn.startsWith('https://');
if (domain) { if (domain) {
await configureCoolifyProxyOn({ domain }); await configureCoolifyProxyOn(fqdn);
await setWwwRedirection(fqdn);
if (isHttps && !dev) { if (isHttps && !dev) {
await letsEncrypt({ domain, isCoolify: true }); await letsEncrypt({ domain, isCoolify: true });
await forceSSLOnApplication({ domain }); await forceSSLOnApplication({ domain });

View File

@@ -106,7 +106,7 @@
<div class="px-4 sm:px-6"> <div class="px-4 sm:px-6">
<div class="flex space-x-4 py-4 px-4"> <div class="flex space-x-4 py-4 px-4">
<p class="pt-2 text-base font-bold text-stone-100">Domain (FQDN)</p> <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 <input
bind:value={fqdn} bind:value={fqdn}
readonly={!$session.isAdmin || isFqdnSet} readonly={!$session.isAdmin || isFqdnSet}
@@ -118,7 +118,7 @@
required required
/> />
<Explainer <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>
</div> </div>

View File

@@ -71,8 +71,6 @@
window.open(`${source.htmlUrl}/groups/${payload.groupName}/-/settings/applications`); window.open(`${source.htmlUrl}/groups/${payload.groupName}/-/settings/applications`);
break; break;
case 'instance': case 'instance':
// TODO: This is not correct
// window.location.assign(`${source.htmlUrl}/-/profile/applications`);
break; break;
default: default:
break; break;

View File

@@ -35,7 +35,7 @@ main,
} }
input { 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 { 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; @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;
@@ -50,7 +50,7 @@ label {
} }
button { 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 { a {