feat: Fider service
This commit is contained in:
parent
e5b1ce4eef
commit
a3fd95020d
@ -12,6 +12,7 @@
|
||||
import VaultWarden from './svg/services/VaultWarden.svelte';
|
||||
import VsCodeServer from './svg/services/VSCodeServer.svelte';
|
||||
import Wordpress from './svg/services/Wordpress.svelte';
|
||||
import Fider from './svg/services/Fider.svelte';
|
||||
</script>
|
||||
|
||||
{#if service.type === 'plausibleanalytics'}
|
||||
@ -62,4 +63,8 @@
|
||||
<a href="https://hasura.io" target="_blank">
|
||||
<Hasura />
|
||||
</a>
|
||||
{:else if service.type === 'fider'}
|
||||
<a href="https://fider.io" target="_blank">
|
||||
<Fider />
|
||||
</a>
|
||||
{/if}
|
||||
|
@ -202,5 +202,16 @@ export const supportedServiceTypesAndVersions = [
|
||||
ports: {
|
||||
main: 8080
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'fider',
|
||||
fancyName: 'Fider',
|
||||
baseImage: 'getfider/fider',
|
||||
images: ['postgres:12-alpine'],
|
||||
versions: ['stable'],
|
||||
recommendedVersion: 'stable',
|
||||
ports: {
|
||||
main: 3000
|
||||
}
|
||||
}
|
||||
];
|
||||
|
183
src/routes/services/[id]/_Services/_Fider.svelte
Normal file
183
src/routes/services/[id]/_Services/_Fider.svelte
Normal file
@ -0,0 +1,183 @@
|
||||
<script lang="ts">
|
||||
import CopyPasswordField from '$lib/components/CopyPasswordField.svelte';
|
||||
import Explainer from '$lib/components/Explainer.svelte';
|
||||
import { t } from '$lib/translations';
|
||||
import Select from 'svelte-select';
|
||||
export let service;
|
||||
export let readOnly;
|
||||
|
||||
let mailgunRegions = [
|
||||
{
|
||||
value: 'EU',
|
||||
label: 'EU'
|
||||
},
|
||||
{
|
||||
value: 'US',
|
||||
label: 'US'
|
||||
}
|
||||
];
|
||||
</script>
|
||||
|
||||
<div class="flex space-x-1 py-5 font-bold">
|
||||
<div class="title">Fider</div>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-2 items-center px-10">
|
||||
<label for="jwtSecret">JWT Secret</label>
|
||||
<CopyPasswordField
|
||||
name="jwtSecret"
|
||||
id="jwtSecret"
|
||||
isPasswordField
|
||||
value={service.fider.jwtSecret}
|
||||
readonly
|
||||
disabled
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-2 items-center px-10">
|
||||
<label for="emailNoreply">Noreply Email</label>
|
||||
<input
|
||||
name="emailNoreply"
|
||||
id="emailNoreply"
|
||||
type="email"
|
||||
required
|
||||
readonly={readOnly}
|
||||
disabled={readOnly}
|
||||
bind:value={service.fider.emailNoreply}
|
||||
placeholder="{$t('forms.eg')}: noreply@yourdomain.com"
|
||||
/>
|
||||
</div>
|
||||
<div class="flex space-x-1 py-5 font-bold">
|
||||
<div class="title">Email</div>
|
||||
</div>
|
||||
<div class="grid grid-cols-2 items-center px-10">
|
||||
<label for="emailMailgunApiKey">Mailgun API Key</label>
|
||||
<CopyPasswordField
|
||||
name="emailMailgunApiKey"
|
||||
id="emailMailgunApiKey"
|
||||
isPasswordField
|
||||
bind:value={service.fider.emailMailgunApiKey}
|
||||
readonly={readOnly}
|
||||
disabled={readOnly}
|
||||
placeholder="{$t('forms.eg')}: key-yourkeygoeshere"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-2 items-center px-10">
|
||||
<label for="emailMailgunDomain">Mailgun Domain</label>
|
||||
<input
|
||||
name="emailMailgunDomain"
|
||||
id="emailMailgunDomain"
|
||||
readonly={readOnly}
|
||||
disabled={readOnly}
|
||||
bind:value={service.fider.emailMailgunDomain}
|
||||
placeholder="{$t('forms.eg')}: yourdomain.com"
|
||||
/>
|
||||
</div>
|
||||
<div class="grid grid-cols-2 items-center px-10">
|
||||
<label for="emailMailgunRegion">Mailgun Region</label>
|
||||
<div class="custom-select-wrapper">
|
||||
<Select
|
||||
id="baseBuildImages"
|
||||
items={mailgunRegions}
|
||||
on:select={(event) => (service.fider.emailMailgunRegion = event.detail.value)}
|
||||
value={service.fider.emailMailgunRegion || 'EU'}
|
||||
isClearable={false}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex space-x-1 py-5 px-10 font-bold">
|
||||
<div class="text-lg">Or</div>
|
||||
</div>
|
||||
<div class="grid grid-cols-2 items-center px-10">
|
||||
<label for="emailSmtpHost">SMTP Host</label>
|
||||
<input
|
||||
name="emailSmtpHost"
|
||||
id="emailSmtpHost"
|
||||
readonly={readOnly}
|
||||
disabled={readOnly}
|
||||
bind:value={service.fider.emailSmtpHost}
|
||||
placeholder="{$t('forms.eg')}: smtp.yourdomain.com"
|
||||
/>
|
||||
</div>
|
||||
<div class="grid grid-cols-2 items-center px-10">
|
||||
<label for="emailSmtpPort">SMTP Port</label>
|
||||
<input
|
||||
name="emailSmtpPort"
|
||||
id="emailSmtpPort"
|
||||
readonly={readOnly}
|
||||
disabled={readOnly}
|
||||
bind:value={service.fider.emailSmtpPort}
|
||||
placeholder="{$t('forms.eg')}: 587"
|
||||
/>
|
||||
</div>
|
||||
<div class="grid grid-cols-2 items-center px-10">
|
||||
<label for="emailSmtpUser">SMTP User</label>
|
||||
<input
|
||||
name="emailSmtpUser"
|
||||
id="emailSmtpUser"
|
||||
readonly={readOnly}
|
||||
disabled={readOnly}
|
||||
bind:value={service.fider.emailSmtpUser}
|
||||
placeholder="{$t('forms.eg')}: user@yourdomain.com"
|
||||
/>
|
||||
</div>
|
||||
<div class="grid grid-cols-2 items-center px-10">
|
||||
<label for="emailSmtpPassword">SMTP Password</label>
|
||||
<CopyPasswordField
|
||||
name="emailSmtpPassword"
|
||||
id="emailSmtpPassword"
|
||||
isPasswordField
|
||||
value={service.fider.emailSmtpPassword}
|
||||
readonly={readOnly}
|
||||
disabled={readOnly}
|
||||
placeholder="{$t('forms.eg')}: s0m3p4ssw0rd"
|
||||
/>
|
||||
</div>
|
||||
<div class="grid grid-cols-2 items-center px-10">
|
||||
<label for="emailSmtpEnableStartTls">SMTP Start TLS</label>
|
||||
<input
|
||||
name="emailSmtpEnableStartTls"
|
||||
id="emailSmtpEnableStartTls"
|
||||
readonly={readOnly}
|
||||
disabled={readOnly}
|
||||
bind:value={service.fider.emailSmtpEnableStartTls}
|
||||
placeholder="{$t('forms.eg')}: true"
|
||||
/>
|
||||
</div>
|
||||
<div class="flex space-x-1 py-5 font-bold">
|
||||
<div class="title">PostgreSQL</div>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-2 items-center px-10">
|
||||
<label for="postgresqlUser">{$t('forms.username')}</label>
|
||||
<CopyPasswordField
|
||||
name="postgresqlUser"
|
||||
id="postgresqlUser"
|
||||
value={service.fider.postgresqlUser}
|
||||
readonly
|
||||
disabled
|
||||
/>
|
||||
</div>
|
||||
<div class="grid grid-cols-2 items-center px-10">
|
||||
<label for="postgresqlPassword">{$t('forms.password')}</label>
|
||||
<CopyPasswordField
|
||||
id="postgresqlPassword"
|
||||
isPasswordField
|
||||
readonly
|
||||
disabled
|
||||
name="postgresqlPassword"
|
||||
value={service.fider.postgresqlPassword}
|
||||
/>
|
||||
</div>
|
||||
<div class="grid grid-cols-2 items-center px-10">
|
||||
<label for="postgresqlDatabase">{$t('index.database')}</label>
|
||||
<CopyPasswordField
|
||||
name="postgresqlDatabase"
|
||||
id="postgresqlDatabase"
|
||||
value={service.fider.postgresqlDatabase}
|
||||
readonly
|
||||
disabled
|
||||
/>
|
||||
</div>
|
@ -12,6 +12,7 @@
|
||||
import { errorNotification } from '$lib/form';
|
||||
import { t } from '$lib/translations';
|
||||
import { toast } from '@zerodevx/svelte-toast';
|
||||
import Fider from './_Fider.svelte';
|
||||
import Ghost from './_Ghost.svelte';
|
||||
import Hasura from './_Hasura.svelte';
|
||||
import MeiliSearch from './_MeiliSearch.svelte';
|
||||
@ -175,6 +176,8 @@
|
||||
<Umami bind:service />
|
||||
{:else if service.type === 'hasura'}
|
||||
<Hasura bind:service />
|
||||
{:else if service.type === 'fider'}
|
||||
<Fider bind:service {readOnly} />
|
||||
{/if}
|
||||
</div>
|
||||
</form>
|
||||
|
@ -45,6 +45,7 @@
|
||||
import MeiliSearch from '$lib/components/svg/services/MeiliSearch.svelte';
|
||||
import Umami from '$lib/components/svg/services/Umami.svelte';
|
||||
import Hasura from '$lib/components/svg/services/Hasura.svelte';
|
||||
import Fider from '$lib/components/svg/services/Fider.svelte';
|
||||
|
||||
const { id } = $page.params;
|
||||
const from = $page.url.searchParams.get('from');
|
||||
@ -96,6 +97,8 @@
|
||||
<Umami isAbsolute />
|
||||
{:else if type.name === 'hasura'}
|
||||
<Hasura isAbsolute />
|
||||
{:else if type.name === 'fider'}
|
||||
<Fider isAbsolute />
|
||||
{/if}{type.fancyName}
|
||||
</button>
|
||||
</form>
|
||||
|
57
src/routes/services/[id]/fider/index.json.ts
Normal file
57
src/routes/services/[id]/fider/index.json.ts
Normal file
@ -0,0 +1,57 @@
|
||||
import { getUserDetails } from '$lib/common';
|
||||
import { encrypt } from '$lib/crypto';
|
||||
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,
|
||||
fider: {
|
||||
emailNoreply,
|
||||
emailMailgunApiKey,
|
||||
emailMailgunDomain,
|
||||
emailMailgunRegion,
|
||||
emailSmtpHost,
|
||||
emailSmtpPort,
|
||||
emailSmtpUser,
|
||||
emailSmtpPassword,
|
||||
emailSmtpEnableStartTls
|
||||
}
|
||||
} = await event.request.json();
|
||||
|
||||
if (fqdn) fqdn = fqdn.toLowerCase();
|
||||
if (emailNoreply) emailNoreply = emailNoreply.toLowerCase();
|
||||
if (emailSmtpHost) emailSmtpHost = emailSmtpHost.toLowerCase();
|
||||
if (emailSmtpPassword) {
|
||||
emailSmtpPassword = encrypt(emailSmtpPassword);
|
||||
}
|
||||
if (emailSmtpPort) emailSmtpPort = Number(emailSmtpPort);
|
||||
if (emailSmtpEnableStartTls) emailSmtpEnableStartTls = Boolean(emailSmtpEnableStartTls);
|
||||
|
||||
try {
|
||||
await db.updateFiderService({
|
||||
id,
|
||||
fqdn,
|
||||
name,
|
||||
emailNoreply,
|
||||
emailMailgunApiKey,
|
||||
emailMailgunDomain,
|
||||
emailMailgunRegion,
|
||||
emailSmtpHost,
|
||||
emailSmtpPort,
|
||||
emailSmtpUser,
|
||||
emailSmtpPassword,
|
||||
emailSmtpEnableStartTls
|
||||
});
|
||||
return { status: 201 };
|
||||
} catch (error) {
|
||||
return ErrorHandler(error);
|
||||
}
|
||||
};
|
147
src/routes/services/[id]/fider/start.json.ts
Normal file
147
src/routes/services/[id]/fider/start.json.ts
Normal file
@ -0,0 +1,147 @@
|
||||
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';
|
||||
import type { ComposeFile } from '$lib/types/composeFile';
|
||||
import type { Service, DestinationDocker, Prisma } from '@prisma/client';
|
||||
|
||||
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: Service & Prisma.ServiceInclude & { destinationDocker: DestinationDocker } =
|
||||
await db.getService({ id, teamId });
|
||||
const {
|
||||
type,
|
||||
version,
|
||||
fqdn,
|
||||
destinationDockerId,
|
||||
destinationDocker,
|
||||
serviceSecret,
|
||||
fider: {
|
||||
postgresqlUser,
|
||||
postgresqlPassword,
|
||||
postgresqlDatabase,
|
||||
jwtSecret,
|
||||
emailNoreply,
|
||||
emailMailgunApiKey,
|
||||
emailMailgunDomain,
|
||||
emailSmtpHost,
|
||||
emailSmtpPort,
|
||||
emailSmtpUser,
|
||||
emailSmtpPassword,
|
||||
emailSmtpEnableStartTls
|
||||
}
|
||||
} = 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 = {
|
||||
fider: {
|
||||
image: `${image}:${version}`,
|
||||
environmentVariables: {
|
||||
HOST_DOMAIN: domain,
|
||||
DATABASE_URL: `postgresql://${postgresqlUser}:${postgresqlPassword}@${id}-postgresql:5432/${postgresqlDatabase}?sslmode=disable`,
|
||||
JWT_SECRET: `${jwtSecret.replace(/\$/g, '$$$')}`,
|
||||
EMAIL_NOREPLY: emailNoreply,
|
||||
EMAIL_MAILGUN_API: emailMailgunApiKey || null,
|
||||
EMAIL_MAILGUN_DOMAIN: emailMailgunDomain || null
|
||||
}
|
||||
},
|
||||
postgresql: {
|
||||
image: 'postgres:12-alpine',
|
||||
volume: `${id}-postgresql-data:/var/lib/postgresql/data`,
|
||||
environmentVariables: {
|
||||
POSTGRES_USER: postgresqlUser,
|
||||
POSTGRES_PASSWORD: postgresqlPassword,
|
||||
POSTGRES_DB: postgresqlDatabase
|
||||
}
|
||||
}
|
||||
};
|
||||
if (serviceSecret.length > 0) {
|
||||
serviceSecret.forEach((secret) => {
|
||||
config.fider.environmentVariables[secret.name] = secret.value;
|
||||
});
|
||||
}
|
||||
|
||||
const composeFile: ComposeFile = {
|
||||
version: '3.8',
|
||||
services: {
|
||||
[id]: {
|
||||
container_name: id,
|
||||
image: config.fider.image,
|
||||
environment: config.fider.environmentVariables,
|
||||
networks: [network],
|
||||
volumes: [],
|
||||
restart: 'always',
|
||||
labels: makeLabelForServices('fider'),
|
||||
deploy: {
|
||||
restart_policy: {
|
||||
condition: 'on-failure',
|
||||
delay: '5s',
|
||||
max_attempts: 3,
|
||||
window: '120s'
|
||||
}
|
||||
},
|
||||
depends_on: [`${id}-postgresql`]
|
||||
},
|
||||
[`${id}-postgresql`]: {
|
||||
image: config.postgresql.image,
|
||||
container_name: `${id}-postgresql`,
|
||||
environment: config.postgresql.environmentVariables,
|
||||
networks: [network],
|
||||
volumes: [config.postgresql.volume],
|
||||
restart: 'always',
|
||||
deploy: {
|
||||
restart_policy: {
|
||||
condition: 'on-failure',
|
||||
delay: '5s',
|
||||
max_attempts: 3,
|
||||
window: '120s'
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
networks: {
|
||||
[network]: {
|
||||
external: true
|
||||
}
|
||||
},
|
||||
volumes: {
|
||||
[config.postgresql.volume.split(':')[0]]: {
|
||||
name: config.postgresql.volume.split(':')[0]
|
||||
}
|
||||
}
|
||||
};
|
||||
const composeFileDestination = `${workdir}/docker-compose.yaml`;
|
||||
await fs.writeFile(composeFileDestination, yaml.dump(composeFile));
|
||||
|
||||
try {
|
||||
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) {
|
||||
console.log(error);
|
||||
return ErrorHandler(error);
|
||||
}
|
||||
} catch (error) {
|
||||
return ErrorHandler(error);
|
||||
}
|
||||
};
|
42
src/routes/services/[id]/fider/stop.json.ts
Normal file
42
src/routes/services/[id]/fider/stop.json.ts
Normal file
@ -0,0 +1,42 @@
|
||||
import { getUserDetails, removeDestinationDocker } from '$lib/common';
|
||||
import * as db from '$lib/database';
|
||||
import { ErrorHandler } from '$lib/database';
|
||||
import { checkContainer, stopTcpHttpProxy } 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 } = service;
|
||||
if (destinationDockerId) {
|
||||
const engine = destinationDocker.engine;
|
||||
|
||||
try {
|
||||
const found = await checkContainer(engine, id);
|
||||
if (found) {
|
||||
await removeDestinationDocker({ id, engine });
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
try {
|
||||
const found = await checkContainer(engine, `${id}-postgresql`);
|
||||
if (found) {
|
||||
await removeDestinationDocker({ id: `${id}-postgresql`, engine });
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
return {
|
||||
status: 200
|
||||
};
|
||||
} catch (error) {
|
||||
return ErrorHandler(error);
|
||||
}
|
||||
};
|
@ -17,6 +17,7 @@
|
||||
import { getDomain } from '$lib/components/common';
|
||||
import Umami from '$lib/components/svg/services/Umami.svelte';
|
||||
import Hasura from '$lib/components/svg/services/Hasura.svelte';
|
||||
import Fider from '$lib/components/svg/services/Fider.svelte';
|
||||
|
||||
export let services;
|
||||
async function newService() {
|
||||
@ -92,6 +93,8 @@
|
||||
<Umami isAbsolute />
|
||||
{:else if service.type === 'hasura'}
|
||||
<Hasura isAbsolute />
|
||||
{:else if service.type === 'fider'}
|
||||
<Fider isAbsolute />
|
||||
{/if}
|
||||
<div class="truncate text-center text-xl font-bold">
|
||||
{service.name}
|
||||
@ -143,6 +146,8 @@
|
||||
<Umami isAbsolute />
|
||||
{:else if service.type === 'hasura'}
|
||||
<Hasura isAbsolute />
|
||||
{:else if service.type === 'fider'}
|
||||
<Fider isAbsolute />
|
||||
{/if}
|
||||
<div class="truncate text-center text-xl font-bold">
|
||||
{service.name}
|
||||
|
Loading…
Reference in New Issue
Block a user