mirror of
https://github.com/ershisan99/coolify.git
synced 2025-12-28 20:59:23 +00:00
Compare commits
54 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
67fc2fd3c0 | ||
|
|
d3a1bbc3d0 | ||
|
|
0078574ee6 | ||
|
|
7cfd313531 | ||
|
|
e7919e9a1b | ||
|
|
98073202e9 | ||
|
|
8dee345f85 | ||
|
|
9161882f33 | ||
|
|
eef313665b | ||
|
|
53e70fbfcb | ||
|
|
05a1721499 | ||
|
|
2f772080b8 | ||
|
|
a5548c080c | ||
|
|
7e0a1ecc80 | ||
|
|
3f2dcccc07 | ||
|
|
adc5965b32 | ||
|
|
6088f2e573 | ||
|
|
fc705746c0 | ||
|
|
8182359fe4 | ||
|
|
e7ae15162c | ||
|
|
12ca20432d | ||
|
|
8b7406e168 | ||
|
|
9d6317f782 | ||
|
|
d8bdb73140 | ||
|
|
476db15431 | ||
|
|
20ce356296 | ||
|
|
ea594dcbc6 | ||
|
|
021b9746a8 | ||
|
|
c4615ae557 | ||
|
|
95a5089bdc | ||
|
|
cef1fba281 | ||
|
|
5c7859a258 | ||
|
|
986cdae5b0 | ||
|
|
3b11e28d6c | ||
|
|
eba63e8e76 | ||
|
|
2fc65e3b42 | ||
|
|
7d504ab2bf | ||
|
|
216c7efd42 | ||
|
|
8c4149db16 | ||
|
|
20ac8f69ea | ||
|
|
1db3d7a6fb | ||
|
|
b1c1138cf8 | ||
|
|
00b1a4f174 | ||
|
|
86cc665b58 | ||
|
|
e26dd578ef | ||
|
|
f1f3217052 | ||
|
|
8f14fd89ef | ||
|
|
26e4d52a61 | ||
|
|
319c647147 | ||
|
|
f4cd93bd36 | ||
|
|
5a80bb1d2a | ||
|
|
1126dcacf5 | ||
|
|
bdf9a73d19 | ||
|
|
1f73b83a79 |
@@ -20,7 +20,8 @@
|
|||||||
"svelte.svelte-vscode",
|
"svelte.svelte-vscode",
|
||||||
"ardenivanov.svelte-intellisense",
|
"ardenivanov.svelte-intellisense",
|
||||||
"Prisma.prisma",
|
"Prisma.prisma",
|
||||||
"bradlc.vscode-tailwindcss"
|
"bradlc.vscode-tailwindcss",
|
||||||
|
"waderyan.gitblame"
|
||||||
],
|
],
|
||||||
// Use 'forwardPorts' to make a list of ports inside the container available locally.
|
// Use 'forwardPorts' to make a list of ports inside the container available locally.
|
||||||
"forwardPorts": [3000, 3001],
|
"forwardPorts": [3000, 3001],
|
||||||
|
|||||||
62
.github/workflows/production-release.yml
vendored
62
.github/workflows/production-release.yml
vendored
@@ -5,11 +5,11 @@ on:
|
|||||||
types: [released]
|
types: [released]
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
making-something-cool:
|
arm64-build:
|
||||||
runs-on: ubuntu-latest
|
runs-on: [self-hosted, arm64]
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v3
|
||||||
- name: Set up QEMU
|
- name: Set up QEMU
|
||||||
uses: docker/setup-qemu-action@v1
|
uses: docker/setup-qemu-action@v1
|
||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
@@ -26,11 +26,59 @@ jobs:
|
|||||||
uses: docker/build-push-action@v2
|
uses: docker/build-push-action@v2
|
||||||
with:
|
with:
|
||||||
context: .
|
context: .
|
||||||
platforms: linux/amd64,linux/arm64
|
platforms: linux/arm64
|
||||||
push: true
|
push: true
|
||||||
tags: coollabsio/coolify:latest,coollabsio/coolify:${{steps.package-version.outputs.current-version}}
|
tags: coollabsio/coolify:${{steps.package-version.outputs.current-version}}-arm64
|
||||||
cache-from: type=registry,ref=coollabsio/coolify:buildcache
|
cache-from: type=registry,ref=coollabsio/coolify:buildcache-arm64
|
||||||
cache-to: type=registry,ref=coollabsio/coolify:buildcache,mode=max
|
cache-to: type=registry,ref=coollabsio/coolify:buildcache-arm64,mode=max
|
||||||
|
amd64-build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
- name: Set up QEMU
|
||||||
|
uses: docker/setup-qemu-action@v2
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v2
|
||||||
|
- name: Login to DockerHub
|
||||||
|
uses: docker/login-action@v2
|
||||||
|
with:
|
||||||
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
|
- name: Get current package version
|
||||||
|
uses: martinbeentjes/npm-get-version-action@v1.2.3
|
||||||
|
id: package-version
|
||||||
|
- name: Build and push
|
||||||
|
uses: docker/build-push-action@v3
|
||||||
|
with:
|
||||||
|
context: .
|
||||||
|
platforms: linux/amd64
|
||||||
|
push: true
|
||||||
|
tags: coollabsio/coolify:${{steps.package-version.outputs.current-version}}-amd64
|
||||||
|
cache-from: type=registry,ref=coollabsio/coolify:buildcache-amd64
|
||||||
|
cache-to: type=registry,ref=coollabsio/coolify:buildcache-amd64,mode=max
|
||||||
|
merge-manifest:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs: [amd64-build, arm64-build]
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
- name: Set up QEMU
|
||||||
|
uses: docker/setup-qemu-action@v2
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v2
|
||||||
|
- name: Login to DockerHub
|
||||||
|
uses: docker/login-action@v2
|
||||||
|
with:
|
||||||
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
|
- name: Get current package version
|
||||||
|
uses: martinbeentjes/npm-get-version-action@v1.2.3
|
||||||
|
id: package-version
|
||||||
|
- name: Create & publish manifest
|
||||||
|
run: |
|
||||||
|
docker manifest create coollabsio/coolify:${{steps.package-version.outputs.current-version}} --amend coollabsio/coolify:${{steps.package-version.outputs.current-version}}-amd64 --amend coollabsio/coolify:${{steps.package-version.outputs.current-version}}-arm64
|
||||||
|
docker manifest push coollabsio/coolify:${{steps.package-version.outputs.current-version}}
|
||||||
- uses: sarisia/actions-status-discord@v1
|
- uses: sarisia/actions-status-discord@v1
|
||||||
if: always()
|
if: always()
|
||||||
with:
|
with:
|
||||||
|
|||||||
67
.github/workflows/release-candidate.yml
vendored
67
.github/workflows/release-candidate.yml
vendored
@@ -1,17 +1,16 @@
|
|||||||
name: release-candidate
|
name: release-candidate
|
||||||
|
|
||||||
on:
|
on:
|
||||||
release:
|
release:
|
||||||
types: [prereleased]
|
types: [prereleased]
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
making-something-cool:
|
arm64-making-something-cool:
|
||||||
runs-on: ubuntu-latest
|
runs-on: [self-hosted, arm64]
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v3
|
||||||
with:
|
with:
|
||||||
ref: 'next'
|
ref: "next"
|
||||||
- name: Set up QEMU
|
- name: Set up QEMU
|
||||||
uses: docker/setup-qemu-action@v1
|
uses: docker/setup-qemu-action@v1
|
||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
@@ -28,12 +27,64 @@ jobs:
|
|||||||
uses: docker/build-push-action@v2
|
uses: docker/build-push-action@v2
|
||||||
with:
|
with:
|
||||||
context: .
|
context: .
|
||||||
platforms: linux/amd64,linux/arm64
|
platforms: linux/arm64
|
||||||
push: true
|
push: true
|
||||||
tags: coollabsio/coolify:${{github.event.release.name}}
|
tags: coollabsio/coolify:${{github.event.release.name}}-arm64
|
||||||
cache-from: type=registry,ref=coollabsio/coolify:buildcache-rc
|
cache-from: type=registry,ref=coollabsio/coolify:buildcache-rc-arm64
|
||||||
cache-to: type=registry,ref=coollabsio/coolify:buildcache-rc,mode=max
|
cache-to: type=registry,ref=coollabsio/coolify:buildcache-rc-arm64,mode=max
|
||||||
- uses: sarisia/actions-status-discord@v1
|
- uses: sarisia/actions-status-discord@v1
|
||||||
if: always()
|
if: always()
|
||||||
with:
|
with:
|
||||||
webhook: ${{ secrets.DISCORD_WEBHOOK_DEV_RELEASE_CHANNEL }}
|
webhook: ${{ secrets.DISCORD_WEBHOOK_DEV_RELEASE_CHANNEL }}
|
||||||
|
amd64-making-something-cool:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
with:
|
||||||
|
ref: "next"
|
||||||
|
- name: Set up QEMU
|
||||||
|
uses: docker/setup-qemu-action@v2
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v2
|
||||||
|
- name: Login to DockerHub
|
||||||
|
uses: docker/login-action@v2
|
||||||
|
with:
|
||||||
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
|
- name: Get current package version
|
||||||
|
uses: martinbeentjes/npm-get-version-action@v1.2.3
|
||||||
|
id: package-version
|
||||||
|
- name: Build and push
|
||||||
|
uses: docker/build-push-action@v3
|
||||||
|
with:
|
||||||
|
context: .
|
||||||
|
platforms: linux/amd64
|
||||||
|
push: true
|
||||||
|
tags: coollabsio/coolify:${{github.event.release.name}}-amd64
|
||||||
|
cache-from: type=registry,ref=coollabsio/coolify:buildcache-rc-amd64
|
||||||
|
cache-to: type=registry,ref=coollabsio/coolify:buildcache-rc-amd64,mode=max
|
||||||
|
- uses: sarisia/actions-status-discord@v1
|
||||||
|
if: always()
|
||||||
|
with:
|
||||||
|
webhook: ${{ secrets.DISCORD_WEBHOOK_DEV_RELEASE_CHANNEL }}
|
||||||
|
merge-manifest-to-be-cool:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs: [arm64-making-something-cool, amd64-making-something-cool]
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
- name: Set up QEMU
|
||||||
|
uses: docker/setup-qemu-action@v2
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v2
|
||||||
|
- name: Login to DockerHub
|
||||||
|
uses: docker/login-action@v2
|
||||||
|
with:
|
||||||
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
|
- name: Create & publish manifest
|
||||||
|
run: |
|
||||||
|
docker manifest create coollabsio/coolify:${{github.event.release.name}} --amend coollabsio/coolify:${{github.event.release.name}}-amd64 --amend coollabsio/coolify:${{github.event.release.name}}-arm64
|
||||||
|
docker manifest push coollabsio/coolify:${{github.event.release.name}}
|
||||||
|
|
||||||
|
|||||||
64
.github/workflows/staging-release.yml
vendored
64
.github/workflows/staging-release.yml
vendored
@@ -6,11 +6,13 @@ on:
|
|||||||
- next
|
- next
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
staging-release:
|
arm64-making-something-cool:
|
||||||
runs-on: ubuntu-latest
|
runs-on: [self-hosted, arm64]
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v3
|
||||||
|
with:
|
||||||
|
ref: "next"
|
||||||
- name: Set up QEMU
|
- name: Set up QEMU
|
||||||
uses: docker/setup-qemu-action@v1
|
uses: docker/setup-qemu-action@v1
|
||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
@@ -20,15 +22,65 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
|
- name: Get current package version
|
||||||
|
uses: martinbeentjes/npm-get-version-action@v1.2.3
|
||||||
|
id: package-version
|
||||||
- name: Build and push
|
- name: Build and push
|
||||||
uses: docker/build-push-action@v2
|
uses: docker/build-push-action@v2
|
||||||
|
with:
|
||||||
|
context: .
|
||||||
|
platforms: linux/arm64
|
||||||
|
push: true
|
||||||
|
tags: coollabsio/coolify:next-arm64
|
||||||
|
cache-from: type=registry,ref=coollabsio/coolify:buildcache-next-arm64
|
||||||
|
cache-to: type=registry,ref=coollabsio/coolify:buildcache-next-arm64,mode=max
|
||||||
|
amd64-making-something-cool:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
with:
|
||||||
|
ref: "next"
|
||||||
|
- name: Set up QEMU
|
||||||
|
uses: docker/setup-qemu-action@v2
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v2
|
||||||
|
- name: Login to DockerHub
|
||||||
|
uses: docker/login-action@v2
|
||||||
|
with:
|
||||||
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
|
- name: Get current package version
|
||||||
|
uses: martinbeentjes/npm-get-version-action@v1.2.3
|
||||||
|
id: package-version
|
||||||
|
- name: Build and push
|
||||||
|
uses: docker/build-push-action@v3
|
||||||
with:
|
with:
|
||||||
context: .
|
context: .
|
||||||
platforms: linux/amd64
|
platforms: linux/amd64
|
||||||
push: true
|
push: true
|
||||||
tags: coollabsio/coolify:next
|
tags: coollabsio/coolify:next-amd64,coollabsio/coolify:next-test
|
||||||
cache-from: type=registry,ref=coollabsio/coolify:buildcache-next
|
cache-from: type=registry,ref=coollabsio/coolify:buildcache-next-amd64
|
||||||
cache-to: type=registry,ref=coollabsio/coolify:buildcache-next,mode=max
|
cache-to: type=registry,ref=coollabsio/coolify:buildcache-next-amd64,mode=max
|
||||||
|
merge-manifest-to-be-cool:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs: [arm64-making-something-cool, amd64-making-something-cool]
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
- name: Set up QEMU
|
||||||
|
uses: docker/setup-qemu-action@v2
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v2
|
||||||
|
- name: Login to DockerHub
|
||||||
|
uses: docker/login-action@v2
|
||||||
|
with:
|
||||||
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
|
- name: Create & publish manifest
|
||||||
|
run: |
|
||||||
|
docker manifest create coollabsio/coolify:next --amend coollabsio/coolify:next-amd64 --amend coollabsio/coolify:next-arm64
|
||||||
|
docker manifest push coollabsio/coolify:next
|
||||||
- uses: sarisia/actions-status-discord@v1
|
- uses: sarisia/actions-status-discord@v1
|
||||||
if: always()
|
if: always()
|
||||||
with:
|
with:
|
||||||
|
|||||||
11
Dockerfile
11
Dockerfile
@@ -1,8 +1,11 @@
|
|||||||
|
ARG PNPM_VERSION=7.11.0
|
||||||
|
ARG NPM_VERSION=8.19.1
|
||||||
|
|
||||||
FROM node:18-slim as build
|
FROM node:18-slim as build
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
RUN apt update && apt -y install curl
|
RUN apt update && apt -y install curl
|
||||||
RUN curl -sL https://unpkg.com/@pnpm/self-installer | node
|
RUN npm --no-update-notifier --no-fund --global install pnpm@${PNPM_VERSION}
|
||||||
|
|
||||||
COPY . .
|
COPY . .
|
||||||
RUN pnpm install
|
RUN pnpm install
|
||||||
@@ -14,8 +17,10 @@ WORKDIR /app
|
|||||||
ENV NODE_ENV production
|
ENV NODE_ENV production
|
||||||
ARG TARGETPLATFORM
|
ARG TARGETPLATFORM
|
||||||
|
|
||||||
RUN apt update && apt -y install git git-lfs openssh-client curl jq cmake sqlite3 openssl psmisc python3 && apt-get clean autoclean && apt-get autoremove --yes && rm -rf /var/lib/{apt,dpkg,cache,log}/
|
RUN apt update && apt -y install --no-install-recommends ca-certificates git git-lfs openssh-client curl jq cmake sqlite3 openssl psmisc python3
|
||||||
RUN curl -sL https://unpkg.com/@pnpm/self-installer | node
|
RUN apt-get clean autoclean && apt-get autoremove --yes && rm -rf /var/lib/{apt,dpkg,cache,log}/
|
||||||
|
RUN npm --no-update-notifier --no-fund --global install pnpm@${PNPM_VERSION}
|
||||||
|
RUN npm install -g npm@${PNPM_VERSION}
|
||||||
|
|
||||||
RUN mkdir -p ~/.docker/cli-plugins/
|
RUN mkdir -p ~/.docker/cli-plugins/
|
||||||
# https://download.docker.com/linux/static/stable/
|
# https://download.docker.com/linux/static/stable/
|
||||||
|
|||||||
@@ -0,0 +1,2 @@
|
|||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "Setting" ADD COLUMN "isAPIDebuggingEnabled" BOOLEAN DEFAULT false;
|
||||||
@@ -11,6 +11,7 @@ datasource db {
|
|||||||
model Setting {
|
model Setting {
|
||||||
id String @id @default(cuid())
|
id String @id @default(cuid())
|
||||||
fqdn String? @unique
|
fqdn String? @unique
|
||||||
|
isAPIDebuggingEnabled Boolean? @default(false)
|
||||||
isRegistrationEnabled Boolean @default(false)
|
isRegistrationEnabled Boolean @default(false)
|
||||||
dualCerts Boolean @default(false)
|
dualCerts Boolean @default(false)
|
||||||
minPort Int @default(9000)
|
minPort Int @default(9000)
|
||||||
|
|||||||
@@ -17,7 +17,6 @@ const algorithm = 'aes-256-ctr';
|
|||||||
|
|
||||||
async function main() {
|
async function main() {
|
||||||
// Enable registration for the first user
|
// Enable registration for the first user
|
||||||
// Set initial HAProxy password
|
|
||||||
const settingsFound = await prisma.setting.findFirst({});
|
const settingsFound = await prisma.setting.findFirst({});
|
||||||
if (!settingsFound) {
|
if (!settingsFound) {
|
||||||
await prisma.setting.create({
|
await prisma.setting.create({
|
||||||
@@ -25,7 +24,8 @@ async function main() {
|
|||||||
isRegistrationEnabled: true,
|
isRegistrationEnabled: true,
|
||||||
proxyPassword: encrypt(generatePassword()),
|
proxyPassword: encrypt(generatePassword()),
|
||||||
proxyUser: cuid(),
|
proxyUser: cuid(),
|
||||||
arch: process.arch
|
arch: process.arch,
|
||||||
|
DNSServers: '1.1.1.1,8.8.8.8'
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import env from '@fastify/env';
|
|||||||
import cookie from '@fastify/cookie';
|
import cookie from '@fastify/cookie';
|
||||||
import path, { join } from 'path';
|
import path, { join } from 'path';
|
||||||
import autoLoad from '@fastify/autoload';
|
import autoLoad from '@fastify/autoload';
|
||||||
import { asyncExecShell, createRemoteEngineConfiguration, isDev, listSettings, prisma, version } from './lib/common';
|
import { asyncExecShell, createRemoteEngineConfiguration, getDomain, isDev, listSettings, prisma, version } from './lib/common';
|
||||||
import { scheduler } from './lib/scheduler';
|
import { scheduler } from './lib/scheduler';
|
||||||
import { compareVersions } from 'compare-versions';
|
import { compareVersions } from 'compare-versions';
|
||||||
import Graceful from '@ladjs/graceful'
|
import Graceful from '@ladjs/graceful'
|
||||||
@@ -26,122 +26,143 @@ declare module 'fastify' {
|
|||||||
|
|
||||||
const port = isDev ? 3001 : 3000;
|
const port = isDev ? 3001 : 3000;
|
||||||
const host = '0.0.0.0';
|
const host = '0.0.0.0';
|
||||||
const fastify = Fastify({
|
prisma.setting.findFirst().then(async (settings) => {
|
||||||
logger: false,
|
const fastify = Fastify({
|
||||||
trustProxy: true
|
logger: settings?.isAPIDebuggingEnabled || false,
|
||||||
});
|
trustProxy: true
|
||||||
const schema = {
|
|
||||||
type: 'object',
|
|
||||||
required: ['COOLIFY_SECRET_KEY', 'COOLIFY_DATABASE_URL', 'COOLIFY_IS_ON'],
|
|
||||||
properties: {
|
|
||||||
COOLIFY_APP_ID: {
|
|
||||||
type: 'string',
|
|
||||||
},
|
|
||||||
COOLIFY_SECRET_KEY: {
|
|
||||||
type: 'string',
|
|
||||||
},
|
|
||||||
COOLIFY_DATABASE_URL: {
|
|
||||||
type: 'string',
|
|
||||||
default: 'file:../db/dev.db'
|
|
||||||
},
|
|
||||||
COOLIFY_SENTRY_DSN: {
|
|
||||||
type: 'string',
|
|
||||||
default: null
|
|
||||||
},
|
|
||||||
COOLIFY_IS_ON: {
|
|
||||||
type: 'string',
|
|
||||||
default: 'docker'
|
|
||||||
},
|
|
||||||
COOLIFY_WHITE_LABELED: {
|
|
||||||
type: 'string',
|
|
||||||
default: 'false'
|
|
||||||
},
|
|
||||||
COOLIFY_WHITE_LABELED_ICON: {
|
|
||||||
type: 'string',
|
|
||||||
default: null
|
|
||||||
},
|
|
||||||
COOLIFY_AUTO_UPDATE: {
|
|
||||||
type: 'string',
|
|
||||||
default: 'false'
|
|
||||||
},
|
|
||||||
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const options = {
|
|
||||||
schema,
|
|
||||||
dotenv: true
|
|
||||||
};
|
|
||||||
fastify.register(env, options);
|
|
||||||
if (!isDev) {
|
|
||||||
fastify.register(serve, {
|
|
||||||
root: path.join(__dirname, './public'),
|
|
||||||
preCompressed: true
|
|
||||||
});
|
});
|
||||||
fastify.setNotFoundHandler(async function (request, reply) {
|
const schema = {
|
||||||
if (request.raw.url && request.raw.url.startsWith('/api')) {
|
type: 'object',
|
||||||
return reply.status(404).send({
|
required: ['COOLIFY_SECRET_KEY', 'COOLIFY_DATABASE_URL', 'COOLIFY_IS_ON'],
|
||||||
success: false
|
properties: {
|
||||||
});
|
COOLIFY_APP_ID: {
|
||||||
}
|
type: 'string',
|
||||||
return reply.status(200).sendFile('index.html');
|
},
|
||||||
});
|
COOLIFY_SECRET_KEY: {
|
||||||
}
|
type: 'string',
|
||||||
fastify.register(autoLoad, {
|
},
|
||||||
dir: join(__dirname, 'plugins')
|
COOLIFY_DATABASE_URL: {
|
||||||
});
|
type: 'string',
|
||||||
fastify.register(autoLoad, {
|
default: 'file:../db/dev.db'
|
||||||
dir: join(__dirname, 'routes')
|
},
|
||||||
});
|
COOLIFY_SENTRY_DSN: {
|
||||||
|
type: 'string',
|
||||||
|
default: null
|
||||||
|
},
|
||||||
|
COOLIFY_IS_ON: {
|
||||||
|
type: 'string',
|
||||||
|
default: 'docker'
|
||||||
|
},
|
||||||
|
COOLIFY_WHITE_LABELED: {
|
||||||
|
type: 'string',
|
||||||
|
default: 'false'
|
||||||
|
},
|
||||||
|
COOLIFY_WHITE_LABELED_ICON: {
|
||||||
|
type: 'string',
|
||||||
|
default: null
|
||||||
|
},
|
||||||
|
COOLIFY_AUTO_UPDATE: {
|
||||||
|
type: 'string',
|
||||||
|
default: 'false'
|
||||||
|
},
|
||||||
|
|
||||||
fastify.register(cookie)
|
}
|
||||||
fastify.register(cors);
|
};
|
||||||
fastify.listen({ port, host }, async (err: any, address: any) => {
|
|
||||||
if (err) {
|
const options = {
|
||||||
console.error(err);
|
schema,
|
||||||
process.exit(1);
|
dotenv: true
|
||||||
|
};
|
||||||
|
fastify.register(env, options);
|
||||||
|
if (!isDev) {
|
||||||
|
fastify.register(serve, {
|
||||||
|
root: path.join(__dirname, './public'),
|
||||||
|
preCompressed: true
|
||||||
|
});
|
||||||
|
fastify.setNotFoundHandler(async function (request, reply) {
|
||||||
|
if (request.raw.url && request.raw.url.startsWith('/api')) {
|
||||||
|
return reply.status(404).send({
|
||||||
|
success: false
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return reply.status(200).sendFile('index.html');
|
||||||
|
});
|
||||||
}
|
}
|
||||||
console.log(`Coolify's API is listening on ${host}:${port}`);
|
fastify.register(autoLoad, {
|
||||||
await initServer();
|
dir: join(__dirname, 'plugins')
|
||||||
|
});
|
||||||
|
fastify.register(autoLoad, {
|
||||||
|
dir: join(__dirname, 'routes')
|
||||||
|
});
|
||||||
|
|
||||||
const graceful = new Graceful({ brees: [scheduler] });
|
fastify.register(cookie)
|
||||||
graceful.listen();
|
fastify.register(cors);
|
||||||
|
fastify.addHook('onRequest', async (request, reply) => {
|
||||||
|
let allowedList = ['coolify:3000'];
|
||||||
|
const { ipv4, ipv6, fqdn } = await prisma.setting.findFirst({})
|
||||||
|
|
||||||
setInterval(async () => {
|
ipv4 && allowedList.push(`${ipv4}:3000`);
|
||||||
if (!scheduler.workers.has('deployApplication')) {
|
ipv6 && allowedList.push(ipv6);
|
||||||
scheduler.run('deployApplication');
|
fqdn && allowedList.push(getDomain(fqdn));
|
||||||
|
isDev && allowedList.push('localhost:3000') && allowedList.push('localhost:3001') && allowedList.push('host.docker.internal:3001');
|
||||||
|
const remotes = await prisma.destinationDocker.findMany({ where: { remoteEngine: true, remoteVerified: true } })
|
||||||
|
if (remotes.length > 0) {
|
||||||
|
remotes.forEach(remote => {
|
||||||
|
allowedList.push(`${remote.remoteIpAddress}:3000`);
|
||||||
|
})
|
||||||
}
|
}
|
||||||
if (!scheduler.workers.has('infrastructure')) {
|
if (!allowedList.includes(request.headers.host)) {
|
||||||
scheduler.run('infrastructure');
|
// console.log('not allowed', request.headers.host)
|
||||||
}
|
}
|
||||||
}, 2000)
|
})
|
||||||
|
fastify.listen({ port, host }, async (err: any, address: any) => {
|
||||||
|
if (err) {
|
||||||
|
console.error(err);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
console.log(`Coolify's API is listening on ${host}:${port}`);
|
||||||
|
await initServer();
|
||||||
|
|
||||||
// autoUpdater
|
const graceful = new Graceful({ brees: [scheduler] });
|
||||||
setInterval(async () => {
|
graceful.listen();
|
||||||
scheduler.workers.has('infrastructure') && scheduler.workers.get('infrastructure').postMessage("action:autoUpdater")
|
|
||||||
}, isDev ? 5000 : 60000 * 15)
|
|
||||||
|
|
||||||
// cleanupStorage
|
setInterval(async () => {
|
||||||
setInterval(async () => {
|
if (!scheduler.workers.has('deployApplication')) {
|
||||||
scheduler.workers.has('infrastructure') && scheduler.workers.get('infrastructure').postMessage("action:cleanupStorage")
|
scheduler.run('deployApplication');
|
||||||
}, isDev ? 6000 : 60000 * 10)
|
}
|
||||||
|
if (!scheduler.workers.has('infrastructure')) {
|
||||||
|
scheduler.run('infrastructure');
|
||||||
|
}
|
||||||
|
}, 2000)
|
||||||
|
|
||||||
// checkProxies
|
// autoUpdater
|
||||||
setInterval(async () => {
|
setInterval(async () => {
|
||||||
scheduler.workers.has('infrastructure') && scheduler.workers.get('infrastructure').postMessage("action:checkProxies")
|
scheduler.workers.has('infrastructure') && scheduler.workers.get('infrastructure').postMessage("action:autoUpdater")
|
||||||
}, 10000)
|
}, isDev ? 5000 : 60000 * 15)
|
||||||
|
|
||||||
// cleanupPrismaEngines
|
// cleanupStorage
|
||||||
// setInterval(async () => {
|
setInterval(async () => {
|
||||||
// scheduler.workers.has('infrastructure') && scheduler.workers.get('infrastructure').postMessage("action:cleanupPrismaEngines")
|
scheduler.workers.has('infrastructure') && scheduler.workers.get('infrastructure').postMessage("action:cleanupStorage")
|
||||||
// }, 60000)
|
}, isDev ? 6000 : 60000 * 10)
|
||||||
|
|
||||||
|
// checkProxies
|
||||||
|
setInterval(async () => {
|
||||||
|
scheduler.workers.has('infrastructure') && scheduler.workers.get('infrastructure').postMessage("action:checkProxies")
|
||||||
|
}, 10000)
|
||||||
|
|
||||||
|
// cleanupPrismaEngines
|
||||||
|
// setInterval(async () => {
|
||||||
|
// scheduler.workers.has('infrastructure') && scheduler.workers.get('infrastructure').postMessage("action:cleanupPrismaEngines")
|
||||||
|
// }, 60000)
|
||||||
|
|
||||||
|
await Promise.all([
|
||||||
|
getArch(),
|
||||||
|
getIPAddress(),
|
||||||
|
configureRemoteDockers(),
|
||||||
|
])
|
||||||
|
});
|
||||||
|
})
|
||||||
|
|
||||||
await Promise.all([
|
|
||||||
getArch(),
|
|
||||||
getIPAddress(),
|
|
||||||
// configureRemoteDockers(),
|
|
||||||
])
|
|
||||||
});
|
|
||||||
async function getIPAddress() {
|
async function getIPAddress() {
|
||||||
const { publicIpv4, publicIpv6 } = await import('public-ip')
|
const { publicIpv4, publicIpv6 } = await import('public-ip')
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -357,21 +357,15 @@ import * as buildpacks from '../lib/buildPacks';
|
|||||||
await saveBuildLog({ line: error, buildId, applicationId: application.id });
|
await saveBuildLog({ line: error, buildId, applicationId: application.id });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
await pAll.default(actions, { concurrency })
|
await pAll.default(actions, { concurrency })
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(error)
|
console.log(error)
|
||||||
} finally {
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
await th()
|
await th()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
} else process.exit(0);
|
} else process.exit(0);
|
||||||
})();
|
})();
|
||||||
|
|||||||
@@ -89,6 +89,22 @@ export function setDefaultBaseImage(buildPack: string | null, deploymentType: st
|
|||||||
}
|
}
|
||||||
];
|
];
|
||||||
const phpVersions = [
|
const phpVersions = [
|
||||||
|
{
|
||||||
|
value: 'webdevops/php-apache:8.2',
|
||||||
|
label: 'webdevops/php-apache:8.2'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'webdevops/php-nginx:8.2',
|
||||||
|
label: 'webdevops/php-nginx:8.2'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'webdevops/php-apache:8.1',
|
||||||
|
label: 'webdevops/php-apache:8.1'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'webdevops/php-nginx:8.1',
|
||||||
|
label: 'webdevops/php-nginx:8.1'
|
||||||
|
},
|
||||||
{
|
{
|
||||||
value: 'webdevops/php-apache:8.0',
|
value: 'webdevops/php-apache:8.0',
|
||||||
label: 'webdevops/php-apache:8.0'
|
label: 'webdevops/php-apache:8.0'
|
||||||
@@ -145,6 +161,22 @@ export function setDefaultBaseImage(buildPack: string | null, deploymentType: st
|
|||||||
value: 'webdevops/php-nginx:5.6',
|
value: 'webdevops/php-nginx:5.6',
|
||||||
label: 'webdevops/php-nginx:5.6'
|
label: 'webdevops/php-nginx:5.6'
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
value: 'webdevops/php-apache:8.2-alpine',
|
||||||
|
label: 'webdevops/php-apache:8.2-alpine'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'webdevops/php-nginx:8.2-alpine',
|
||||||
|
label: 'webdevops/php-nginx:8.2-alpine'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'webdevops/php-apache:8.1-alpine',
|
||||||
|
label: 'webdevops/php-apache:8.1-alpine'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'webdevops/php-nginx:8.1-alpine',
|
||||||
|
label: 'webdevops/php-nginx:8.1-alpine'
|
||||||
|
},
|
||||||
{
|
{
|
||||||
value: 'webdevops/php-apache:8.0-alpine',
|
value: 'webdevops/php-apache:8.0-alpine',
|
||||||
label: 'webdevops/php-apache:8.0-alpine'
|
label: 'webdevops/php-apache:8.0-alpine'
|
||||||
@@ -305,11 +337,11 @@ export function setDefaultBaseImage(buildPack: string | null, deploymentType: st
|
|||||||
payload.baseImage = 'denoland/deno:latest';
|
payload.baseImage = 'denoland/deno:latest';
|
||||||
}
|
}
|
||||||
if (buildPack === 'php') {
|
if (buildPack === 'php') {
|
||||||
payload.baseImage = 'webdevops/php-apache:8.0-alpine';
|
payload.baseImage = 'webdevops/php-apache:8.2-alpine';
|
||||||
payload.baseImages = phpVersions;
|
payload.baseImages = phpVersions;
|
||||||
}
|
}
|
||||||
if (buildPack === 'laravel') {
|
if (buildPack === 'laravel') {
|
||||||
payload.baseImage = 'webdevops/php-apache:8.0-alpine';
|
payload.baseImage = 'webdevops/php-apache:8.2-alpine';
|
||||||
payload.baseBuildImage = 'node:18';
|
payload.baseBuildImage = 'node:18';
|
||||||
payload.baseBuildImages = nodeVersions;
|
payload.baseBuildImages = nodeVersions;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ import { scheduler } from './scheduler';
|
|||||||
import { supportedServiceTypesAndVersions } from './services/supportedVersions';
|
import { supportedServiceTypesAndVersions } from './services/supportedVersions';
|
||||||
import { includeServices } from './services/common';
|
import { includeServices } from './services/common';
|
||||||
|
|
||||||
export const version = '3.9.0';
|
export const version = '3.9.3';
|
||||||
export const isDev = process.env.NODE_ENV === 'development';
|
export const isDev = process.env.NODE_ENV === 'development';
|
||||||
|
|
||||||
const algorithm = 'aes-256-ctr';
|
const algorithm = 'aes-256-ctr';
|
||||||
@@ -974,9 +974,14 @@ export const createDirectories = async ({
|
|||||||
}): Promise<{ workdir: string; repodir: string }> => {
|
}): Promise<{ workdir: string; repodir: string }> => {
|
||||||
const repodir = `/tmp/build-sources/${repository}/`;
|
const repodir = `/tmp/build-sources/${repository}/`;
|
||||||
const workdir = `/tmp/build-sources/${repository}/${buildId}`;
|
const workdir = `/tmp/build-sources/${repository}/${buildId}`;
|
||||||
|
let workdirFound = false;
|
||||||
|
try {
|
||||||
|
workdirFound = !!(await fs.stat(workdir));
|
||||||
|
} catch (error) { }
|
||||||
|
if (workdirFound) {
|
||||||
|
await asyncExecShell(`rm -fr ${workdir}`);
|
||||||
|
}
|
||||||
await asyncExecShell(`mkdir -p ${workdir}`);
|
await asyncExecShell(`mkdir -p ${workdir}`);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
workdir,
|
workdir,
|
||||||
repodir
|
repodir
|
||||||
|
|||||||
@@ -198,7 +198,7 @@ COPY ./init-db.sh /docker-entrypoint-initdb.d/init-db.sh`;
|
|||||||
|
|
||||||
await fs.writeFile(`${workdir}/Dockerfile`, Dockerfile);
|
await fs.writeFile(`${workdir}/Dockerfile`, Dockerfile);
|
||||||
|
|
||||||
const { volumeMounts } = persistentVolumes(id, persistentStorage, config.plausibleAnalytics)
|
const { volumeMounts } = persistentVolumes(id, persistentStorage, config)
|
||||||
|
|
||||||
const composeFile: ComposeFile = {
|
const composeFile: ComposeFile = {
|
||||||
version: '3.8',
|
version: '3.8',
|
||||||
@@ -333,6 +333,8 @@ async function startMinioService(request: FastifyRequest<ServiceStartStop>) {
|
|||||||
image: `${image}:${version}`,
|
image: `${image}:${version}`,
|
||||||
volumes: [`${id}-minio-data:/data`],
|
volumes: [`${id}-minio-data:/data`],
|
||||||
environmentVariables: {
|
environmentVariables: {
|
||||||
|
MINIO_SERVER_URL: fqdn,
|
||||||
|
MINIO_DOMAIN: getDomain(fqdn),
|
||||||
MINIO_ROOT_USER: rootUser,
|
MINIO_ROOT_USER: rootUser,
|
||||||
MINIO_ROOT_PASSWORD: rootUserPassword,
|
MINIO_ROOT_PASSWORD: rootUserPassword,
|
||||||
MINIO_BROWSER_REDIRECT_URL: fqdn
|
MINIO_BROWSER_REDIRECT_URL: fqdn
|
||||||
@@ -852,7 +854,7 @@ async function startGhostService(request: FastifyRequest<ServiceStartStop>) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const { volumeMounts } = persistentVolumes(id, persistentStorage, config.ghost)
|
const { volumeMounts } = persistentVolumes(id, persistentStorage, config)
|
||||||
const composeFile: ComposeFile = {
|
const composeFile: ComposeFile = {
|
||||||
version: '3.8',
|
version: '3.8',
|
||||||
services: {
|
services: {
|
||||||
@@ -1086,7 +1088,7 @@ async function startUmamiService(request: FastifyRequest<ServiceStartStop>) {
|
|||||||
FROM ${config.postgresql.image}
|
FROM ${config.postgresql.image}
|
||||||
COPY ./schema.postgresql.sql /docker-entrypoint-initdb.d/schema.postgresql.sql`;
|
COPY ./schema.postgresql.sql /docker-entrypoint-initdb.d/schema.postgresql.sql`;
|
||||||
await fs.writeFile(`${workdir}/Dockerfile`, Dockerfile);
|
await fs.writeFile(`${workdir}/Dockerfile`, Dockerfile);
|
||||||
const { volumeMounts } = persistentVolumes(id, persistentStorage, config.umami)
|
const { volumeMounts } = persistentVolumes(id, persistentStorage, config)
|
||||||
const composeFile: ComposeFile = {
|
const composeFile: ComposeFile = {
|
||||||
version: '3.8',
|
version: '3.8',
|
||||||
services: {
|
services: {
|
||||||
@@ -1114,6 +1116,7 @@ async function startUmamiService(request: FastifyRequest<ServiceStartStop>) {
|
|||||||
},
|
},
|
||||||
volumes: volumeMounts
|
volumes: volumeMounts
|
||||||
};
|
};
|
||||||
|
console.log(composeFile)
|
||||||
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));
|
||||||
await startServiceContainers(destinationDocker.id, composeFileDestination)
|
await startServiceContainers(destinationDocker.id, composeFileDestination)
|
||||||
@@ -1167,7 +1170,7 @@ async function startHasuraService(request: FastifyRequest<ServiceStartStop>) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const { volumeMounts } = persistentVolumes(id, persistentStorage, config.hasura)
|
const { volumeMounts } = persistentVolumes(id, persistentStorage, config)
|
||||||
const composeFile: ComposeFile = {
|
const composeFile: ComposeFile = {
|
||||||
version: '3.8',
|
version: '3.8',
|
||||||
services: {
|
services: {
|
||||||
@@ -1272,7 +1275,7 @@ async function startFiderService(request: FastifyRequest<ServiceStartStop>) {
|
|||||||
config.fider.environmentVariables[secret.name] = secret.value;
|
config.fider.environmentVariables[secret.name] = secret.value;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
const { volumeMounts } = persistentVolumes(id, persistentStorage, config.fider)
|
const { volumeMounts } = persistentVolumes(id, persistentStorage, config)
|
||||||
const composeFile: ComposeFile = {
|
const composeFile: ComposeFile = {
|
||||||
version: '3.8',
|
version: '3.8',
|
||||||
services: {
|
services: {
|
||||||
@@ -1880,7 +1883,7 @@ async function startMoodleService(request: FastifyRequest<ServiceStartStop>) {
|
|||||||
config.moodle.environmentVariables[secret.name] = secret.value;
|
config.moodle.environmentVariables[secret.name] = secret.value;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
const { volumeMounts } = persistentVolumes(id, persistentStorage, config.moodle)
|
const { volumeMounts } = persistentVolumes(id, persistentStorage, config)
|
||||||
const composeFile: ComposeFile = {
|
const composeFile: ComposeFile = {
|
||||||
version: '3.8',
|
version: '3.8',
|
||||||
services: {
|
services: {
|
||||||
@@ -2006,7 +2009,7 @@ async function startGlitchTipService(request: FastifyRequest<ServiceStartStop>)
|
|||||||
config.glitchTip.environmentVariables[secret.name] = secret.value;
|
config.glitchTip.environmentVariables[secret.name] = secret.value;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
const { volumeMounts } = persistentVolumes(id, persistentStorage, config.glitchTip)
|
const { volumeMounts } = persistentVolumes(id, persistentStorage, config)
|
||||||
const composeFile: ComposeFile = {
|
const composeFile: ComposeFile = {
|
||||||
version: '3.8',
|
version: '3.8',
|
||||||
services: {
|
services: {
|
||||||
|
|||||||
@@ -525,9 +525,7 @@ export async function checkDomain(request: FastifyRequest<CheckDomain>) {
|
|||||||
}
|
}
|
||||||
export async function checkDNS(request: FastifyRequest<CheckDNS>) {
|
export async function checkDNS(request: FastifyRequest<CheckDNS>) {
|
||||||
try {
|
try {
|
||||||
|
|
||||||
const { id } = request.params
|
const { id } = request.params
|
||||||
|
|
||||||
let { exposePort, fqdn, forceSave, dualCerts } = request.body
|
let { exposePort, fqdn, forceSave, dualCerts } = request.body
|
||||||
if (!fqdn) {
|
if (!fqdn) {
|
||||||
return {}
|
return {}
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ export async function saveSettings(request: FastifyRequest<SaveSettings>, reply:
|
|||||||
try {
|
try {
|
||||||
const {
|
const {
|
||||||
fqdn,
|
fqdn,
|
||||||
|
isAPIDebuggingEnabled,
|
||||||
isRegistrationEnabled,
|
isRegistrationEnabled,
|
||||||
dualCerts,
|
dualCerts,
|
||||||
minPort,
|
minPort,
|
||||||
@@ -39,7 +40,7 @@ export async function saveSettings(request: FastifyRequest<SaveSettings>, reply:
|
|||||||
const { id } = await listSettings();
|
const { id } = await listSettings();
|
||||||
await prisma.setting.update({
|
await prisma.setting.update({
|
||||||
where: { id },
|
where: { id },
|
||||||
data: { isRegistrationEnabled, dualCerts, isAutoUpdateEnabled, isDNSCheckEnabled, DNSServers }
|
data: { isRegistrationEnabled, dualCerts, isAutoUpdateEnabled, isDNSCheckEnabled, DNSServers, isAPIDebuggingEnabled }
|
||||||
});
|
});
|
||||||
if (fqdn) {
|
if (fqdn) {
|
||||||
await prisma.setting.update({ where: { id }, data: { fqdn } });
|
await prisma.setting.update({ where: { id }, data: { fqdn } });
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { OnlyId } from "../../../../types"
|
|||||||
export interface SaveSettings {
|
export interface SaveSettings {
|
||||||
Body: {
|
Body: {
|
||||||
fqdn: string,
|
fqdn: string,
|
||||||
|
isAPIDebuggingEnabled: boolean,
|
||||||
isRegistrationEnabled: boolean,
|
isRegistrationEnabled: boolean,
|
||||||
dualCerts: boolean,
|
dualCerts: boolean,
|
||||||
minPort: number,
|
minPort: number,
|
||||||
|
|||||||
@@ -173,16 +173,16 @@ export async function gitHubEvents(request: FastifyRequest<GitHubEvents>): Promi
|
|||||||
where: { id: application.id },
|
where: { id: application.id },
|
||||||
data: { updatedAt: new Date() }
|
data: { updatedAt: new Date() }
|
||||||
});
|
});
|
||||||
if (application.connectedDatabase && pullmergeRequestAction === 'opened' || pullmergeRequestAction === 'reopened') {
|
// if (application.connectedDatabase && pullmergeRequestAction === 'opened' || pullmergeRequestAction === 'reopened') {
|
||||||
// Coolify hosted database
|
// // Coolify hosted database
|
||||||
if (application.connectedDatabase.databaseId) {
|
// if (application.connectedDatabase.databaseId) {
|
||||||
const databaseId = application.connectedDatabase.databaseId;
|
// const databaseId = application.connectedDatabase.databaseId;
|
||||||
const database = await prisma.database.findUnique({ where: { id: databaseId } });
|
// const database = await prisma.database.findUnique({ where: { id: databaseId } });
|
||||||
if (database) {
|
// if (database) {
|
||||||
await createdBranchDatabase(database, application.connectedDatabase.hostedDatabaseDBName, pullmergeRequestId);
|
// await createdBranchDatabase(database, application.connectedDatabase.hostedDatabaseDBName, pullmergeRequestId);
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
await prisma.build.create({
|
await prisma.build.create({
|
||||||
data: {
|
data: {
|
||||||
id: buildId,
|
id: buildId,
|
||||||
|
|||||||
@@ -133,7 +133,7 @@ export async function gitLabEvents(request: FastifyRequest<GitLabEvents>) {
|
|||||||
await prisma.build.create({
|
await prisma.build.create({
|
||||||
data: {
|
data: {
|
||||||
id: buildId,
|
id: buildId,
|
||||||
pullmergeRequestId,
|
pullmergeRequestId: pullmergeRequestId.toString(),
|
||||||
sourceBranch,
|
sourceBranch,
|
||||||
applicationId: application.id,
|
applicationId: application.id,
|
||||||
destinationDockerId: application.destinationDocker.id,
|
destinationDockerId: application.destinationDocker.id,
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { FastifyPluginAsync } from 'fastify';
|
import { FastifyPluginAsync } from 'fastify';
|
||||||
|
import { OnlyId } from '../../../types';
|
||||||
import { remoteTraefikConfiguration, traefikConfiguration, traefikOtherConfiguration } from './handlers';
|
import { remoteTraefikConfiguration, traefikConfiguration, traefikOtherConfiguration } from './handlers';
|
||||||
import { TraefikOtherConfiguration } from './types';
|
import { TraefikOtherConfiguration } from './types';
|
||||||
|
|
||||||
@@ -6,7 +7,7 @@ const root: FastifyPluginAsync = async (fastify): Promise<void> => {
|
|||||||
fastify.get('/main.json', async (request, reply) => traefikConfiguration(request, reply));
|
fastify.get('/main.json', async (request, reply) => traefikConfiguration(request, reply));
|
||||||
fastify.get<TraefikOtherConfiguration>('/other.json', async (request, reply) => traefikOtherConfiguration(request));
|
fastify.get<TraefikOtherConfiguration>('/other.json', async (request, reply) => traefikOtherConfiguration(request));
|
||||||
|
|
||||||
fastify.get('/remote/:id', async (request) => remoteTraefikConfiguration(request));
|
fastify.get<OnlyId>('/remote/:id', async (request) => remoteTraefikConfiguration(request));
|
||||||
};
|
};
|
||||||
|
|
||||||
export default root;
|
export default root;
|
||||||
|
|||||||
@@ -20,13 +20,12 @@
|
|||||||
let usageInterval: any;
|
let usageInterval: any;
|
||||||
let loading = {
|
let loading = {
|
||||||
usage: false,
|
usage: false,
|
||||||
cleanup: false,
|
cleanup: false
|
||||||
restart: false
|
|
||||||
};
|
};
|
||||||
import { addToast, appSession } from '$lib/store';
|
import { addToast, appSession } from '$lib/store';
|
||||||
import { onDestroy, onMount } from 'svelte';
|
import { onDestroy, onMount } from 'svelte';
|
||||||
import { get, post } from '$lib/api';
|
import { get, post } from '$lib/api';
|
||||||
import { asyncSleep, errorNotification } from '$lib/common';
|
import { errorNotification } from '$lib/common';
|
||||||
async function getStatus() {
|
async function getStatus() {
|
||||||
if (loading.usage) return;
|
if (loading.usage) return;
|
||||||
loading.usage = true;
|
loading.usage = true;
|
||||||
@@ -34,45 +33,7 @@
|
|||||||
usage = data.usage;
|
usage = data.usage;
|
||||||
loading.usage = false;
|
loading.usage = false;
|
||||||
}
|
}
|
||||||
async function restartCoolify() {
|
|
||||||
const sure = confirm(
|
|
||||||
'Are you sure you would like to restart Coolify? Currently running deployments will be stopped and restarted.'
|
|
||||||
);
|
|
||||||
if (sure) {
|
|
||||||
loading.restart = true;
|
|
||||||
try {
|
|
||||||
await post(`/internal/restart`, {});
|
|
||||||
await asyncSleep(10000);
|
|
||||||
let reachable = false;
|
|
||||||
let tries = 0;
|
|
||||||
do {
|
|
||||||
await asyncSleep(4000);
|
|
||||||
try {
|
|
||||||
await get(`/undead`);
|
|
||||||
reachable = true;
|
|
||||||
} catch (error) {
|
|
||||||
reachable = false;
|
|
||||||
}
|
|
||||||
if (reachable) break;
|
|
||||||
tries++;
|
|
||||||
} while (!reachable || tries < 120);
|
|
||||||
addToast({
|
|
||||||
message: 'New version reachable. Reloading...',
|
|
||||||
type: 'success'
|
|
||||||
});
|
|
||||||
await asyncSleep(3000);
|
|
||||||
return window.location.reload();
|
|
||||||
addToast({
|
|
||||||
type: 'success',
|
|
||||||
message: 'Coolify restarted successfully. It will take a moment.'
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
return errorNotification(error);
|
|
||||||
} finally {
|
|
||||||
loading.restart = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
onDestroy(() => {
|
onDestroy(() => {
|
||||||
clearInterval(usageInterval);
|
clearInterval(usageInterval);
|
||||||
});
|
});
|
||||||
@@ -112,11 +73,6 @@
|
|||||||
<button on:click={manuallyCleanupStorage} class:loading={loading.cleanup} class="btn btn-sm"
|
<button on:click={manuallyCleanupStorage} class:loading={loading.cleanup} class="btn btn-sm"
|
||||||
>Cleanup Storage</button
|
>Cleanup Storage</button
|
||||||
>
|
>
|
||||||
<button
|
|
||||||
on:click={restartCoolify}
|
|
||||||
class:loading={loading.restart}
|
|
||||||
class="btn btn-sm bg-red-600 hover:bg-red-500">Restart Coolify</button
|
|
||||||
>
|
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -244,7 +244,7 @@
|
|||||||
{/if}
|
{/if}
|
||||||
{#if $status.application.initialLoading}
|
{#if $status.application.initialLoading}
|
||||||
<button
|
<button
|
||||||
class="icons flex animate-spin items-center space-x-2 bg-transparent text-sm duration-500 ease-in-out"
|
class="icons flex animate-spin items-center space-x-2 bg-transparent text-sm duration-500 ease-in-out hover:bg-transparent"
|
||||||
>
|
>
|
||||||
<svg
|
<svg
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
|||||||
@@ -218,7 +218,8 @@
|
|||||||
if (loading) return;
|
if (loading) return;
|
||||||
loading = true;
|
loading = true;
|
||||||
try {
|
try {
|
||||||
nonWWWDomain = application.fqdn != null && getDomain(application.fqdn).replace(/^www\./, '');
|
nonWWWDomain = application.fqdn && getDomain(application.fqdn).replace(/^www\./, '');
|
||||||
|
console.log({debug: nonWWWDomain})
|
||||||
if (application.deploymentType)
|
if (application.deploymentType)
|
||||||
application.deploymentType = application.deploymentType.toLowerCase();
|
application.deploymentType = application.deploymentType.toLowerCase();
|
||||||
!isBot &&
|
!isBot &&
|
||||||
|
|||||||
@@ -87,12 +87,15 @@
|
|||||||
const sure = confirm($t('database.confirm_stop', { name: database.name }));
|
const sure = confirm($t('database.confirm_stop', { name: database.name }));
|
||||||
if (sure) {
|
if (sure) {
|
||||||
$status.database.initialLoading = true;
|
$status.database.initialLoading = true;
|
||||||
|
$status.database.loading = true;
|
||||||
try {
|
try {
|
||||||
await post(`/databases/${database.id}/stop`, {});
|
await post(`/databases/${database.id}/stop`, {});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return errorNotification(error);
|
return errorNotification(error);
|
||||||
} finally {
|
} finally {
|
||||||
$status.database.initialLoading = false;
|
$status.database.initialLoading = false;
|
||||||
|
$status.database.loading = false;
|
||||||
|
await getStatus();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -175,7 +178,7 @@
|
|||||||
{/if}
|
{/if}
|
||||||
{#if $status.database.initialLoading}
|
{#if $status.database.initialLoading}
|
||||||
<button
|
<button
|
||||||
class="icons flex animate-spin items-center space-x-2 bg-transparent text-sm duration-500 ease-in-out"
|
class="icons flex animate-spin items-center space-x-2 bg-transparent text-sm duration-500 ease-in-out hover:bg-transparent"
|
||||||
>
|
>
|
||||||
<svg
|
<svg
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
|||||||
@@ -70,7 +70,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="prose">
|
<div class="prose">
|
||||||
<h4>Coolify dashboard</h4>
|
<h4>Coolify</h4>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -100,7 +100,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="prose">
|
<div class="prose">
|
||||||
<h4>Coolify dashboard</h4>
|
<h4>Coolify</h4>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -12,7 +12,14 @@
|
|||||||
import { get, post } from '$lib/api';
|
import { get, post } from '$lib/api';
|
||||||
import { errorNotification, getDomain } from '$lib/common';
|
import { errorNotification, getDomain } from '$lib/common';
|
||||||
import { t } from '$lib/translations';
|
import { t } from '$lib/translations';
|
||||||
import { appSession, disabledButton, status, location, setLocation, addToast } from '$lib/store';
|
import {
|
||||||
|
appSession,
|
||||||
|
status,
|
||||||
|
setLocation,
|
||||||
|
addToast,
|
||||||
|
checkIfDeploymentEnabledServices,
|
||||||
|
isDeploymentEnabled
|
||||||
|
} from '$lib/store';
|
||||||
import CopyPasswordField from '$lib/components/CopyPasswordField.svelte';
|
import CopyPasswordField from '$lib/components/CopyPasswordField.svelte';
|
||||||
import Setting from '$lib/components/Setting.svelte';
|
import Setting from '$lib/components/Setting.svelte';
|
||||||
|
|
||||||
@@ -78,8 +85,8 @@
|
|||||||
});
|
});
|
||||||
await post(`/services/${id}`, { ...service });
|
await post(`/services/${id}`, { ...service });
|
||||||
setLocation(service);
|
setLocation(service);
|
||||||
$disabledButton = false;
|
|
||||||
forceSave = false;
|
forceSave = false;
|
||||||
|
$isDeploymentEnabled = checkIfDeploymentEnabledServices($appSession.isAdmin, service);
|
||||||
return addToast({
|
return addToast({
|
||||||
message: 'Configuration saved.',
|
message: 'Configuration saved.',
|
||||||
type: 'success'
|
type: 'success'
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
export let readOnly: any;
|
export let readOnly: any;
|
||||||
export let settings: any;
|
export let settings: any;
|
||||||
const { id } = $page.params;
|
const { id } = $page.params;
|
||||||
|
const { ipv4, ipv6 } = settings;
|
||||||
let ftpUrl = generateUrl(service.wordpress.ftpPublicPort);
|
let ftpUrl = generateUrl(service.wordpress.ftpPublicPort);
|
||||||
let ftpUser = service.wordpress.ftpUser;
|
let ftpUser = service.wordpress.ftpUser;
|
||||||
let ftpPassword = service.wordpress.ftpPassword;
|
let ftpPassword = service.wordpress.ftpPassword;
|
||||||
@@ -22,7 +22,7 @@
|
|||||||
function generateUrl(publicPort: any) {
|
function generateUrl(publicPort: any) {
|
||||||
return browser
|
return browser
|
||||||
? `sftp://${
|
? `sftp://${
|
||||||
settings?.fqdn ? getDomain(settings.fqdn) : window.location.hostname
|
settings?.fqdn ? getDomain(settings.fqdn) : ipv4 || ipv6
|
||||||
}:${publicPort}`
|
}:${publicPort}`
|
||||||
: 'Loading...';
|
: 'Loading...';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -97,12 +97,15 @@
|
|||||||
const sure = confirm($t('database.confirm_stop', { name: service.name }));
|
const sure = confirm($t('database.confirm_stop', { name: service.name }));
|
||||||
if (sure) {
|
if (sure) {
|
||||||
$status.service.initialLoading = true;
|
$status.service.initialLoading = true;
|
||||||
|
$status.service.loading = true;
|
||||||
try {
|
try {
|
||||||
await post(`/services/${service.id}/${service.type}/stop`, {});
|
await post(`/services/${service.id}/${service.type}/stop`, {});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return errorNotification(error);
|
return errorNotification(error);
|
||||||
} finally {
|
} finally {
|
||||||
$status.service.initialLoading = false;
|
$status.service.initialLoading = false;
|
||||||
|
$status.service.loading = false;
|
||||||
|
await getStatus();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,10 +23,11 @@
|
|||||||
import { browser } from '$app/env';
|
import { browser } from '$app/env';
|
||||||
import { t } from '$lib/translations';
|
import { t } from '$lib/translations';
|
||||||
import { addToast, appSession, features } from '$lib/store';
|
import { addToast, appSession, features } from '$lib/store';
|
||||||
import { errorNotification, getDomain } from '$lib/common';
|
import { asyncSleep, errorNotification, getDomain } from '$lib/common';
|
||||||
import Menu from './_Menu.svelte';
|
import Menu from './_Menu.svelte';
|
||||||
import Explainer from '$lib/components/Explainer.svelte';
|
import Explainer from '$lib/components/Explainer.svelte';
|
||||||
|
|
||||||
|
let isAPIDebuggingEnabled = settings.isAPIDebuggingEnabled;
|
||||||
let isRegistrationEnabled = settings.isRegistrationEnabled;
|
let isRegistrationEnabled = settings.isRegistrationEnabled;
|
||||||
let dualCerts = settings.dualCerts;
|
let dualCerts = settings.dualCerts;
|
||||||
let isAutoUpdateEnabled = settings.isAutoUpdateEnabled;
|
let isAutoUpdateEnabled = settings.isAutoUpdateEnabled;
|
||||||
@@ -44,7 +45,8 @@
|
|||||||
let loading = {
|
let loading = {
|
||||||
save: false,
|
save: false,
|
||||||
remove: false,
|
remove: false,
|
||||||
proxyMigration: false
|
proxyMigration: false,
|
||||||
|
restart: false
|
||||||
};
|
};
|
||||||
|
|
||||||
async function removeFqdn() {
|
async function removeFqdn() {
|
||||||
@@ -75,8 +77,11 @@
|
|||||||
if (name === 'isDNSCheckEnabled') {
|
if (name === 'isDNSCheckEnabled') {
|
||||||
isDNSCheckEnabled = !isDNSCheckEnabled;
|
isDNSCheckEnabled = !isDNSCheckEnabled;
|
||||||
}
|
}
|
||||||
|
if (name === 'isAPIDebuggingEnabled') {
|
||||||
|
isAPIDebuggingEnabled = !isAPIDebuggingEnabled;
|
||||||
|
}
|
||||||
await post(`/settings`, {
|
await post(`/settings`, {
|
||||||
|
isAPIDebuggingEnabled,
|
||||||
isRegistrationEnabled,
|
isRegistrationEnabled,
|
||||||
dualCerts,
|
dualCerts,
|
||||||
isAutoUpdateEnabled,
|
isAutoUpdateEnabled,
|
||||||
@@ -152,6 +157,41 @@
|
|||||||
function resetView() {
|
function resetView() {
|
||||||
forceSave = false;
|
forceSave = false;
|
||||||
}
|
}
|
||||||
|
async function restartCoolify() {
|
||||||
|
const sure = confirm(
|
||||||
|
'Are you sure you would like to restart Coolify? Currently running deployments will be stopped and restarted.'
|
||||||
|
);
|
||||||
|
if (sure) {
|
||||||
|
loading.restart = true;
|
||||||
|
try {
|
||||||
|
await post(`/internal/restart`, {});
|
||||||
|
await asyncSleep(10000);
|
||||||
|
let reachable = false;
|
||||||
|
let tries = 0;
|
||||||
|
do {
|
||||||
|
await asyncSleep(4000);
|
||||||
|
try {
|
||||||
|
await get(`/undead`);
|
||||||
|
reachable = true;
|
||||||
|
} catch (error) {
|
||||||
|
reachable = false;
|
||||||
|
}
|
||||||
|
if (reachable) break;
|
||||||
|
tries++;
|
||||||
|
} while (!reachable || tries < 120);
|
||||||
|
addToast({
|
||||||
|
message: 'New version reachable. Reloading...',
|
||||||
|
type: 'success'
|
||||||
|
});
|
||||||
|
await asyncSleep(3000);
|
||||||
|
return window.location.reload();
|
||||||
|
} catch (error) {
|
||||||
|
return errorNotification(error);
|
||||||
|
} finally {
|
||||||
|
loading.restart = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="flex space-x-1 p-6 font-bold">
|
<div class="flex space-x-1 p-6 font-bold">
|
||||||
@@ -182,11 +222,14 @@
|
|||||||
on:click|preventDefault={removeFqdn}
|
on:click|preventDefault={removeFqdn}
|
||||||
disabled={loading.remove}
|
disabled={loading.remove}
|
||||||
class="btn btn-sm"
|
class="btn btn-sm"
|
||||||
class:bg-red-600={!loading.remove}
|
|
||||||
class:hover:bg-red-500={!loading.remove}
|
|
||||||
>{loading.remove ? $t('forms.removing') : $t('forms.remove_domain')}</button
|
>{loading.remove ? $t('forms.removing') : $t('forms.remove_domain')}</button
|
||||||
>
|
>
|
||||||
{/if}
|
{/if}
|
||||||
|
<button
|
||||||
|
on:click={restartCoolify}
|
||||||
|
class:loading={loading.restart}
|
||||||
|
class="btn btn-sm bg-red-600 hover:bg-red-500">Restart Coolify</button
|
||||||
|
>
|
||||||
</div>
|
</div>
|
||||||
<div class="grid grid-flow-row gap-2 px-10">
|
<div class="grid grid-flow-row gap-2 px-10">
|
||||||
<!-- <Language /> -->
|
<!-- <Language /> -->
|
||||||
@@ -310,17 +353,24 @@
|
|||||||
on:click={() => changeSettings('isRegistrationEnabled')}
|
on:click={() => changeSettings('isRegistrationEnabled')}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{#if browser && $features.beta}
|
<div class="grid grid-cols-2 items-center">
|
||||||
<div class="grid grid-cols-2 items-center">
|
<Setting
|
||||||
<Setting
|
id="isAPIDebuggingEnabled"
|
||||||
id="isAutoUpdateEnabled"
|
bind:setting={isAPIDebuggingEnabled}
|
||||||
bind:setting={isAutoUpdateEnabled}
|
title="API Debugging"
|
||||||
title={$t('setting.auto_update_enabled')}
|
description="Enable API debugging. This will log all API requests and responses.<br><br>You need to restart the Coolify for this to take effect."
|
||||||
description={$t('setting.auto_update_enabled_explainer')}
|
on:click={() => changeSettings('isAPIDebuggingEnabled')}
|
||||||
on:click={() => changeSettings('isAutoUpdateEnabled')}
|
/>
|
||||||
/>
|
</div>
|
||||||
</div>
|
<div class="grid grid-cols-2 items-center">
|
||||||
{/if}
|
<Setting
|
||||||
|
id="isAutoUpdateEnabled"
|
||||||
|
bind:setting={isAutoUpdateEnabled}
|
||||||
|
title={$t('setting.auto_update_enabled')}
|
||||||
|
description={$t('setting.auto_update_enabled_explainer')}
|
||||||
|
on:click={() => changeSettings('isAutoUpdateEnabled')}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -46,8 +46,8 @@
|
|||||||
customPort: source.customPort
|
customPort: source.customPort
|
||||||
});
|
});
|
||||||
const { organization, htmlUrl } = source;
|
const { organization, htmlUrl } = source;
|
||||||
const { fqdn } = settings;
|
const { fqdn, ipv4, ipv6 } = settings;
|
||||||
const host = dev ? getAPIUrl() : fqdn ? fqdn : `http://${window.location.host}` || '';
|
const host = dev ? getAPIUrl() : fqdn ? fqdn : `http://${ipv4 || ipv6}` || '';
|
||||||
const domain = getDomain(fqdn);
|
const domain = getDomain(fqdn);
|
||||||
|
|
||||||
let url = 'settings/apps/new';
|
let url = 'settings/apps/new';
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "coolify",
|
"name": "coolify",
|
||||||
"description": "An open-source & self-hostable Heroku / Netlify alternative.",
|
"description": "An open-source & self-hostable Heroku / Netlify alternative.",
|
||||||
"version": "3.9.0",
|
"version": "3.9.3",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"repository": "github:coollabsio/coolify",
|
"repository": "github:coollabsio/coolify",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
Reference in New Issue
Block a user