feat: Ghost service

This commit is contained in:
Andras Bacsai 2022-03-27 22:03:21 +02:00
parent e471b11d3b
commit 2475031f88
27 changed files with 509 additions and 111 deletions

View File

@ -0,0 +1,19 @@
-- CreateTable
CREATE TABLE "Ghost" (
"id" TEXT NOT NULL PRIMARY KEY,
"defaultEmail" TEXT NOT NULL,
"defaultPassword" TEXT NOT NULL,
"mariadbUser" TEXT NOT NULL,
"mariadbPassword" TEXT NOT NULL,
"mariadbRootUser" TEXT NOT NULL,
"mariadbRootUserPassword" TEXT NOT NULL,
"mariadbDatabase" TEXT,
"mariadbPublicPort" INTEGER,
"serviceId" TEXT NOT NULL,
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" DATETIME NOT NULL,
CONSTRAINT "Ghost_serviceId_fkey" FOREIGN KEY ("serviceId") REFERENCES "Service" ("id") ON DELETE RESTRICT ON UPDATE CASCADE
);
-- CreateIndex
CREATE UNIQUE INDEX "Ghost_serviceId_key" ON "Ghost"("serviceId");

View File

@ -278,6 +278,7 @@ model Service {
minio Minio? minio Minio?
vscodeserver Vscodeserver? vscodeserver Vscodeserver?
wordpress Wordpress? wordpress Wordpress?
ghost Ghost?
serviceSecret ServiceSecret[] serviceSecret ServiceSecret[]
} }
@ -332,3 +333,19 @@ model Wordpress {
createdAt DateTime @default(now()) createdAt DateTime @default(now())
updatedAt DateTime @updatedAt updatedAt DateTime @updatedAt
} }
model Ghost {
id String @id @default(cuid())
defaultEmail String
defaultPassword String
mariadbUser String
mariadbPassword String
mariadbRootUser String
mariadbRootUserPassword String
mariadbDatabase String?
mariadbPublicPort Int?
serviceId String @unique
service Service @relation(fields: [serviceId], references: [id])
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}

View File

@ -0,0 +1,9 @@
<script lang="ts">
export let isAbsolute = false;
</script>
<img
alt="ghost logo"
class={isAbsolute ? 'w-12 absolute top-0 left-0 -m-3 -mt-5' : 'w-10 mx-auto'}
src="/ghost.png"
/>

View File

@ -107,6 +107,7 @@ export const supportedServiceTypesAndVersions = [
name: 'plausibleanalytics', name: 'plausibleanalytics',
fancyName: 'Plausible Analytics', fancyName: 'Plausible Analytics',
baseImage: 'plausible/analytics', baseImage: 'plausible/analytics',
images: ['bitnami/postgresql:13.2.0', 'yandex/clickhouse-server:21.3.2.5'],
versions: ['latest'], versions: ['latest'],
ports: { ports: {
main: 8000 main: 8000
@ -143,6 +144,7 @@ export const supportedServiceTypesAndVersions = [
name: 'wordpress', name: 'wordpress',
fancyName: 'Wordpress', fancyName: 'Wordpress',
baseImage: 'wordpress', baseImage: 'wordpress',
images: ['bitnami/mysql:5.7'],
versions: ['latest', 'php8.1', 'php8.0', 'php7.4', 'php7.3'], versions: ['latest', 'php8.1', 'php8.0', 'php7.4', 'php7.3'],
ports: { ports: {
main: 80 main: 80
@ -183,6 +185,16 @@ export const supportedServiceTypesAndVersions = [
ports: { ports: {
main: 3001 main: 3001
} }
},
{
name: 'ghost',
fancyName: 'Ghost',
baseImage: 'bitnami/ghost',
images: ['bitnami/mariadb'],
versions: ['latest'],
ports: {
main: 2368
}
} }
]; ];
@ -207,6 +219,13 @@ export function getServiceImage(type) {
} }
return ''; return '';
} }
export function getServiceImages(type) {
const found = supportedServiceTypesAndVersions.find((t) => t.name === type);
if (found) {
return found.images;
}
return [];
}
export function generateDatabaseConfiguration(database) { export function generateDatabaseConfiguration(database) {
const { const {
id, id,

View File

@ -1,3 +1,4 @@
import { asyncExecShell, getEngine } from '$lib/common';
import { decrypt, encrypt } from '$lib/crypto'; import { decrypt, encrypt } from '$lib/crypto';
import cuid from 'cuid'; import cuid from 'cuid';
import { generatePassword } from '.'; import { generatePassword } from '.';
@ -20,6 +21,7 @@ export async function getService({ id, teamId }) {
minio: true, minio: true,
vscodeserver: true, vscodeserver: true,
wordpress: true, wordpress: true,
ghost: true,
serviceSecret: true serviceSecret: true
} }
}); });
@ -43,12 +45,18 @@ export async function getService({ id, teamId }) {
if (body.wordpress?.mysqlRootUserPassword) if (body.wordpress?.mysqlRootUserPassword)
body.wordpress.mysqlRootUserPassword = decrypt(body.wordpress.mysqlRootUserPassword); body.wordpress.mysqlRootUserPassword = decrypt(body.wordpress.mysqlRootUserPassword);
if (body.ghost?.mariadbPassword) body.ghost.mariadbPassword = decrypt(body.ghost.mariadbPassword);
if (body.ghost?.mariadbRootUserPassword)
body.ghost.mariadbRootUserPassword = decrypt(body.ghost.mariadbRootUserPassword);
if (body.ghost?.defaultPassword) body.ghost.defaultPassword = decrypt(body.ghost.defaultPassword);
if (body?.serviceSecret.length > 0) { if (body?.serviceSecret.length > 0) {
body.serviceSecret = body.serviceSecret.map((s) => { body.serviceSecret = body.serviceSecret.map((s) => {
s.value = decrypt(s.value); s.value = decrypt(s.value);
return s; return s;
}); });
} }
return { ...body }; return { ...body };
} }
@ -133,6 +141,30 @@ export async function configureServiceType({ id, type }) {
type type
} }
}); });
} else if (type === 'ghost') {
const defaultEmail = `${cuid()}@coolify.io`;
const defaultPassword = encrypt(generatePassword());
const mariadbUser = cuid();
const mariadbPassword = encrypt(generatePassword());
const mariadbRootUser = cuid();
const mariadbRootUserPassword = encrypt(generatePassword());
await prisma.service.update({
where: { id },
data: {
type,
ghost: {
create: {
defaultEmail,
defaultPassword,
mariadbUser,
mariadbPassword,
mariadbRootUser,
mariadbRootUserPassword
}
}
}
});
} }
} }
export async function setServiceVersion({ id, version }) { export async function setServiceVersion({ id, version }) {
@ -174,8 +206,15 @@ export async function updateWordpress({ id, fqdn, name, mysqlDatabase, extraConf
export async function updateMinioService({ id, publicPort }) { export async function updateMinioService({ id, publicPort }) {
return await prisma.minio.update({ where: { serviceId: id }, data: { publicPort } }); return await prisma.minio.update({ where: { serviceId: id }, data: { publicPort } });
} }
export async function updateGhostService({ id, fqdn, name, mariadbDatabase }) {
return await prisma.service.update({
where: { id },
data: { fqdn, name, ghost: { update: { mariadbDatabase } } }
});
}
export async function removeService({ id }) { export async function removeService({ id }) {
await prisma.ghost.deleteMany({ where: { serviceId: id } });
await prisma.plausibleAnalytics.deleteMany({ where: { serviceId: id } }); await prisma.plausibleAnalytics.deleteMany({ where: { serviceId: id } });
await prisma.minio.deleteMany({ where: { serviceId: id } }); await prisma.minio.deleteMany({ where: { serviceId: id } });
await prisma.vscodeserver.deleteMany({ where: { serviceId: id } }); await prisma.vscodeserver.deleteMany({ where: { serviceId: id } });

View File

@ -104,32 +104,34 @@ export async function generateSSLCerts() {
}); });
for (const application of applications) { for (const application of applications) {
try { try {
const { if (application.fqdn && application.destinationDockerId) {
fqdn, const {
id, fqdn,
destinationDocker: { engine, network }, id,
settings: { previews } destinationDocker: { engine, network },
} = application; settings: { previews }
const isRunning = await checkContainer(engine, id); } = application;
const domain = getDomain(fqdn); const isRunning = await checkContainer(engine, id);
const isHttps = fqdn.startsWith('https://'); const domain = getDomain(fqdn);
if (isRunning) { const isHttps = fqdn.startsWith('https://');
if (isHttps) ssls.push({ domain, id, isCoolify: false }); if (isRunning) {
} if (isHttps) ssls.push({ domain, id, isCoolify: false });
if (previews) { }
const host = getEngine(engine); if (previews) {
const { stdout } = await asyncExecShell( const host = getEngine(engine);
`DOCKER_HOST=${host} docker container ls --filter="status=running" --filter="network=${network}" --filter="name=${id}-" --format="{{json .Names}}"` const { stdout } = await asyncExecShell(
); `DOCKER_HOST=${host} docker container ls --filter="status=running" --filter="network=${network}" --filter="name=${id}-" --format="{{json .Names}}"`
const containers = stdout );
.trim() const containers = stdout
.split('\n') .trim()
.filter((a) => a) .split('\n')
.map((c) => c.replace(/"/g, '')); .filter((a) => a)
if (containers.length > 0) { .map((c) => c.replace(/"/g, ''));
for (const container of containers) { if (containers.length > 0) {
let previewDomain = `${container.split('-')[1]}.${domain}`; for (const container of containers) {
if (isHttps) ssls.push({ domain: previewDomain, id, isCoolify: false }); let previewDomain = `${container.split('-')[1]}.${domain}`;
if (isHttps) ssls.push({ domain: previewDomain, id, isCoolify: false });
}
} }
} }
} }
@ -143,26 +145,29 @@ export async function generateSSLCerts() {
minio: true, minio: true,
plausibleAnalytics: true, plausibleAnalytics: true,
vscodeserver: true, vscodeserver: true,
wordpress: true wordpress: true,
ghost: true
}, },
orderBy: { createdAt: 'desc' } orderBy: { createdAt: 'desc' }
}); });
for (const service of services) { for (const service of services) {
try { try {
const { if (service.fqdn && service.destinationDockerId) {
fqdn, const {
id, fqdn,
type, id,
destinationDocker: { engine } type,
} = service; destinationDocker: { engine }
const found = db.supportedServiceTypesAndVersions.find((a) => a.name === type); } = service;
if (found) { const found = db.supportedServiceTypesAndVersions.find((a) => a.name === type);
const domain = getDomain(fqdn); if (found) {
const isHttps = fqdn.startsWith('https://'); const domain = getDomain(fqdn);
const isRunning = await checkContainer(engine, id); const isHttps = fqdn.startsWith('https://');
if (isRunning) { const isRunning = await checkContainer(engine, id);
if (isHttps) ssls.push({ domain, id, isCoolify: false }); if (isRunning) {
if (isHttps) ssls.push({ domain, id, isCoolify: false });
}
} }
} }
} catch (error) { } catch (error) {

View File

@ -103,7 +103,7 @@
} }
async function forceRestartProxy() { async function forceRestartProxy() {
const sure = confirm( const sure = confirm(
'Are you sure you want to restart the proxy? Everyting will be reconfigured in ~10 sec.' 'Are you sure you want to restart the proxy? Everything will be reconfigured in ~10 secs.'
); );
if (sure) { if (sure) {
try { try {

View File

@ -106,7 +106,7 @@
} }
async function forceRestartProxy() { async function forceRestartProxy() {
const sure = confirm( const sure = confirm(
'Are you sure you want to restart the proxy? Everyting will be reconfigured in ~10 sec.' 'Are you sure you want to restart the proxy? Everything will be reconfigured in ~10 secs.'
); );
if (sure) { if (sure) {
try { try {

View File

@ -0,0 +1,90 @@
<script lang="ts">
import CopyPasswordField from '$lib/components/CopyPasswordField.svelte';
export let readOnly;
export let service;
</script>
<div class="flex space-x-1 py-5 font-bold">
<div class="title">Ghost</div>
</div>
<div class="grid grid-cols-2 items-center px-10">
<label for="email">Default Email Address</label>
<input
name="email"
id="email"
disabled
readonly
placeholder="Email address"
value={service.ghost.defaultEmail}
required
/>
</div>
<div class="grid grid-cols-2 items-center px-10">
<label for="defaultPassword">Default Password</label>
<CopyPasswordField
id="defaultPassword"
isPasswordField
readonly
disabled
name="defaultPassword"
value={service.ghost.defaultPassword}
/>
</div>
<div class="flex space-x-1 py-5 font-bold">
<div class="title">MariaDB</div>
</div>
<div class="grid grid-cols-2 items-center px-10">
<label for="mariadbUser">Username</label>
<CopyPasswordField
name="mariadbUser"
id="mariadbUser"
value={service.ghost.mariadbUser}
readonly
disabled
/>
</div>
<div class="grid grid-cols-2 items-center px-10">
<label for="mariadbPassword">Password</label>
<CopyPasswordField
id="mariadbPassword"
isPasswordField
readonly
disabled
name="mariadbPassword"
value={service.ghost.mariadbPassword}
/>
</div>
<div class="grid grid-cols-2 items-center px-10">
<label for="mariadbDatabase">Database</label>
<input
name="mariadbDatabase"
id="mariadbDatabase"
required
readonly={readOnly}
disabled={readOnly}
bind:value={service.ghost.mariadbDatabase}
placeholder="eg: ghost_db"
/>
</div>
<div class="grid grid-cols-2 items-center px-10">
<label for="mariadbRootUser">Root DB User</label>
<CopyPasswordField
id="mariadbRootUser"
isPasswordField
readonly
disabled
name="mariadbRootUser"
value={service.ghost.mariadbRootUser}
/>
</div>
<div class="grid grid-cols-2 items-center px-10">
<label for="mariadbRootUserPassword">Root DB Password</label>
<CopyPasswordField
id="mariadbRootUserPassword"
isPasswordField
readonly
disabled
name="mariadbRootUserPassword"
value={service.ghost.mariadbRootUserPassword}
/>
</div>

View File

@ -10,6 +10,7 @@
import Setting from '$lib/components/Setting.svelte'; import Setting from '$lib/components/Setting.svelte';
import { errorNotification } from '$lib/form'; import { errorNotification } from '$lib/form';
import { toast } from '@zerodevx/svelte-toast'; import { toast } from '@zerodevx/svelte-toast';
import Ghost from './_Ghost.svelte';
import MinIo from './_MinIO.svelte'; import MinIo from './_MinIO.svelte';
import PlausibleAnalytics from './_PlausibleAnalytics.svelte'; import PlausibleAnalytics from './_PlausibleAnalytics.svelte';
import VsCodeServer from './_VSCodeServer.svelte'; import VsCodeServer from './_VSCodeServer.svelte';
@ -142,6 +143,8 @@
<VsCodeServer {service} /> <VsCodeServer {service} />
{:else if service.type === 'wordpress'} {:else if service.type === 'wordpress'}
<Wordpress bind:service {isRunning} {readOnly} /> <Wordpress bind:service {isRunning} {readOnly} />
{:else if service.type === 'ghost'}
<Ghost bind:service {readOnly} />
{/if} {/if}
</div> </div>
</form> </form>

View File

@ -35,6 +35,7 @@
} }
if (service.plausibleAnalytics?.email && service.plausibleAnalytics.username) readOnly = true; if (service.plausibleAnalytics?.email && service.plausibleAnalytics.username) readOnly = true;
if (service.wordpress?.mysqlDatabase) readOnly = true; if (service.wordpress?.mysqlDatabase) readOnly = true;
if (service.ghost?.mariadbDatabase && service.ghost.mariadbDatabase) readOnly = true;
return { return {
props: { props: {

View File

@ -40,6 +40,7 @@
import LanguageTool from '$lib/components/svg/services/LanguageTool.svelte'; import LanguageTool from '$lib/components/svg/services/LanguageTool.svelte';
import N8n from '$lib/components/svg/services/N8n.svelte'; import N8n from '$lib/components/svg/services/N8n.svelte';
import UptimeKuma from '$lib/components/svg/services/UptimeKuma.svelte'; import UptimeKuma from '$lib/components/svg/services/UptimeKuma.svelte';
import Ghost from '$lib/components/svg/services/Ghost.svelte';
const { id } = $page.params; const { id } = $page.params;
const from = $page.url.searchParams.get('from'); const from = $page.url.searchParams.get('from');
@ -83,6 +84,8 @@
<N8n isAbsolute /> <N8n isAbsolute />
{:else if type.name === 'uptimekuma'} {:else if type.name === 'uptimekuma'}
<UptimeKuma isAbsolute /> <UptimeKuma isAbsolute />
{:else if type.name === 'ghost'}
<Ghost isAbsolute />
{/if}{type.fancyName} {/if}{type.fancyName}
</button> </button>
</form> </form>

View File

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

View File

@ -0,0 +1,134 @@
import {
asyncExecShell,
createDirectories,
getDomain,
getEngine,
getUserDetails
} from '$lib/common';
import * as db from '$lib/database';
import { promises as fs } from 'fs';
import yaml from 'js-yaml';
import type { RequestHandler } from '@sveltejs/kit';
import { ErrorHandler, getServiceImage } from '$lib/database';
import { makeLabelForServices } from '$lib/buildPacks/common';
export const post: RequestHandler = async (event) => {
const { teamId, status, body } = await getUserDetails(event);
if (status === 401) return { status, body };
const { id } = event.params;
try {
const service = await db.getService({ id, teamId });
const {
type,
version,
destinationDockerId,
destinationDocker,
serviceSecret,
fqdn,
ghost: {
defaultEmail,
defaultPassword,
mariadbRootUser,
mariadbRootUserPassword,
mariadbDatabase,
mariadbPassword,
mariadbUser
}
} = service;
const network = destinationDockerId && destinationDocker.network;
const host = getEngine(destinationDocker.engine);
const { workdir } = await createDirectories({ repository: type, buildId: id });
const image = getServiceImage(type);
const domain = getDomain(fqdn);
const config = {
ghost: {
image: `${image}:${version}`,
volume: `${id}-ghost:/bitnami/ghost`,
environmentVariables: {
GHOST_HOST: domain,
GHOST_EMAIL: defaultEmail,
GHOST_PASSWORD: defaultPassword,
GHOST_DATABASE_HOST: `${id}-mariadb`,
GHOST_DATABASE_USER: mariadbUser,
GHOST_DATABASE_PASSWORD: mariadbPassword,
GHOST_DATABASE_NAME: mariadbDatabase,
GHOST_DATABASE_PORT_NUMBER: 3306
}
},
mariadb: {
image: `bitnami/mariadb:latest`,
volume: `${id}-mariadb:/bitnami/mariadb`,
environmentVariables: {
MARIADB_USER: mariadbUser,
MARIADB_PASSWORD: mariadbPassword,
MARIADB_DATABASE: mariadbDatabase,
MARIADB_ROOT_USER: mariadbRootUser,
MARIADB_ROOT_PASSWORD: mariadbRootUserPassword
}
}
};
if (serviceSecret.length > 0) {
serviceSecret.forEach((secret) => {
config.ghost.environmentVariables[secret.name] = secret.value;
});
}
const composeFile = {
version: '3.8',
services: {
[id]: {
container_name: id,
image: config.ghost.image,
networks: [network],
volumes: [config.ghost.volume],
environment: config.ghost.environmentVariables,
restart: 'always',
labels: makeLabelForServices('ghost'),
depends_on: [`${id}-mariadb`]
},
[`${id}-mariadb`]: {
container_name: `${id}-mariadb`,
image: config.mariadb.image,
networks: [network],
volumes: [config.mariadb.volume],
environment: config.mariadb.environmentVariables,
restart: 'always'
}
},
networks: {
[network]: {
external: true
}
},
volumes: {
[config.ghost.volume.split(':')[0]]: {
name: config.ghost.volume.split(':')[0]
},
[config.mariadb.volume.split(':')[0]]: {
name: config.mariadb.volume.split(':')[0]
}
}
};
console.log(JSON.stringify(composeFile.volumes));
const composeFileDestination = `${workdir}/docker-compose.yaml`;
await fs.writeFile(composeFileDestination, yaml.dump(composeFile));
try {
if (version === 'latest') {
await asyncExecShell(
`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} pull`
);
}
await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} up -d`);
return {
status: 200
};
} catch (error) {
return ErrorHandler(error);
}
} catch (error) {
return ErrorHandler(error);
}
};

View File

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

View File

@ -4,7 +4,8 @@ import {
generateDatabaseConfiguration, generateDatabaseConfiguration,
getServiceImage, getServiceImage,
getVersions, getVersions,
ErrorHandler ErrorHandler,
getServiceImages
} from '$lib/database'; } from '$lib/database';
import { dockerInstance } from '$lib/docker'; import { dockerInstance } from '$lib/docker';
import type { RequestHandler } from '@sveltejs/kit'; import type { RequestHandler } from '@sveltejs/kit';
@ -23,7 +24,13 @@ export const get: RequestHandler = async (event) => {
const host = getEngine(destinationDocker.engine); const host = getEngine(destinationDocker.engine);
const docker = dockerInstance({ destinationDocker }); const docker = dockerInstance({ destinationDocker });
const baseImage = getServiceImage(type); const baseImage = getServiceImage(type);
const images = getServiceImages(type);
docker.engine.pull(`${baseImage}:${version}`); docker.engine.pull(`${baseImage}:${version}`);
if (images?.length > 0) {
for (const image of images) {
docker.engine.pull(`${image}:latest`);
}
}
try { try {
const { stdout } = await asyncExecShell( const { stdout } = await asyncExecShell(
`DOCKER_HOST=${host} docker inspect --format '{{json .State}}' ${id}` `DOCKER_HOST=${host} docker inspect --format '{{json .State}}' ${id}`

View File

@ -41,6 +41,7 @@
import LanguageTool from '$lib/components/svg/services/LanguageTool.svelte'; import LanguageTool from '$lib/components/svg/services/LanguageTool.svelte';
import N8n from '$lib/components/svg/services/N8n.svelte'; import N8n from '$lib/components/svg/services/N8n.svelte';
import UptimeKuma from '$lib/components/svg/services/UptimeKuma.svelte'; import UptimeKuma from '$lib/components/svg/services/UptimeKuma.svelte';
import Ghost from '$lib/components/svg/services/Ghost.svelte';
export let service; export let service;
export let isRunning; export let isRunning;
@ -119,6 +120,10 @@
<a href="https://github.com/louislam/uptime-kuma" target="_blank"> <a href="https://github.com/louislam/uptime-kuma" target="_blank">
<UptimeKuma /> <UptimeKuma />
</a> </a>
{:else if service.type === 'ghost'}
<a href="https://ghost.org" target="_blank">
<Ghost />
</a>
{/if} {/if}
</div> </div>
</div> </div>

View File

@ -41,7 +41,7 @@ export const post: RequestHandler = async (event) => {
networks: [network], networks: [network],
environment: config.environmentVariables, environment: config.environmentVariables,
restart: 'always', restart: 'always',
volumes: [`${id}-ngrams:/ngrams`], volumes: [config.volume],
labels: makeLabelForServices('languagetool') labels: makeLabelForServices('languagetool')
} }
}, },
@ -51,20 +51,20 @@ export const post: RequestHandler = async (event) => {
} }
}, },
volumes: { volumes: {
[`${id}-ngrams`]: { [config.volume.split(':')[0]]: {
external: true name: config.volume.split(':')[0]
} }
} }
}; };
const composeFileDestination = `${workdir}/docker-compose.yaml`; const composeFileDestination = `${workdir}/docker-compose.yaml`;
await fs.writeFile(composeFileDestination, yaml.dump(composeFile)); await fs.writeFile(composeFileDestination, yaml.dump(composeFile));
try {
await asyncExecShell(`DOCKER_HOST=${host} docker volume create ${id}-ngrams`);
} catch (error) {
console.log(error);
}
try { try {
if (version === 'latest') {
await asyncExecShell(
`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} pull`
);
}
await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} up -d`); await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} up -d`);
return { return {
status: 200 status: 200

View File

@ -76,19 +76,13 @@ export const post: RequestHandler = async (event) => {
}, },
volumes: { volumes: {
[config.volume.split(':')[0]]: { [config.volume.split(':')[0]]: {
external: true name: config.volume.split(':')[0]
} }
} }
}; };
const composeFileDestination = `${workdir}/docker-compose.yaml`; const composeFileDestination = `${workdir}/docker-compose.yaml`;
await fs.writeFile(composeFileDestination, yaml.dump(composeFile)); await fs.writeFile(composeFileDestination, yaml.dump(composeFile));
try {
await asyncExecShell(
`DOCKER_HOST=${host} docker volume create ${config.volume.split(':')[0]}`
);
} catch (error) {
console.log(error);
}
try { try {
await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} up -d`); await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} up -d`);
await db.updateMinioService({ id, publicPort }); await db.updateMinioService({ id, publicPort });

View File

@ -59,6 +59,11 @@ export const post: RequestHandler = async (event) => {
await fs.writeFile(composeFileDestination, yaml.dump(composeFile)); await fs.writeFile(composeFileDestination, yaml.dump(composeFile));
try { try {
if (version === 'latest') {
await asyncExecShell(
`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} pull`
);
}
await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} up -d`); await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} up -d`);
return { return {
status: 200 status: 200

View File

@ -52,6 +52,11 @@ export const post: RequestHandler = async (event) => {
await fs.writeFile(composeFileDestination, yaml.dump(composeFile)); await fs.writeFile(composeFileDestination, yaml.dump(composeFile));
try { try {
if (version === 'latest') {
await asyncExecShell(
`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} pull`
);
}
await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} up -d`); await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} up -d`);
return { return {
status: 200 status: 200

View File

@ -158,29 +158,21 @@ COPY ./init-db.sh /docker-entrypoint-initdb.d/init-db.sh`;
}, },
volumes: { volumes: {
[config.postgresql.volume.split(':')[0]]: { [config.postgresql.volume.split(':')[0]]: {
external: true name: config.postgresql.volume.split(':')[0]
}, },
[config.clickhouse.volume.split(':')[0]]: { [config.clickhouse.volume.split(':')[0]]: {
external: true name: config.clickhouse.volume.split(':')[0]
} }
} }
}; };
const composeFileDestination = `${workdir}/docker-compose.yaml`; const composeFileDestination = `${workdir}/docker-compose.yaml`;
await fs.writeFile(composeFileDestination, yaml.dump(composeFile)); await fs.writeFile(composeFileDestination, yaml.dump(composeFile));
try { if (version === 'latest') {
await asyncExecShell( await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} pull`);
`DOCKER_HOST=${host} docker volume create ${config.postgresql.volume.split(':')[0]}`
);
await asyncExecShell(
`DOCKER_HOST=${host} docker volume create ${config.clickhouse.volume.split(':')[0]}`
);
} catch (error) {
console.log(error);
} }
await asyncExecShell( await asyncExecShell(
`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} up --build -d` `DOCKER_HOST=${host} docker compose -f ${composeFileDestination} up --build -d`
); );
return { return {
status: 200 status: 200
}; };

View File

@ -59,6 +59,11 @@ export const post: RequestHandler = async (event) => {
await fs.writeFile(composeFileDestination, yaml.dump(composeFile)); await fs.writeFile(composeFileDestination, yaml.dump(composeFile));
try { try {
if (version === 'latest') {
await asyncExecShell(
`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} pull`
);
}
await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} up -d`); await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} up -d`);
return { return {
status: 200 status: 200

View File

@ -52,20 +52,18 @@ export const post: RequestHandler = async (event) => {
}, },
volumes: { volumes: {
[config.volume.split(':')[0]]: { [config.volume.split(':')[0]]: {
external: true name: config.volume.split(':')[0]
} }
} }
}; };
const composeFileDestination = `${workdir}/docker-compose.yaml`; const composeFileDestination = `${workdir}/docker-compose.yaml`;
await fs.writeFile(composeFileDestination, yaml.dump(composeFile)); await fs.writeFile(composeFileDestination, yaml.dump(composeFile));
try { try {
await asyncExecShell( if (version === 'latest') {
`DOCKER_HOST=${host} docker volume create ${config.volume.split(':')[0]}` await asyncExecShell(
); `DOCKER_HOST=${host} docker compose -f ${composeFileDestination} pull`
} catch (error) { );
console.log(error); }
}
try {
await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} up -d`); await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} up -d`);
return { return {
status: 200 status: 200

View File

@ -61,29 +61,20 @@ export const post: RequestHandler = async (event) => {
}, },
volumes: { volumes: {
[config.volume.split(':')[0]]: { [config.volume.split(':')[0]]: {
external: true name: config.volume.split(':')[0]
} }
} }
}; };
const composeFileDestination = `${workdir}/docker-compose.yaml`; const composeFileDestination = `${workdir}/docker-compose.yaml`;
await fs.writeFile(composeFileDestination, yaml.dump(composeFile)); await fs.writeFile(composeFileDestination, yaml.dump(composeFile));
try { if (version === 'latest') {
await asyncExecShell( await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} pull`);
`DOCKER_HOST=${host} docker volume create ${config.volume.split(':')[0]}`
);
} catch (error) {
console.log(error);
}
try {
await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} up -d`);
return {
status: 200
};
} catch (error) {
return ErrorHandler(error);
} }
await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} up -d`);
return {
status: 200
};
} catch (error) { } catch (error) {
return ErrorHandler(error); return ErrorHandler(error);
} }

View File

@ -72,6 +72,7 @@ export const post: RequestHandler = async (event) => {
container_name: id, container_name: id,
image: config.wordpress.image, image: config.wordpress.image,
environment: config.wordpress.environmentVariables, environment: config.wordpress.environmentVariables,
volumes: [config.wordpress.volume],
networks: [network], networks: [network],
restart: 'always', restart: 'always',
depends_on: [`${id}-mysql`], depends_on: [`${id}-mysql`],
@ -80,6 +81,7 @@ export const post: RequestHandler = async (event) => {
[`${id}-mysql`]: { [`${id}-mysql`]: {
container_name: `${id}-mysql`, container_name: `${id}-mysql`,
image: config.mysql.image, image: config.mysql.image,
volumes: [config.mysql.volume],
environment: config.mysql.environmentVariables, environment: config.mysql.environmentVariables,
networks: [network], networks: [network],
restart: 'always' restart: 'always'
@ -91,29 +93,22 @@ export const post: RequestHandler = async (event) => {
} }
}, },
volumes: { volumes: {
[config.mysql.volume.split(':')[0]]: {
external: true
},
[config.wordpress.volume.split(':')[0]]: { [config.wordpress.volume.split(':')[0]]: {
external: true name: config.wordpress.volume.split(':')[0]
},
[config.mysql.volume.split(':')[0]]: {
name: config.mysql.volume.split(':')[0]
} }
} }
}; };
const composeFileDestination = `${workdir}/docker-compose.yaml`; const composeFileDestination = `${workdir}/docker-compose.yaml`;
await fs.writeFile(composeFileDestination, yaml.dump(composeFile)); await fs.writeFile(composeFileDestination, yaml.dump(composeFile));
try {
await asyncExecShell(
`DOCKER_HOST=${host} docker volume create ${config.mysql.volume.split(':')[0]}`
);
await asyncExecShell(
`DOCKER_HOST=${host} docker volume create ${config.wordpress.volume.split(':')[0]}`
);
} catch (error) {
console.log(error);
}
try { try {
if (version === 'latest') {
await asyncExecShell(
`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} pull`
);
}
await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} up -d`); await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} up -d`);
return { return {
status: 200 status: 200

BIN
static/ghost.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB