This commit is contained in:
Andras Bacsai
2021-05-16 21:54:44 +02:00
committed by GitHub
parent 23a4ebb74a
commit adcd68c1ab
68 changed files with 2466 additions and 1194 deletions

View File

@@ -1,4 +1,3 @@
# Coolify # Coolify
An open-source, hassle-free, self-hostable Heroku & Netlify alternative. An open-source, hassle-free, self-hostable Heroku & Netlify alternative.
@@ -7,7 +6,6 @@ An open-source, hassle-free, self-hostable Heroku & Netlify alternative.
[Small video](https://cdn.coollabs.io/assets/coolify/video/coolify.webm) [Small video](https://cdn.coollabs.io/assets/coolify/video/coolify.webm)
## Installation ## Installation
Installation is automated with the following command: Installation is automated with the following command:
@@ -16,19 +14,21 @@ Installation is automated with the following command:
/bin/bash -c "$(curl -fsSL https://get.coollabs.io/coolify/install.sh)" /bin/bash -c "$(curl -fsSL https://get.coollabs.io/coolify/install.sh)"
``` ```
## Features ## Features
You can deploy any of the following applications, databases and services easily. You can deploy any of the following applications, databases and services easily.
(constantly growing lists) (constantly growing lists)
### Applications ### Applications
With Github integration With Github integration
- Static sites - Static sites
- NodeJS - NodeJS
- VueJS - VueJS
- NuxtJS - NuxtJS
- NextJS
- React/Preact - React/Preact
- NextJS - NextJS
- Gatsby - Gatsby
@@ -38,14 +38,16 @@ With Github integration
- or any custom dockerfile - or any custom dockerfile
### Databases ### Databases
- MongoDB - MongoDB
- MySQL - MySQL
- PostgreSQL - PostgreSQL
- CouchDB - CouchDB
- Redis
### Services ### Services
- [Plausible Analytics](https://plausible.io)
- [Plausible Analytics](https://plausible.io)
## Support ## Support
@@ -55,10 +57,9 @@ With Github integration
- Discord: [Invitation](https://discord.com/invite/bvS3WhR) - Discord: [Invitation](https://discord.com/invite/bvS3WhR)
## Roadmap ## Roadmap
[See the Roadmap here](https://github.com/coollabsio/coolify/projects/1) [See the Roadmap here](https://github.com/coollabsio/coolify/projects/1)
## 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, hassle-free, self-hostable Heroku & Netlify alternative.", "description": "An open-source, hassle-free, self-hostable Heroku & Netlify alternative.",
"version": "1.0.12", "version": "1.0.13",
"license": "AGPL-3.0", "license": "AGPL-3.0",
"scripts": { "scripts": {
"dev:docker:start": "docker-compose -f docker-compose-dev.yml up -d", "dev:docker:start": "docker-compose -f docker-compose-dev.yml up -d",
@@ -31,7 +31,7 @@
"prettier-plugin-svelte": "^2.3.0", "prettier-plugin-svelte": "^2.3.0",
"svelte": "^3.38.2", "svelte": "^3.38.2",
"svelte-preprocess": "^4.7.3", "svelte-preprocess": "^4.7.3",
"tailwindcss": "canary", "tailwindcss": "2.2.0-canary.8",
"tslib": "^2.2.0", "tslib": "^2.2.0",
"typescript": "^4.2.4", "typescript": "^4.2.4",
"vite": "^2.3.2" "vite": "^2.3.2"

2716
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -9,77 +9,96 @@
active: false, active: false,
number: 80 number: 80
}, },
build: true build: true,
start: false
}, },
nodejs: { nodejs: {
port: { port: {
active: true, active: true,
number: 3000 number: 3000
}, },
build: true build: true,
start: true
},
nestjs: {
port: {
active: true,
number: 3000
},
build: true,
start: true
}, },
vuejs: { vuejs: {
port: { port: {
active: false, active: false,
number: 80 number: 80
}, },
build: true build: true,
start: false
}, },
nuxtjs: { nuxtjs: {
port: { port: {
active: true, active: true,
number: 3000 number: 3000
}, },
build: true build: true,
start: true
}, },
react: { react: {
port: { port: {
active: false, active: false,
number: 80 number: 80
}, },
build: true build: true,
start: false
}, },
nextjs: { nextjs: {
port: { port: {
active: true, active: true,
number: 3000 number: 3000
}, },
build: true build: true,
start: true
}, },
gatsby: { gatsby: {
port: { port: {
active: true, active: true,
number: 3000 number: 3000
}, },
build: true build: true,
start: false
}, },
svelte: { svelte: {
port: { port: {
active: false, active: false,
number: 80 number: 80
}, },
build: true build: true,
start: false
}, },
php: { php: {
port: { port: {
active: false, active: false,
number: 80 number: 80
}, },
build: false build: false,
start: false
}, },
rust: { rust: {
port: { port: {
active: true, active: true,
number: 3000 number: 3000
}, },
build: false build: false,
start: false
}, },
docker: { docker: {
port: { port: {
active: true, active: true,
number: 3000 number: 3000
}, },
build: false build: false,
start: false
} }
}; };
function selectBuildPack(event) { function selectBuildPack(event) {
@@ -90,8 +109,9 @@
} }
} }
onMount(() => { onMount(() => {
if(!$application.publish.domain) domainInput.focus(); if (!$application.publish.domain) domainInput.focus();
}); });
</script> </script>
<div> <div>
@@ -178,6 +198,14 @@
> >
Rust Rust
</div> </div>
<div
class={$application.build.pack === 'nestjs'
? 'buildpack bg-red-500'
: 'buildpack hover:border-red-500'}
on:click={selectBuildPack}
>
NestJS
</div>
<div <div
class={$application.build.pack === 'docker' class={$application.build.pack === 'docker'
? 'buildpack bg-purple-500' ? 'buildpack bg-purple-500'
@@ -267,7 +295,7 @@
for="installCommand" for="installCommand"
class:text-warmGray-800={!buildpacks[$application.build.pack].build} class:text-warmGray-800={!buildpacks[$application.build.pack].build}
>Install Command <TooltipInfo >Install Command <TooltipInfo
label="Command to run for installing dependencies. eg: yarn install." label="Command to run for installing dependencies. eg: yarn install"
/> />
</label> </label>
@@ -300,6 +328,22 @@
bind:value={$application.build.command.build} bind:value={$application.build.command.build}
placeholder="eg: yarn build" placeholder="eg: yarn build"
/> />
<label
for="startCommand"
class:text-warmGray-800={!buildpacks[$application.build.pack].start}
>Start Command <TooltipInfo label="Command to start the application. eg: yarn start" /></label
>
<input
class="mb-6"
class:bg-warmGray-900={!buildpacks[$application.build.pack].start}
class:text-warmGray-900={!buildpacks[$application.build.pack].start}
class:placeholder-warmGray-800={!buildpacks[$application.build.pack].start}
class:hover:bg-warmGray-900={!buildpacks[$application.build.pack].start}
class:cursor-not-allowed={!buildpacks[$application.build.pack].start}
id="startcommand"
bind:value={$application.build.command.start}
placeholder="eg: yarn start"
/>
</div> </div>
</div> </div>
</div> </div>
@@ -309,4 +353,5 @@
.buildpack { .buildpack {
@apply px-6 py-2 mx-2 my-2 bg-warmGray-800 w-48 ease-in-out transform hover:scale-105 text-center rounded border-2 border-transparent border-dashed cursor-pointer transition duration-100; @apply px-6 py-2 mx-2 my-2 bg-warmGray-800 w-48 ease-in-out transform hover:scale-105 text-center rounded border-2 border-transparent border-dashed cursor-pointer transition duration-100;
} }
</style> </style>

View File

@@ -1,6 +1,6 @@
<script lang="ts"> <script lang="ts">
import { goto } from '$app/navigation'; import { goto } from '$app/navigation';
import { request } from '$lib/api/request'; import { request } from '$lib/request';
import { session } from '$app/stores'; import { session } from '$app/stores';
import { githubRepositories, application, githubInstallations } from '$store'; import { githubRepositories, application, githubInstallations } from '$store';

View File

@@ -3,7 +3,7 @@
import { onDestroy } from 'svelte'; import { onDestroy } from 'svelte';
import { toast } from '@zerodevx/svelte-toast'; import { toast } from '@zerodevx/svelte-toast';
import Tooltip from '$components/Tooltip.svelte'; import Tooltip from '$components/Tooltip.svelte';
import { request } from '$lib/api/request'; import { request } from '$lib/request';
import { page, session } from '$app/stores'; import { page, session } from '$app/stores';
import { goto } from '$app/navigation'; import { goto } from '$app/navigation';
import { browser } from '$app/env'; import { browser } from '$app/env';

View File

@@ -1,13 +1,13 @@
<script> <script>
import { toast } from '@zerodevx/svelte-toast'; import { toast } from '@zerodevx/svelte-toast';
import templates from '$lib/api/applications/templates'; import templates from '$lib/api/applications/packs/templates';
import { application, dashboard } from '$store'; import { application, dashboard } from '$store';
import General from '$components/Application/ActiveTab/General.svelte'; import General from '$components/Application/ActiveTab/General.svelte';
import Secrets from '$components/Application/ActiveTab/Secrets.svelte'; import Secrets from '$components/Application/ActiveTab/Secrets.svelte';
import Loading from '$components/Loading.svelte'; import Loading from '$components/Loading.svelte';
import { goto } from '$app/navigation'; import { goto } from '$app/navigation';
import { page, session } from '$app/stores'; import { page, session } from '$app/stores';
import { request } from '$lib/api/request'; import { request } from '$lib/request';
import { browser } from '$app/env'; import { browser } from '$app/env';
let activeTab = { let activeTab = {
@@ -80,10 +80,18 @@
if (checkPackageJSONContents(dep)) { if (checkPackageJSONContents(dep)) {
const config = templates[dep]; const config = templates[dep];
$application.build.pack = config.pack; $application.build.pack = config.pack;
if (config.installation) if (config.installation) {
$application.build.command.installation = config.installation; $application.build.command.installation = config.installation;
if (config.port) $application.publish.port = config.port; }
if (config.directory) $application.publish.directory = config.directory; if (config.start) {
$application.build.command.start = config.start;
}
if (config.port) {
$application.publish.port = config.port;
}
if (config.directory) {
$application.publish.directory = config.directory;
}
if (packageJsonContent.scripts.hasOwnProperty('build') && config.build) { if (packageJsonContent.scripts.hasOwnProperty('build') && config.build) {
$application.build.command.build = config.build; $application.build.command.build = config.build;

View File

@@ -5,16 +5,19 @@
import Postgresql from './SVGs/Postgresql.svelte'; import Postgresql from './SVGs/Postgresql.svelte';
import Mysql from './SVGs/Mysql.svelte'; import Mysql from './SVGs/Mysql.svelte';
import CouchDb from './SVGs/CouchDb.svelte'; import CouchDb from './SVGs/CouchDb.svelte';
import Redis from './SVGs/Redis.svelte';
import { page, session } from '$app/stores'; import { page, session } from '$app/stores';
import { goto } from '$app/navigation'; import { goto } from '$app/navigation';
import { request } from '$lib/api/request'; import { request } from '$lib/request';
import { browser } from '$app/env'; import { browser } from '$app/env';
import Loading from '$components/Loading.svelte';
let type; let type;
let defaultDatabaseName; let defaultDatabaseName;
let loading = false;
async function deploy() { async function deploy() {
try { try {
loading = true;
await request(`/api/v1/databases/deploy`, $session, { await request(`/api/v1/databases/deploy`, $session, {
body: { body: {
type, type,
@@ -28,11 +31,17 @@
} }
} catch (error) { } catch (error) {
console.log(error); console.log(error);
} finally {
loading = false;
} }
} }
</script> </script>
<div class="text-center space-y-2 max-w-4xl mx-auto px-6" in:fade={{ duration: 100 }}> {#if loading}
<Loading />
{:else}
<div class="text-center space-y-2 max-w-4xl mx-auto px-6" in:fade={{ duration: 100 }}>
{#if $page.path === '/database/new'} {#if $page.path === '/database/new'}
<div class="flex justify-center space-x-4 font-bold pb-6"> <div class="flex justify-center space-x-4 font-bold pb-6">
<div <div
@@ -75,6 +84,16 @@
</div> </div>
<div class="text-white">MySQL</div> <div class="text-white">MySQL</div>
</div> </div>
<div
class="text-center flex-col items-center cursor-pointer ease-in-out transform hover:scale-105 duration-100 border-2 border-dashed border-transparent hover:border-red-600 p-2 rounded bg-warmGray-800 w-32"
class:border-red-600={type === 'redis'}
on:click={() => (type = 'redis')}
>
<div class="flex items-center justify-center">
<Redis customClass="w-12" />
</div>
<div class="text-white">Redis</div>
</div>
<!-- <button <!-- <button
class="button bg-gray-500 p-2 text-white hover:bg-yellow-500 cursor-pointer w-32" class="button bg-gray-500 p-2 text-white hover:bg-yellow-500 cursor-pointer w-32"
@@ -86,8 +105,15 @@
</div> </div>
{#if type} {#if type}
<div class="flex justify-center space-x-4 items-center"> <div class="flex justify-center space-x-4 items-center">
{#if type !== 'redis'}
<label for="defaultDB">Default database</label> <label for="defaultDB">Default database</label>
<input id="defaultDB" class="w-64" placeholder="random" bind:value={defaultDatabaseName} /> <input
id="defaultDB"
class="w-64"
placeholder="random"
bind:value={defaultDatabaseName}
/>
{/if}
<button <button
class:bg-green-600={type === 'mongodb'} class:bg-green-600={type === 'mongodb'}
@@ -96,8 +122,8 @@
class:hover:bg-blue-500={type === 'postgresql'} class:hover:bg-blue-500={type === 'postgresql'}
class:bg-orange-600={type === 'mysql'} class:bg-orange-600={type === 'mysql'}
class:hover:bg-orange-500={type === 'mysql'} class:hover:bg-orange-500={type === 'mysql'}
class:bg-red-600={type === 'couchdb'} class:bg-red-600={type === 'couchdb' || type === 'redis'}
class:hover:bg-red-500={type === 'couchdb'} class:hover:bg-red-500={type === 'couchdb' || type === 'redis'}
class:bg-yellow-500={type === 'clickhouse'} class:bg-yellow-500={type === 'clickhouse'}
class:hover:bg-yellow-400={type === 'clickhouse'} class:hover:bg-yellow-400={type === 'clickhouse'}
class="button p-2 w-32 text-white" class="button p-2 w-32 text-white"
@@ -106,4 +132,5 @@
</div> </div>
{/if} {/if}
{/if} {/if}
</div> </div>
{/if}

View File

@@ -0,0 +1,37 @@
<script>
export let customClass;
</script>
<svg
class={customClass}
height="64"
viewBox="0 0 32 32"
width="64"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
><defs
><path
id="a"
d="m45.536 38.764c-2.013 1.05-12.44 5.337-14.66 6.494s-3.453 1.146-5.207.308-12.85-5.32-14.85-6.276c-1-.478-1.524-.88-1.524-1.26v-3.813s14.447-3.145 16.78-3.982 3.14-.867 5.126-.14 13.853 2.868 15.814 3.587v3.76c0 .377-.452.8-1.477 1.324z"
/><path
id="b"
d="m45.536 28.733c-2.013 1.05-12.44 5.337-14.66 6.494s-3.453 1.146-5.207.308-12.85-5.32-14.85-6.276-2.04-1.613-.077-2.382l15.332-5.935c2.332-.837 3.14-.867 5.126-.14s12.35 4.853 14.312 5.57 2.037 1.31.024 2.36z"
/></defs
><g transform="matrix(.848327 0 0 .848327 -7.883573 -9.449691)"
><use fill="#a41e11" xlink:href="#a" /><path
d="m45.536 34.95c-2.013 1.05-12.44 5.337-14.66 6.494s-3.453 1.146-5.207.308-12.85-5.32-14.85-6.276-2.04-1.613-.077-2.382l15.332-5.936c2.332-.836 3.14-.867 5.126-.14s12.35 4.852 14.31 5.582 2.037 1.31.024 2.36z"
fill="#d82c20"
/><use fill="#a41e11" xlink:href="#a" y="-6.218" /><use fill="#d82c20" xlink:href="#b" /><path
d="m45.536 26.098c-2.013 1.05-12.44 5.337-14.66 6.495s-3.453 1.146-5.207.308-12.85-5.32-14.85-6.276c-1-.478-1.524-.88-1.524-1.26v-3.815s14.447-3.145 16.78-3.982 3.14-.867 5.126-.14 13.853 2.868 15.814 3.587v3.76c0 .377-.452.8-1.477 1.324z"
fill="#a41e11"
/><use fill="#d82c20" xlink:href="#b" y="-6.449" /><g fill="#fff"
><path
d="m29.096 20.712-1.182-1.965-3.774-.34 2.816-1.016-.845-1.56 2.636 1.03 2.486-.814-.672 1.612 2.534.95-3.268.34zm-6.296 3.912 8.74-1.342-2.64 3.872z"
/><ellipse cx="20.444" cy="21.402" rx="4.672" ry="1.811" /></g
><path d="m42.132 21.138-5.17 2.042-.004-4.087z" fill="#7a0c00" /><path
d="m36.963 23.18-.56.22-5.166-2.042 5.723-2.264z"
fill="#ad2115"
/></g
></svg
>

View File

@@ -3,7 +3,7 @@
import { toast } from '@zerodevx/svelte-toast'; import { toast } from '@zerodevx/svelte-toast';
import Loading from '../Loading.svelte'; import Loading from '../Loading.svelte';
import Tooltip from '$components/Tooltip.svelte'; import Tooltip from '$components/Tooltip.svelte';
import { request } from '$lib/api/request'; import { request } from '$lib/request';
import { page, session } from '$app/stores'; import { page, session } from '$app/stores';
import PasswordField from '$components/PasswordField.svelte'; import PasswordField from '$components/PasswordField.svelte';
import { browser } from '$app/env'; import { browser } from '$app/env';

1
src/global.d.ts vendored
View File

@@ -43,6 +43,7 @@ export type Application = {
command: { command: {
build: string | null; build: string | null;
installation: string; installation: string;
start: string;
}; };
container: { container: {
name: string; name: string;

View File

@@ -4,6 +4,9 @@ import { publicPages } from '$lib/consts';
import mongoose from 'mongoose'; import mongoose from 'mongoose';
import { verifyUserId } from '$lib/api/common'; import { verifyUserId } from '$lib/api/common';
import { initializeSession } from 'svelte-kit-cookie-session'; import { initializeSession } from 'svelte-kit-cookie-session';
import { cleanupStuckedDeploymentsInDB } from '$lib/api/applications/cleanup';
import { docker } from '$lib/api/docker';
import Configuration from '$models/Configuration';
process.on('SIGINT', function () { process.on('SIGINT', function () {
mongoose.connection.close(function () { mongoose.connection.close(function () {
@@ -13,6 +16,7 @@ process.on('SIGINT', function () {
}); });
async function connectMongoDB() { async function connectMongoDB() {
// TODO: Save configurations on start?
const { MONGODB_USER, MONGODB_PASSWORD, MONGODB_HOST, MONGODB_PORT, MONGODB_DB } = process.env; const { MONGODB_USER, MONGODB_PASSWORD, MONGODB_HOST, MONGODB_PORT, MONGODB_DB } = process.env;
try { try {
if (process.env.NODE_ENV === 'production') { if (process.env.NODE_ENV === 'production') {
@@ -27,12 +31,65 @@ async function connectMongoDB() {
); );
} }
console.log('Connected to mongodb.'); console.log('Connected to mongodb.');
} catch (error) { } catch (error) {
console.log(error); console.log(error);
} }
} }
if (mongoose.connection.readyState !== 1) connectMongoDB(); (async () => {
console.log(mongoose.connection.readyState)
if (mongoose.connection.readyState !== 1) await connectMongoDB();
try {
await mongoose.connection.db.dropCollection('logs-servers');
} catch (error) {
//
}
try {
await cleanupStuckedDeploymentsInDB();
} catch (error) {
console.log(error)
}
try {
const dockerServices = await docker.engine.listServices();
let applications: any = dockerServices.filter(
(r) =>
r.Spec.Labels.managedBy === 'coolify' &&
r.Spec.Labels.type === 'application' &&
r.Spec.Labels.configuration
);
applications = applications.map((r) => {
if (JSON.parse(r.Spec.Labels.configuration)) {
return {
configuration: JSON.parse(r.Spec.Labels.configuration),
UpdatedAt: r.UpdatedAt
};
}
return {};
});
applications = [
...new Map(
applications.map((item) => [
item.configuration.publish.domain + item.configuration.publish.path,
item
])
).values()
];
for (const application of applications) {
await Configuration.findOneAndUpdate({
'repository.name': application.configuration.repository.name,
'repository.organization': application.configuration.repository.organization,
'repository.branch': application.configuration.repository.branch,
}, {
...application.configuration
}, { upsert: true })
}
} catch (error) {
console.log(error)
}
})()
export async function handle({ request, render }) { export async function handle({ request, render }) {
const { SECRETS_ENCRYPTION_KEY } = process.env; const { SECRETS_ENCRYPTION_KEY } = process.env;

View File

@@ -1,4 +1,4 @@
import Deployment from '$models/Logs/Deployment'; import Deployment from '$models/Deployment';
import { saveAppLog } from './logging'; import { saveAppLog } from './logging';
import * as packs from './packs'; import * as packs from './packs';

View File

@@ -1,4 +1,5 @@
import { docker } from '$lib/api/docker'; import { docker } from '$lib/api/docker';
import Deployment from '$models/Deployment';
import { execShellAsync } from '../common'; import { execShellAsync } from '../common';
export async function deleteSameDeployments(configuration) { export async function deleteSameDeployments(configuration) {
@@ -17,18 +18,31 @@ export async function deleteSameDeployments(configuration) {
}); });
} }
export async function cleanupStuckedDeploymentsInDB() {
// Cleanup stucked deployments.
await Deployment.updateMany(
{ progress: { $in: ['queued', 'inprogress'] } },
{ progress: 'failed' }
);
}
export async function purgeImagesContainers(configuration, deleteAll = false) { export async function purgeImagesContainers(configuration, deleteAll = false) {
const { name, tag } = configuration.build.container; const { name, tag } = configuration.build.container;
try {
await execShellAsync('docker container prune -f'); await execShellAsync('docker container prune -f');
} catch (error) {
//
}
try {
if (deleteAll) { if (deleteAll) {
const IDsToDelete = ( const IDsToDelete = (
await execShellAsync(`docker images ls --filter=reference='${name}' --format '{{json .ID }}'`) await execShellAsync(
`docker images ls --filter=reference='${name}' --format '{{json .ID }}'`
)
) )
.trim() .trim()
.replace(/"/g, '') .replace(/"/g, '')
.split('\n'); .split('\n');
if (IDsToDelete.length > 0) if (IDsToDelete.length > 0) await execShellAsync(`docker rmi -f ${IDsToDelete.join(' ')}`);
await execShellAsync(`docker rmi -f ${IDsToDelete.toString().replace(',', ' ')}`);
} else { } else {
const IDsToDelete = ( const IDsToDelete = (
await execShellAsync( await execShellAsync(
@@ -38,8 +52,15 @@ export async function purgeImagesContainers(configuration, deleteAll = false) {
.trim() .trim()
.replace(/"/g, '') .replace(/"/g, '')
.split('\n'); .split('\n');
if (IDsToDelete.length > 1) if (IDsToDelete.length > 1) await execShellAsync(`docker rmi -f ${IDsToDelete.join(' ')}`);
await execShellAsync(`docker rmi -f ${IDsToDelete.toString().replace(',', ' ')}`);
} }
} catch (error) {
console.log(error);
}
try {
await execShellAsync('docker image prune -f'); await execShellAsync('docker image prune -f');
} catch (error) {
//
}
} }

View File

@@ -1,9 +1,10 @@
import Settings from '$models/Settings'; import Settings from '$models/Settings';
import ServerLog from '$models/Logs/Server'; import ServerLog from '$models/ServerLog';
import ApplicationLog from '$models/Logs/Application'; import ApplicationLog from '$models/ApplicationLog';
import dayjs from 'dayjs'; import dayjs from 'dayjs';
import { version } from '../../../../package.json'; import { version } from '../../../../package.json';
function generateTimestamp() { function generateTimestamp() {
return `${dayjs().format('YYYY-MM-DD HH:mm:ss.SSS')} `; return `${dayjs().format('YYYY-MM-DD HH:mm:ss.SSS')} `;
} }

View File

@@ -1,20 +1,21 @@
import { docker, streamEvents } from '$lib/api/docker'; import { docker, streamEvents } from '$lib/api/docker';
import { promises as fs } from 'fs'; import { promises as fs } from 'fs';
const buildImageNodeDocker = (configuration) => { const buildImageNodeDocker = (configuration, prodBuild) => {
return [ return [
'FROM node:lts', 'FROM node:lts',
'WORKDIR /usr/src/app', 'WORKDIR /usr/src/app',
`COPY ${configuration.build.directory}/package*.json ./`, `COPY ${configuration.build.directory}/package*.json ./`,
configuration.build.command.installation && `RUN ${configuration.build.command.installation}`, configuration.build.command.installation && `RUN ${configuration.build.command.installation}`,
`COPY ./${configuration.build.directory} ./`, `COPY ./${configuration.build.directory} ./`,
`RUN ${configuration.build.command.build}` `RUN ${configuration.build.command.build}`,
prodBuild && `RUN rm -fr node_modules && ${configuration.build.command.installation} --prod`
].join('\n'); ].join('\n');
}; };
export async function buildImage(configuration, cacheBuild?: boolean) { export async function buildImage(configuration, cacheBuild?: boolean, prodBuild?: boolean) {
await fs.writeFile( await fs.writeFile(
`${configuration.general.workdir}/Dockerfile`, `${configuration.general.workdir}/Dockerfile`,
buildImageNodeDocker(configuration) buildImageNodeDocker(configuration, prodBuild)
); );
const stream = await docker.engine.buildImage( const stream = await docker.engine.buildImage(
{ src: ['.'], context: configuration.general.workdir }, { src: ['.'], context: configuration.general.workdir },

View File

@@ -7,6 +7,7 @@ import php from './php';
import nuxtjs from './nuxtjs'; import nuxtjs from './nuxtjs';
import nodejs from './nodejs'; import nodejs from './nodejs';
import nextjs from './nextjs'; import nextjs from './nextjs';
import nestjs from './nestjs';
import gatsby from './gatsby'; import gatsby from './gatsby';
import docker from './docker'; import docker from './docker';
@@ -20,6 +21,7 @@ export {
nuxtjs, nuxtjs,
nodejs, nodejs,
nextjs, nextjs,
nestjs,
gatsby, gatsby,
docker docker
}; };

View File

@@ -0,0 +1,31 @@
import { docker, streamEvents } from '$lib/api/docker';
import { promises as fs } from 'fs';
import { buildImage } from '../helpers';
// `HEALTHCHECK --timeout=10s --start-period=10s --interval=5s CMD curl -I -s -f http://localhost:${configuration.publish.port}${configuration.publish.path} || exit 1`,
const publishNodejsDocker = (configuration) => {
return [
'FROM node:lts',
'WORKDIR /usr/src/app',
configuration.build.command.build
? `COPY --from=${configuration.build.container.name}:${configuration.build.container.tag} /usr/src/app/${configuration.publish.directory} ./`
: `
COPY ${configuration.build.directory}/package*.json ./
RUN ${configuration.build.command.installation}
COPY ./${configuration.build.directory} ./`,
`EXPOSE ${configuration.publish.port}`,
`CMD ${configuration.build.command.start}`
].join('\n');
};
export default async function (configuration) {
if (configuration.build.command.build) await buildImage(configuration, false, true);
await fs.writeFile(
`${configuration.general.workdir}/Dockerfile`,
publishNodejsDocker(configuration)
);
const stream = await docker.engine.buildImage(
{ src: ['.'], context: configuration.general.workdir },
{ t: `${configuration.build.container.name}:${configuration.build.container.tag}` }
);
await streamEvents(stream, configuration);
}

View File

@@ -13,7 +13,7 @@ const publishNodejsDocker = (configuration) => {
RUN ${configuration.build.command.installation} RUN ${configuration.build.command.installation}
COPY ./${configuration.build.directory} ./`, COPY ./${configuration.build.directory} ./`,
`EXPOSE ${configuration.publish.port}`, `EXPOSE ${configuration.publish.port}`,
'CMD [ "yarn", "start" ]' `CMD ${configuration.build.command.start}`
].join('\n'); ].join('\n');
}; };
export default async function (configuration) { export default async function (configuration) {

View File

@@ -13,7 +13,7 @@ const publishNodejsDocker = (configuration) => {
RUN ${configuration.build.command.installation} RUN ${configuration.build.command.installation}
COPY ./${configuration.build.directory} ./`, COPY ./${configuration.build.directory} ./`,
`EXPOSE ${configuration.publish.port}`, `EXPOSE ${configuration.publish.port}`,
'CMD [ "yarn", "start" ]' `CMD ${configuration.build.command.start}`
].join('\n'); ].join('\n');
}; };

View File

@@ -13,7 +13,7 @@ const publishNodejsDocker = (configuration) => {
RUN ${configuration.build.command.installation} RUN ${configuration.build.command.installation}
COPY ./${configuration.build.directory} ./`, COPY ./${configuration.build.directory} ./`,
`EXPOSE ${configuration.publish.port}`, `EXPOSE ${configuration.publish.port}`,
'CMD [ "yarn", "start" ]' `CMD ${configuration.build.command.start}`
].join('\n'); ].join('\n');
}; };

View File

@@ -1,6 +1,7 @@
const defaultBuildAndDeploy = { const defaultBuildAndDeploy = {
installation: 'yarn install', installation: 'yarn install',
build: 'yarn build' build: 'yarn build',
start: 'yarn start'
}; };
const templates = { const templates = {
@@ -10,6 +11,13 @@ const templates = {
directory: 'public', directory: 'public',
name: 'Svelte' name: 'Svelte'
}, },
'@nestjs/core': {
pack: 'nestjs',
...defaultBuildAndDeploy,
start: 'yarn start:prod',
port: 3000,
name: 'NestJS'
},
next: { next: {
pack: 'nextjs', pack: 'nextjs',
...defaultBuildAndDeploy, ...defaultBuildAndDeploy,

View File

@@ -1,4 +1,5 @@
import Deployment from '$models/Logs/Deployment';
import Deployment from '$models/Deployment';
import dayjs from 'dayjs'; import dayjs from 'dayjs';
import buildContainer from './buildContainer'; import buildContainer from './buildContainer';
import { updateServiceLabels } from './configuration'; import { updateServiceLabels } from './configuration';
@@ -9,16 +10,8 @@ import { saveAppLog } from './logging';
export default async function (configuration, imageChanged) { export default async function (configuration, imageChanged) {
const { id, organization, name, branch } = configuration.repository; const { id, organization, name, branch } = configuration.repository;
const { domain } = configuration.publish; const { domain } = configuration.publish;
const { deployId, nickname } = configuration.general; const { deployId} = configuration.general;
await new Deployment({ try {
repoId: id,
branch,
deployId,
domain,
organization,
name,
nickname
}).save();
await saveAppLog(`${dayjs().format('YYYY-MM-DD HH:mm:ss.SSS')} Queued.`, configuration); await saveAppLog(`${dayjs().format('YYYY-MM-DD HH:mm:ss.SSS')} Queued.`, configuration);
await copyFiles(configuration); await copyFiles(configuration);
await buildContainer(configuration); await buildContainer(configuration);
@@ -28,4 +21,10 @@ export default async function (configuration, imageChanged) {
{ repoId: id, branch, deployId, organization, name, domain, progress: 'done' } { repoId: id, branch, deployId, organization, name, domain, progress: 'done' }
); );
await updateServiceLabels(configuration); await updateServiceLabels(configuration);
} catch (error) {
await Deployment.findOneAndUpdate(
{ repoId: id, branch, deployId, organization, name, domain },
{ repoId: id, branch, deployId, organization, name, domain, progress: 'failed' }
);
}
} }

View File

@@ -1,13 +1,17 @@
import shell from 'shelljs'; import shell from 'shelljs';
import User from '$models/User'; import User from '$models/User';
import jsonwebtoken from 'jsonwebtoken'; import jsonwebtoken from 'jsonwebtoken';
import { saveServerLog } from './applications/logging';
export function execShellAsync(cmd, opts = {}) { export function execShellAsync(cmd, opts = {}) {
try { try {
return new Promise(function (resolve, reject) { return new Promise(function (resolve, reject) {
shell.config.silent = true; shell.config.silent = true;
shell.exec(cmd, opts, function (code, stdout, stderr) { shell.exec(cmd, opts, async function (code, stdout, stderr) {
if (code !== 0) return reject(new Error(stderr)); if (code !== 0) {
await saveServerLog({ message: JSON.stringify({ cmd, opts, code, stdout, stderr }) })
return reject(new Error(stderr));
}
return resolve(stdout); return resolve(stdout);
}); });
}); });

23
src/lib/api/github.ts Normal file
View File

@@ -0,0 +1,23 @@
import type { Request } from '@sveltejs/kit';
export async function githubAPI(
request: Request,
resource: string,
token?: string,
data?: Record<string, unknown>
) {
const base = 'https://api.github.com';
const res = await fetch(`${base}${resource}`, {
method: request.method,
headers: {
'content-type': 'application/json',
accept: 'application/json',
authorization: token ? `token ${token}` : ''
},
body: data && JSON.stringify(data)
});
return {
status: res.status,
body: await res.json()
};
}

View File

@@ -61,7 +61,6 @@ export async function request(
} else if (response.headers.get('content-type').match(/multipart\/form-data/)) { } else if (response.headers.get('content-type').match(/multipart\/form-data/)) {
return await response.formData(); return await response.formData();
} else { } else {
console.log(response);
if (response.headers.get('content-disposition')) { if (response.headers.get('content-disposition')) {
const blob = await response.blob(); const blob = await response.blob();
console.log(blob); console.log(blob);
@@ -86,10 +85,10 @@ export async function request(
}); });
} else if (response.status >= 500) { } else if (response.status >= 500) {
const error = (await response.json()).error; const error = (await response.json()).error;
browser && toast.push(error); browser && toast.push(error.message || error);
return Promise.reject({ return Promise.reject({
status: response.status, status: response.status,
error: error || 'Oops, something is not okay. Are you okay?' error: error.message || error || 'Oops, something is not okay. Are you okay?'
}); });
} else { } else {
browser && toast.push(response.statusText); browser && toast.push(response.statusText);

View File

@@ -8,4 +8,4 @@ const ApplicationLogsSchema = new Schema({
ApplicationLogsSchema.set('timestamps', true); ApplicationLogsSchema.set('timestamps', true);
export default mongoose.model('logs-application', ApplicationLogsSchema); export default mongoose.models['logs-application'] || mongoose.model('logs-application', ApplicationLogsSchema);

View File

@@ -0,0 +1,48 @@
import mongoose from 'mongoose';
const { Schema } = mongoose;
const ConfigurationSchema = new Schema({
github: {
installation: {
id: { type: Number, required: true }
},
app: {
id: { type: Number, required: true }
}
},
repository: {
id: { type: Number, required: true },
organization: { type: String, required: true },
name: { type: String, required: true },
branch: { type: String, required: true },
},
general: {
deployId: { type: String, required: true },
nickname: { type: String, required: true },
workdir: { type: String, required: true },
},
build: {
pack: { type: String, required: true },
directory: { type: String },
command: {
build: { type: String },
installation: { type: String },
start: { type: String },
},
container: {
name: { type: String, required: true },
tag: { type: String, required: true },
baseSHA: { type: String, required: true },
},
},
publish: {
directory: { type: String },
domain: { type: String, required: true },
path: { type: String },
port: { type: Number },
secrets: { type: Array },
}
});
ConfigurationSchema.set('timestamps', true);
export default mongoose.models['configuration'] || mongoose.model('configuration', ConfigurationSchema);

View File

@@ -14,4 +14,4 @@ const DeploymentSchema = new Schema({
DeploymentSchema.set('timestamps', true); DeploymentSchema.set('timestamps', true);
export default mongoose.model('deployment', DeploymentSchema); export default mongoose.models['deployment'] || mongoose.model('deployment', DeploymentSchema);

View File

@@ -1,5 +1,5 @@
import mongoose from 'mongoose'; import mongoose from 'mongoose';
import { version } from '../../../package.json'; import { version } from '../../package.json'
const { Schema, Document } = mongoose; const { Schema, Document } = mongoose;
// export interface ILogsServer extends Document { // export interface ILogsServer extends Document {
@@ -20,4 +20,4 @@ const LogsServerSchema = new Schema({
LogsServerSchema.set('timestamps', { createdAt: 'createdAt', updatedAt: false }); LogsServerSchema.set('timestamps', { createdAt: 'createdAt', updatedAt: false });
export default mongoose.model('logs-server', LogsServerSchema); export default mongoose.models['logs-server'] || mongoose.model('logs-server', LogsServerSchema);

View File

@@ -14,4 +14,4 @@ const SettingsSchema = new Schema({
SettingsSchema.set('timestamps', true); SettingsSchema.set('timestamps', true);
export default mongoose.model('settings', SettingsSchema); export default mongoose.models['settings'] || mongoose.model('settings', SettingsSchema);

View File

@@ -14,4 +14,4 @@ const UserSchema = new Schema({
UserSchema.set('timestamps', true); UserSchema.set('timestamps', true);
export default mongoose.model('user', UserSchema); export default mongoose.models['user'] || mongoose.model('user', UserSchema);

View File

@@ -1,6 +1,6 @@
<script context="module" lang="ts"> <script context="module" lang="ts">
import { publicPages } from '$lib/consts'; import { publicPages } from '$lib/consts';
import { request } from '$lib/api/request'; import { request } from '$lib/request';
/** /**
* @type {import('@sveltejs/kit').Load} * @type {import('@sveltejs/kit').Load}
*/ */

View File

@@ -1,42 +0,0 @@
import type { Request } from '@sveltejs/kit';
// export async function api(request: Request, resource: string, data?: {}) {
// const base = 'https://github.com/';
// if (!request.context.isLoggedIn) {
// return { status: 401, body: 'Unauthorized' };
// }
// const res = await fetch(`${base}${resource}`, {
// method: request.method,
// headers: {
// 'content-type': 'application/json'
// },
// body: data && JSON.stringify(data)
// });
// return {
// status: res.status,
// body: await res.json()
// };
// }
export async function githubAPI(
request: Request,
resource: string,
token?: string,
data?: Record<string, unknown>
) {
const base = 'https://api.github.com';
const res = await fetch(`${base}${resource}`, {
method: request.method,
headers: {
'content-type': 'application/json',
accept: 'application/json',
authorization: token ? `token ${token}` : ''
},
body: data && JSON.stringify(data)
});
return {
status: res.status,
body: await res.json()
};
}

View File

@@ -44,7 +44,7 @@ export async function post(request: Request) {
return { return {
status: 500, status: 500,
body: { body: {
error error: error.message || error
} }
}; };
} }

View File

@@ -1,9 +1,25 @@
import { docker } from '$lib/api/docker'; import { docker } from '$lib/api/docker';
import Configuration from '$models/Configuration';
import type { Request } from '@sveltejs/kit'; import type { Request } from '@sveltejs/kit';
export async function post(request: Request) { export async function post(request: Request) {
const { name, organization, branch }: any = request.body || {}; const { name, organization, branch }: any = request.body || {};
if (name && organization && branch) { if (name && organization && branch) {
const configurationFound = await Configuration.findOne({
'repository.name': name,
'repository.organization': organization,
'repository.branch': branch,
}).lean()
if (configurationFound) {
return {
status: 200,
body: {
success: true,
...configurationFound
}
};
}
const services = await docker.engine.listServices(); const services = await docker.engine.listServices();
const applications = services.filter( const applications = services.filter(
(r) => r.Spec.Labels.managedBy === 'coolify' && r.Spec.Labels.type === 'application' (r) => r.Spec.Labels.managedBy === 'coolify' && r.Spec.Labels.type === 'application'
@@ -38,7 +54,7 @@ export async function post(request: Request) {
...JSON.parse(found.Spec.Labels.configuration) ...JSON.parse(found.Spec.Labels.configuration)
} }
}; };
} else { }
return { return {
status: 500, status: 500,
body: { body: {
@@ -46,5 +62,4 @@ export async function post(request: Request) {
} }
}; };
} }
}
} }

View File

@@ -1,10 +1,11 @@
import type { Request } from '@sveltejs/kit'; import type { Request } from '@sveltejs/kit';
import Deployment from '$models/Logs/Deployment'; import Deployment from '$models/Deployment';
import { docker } from '$lib/api/docker'; import { docker } from '$lib/api/docker';
import { precheckDeployment, setDefaultConfiguration } from '$lib/api/applications/configuration'; import { precheckDeployment, setDefaultConfiguration } from '$lib/api/applications/configuration';
import cloneRepository from '$lib/api/applications/cloneRepository'; import cloneRepository from '$lib/api/applications/cloneRepository';
import { cleanupTmp } from '$lib/api/common'; import { cleanupTmp } from '$lib/api/common';
import queueAndBuild from '$lib/api/applications/queueAndBuild'; import queueAndBuild from '$lib/api/applications/queueAndBuild';
import Configuration from '$models/Configuration';
export async function post(request: Request) { export async function post(request: Request) {
let configuration; let configuration;
try { try {
@@ -53,6 +54,27 @@ export async function post(request: Request) {
} }
}; };
} }
const { id, organization, name, branch } = configuration.repository;
const { domain } = configuration.publish;
const { deployId, nickname } = configuration.general;
await new Deployment({
repoId: id,
branch,
deployId,
domain,
organization,
name,
nickname
}).save();
await Configuration.findOneAndUpdate({
'repository.id': id,
'repository.organization': organization,
'repository.name': name,
'repository.branch': branch,
},
{ ...configuration },
{ upsert: true, new: true })
queueAndBuild(configuration, imageChanged); queueAndBuild(configuration, imageChanged);
return { return {
status: 200, status: 200,
@@ -70,20 +92,21 @@ export async function post(request: Request) {
branch: configuration.repository.branch, branch: configuration.repository.branch,
organization: configuration.repository.organization, organization: configuration.repository.organization,
name: configuration.repository.name, name: configuration.repository.name,
domain: configuration.publish.domain, domain: configuration.publish.domain
}, },
{ {
repoId: configuration.repository.id, repoId: configuration.repository.id,
branch: configuration.repository.branch, branch: configuration.repository.branch,
organization: configuration.repository.organization, organization: configuration.repository.organization,
name: configuration.repository.name, name: configuration.repository.name,
domain: configuration.publish.domain, progress: 'failed' domain: configuration.publish.domain,
progress: 'failed'
} }
); );
return { return {
status: 500, status: 500,
body: { body: {
error error: error.message || error
} }
}; };
} }

View File

@@ -1,6 +1,6 @@
import type { Request } from '@sveltejs/kit'; import type { Request } from '@sveltejs/kit';
import ApplicationLog from '$models/Logs/Application'; import ApplicationLog from '$models/ApplicationLog';
import Deployment from '$models/Logs/Deployment'; import Deployment from '$models/Deployment';
import dayjs from 'dayjs'; import dayjs from 'dayjs';
export async function get(request: Request) { export async function get(request: Request) {
@@ -24,11 +24,11 @@ export async function get(request: Request) {
...finalLogs ...finalLogs
} }
}; };
} catch (e) { } catch (error) {
return { return {
status: 500, status: 500,
body: { body: {
error: e error: error.message || error
} }
}; };
} }

View File

@@ -2,7 +2,7 @@ import type { Request } from '@sveltejs/kit';
import dayjs from 'dayjs'; import dayjs from 'dayjs';
import utc from 'dayjs/plugin/utc.js'; import utc from 'dayjs/plugin/utc.js';
import relativeTime from 'dayjs/plugin/relativeTime.js'; import relativeTime from 'dayjs/plugin/relativeTime.js';
import Deployment from '$models/Logs/Deployment'; import Deployment from '$models/Deployment';
dayjs.extend(utc); dayjs.extend(utc);
dayjs.extend(relativeTime); dayjs.extend(relativeTime);
export async function get(request: Request) { export async function get(request: Request) {
@@ -10,7 +10,6 @@ export async function get(request: Request) {
const repoId = request.query.get('repoId'); const repoId = request.query.get('repoId');
const branch = request.query.get('branch'); const branch = request.query.get('branch');
const page = request.query.get('page'); const page = request.query.get('page');
const onePage = 5; const onePage = 5;
const show = Number(page) * onePage || 5; const show = Number(page) * onePage || 5;
const deploy: any = await Deployment.find({ repoId, branch }) const deploy: any = await Deployment.find({ repoId, branch })
@@ -20,12 +19,9 @@ export async function get(request: Request) {
const finalLogs = deploy.map((d) => { const finalLogs = deploy.map((d) => {
const finalLogs = { ...d._doc }; const finalLogs = { ...d._doc };
const updatedAt = dayjs(d.updatedAt).utc(); const updatedAt = dayjs(d.updatedAt).utc();
finalLogs.took = updatedAt.diff(dayjs(d.createdAt)) / 1000; finalLogs.took = updatedAt.diff(dayjs(d.createdAt)) / 1000;
finalLogs.since = updatedAt.fromNow(); finalLogs.since = updatedAt.fromNow();
return finalLogs; return finalLogs;
}); });
return { return {
@@ -36,11 +32,10 @@ export async function get(request: Request) {
} }
}; };
} catch (error) { } catch (error) {
console.log(error);
return { return {
status: 500, status: 500,
body: { body: {
error error: error.message || error
} }
}; };
} }

View File

@@ -16,11 +16,12 @@ export async function get(request: Request) {
body: { success: true, logs } body: { success: true, logs }
}; };
} catch (error) { } catch (error) {
console.log(error)
await saveServerLog(error); await saveServerLog(error);
return { return {
status: 500, status: 500,
body: { body: {
error error: 'No such service. Is it under deployment?'
} }
}; };
} }

View File

@@ -1,8 +1,9 @@
import { purgeImagesContainers } from '$lib/api/applications/cleanup'; import { purgeImagesContainers } from '$lib/api/applications/cleanup';
import { docker } from '$lib/api/docker'; import { docker } from '$lib/api/docker';
import Deployment from '$models/Logs/Deployment'; import Deployment from '$models/Deployment';
import ApplicationLog from '$models/Logs/Application'; import ApplicationLog from '$models/ApplicationLog';
import { delay, execShellAsync } from '$lib/api/common'; import { delay, execShellAsync } from '$lib/api/common';
import Configuration from '$models/Configuration';
async function call(found) { async function call(found) {
await delay(10000); await delay(10000);
@@ -26,6 +27,11 @@ export async function post(request: Request) {
return null; return null;
}); });
if (found) { if (found) {
await Configuration.findOneAndRemove({
'repository.name': name,
'repository.organization': organization,
'repository.branch': branch,
})
const deploys = await Deployment.find({ organization, branch, name }); const deploys = await Deployment.find({ organization, branch, name });
for (const deploy of deploys) { for (const deploy of deploys) {
await ApplicationLog.deleteMany({ deployId: deploy.deployId }); await ApplicationLog.deleteMany({ deployId: deploy.deployId });

View File

@@ -1,9 +1,7 @@
import { docker } from '$lib/api/docker'; import { docker } from '$lib/api/docker';
import LogsServer from '$models/Logs/Server';
import type { Request } from '@sveltejs/kit'; import type { Request } from '@sveltejs/kit';
export async function get(request: Request) { export async function get(request: Request) {
const serverLogs = await LogsServer.find();
const dockerServices = await docker.engine.listServices(); const dockerServices = await docker.engine.listServices();
let applications: any = dockerServices.filter( let applications: any = dockerServices.filter(
(r) => (r) =>
@@ -61,7 +59,6 @@ export async function get(request: Request) {
status: 200, status: 200,
body: { body: {
success: true, success: true,
serverLogs,
applications: { applications: {
deployed: applications deployed: applications
}, },

View File

@@ -100,6 +100,31 @@ export async function post(request: Request) {
body: fs.readFileSync(`${fullfilename}`) body: fs.readFileSync(`${fullfilename}`)
}; };
} }
} else if (type === 'redis') {
if (databaseService) {
const password = configuration.database.passwords[0];
const databaseName = configuration.database.defaultDatabaseName;
const filename = `${databaseName}_${now.getTime()}.rdb`;
const fullfilename = `${tmpdir}/${filename}`;
await execShellAsync(
`docker exec -i ${containerID} /bin/bash -c "redis-cli --pass ${password} save"`
);
await execShellAsync(
`docker cp ${containerID}:/bitnami/redis/data/dump.rdb ${fullfilename}`
);
await execShellAsync(
`docker exec -i ${containerID} /bin/bash -c "rm -f /bitnami/redis/data/dump.rdb"`
);
return {
status: 200,
headers: {
'Content-Type': 'application/octet-stream',
'Content-Transfer-Encoding': 'binary',
'Content-Disposition': `attachment; filename=${filename}`
},
body: fs.readFileSync(`${fullfilename}`)
};
}
} }
return { return {
status: 501, status: 501,
@@ -108,12 +133,11 @@ export async function post(request: Request) {
} }
}; };
} catch (error) { } catch (error) {
console.log(error);
await saveServerLog(error); await saveServerLog(error);
return { return {
status: 500, status: 500,
body: { body: {
error error: error.message || error
} }
}; };
} finally { } finally {

View File

@@ -96,6 +96,12 @@ export async function post(request: Request) {
hard: 262144 hard: 262144
} }
}; };
} else if (type === 'redis') {
image = 'bitnami/redis';
volume = `${configuration.general.deployId}-${type}-data:/bitnami/redis/data`;
generateEnvs = {
REDIS_PASSWORD: passwords[0]
};
} }
const stack = { const stack = {

View File

@@ -1,10 +1,10 @@
import { githubAPI } from '$api';
import type { Request } from '@sveltejs/kit'; import type { Request } from '@sveltejs/kit';
import mongoose from 'mongoose'; import mongoose from 'mongoose';
import User from '$models/User'; import User from '$models/User';
import Settings from '$models/Settings'; import Settings from '$models/Settings';
import cuid from 'cuid'; import cuid from 'cuid';
import jsonwebtoken from 'jsonwebtoken'; import jsonwebtoken from 'jsonwebtoken';
import { githubAPI } from '$lib/api/github';
export async function get(request: Request) { export async function get(request: Request) {
const code = request.query.get('code'); const code = request.query.get('code');
@@ -17,7 +17,7 @@ export async function get(request: Request) {
{ headers: { accept: 'application/json' } } { headers: { accept: 'application/json' } }
) )
).json(); ).json();
const { avatar_url, id } = await (await githubAPI(request, '/user', access_token)).body; const { avatar_url } = await (await githubAPI(request, '/user', access_token)).body;
const email = (await githubAPI(request, '/user/emails', access_token)).body.filter( const email = (await githubAPI(request, '/user/emails', access_token)).body.filter(
(e) => e.primary (e) => e.primary
)[0].email; )[0].email;
@@ -41,11 +41,10 @@ export async function get(request: Request) {
try { try {
await newUser.save(); await newUser.save();
await defaultSettings.save(); await defaultSettings.save();
} catch (e) { } catch (error) {
console.log(e);
return { return {
status: 500, status: 500,
body: e error: error.message || error
}; };
} }
} else { } else {
@@ -73,12 +72,11 @@ export async function get(request: Request) {
}); });
try { try {
await newUser.save(); await newUser.save();
} catch (e) { } catch (error) {
console.log(e);
return { return {
status: 500, status: 500,
body: { body: {
error: e error: error.message || error
} }
}; };
} }
@@ -103,8 +101,6 @@ export async function get(request: Request) {
} }
}; };
} catch (error) { } catch (error) {
console.log('error happened'); return { status: 500, body: { error: error.message || error } };
console.log(error);
return { status: 500, body: { ...error } };
} }
} }

View File

@@ -34,12 +34,11 @@ export async function get(request: Request) {
}; };
} }
} catch (error) { } catch (error) {
console.log(error);
return { return {
status: 500, status: 500,
body: { body: {
success: false, success: false,
error error: error.message || error
} }
}; };
} }

View File

@@ -22,7 +22,7 @@ export async function get(request: Request) {
return { return {
status: 500, status: 500,
body: { body: {
error error: error.message || error
} }
}; };
} }
@@ -45,7 +45,7 @@ export async function post(request: Request) {
return { return {
status: 500, status: 500,
body: { body: {
error error: error.message || error
} }
}; };
} }

View File

@@ -10,7 +10,6 @@ export async function get(request: Request) {
execShellAsync( execShellAsync(
'docker run --rm -v /var/run/docker.sock:/var/run/docker.sock -u root coolify bash -c "$(curl -fsSL https://get.coollabs.io/coolify/upgrade-p2.sh)"' 'docker run --rm -v /var/run/docker.sock:/var/run/docker.sock -u root coolify bash -c "$(curl -fsSL https://get.coollabs.io/coolify/upgrade-p2.sh)"'
); );
// saveServerLog({ message: upgradeP2, type: 'UPGRADE-P-2' })
return { return {
status: 200, status: 200,
body: { body: {

View File

@@ -1,24 +0,0 @@
// import { deleteCookies } from '$lib/api/common';
// import { verifyUserId } from '$lib/api/common';
// import type { Request } from '@sveltejs/kit';
// import * as cookie from 'cookie';
// export async function post(request: Request) {
// const { coolToken } = cookie.parse(request.headers.cookie || '');
// try {
// await verifyUserId(coolToken);
// return {
// status: 200,
// body: { success: true }
// };
// } catch (error) {
// return {
// status: 301,
// headers: {
// location: '/',
// 'set-cookie': [...deleteCookies]
// },
// body: { error: 'Unauthorized' }
// };
// }
// }

View File

@@ -1,6 +1,6 @@
import type { Request } from '@sveltejs/kit'; import type { Request } from '@sveltejs/kit';
import crypto from 'crypto'; import crypto from 'crypto';
import Deployment from '$models/Logs/Deployment'; import Deployment from '$models/Deployment';
import { docker } from '$lib/api/docker'; import { docker } from '$lib/api/docker';
import { precheckDeployment, setDefaultConfiguration } from '$lib/api/applications/configuration'; import { precheckDeployment, setDefaultConfiguration } from '$lib/api/applications/configuration';
import cloneRepository from '$lib/api/applications/cloneRepository'; import cloneRepository from '$lib/api/applications/cloneRepository';
@@ -106,7 +106,7 @@ export async function post(request: Request) {
return { return {
status: 500, status: 500,
body: { body: {
error error: error.message || error
} }
}; };
} }

View File

@@ -2,7 +2,7 @@
import { onDestroy, onMount } from 'svelte'; import { onDestroy, onMount } from 'svelte';
import { fade } from 'svelte/transition'; import { fade } from 'svelte/transition';
import Loading from '$components/Loading.svelte'; import Loading from '$components/Loading.svelte';
import { request } from '$lib/api/request'; import { request } from '$lib/request';
import { page, session } from '$app/stores'; import { page, session } from '$app/stores';
import { goto } from '$app/navigation'; import { goto } from '$app/navigation';
import { browser } from '$app/env'; import { browser } from '$app/env';

View File

@@ -5,7 +5,7 @@
import { onDestroy, onMount } from 'svelte'; import { onDestroy, onMount } from 'svelte';
import Loading from '$components/Loading.svelte'; import Loading from '$components/Loading.svelte';
import { request } from '$lib/api/request'; import { request } from '$lib/request';
import { session } from '$app/stores'; import { session } from '$app/stores';
import { goto } from '$app/navigation'; import { goto } from '$app/navigation';

View File

@@ -6,7 +6,7 @@
import { page, session } from '$app/stores'; import { page, session } from '$app/stores';
import { goto } from '$app/navigation'; import { goto } from '$app/navigation';
import { browser } from '$app/env'; import { browser } from '$app/env';
import { request } from '$lib/api/request'; import { request } from '$lib/request';
$application.repository.organization = $page.params.organization; $application.repository.organization = $page.params.organization;
$application.repository.name = $page.params.name; $application.repository.name = $page.params.name;

View File

@@ -1,5 +1,5 @@
<script context="module" lang="ts"> <script context="module" lang="ts">
import { request } from '$lib/api/request'; import { request } from '$lib/request';
/** /**
* @type {import('@sveltejs/kit').Load} * @type {import('@sveltejs/kit').Load}
*/ */

File diff suppressed because one or more lines are too long

View File

@@ -7,6 +7,8 @@
import Mysql from '$components/Database/SVGs/Mysql.svelte'; import Mysql from '$components/Database/SVGs/Mysql.svelte';
import { dashboard } from '$store'; import { dashboard } from '$store';
import { fade } from 'svelte/transition'; import { fade } from 'svelte/transition';
import Redis from '$components/Database/SVGs/Redis.svelte';
</script> </script>
<div class="py-5 text-left px-6 text-3xl tracking-tight font-bold flex items-center"> <div class="py-5 text-left px-6 text-3xl tracking-tight font-bold flex items-center">
@@ -56,6 +58,10 @@
<CouchDb <CouchDb
customClass="w-10 h-10 fill-current text-red-600 absolute top-0 left-0 -m-4" customClass="w-10 h-10 fill-current text-red-600 absolute top-0 left-0 -m-4"
/> />
{:else if database.configuration.general.type == 'redis'}
<Redis
customClass="w-10 h-10 absolute top-0 left-0 -m-4"
/>
{:else if database.configuration.general.type == 'clickhouse'} {:else if database.configuration.general.type == 'clickhouse'}
<Clickhouse <Clickhouse
customClass="w-10 h-10 fill-current text-red-600 absolute top-0 left-0 -m-4" customClass="w-10 h-10 fill-current text-red-600 absolute top-0 left-0 -m-4"
@@ -79,4 +85,3 @@
<div class="text-2xl font-bold text-center">No databases found</div> <div class="text-2xl font-bold text-center">No databases found</div>
{/if} {/if}
</div> </div>

View File

@@ -1,7 +1,7 @@
<script> <script>
import { database } from '$store'; import { database } from '$store';
import { page, session } from '$app/stores'; import { page, session } from '$app/stores';
import { request } from '$lib/api/request'; import { request } from '$lib/request';
import { fade } from 'svelte/transition'; import { fade } from 'svelte/transition';
import { goto } from '$app/navigation'; import { goto } from '$app/navigation';
import MongoDb from '$components/Database/SVGs/MongoDb.svelte'; import MongoDb from '$components/Database/SVGs/MongoDb.svelte';
@@ -12,10 +12,11 @@
import PasswordField from '$components/PasswordField.svelte'; import PasswordField from '$components/PasswordField.svelte';
import { browser } from '$app/env'; import { browser } from '$app/env';
import { toast } from '@zerodevx/svelte-toast'; import { toast } from '@zerodevx/svelte-toast';
import Redis from '$components/Database/SVGs/Redis.svelte';
async function backup() { async function backup() {
try { try {
await request(`/api/v1/databases/${$page.params.name}/backup`, $session, {body: {}}); await request(`/api/v1/databases/${$page.params.name}/backup`, $session, { body: {} });
browser && toast.push(`Successfully created backup.`); browser && toast.push(`Successfully created backup.`);
} catch (error) { } catch (error) {
@@ -56,6 +57,8 @@
<Mysql customClass="w-8 h-8" /> <Mysql customClass="w-8 h-8" />
{:else if $database.config.general.type === 'couchdb'} {:else if $database.config.general.type === 'couchdb'}
<CouchDb customClass="w-8 h-8 fill-current text-red-600" /> <CouchDb customClass="w-8 h-8 fill-current text-red-600" />
{:else if $database.config.general.type === 'redis'}
<Redis customClass="w-8 h-8" />
{/if} {/if}
</div> </div>
</div> </div>
@@ -64,7 +67,9 @@
<div class="pb-2 pt-5 space-y-4"> <div class="pb-2 pt-5 space-y-4">
<div class="text-2xl font-bold border-gradient w-32">Database</div> <div class="text-2xl font-bold border-gradient w-32">Database</div>
<div class="flex items-center pt-4"> <div class="flex items-center pt-4">
{#if $database.config.general.type !== 'redis'}
<div class="font-bold w-64 text-warmGray-400">Connection string</div> <div class="font-bold w-64 text-warmGray-400">Connection string</div>
{/if}
{#if $database.config.general.type === 'mongodb'} {#if $database.config.general.type === 'mongodb'}
<PasswordField <PasswordField
value={`mongodb://${$database.envs.MONGODB_USERNAME}:${$database.envs.MONGODB_PASSWORD}@${$database.config.general.deployId}:27017/${$database.envs.MONGODB_DATABASE}`} value={`mongodb://${$database.envs.MONGODB_USERNAME}:${$database.envs.MONGODB_PASSWORD}@${$database.config.general.deployId}:27017/${$database.envs.MONGODB_DATABASE}`}
@@ -97,6 +102,12 @@
<PasswordField value={$database.envs.MONGODB_ROOT_PASSWORD} /> <PasswordField value={$database.envs.MONGODB_ROOT_PASSWORD} />
</div> </div>
{/if} {/if}
{#if $database.config.general.type === 'redis'}
<div class="flex items-center">
<div class="font-bold w-64 text-warmGray-400">Redis password</div>
<PasswordField value={$database.envs.REDIS_PASSWORD} />
</div>
{/if}
<div class="pb-2 pt-5 space-y-4"> <div class="pb-2 pt-5 space-y-4">
<div class="text-2xl font-bold border-gradient w-32">Backup</div> <div class="text-2xl font-bold border-gradient w-32">Backup</div>
<div class="pt-4"> <div class="pt-4">

View File

@@ -4,7 +4,7 @@
import { goto } from '$app/navigation'; import { goto } from '$app/navigation';
import { page, session } from '$app/stores'; import { page, session } from '$app/stores';
import Tooltip from '$components/Tooltip.svelte'; import Tooltip from '$components/Tooltip.svelte';
import { request } from '$lib/api/request'; import { request } from '$lib/request';
import { database, initialDatabase } from '$store'; import { database, initialDatabase } from '$store';
import { toast } from '@zerodevx/svelte-toast'; import { toast } from '@zerodevx/svelte-toast';
import { onDestroy } from 'svelte'; import { onDestroy } from 'svelte';

View File

@@ -3,7 +3,7 @@
import { goto } from '$app/navigation'; import { goto } from '$app/navigation';
import { session } from '$app/stores'; import { session } from '$app/stores';
import { request } from '$lib/api/request'; import { request } from '$lib/request';
async function login() { async function login() {
const left = screen.width / 2 - 1020 / 2; const left = screen.width / 2 - 1020 / 2;

View File

@@ -5,7 +5,7 @@
import { page, session } from '$app/stores'; import { page, session } from '$app/stores';
import Tooltip from '$components/Tooltip.svelte'; import Tooltip from '$components/Tooltip.svelte';
import { request } from '$lib/api/request'; import { request } from '$lib/request';
import { toast } from '@zerodevx/svelte-toast'; import { toast } from '@zerodevx/svelte-toast';

View File

@@ -3,7 +3,7 @@
import { toast } from '@zerodevx/svelte-toast'; import { toast } from '@zerodevx/svelte-toast';
import { page, session } from '$app/stores'; import { page, session } from '$app/stores';
import { request } from '$lib/api/request'; import { request } from '$lib/request';
import { goto } from '$app/navigation'; import { goto } from '$app/navigation';
import Loading from '$components/Loading.svelte'; import Loading from '$components/Loading.svelte';
import Plausible from '$components/Service/Plausible.svelte'; import Plausible from '$components/Service/Plausible.svelte';

View File

@@ -5,7 +5,7 @@
import { page, session } from '$app/stores'; import { page, session } from '$app/stores';
import Loading from '$components/Loading.svelte'; import Loading from '$components/Loading.svelte';
import { request } from '$lib/api/request'; import { request } from '$lib/request';
import { initialNewService, newService } from '$store'; import { initialNewService, newService } from '$store';
import { toast } from '@zerodevx/svelte-toast'; import { toast } from '@zerodevx/svelte-toast';

View File

@@ -4,7 +4,7 @@
import { toast } from '@zerodevx/svelte-toast'; import { toast } from '@zerodevx/svelte-toast';
import { newService } from '$store'; import { newService } from '$store';
import { page, session } from '$app/stores'; import { page, session } from '$app/stores';
import { request } from '$lib/api/request'; import { request } from '$lib/request';
import { goto } from '$app/navigation'; import { goto } from '$app/navigation';
import Loading from '$components/Loading.svelte'; import Loading from '$components/Loading.svelte';
import TooltipInfo from '$components/TooltipInfo.svelte'; import TooltipInfo from '$components/TooltipInfo.svelte';

View File

@@ -29,7 +29,7 @@
import { browser } from '$app/env'; import { browser } from '$app/env';
import { session } from '$app/stores'; import { session } from '$app/stores';
import { request } from '$lib/api/request'; import { request } from '$lib/request';
import { toast } from '@zerodevx/svelte-toast'; import { toast } from '@zerodevx/svelte-toast';
import { fade } from 'svelte/transition'; import { fade } from 'svelte/transition';
let settings = { let settings = {

View File

@@ -30,7 +30,7 @@ export default {
alias: { alias: {
$components: path.resolve('./src/components/'), $components: path.resolve('./src/components/'),
$store: path.resolve('./src/store/index.ts'), $store: path.resolve('./src/store/index.ts'),
$api: path.resolve('./src/routes/api/_index.ts'), $api: path.resolve('./src/routes/api/'),
$models: path.resolve('./src/models/') $models: path.resolve('./src/models/')
} }
} }

View File

@@ -8,30 +8,6 @@ const svelteClassColonExtractor = (content) => {
module.exports = { module.exports = {
mode: 'jit', mode: 'jit',
purge: ['./**/*.html', './src/**/*.{js,jsx,ts,tsx,svelte}'], purge: ['./**/*.html', './src/**/*.{js,jsx,ts,tsx,svelte}'],
// purge: {
// enabled: process.env.NODE_ENV === 'production',
// content: ['./src/**/*.svelte', './src/**/*.html', './src/**/*.css', './index.html'],
// preserveHtmlElements: true,
// options: {
// safelist: [
// /svelte-/,
// 'border-green-500',
// 'border-yellow-300',
// 'border-red-500',
// 'hover:border-green-500',
// 'hover:border-red-200',
// 'hover:bg-red-200',
// 'hover:bg-warmGray-900',
// 'hover:bg-transparent'
// ],
// defaultExtractor: (content) => {
// // WARNING: tailwindExtractor is internal tailwind api
// // if this breaks after a tailwind update, report to svite repo
// return [...tailwindExtractor(content), ...svelteClassColonExtractor(content)];
// },
// keyframes: false
// }
// },
important: true, important: true,
theme: { theme: {
extend: { extend: {

View File

@@ -26,7 +26,7 @@
"paths": { "paths": {
"$lib/*": ["src/lib/*"], "$lib/*": ["src/lib/*"],
"$store": ["src/store/index.ts"], "$store": ["src/store/index.ts"],
"$api": ["src/routes/api/_index.ts"], "$api/*": ["src/routes/api/*"],
"$models/*": ["src/models/*"], "$models/*": ["src/models/*"],
"$components/*": ["src/components/*"] "$components/*": ["src/components/*"]
} }