Compare commits

...

60 Commits

Author SHA1 Message Date
Andras Bacsai
0b416cd03e Merge pull request #549 from coollabsio/next
v3.7.0
2022-08-19 12:26:36 +02:00
Andras Bacsai
857e0f251b chore: version++ 2022-08-19 10:24:42 +00:00
Andras Bacsai
f040c7c742 fix: exposedPort is just optional 2022-08-19 10:18:27 +00:00
Andras Bacsai
2e82c9d312 Merge pull request #538 from MrSquaare/feature/glitchtip-service
feat: add GlitchTip service
2022-08-18 22:20:54 +02:00
Andras Bacsai
126923c33e update gitpod dockerfile 2022-08-18 20:11:48 +00:00
Andras Bacsai
26528d8bec gitpod configuration 2022-08-18 20:02:09 +00:00
Andras Bacsai
f99da111f7 Merge pull request #547 from coollabsio/next
v3.6.0
2022-08-18 21:50:29 +02:00
Andras Bacsai
8c30472472 Merge branch 'next' into feature/glitchtip-service 2022-08-18 21:47:06 +02:00
Andras Bacsai
11131ebe06 custom dockerfile for gitpod 2022-08-18 19:30:53 +00:00
Andras Bacsai
8dd80589d6 fix: bots without exposed ports 2022-08-18 19:27:38 +00:00
Andras Bacsai
51e27146f3 Update .gitpod.yml 2022-08-18 21:14:56 +02:00
Andras Bacsai
70717dcbe5 Merge pull request #546 from coollabsio/public_repos
Import public repositories feature and more
2022-08-18 20:56:08 +02:00
Andras Bacsai
51cba32d8d chore: version++ 2022-08-18 18:53:24 +00:00
Andras Bacsai
b9076714cf ui: fixes here and there 2022-08-18 18:53:02 +00:00
Andras Bacsai
b76caabd32 examples 2022-08-18 16:37:46 +02:00
Andras Bacsai
0922fd66a4 feat: force rebuild + env.PORT for port + public repo build 2022-08-18 16:33:32 +02:00
Andras Bacsai
4e7e9b2cfc feat: public repo deployment 2022-08-18 15:29:59 +02:00
Andras Bacsai
0c24134ac2 feat: import public repos (wip) 2022-08-18 11:53:42 +02:00
Andras Bacsai
f96e418dd6 Merge pull request #545 from coollabsio/next
v3.5.2
2022-08-17 14:06:51 +02:00
Andras Bacsai
1627415cca chore: version++ 2022-08-17 14:00:45 +02:00
Andras Bacsai
d047c91399 fix: show that Ghost values could be changed 2022-08-17 13:59:35 +02:00
Andras Bacsai
8bec5550cf fix: restart containers on-failure instead of always 2022-08-17 13:59:23 +02:00
Andras Bacsai
7a61ade4a0 Merge pull request #544 from coollabsio/next
v3.5.1
2022-08-17 13:43:24 +02:00
Andras Bacsai
0239be69e6 chore: version++ 2022-08-17 13:31:27 +02:00
Andras Bacsai
883bdc2879 fix: trim secrets 2022-08-17 13:31:06 +02:00
Andras Bacsai
c3457a4c8a fix: revert docker compose version to 2.6.1 2022-08-17 13:30:53 +02:00
Andras Bacsai
d93506a18c Merge pull request #543 from coollabsio/next
v3.5.0
2022-08-17 11:16:28 +02:00
Andras Bacsai
0bb77a671b ui: typing 2022-08-17 11:06:51 +02:00
Andras Bacsai
028f883499 fix: autoUpdater & cleanupStorage jobs 2022-08-17 11:03:44 +02:00
Andras Bacsai
727133e28b feat: custom dns servers 2022-08-17 10:43:57 +02:00
Andras Bacsai
1bd08cb2db fix: bots 2022-08-17 10:18:38 +02:00
Guillaume Bonnet
2962aa6166 Merge remote-tracking branch 'upstream/next' into feature/glitchtip-service
# Conflicts:
#	README.md
#	apps/api/src/routes/api/v1/services/handlers.ts
#	apps/ui/src/lib/components/svg/services/index.ts
2022-08-16 20:21:44 +02:00
Andras Bacsai
bac55cd90d fix: bot deployments 2022-08-16 16:03:09 +02:00
Andras Bacsai
9b51936131 feat: deploy bots (no domains) 2022-08-16 15:49:33 +02:00
Andras Bacsai
692665d0da fix: dns button ui 2022-08-16 14:46:24 +02:00
Andras Bacsai
14cdf33473 Merge pull request #540 from coollabsio/next
v3.4.0
2022-08-16 14:42:12 +02:00
Andras Bacsai
17f4f83ad6 fix discord invite 2022-08-16 14:37:08 +02:00
Andras Bacsai
c981606da7 update README 2022-08-16 14:35:21 +02:00
Andras Bacsai
e21d1bffe8 fix: heroku icon 2022-08-16 14:29:06 +02:00
Andras Bacsai
32706092f3 fix: heroku icon 2022-08-16 14:10:44 +02:00
Andras Bacsai
a7fe446550 feat: Heroku deployments 2022-08-16 13:58:37 +02:00
Andras Bacsai
bb01cfb330 remove console.log 2022-08-16 11:08:23 +00:00
Andras Bacsai
2590efba4b fix: services import 2022-08-16 09:52:47 +00:00
Andras Bacsai
186c5897a0 fix: autoimport + readme 2022-08-16 09:40:07 +00:00
Andras Bacsai
6150a008d5 fixes 2022-08-16 09:38:34 +00:00
Andras Bacsai
fb2a86ba3f fix ui 2022-08-16 09:13:58 +00:00
Andras Bacsai
91fa762985 fix: appwrite 2022-08-16 09:13:22 +00:00
Guillaume Bonnet
d80f760c92 fix: missing commas 2022-08-15 19:41:38 +00:00
Guillaume Bonnet
ce2c887469 Merge remote-tracking branch 'upstream/next' into feature/glitchtip-service
# Conflicts:
#	apps/api/prisma/schema.prisma
#	apps/api/src/lib/common.ts
#	apps/api/src/lib/serviceFields.ts
#	apps/api/src/routes/api/v1/services/handlers.ts
2022-08-15 21:30:53 +02:00
Andras Bacsai
8b5c7c94cd feat: Appwrite service 2022-08-15 14:58:10 +00:00
Andras Bacsai
3dd2a059bf fix: gitpod conf and heroku buildpacks 2022-08-15 11:23:21 +00:00
Andras Bacsai
2f724ffba2 fix: create coolify-infra, if it does not exists 2022-08-15 10:28:57 +00:00
Andras Bacsai
0586ec3f42 fix: dashboard ui 2022-08-15 10:28:40 +00:00
Guillaume Bonnet
4908463722 chore: add .pnpm-store in .gitignore 2022-08-15 09:57:01 +00:00
Guillaume Bonnet
26d0ef9ac9 feat: add GlitchTip service 2022-08-15 09:56:34 +00:00
Andras Bacsai
c2c9d992e7 fix: replace docker compose with docker-compose on CSB 2022-08-15 09:27:54 +00:00
Andras Bacsai
ce9aa636c4 Merge pull request #537 from coollabsio/next
v3.3.4
2022-08-15 11:20:54 +02:00
Andras Bacsai
68d06dcd19 chore: version++ 2022-08-15 09:17:48 +00:00
Andras Bacsai
03bf93eb12 fix: loading indicator 2022-08-15 09:17:31 +00:00
Andras Bacsai
163eabb76c fix: make it public button 2022-08-15 09:15:42 +00:00
75 changed files with 3163 additions and 1509 deletions

1
.gitignore vendored
View File

@@ -1,5 +1,6 @@
.DS_Store
node_modules
.pnpm-store
build
.svelte-kit
package

2
.gitpod.Dockerfile vendored Normal file
View File

@@ -0,0 +1,2 @@
FROM gitpod/workspace-full:2022-08-17-18-37-55
RUN brew install buildpacks/tap/pack

View File

@@ -1,10 +1,11 @@
# This configuration file was automatically generated by Gitpod.
# Please adjust to your needs (see https://www.gitpod.io/docs/config-gitpod-file)
# and commit this file to your remote git repository to share the goodness with others.
image: gitpod/workspace-node:2022-06-20-19-54-55
tasks:
- init: pnpm install && pnpm db:push && pnpm db:seed
command: pnpm dev
#image:
# file: .gitpod.Dockerfile
#tasks:
# - init: pnpm install && pnpm db:push && pnpm db:seed
# command: pnpm dev
ports:
- port: 3001

View File

@@ -30,7 +30,8 @@ RUN mkdir -p ~/.docker/cli-plugins/
# https://download.docker.com/linux/static/stable/
RUN curl -SL https://cdn.coollabs.io/bin/$TARGETPLATFORM/docker-20.10.9 -o /usr/bin/docker
# https://github.com/docker/compose/releases
RUN curl -SL https://cdn.coollabs.io/bin/$TARGETPLATFORM/docker-compose-linux-2.7.0 -o ~/.docker/cli-plugins/docker-compose
# Reverted to 2.6.1 because of this https://github.com/docker/compose/issues/9704. 2.9.0 still has a bug.
RUN curl -SL https://cdn.coollabs.io/bin/$TARGETPLATFORM/docker-compose-linux-2.6.1 -o ~/.docker/cli-plugins/docker-compose
RUN chmod +x ~/.docker/cli-plugins/docker-compose /usr/bin/docker
RUN (curl -sSL "https://github.com/buildpacks/pack/releases/download/v0.27.0/pack-v0.27.0-linux.tgz" | tar -C /usr/local/bin/ --no-same-owner -xzv pack)

231
README.md
View File

@@ -1,7 +1,109 @@
# Coolify
An open-source & self-hostable Heroku / Netlify alternative
(ARM support is in beta).
An open-source & self-hostable Heroku / Netlify alternative.
## Live Demo
https://demo.coolify.io/
(If it is unresponsive, that means someone overloaded the server. 😄)
## Feedback
If you have a new service / build pack you would like to add, raise an idea [here](https://feedback.coolify.io/) to get feedback from the community!
---
## How to install
For more details goto the [docs](https://docs.coollabs.io/coolify/installation.html).
Installation is automated with the following command:
```bash
wget -q https://get.coollabs.io/coolify/install.sh -O install.sh; sudo bash ./install.sh
```
If you would like no questions during installation:
```bash
wget -q https://get.coollabs.io/coolify/install.sh -O install.sh; sudo bash ./install.sh -f
```
---
## Features
### Git Sources
Self-hosted versions also!
<a href="https://github.com"><img style="width:40px;height:40px" src="https://icon.horse/icon/github.com"></a>
<a href="https://gitlab.com"><img style="width:40px;height:40px" src="https://icon.horse/icon/gitlab.com"></a>
### Destinations
Deploy your resource to:
- Local Docker Engine
- Remote Docker Engine
### Applications
<a href="https://heroku.com"><img style="width:40px;height:40px" src="https://icon.horse/icon/heroku.com"></a>
<a href="https://html5.org/">
<svg style="width:40px;height:40px" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg" ><g clip-path="url(#HTML5_Clip0_4)" ><path d="M30.216 0L27.6454 28.7967L16.0907 32L4.56783 28.8012L2 0H30.216Z" fill="#E44D26" /><path d="M16.108 29.5515L25.4447 26.963L27.6415 2.35497H16.108V29.5515Z" fill="#F16529" /><path d="M11.1109 9.4197H16.108V5.88731H7.25053L7.33509 6.83499L8.20327 16.5692H16.108V13.0369H11.4338L11.1109 9.4197Z" fill="#EBEBEB" /><path d="M11.907 18.3354H8.36111L8.856 23.8818L16.0917 25.8904L16.108 25.8859V22.2108L16.0925 22.2149L12.1585 21.1527L11.907 18.3354Z" fill="#EBEBEB" /><path d="M16.0958 16.5692H20.4455L20.0354 21.1504L16.0958 22.2138V25.8887L23.3373 23.8817L23.3904 23.285L24.2205 13.9855L24.3067 13.0369H16.0958V16.5692Z" fill="white" /><path d="M16.0958 9.41105V9.41969H24.6281L24.6989 8.62572L24.8599 6.83499L24.9444 5.88731H16.0958V9.41105Z" fill="white" /></g><defs><clipPath id="HTML5_Clip0_4"><rect width="32" height="32" fill="white" /></clipPath></defs></svg></a>
<a href="https://nodejs.org"><img style="width:40px;height:40px" src="https://icon.horse/icon/nodejs.org"></a>
<a href="https://vuejs.org"><img style="width:40px;height:40px" src="https://icon.horse/icon/vuejs.org"></a>
<a href="https://nuxtjs.org"><img style="width:40px;height:40px" src="https://icon.horse/icon/nuxtjs.org"></a>
<a href="https://nextjs.org"><img style="width:40px;height:40px" src="https://icon.horse/icon/nextjs.org"></a>
<a href="https://reactjs.org"><img style="width:40px;height:40px" src="https://icon.horse/icon/reactjs.org"></a>
<a href="https://preactjs.org"><img style="width:40px;height:40px" src="https://icon.horse/icon/preactjs.org"></a>
<a href="https://gatsbyjs.org"><img style="width:40px;height:40px" src="https://icon.horse/icon/gatsbyjs.org"></a>
<a href="https://svelte.dev"><img style="width:40px;height:40px" src="https://icon.horse/icon/svelte.dev"></a>
<a href="https://php.net"><img style="width:40px;height:40px" src="https://icon.horse/icon/php.net"></a>
<a href="https://laravel.com"><img style="width:40px;height:40px" src="https://icon.horse/icon/laravel.com"></a>
<a href="https://python.org"><img style="width:40px;height:40px" src="https://icon.horse/icon/python.org"></a>
<a href="https://deno.com"><img style="width:40px;height:40px" src="https://icon.horse/icon/deno.com"></a>
<a href="https://docker.com"><img style="width:40px;height:40px" src="https://icon.horse/icon/docker.com"></a>
### Databases
<a href="https://mongodb.org"><img style="width:40px;height:40px" src="https://icon.horse/icon/mongodb.org"></a>
<a href="https://mariadb.org"><img style="width:40px;height:40px" src="https://icon.horse/icon/mariadb.org"></a>
<a href="https://mysql.com"><svg style="width:40px;height:40px" xmlns="http://www.w3.org/2000/svg" width="64" height="64" viewBox="0 0 25.6 25.6" ><path d="M179.076 94.886c-3.568-.1-6.336.268-8.656 1.25-.668.27-1.74.27-1.828 1.116.357.355.4.936.713 1.428.535.893 1.473 2.096 2.32 2.72l2.855 2.053c1.74 1.07 3.703 1.695 5.398 2.766.982.625 1.963 1.428 2.945 2.098.5.357.803.938 1.428 1.16v-.135c-.312-.4-.402-.98-.713-1.428l-1.34-1.293c-1.293-1.74-2.9-3.258-4.64-4.506-1.428-.982-4.55-2.32-5.13-3.97l-.088-.1c.98-.1 2.14-.447 3.078-.715 1.518-.4 2.9-.312 4.46-.713l2.143-.625v-.4c-.803-.803-1.383-1.874-2.23-2.632-2.275-1.963-4.775-3.882-7.363-5.488-1.383-.892-3.168-1.473-4.64-2.23-.537-.268-1.428-.402-1.74-.848-.805-.98-1.25-2.275-1.83-3.436l-3.658-7.763c-.803-1.74-1.295-3.48-2.275-5.086-4.596-7.585-9.594-12.18-17.268-16.687-1.65-.937-3.613-1.34-5.7-1.83l-3.346-.18c-.715-.312-1.428-1.16-2.053-1.562-2.543-1.606-9.102-5.086-10.977-.5-1.205 2.9 1.785 5.755 2.8 7.228.76 1.026 1.74 2.186 2.277 3.346.3.758.4 1.562.713 2.365.713 1.963 1.383 4.15 2.32 5.98.5.937 1.025 1.92 1.65 2.767.357.5.982.714 1.115 1.517-.625.893-.668 2.23-1.025 3.347-1.607 5.042-.982 11.288 1.293 15 .715 1.115 2.4 3.57 4.686 2.632 2.008-.803 1.56-3.346 2.14-5.577.135-.535.045-.892.312-1.25v.1l1.83 3.703c1.383 2.186 3.793 4.462 5.8 5.98 1.07.803 1.918 2.187 3.256 2.677v-.135h-.088c-.268-.4-.67-.58-1.027-.892-.803-.803-1.695-1.785-2.32-2.677-1.873-2.498-3.523-5.265-4.996-8.12-.715-1.383-1.34-2.9-1.918-4.283-.27-.536-.27-1.34-.715-1.606-.67.98-1.65 1.83-2.143 3.034-.848 1.918-.936 4.283-1.248 6.737-.18.045-.1 0-.18.1-1.426-.356-1.918-1.83-2.453-3.078-1.338-3.168-1.562-8.254-.402-11.913.312-.937 1.652-3.882 1.117-4.774-.27-.848-1.16-1.338-1.652-2.008-.58-.848-1.203-1.918-1.605-2.855-1.07-2.5-1.605-5.265-2.766-7.764-.537-1.16-1.473-2.365-2.232-3.435-.848-1.205-1.783-2.053-2.453-3.48-.223-.5-.535-1.294-.178-1.83.088-.357.268-.5.623-.58.58-.5 2.232.134 2.812.4 1.65.67 3.033 1.294 4.416 2.23.625.446 1.295 1.294 2.098 1.518h.938c1.428.312 3.033.1 4.37.5 2.365.76 4.506 1.874 6.426 3.08 5.844 3.703 10.664 8.968 13.92 15.26.535 1.026.758 1.963 1.25 3.034.938 2.187 2.098 4.417 3.033 6.56.938 2.097 1.83 4.24 3.168 5.98.67.937 3.346 1.427 4.55 1.918.893.4 2.275.76 3.08 1.25 1.516.937 3.033 2.008 4.46 3.034.713.534 2.945 1.65 3.078 2.54zm-45.5-38.772a7.09 7.09 0 0 0-1.828.223v.1h.088c.357.714.982 1.205 1.428 1.83l1.027 2.142.088-.1c.625-.446.938-1.16.938-2.23-.268-.312-.312-.625-.535-.937-.268-.446-.848-.67-1.206-1.026z" transform="matrix(.390229 0 0 .38781 -46.300037 -16.856717)" fill-rule="evenodd" fill="#00678c" /></svg></a>
<a href="https://postgresql.org"><img style="width:40px;height:40px" src="https://icon.horse/icon/postgresql.org"></a>
<a href="https://couchdb.apache.org"><img style="width:40px;height:40px" src="https://icon.horse/icon/couchdb.apache.org"></a>
<a href="https://redis.io"><svg style="width:40px;height:40px" viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" ><defs ><path id="a" d="m45.536 38.764c-2.013 1.05-12.44 5.337-14.66 6.494s-3.453 1.146-5.207.308-12.85-5.32-14.85-6.276c-1-.478-1.524-.88-1.524-1.26v-3.813s14.447-3.145 16.78-3.982 3.14-.867 5.126-.14 13.853 2.868 15.814 3.587v3.76c0 .377-.452.8-1.477 1.324z" /><path id="b" d="m45.536 28.733c-2.013 1.05-12.44 5.337-14.66 6.494s-3.453 1.146-5.207.308-12.85-5.32-14.85-6.276-2.04-1.613-.077-2.382l15.332-5.935c2.332-.837 3.14-.867 5.126-.14s12.35 4.853 14.312 5.57 2.037 1.31.024 2.36z" /></defs ><g transform="matrix(.848327 0 0 .848327 -7.883573 -9.449691)" ><use fill="#a41e11" xlink:href="#a" /><path d="m45.536 34.95c-2.013 1.05-12.44 5.337-14.66 6.494s-3.453 1.146-5.207.308-12.85-5.32-14.85-6.276-2.04-1.613-.077-2.382l15.332-5.936c2.332-.836 3.14-.867 5.126-.14s12.35 4.852 14.31 5.582 2.037 1.31.024 2.36z" fill="#d82c20" /><use fill="#a41e11" xlink:href="#a" y="-6.218" /><use fill="#d82c20" xlink:href="#b" /><path d="m45.536 26.098c-2.013 1.05-12.44 5.337-14.66 6.495s-3.453 1.146-5.207.308-12.85-5.32-14.85-6.276c-1-.478-1.524-.88-1.524-1.26v-3.815s14.447-3.145 16.78-3.982 3.14-.867 5.126-.14 13.853 2.868 15.814 3.587v3.76c0 .377-.452.8-1.477 1.324z" fill="#a41e11" /><use fill="#d82c20" xlink:href="#b" y="-6.449" /><g fill="#fff" ><path d="m29.096 20.712-1.182-1.965-3.774-.34 2.816-1.016-.845-1.56 2.636 1.03 2.486-.814-.672 1.612 2.534.95-3.268.34zm-6.296 3.912 8.74-1.342-2.64 3.872z" /><ellipse cx="20.444" cy="21.402" rx="4.672" ry="1.811" /></g ><path d="m42.132 21.138-5.17 2.042-.004-4.087z" fill="#7a0c00" /><path d="m36.963 23.18-.56.22-5.166-2.042 5.723-2.264z" fill="#ad2115" /></g ></svg ></a>
### Services
- [Appwrite](https://appwrite.io)
- [WordPress](https://docs.coollabs.io/coolify/services/wordpress)
- [Ghost](https://ghost.org)
- [Plausible Analytics](https://docs.coollabs.io/coolify/services/plausible-analytics)
- [NocoDB](https://nocodb.com)
- [VSCode Server](https://github.com/cdr/code-server)
- [MinIO](https://min.io)
- [VaultWarden](https://github.com/dani-garcia/vaultwarden)
- [LanguageTool](https://languagetool.org)
- [n8n](https://n8n.io)
- [Uptime Kuma](https://github.com/louislam/uptime-kuma)
- [MeiliSearch](https://github.com/meilisearch/meilisearch)
- [Umami](https://github.com/mikecao/umami)
- [Fider](https://fider.io)
- [Hasura](https://hasura.io)
- [GlitchTip](https://glitchtip.com)
## Migration from v1
A fresh installation is necessary. v2 and v3 are not compatible with v1.
## Support
- Twitter: [@andrasbacsai](https://twitter.com/andrasbacsai)
- Telegram: [@andrasbacsai](https://t.me/andrasbacsai)
- Email: [andras@coollabs.io](mailto:andras@coollabs.io)
- Discord: [Invitation](https://discord.gg/6rDM4fkymF)
## Financial Contributors
@@ -24,127 +126,4 @@ Support this project with your organization. Your logo will show up here with a
<a href="https://opencollective.com/coollabsio/organization/6/website"><img src="https://opencollective.com/coollabsio/organization/6/avatar.svg"></a>
<a href="https://opencollective.com/coollabsio/organization/7/website"><img src="https://opencollective.com/coollabsio/organization/7/avatar.svg"></a>
<a href="https://opencollective.com/coollabsio/organization/8/website"><img src="https://opencollective.com/coollabsio/organization/8/avatar.svg"></a>
<a href="https://opencollective.com/coollabsio/organization/9/website"><img src="https://opencollective.com/coollabsio/organization/9/avatar.svg"></a>
---
## Live Demo
https://demo.coolify.io/
(If it is unresponsive, that means someone overloaded the server. 😄)
## Feedback
If you have a new service / build pack you would like to add, raise an idea [here](https://feedback.coolify.io/) to get feedback from the community!
---
## How to install
Installation is automated with the following command:
```bash
wget -q https://get.coollabs.io/coolify/install.sh -O install.sh; sudo bash ./install.sh
```
If you would like no questions during installation:
```bash
wget -q https://get.coollabs.io/coolify/install.sh -O install.sh; sudo bash ./install.sh -f
```
For more details goto the [docs](https://docs.coollabs.io/coolify/installation).
---
## Features
### Git Sources
You can use the following Git Sources to be auto-deployed to your Coolify instance! (Self-hosted versions are also supported.)
<a href="https://github.com"><img style="width:40px;height:40px" src="https://icon.horse/icon/github.com"></a>
<a href="https://gitlab.com"><img style="width:40px;height:40px" src="https://icon.horse/icon/gitlab.com"></a>
### Destinations
You can deploy your applications to the following destinations:
- Local Docker Engine
- Remote Docker Engine
### Applications
Predefined build packs to cover the basic needs to deploy applications.
If you have an advanced use case, you can use the Docker build pack that allows you to deploy your application based on your custom Dockerfile.
<a href="https://html5.org/">
<svg style="width:40px;height:40px" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg" ><g clip-path="url(#HTML5_Clip0_4)" ><path d="M30.216 0L27.6454 28.7967L16.0907 32L4.56783 28.8012L2 0H30.216Z" fill="#E44D26" /><path d="M16.108 29.5515L25.4447 26.963L27.6415 2.35497H16.108V29.5515Z" fill="#F16529" /><path d="M11.1109 9.4197H16.108V5.88731H7.25053L7.33509 6.83499L8.20327 16.5692H16.108V13.0369H11.4338L11.1109 9.4197Z" fill="#EBEBEB" /><path d="M11.907 18.3354H8.36111L8.856 23.8818L16.0917 25.8904L16.108 25.8859V22.2108L16.0925 22.2149L12.1585 21.1527L11.907 18.3354Z" fill="#EBEBEB" /><path d="M16.0958 16.5692H20.4455L20.0354 21.1504L16.0958 22.2138V25.8887L23.3373 23.8817L23.3904 23.285L24.2205 13.9855L24.3067 13.0369H16.0958V16.5692Z" fill="white" /><path d="M16.0958 9.41105V9.41969H24.6281L24.6989 8.62572L24.8599 6.83499L24.9444 5.88731H16.0958V9.41105Z" fill="white" /></g><defs><clipPath id="HTML5_Clip0_4"><rect width="32" height="32" fill="white" /></clipPath></defs></svg></a>
<a href="https://nodejs.org"><img style="width:40px;height:40px" src="https://icon.horse/icon/nodejs.org"></a>
<a href="https://vuejs.org"><img style="width:40px;height:40px" src="https://icon.horse/icon/vuejs.org"></a>
<a href="https://nuxtjs.org"><img style="width:40px;height:40px" src="https://icon.horse/icon/nuxtjs.org"></a>
<a href="https://nextjs.org"><img style="width:40px;height:40px" src="https://icon.horse/icon/nextjs.org"></a>
<a href="https://reactjs.org"><img style="width:40px;height:40px" src="https://icon.horse/icon/reactjs.org"></a>
<a href="https://preactjs.org"><img style="width:40px;height:40px" src="https://icon.horse/icon/preactjs.org"></a>
<a href="https://gatsbyjs.org"><img style="width:40px;height:40px" src="https://icon.horse/icon/gatsbyjs.org"></a>
<a href="https://svelte.dev"><img style="width:40px;height:40px" src="https://icon.horse/icon/svelte.dev"></a>
<a href="https://php.net"><img style="width:40px;height:40px" src="https://icon.horse/icon/php.net"></a>
<a href="https://laravel.com"><img style="width:40px;height:40px" src="https://icon.horse/icon/laravel.com"></a>
<a href="https://python.org"><img style="width:40px;height:40px" src="https://icon.horse/icon/python.org"></a>
<a href="https://deno.com"><img style="width:40px;height:40px" src="https://icon.horse/icon/deno.com"></a>
<a href="https://docker.com"><img style="width:40px;height:40px" src="https://icon.horse/icon/docker.com"></a>
If you have a new build pack you would like to add, raise an idea [here](https://feedback.coolify.io/) to get feedback from the community!
### Databases
One-click database is ready to be used internally or shared over the internet:
<a href="https://mongodb.org"><img style="width:40px;height:40px" src="https://icon.horse/icon/mongodb.org"></a>
<a href="https://mariadb.org"><img style="width:40px;height:40px" src="https://icon.horse/icon/mariadb.org"></a>
<a href="https://mysql.com"><svg style="width:40px;height:40px" xmlns="http://www.w3.org/2000/svg" width="64" height="64" viewBox="0 0 25.6 25.6" ><path d="M179.076 94.886c-3.568-.1-6.336.268-8.656 1.25-.668.27-1.74.27-1.828 1.116.357.355.4.936.713 1.428.535.893 1.473 2.096 2.32 2.72l2.855 2.053c1.74 1.07 3.703 1.695 5.398 2.766.982.625 1.963 1.428 2.945 2.098.5.357.803.938 1.428 1.16v-.135c-.312-.4-.402-.98-.713-1.428l-1.34-1.293c-1.293-1.74-2.9-3.258-4.64-4.506-1.428-.982-4.55-2.32-5.13-3.97l-.088-.1c.98-.1 2.14-.447 3.078-.715 1.518-.4 2.9-.312 4.46-.713l2.143-.625v-.4c-.803-.803-1.383-1.874-2.23-2.632-2.275-1.963-4.775-3.882-7.363-5.488-1.383-.892-3.168-1.473-4.64-2.23-.537-.268-1.428-.402-1.74-.848-.805-.98-1.25-2.275-1.83-3.436l-3.658-7.763c-.803-1.74-1.295-3.48-2.275-5.086-4.596-7.585-9.594-12.18-17.268-16.687-1.65-.937-3.613-1.34-5.7-1.83l-3.346-.18c-.715-.312-1.428-1.16-2.053-1.562-2.543-1.606-9.102-5.086-10.977-.5-1.205 2.9 1.785 5.755 2.8 7.228.76 1.026 1.74 2.186 2.277 3.346.3.758.4 1.562.713 2.365.713 1.963 1.383 4.15 2.32 5.98.5.937 1.025 1.92 1.65 2.767.357.5.982.714 1.115 1.517-.625.893-.668 2.23-1.025 3.347-1.607 5.042-.982 11.288 1.293 15 .715 1.115 2.4 3.57 4.686 2.632 2.008-.803 1.56-3.346 2.14-5.577.135-.535.045-.892.312-1.25v.1l1.83 3.703c1.383 2.186 3.793 4.462 5.8 5.98 1.07.803 1.918 2.187 3.256 2.677v-.135h-.088c-.268-.4-.67-.58-1.027-.892-.803-.803-1.695-1.785-2.32-2.677-1.873-2.498-3.523-5.265-4.996-8.12-.715-1.383-1.34-2.9-1.918-4.283-.27-.536-.27-1.34-.715-1.606-.67.98-1.65 1.83-2.143 3.034-.848 1.918-.936 4.283-1.248 6.737-.18.045-.1 0-.18.1-1.426-.356-1.918-1.83-2.453-3.078-1.338-3.168-1.562-8.254-.402-11.913.312-.937 1.652-3.882 1.117-4.774-.27-.848-1.16-1.338-1.652-2.008-.58-.848-1.203-1.918-1.605-2.855-1.07-2.5-1.605-5.265-2.766-7.764-.537-1.16-1.473-2.365-2.232-3.435-.848-1.205-1.783-2.053-2.453-3.48-.223-.5-.535-1.294-.178-1.83.088-.357.268-.5.623-.58.58-.5 2.232.134 2.812.4 1.65.67 3.033 1.294 4.416 2.23.625.446 1.295 1.294 2.098 1.518h.938c1.428.312 3.033.1 4.37.5 2.365.76 4.506 1.874 6.426 3.08 5.844 3.703 10.664 8.968 13.92 15.26.535 1.026.758 1.963 1.25 3.034.938 2.187 2.098 4.417 3.033 6.56.938 2.097 1.83 4.24 3.168 5.98.67.937 3.346 1.427 4.55 1.918.893.4 2.275.76 3.08 1.25 1.516.937 3.033 2.008 4.46 3.034.713.534 2.945 1.65 3.078 2.54zm-45.5-38.772a7.09 7.09 0 0 0-1.828.223v.1h.088c.357.714.982 1.205 1.428 1.83l1.027 2.142.088-.1c.625-.446.938-1.16.938-2.23-.268-.312-.312-.625-.535-.937-.268-.446-.848-.67-1.206-1.026z" transform="matrix(.390229 0 0 .38781 -46.300037 -16.856717)" fill-rule="evenodd" fill="#00678c" /></svg></a>
<a href="https://postgresql.org"><img style="width:40px;height:40px" src="https://icon.horse/icon/postgresql.org"></a>
<a href="https://couchdb.apache.org"><img style="width:40px;height:40px" src="https://icon.horse/icon/couchdb.apache.org"></a>
<a href="https://redis.io"><svg style="width:40px;height:40px" viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" ><defs ><path id="a" d="m45.536 38.764c-2.013 1.05-12.44 5.337-14.66 6.494s-3.453 1.146-5.207.308-12.85-5.32-14.85-6.276c-1-.478-1.524-.88-1.524-1.26v-3.813s14.447-3.145 16.78-3.982 3.14-.867 5.126-.14 13.853 2.868 15.814 3.587v3.76c0 .377-.452.8-1.477 1.324z" /><path id="b" d="m45.536 28.733c-2.013 1.05-12.44 5.337-14.66 6.494s-3.453 1.146-5.207.308-12.85-5.32-14.85-6.276-2.04-1.613-.077-2.382l15.332-5.935c2.332-.837 3.14-.867 5.126-.14s12.35 4.853 14.312 5.57 2.037 1.31.024 2.36z" /></defs ><g transform="matrix(.848327 0 0 .848327 -7.883573 -9.449691)" ><use fill="#a41e11" xlink:href="#a" /><path d="m45.536 34.95c-2.013 1.05-12.44 5.337-14.66 6.494s-3.453 1.146-5.207.308-12.85-5.32-14.85-6.276-2.04-1.613-.077-2.382l15.332-5.936c2.332-.836 3.14-.867 5.126-.14s12.35 4.852 14.31 5.582 2.037 1.31.024 2.36z" fill="#d82c20" /><use fill="#a41e11" xlink:href="#a" y="-6.218" /><use fill="#d82c20" xlink:href="#b" /><path d="m45.536 26.098c-2.013 1.05-12.44 5.337-14.66 6.495s-3.453 1.146-5.207.308-12.85-5.32-14.85-6.276c-1-.478-1.524-.88-1.524-1.26v-3.815s14.447-3.145 16.78-3.982 3.14-.867 5.126-.14 13.853 2.868 15.814 3.587v3.76c0 .377-.452.8-1.477 1.324z" fill="#a41e11" /><use fill="#d82c20" xlink:href="#b" y="-6.449" /><g fill="#fff" ><path d="m29.096 20.712-1.182-1.965-3.774-.34 2.816-1.016-.845-1.56 2.636 1.03 2.486-.814-.672 1.612 2.534.95-3.268.34zm-6.296 3.912 8.74-1.342-2.64 3.872z" /><ellipse cx="20.444" cy="21.402" rx="4.672" ry="1.811" /></g ><path d="m42.132 21.138-5.17 2.042-.004-4.087z" fill="#7a0c00" /><path d="m36.963 23.18-.56.22-5.166-2.042 5.723-2.264z" fill="#ad2115" /></g ></svg ></a>
If you have a new database you would like to add, raise an idea [here](https://feedback.coolify.io/) to get feedback from the community!
### Services
You quickly need to host a self-hostable, open-source service? You can do it with a few clicks!
- [WordPress](https://docs.coollabs.io/coolify/services/wordpress)
- [Ghost](https://ghost.org)
- [Plausible Analytics](https://docs.coollabs.io/coolify/services/plausible-analytics)
- [NocoDB](https://nocodb.com)
- [VSCode Server](https://github.com/cdr/code-server)
- [MinIO](https://min.io)
- [VaultWarden](https://github.com/dani-garcia/vaultwarden)
- [LanguageTool](https://languagetool.org)
- [n8n](https://n8n.io)
- [Uptime Kuma](https://github.com/louislam/uptime-kuma)
- [MeiliSearch](https://github.com/meilisearch/meilisearch)
- [Umami](https://github.com/mikecao/umami)
- [Fider](https://fider.io)
- [Hasura](https://hasura.io)
If you have a new service you would like to add, raise an idea [here](https://feedback.coolify.io/) to get feedback from the community!
## Migration from v1
A fresh installation is necessary. v2 and v3 are not compatible with v1.
## Support
- Twitter: [@andrasbacsai](https://twitter.com/andrasbacsai)
- Telegram: [@andrasbacsai](https://t.me/andrasbacsai)
- Email: [andras@coollabs.io](mailto:andras@coollabs.io)
- Discord: [Invitation](https://discord.gg/xhBCC7eGKw)
## License
This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Please see the [LICENSE](/LICENSE) file in our repository for the full text.
<a href="https://opencollective.com/coollabsio/organization/9/website"><img src="https://opencollective.com/coollabsio/organization/9/avatar.svg"></a>

View File

@@ -0,0 +1,30 @@
-- CreateTable
CREATE TABLE "GlitchTip" (
"id" TEXT NOT NULL PRIMARY KEY,
"postgresqlUser" TEXT NOT NULL,
"postgresqlPassword" TEXT NOT NULL,
"postgresqlDatabase" TEXT NOT NULL,
"postgresqlPublicPort" INTEGER,
"secretKeyBase" TEXT,
"defaultEmail" TEXT NOT NULL,
"defaultUsername" TEXT NOT NULL,
"defaultPassword" TEXT NOT NULL,
"defaultEmailFrom" TEXT NOT NULL DEFAULT 'glitchtip@domain.tdl',
"emailSmtpHost" TEXT DEFAULT 'domain.tdl',
"emailSmtpPort" INTEGER DEFAULT 25,
"emailSmtpUser" TEXT,
"emailSmtpPassword" TEXT,
"emailSmtpUseTls" BOOLEAN DEFAULT false,
"emailSmtpUseSsl" BOOLEAN DEFAULT false,
"emailBackend" TEXT,
"mailgunApiKey" TEXT,
"sendgridApiKey" TEXT,
"enableOpenUserRegistration" BOOLEAN NOT NULL DEFAULT true,
"serviceId" TEXT NOT NULL,
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" DATETIME NOT NULL,
CONSTRAINT "GlitchTip_serviceId_fkey" FOREIGN KEY ("serviceId") REFERENCES "Service" ("id") ON DELETE RESTRICT ON UPDATE CASCADE
);
-- CreateIndex
CREATE UNIQUE INDEX "GlitchTip_serviceId_key" ON "GlitchTip"("serviceId");

View File

@@ -0,0 +1,22 @@
-- CreateTable
CREATE TABLE "Appwrite" (
"id" TEXT NOT NULL PRIMARY KEY,
"serviceId" TEXT NOT NULL,
"opensslKeyV1" TEXT NOT NULL,
"executorSecret" TEXT NOT NULL,
"redisPassword" TEXT NOT NULL,
"mariadbHost" TEXT,
"mariadbPort" INTEGER NOT NULL DEFAULT 3306,
"mariadbUser" TEXT NOT NULL,
"mariadbPassword" TEXT NOT NULL,
"mariadbRootUser" TEXT NOT NULL,
"mariadbRootUserPassword" TEXT NOT NULL,
"mariadbDatabase" TEXT NOT NULL,
"mariadbPublicPort" INTEGER,
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" DATETIME NOT NULL,
CONSTRAINT "Appwrite_serviceId_fkey" FOREIGN KEY ("serviceId") REFERENCES "Service" ("id") ON DELETE RESTRICT ON UPDATE CASCADE
);
-- CreateIndex
CREATE UNIQUE INDEX "Appwrite_serviceId_key" ON "Appwrite"("serviceId");

View File

@@ -0,0 +1,20 @@
-- RedefineTables
PRAGMA foreign_keys=OFF;
CREATE TABLE "new_ApplicationSettings" (
"id" TEXT NOT NULL PRIMARY KEY,
"applicationId" TEXT NOT NULL,
"dualCerts" BOOLEAN NOT NULL DEFAULT false,
"debug" BOOLEAN NOT NULL DEFAULT false,
"previews" BOOLEAN NOT NULL DEFAULT false,
"autodeploy" BOOLEAN NOT NULL DEFAULT true,
"isBot" BOOLEAN NOT NULL DEFAULT false,
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" DATETIME NOT NULL,
CONSTRAINT "ApplicationSettings_applicationId_fkey" FOREIGN KEY ("applicationId") REFERENCES "Application" ("id") ON DELETE RESTRICT ON UPDATE CASCADE
);
INSERT INTO "new_ApplicationSettings" ("applicationId", "autodeploy", "createdAt", "debug", "dualCerts", "id", "previews", "updatedAt") SELECT "applicationId", "autodeploy", "createdAt", "debug", "dualCerts", "id", "previews", "updatedAt" FROM "ApplicationSettings";
DROP TABLE "ApplicationSettings";
ALTER TABLE "new_ApplicationSettings" RENAME TO "ApplicationSettings";
CREATE UNIQUE INDEX "ApplicationSettings_applicationId_key" ON "ApplicationSettings"("applicationId");
PRAGMA foreign_key_check;
PRAGMA foreign_keys=ON;

View File

@@ -0,0 +1,2 @@
-- AlterTable
ALTER TABLE "Setting" ADD COLUMN "DNSServers" TEXT;

View File

@@ -0,0 +1,42 @@
-- RedefineTables
PRAGMA foreign_keys=OFF;
CREATE TABLE "new_GitSource" (
"id" TEXT NOT NULL PRIMARY KEY,
"name" TEXT NOT NULL,
"forPublic" BOOLEAN NOT NULL DEFAULT false,
"type" TEXT,
"apiUrl" TEXT,
"htmlUrl" TEXT,
"customPort" INTEGER NOT NULL DEFAULT 22,
"organization" TEXT,
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" DATETIME NOT NULL,
"githubAppId" TEXT,
"gitlabAppId" TEXT,
CONSTRAINT "GitSource_githubAppId_fkey" FOREIGN KEY ("githubAppId") REFERENCES "GithubApp" ("id") ON DELETE SET NULL ON UPDATE CASCADE,
CONSTRAINT "GitSource_gitlabAppId_fkey" FOREIGN KEY ("gitlabAppId") REFERENCES "GitlabApp" ("id") ON DELETE SET NULL ON UPDATE CASCADE
);
INSERT INTO "new_GitSource" ("apiUrl", "createdAt", "customPort", "githubAppId", "gitlabAppId", "htmlUrl", "id", "name", "organization", "type", "updatedAt") SELECT "apiUrl", "createdAt", "customPort", "githubAppId", "gitlabAppId", "htmlUrl", "id", "name", "organization", "type", "updatedAt" FROM "GitSource";
DROP TABLE "GitSource";
ALTER TABLE "new_GitSource" RENAME TO "GitSource";
CREATE UNIQUE INDEX "GitSource_githubAppId_key" ON "GitSource"("githubAppId");
CREATE UNIQUE INDEX "GitSource_gitlabAppId_key" ON "GitSource"("gitlabAppId");
CREATE TABLE "new_ApplicationSettings" (
"id" TEXT NOT NULL PRIMARY KEY,
"applicationId" TEXT NOT NULL,
"dualCerts" BOOLEAN NOT NULL DEFAULT false,
"debug" BOOLEAN NOT NULL DEFAULT false,
"previews" BOOLEAN NOT NULL DEFAULT false,
"autodeploy" BOOLEAN NOT NULL DEFAULT true,
"isBot" BOOLEAN NOT NULL DEFAULT false,
"isPublicRepository" BOOLEAN NOT NULL DEFAULT false,
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" DATETIME NOT NULL,
CONSTRAINT "ApplicationSettings_applicationId_fkey" FOREIGN KEY ("applicationId") REFERENCES "Application" ("id") ON DELETE RESTRICT ON UPDATE CASCADE
);
INSERT INTO "new_ApplicationSettings" ("applicationId", "autodeploy", "createdAt", "debug", "dualCerts", "id", "isBot", "previews", "updatedAt") SELECT "applicationId", "autodeploy", "createdAt", "debug", "dualCerts", "id", "isBot", "previews", "updatedAt" FROM "ApplicationSettings";
DROP TABLE "ApplicationSettings";
ALTER TABLE "new_ApplicationSettings" RENAME TO "ApplicationSettings";
CREATE UNIQUE INDEX "ApplicationSettings_applicationId_key" ON "ApplicationSettings"("applicationId");
PRAGMA foreign_key_check;
PRAGMA foreign_keys=ON;

View File

@@ -20,6 +20,7 @@ model Setting {
proxyHash String?
isAutoUpdateEnabled Boolean @default(false)
isDNSCheckEnabled Boolean @default(true)
DNSServers String?
isTraefikUsed Boolean @default(true)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@ -118,15 +119,17 @@ model Application {
}
model ApplicationSettings {
id String @id @default(cuid())
applicationId String @unique
dualCerts Boolean @default(false)
debug Boolean @default(false)
previews Boolean @default(false)
autodeploy Boolean @default(true)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
application Application @relation(fields: [applicationId], references: [id])
id String @id @default(cuid())
applicationId String @unique
dualCerts Boolean @default(false)
debug Boolean @default(false)
previews Boolean @default(false)
autodeploy Boolean @default(true)
isBot Boolean @default(false)
isPublicRepository Boolean @default(false)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
application Application @relation(fields: [applicationId], references: [id])
}
model ApplicationPersistentStorage {
@@ -236,6 +239,7 @@ model SshKey {
model GitSource {
id String @id @default(cuid())
name String
forPublic Boolean @default(false)
type String?
apiUrl String?
htmlUrl String?
@@ -323,8 +327,10 @@ model Service {
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
destinationDocker DestinationDocker? @relation(fields: [destinationDockerId], references: [id])
fider Fider?
ghost Ghost?
glitchTip GlitchTip?
hasura Hasura?
meiliSearch MeiliSearch?
minio Minio?
@@ -335,7 +341,9 @@ model Service {
umami Umami?
vscodeserver Vscodeserver?
wordpress Wordpress?
teams Team[]
appwrite Appwrite?
teams Team[]
}
model PlausibleAnalytics {
@@ -491,3 +499,49 @@ model Moodle {
updatedAt DateTime @updatedAt
service Service @relation(fields: [serviceId], references: [id])
}
model Appwrite {
id String @id @default(cuid())
serviceId String @unique
opensslKeyV1 String
executorSecret String
redisPassword String
mariadbHost String?
mariadbPort Int @default(3306)
mariadbUser String
mariadbPassword String
mariadbRootUser String
mariadbRootUserPassword String
mariadbDatabase String
mariadbPublicPort Int?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
service Service @relation(fields: [serviceId], references: [id])
}
model GlitchTip {
id String @id @default(cuid())
postgresqlUser String
postgresqlPassword String
postgresqlDatabase String
postgresqlPublicPort Int?
secretKeyBase String?
defaultEmail String
defaultUsername String
defaultPassword String
defaultEmailFrom String @default("glitchtip@domain.tdl")
emailSmtpHost String? @default("domain.tdl")
emailSmtpPort Int? @default(25)
emailSmtpUser String?
emailSmtpPassword String?
emailSmtpUseTls Boolean? @default(false)
emailSmtpUseSsl Boolean? @default(false)
emailBackend String?
mailgunApiKey String?
sendgridApiKey String?
enableOpenUserRegistration Boolean @default(true)
serviceId String @unique
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
service Service @relation(fields: [serviceId], references: [id])
}

View File

@@ -66,6 +66,34 @@ async function main() {
}
});
}
const github = await prisma.gitSource.findFirst({
where: { htmlUrl: 'https://github.com', forPublic: true }
});
const gitlab = await prisma.gitSource.findFirst({
where: { htmlUrl: 'https://gitlab.com', forPublic: true }
});
if (!github) {
await prisma.gitSource.create({
data: {
apiUrl: 'https://api.github.com',
htmlUrl: 'https://github.com',
forPublic: true,
name: 'Github Public',
type: 'github'
}
});
}
if (!gitlab) {
await prisma.gitSource.create({
data: {
apiUrl: 'https://gitlab.com/api/v4',
htmlUrl: 'https://gitlab.com',
forPublic: true,
name: 'Gitlab Public',
type: 'gitlab'
}
});
}
}
main()
.catch((e) => {

View File

@@ -5,8 +5,10 @@ import env from '@fastify/env';
import cookie from '@fastify/cookie';
import path, { join } from 'path';
import autoLoad from '@fastify/autoload';
import { asyncExecShell, isDev, listSettings, prisma } from './lib/common';
import { asyncExecShell, isDev, listSettings, prisma, version } from './lib/common';
import { scheduler } from './lib/scheduler';
import axios from 'axios';
import compareVersions from 'compare-versions';
declare module 'fastify' {
interface FastifyInstance {
@@ -113,8 +115,22 @@ fastify.listen({ port, host }, async (err: any, address: any) => {
setInterval(async () => {
const { isAutoUpdateEnabled } = await prisma.setting.findFirst();
if (isAutoUpdateEnabled) {
if (scheduler.workers.has('deployApplication')) {
scheduler.workers.get('deployApplication').postMessage("status:autoUpdater");
const currentVersion = version;
const { data: versions } = await axios
.get(
`https://get.coollabs.io/versions.json`
, {
params: {
appId: process.env['COOLIFY_APP_ID'] || undefined,
version: currentVersion
}
})
const latestVersion = versions['coolify'].main.version;
const isUpdateAvailable = compareVersions(latestVersion, currentVersion);
if (isUpdateAvailable === 1) {
if (scheduler.workers.has('deployApplication')) {
scheduler.workers.get('deployApplication').postMessage("status:autoUpdater");
}
}
}
}, isDev ? 5000 : 60000 * 15)

View File

@@ -4,7 +4,7 @@ import fs from 'fs/promises';
import yaml from 'js-yaml';
import { copyBaseConfigurationFiles, makeLabelForStandaloneApplication, saveBuildLog, setDefaultConfiguration } from '../lib/buildPacks/common';
import { createDirectories, decrypt, executeDockerCmd, getDomain, prisma } from '../lib/common';
import { createDirectories, decrypt, defaultComposeConfiguration, executeDockerCmd, getDomain, prisma } from '../lib/common';
import * as importers from '../lib/importers';
import * as buildpacks from '../lib/buildPacks';
@@ -56,6 +56,7 @@ import * as buildpacks from '../lib/buildPacks';
baseImage,
baseBuildImage,
deploymentType,
forceRebuild
} = message
let {
branch,
@@ -69,6 +70,30 @@ import * as buildpacks from '../lib/buildPacks';
dockerFileLocation,
denoMainFile
} = message
const currentHash = crypto
.createHash('sha256')
.update(
JSON.stringify({
pythonWSGI,
pythonModule,
pythonVariable,
deploymentType,
denoOptions,
baseImage,
baseBuildImage,
buildPack,
port,
exposePort,
installCommand,
buildCommand,
startCommand,
secrets,
branch,
repository,
fqdn
})
)
.digest('hex');
try {
const { debug } = settings;
if (concurrency === 1) {
@@ -131,7 +156,8 @@ import * as buildpacks from '../lib/buildPacks';
htmlUrl: gitSource.htmlUrl,
projectId,
deployKeyId: gitSource.gitlabApp?.deployKeyId || null,
privateSshKey: decrypt(gitSource.gitlabApp?.privateSshKey) || null
privateSshKey: decrypt(gitSource.gitlabApp?.privateSshKey) || null,
forPublic: gitSource.forPublic
});
if (!commit) {
throw new Error('No commit found?');
@@ -146,31 +172,10 @@ import * as buildpacks from '../lib/buildPacks';
} catch (err) {
console.log(err);
}
if (!pullmergeRequestId) {
const currentHash = crypto
//@ts-ignore
.createHash('sha256')
.update(
JSON.stringify({
buildPack,
port,
exposePort,
installCommand,
buildCommand,
startCommand,
secrets,
branch,
repository,
fqdn
})
)
.digest('hex');
if (configHash !== currentHash) {
await prisma.application.update({
where: { id: applicationId },
data: { configHash: currentHash }
});
deployNeeded = true;
if (configHash) {
await saveBuildLog({ line: 'Configuration changed.', buildId, applicationId });
@@ -193,8 +198,10 @@ import * as buildpacks from '../lib/buildPacks';
//
}
await copyBaseConfigurationFiles(buildPack, workdir, buildId, applicationId, baseImage);
if (forceRebuild) deployNeeded = true
if (!imageFound || deployNeeded) {
// if (true) {
// if (true) {
if (buildpacks[buildPack])
await buildpacks[buildPack]({
dockerId: destinationDocker.id,
@@ -243,16 +250,18 @@ import * as buildpacks from '../lib/buildPacks';
} catch (error) {
//
}
const envs = [];
const envs = [
`PORT=${port}`
];
if (secrets.length > 0) {
secrets.forEach((secret) => {
if (pullmergeRequestId) {
if (secret.isPRMRSecret) {
envs.push(`${secret.name}='${secret.value}'`);
envs.push(`${secret.name}=${secret.value}`);
}
} else {
if (!secret.isPRMRSecret) {
envs.push(`${secret.name}='${secret.value}'`);
envs.push(`${secret.name}=${secret.value}`);
}
}
});
@@ -299,23 +308,14 @@ import * as buildpacks from '../lib/buildPacks';
container_name: imageId,
volumes,
env_file: envFound ? [`${workdir}/.env`] : [],
networks: [destinationDocker.network],
labels,
depends_on: [],
restart: 'always',
expose: [port],
...(exposePort ? { ports: [`${exposePort}:${port}`] } : {}),
// logging: {
// driver: 'fluentd',
// },
deploy: {
restart_policy: {
condition: 'on-failure',
delay: '5s',
max_attempts: 3,
window: '120s'
}
}
...defaultComposeConfiguration(destinationDocker.network),
}
},
networks: {
@@ -338,6 +338,10 @@ import * as buildpacks from '../lib/buildPacks';
}
await saveBuildLog({ line: 'Proxy will be updated shortly.', buildId, applicationId });
await prisma.build.update({ where: { id: message.build_id }, data: { status: 'success' } });
if (!pullmergeRequestId) await prisma.application.update({
where: { id: applicationId },
data: { configHash: currentHash }
});
}
}

View File

@@ -252,6 +252,20 @@ export function setDefaultBaseImage(buildPack: string | null, deploymentType: st
label: 'python:3.7-slim-bullseye'
}
];
const herokuVersions = [
{
value: 'heroku/builder:22',
label: 'heroku/builder:22'
},
{
value: 'heroku/buildpacks:20',
label: 'heroku/buildpacks:20'
},
{
value: 'heroku/builder-classic:22',
label: 'heroku/builder-classic:22'
},
]
let payload: any = {
baseImage: null,
baseBuildImage: null,
@@ -299,6 +313,11 @@ export function setDefaultBaseImage(buildPack: string | null, deploymentType: st
payload.baseBuildImage = 'node:18';
payload.baseBuildImages = nodeVersions;
}
if (buildPack === 'heroku') {
payload.baseImage = 'heroku/buildpacks:20';
payload.baseImages = herokuVersions;
}
return payload;
}

View File

@@ -2,14 +2,14 @@ import { executeDockerCmd, prisma } from "../common"
import { saveBuildLog } from "./common";
export default async function (data: any): Promise<void> {
const { buildId, applicationId, tag, dockerId, debug, workdir } = data
try {
const { buildId, applicationId, tag, dockerId, debug, workdir } = data
await saveBuildLog({ line: `Building image started.`, buildId, applicationId });
const { stdout } = await executeDockerCmd({
dockerId,
command: `pack build -p ${workdir} ${applicationId}:${tag} --builder heroku/buildpacks:20`
})
if (debug) {
const array = stdout.split('\n')
for (const line of array) {
@@ -24,6 +24,16 @@ export default async function (data: any): Promise<void> {
}
await saveBuildLog({ line: `Building image successful.`, buildId, applicationId });
} catch (error) {
const array = error.stdout.split('\n')
for (const line of array) {
if (line !== '\n') {
await saveBuildLog({
line: `${line.replace('\n', '')}`,
buildId,
applicationId
});
}
}
throw error;
}
}

View File

@@ -17,7 +17,7 @@ import { checkContainer, removeContainer } from './docker';
import { day } from './dayjs';
import * as serviceFields from './serviceFields'
export const version = '3.3.3';
export const version = '3.7.0';
export const isDev = process.env.NODE_ENV === 'development';
const algorithm = 'aes-256-ctr';
@@ -78,6 +78,9 @@ export const include: any = {
umami: true,
hasura: true,
fider: true,
moodle: true,
appwrite: true,
glitchTip: true,
};
export const uniqueName = (): string => uniqueNamesGenerator(customConfig);
@@ -258,8 +261,8 @@ export const supportedServiceTypesAndVersions = [
fancyName: 'Hasura',
baseImage: 'hasura/graphql-engine',
images: ['postgres:12-alpine'],
versions: ['latest', 'v2.8.4', 'v2.5.1'],
recommendedVersion: 'v2.8.4',
versions: ['latest', 'v2.10.0', 'v2.5.1'],
recommendedVersion: 'v2.10.0',
ports: {
main: 8080
}
@@ -275,6 +278,17 @@ export const supportedServiceTypesAndVersions = [
main: 3000
}
},
{
name: 'appwrite',
fancyName: 'Appwrite',
baseImage: 'appwrite/appwrite',
images: ['mariadb:10.7', 'redis:6.2-alpine', 'appwrite/telegraf:1.4.0'],
versions: ['latest', '0.15.3'],
recommendedVersion: '0.15.3',
ports: {
main: 80
}
},
// {
// name: 'moodle',
// fancyName: 'Moodle',
@@ -286,6 +300,17 @@ export const supportedServiceTypesAndVersions = [
// main: 8080
// }
// }
{
name: 'glitchTip',
fancyName: 'GlitchTip',
baseImage: 'glitchtip/glitchtip',
images: ['postgres:14-alpine', 'redis:7-alpine'],
versions: ['latest'],
recommendedVersion: 'latest',
ports: {
main: 8000
}
},
];
export async function checkDoubleBranch(branch: string, projectId: number): Promise<boolean> {
@@ -294,6 +319,10 @@ export async function checkDoubleBranch(branch: string, projectId: number): Prom
}
export async function isDNSValid(hostname: any, domain: string): Promise<any> {
const { isIP } = await import('is-ip');
const { DNSServers } = await listSettings();
if (DNSServers) {
dns.setServers([DNSServers]);
}
let resolves = [];
try {
if (isIP(hostname)) {
@@ -307,7 +336,6 @@ export async function isDNSValid(hostname: any, domain: string): Promise<any> {
try {
let ipDomainFound = false;
dns.setServers(['1.1.1.1', '8.8.8.8']);
const dnsResolve = await dns.resolve4(domain);
if (dnsResolve.length > 0) {
for (const ip of dnsResolve) {
@@ -399,7 +427,12 @@ export async function checkDomainsIsValidInDNS({ hostname, fqdn, dualCerts }): P
const { isIP } = await import('is-ip');
const domain = getDomain(fqdn);
const domainDualCert = domain.includes('www.') ? domain.replace('www.', '') : `www.${domain}`;
dns.setServers(['1.1.1.1', '8.8.8.8']);
const { DNSServers } = await listSettings();
if (DNSServers) {
dns.setServers([DNSServers]);
}
let resolves = [];
try {
if (isIP(hostname)) {
@@ -579,6 +612,11 @@ export async function executeDockerCmd({ dockerId, command }: { dockerId: string
} else {
engine = 'unix:///var/run/docker.sock'
}
if (process.env.CODESANDBOX_HOST) {
if (command.startsWith('docker compose')) {
command = command.replace(/docker compose/gi, 'docker-compose')
}
}
return await asyncExecShell(
`DOCKER_BUILDKIT=1 DOCKER_HOST="${engine}" ${command}`
);
@@ -590,6 +628,11 @@ export async function startTraefikProxy(id: string): Promise<void> {
const { id: settingsId, ipv4, ipv6 } = await listSettings();
if (!found) {
const { stdout: coolifyNetwork } = await executeDockerCmd({ dockerId: id, command: `docker network ls --filter 'name=coolify-infra' --no-trunc --format "{{json .}}"` })
if (!coolifyNetwork) {
await executeDockerCmd({ dockerId: id, command: `docker network create --attachable coolify-infra` })
}
const { stdout: Config } = await executeDockerCmd({ dockerId: id, command: `docker network inspect ${network} --format '{{json .IPAM.Config }}'` })
const ip = JSON.parse(Config)[0].Gateway;
let traefikUrl = mainTraefikEndpoint
@@ -873,11 +916,11 @@ export function generateDatabaseConfiguration(database: any, arch: string):
}
if (isARM(arch)) {
configuration.volume = `${id}-${type}-data:/var/lib/postgresql`;
configuration.environmentVariables = {
POSTGRES_PASSWORD: dbUserPassword,
POSTGRES_USER: dbUser,
POSTGRES_DB: defaultDatabase
}
configuration.environmentVariables = {
POSTGRES_PASSWORD: dbUserPassword,
POSTGRES_USER: dbUser,
POSTGRES_DB: defaultDatabase
}
}
return configuration
} else if (type === 'redis') {
@@ -1145,6 +1188,25 @@ export async function updatePasswordInDb(database, user, newPassword, isRoot) {
}
}
}
export async function checkExposedPort({ id, configuredPort, exposePort, dockerId, remoteIpAddress }: { id: string, configuredPort?: number, exposePort: number, dockerId: string, remoteIpAddress?: string }) {
if (exposePort < 1024 || exposePort > 65535) {
throw { status: 500, message: `Exposed Port needs to be between 1024 and 65535.` }
}
if (configuredPort) {
if (configuredPort !== exposePort) {
const availablePort = await getFreeExposedPort(id, exposePort, dockerId, remoteIpAddress);
if (availablePort.toString() !== exposePort.toString()) {
throw { status: 500, message: `Port ${exposePort} is already in use.` }
}
}
} else {
const availablePort = await getFreeExposedPort(id, exposePort, dockerId, remoteIpAddress);
if (availablePort.toString() !== exposePort.toString()) {
throw { status: 500, message: `Port ${exposePort} is already in use.` }
}
}
}
export async function getFreeExposedPort(id, exposePort, dockerId, remoteIpAddress) {
const { default: getPort } = await import('get-port');
const applicationUsed = await (
@@ -1528,7 +1590,62 @@ export async function configureServiceType({
}
}
});
} else {
} else if (type === 'appwrite') {
const opensslKeyV1 = encrypt(generatePassword());
const executorSecret = encrypt(generatePassword());
const redisPassword = encrypt(generatePassword());
const mariadbHost = `${id}-mariadb`
const mariadbUser = cuid();
const mariadbPassword = encrypt(generatePassword());
const mariadbDatabase = 'appwrite';
const mariadbRootUser = cuid();
const mariadbRootUserPassword = encrypt(generatePassword());
await prisma.service.update({
where: { id },
data: {
type,
appwrite: {
create: {
opensslKeyV1,
executorSecret,
redisPassword,
mariadbHost,
mariadbUser,
mariadbPassword,
mariadbDatabase,
mariadbRootUser,
mariadbRootUserPassword
}
}
}
});
} else if (type === 'glitchTip') {
const defaultUsername = cuid();
const defaultEmail = `${defaultUsername}@example.com`;
const defaultPassword = encrypt(generatePassword());
const postgresqlUser = cuid();
const postgresqlPassword = encrypt(generatePassword());
const postgresqlDatabase = 'glitchTip';
const secretKeyBase = encrypt(generatePassword(64));
await prisma.service.update({
where: { id },
data: {
type,
glitchTip: {
create: {
postgresqlDatabase,
postgresqlUser,
postgresqlPassword,
secretKeyBase,
defaultEmail,
defaultUsername,
defaultPassword,
}
}
}
});
} else {
await prisma.service.update({
where: { id },
data: {
@@ -1539,6 +1656,7 @@ export async function configureServiceType({
}
export async function removeService({ id }: { id: string }): Promise<void> {
await prisma.serviceSecret.deleteMany({ where: { serviceId: id } });
await prisma.servicePersistentStorage.deleteMany({ where: { serviceId: id } });
await prisma.meiliSearch.deleteMany({ where: { serviceId: id } });
await prisma.fider.deleteMany({ where: { serviceId: id } });
@@ -1549,8 +1667,9 @@ export async function removeService({ id }: { id: string }): Promise<void> {
await prisma.minio.deleteMany({ where: { serviceId: id } });
await prisma.vscodeserver.deleteMany({ where: { serviceId: id } });
await prisma.wordpress.deleteMany({ where: { serviceId: id } });
await prisma.serviceSecret.deleteMany({ where: { serviceId: id } });
await prisma.glitchTip.deleteMany({ where: { serviceId: id } });
await prisma.moodle.deleteMany({ where: { serviceId: id } });
await prisma.appwrite.deleteMany({ where: { serviceId: id } });
await prisma.service.delete({ where: { id } });
}
@@ -1615,9 +1734,9 @@ export const getServiceMainPort = (service: string) => {
export function makeLabelForServices(type) {
return [
'coolify.managed=true',
`coolify.version = ${version} `,
`coolify.version = ${version}`,
`coolify.type = service`,
`coolify.service.type = ${type} `
`coolify.service.type = ${type}`
];
}
export function errorHandler({ status = 500, message = 'Unknown error.' }: { status: number, message: string | any }) {
@@ -1689,7 +1808,7 @@ export function convertTolOldVolumeNames(type) {
// export async function getAvailableServices(): Promise<any> {
// const { data } = await axios.get(`https://gist.githubusercontent.com/andrasbacsai/4aac36d8d6214dbfc34fa78110554a50/raw/5b27e6c37d78aaeedc1148d797112c827a2f43cf/availableServices.json`)
// return data
//
//
export async function cleanupDockerStorage(dockerId, lowDiskSpace, force) {
// Cleanup old coolify images
try {
@@ -1752,4 +1871,18 @@ export function persistentVolumes(id, persistentStorage, config) {
...composeVolumes
) || {}
return { volumes, volumeMounts }
}
}
export function defaultComposeConfiguration(network: string): any {
return {
networks: [network],
restart: 'on-failure',
deploy: {
restart_policy: {
condition: 'on-failure',
delay: '5s',
max_attempts: 10,
window: '120s'
}
}
}
}

View File

@@ -71,7 +71,6 @@ export async function removeContainer({
}): Promise<void> {
try {
const { stdout } = await executeDockerCmd({ dockerId, command: `docker inspect --format '{{json .State}}' ${id}` })
if (JSON.parse(stdout).Running) {
await executeDockerCmd({ dockerId, command: `docker stop -t 0 ${id}` })
await executeDockerCmd({ dockerId, command: `docker rm ${id}` })

View File

@@ -12,7 +12,8 @@ export default async function ({
htmlUrl,
branch,
buildId,
customPort
customPort,
forPublic
}: {
applicationId: string;
workdir: string;
@@ -23,41 +24,55 @@ export default async function ({
branch: string;
buildId: string;
customPort: number;
forPublic?: boolean;
}): Promise<string> {
const { default: got } = await import('got')
const url = htmlUrl.replace('https://', '').replace('http://', '');
await saveBuildLog({ line: 'GitHub importer started.', buildId, applicationId });
if (forPublic) {
await saveBuildLog({
line: `Cloning ${repository}:${branch} branch.`,
buildId,
applicationId
});
await asyncExecShell(
`git clone -q -b ${branch} https://${url}/${repository}.git ${workdir}/ && cd ${workdir} && git submodule update --init --recursive && git lfs pull && cd .. `
);
const body = await prisma.githubApp.findUnique({ where: { id: githubAppId } });
if (body.privateKey) body.privateKey = decrypt(body.privateKey);
const { privateKey, appId, installationId } = body
} else {
const body = await prisma.githubApp.findUnique({ where: { id: githubAppId } });
if (body.privateKey) body.privateKey = decrypt(body.privateKey);
const { privateKey, appId, installationId } = body
const githubPrivateKey = privateKey.replace(/\\n/g, '\n').replace(/"/g, '');
const githubPrivateKey = privateKey.replace(/\\n/g, '\n').replace(/"/g, '');
const payload = {
iat: Math.round(new Date().getTime() / 1000),
exp: Math.round(new Date().getTime() / 1000 + 60),
iss: appId
};
const jwtToken = jsonwebtoken.sign(payload, githubPrivateKey, {
algorithm: 'RS256'
});
const { token } = await got
.post(`${apiUrl}/app/installations/${installationId}/access_tokens`, {
headers: {
Authorization: `Bearer ${jwtToken}`,
Accept: 'application/vnd.github.machine-man-preview+json'
}
})
.json();
await saveBuildLog({
line: `Cloning ${repository}:${branch} branch.`,
buildId,
applicationId
});
await asyncExecShell(
`git clone -q -b ${branch} https://x-access-token:${token}@${url}/${repository}.git --config core.sshCommand="ssh -p ${customPort}" ${workdir}/ && cd ${workdir} && git submodule update --init --recursive && git lfs pull && cd .. `
);
const payload = {
iat: Math.round(new Date().getTime() / 1000),
exp: Math.round(new Date().getTime() / 1000 + 60),
iss: appId
};
const jwtToken = jsonwebtoken.sign(payload, githubPrivateKey, {
algorithm: 'RS256'
});
const { token } = await got
.post(`${apiUrl}/app/installations/${installationId}/access_tokens`, {
headers: {
Authorization: `Bearer ${jwtToken}`,
Accept: 'application/vnd.github.machine-man-preview+json'
}
})
.json();
await saveBuildLog({
line: `Cloning ${repository}:${branch} branch.`,
buildId,
applicationId
});
await asyncExecShell(
`git clone -q -b ${branch} https://x-access-token:${token}@${url}/${repository}.git --config core.sshCommand="ssh -p ${customPort}" ${workdir}/ && cd ${workdir} && git submodule update --init --recursive && git lfs pull && cd .. `
);
}
const { stdout: commit } = await asyncExecShell(`cd ${workdir}/ && git rev-parse HEAD`);
return commit.replace('\n', '');
}

View File

@@ -20,7 +20,6 @@ const options: any = {
}
if (message.caller === 'cleanupStorage') {
if (!scheduler.workers.has('cleanupStorage')) {
await scheduler.stop('deployApplication');
await scheduler.run('cleanupStorage')
}
}

View File

@@ -476,4 +476,198 @@ export const moodle = [{
isNumber: false,
isBoolean: false,
isEncrypted: false
}]
}]
export const appwrite = [{
name: 'opensslKeyV1',
isEditable: false,
isLowerCase: false,
isNumber: false,
isBoolean: false,
isEncrypted: true
},
{
name: 'executorSecret',
isEditable: false,
isLowerCase: false,
isNumber: false,
isBoolean: false,
isEncrypted: true
},
{
name: 'redisPassword',
isEditable: false,
isLowerCase: false,
isNumber: false,
isBoolean: false,
isEncrypted: true
},
{
name: 'mariadbHost',
isEditable: false,
isLowerCase: false,
isNumber: false,
isBoolean: false,
isEncrypted: false
},
{
name: 'mariadbPort',
isEditable: false,
isLowerCase: false,
isNumber: false,
isBoolean: false,
isEncrypted: false
},
{
name: 'mariadbUser',
isEditable: false,
isLowerCase: false,
isNumber: false,
isBoolean: false,
isEncrypted: false
},
{
name: 'mariadbPassword',
isEditable: false,
isLowerCase: false,
isNumber: false,
isBoolean: false,
isEncrypted: true
},
{
name: 'mariadbRootUser',
isEditable: false,
isLowerCase: false,
isNumber: false,
isBoolean: false,
isEncrypted: false
},
{
name: 'mariadbRootUserPassword',
isEditable: false,
isLowerCase: false,
isNumber: false,
isBoolean: false,
isEncrypted: true
},
{
name: 'mariadbDatabase',
isEditable: true,
isLowerCase: false,
isNumber: false,
isBoolean: false,
isEncrypted: false
}]
export const glitchTip = [{
name: 'postgresqlUser',
isEditable: false,
isLowerCase: false,
isNumber: false,
isBoolean: false,
isEncrypted: false
},
{
name: 'postgresqlPassword',
isEditable: false,
isLowerCase: false,
isNumber: false,
isBoolean: false,
isEncrypted: true
},
{
name: 'postgresqlDatabase',
isEditable: false,
isLowerCase: false,
isNumber: false,
isBoolean: false,
isEncrypted: false
},
{
name: 'postgresqlPublicPort',
isEditable: false,
isLowerCase: false,
isNumber: true,
isBoolean: false,
isEncrypted: false
},
{
name: 'secretKeyBase',
isEditable: false,
isLowerCase: false,
isNumber: false,
isBoolean: false,
isEncrypted: true
},
{
name: 'defaultEmail',
isEditable: false,
isLowerCase: false,
isNumber: false,
isBoolean: false,
isEncrypted: false
},
{
name: 'defaultUsername',
isEditable: false,
isLowerCase: false,
isNumber: false,
isBoolean: false,
isEncrypted: false
},
{
name: 'defaultPassword',
isEditable: false,
isLowerCase: false,
isNumber: false,
isBoolean: false,
isEncrypted: true
},
{
name: 'defaultFromEmail',
isEditable: true,
isLowerCase: false,
isNumber: false,
isBoolean: false,
isEncrypted: false
},
{
name: 'emailUrl',
isEditable: true,
isLowerCase: false,
isNumber: false,
isBoolean: false,
isEncrypted: false
},
{
name: 'emailBackend',
isEditable: true,
isLowerCase: false,
isNumber: false,
isBoolean: false,
isEncrypted: false
},
{
name: 'mailgunApiKey',
isEditable: true,
isLowerCase: false,
isNumber: false,
isBoolean: false,
isEncrypted: true
},
{
name: 'sendgridApiKey',
isEditable: true,
isLowerCase: false,
isNumber: false,
isBoolean: false,
isEncrypted: true
},
{
name: 'enableOpenUserRegistration',
isEditable: true,
isLowerCase: false,
isNumber: false,
isBoolean: true,
isEncrypted: false
}]

View File

@@ -0,0 +1,20 @@
import { createDirectories, getServiceFromDB, getServiceImage, getServiceMainPort, makeLabelForServices } from "./common";
export async function defaultServiceConfigurations({ id, teamId }) {
const service = await getServiceFromDB({ id, teamId });
const { destinationDockerId, destinationDocker, type, serviceSecret } = service;
const network = destinationDockerId && destinationDocker.network;
const port = getServiceMainPort(type);
const { workdir } = await createDirectories({ repository: type, buildId: id });
const image = getServiceImage(type);
let secrets = [];
if (serviceSecret.length > 0) {
serviceSecret.forEach((secret) => {
secrets.push(`${secret.name}=${secret.value}`);
});
}
return { ...service, network, port, workdir, image, secrets }
}

View File

@@ -5,7 +5,7 @@ import axios from 'axios';
import { FastifyReply } from 'fastify';
import { day } from '../../../../lib/dayjs';
import { setDefaultBaseImage, setDefaultConfiguration } from '../../../../lib/buildPacks/common';
import { checkDomainsIsValidInDNS, checkDoubleBranch, decrypt, encrypt, errorHandler, executeDockerCmd, generateSshKeyPair, getContainerUsage, getDomain, getFreeExposedPort, isDev, isDomainConfigured, prisma, stopBuild, uniqueName } from '../../../../lib/common';
import { checkDomainsIsValidInDNS, checkDoubleBranch, checkExposedPort, decrypt, encrypt, errorHandler, executeDockerCmd, generateSshKeyPair, getContainerUsage, getDomain, getFreeExposedPort, isDev, isDomainConfigured, listSettings, prisma, stopBuild, uniqueName } from '../../../../lib/common';
import { checkContainer, formatLabelsOnDocker, isContainerExited, removeContainer } from '../../../../lib/docker';
import { scheduler } from '../../../../lib/scheduler';
@@ -18,7 +18,7 @@ export async function listApplications(request: FastifyRequest) {
const { teamId } = request.user
const applications = await prisma.application.findMany({
where: { teams: { some: { id: teamId === '0' ? undefined : teamId } } },
include: { teams: true, destinationDocker: true }
include: { teams: true, destinationDocker: true, settings: true }
});
const settings = await prisma.setting.findFirst()
return {
@@ -90,10 +90,11 @@ export async function getApplication(request: FastifyRequest<OnlyId>) {
const { teamId } = request.user
const appId = process.env['COOLIFY_APP_ID'];
const application: any = await getApplicationFromDB(id, teamId);
const settings = await listSettings();
return {
application,
appId
appId,
settings
};
} catch ({ status, message }) {
@@ -237,6 +238,9 @@ export async function saveApplication(request: FastifyRequest<SaveApplication>,
if (exposePort) {
exposePort = Number(exposePort);
}
const { destinationDockerId } = await prisma.application.findUnique({ where: { id } })
if (exposePort) await checkExposedPort({ id, exposePort, dockerId: destinationDockerId })
if (denoOptions) denoOptions = denoOptions.trim();
const defaultConfiguration = await setDefaultConfiguration({
buildPack,
@@ -275,7 +279,7 @@ export async function saveApplication(request: FastifyRequest<SaveApplication>,
export async function saveApplicationSettings(request: FastifyRequest<SaveApplicationSettings>, reply: FastifyReply) {
try {
const { id } = request.params
const { debug, previews, dualCerts, autodeploy, branch, projectId } = request.body
const { debug, previews, dualCerts, autodeploy, branch, projectId, isBot } = request.body
const isDouble = await checkDoubleBranch(branch, projectId);
if (isDouble && autodeploy) {
await prisma.applicationSettings.updateMany({ where: { application: { branch, projectId } }, data: { autodeploy: false } })
@@ -283,7 +287,7 @@ export async function saveApplicationSettings(request: FastifyRequest<SaveApplic
}
await prisma.application.update({
where: { id },
data: { settings: { update: { debug, previews, dualCerts, autodeploy } } },
data: { fqdn: isBot ? null : undefined, settings: { update: { debug, previews, dualCerts, autodeploy, isBot } } },
include: { destinationDocker: true }
});
return reply.code(201).send();
@@ -391,18 +395,7 @@ export async function checkDNS(request: FastifyRequest<CheckDNS>) {
if (found) {
throw { status: 500, message: `Domain ${getDomain(fqdn).replace('www.', '')} is already in use!` }
}
if (exposePort) {
if (exposePort < 1024 || exposePort > 65535) {
throw { status: 500, message: `Exposed Port needs to be between 1024 and 65535.` }
}
if (configuredPort !== exposePort) {
const availablePort = await getFreeExposedPort(id, exposePort, dockerId, remoteIpAddress);
if (availablePort.toString() !== exposePort.toString()) {
throw { status: 500, message: `Port ${exposePort} is already in use.` }
}
}
}
if (exposePort) await checkExposedPort({ id, configuredPort, exposePort, dockerId, remoteIpAddress })
if (isDNSCheckEnabled && !isDev && !forceSave) {
let hostname = request.hostname.split(':')[0];
if (remoteEngine) hostname = remoteIpAddress;
@@ -435,7 +428,7 @@ export async function deployApplication(request: FastifyRequest<DeployApplicatio
try {
const { id } = request.params
const teamId = request.user?.teamId;
const { pullmergeRequestId = null, branch } = request.body
const { pullmergeRequestId = null, branch, forceRebuild } = request.body
const buildId = cuid();
const application = await getApplicationFromDB(id, teamId);
if (application) {
@@ -474,13 +467,15 @@ export async function deployApplication(request: FastifyRequest<DeployApplicatio
type: 'manual',
...application,
sourceBranch: branch,
pullmergeRequestId
pullmergeRequestId,
forceRebuild
});
} else {
scheduler.workers.get('deployApplication').postMessage({
build_id: buildId,
type: 'manual',
...application
...application,
forceRebuild
});
}
@@ -498,11 +493,20 @@ export async function deployApplication(request: FastifyRequest<DeployApplicatio
export async function saveApplicationSource(request: FastifyRequest<SaveApplicationSource>, reply: FastifyReply) {
try {
const { id } = request.params
const { gitSourceId } = request.body
await prisma.application.update({
where: { id },
data: { gitSource: { connect: { id: gitSourceId } } }
});
const { gitSourceId, forPublic, type } = request.body
if (forPublic) {
const publicGit = await prisma.gitSource.findFirst({ where: { type, forPublic } });
await prisma.application.update({
where: { id },
data: { gitSource: { connect: { id: publicGit.id } } }
});
} else {
await prisma.application.update({
where: { id },
data: { gitSource: { connect: { id: gitSourceId } } }
});
}
return reply.code(201).send()
} catch ({ status, message }) {
return errorHandler({ status, message })
@@ -556,7 +560,7 @@ export async function checkRepository(request: FastifyRequest<CheckRepository>)
export async function saveRepository(request, reply) {
try {
const { id } = request.params
let { repository, branch, projectId, autodeploy, webhookToken } = request.body
let { repository, branch, projectId, autodeploy, webhookToken, isPublicRepository = false } = request.body
repository = repository.toLowerCase();
branch = branch.toLowerCase();
@@ -564,17 +568,19 @@ export async function saveRepository(request, reply) {
if (webhookToken) {
await prisma.application.update({
where: { id },
data: { repository, branch, projectId, gitSource: { update: { gitlabApp: { update: { webhookToken: webhookToken ? webhookToken : undefined } } } }, settings: { update: { autodeploy } } }
data: { repository, branch, projectId, gitSource: { update: { gitlabApp: { update: { webhookToken: webhookToken ? webhookToken : undefined } } } }, settings: { update: { autodeploy, isPublicRepository } } }
});
} else {
await prisma.application.update({
where: { id },
data: { repository, branch, projectId, settings: { update: { autodeploy } } }
data: { repository, branch, projectId, settings: { update: { autodeploy, isPublicRepository } } }
});
}
const isDouble = await checkDoubleBranch(branch, projectId);
if (isDouble) {
await prisma.applicationSettings.updateMany({ where: { application: { branch, projectId } }, data: { autodeploy: false } })
if (!isPublicRepository) {
const isDouble = await checkDoubleBranch(branch, projectId);
if (isDouble) {
await prisma.applicationSettings.updateMany({ where: { application: { branch, projectId } }, data: { autodeploy: false, isPublicRepository } })
}
}
return reply.code(201).send()
} catch ({ status, message }) {
@@ -606,7 +612,8 @@ export async function getBuildPack(request) {
projectId: application.projectId,
repository: application.repository,
branch: application.branch,
apiUrl: application.gitSource.apiUrl
apiUrl: application.gitSource.apiUrl,
isPublicRepository: application.settings.isPublicRepository
}
} catch ({ status, message }) {
return errorHandler({ status, message })
@@ -656,13 +663,13 @@ export async function saveSecret(request: FastifyRequest<SaveSecret>, reply: Fas
if (found) {
throw { status: 500, message: `Secret ${name} already exists.` }
} else {
value = encrypt(value);
value = encrypt(value.trim());
await prisma.secret.create({
data: { name, value, isBuildSecret, isPRMRSecret, application: { connect: { id } } }
});
}
} else {
value = encrypt(value);
value = encrypt(value.trim());
const found = await prisma.secret.findFirst({ where: { applicationId: id, name, isPRMRSecret } });
if (found) {

View File

@@ -25,7 +25,7 @@ export interface SaveApplication extends OnlyId {
}
export interface SaveApplicationSettings extends OnlyId {
Querystring: { domain: string; };
Body: { debug: boolean; previews: boolean; dualCerts: boolean; autodeploy: boolean; branch: string; projectId: number; };
Body: { debug: boolean; previews: boolean; dualCerts: boolean; autodeploy: boolean; branch: string; projectId: number; isBot: boolean; };
}
export interface DeleteApplication extends OnlyId {
Querystring: { domain: string; };
@@ -44,13 +44,13 @@ export interface CheckDNS extends OnlyId {
}
export interface DeployApplication {
Querystring: { domain: string }
Body: { pullmergeRequestId: string | null, branch: string }
Body: { pullmergeRequestId: string | null, branch: string, forceRebuild?: boolean }
}
export interface GetImages {
Body: { buildPack: string, deploymentType: string }
}
export interface SaveApplicationSource extends OnlyId {
Body: { gitSourceId: string }
Body: { gitSourceId?: string | null, forPublic?: boolean, type?: string }
}
export interface CheckRepository extends OnlyId {
Querystring: { repository: string, branch: string }
@@ -115,7 +115,8 @@ export interface CancelDeployment {
export interface DeployApplication extends OnlyId {
Body: {
pullmergeRequestId: string | null,
branch: string
branch: string,
forceRebuild?: boolean
}
}

View File

@@ -3,8 +3,7 @@ import type { FastifyRequest } from 'fastify';
import { FastifyReply } from 'fastify';
import yaml from 'js-yaml';
import fs from 'fs/promises';
import { ComposeFile, createDirectories, decrypt, encrypt, errorHandler, executeDockerCmd, generateDatabaseConfiguration, generatePassword, getContainerUsage, getDatabaseImage, getDatabaseVersions, getFreePublicPort, isARM, listSettings, makeLabelForStandaloneDatabase, prisma, startTraefikTCPProxy, stopDatabaseContainer, stopTcpHttpProxy, supportedDatabaseTypesAndVersions, uniqueName, updatePasswordInDb } from '../../../../lib/common';
import { checkContainer } from '../../../../lib/docker';
import { ComposeFile, createDirectories, decrypt, encrypt, errorHandler, executeDockerCmd, generateDatabaseConfiguration, generatePassword, getContainerUsage, getDatabaseImage, getDatabaseVersions, getFreePublicPort, listSettings, makeLabelForStandaloneDatabase, prisma, startTraefikTCPProxy, stopDatabaseContainer, stopTcpHttpProxy, supportedDatabaseTypesAndVersions, uniqueName, updatePasswordInDb } from '../../../../lib/common';
import { day } from '../../../../lib/dayjs';
import { GetDatabaseLogs, OnlyId, SaveDatabase, SaveDatabaseDestination, SaveDatabaseSettings, SaveVersion } from '../../../../types';

View File

@@ -79,7 +79,6 @@ export async function newDestination(request: FastifyRequest<NewDestination>, re
let { name, network, engine, isCoolifyProxyUsed, remoteIpAddress, remoteUser, remotePort } = request.body
if (id === 'new') {
console.log(engine)
if (engine) {
const { stdout } = await asyncExecShell(`DOCKER_HOST=unix:///var/run/docker.sock docker network ls --filter 'name=^${network}$' --format '{{json .}}'`);
if (stdout === '') {

View File

@@ -4,7 +4,7 @@ import axios from 'axios';
import compare from 'compare-versions';
import cuid from 'cuid';
import bcrypt from 'bcryptjs';
import { asyncExecShell, asyncSleep, cleanupDockerStorage, errorHandler, isDev, prisma, uniqueName, version } from '../../../lib/common';
import { asyncExecShell, asyncSleep, cleanupDockerStorage, errorHandler, isDev, listSettings, prisma, uniqueName, version } from '../../../lib/common';
import type { FastifyReply, FastifyRequest } from 'fastify';
import type { Login, Update } from '.';
@@ -97,7 +97,8 @@ export async function showDashboard(request: FastifyRequest) {
const userId = request.user.userId;
const teamId = request.user.teamId;
const applications = await prisma.application.findMany({
where: { teams: { some: { id: teamId === '0' ? undefined : teamId } } }
where: { teams: { some: { id: teamId === '0' ? undefined : teamId } } },
include: { settings: true }
});
const databases = await prisma.database.findMany({
where: { teams: { some: { id: teamId === '0' ? undefined : teamId } } }
@@ -105,10 +106,12 @@ export async function showDashboard(request: FastifyRequest) {
const services = await prisma.service.findMany({
where: { teams: { some: { id: teamId === '0' ? undefined : teamId } } }
});
const settings = await listSettings();
return {
applications,
databases,
services,
settings,
};
} catch ({ status, message }) {
return errorHandler({ status, message })

File diff suppressed because it is too large Load Diff

View File

@@ -33,12 +33,13 @@ export async function saveSettings(request: FastifyRequest<SaveSettings>, reply:
minPort,
maxPort,
isAutoUpdateEnabled,
isDNSCheckEnabled
isDNSCheckEnabled,
DNSServers
} = request.body
const { id } = await listSettings();
await prisma.setting.update({
where: { id },
data: { isRegistrationEnabled, dualCerts, isAutoUpdateEnabled, isDNSCheckEnabled }
data: { isRegistrationEnabled, dualCerts, isAutoUpdateEnabled, isDNSCheckEnabled, DNSServers }
});
if (fqdn) {
await prisma.setting.update({ where: { id }, data: { fqdn } });
@@ -54,6 +55,10 @@ export async function saveSettings(request: FastifyRequest<SaveSettings>, reply:
export async function deleteDomain(request: FastifyRequest<DeleteDomain>, reply: FastifyReply) {
try {
const { fqdn } = request.body
const { DNSServers } = await listSettings();
if (DNSServers) {
dns.setServers([DNSServers]);
}
let ip;
try {
ip = await dns.resolve(fqdn);

View File

@@ -8,7 +8,8 @@ export interface SaveSettings {
minPort: number,
maxPort: number,
isAutoUpdateEnabled: boolean,
isDNSCheckEnabled: boolean
isDNSCheckEnabled: boolean,
DNSServers: string
}
}
export interface DeleteDomain {

View File

@@ -484,7 +484,6 @@ export async function traefikOtherConfiguration(request: FastifyRequest<TraefikO
}
throw { status: 500 }
} catch ({ status, message }) {
console.log(status, message);
return errorHandler({ status, message })
}
}

View File

@@ -131,8 +131,8 @@ export const supportedServiceTypesAndVersions = [
fancyName: 'Hasura',
baseImage: 'hasura/graphql-engine',
images: ['postgres:12-alpine'],
versions: ['latest', 'v2.5.1'],
recommendedVersion: 'v2.5.1',
versions: ['latest', 'v2.10.0', 'v2.5.1'],
recommendedVersion: 'v2.10.0',
ports: {
main: 8080
}
@@ -148,6 +148,17 @@ export const supportedServiceTypesAndVersions = [
main: 3000
}
},
{
name: 'appwrite',
fancyName: 'Appwrite',
baseImage: 'appwrite/appwrite',
images: ['mariadb:10.7', 'redis:6.2-alpine', 'appwrite/telegraf:1.4.0'],
versions: ['latest', '0.15.3'],
recommendedVersion: '0.15.3',
ports: {
main: 80
}
},
// {
// name: 'moodle',
// fancyName: 'Moodle',
@@ -159,6 +170,17 @@ export const supportedServiceTypesAndVersions = [
// main: 8080
// }
// }
{
name: 'glitchTip',
fancyName: 'GlitchTip',
baseImage: 'glitchtip/glitchtip',
images: ['postgres:14-alpine', 'redis:7-alpine'],
versions: ['latest'],
recommendedVersion: 'latest',
ports: {
main: 8000
}
},
];
export const asyncSleep = (delay: number) =>
@@ -218,7 +240,7 @@ export const staticDeployments = [
'astro',
'eleventy'
];
export const notNodeDeployments = ['php', 'docker', 'rust', 'python', 'deno', 'laravel'];
export const notNodeDeployments = ['php', 'docker', 'rust', 'python', 'deno', 'laravel', 'heroku'];
export function generateRemoteEngine(destination: any) {

View File

@@ -5,16 +5,17 @@
</script>
<div
on:click={() => dispatch('click')}
on:mouseover={() => dispatch('pause')}
on:focus={() => dispatch('pause')}
on:mouseout={() => dispatch('resume')}
on:blur={() => dispatch('resume')}
class="alert shadow-lg text-white rounded"
class="alert shadow-lg text-white rounded hover:scale-105 transition-all duration-100 cursor-pointer"
class:bg-coollabs={type === 'success'}
class:alert-error={type === 'error'}
class:alert-info={type === 'info'}
>
<!-- {#if type === 'success'}
{#if type === 'success'}
<svg
xmlns="http://www.w3.org/2000/svg"
class="stroke-current flex-shrink-0 h-6 w-6"
@@ -53,6 +54,6 @@
d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"
/></svg
>
{/if} -->
{/if}
<slot />
</div>

View File

@@ -2,7 +2,7 @@
import { fade } from 'svelte/transition';
import Toast from './Toast.svelte';
import { pauseToast, resumeToast, toasts } from '$lib/store';
import { dismissToast, pauseToast, resumeToast, toasts } from '$lib/store';
</script>
{#if $toasts}
@@ -12,7 +12,8 @@
<Toast
type={toast.type}
on:resume={() => resumeToast(toast.id)}
on:pause={() => pauseToast(toast.id)}>{@html toast.message}</Toast
on:pause={() => pauseToast(toast.id)}
on:click={() => dismissToast(toast.id)}>{@html toast.message}</Toast
>
{/each}
</article>

View File

@@ -83,7 +83,7 @@
disabled={updateStatus.success === false}
on:click={update}
class="icons tooltip tooltip-right tooltip-primary bg-gradient-to-r from-purple-500 via-pink-500 to-red-500 text-white duration-75 hover:scale-105"
data-tip="Update available!"
data-tip="Update Available!"
>
{#if updateStatus.loading}
<svg

View File

@@ -38,4 +38,6 @@
<Icons.Deno {isAbsolute} />
{:else if application.buildPack?.toLowerCase() === 'laravel'}
<Icons.Laravel {isAbsolute} />
{:else if application.buildPack?.toLowerCase() === 'heroku'}
<Icons.Heroku {isAbsolute} />
{/if}

View File

@@ -0,0 +1,15 @@
<script lang="ts">
export let isAbsolute = true;
</script>
<svg
xmlns="http://www.w3.org/2000/svg"
class={isAbsolute ? 'absolute top-0 left-0 -m-4 h-10 w-10' : 'mx-auto w-8 h-8 '}
viewBox="0 0 72 80"
>
<path
xmlns="http://www.w3.org/2000/svg"
fill="#430098"
d="M64.8,0 L7.2,0 C3.224,0 0,3.224 0,7.2 L0,72.8 C0,76.776 3.224,80 7.2,80 L64.8,80 C68.776,80 72,76.776 72,72.8 L72,7.2 C72,3.224 68.776,0 64.8,0 Z M18,68 L18,52 L27,60 L18,68 Z M46,68 L46,44.11 C45.961,42.243 45.062,40 41,40 C32.866,40 23.742,44.091 23.651,44.132 L18,46.692 L18,12 L26,12 L26,34.711 C29.994,33.411 35.577,32 41,32 C45.945,32 48.905,33.944 50.517,35.575 C53.958,39.055 54.005,43.488 54.0002258,44 L54.0002258,68 L46,68 Z M48,25 L40,25 C43.144,20.875 45.118,16.534 46,12 L54,12 C53.46,16.544 51.618,20.9 48,25 Z"
/>
</svg>

View File

@@ -16,4 +16,4 @@ export { default as Astro } from './Astro.svelte';
export { default as Eleventy } from './Eleventy.svelte';
export { default as Deno } from './Deno.svelte';
export { default as Laravel } from './Laravel.svelte';
export { default as Heroku } from './Heroku.svelte';

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,51 @@
<script lang="ts">
export let isAbsolute = false;
</script>
<svg
class={isAbsolute ? 'w-10 h-10 absolute top-0 left-0 -m-5' : 'w-8 mx-auto'}
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
style="isolation:isolate"
viewBox="0 0 400 400"
>
<defs>
<clipPath id="_clipPath_5kOQy2sGcuF9aeG3NHWmCAGgMEPQrnNW">
<rect width="400" height="400" />
</clipPath>
</defs>
<g clip-path="url(#_clipPath_5kOQy2sGcuF9aeG3NHWmCAGgMEPQrnNW)">
<g>
<g>
<path
d=" M 276.155 367.684 L 337.655 367.684 L 337.655 180.781 L 205.525 180.781 L 205.525 241.801 L 267.987 241.801 L 267.987 258.617 C 267.987 291.29 238.678 308.586 202.162 308.586 C 156.998 308.586 127.689 282.641 127.689 226.906 L 127.689 173.094 C 127.689 117.359 156.998 91.414 202.162 91.414 C 241.08 91.414 261.74 112.554 271.83 138.5 L 331.409 104.386 C 306.424 52.976 261.74 26.55 202.162 26.55 C 111.353 26.55 50.333 88.531 50.333 201.441 C 50.333 313.872 110.873 373.45 187.748 373.45 C 238.197 373.45 268.947 347.985 273.752 314.352 L 276.155 314.352 L 276.155 367.684 Z "
fill="rgb(132,24,128)"
/>
</g>
<g opacity="0.5">
<path
d=" M 139.701 175.78 L 139.701 173.094 C 139.701 117.359 169.01 91.414 214.174 91.414 C 253.092 91.414 273.752 112.554 283.842 138.5 L 343.421 104.386 C 318.436 52.976 273.752 26.55 214.174 26.55 C 128.962 26.55 69.981 81.125 63.033 181.145 L 139.701 175.78 Z "
fill-rule="evenodd"
fill="rgb(233,64,86)"
/>
</g>
<g opacity="0.5">
<path
d=" M 349.667 305.194 L 349.667 247.137 L 279.998 252.019 L 279.998 258.617 C 279.998 291.29 250.69 308.586 214.174 308.586 C 179.697 308.586 154.459 293.467 144.446 261.518 L 70.341 266.711 C 76.285 288.796 85.348 307.563 96.86 322.909 L 349.667 305.194 Z "
fill-rule="evenodd"
fill="rgb(233,64,86)"
/>
</g>
<path
d=" M 337.655 247.03 L 337.655 180.781 L 205.525 180.781 L 205.525 241.801 L 267.987 241.801 L 267.987 251.912 L 337.655 247.03 Z M 132.401 261.413 C 129.319 251.534 127.689 240.048 127.689 226.906 L 127.689 175.099 L 51.069 180.468 C 50.581 187.25 50.333 194.242 50.333 201.441 C 50.333 225.632 53.136 247.376 58.301 266.606 L 132.401 261.413 Z "
fill-rule="evenodd"
fill="rgb(233,64,86)"
/>
<path
d=" M 337.655 305.862 L 337.655 367.684 L 276.155 367.684 L 276.155 314.352 L 273.752 314.352 C 268.947 347.985 238.197 373.45 187.748 373.45 C 146.712 373.45 110.33 356.473 85.327 323.543 L 337.655 305.862 Z "
fill-rule="evenodd"
fill="rgb(255,63,42)"
/>
</g>
</g>
</svg>

View File

@@ -32,6 +32,10 @@
<Icons.Hasura {isAbsolute} />
{:else if type === 'fider'}
<Icons.Fider {isAbsolute} />
{:else if type === 'appwrite'}
<Icons.Appwrite {isAbsolute} />
{:else if type === 'moodle'}
<Icons.Moodle {isAbsolute} />
{:else if type === 'glitchTip'}
<Icons.GlitchTip {isAbsolute} />
{/if}

View File

@@ -1,17 +1,18 @@
//@ts-nocheck
export { default as PlausibleAnalytics } from './PlausibleAnalytics.svelte';
export { default as NocoDb } from './NocoDB.svelte';
export { default as MinIo } from './MinIO.svelte';
export { default as VsCodeServer } from './VSCodeServer.svelte';
export { default as Wordpress } from './Wordpress.svelte';
export { default as VaultWarden } from './VaultWarden.svelte';
export { default as LanguageTool } from './LanguageTool.svelte';
export { default as N8n } from './N8n.svelte';
export { default as UptimeKuma } from './UptimeKuma.svelte';
export { default as Ghost } from './Ghost.svelte';
export { default as MeiliSearch } from './MeiliSearch.svelte';
export { default as Umami } from './Umami.svelte';
export { default as Hasura } from './Hasura.svelte';
export { default as Fider } from './Fider.svelte';
export { default as Moodle } from './Moodle.svelte';
export { default as NocoDb } from './NocoDB.svelte';
export { default as MinIo } from './MinIO.svelte';
export { default as VsCodeServer } from './VSCodeServer.svelte';
export { default as Wordpress } from './Wordpress.svelte';
export { default as VaultWarden } from './VaultWarden.svelte';
export { default as LanguageTool } from './LanguageTool.svelte';
export { default as N8n } from './N8n.svelte';
export { default as UptimeKuma } from './UptimeKuma.svelte';
export { default as Ghost } from './Ghost.svelte';
export { default as MeiliSearch } from './MeiliSearch.svelte';
export { default as Umami } from './Umami.svelte';
export { default as Hasura } from './Hasura.svelte';
export { default as Fider } from './Fider.svelte';
export { default as Appwrite } from './Appwrite.svelte';
export { default as Moodle } from './Moodle.svelte';
export { default as GlitchTip } from './GlitchTip.svelte';

View File

@@ -4,7 +4,7 @@
"wait_new_version_startup": "Waiting for the new version to start...",
"new_version": "New version reachable. Reloading...",
"switch_to_a_different_team": "Switch to a different team...",
"update_available": "Update available"
"update_available": "Update Available"
},
"error": {
"you_can_find_your_way_back": "You can find your way back",
@@ -144,8 +144,8 @@
},
"preview": {
"need_during_buildtime": "Need during buildtime?",
"setup_secret_app_first": "You can add secrets to PR/MR deployments. Please add secrets to the application first. <br>Useful for creating <span class='text-applications font-bold'>staging</span> environments.",
"values_overwriting_app_secrets": "These values overwrite application secrets in PR/MR deployments. Useful for creating <span class='text-applications font-bold'>staging</span> environments.",
"setup_secret_app_first": "You can add secrets to PR/MR deployments. Please add secrets to the application first. <br>Useful for creating <span class='text-green-500 font-bold'>staging</span> environments.",
"values_overwriting_app_secrets": "These values overwrite application secrets in PR/MR deployments. Useful for creating <span class='text-green-500 font-bold'>staging</span> environments.",
"redeploy": "Redeploy",
"no_previews_available": "No previews available"
},
@@ -159,13 +159,13 @@
"storage_saved": "Storage saved.",
"storage_updated": "Storage updated.",
"storage_deleted": "Storage deleted.",
"persistent_storage_explainer": "You can specify any folder that you want to be persistent across deployments. <br>This is useful for storing data such as a database (SQLite) or a cache."
"persistent_storage_explainer": "You can specify any folder that you want to be persistent across deployments.<br><span class='text-green-500 font-bold'>/example</span> means it will preserve <span class='text-green-500 font-bold'>/app/example</span> in the container as <span class='text-green-500 font-bold'>/app</span> is <span class='text-green-500 font-bold'>the root directory</span> for your application.<br><br>This is useful for storing data such as a <span class='text-green-500 font-bold'>database (SQLite)</span> or a <span class='text-green-500 font-bold'>cache</span>."
},
"deployment_queued": "Deployment queued.",
"confirm_to_delete": "Are you sure you would like to delete '{{name}}'?",
"stop_application": "Stop application",
"stop_application": "Stop Application",
"permission_denied_stop_application": "You do not have permission to stop the application.",
"rebuild_application": "Rebuild application",
"rebuild_application": "Rebuild Application",
"permission_denied_rebuild_application": "You do not have permission to rebuild application.",
"build_and_start_application": "Deploy",
"permission_denied_build_and_start_application": "You do not have permission to deploy application.",
@@ -194,14 +194,14 @@
"application": "Application",
"url_fqdn": "URL (FQDN)",
"domain_fqdn": "Domain (FQDN)",
"https_explainer": "If you specify <span class='text-applications font-bold'>https</span>, the application will be accessible only over https. SSL certificate will be generated for you.<br>If you specify <span class='text-applications font-bold'>www</span>, the application will be redirected (302) from non-www and vice versa.<br><br>To modify the domain, you must first stop the application.<br><br><span class='text-white font-bold'>You must set your DNS to point to the server IP in advance.</span>",
"https_explainer": "If you specify <span class='text-green-500 font-bold'>https</span>, the application will be accessible only over https. SSL certificate will be generated for you.<br>If you specify <span class='text-green-500 font-bold'>www</span>, the application will be redirected (302) from non-www and vice versa.<br><br>To modify the domain, you must first stop the application.<br><br><span class='text-white font-bold'>You must set your DNS to point to the server IP in advance.</span>",
"ssl_www_and_non_www": "Generate SSL for www and non-www?",
"ssl_explainer": "It will generate certificates for both www and non-www. <br>You need to have <span class='font-bold text-applications'>both DNS entries</span> set in advance.<br><br>Useful if you expect to have visitors on both.",
"ssl_explainer": "It will generate certificates for both www and non-www. <br>You need to have <span class='font-bold text-green-500'>both DNS entries</span> set in advance.<br><br>Useful if you expect to have visitors on both.",
"install_command": "Install Command",
"build_command": "Build Command",
"start_command": "Start Command",
"directory_to_use_explainer": "Directory to use as the base for all commands.<br>Could be useful with <span class='text-applications font-bold'>monorepos</span>.",
"publish_directory_explainer": "Directory containing all the assets for deployment. <br> For example: <span class='text-applications font-bold'>dist</span>,<span class='text-applications font-bold'>_site</span> or <span class='text-applications font-bold'>public</span>.",
"directory_to_use_explainer": "Directory to use as the base for all commands.<br>Could be useful with <span class='text-green-500 font-bold'>monorepos</span>.",
"publish_directory_explainer": "Directory containing all the assets for deployment. <br> For example: <span class='text-green-500 font-bold'>dist</span>,<span class='text-green-500 font-bold'>_site</span> or <span class='text-green-500 font-bold'>public</span>.",
"features": "Features",
"enable_automatic_deployment": "Enable Automatic Deployment",
"enable_auto_deploy_webhooks": "Enable automatic deployment through webhooks.",

View File

@@ -65,7 +65,7 @@
"features": "Caractéristiques",
"git_repository": "Dépôt Git",
"git_source": "Source Git",
"https_explainer": "Si vous spécifiez <span class='text-applications font-bold'>https</span>, l'application sera accessible uniquement via https. \nUn certificat SSL sera généré pour vous.<br>Si vous spécifiez <span class='text-applications font-bold'>www</span>, l'application sera redirigée (302) à partir de non-www et vice versa \n.<br><br>Pour modifier le domaine, vous devez d'abord arrêter l'application.<br><br><span class='text-white font-bold'>Vous devez configurer, en avance, votre DNS pour pointer vers l'IP du serveur.</span>",
"https_explainer": "Si vous spécifiez <span class='text-green-500 font-bold'>https</span>, l'application sera accessible uniquement via https. \nUn certificat SSL sera généré pour vous.<br>Si vous spécifiez <span class='text-green-500 font-bold'>www</span>, l'application sera redirigée (302) à partir de non-www et vice versa \n.<br><br>Pour modifier le domaine, vous devez d'abord arrêter l'application.<br><br><span class='text-white font-bold'>Vous devez configurer, en avance, votre DNS pour pointer vers l'IP du serveur.</span>",
"install_command": "Commande d'installation",
"logs": "Journaux des applications",
"no_applications_found": "Aucune application trouvée",
@@ -78,11 +78,11 @@
"need_during_buildtime": "Besoin pendant la build ?",
"no_previews_available": "Aucun aperçu disponible",
"redeploy": "Redéployer",
"setup_secret_app_first": "Vous pouvez ajouter des secrets aux déploiements PR/MR. \nVeuillez d'abord ajouter des secrets à l'application. \n<br>Utile pour créer des environnements <span class='text-applications font-bold'>de mise en scène</span>.",
"values_overwriting_app_secrets": "Ces valeurs remplacent les secrets d'application dans les déploiements PR/MR. \nUtile pour créer des environnements <span class='text-applications font-bold'>de mise en scène</span>."
"setup_secret_app_first": "Vous pouvez ajouter des secrets aux déploiements PR/MR. \nVeuillez d'abord ajouter des secrets à l'application. \n<br>Utile pour créer des environnements <span class='text-green-500 font-bold'>de mise en scène</span>.",
"values_overwriting_app_secrets": "Ces valeurs remplacent les secrets d'application dans les déploiements PR/MR. \nUtile pour créer des environnements <span class='text-green-500 font-bold'>de mise en scène</span>."
},
"previews": "Aperçus",
"publish_directory_explainer": "Répertoire contenant tous les actifs à déployer. \n<br> Par exemple : <span class='text-applications font-bold'>dist</span>,<span class='text-applications font-bold'>_site</span> ou <span \nclass='text-applications font-bold'>public</span>.",
"publish_directory_explainer": "Répertoire contenant tous les actifs à déployer. \n<br> Par exemple : <span class='text-green-500 font-bold'>dist</span>,<span class='text-green-500 font-bold'>_site</span> ou <span \nclass='text-green-500 font-bold'>public</span>.",
"rebuild_application": "Re-build l'application",
"secret": "secrets",
"secrets": {
@@ -91,7 +91,7 @@
"use_isbuildsecret": "Utiliser isBuildSecret"
},
"settings_saved": "Paramètres sauvegardés.",
"ssl_explainer": "Il générera des certificats pour www et non-www. \n<br>Vous devez avoir <span class='font-bold text-applications'>les deux entrées DNS</span> définies à l'avance.<br><br>Utile si vous prévoyez d'avoir des visiteurs sur les deux.",
"ssl_explainer": "Il générera des certificats pour www et non-www. \n<br>Vous devez avoir <span class='font-bold text-green-500'>les deux entrées DNS</span> définies à l'avance.<br><br>Utile si vous prévoyez d'avoir des visiteurs sur les deux.",
"ssl_www_and_non_www": "Générer SSL pour www et non-www ?",
"start_command": "Démarrer la commande",
"stop_application": "Arrêter l'application",

View File

@@ -1,3 +1,4 @@
import { dev } from '$app/env';
import cuid from 'cuid';
import { writable, readable, type Writable } from 'svelte/store';
@@ -70,7 +71,11 @@ export const features = readable({
});
export const location: Writable<null | string> = writable(null)
export const setLocation = (resource: any) => {
export const setLocation = (resource: any, settings?: any) => {
if (resource.settings.isBot && resource.exposePort) {
disabledButton.set(false);
return location.set(`http://${dev ? 'localhost' : settings.ipv4}:${resource.exposePort}`)
}
if (GITPOD_WORKSPACE_URL && resource.exposePort) {
const { href } = new URL(GITPOD_WORKSPACE_URL);
const newURL = href
@@ -81,7 +86,12 @@ export const setLocation = (resource: any) => {
const newURL = `https://${CODESANDBOX_HOST.replace(/\$PORT/, resource.exposePort)}`
return location.set(newURL)
}
return location.set(resource.fqdn)
if (resource.fqdn) {
return location.set(resource.fqdn)
} else {
location.set(null);
disabledButton.set(false);
}
}
export const toasts: any = writable([])

View File

@@ -170,6 +170,16 @@ export function findBuildPack(pack: string, packageManager = 'npm') {
port: 80
};
}
if (pack === 'heroku') {
return {
...metaData,
installCommand: null,
buildCommand: null,
startCommand: null,
publishDirectory: null,
port: 5000
};
}
return {
name: 'node',
fancyName: 'Node.js',
@@ -187,119 +197,137 @@ export const buildPacks = [
name: 'node',
fancyName: 'Node.js',
hoverColor: 'hover:bg-green-700',
color: 'bg-green-700'
color: 'bg-green-700',
isCoolifyBuildPack: true,
},
{
name: 'static',
fancyName: 'Static',
hoverColor: 'hover:bg-orange-700',
color: 'bg-orange-700'
color: 'bg-orange-700',
isCoolifyBuildPack: true,
},
{
name: 'php',
fancyName: 'PHP',
hoverColor: 'hover:bg-indigo-700',
color: 'bg-indigo-700'
color: 'bg-indigo-700',
isCoolifyBuildPack: true,
},
{
name: 'laravel',
fancyName: 'Laravel',
hoverColor: 'hover:bg-indigo-700',
color: 'bg-indigo-700'
color: 'bg-indigo-700',
isCoolifyBuildPack: true,
},
{
name: 'docker',
fancyName: 'Docker',
hoverColor: 'hover:bg-sky-700',
color: 'bg-sky-700'
color: 'bg-sky-700',
isCoolifyBuildPack: true,
},
{
name: 'svelte',
fancyName: 'Svelte',
hoverColor: 'hover:bg-orange-700',
color: 'bg-orange-700'
color: 'bg-orange-700',
isCoolifyBuildPack: true,
},
{
name: 'vuejs',
fancyName: 'VueJS',
hoverColor: 'hover:bg-green-700',
color: 'bg-green-700'
color: 'bg-green-700',
isCoolifyBuildPack: true,
},
{
name: 'nuxtjs',
fancyName: 'NuxtJS',
hoverColor: 'hover:bg-green-700',
color: 'bg-green-700'
color: 'bg-green-700',
isCoolifyBuildPack: true,
},
{
name: 'gatsby',
fancyName: 'Gatsby',
hoverColor: 'hover:bg-blue-700',
color: 'bg-blue-700'
color: 'bg-blue-700',
isCoolifyBuildPack: true,
},
{
name: 'astro',
fancyName: 'Astro',
hoverColor: 'hover:bg-pink-700',
color: 'bg-pink-700'
color: 'bg-pink-700',
isCoolifyBuildPack: true,
},
{
name: 'eleventy',
fancyName: 'Eleventy',
hoverColor: 'hover:bg-red-700',
color: 'bg-red-700'
color: 'bg-red-700',
isCoolifyBuildPack: true,
},
{
name: 'react',
fancyName: 'React',
hoverColor: 'hover:bg-blue-700',
color: 'bg-blue-700'
color: 'bg-blue-700',
isCoolifyBuildPack: true,
},
{
name: 'preact',
fancyName: 'Preact',
hoverColor: 'hover:bg-blue-700',
color: 'bg-blue-700'
color: 'bg-blue-700',
isCoolifyBuildPack: true,
},
{
name: 'nextjs',
fancyName: 'NextJS',
hoverColor: 'hover:bg-blue-700',
color: 'bg-blue-700'
color: 'bg-blue-700',
isCoolifyBuildPack: true,
},
{
name: 'nestjs',
fancyName: 'NestJS',
hoverColor: 'hover:bg-red-700',
color: 'bg-red-700'
color: 'bg-red-700',
isCoolifyBuildPack: true,
},
{
name: 'rust',
fancyName: 'Rust',
hoverColor: 'hover:bg-pink-700',
color: 'bg-pink-700'
color: 'bg-pink-700',
isCoolifyBuildPack: true,
},
{
name: 'python',
fancyName: 'Python',
hoverColor: 'hover:bg-green-700',
color: 'bg-green-700'
color: 'bg-green-700',
isCoolifyBuildPack: true,
},
{
name: 'deno',
fancyName: 'Deno',
hoverColor: 'hover:bg-green-700',
color: 'bg-green-700'
}
// },
// {
// name: 'heroku',
// fancyName: 'Heroku Buildpack',
// hoverColor: 'hover:bg-indigo-700',
// color: 'bg-indigo-700'
// }
color: 'bg-green-700',
isCoolifyBuildPack: true,
},
{
name: 'heroku',
fancyName: 'Heroku',
hoverColor: 'hover:bg-purple-700',
color: 'bg-purple-700',
isHerokuBuildPack: true,
}
];
export const scanningTemplates = {
'@sveltejs/kit': {

View File

@@ -16,7 +16,7 @@
export const load: Load = async ({ fetch, url, params }) => {
try {
const response = await get(`/applications/${params.id}`);
let { application, appId, settings, isQueueActive } = response;
let { application, appId, settings } = response;
if (!application || Object.entries(application).length === 0) {
return {
status: 302,
@@ -36,7 +36,8 @@
return {
props: {
application
application,
settings
},
stuff: {
application,
@@ -52,7 +53,7 @@
<script lang="ts">
export let application: any;
export let settings: any;
import { page } from '$app/stores';
import DeleteIcon from '$lib/components/DeleteIcon.svelte';
import { del, get, post } from '$lib/api';
@@ -65,10 +66,10 @@
let loading = false;
let statusInterval: any;
let isQueueActive= false;
let isQueueActive = false;
$disabledButton =
!$appSession.isAdmin ||
!application.fqdn ||
(!application.fqdn && !application.settings.isBot) ||
!application.gitSource ||
!application.repository ||
!application.destinationDocker ||
@@ -76,13 +77,13 @@
const { id } = $page.params;
async function handleDeploySubmit() {
async function handleDeploySubmit(forceRebuild = false) {
try {
const { buildId } = await post(`/applications/${id}/deploy`, { ...application });
const { buildId } = await post(`/applications/${id}/deploy`, { ...application, forceRebuild });
addToast({
message: $t('application.deployment_queued'),
type: 'success'
});
message: $t('application.deployment_queued'),
type: 'success'
});
if ($page.url.pathname.startsWith(`/applications/${id}/logs/build`)) {
return window.location.assign(`/applications/${id}/logs/build?buildId=${buildId}`);
} else {
@@ -114,7 +115,7 @@
return window.location.reload();
} catch (error) {
return errorNotification(error);
}
}
}
async function getStatus() {
if ($status.application.loading) return;
@@ -126,18 +127,22 @@
$status.application.loading = false;
$status.application.initialLoading = false;
}
onDestroy(() => {
$status.application.initialLoading = true;
$location = null;
clearInterval(statusInterval);
});
onMount(async () => {
setLocation(application);
setLocation(application, settings);
$status.application.isRunning = false;
$status.application.isExited = false;
$status.application.loading = false;
if (application.gitSourceId && application.destinationDockerId && application.fqdn) {
if (
application.gitSourceId &&
application.destinationDockerId &&
(application.fqdn || application.settings.isBot)
) {
await getStatus();
statusInterval = setInterval(async () => {
await getStatus();
@@ -173,9 +178,10 @@
<polyline points="15 4 20 4 20 9" />
</svg></a
>
<div class="border border-coolgray-500 h-8" />
{/if}
<div class="border border-coolgray-500 h-8" />
{#if $status.application.isExited}
<a
href={!$disabledButton ? `/applications/${id}/logs` : null}
@@ -250,7 +256,7 @@
<rect x="14" y="5" width="4" height="14" rx="1" />
</svg>
</button>
<form on:submit|preventDefault={handleDeploySubmit}>
<form on:submit|preventDefault={() => handleDeploySubmit(true)}>
<button
type="submit"
disabled={$disabledButton || !isQueueActive}
@@ -258,7 +264,7 @@
class="icons bg-transparent tooltip tooltip-primary tooltip-bottom text-sm flex items-center space-x-2"
data-tip={$appSession.isAdmin
? isQueueActive
? 'Rebuild application'
? 'Force Rebuild Application'
: 'Autoupdate inprogress. Cannot rebuild application.'
: 'You do not have permission to rebuild application.'}
>
@@ -403,37 +409,39 @@
</svg>
</button></a
>
<a
href={!$disabledButton ? `/applications/${id}/previews` : null}
sveltekit:prefetch
class="hover:text-orange-500 rounded"
class:text-orange-500={$page.url.pathname === `/applications/${id}/previews`}
class:bg-coolgray-500={$page.url.pathname === `/applications/${id}/previews`}
>
<button
disabled={$disabledButton}
class="icons bg-transparent tooltip tooltip-primary tooltip-bottom text-sm"
data-tip="Previews"
{#if !application.settings.isBot}
<a
href={!$disabledButton ? `/applications/${id}/previews` : null}
sveltekit:prefetch
class="hover:text-orange-500 rounded"
class:text-orange-500={$page.url.pathname === `/applications/${id}/previews`}
class:bg-coolgray-500={$page.url.pathname === `/applications/${id}/previews`}
>
<svg
xmlns="http://www.w3.org/2000/svg"
class="w-6 h-6"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
fill="none"
stroke-linecap="round"
stroke-linejoin="round"
<button
disabled={$disabledButton}
class="icons bg-transparent tooltip tooltip-primary tooltip-bottom text-sm"
data-tip="Previews"
>
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
<circle cx="7" cy="18" r="2" />
<circle cx="7" cy="6" r="2" />
<circle cx="17" cy="12" r="2" />
<line x1="7" y1="8" x2="7" y2="16" />
<path d="M7 8a4 4 0 0 0 4 4h4" />
</svg></button
></a
>
<svg
xmlns="http://www.w3.org/2000/svg"
class="w-6 h-6"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
fill="none"
stroke-linecap="round"
stroke-linejoin="round"
>
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
<circle cx="7" cy="18" r="2" />
<circle cx="7" cy="6" r="2" />
<circle cx="17" cy="12" r="2" />
<line x1="7" y1="8" x2="7" y2="16" />
<path d="M7 8a4 4 0 0 0 4 4h4" />
</svg></button
></a
>
{/if}
<div class="border border-coolgray-500 h-8" />
<a
href={!$disabledButton && $status.application.isRunning ? `/applications/${id}/logs` : null}

View File

@@ -26,7 +26,7 @@
delete tempBuildPack.color;
delete tempBuildPack.hoverColor;
if (foundConfig.buildPack !== name) {
if (foundConfig?.buildPack !== name) {
await post(`/applications/${id}`, { ...tempBuildPack, buildPack: name });
}
await post(`/applications/${id}/configuration/buildpack`, { buildPack: name });

View File

@@ -0,0 +1,199 @@
<script lang="ts">
import { get, post } from '$lib/api';
import { t } from '$lib/translations';
import { page } from '$app/stores';
import Select from 'svelte-select';
import Explainer from '$lib/components/Explainer.svelte';
import { goto } from '$app/navigation';
import { errorNotification } from '$lib/common';
const { id } = $page.params;
let publicRepositoryLink: string;
let projectId: number;
let repositoryName: string;
let branchName: string;
let ownerName: string;
let type: string;
let branchSelectOptions: any = [];
let loading = {
branches: false
};
async function loadBranches() {
try {
loading.branches = true;
const protocol = publicRepositoryLink.split(':')[0];
const gitUrl = publicRepositoryLink.replace('http://', '').replace('https://', '');
let [host, ...path] = gitUrl.split('/');
const [owner, repository, ...branch] = path;
ownerName = owner;
repositoryName = repository;
if (host === 'github.com') {
host = 'api.github.com';
type = 'github';
if (branch[0] === 'tree' && branch[1]) {
branchName = branch[1];
}
}
if (host === 'gitlab.com') {
host = 'gitlab.com/api/v4';
type = 'gitlab';
if (branch[1] === 'tree' && branch[2]) {
branchName = branch[2];
}
}
const apiUrl = `${protocol}://${host}`;
if (type === 'github') {
const repositoryDetails = await get(`${apiUrl}/repos/${ownerName}/${repositoryName}`);
projectId = repositoryDetails.id.toString();
}
if (type === 'gitlab') {
const repositoryDetails = await get(`${apiUrl}/projects/${ownerName}%2F${repositoryName}`);
projectId = repositoryDetails.id.toString();
}
if (type === 'github' && branchName) {
try {
await get(`${apiUrl}/repos/${ownerName}/${repositoryName}/branches/${branchName}`);
await saveRepository();
loading.branches = false;
return;
} catch (error) {
errorNotification(error);
}
}
if (type === 'gitlab' && branchName) {
try {
await get(
`${apiUrl}/projects/${ownerName}%2F${repositoryName}/repository/branches/${branchName}`
);
await saveRepository();
loading.branches = false;
return;
} catch (error) {
errorNotification(error);
}
}
let branches: any[] = [];
let page = 1;
let branchCount = 0;
const loadedBranches = await loadBranchesByPage(
apiUrl,
ownerName,
repositoryName,
page,
type
);
branches = branches.concat(loadedBranches);
branchCount = branches.length;
if (branchCount === 100) {
while (branchCount === 100) {
page = page + 1;
const nextBranches = await loadBranchesByPage(
apiUrl,
ownerName,
repositoryName,
page,
type
);
branches = branches.concat(nextBranches);
branchCount = nextBranches.length;
}
}
loading.branches = false;
branchSelectOptions = branches.map((branch: any) => ({
value: branch.name,
label: branch.name
}));
} catch (error) {
return errorNotification(error);
} finally {
loading.branches = false;
}
}
async function loadBranchesByPage(
apiUrl: string,
owner: string,
repository: string,
page = 1,
type: string
) {
if (type === 'github') {
return await get(`${apiUrl}/repos/${owner}/${repository}/branches?per_page=100&page=${page}`);
}
if (type === 'gitlab') {
return await get(
`${apiUrl}/projects/${ownerName}%2F${repositoryName}/repository/branches?page=${page}`
);
}
}
async function saveRepository(event?: any) {
try {
if (event?.detail?.value) {
branchName = event.detail.value;
}
await post(`/applications/${id}/configuration/source`, {
gitSourceId: null,
forPublic: true,
type
});
await post(`/applications/${id}/configuration/repository`, {
repository: `${ownerName}/${repositoryName}`,
branch: branchName,
projectId,
autodeploy: false,
webhookToken: null,
isPublicRepository: true
});
return await goto(`/applications/${id}/configuration/destination`);
} catch (error) {
return errorNotification(error);
}
}
</script>
<div class="mx-auto max-w-5xl">
<div class="grid grid-flow-row gap-2 px-10">
<div class="flex">
<form class="flex" on:submit|preventDefault={loadBranches}>
<div class="space-y-4">
<input
placeholder="eg: https://github.com/coollabsio/nodejs-example/tree/main"
class="text-xs"
bind:value={publicRepositoryLink}
/>
{#if branchSelectOptions.length > 0}
<div class="custom-select-wrapper">
<Select
placeholder={loading.branches
? $t('application.configuration.loading_branches')
: !publicRepositoryLink
? $t('application.configuration.select_a_repository_first')
: $t('application.configuration.select_a_branch')}
isWaiting={loading.branches}
showIndicator={!!publicRepositoryLink && !loading.branches}
id="branches"
on:select={saveRepository}
items={branchSelectOptions}
isDisabled={loading.branches || !!!publicRepositoryLink}
isClearable={false}
/>
</div>
{/if}
</div>
<button class="btn mx-4 bg-orange-600" class:loading={loading.branches} type="submit"
>Load Repository</button
>
</form>
</div>
</div>
<Explainer
text="Examples:<br><br>https://github.com/coollabsio/nodejs-example<br>https://github.com/coollabsio/nodejs-example/tree/main<br>https://gitlab.com/aleveha/fastify-example<br>https://gitlab.com/aleveha/fastify-example/-/tree/master<br><br>Only works with Github.com and Gitlab.com."
/>
</div>

View File

@@ -30,7 +30,6 @@
import { page } from '$app/stores';
import { get } from '$lib/api';
import { appSession } from '$lib/store';
import { browser } from '$app/env';
import { t } from '$lib/translations';
import { buildPacks, findBuildPack, scanningTemplates } from '$lib/templates';
import { errorNotification } from '$lib/common';
@@ -48,6 +47,7 @@
export let branch: any;
export let type: any;
export let application: any;
export let isPublicRepository: boolean;
function checkPackageJSONContents({ key, json }: { key: any; json: any }) {
return json?.dependencies?.hasOwnProperty(key) || json?.devDependencies?.hasOwnProperty(key);
@@ -237,7 +237,7 @@
if (error.message === 'Bad credentials') {
const { token } = await get(`/applications/${id}/configuration/githubToken`);
$appSession.tokens.github = token;
return await scanRepository()
return await scanRepository();
}
return errorNotification(error);
} finally {
@@ -246,7 +246,11 @@
}
}
onMount(async () => {
await scanRepository();
if (!isPublicRepository) {
await scanRepository();
} else {
scanning = false;
}
});
</script>
@@ -263,11 +267,25 @@
</div>
</div>
{:else}
<div class="max-w-7xl mx-auto flex flex-wrap justify-center">
{#each buildPacks as buildPack}
<div class="p-2">
<BuildPack {packageManager} {buildPack} {scanning} bind:foundConfig />
</div>
{/each}
<div class="max-w-5xl mx-auto ">
<div class="title pb-2">Coolify</div>
<div class="flex flex-wrap justify-center">
{#each buildPacks.filter((bp) => bp.isCoolifyBuildPack === true) as buildPack}
<div class="p-2">
<BuildPack {packageManager} {buildPack} {scanning} bind:foundConfig />
</div>
{/each}
</div>
</div>
<div class="max-w-5xl mx-auto ">
<div class="title pb-2">Other</div>
<div class="flex flex-wrap justify-center">
{#each buildPacks.filter((bp) => bp.isHerokuBuildPack === true) as buildPack}
<div class="p-2">
<BuildPack {packageManager} {buildPack} {scanning} bind:foundConfig />
</div>
{/each}
</div>
</div>
{/if}

View File

@@ -31,6 +31,7 @@
import { t } from '$lib/translations';
import { appSession } from '$lib/store';
import { errorNotification } from '$lib/common';
import { onMount } from 'svelte';
const { id } = $page.params;
const from = $page.url.searchParams.get('from');
@@ -55,6 +56,11 @@
return errorNotification(error);
}
}
onMount(async () => {
if (destinations.length === 1) {
await handleSubmit(destinations[0].id);
}
});
</script>
<div class="flex space-x-1 p-6 font-bold">
@@ -65,7 +71,9 @@
<div class="flex flex-col justify-center">
{#if !destinations || ownDestinations.length === 0}
<div class="flex-col">
<div class="pb-2 text-center font-bold">{$t('application.configuration.no_configurable_destination')}</div>
<div class="pb-2 text-center font-bold">
{$t('application.configuration.no_configurable_destination')}
</div>
<div class="flex justify-center">
<a href="/new/destination" sveltekit:prefetch class="add-icon bg-sky-600 hover:bg-sky-500">
<svg

View File

@@ -48,3 +48,4 @@
<GitlabRepositories {application} {appId} {settings} />
{/if}
</div>

View File

@@ -31,6 +31,8 @@
import { t } from '$lib/translations';
import { errorNotification } from '$lib/common';
import { appSession } from '$lib/store';
import PublicRepository from './_PublicRepository.svelte';
import Explainer from '$lib/components/Explainer.svelte';
const { id } = $page.params;
const from = $page.url.searchParams.get('from');
@@ -71,120 +73,126 @@
{$t('application.configuration.select_a_git_source')}
</div>
</div>
<div class="flex flex-col justify-center">
{#if !filteredSources || ownSources.length === 0}
<div class="flex-col">
<div class="pb-2 text-center font-bold">
{$t('application.configuration.no_configurable_git')}
</div>
<div class="flex justify-center">
<a
href="/sources/new?from={$page.url.pathname}"
class="add-icon bg-orange-600 hover:bg-orange-500"
>
<svg
class="w-6"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
><path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M12 6v6m0 0v6m0-6h6m-6 0H6"
/></svg
<div class="max-w-5xl mx-auto ">
<div class="title pb-8">Git App</div>
<div class="flex flex-wrap justify-center">
{#if !filteredSources || ownSources.length === 0}
<div class="flex-col">
<div class="pb-2 text-center font-bold">
{$t('application.configuration.no_configurable_git')}
</div>
<div class="flex justify-center">
<a
href="/sources/new?from={$page.url.pathname}"
class="add-icon bg-orange-600 hover:bg-orange-500"
>
</a>
<svg
class="w-6"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
><path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M12 6v6m0 0v6m0-6h6m-6 0H6"
/></svg
>
</a>
</div>
</div>
</div>
{:else}
<div class="flex flex-col flex-wrap justify-center px-2 md:flex-row ">
{#each ownSources as source}
<div class="p-2 relative">
<div class="absolute -m-4">
{#if source?.type === 'gitlab'}
<svg viewBox="0 0 128 128" class="w-8">
<path
fill="#FC6D26"
d="M126.615 72.31l-7.034-21.647L105.64 7.76c-.716-2.206-3.84-2.206-4.556 0l-13.94 42.903H40.856L26.916 7.76c-.717-2.206-3.84-2.206-4.557 0L8.42 50.664 1.385 72.31a4.792 4.792 0 001.74 5.358L64 121.894l60.874-44.227a4.793 4.793 0 001.74-5.357"
/><path fill="#E24329" d="M64 121.894l23.144-71.23H40.856L64 121.893z" /><path
fill="#FC6D26"
d="M64 121.894l-23.144-71.23H8.42L64 121.893z"
/><path
fill="#FCA326"
d="M8.42 50.663L1.384 72.31a4.79 4.79 0 001.74 5.357L64 121.894 8.42 50.664z"
/><path
fill="#E24329"
d="M8.42 50.663h32.436L26.916 7.76c-.717-2.206-3.84-2.206-4.557 0L8.42 50.664z"
/><path fill="#FC6D26" d="M64 121.894l23.144-71.23h32.437L64 121.893z" /><path
fill="#FCA326"
d="M119.58 50.663l7.035 21.647a4.79 4.79 0 01-1.74 5.357L64 121.894l55.58-71.23z"
/><path
fill="#E24329"
d="M119.58 50.663H87.145l13.94-42.902c.717-2.206 3.84-2.206 4.557 0l13.94 42.903z"
/>
</svg>
{:else if source?.type === 'github'}
<svg viewBox="0 0 128 128" class="w-8">
<g fill="#ffffff"
><path
fill-rule="evenodd"
clip-rule="evenodd"
d="M64 5.103c-33.347 0-60.388 27.035-60.388 60.388 0 26.682 17.303 49.317 41.297 57.303 3.017.56 4.125-1.31 4.125-2.905 0-1.44-.056-6.197-.082-11.243-16.8 3.653-20.345-7.125-20.345-7.125-2.747-6.98-6.705-8.836-6.705-8.836-5.48-3.748.413-3.67.413-3.67 6.063.425 9.257 6.223 9.257 6.223 5.386 9.23 14.127 6.562 17.573 5.02.542-3.903 2.107-6.568 3.834-8.076-13.413-1.525-27.514-6.704-27.514-29.843 0-6.593 2.36-11.98 6.223-16.21-.628-1.52-2.695-7.662.584-15.98 0 0 5.07-1.623 16.61 6.19C53.7 35 58.867 34.327 64 34.304c5.13.023 10.3.694 15.127 2.033 11.526-7.813 16.59-6.19 16.59-6.19 3.287 8.317 1.22 14.46.593 15.98 3.872 4.23 6.215 9.617 6.215 16.21 0 23.194-14.127 28.3-27.574 29.796 2.167 1.874 4.097 5.55 4.097 11.183 0 8.08-.07 14.583-.07 16.572 0 1.607 1.088 3.49 4.148 2.897 23.98-7.994 41.263-30.622 41.263-57.294C124.388 32.14 97.35 5.104 64 5.104z"
{:else}
<div class="flex flex-col flex-wrap justify-center px-2 md:flex-row ">
{#each ownSources as source}
<div class="p-2 relative">
<div class="absolute -m-4">
{#if source?.type === 'gitlab'}
<svg viewBox="0 0 128 128" class="w-8">
<path
fill="#FC6D26"
d="M126.615 72.31l-7.034-21.647L105.64 7.76c-.716-2.206-3.84-2.206-4.556 0l-13.94 42.903H40.856L26.916 7.76c-.717-2.206-3.84-2.206-4.557 0L8.42 50.664 1.385 72.31a4.792 4.792 0 001.74 5.358L64 121.894l60.874-44.227a4.793 4.793 0 001.74-5.357"
/><path fill="#E24329" d="M64 121.894l23.144-71.23H40.856L64 121.893z" /><path
fill="#FC6D26"
d="M64 121.894l-23.144-71.23H8.42L64 121.893z"
/><path
d="M26.484 91.806c-.133.3-.605.39-1.035.185-.44-.196-.685-.605-.543-.906.13-.31.603-.395 1.04-.188.44.197.69.61.537.91zm2.446 2.729c-.287.267-.85.143-1.232-.28-.396-.42-.47-.983-.177-1.254.298-.266.844-.14 1.24.28.394.426.472.984.17 1.255zM31.312 98.012c-.37.258-.976.017-1.35-.52-.37-.538-.37-1.183.01-1.44.373-.258.97-.025 1.35.507.368.545.368 1.19-.01 1.452zm3.261 3.361c-.33.365-1.036.267-1.552-.23-.527-.487-.674-1.18-.343-1.544.336-.366 1.045-.264 1.564.23.527.486.686 1.18.333 1.543zm4.5 1.951c-.147.473-.825.688-1.51.486-.683-.207-1.13-.76-.99-1.238.14-.477.823-.7 1.512-.485.683.206 1.13.756.988 1.237zm4.943.361c.017.498-.563.91-1.28.92-.723.017-1.308-.387-1.315-.877 0-.503.568-.91 1.29-.924.717-.013 1.306.387 1.306.88zm4.598-.782c.086.485-.413.984-1.126 1.117-.7.13-1.35-.172-1.44-.653-.086-.498.422-.997 1.122-1.126.714-.123 1.354.17 1.444.663zm0 0"
/></g
>
</svg>
{/if}
fill="#FCA326"
d="M8.42 50.663L1.384 72.31a4.79 4.79 0 001.74 5.357L64 121.894 8.42 50.664z"
/><path
fill="#E24329"
d="M8.42 50.663h32.436L26.916 7.76c-.717-2.206-3.84-2.206-4.557 0L8.42 50.664z"
/><path fill="#FC6D26" d="M64 121.894l23.144-71.23h32.437L64 121.893z" /><path
fill="#FCA326"
d="M119.58 50.663l7.035 21.647a4.79 4.79 0 01-1.74 5.357L64 121.894l55.58-71.23z"
/><path
fill="#E24329"
d="M119.58 50.663H87.145l13.94-42.902c.717-2.206 3.84-2.206 4.557 0l13.94 42.903z"
/>
</svg>
{:else if source?.type === 'github'}
<svg viewBox="0 0 128 128" class="w-8">
<g fill="#ffffff"
><path
fill-rule="evenodd"
clip-rule="evenodd"
d="M64 5.103c-33.347 0-60.388 27.035-60.388 60.388 0 26.682 17.303 49.317 41.297 57.303 3.017.56 4.125-1.31 4.125-2.905 0-1.44-.056-6.197-.082-11.243-16.8 3.653-20.345-7.125-20.345-7.125-2.747-6.98-6.705-8.836-6.705-8.836-5.48-3.748.413-3.67.413-3.67 6.063.425 9.257 6.223 9.257 6.223 5.386 9.23 14.127 6.562 17.573 5.02.542-3.903 2.107-6.568 3.834-8.076-13.413-1.525-27.514-6.704-27.514-29.843 0-6.593 2.36-11.98 6.223-16.21-.628-1.52-2.695-7.662.584-15.98 0 0 5.07-1.623 16.61 6.19C53.7 35 58.867 34.327 64 34.304c5.13.023 10.3.694 15.127 2.033 11.526-7.813 16.59-6.19 16.59-6.19 3.287 8.317 1.22 14.46.593 15.98 3.872 4.23 6.215 9.617 6.215 16.21 0 23.194-14.127 28.3-27.574 29.796 2.167 1.874 4.097 5.55 4.097 11.183 0 8.08-.07 14.583-.07 16.572 0 1.607 1.088 3.49 4.148 2.897 23.98-7.994 41.263-30.622 41.263-57.294C124.388 32.14 97.35 5.104 64 5.104z"
/><path
d="M26.484 91.806c-.133.3-.605.39-1.035.185-.44-.196-.685-.605-.543-.906.13-.31.603-.395 1.04-.188.44.197.69.61.537.91zm2.446 2.729c-.287.267-.85.143-1.232-.28-.396-.42-.47-.983-.177-1.254.298-.266.844-.14 1.24.28.394.426.472.984.17 1.255zM31.312 98.012c-.37.258-.976.017-1.35-.52-.37-.538-.37-1.183.01-1.44.373-.258.97-.025 1.35.507.368.545.368 1.19-.01 1.452zm3.261 3.361c-.33.365-1.036.267-1.552-.23-.527-.487-.674-1.18-.343-1.544.336-.366 1.045-.264 1.564.23.527.486.686 1.18.333 1.543zm4.5 1.951c-.147.473-.825.688-1.51.486-.683-.207-1.13-.76-.99-1.238.14-.477.823-.7 1.512-.485.683.206 1.13.756.988 1.237zm4.943.361c.017.498-.563.91-1.28.92-.723.017-1.308-.387-1.315-.877 0-.503.568-.91 1.29-.924.717-.013 1.306.387 1.306.88zm4.598-.782c.086.485-.413.984-1.126 1.117-.7.13-1.35-.172-1.44-.653-.086-.498.422-.997 1.122-1.126.714-.123 1.354.17 1.444.663zm0 0"
/></g
>
</svg>
{/if}
</div>
<form on:submit|preventDefault={() => handleSubmit(source.id)}>
<button
disabled={source.gitlabApp && !source.gitlabAppId}
type="submit"
class="disabled:opacity-95 bg-coolgray-200 disabled:text-white box-selection hover:bg-orange-700 group"
class:border-red-500={source.gitlabApp && !source.gitlabAppId}
class:border-0={source.gitlabApp && !source.gitlabAppId}
class:border-l-4={source.gitlabApp && !source.gitlabAppId}
>
<div class="font-bold text-xl text-center truncate">{source.name}</div>
{#if source.gitlabApp && !source.gitlabAppId}
<div class="font-bold text-center truncate text-red-500 group-hover:text-white">
Configuration missing
</div>
{/if}
</button>
</form>
</div>
<form on:submit|preventDefault={() => handleSubmit(source.id)}>
<button
disabled={source.gitlabApp && !source.gitlabAppId}
type="submit"
class="disabled:opacity-95 bg-coolgray-200 disabled:text-white box-selection hover:bg-orange-700 group"
class:border-red-500={source.gitlabApp && !source.gitlabAppId}
class:border-0={source.gitlabApp && !source.gitlabAppId}
class:border-l-4={source.gitlabApp && !source.gitlabAppId}
>
<div class="font-bold text-xl text-center truncate">{source.name}</div>
{#if source.gitlabApp && !source.gitlabAppId}
<div class="font-bold text-center truncate text-red-500 group-hover:text-white">
Configuration missing
</div>
{/if}
</button>
</form>
</div>
{/each}
</div>
{/each}
</div>
{#if otherSources.length > 0 && $appSession.teamId === '0'}
<div class="px-6 pb-5 pt-10 text-xl font-bold">Other Sources</div>
{#if otherSources.length > 0 && $appSession.teamId === '0'}
<div class="px-6 pb-5 pt-10 text-xl font-bold">Other Sources</div>
{/if}
<div class="flex flex-col flex-wrap justify-center px-2 md:flex-row">
{#each otherSources as source}
<div class="p-2">
<form on:submit|preventDefault={() => handleSubmit(source.id)}>
<button
disabled={source.gitlabApp && !source.gitlabAppId}
type="submit"
class="disabled:opacity-95 bg-coolgray-200 disabled:text-white box-selection hover:bg-orange-700 group"
class:border-red-500={source.gitlabApp && !source.gitlabAppId}
class:border-0={source.gitlabApp && !source.gitlabAppId}
class:border-l-4={source.gitlabApp && !source.gitlabAppId}
>
<div class="font-bold text-xl text-center truncate">{source.name}</div>
{#if source.gitlabApp && !source.gitlabAppId}
<div class="font-bold text-center truncate text-red-500 group-hover:text-white">
{$t('application.configuration.configuration_missing')}
</div>
{/if}
</button>
</form>
</div>
{/each}
</div>
{/if}
<div class="flex flex-col flex-wrap justify-center px-2 md:flex-row">
{#each otherSources as source}
<div class="p-2">
<form on:submit|preventDefault={() => handleSubmit(source.id)}>
<button
disabled={source.gitlabApp && !source.gitlabAppId}
type="submit"
class="disabled:opacity-95 bg-coolgray-200 disabled:text-white box-selection hover:bg-orange-700 group"
class:border-red-500={source.gitlabApp && !source.gitlabAppId}
class:border-0={source.gitlabApp && !source.gitlabAppId}
class:border-l-4={source.gitlabApp && !source.gitlabAppId}
>
<div class="font-bold text-xl text-center truncate">{source.name}</div>
{#if source.gitlabApp && !source.gitlabAppId}
<div class="font-bold text-center truncate text-red-500 group-hover:text-white">
{$t('application.configuration.configuration_missing')}
</div>
{/if}
</button>
</form>
</div>
{/each}
</div>
{/if}
</div>
<div class="title py-4">Public Repository</div>
<PublicRepository />
</div>

View File

@@ -5,7 +5,8 @@
if (stuff?.application?.id) {
return {
props: {
application: stuff.application
application: stuff.application,
settings: stuff.settings
}
};
}
@@ -26,6 +27,7 @@
<script lang="ts">
export let application: any;
export let settings: any;
import { page } from '$app/stores';
import { onDestroy, onMount } from 'svelte';
import Select from 'svelte-select';
@@ -60,6 +62,7 @@
let previews = application.settings.previews;
let dualCerts = application.settings.dualCerts;
let autodeploy = application.settings.autodeploy;
let isBot = application.settings.isBot;
let nonWWWDomain = application.fqdn && getDomain(application.fqdn).replace(/^www\./, '');
let isNonWWWDomainOK = false;
@@ -99,7 +102,7 @@
application.fqdn = `http://${cuid()}.demo.coolify.io`;
await handleSubmit();
}
domainEl.focus();
// !isBot && domainEl.focus();
await getUsage();
usageInterval = setInterval(async () => {
await getUsage();
@@ -129,11 +132,18 @@
if (name === 'autodeploy') {
autodeploy = !autodeploy;
}
if (name === 'isBot') {
if ($status.application.isRunning) return;
isBot = !isBot;
application.settings.isBot = isBot;
setLocation(application, settings);
}
try {
await post(`/applications/${id}/settings`, {
previews,
debug,
dualCerts,
isBot,
autodeploy,
branch: application.branch,
projectId: application.projectId
@@ -155,24 +165,28 @@
if (name === 'autodeploy') {
autodeploy = !autodeploy;
}
if (name === 'isBot') {
isBot = !isBot;
}
return errorNotification(error);
}
}
async function handleSubmit() {
if (loading || !application.fqdn) return;
if (loading || (!application.fqdn && !isBot)) return;
loading = true;
try {
nonWWWDomain = application.fqdn && getDomain(application.fqdn).replace(/^www\./, '');
if (application.deploymentType)
application.deploymentType = application.deploymentType.toLowerCase();
await post(`/applications/${id}/check`, {
fqdn: application.fqdn,
forceSave,
dualCerts,
exposePort: application.exposePort
});
!isBot &&
(await post(`/applications/${id}/check`, {
fqdn: application.fqdn,
forceSave,
dualCerts,
exposePort: application.exposePort
}));
await post(`/applications/${id}`, { ...application });
setLocation(application);
setLocation(application, settings);
$disabledButton = false;
forceSave = false;
addToast({
@@ -332,53 +346,62 @@
<label for="gitSource" class="text-base font-bold text-stone-100"
>{$t('application.git_source')}</label
>
<a
href={!isDisabled
? `/applications/${id}/configuration/source?from=/applications/${id}`
: ''}
class="no-underline"
><input
{#if isDisabled || application.settings.isPublicRepository}
<input
disabled={isDisabled || application.settings.isPublicRepository}
value={application.gitSource.name}
id="gitSource"
disabled
class="cursor-pointer hover:bg-coolgray-500"
/></a
>
/>
{:else}
<a
href={`/applications/${id}/configuration/source?from=/applications/${id}`}
class="no-underline"
><input
value={application.gitSource.name}
id="gitSource"
class="cursor-pointer hover:bg-coolgray-500"
/></a
>
{/if}
</div>
<div class="grid grid-cols-2 items-center">
<label for="repository" class="text-base font-bold text-stone-100"
>{$t('application.git_repository')}</label
>
<a
href={!isDisabled
? `/applications/${id}/configuration/repository?from=/applications/${id}&to=/applications/${id}/configuration/buildpack`
: ''}
class="no-underline"
><input
{#if isDisabled || application.settings.isPublicRepository}
<input
disabled={isDisabled || application.settings.isPublicRepository}
value="{application.repository}/{application.branch}"
id="repository"
disabled
class="cursor-pointer hover:bg-coolgray-500"
/></a
>
/>
{:else}
<a
href={`/applications/${id}/configuration/repository?from=/applications/${id}&to=/applications/${id}/configuration/buildpack`}
class="no-underline"
><input
value="{application.repository}/{application.branch}"
id="repository"
class="cursor-pointer hover:bg-coolgray-500"
/></a
>
{/if}
</div>
<div class="grid grid-cols-2 items-center">
<label for="buildPack" class="text-base font-bold text-stone-100"
>{$t('application.build_pack')}</label
>
<a
href={!isDisabled
? `/applications/${id}/configuration/buildpack?from=/applications/${id}`
: ''}
class="no-underline "
>
<input
value={application.buildPack}
id="buildPack"
disabled
class="cursor-pointer hover:bg-coolgray-500"
/></a
>
{#if isDisabled}
<input class="capitalize" disabled={isDisabled} value={application.buildPack} />
{:else}
<a
href={`/applications/${id}/configuration/buildpack?from=/applications/${id}`}
class="no-underline "
>
<input
value={application.buildPack}
id="buildPack"
class="cursor-pointer hover:bg-coolgray-500 capitalize"
/></a
>
{/if}
</div>
<div class="grid grid-cols-2 items-center pb-8">
<label for="destination" class="text-base font-bold text-stone-100"
@@ -465,77 +488,89 @@
<div class="title">{$t('application.application')}</div>
</div>
<div class="grid grid-flow-row gap-2 px-10">
<div class="grid grid-cols-2">
<div class="flex-col">
<label for="fqdn" class="pt-2 text-base font-bold text-stone-100"
>{$t('application.url_fqdn')}</label
>
{#if browser && window.location.hostname === 'demo.coolify.io'}
<Explainer
text="<span class='text-white font-bold'>You can use the predefined random url name or enter your own domain name.</span>"
<div class="grid grid-cols-2 items-center">
<Setting
isCenter={false}
bind:setting={isBot}
on:click={() => changeSettings('isBot')}
title="Is your application a bot?"
description="You can deploy applications without domains. <br>You can also make them to listen on <span class='text-green-500 font-bold'>IP:EXPOSEDPORT</span> as well.<br></Setting><br>Useful to host <span class='text-green-500 font-bold'>Twitch bots, regular jobs, or anything that does not require an incoming connection.</span>"
disabled={$status.application.isRunning}
/>
</div>
{#if !isBot}
<div class="grid grid-cols-2">
<div class="flex-col">
<label for="fqdn" class="pt-2 text-base font-bold text-stone-100"
>{$t('application.url_fqdn')}</label
>
{#if browser && window.location.hostname === 'demo.coolify.io'}
<Explainer
text="<span class='text-white font-bold'>You can use the predefined random url name or enter your own domain name.</span>"
/>
{/if}
<Explainer text={$t('application.https_explainer')} />
</div>
<div>
<input
readonly={isDisabled}
disabled={isDisabled}
bind:this={domainEl}
name="fqdn"
id="fqdn"
required
bind:value={application.fqdn}
pattern="^https?://([a-z0-9]+(-[a-z0-9]+)*\.)+[a-z]{'{'}2,{'}'}$"
placeholder="eg: https://coollabs.io"
/>
{/if}
<Explainer text={$t('application.https_explainer')} />
</div>
<div>
<input
readonly={isDisabled}
disabled={isDisabled}
bind:this={domainEl}
name="fqdn"
id="fqdn"
required
bind:value={application.fqdn}
pattern="^https?://([a-z0-9]+(-[a-z0-9]+)*\.)+[a-z]{'{'}2,{'}'}$"
placeholder="eg: https://coollabs.io"
/>
{#if forceSave}
<div class="flex-col space-y-2 pt-4 text-center">
{#if isNonWWWDomainOK}
<button
class="bg-green-600 hover:bg-green-500"
on:click|preventDefault={() => isDNSValid(getDomain(nonWWWDomain), false)}
>DNS settings for {nonWWWDomain} is OK, click to recheck.</button
>
{:else}
<button
class="bg-red-600 hover:bg-red-500"
on:click|preventDefault={() => isDNSValid(getDomain(nonWWWDomain), false)}
>DNS settings for {nonWWWDomain} is invalid, click to recheck.</button
>
{/if}
{#if dualCerts}
{#if isWWWDomainOK}
{#if forceSave}
<div class="flex-col space-y-2 pt-4 text-center">
{#if isNonWWWDomainOK}
<button
class="bg-green-600 hover:bg-green-500"
on:click|preventDefault={() =>
isDNSValid(getDomain(`www.${nonWWWDomain}`), true)}
>DNS settings for www.{nonWWWDomain} is OK, click to recheck.</button
class="btn btn-sm bg-green-600 hover:bg-green-500"
on:click|preventDefault={() => isDNSValid(getDomain(nonWWWDomain), false)}
>DNS settings for {nonWWWDomain} is OK, click to recheck.</button
>
{:else}
<button
class="bg-red-600 hover:bg-red-500"
on:click|preventDefault={() =>
isDNSValid(getDomain(`www.${nonWWWDomain}`), true)}
>DNS settings for www.{nonWWWDomain} is invalid, click to recheck.</button
class="btn btn-sm bg-red-600 hover:bg-red-500"
on:click|preventDefault={() => isDNSValid(getDomain(nonWWWDomain), false)}
>DNS settings for {nonWWWDomain} is invalid, click to recheck.</button
>
{/if}
{/if}
</div>
{/if}
{#if dualCerts}
{#if isWWWDomainOK}
<button
class="btn btn-sm bg-green-600 hover:bg-green-500"
on:click|preventDefault={() =>
isDNSValid(getDomain(`www.${nonWWWDomain}`), true)}
>DNS settings for www.{nonWWWDomain} is OK, click to recheck.</button
>
{:else}
<button
class="btn btn-sm bg-red-600 hover:bg-red-500"
on:click|preventDefault={() =>
isDNSValid(getDomain(`www.${nonWWWDomain}`), true)}
>DNS settings for www.{nonWWWDomain} is invalid, click to recheck.</button
>
{/if}
{/if}
</div>
{/if}
</div>
</div>
</div>
<div class="grid grid-cols-2 items-center pb-8">
<Setting
dataTooltip={$t('forms.must_be_stopped_to_modify')}
disabled={$status.application.isRunning}
isCenter={false}
bind:setting={dualCerts}
title={$t('application.ssl_www_and_non_www')}
description={$t('application.ssl_explainer')}
on:click={() => !$status.application.isRunning && changeSettings('dualCerts')}
/>
</div>
<div class="grid grid-cols-2 items-center pb-8">
<Setting
dataTooltip={$t('forms.must_be_stopped_to_modify')}
disabled={$status.application.isRunning}
isCenter={false}
bind:setting={dualCerts}
title={$t('application.ssl_www_and_non_www')}
description={$t('application.ssl_explainer')}
on:click={() => !$status.application.isRunning && changeSettings('dualCerts')}
/>
</div>
{/if}
{#if application.buildPack === 'python'}
<div class="grid grid-cols-2 items-center">
<label for="pythonModule" class="text-base font-bold text-stone-100">WSGI / ASGI</label>
@@ -596,6 +631,9 @@
bind:value={application.port}
placeholder="{$t('forms.default')}: 'python' ? '8000' : '3000'"
/>
<Explainer
text={'The port your application listens on.'}
/>
</div>
{/if}
<div class="grid grid-cols-2 items-center">
@@ -698,7 +736,7 @@
/>
</div>
{/if}
{#if application.buildPack !== 'laravel'}
{#if application.buildPack !== 'laravel' && application.buildPack !== 'heroku'}
<div class="grid grid-cols-2 items-center">
<div class="flex-col">
<label for="baseDirectory" class="pt-2 text-base font-bold text-stone-100"
@@ -742,24 +780,28 @@
<div class="title">{$t('application.features')}</div>
</div>
<div class="px-10 pb-10">
<div class="grid grid-cols-2 items-center">
<Setting
isCenter={false}
bind:setting={autodeploy}
on:click={() => changeSettings('autodeploy')}
title={$t('application.enable_automatic_deployment')}
description={$t('application.enable_auto_deploy_webhooks')}
/>
</div>
<div class="grid grid-cols-2 items-center">
<Setting
isCenter={false}
bind:setting={previews}
on:click={() => changeSettings('previews')}
title={$t('application.enable_mr_pr_previews')}
description={$t('application.enable_preview_deploy_mr_pr_requests')}
/>
</div>
{#if !application.settings.isPublicRepository}
<div class="grid grid-cols-2 items-center">
<Setting
isCenter={false}
bind:setting={autodeploy}
on:click={() => changeSettings('autodeploy')}
title={$t('application.enable_automatic_deployment')}
description={$t('application.enable_auto_deploy_webhooks')}
/>
</div>
{/if}
{#if !application.settings.isBot}
<div class="grid grid-cols-2 items-center">
<Setting
isCenter={false}
bind:setting={previews}
on:click={() => changeSettings('previews')}
title={$t('application.enable_mr_pr_previews')}
description={$t('application.enable_preview_deploy_mr_pr_requests')}
/>
</div>
{/if}
<div class="grid grid-cols-2 items-center">
<Setting
isCenter={false}

View File

@@ -109,7 +109,7 @@
<div class="flex justify-end sticky top-0 p-2 mx-1">
<button
on:click={followBuild}
class="bg-transparent btn btn-sm tooltip tooltip-primary tooltip-bottom hover:text-green-500 hover:bg-coolgray-500"
class="bg-transparent btn btn-sm btn-link tooltip tooltip-primary tooltip-bottom hover:text-green-500 hover:bg-coolgray-500"
data-tip="Follow logs"
class:text-green-500={followingBuild}
>

View File

@@ -147,7 +147,7 @@
<div class="flex justify-end sticky top-0 p-1 mx-1">
<button
on:click={followBuild}
class="bg-transparent btn btn-sm tooltip tooltip-primary tooltip-bottom"
class="bg-transparent btn btn-sm btn-link tooltip tooltip-primary tooltip-bottom"
data-tip="Follow logs"
class:text-green-500={followingLogs}
>

View File

@@ -87,9 +87,7 @@
</div>
<div class="mx-auto max-w-6xl rounded-xl px-6 pt-4">
<div class="flex justify-center py-4 text-center">
<Explainer customClass="w-full" text={$t('application.storage.persistent_storage_explainer')} />
</div>
<table class="mx-auto border-separate text-left">
<thead>
<tr class="h-12">
@@ -109,4 +107,7 @@
</tr>
</tbody>
</table>
<div class="flex justify-center py-4 text-center">
<Explainer customClass="w-full" text={$t('application.storage.persistent_storage_explainer')} />
</div>
</div>

View File

@@ -87,6 +87,9 @@
{#if application.fqdn}
<div class="truncate text-center">{getDomain(application.fqdn) || ''}</div>
{/if}
{#if application.settings.isBot}
<div class="truncate text-center">BOT</div>
{/if}
{#if application.destinationDocker?.name}
<div class="truncate text-center">{application.destinationDocker.name}</div>
{/if}
@@ -98,7 +101,7 @@
<div class="truncate text-center font-bold text-red-500 group-hover:text-white">
Destination Missing
</div>
{:else if !application.fqdn}
{:else if !application.fqdn && !application.settings.isBot}
<div class="truncate text-center font-bold text-red-500 group-hover:text-white">
URL Missing
</div>

View File

@@ -59,7 +59,7 @@
async function changeSettings(name: any) {
if (name !== 'appendOnly') {
if (publicLoading || !$status.database.isRunning || name !== 'appendOnly') return;
if (publicLoading || !$status.database.isRunning) return;
}
publicLoading = true;
let data = {
@@ -247,6 +247,7 @@
{#if database.type === 'redis'}
<div class="grid grid-cols-2 items-center">
<Setting
loading={publicLoading}
bind:setting={appendOnly}
on:click={() => changeSettings('appendOnly')}
title={$t('database.change_append_only_mode')}

View File

@@ -30,6 +30,7 @@
import { get, post } from '$lib/api';
import { t } from '$lib/translations';
import { errorNotification } from '$lib/common';
import { onMount } from 'svelte';
const { id } = $page.params;
const from = $page.url.searchParams.get('from');
@@ -45,6 +46,11 @@
return errorNotification(error);
}
}
onMount(async () => {
if (destinations.length === 1) {
await handleSubmit(destinations[0].id);
}
});
</script>
<div class="flex space-x-1 p-6 font-bold">
@@ -55,7 +61,9 @@
<div class="flex justify-center">
{#if !destinations || destinations.length === 0}
<div class="flex-col">
<div class="pb-2 text-center font-bold">{$t('application.configuration.no_configurable_destination')}</div>
<div class="pb-2 text-center font-bold">
{$t('application.configuration.no_configurable_destination')}
</div>
<div class="flex justify-center">
<a href="/new/destination" sveltekit:prefetch class="add-icon bg-sky-600 hover:bg-sky-500">
<svg

View File

@@ -20,35 +20,38 @@
</script>
<script lang="ts">
export let applications: any;
export let databases: any;
export let services: any;
export let settings: any;
import { get, post } from '$lib/api';
import Usage from '$lib/components/Usage.svelte';
import { t } from '$lib/translations';
import { errorNotification, asyncSleep } from '$lib/common';
import { addToast, appSession } from '$lib/store';
import ApplicationsIcons from '$lib/components/svg/applications/ApplicationIcons.svelte';
import DatabaseIcons from '$lib/components/svg/databases/DatabaseIcons.svelte';
import ServiceIcons from '$lib/components/svg/services/ServiceIcons.svelte';
import { dev } from '$app/env';
let loading = {
cleanup: false
};
export let applications: any;
export let databases: any;
export let services: any;
let numberOfGetStatus = 0;
function getRndInteger(min, max) {
return Math.floor(Math.random() * (max - min + 1) ) + min;
}
let numberOfGetStatus = 0;
function getRndInteger(min: number, max: number) {
return Math.floor(Math.random() * (max - min + 1)) + min;
}
async function getStatus(resources: any) {
while (numberOfGetStatus > 1){
await asyncSleep(getRndInteger(100,200));
while (numberOfGetStatus > 1) {
await asyncSleep(getRndInteger(100, 200));
}
try {
numberOfGetStatus++;
numberOfGetStatus++;
const { id, buildPack, dualCerts } = resources;
let isRunning = false;
if (buildPack) {
@@ -69,8 +72,8 @@
} catch (error) {
return 'Error';
} finally {
numberOfGetStatus--;
}
numberOfGetStatus--;
}
}
async function manuallyCleanupStorage() {
try {
@@ -97,40 +100,91 @@
<div class="mt-10 pb-12 tracking-tight sm:pb-16">
<div class="mx-auto px-10">
<div class="flex flex-col justify-center xl:flex-row">
<div>
<div class="title">Resources</div>
<div class="flex items-start justify-center p-8">
<table class="rounded-none text-base">
<tbody>
{#each applications as application}
<tr>
<td class="space-x-2 items-center tracking-tight font-bold">
{#await getStatus(application)}
<div class="inline-flex w-2 h-2 bg-yellow-500 rounded-full" />
{:then status}
{#if status === 'Running'}
<div class="inline-flex w-2 h-2 bg-success rounded-full" />
{:else}
<div class="inline-flex w-2 h-2 bg-error rounded-full" />
{#if applications.length > 0}
<div>
<div class="title">Resources</div>
<div class="flex items-start justify-center p-8">
<table class="rounded-none text-base">
<tbody>
{#each applications as application}
<tr>
<td class="space-x-2 items-center tracking-tight font-bold">
{#await getStatus(application)}
<div class="inline-flex w-2 h-2 bg-yellow-500 rounded-full" />
{:then status}
{#if status === 'Running'}
<div class="inline-flex w-2 h-2 bg-success rounded-full" />
{:else}
<div class="inline-flex w-2 h-2 bg-error rounded-full" />
{/if}
{/await}
<div class="inline-flex">{application.name}</div>
</td>
<td class="px-10 inline-flex">
<ApplicationsIcons {application} isAbsolute={false} />
</td>
<td class="px-10">
<div
class="badge badge-outline text-xs border-applications rounded text-white"
>
Application
{#if application.settings.isBot}
| BOT
{/if}
</div></td
>
<td class="flex justify-end">
{#if application.fqdn}
<a
href={application.fqdn}
target="_blank"
class="icons bg-transparent text-sm inline-flex"
><svg
xmlns="http://www.w3.org/2000/svg"
class="h-6 w-6"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
fill="none"
stroke-linecap="round"
stroke-linejoin="round"
>
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
<path d="M11 7h-5a2 2 0 0 0 -2 2v9a2 2 0 0 0 2 2h9a2 2 0 0 0 2 -2v-5" />
<line x1="10" y1="14" x2="20" y2="4" />
<polyline points="15 4 20 4 20 9" />
</svg></a
>
{/if}
{#if application.settings.isBot && application.exposePort}
<a
href={`http://${dev ? 'localhost' : settings.ipv4}:${
application.exposePort
}`}
target="_blank"
class="icons bg-transparent text-sm inline-flex"
><svg
xmlns="http://www.w3.org/2000/svg"
class="h-6 w-6"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
fill="none"
stroke-linecap="round"
stroke-linejoin="round"
>
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
<path d="M11 7h-5a2 2 0 0 0 -2 2v9a2 2 0 0 0 2 2h9a2 2 0 0 0 2 -2v-5" />
<line x1="10" y1="14" x2="20" y2="4" />
<polyline points="15 4 20 4 20 9" />
</svg></a
>
{/if}
{/await}
<div class="inline-flex">{application.name}</div>
</td>
<td class="px-10 inline-flex">
<ApplicationsIcons {application} isAbsolute={false} />
</td>
<td class="px-10">
<div class="badge badge-outline text-xs border-applications rounded text-white">
Application
</div></td
>
<td class="flex justify-end">
{#if application.fqdn}
<a
href={application.fqdn}
target="_blank"
href={`/applications/${application.id}`}
class="icons bg-transparent text-sm inline-flex"
><svg
>
<svg
xmlns="http://www.w3.org/2000/svg"
class="h-6 w-6"
viewBox="0 0 24 24"
@@ -141,72 +195,72 @@
stroke-linejoin="round"
>
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
<path d="M11 7h-5a2 2 0 0 0 -2 2v9a2 2 0 0 0 2 2h9a2 2 0 0 0 2 -2v-5" />
<line x1="10" y1="14" x2="20" y2="4" />
<polyline points="15 4 20 4 20 9" />
</svg></a
>
{/if}
<a
href={`/applications/${application.id}`}
class="icons bg-transparent text-sm inline-flex"
>
<svg
xmlns="http://www.w3.org/2000/svg"
class="h-6 w-6"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
fill="none"
stroke-linecap="round"
stroke-linejoin="round"
>
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
<rect x="4" y="8" width="4" height="4" />
<line x1="6" y1="4" x2="6" y2="8" />
<line x1="6" y1="12" x2="6" y2="20" />
<rect x="10" y="14" width="4" height="4" />
<line x1="12" y1="4" x2="12" y2="14" />
<line x1="12" y1="18" x2="12" y2="20" />
<rect x="16" y="5" width="4" height="4" />
<line x1="18" y1="4" x2="18" y2="5" />
<line x1="18" y1="9" x2="18" y2="20" />
</svg>
</a>
</td>
</tr>
{/each}
<rect x="4" y="8" width="4" height="4" />
<line x1="6" y1="4" x2="6" y2="8" />
<line x1="6" y1="12" x2="6" y2="20" />
<rect x="10" y="14" width="4" height="4" />
<line x1="12" y1="4" x2="12" y2="14" />
<line x1="12" y1="18" x2="12" y2="20" />
<rect x="16" y="5" width="4" height="4" />
<line x1="18" y1="4" x2="18" y2="5" />
<line x1="18" y1="9" x2="18" y2="20" />
</svg>
</a>
</td>
</tr>
{/each}
{#each services as service}
<tr>
<td class="space-x-2 items-center tracking-tight font-bold">
{#await getStatus(service)}
<div class="inline-flex w-2 h-2 bg-yellow-500 rounded-full" />
{:then status}
{#if status === 'Running'}
<div class="inline-flex w-2 h-2 bg-success rounded-full" />
{:else}
<div class="inline-flex w-2 h-2 bg-error rounded-full" />
{#each services as service}
<tr>
<td class="space-x-2 items-center tracking-tight font-bold">
{#await getStatus(service)}
<div class="inline-flex w-2 h-2 bg-yellow-500 rounded-full" />
{:then status}
{#if status === 'Running'}
<div class="inline-flex w-2 h-2 bg-success rounded-full" />
{:else}
<div class="inline-flex w-2 h-2 bg-error rounded-full" />
{/if}
{/await}
<div class="inline-flex">{service.name}</div>
</td>
<td class="px-10 inline-flex">
<ServiceIcons type={service.type} isAbsolute={false} />
</td>
<td class="px-10"
><div class="badge badge-outline text-xs border-services rounded text-white">
Service
</div>
</td>
<td class="flex justify-end">
{#if service.fqdn}
<a
href={service.fqdn}
target="_blank"
class="icons bg-transparent text-sm inline-flex"
><svg
xmlns="http://www.w3.org/2000/svg"
class="h-6 w-6"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
fill="none"
stroke-linecap="round"
stroke-linejoin="round"
>
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
<path d="M11 7h-5a2 2 0 0 0 -2 2v9a2 2 0 0 0 2 2h9a2 2 0 0 0 2 -2v-5" />
<line x1="10" y1="14" x2="20" y2="4" />
<polyline points="15 4 20 4 20 9" />
</svg></a
>
{/if}
{/await}
<div class="inline-flex">{service.name}</div>
</td>
<td class="px-10 inline-flex">
<ServiceIcons type={service.type} isAbsolute={false} />
</td>
<td class="px-10"
><div class="badge badge-outline text-xs border-services rounded text-white">
Service
</div>
</td>
<td class="flex justify-end">
{#if service.fqdn}
<a
href={service.fqdn}
target="_blank"
href={`/services/${service.id}`}
class="icons bg-transparent text-sm inline-flex"
><svg
>
<svg
xmlns="http://www.w3.org/2000/svg"
class="h-6 w-6"
viewBox="0 0 24 24"
@@ -217,97 +271,77 @@
stroke-linejoin="round"
>
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
<path d="M11 7h-5a2 2 0 0 0 -2 2v9a2 2 0 0 0 2 2h9a2 2 0 0 0 2 -2v-5" />
<line x1="10" y1="14" x2="20" y2="4" />
<polyline points="15 4 20 4 20 9" />
</svg></a
<rect x="4" y="8" width="4" height="4" />
<line x1="6" y1="4" x2="6" y2="8" />
<line x1="6" y1="12" x2="6" y2="20" />
<rect x="10" y="14" width="4" height="4" />
<line x1="12" y1="4" x2="12" y2="14" />
<line x1="12" y1="18" x2="12" y2="20" />
<rect x="16" y="5" width="4" height="4" />
<line x1="18" y1="4" x2="18" y2="5" />
<line x1="18" y1="9" x2="18" y2="20" />
</svg>
</a>
</td>
</tr>
{/each}
{#each databases as database}
<tr>
<td class="space-x-2 items-center tracking-tight font-bold">
{#await getStatus(database)}
<div class="inline-flex w-2 h-2 bg-yellow-500 rounded-full" />
{:then status}
{#if status === 'Running'}
<div class="inline-flex w-2 h-2 bg-success rounded-full" />
{:else}
<div class="inline-flex w-2 h-2 bg-error rounded-full" />
{/if}
{/await}
<div class="inline-flex">{database.name}</div>
</td>
<td class="px-10 inline-flex">
<DatabaseIcons type={database.type} />
</td>
<td class="px-10">
<div class="badge badge-outline text-xs border-databases rounded text-white">
Database
</div>
</td>
<td class="flex justify-end">
<a
href={`/databases/${database.id}`}
class="icons bg-transparent text-sm inline-flex ml-11"
>
{/if}
<a
href={`/services/${service.id}`}
class="icons bg-transparent text-sm inline-flex"
>
<svg
xmlns="http://www.w3.org/2000/svg"
class="h-6 w-6"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
fill="none"
stroke-linecap="round"
stroke-linejoin="round"
>
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
<rect x="4" y="8" width="4" height="4" />
<line x1="6" y1="4" x2="6" y2="8" />
<line x1="6" y1="12" x2="6" y2="20" />
<rect x="10" y="14" width="4" height="4" />
<line x1="12" y1="4" x2="12" y2="14" />
<line x1="12" y1="18" x2="12" y2="20" />
<rect x="16" y="5" width="4" height="4" />
<line x1="18" y1="4" x2="18" y2="5" />
<line x1="18" y1="9" x2="18" y2="20" />
</svg>
</a>
</td>
</tr>
{/each}
{#each databases as database}
<tr>
<td class="space-x-2 items-center tracking-tight font-bold">
{#await getStatus(database)}
<div class="inline-flex w-2 h-2 bg-yellow-500 rounded-full" />
{:then status}
{#if status === 'Running'}
<div class="inline-flex w-2 h-2 bg-success rounded-full" />
{:else}
<div class="inline-flex w-2 h-2 bg-error rounded-full" />
{/if}
{/await}
<div class="inline-flex">{database.name}</div>
</td>
<td class="px-10 inline-flex">
<DatabaseIcons type={database.type} />
</td>
<td class="px-10">
<div class="badge badge-outline text-xs border-databases rounded text-white">
Database
</div>
</td>
<td class="flex justify-end">
<a
href={`/databases/${database.id}`}
class="icons bg-transparent text-sm inline-flex ml-11"
>
<svg
xmlns="http://www.w3.org/2000/svg"
class="h-6 w-6"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
fill="none"
stroke-linecap="round"
stroke-linejoin="round"
>
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
<rect x="4" y="8" width="4" height="4" />
<line x1="6" y1="4" x2="6" y2="8" />
<line x1="6" y1="12" x2="6" y2="20" />
<rect x="10" y="14" width="4" height="4" />
<line x1="12" y1="4" x2="12" y2="14" />
<line x1="12" y1="18" x2="12" y2="20" />
<rect x="16" y="5" width="4" height="4" />
<line x1="18" y1="4" x2="18" y2="5" />
<line x1="18" y1="9" x2="18" y2="20" />
</svg>
</a>
</td>
</tr>
{/each}
</tbody>
</table>
<svg
xmlns="http://www.w3.org/2000/svg"
class="h-6 w-6"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
fill="none"
stroke-linecap="round"
stroke-linejoin="round"
>
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
<rect x="4" y="8" width="4" height="4" />
<line x1="6" y1="4" x2="6" y2="8" />
<line x1="6" y1="12" x2="6" y2="20" />
<rect x="10" y="14" width="4" height="4" />
<line x1="12" y1="4" x2="12" y2="14" />
<line x1="12" y1="18" x2="12" y2="20" />
<rect x="16" y="5" width="4" height="4" />
<line x1="18" y1="4" x2="18" y2="5" />
<line x1="18" y1="9" x2="18" y2="20" />
</svg>
</a>
</td>
</tr>
{/each}
</tbody>
</table>
</div>
</div>
</div>
{/if}
{#if $appSession.teamId === '0'}
<Usage />
{/if}

View File

@@ -55,9 +55,16 @@
<a href="https://fider.io" target="_blank">
<Icons.Fider />
</a>
{:else if service.type === 'appwrite'}
<a href="https://appwrite.io" target="_blank">
<Icons.Appwrite/>
</a>
{:else if service.type === 'moodle'}
<a href="https://moodle.org" target="_blank">
<Icons.Moodle />
</a>
{:else if service.type === 'glitchTip'}
<a href="https://glitchtip.com" target="_blank">
<Icons.GlitchTip />
</a>
{/if}

View File

@@ -0,0 +1,126 @@
<script lang="ts">
import CopyPasswordField from '$lib/components/CopyPasswordField.svelte';
import { t } from '$lib/translations';
import Select from 'svelte-select';
export let service: any;
export let readOnly: any;
</script>
<div class="flex space-x-1 py-5 font-bold">
<div class="title">Appwrite</div>
</div>
<div class="grid grid-cols-2 items-center px-10">
<label for="opensslKeyV1">Encryption Key</label>
<CopyPasswordField
name="opensslKeyV1"
id="opensslKeyV1"
isPasswordField
value={service.appwrite.opensslKeyV1}
readonly
disabled
/>
</div>
<div class="grid grid-cols-2 items-center px-10">
<label for="executorSecret">Executor Secret</label>
<CopyPasswordField
name="executorSecret"
id="executorSecret"
isPasswordField
value={service.appwrite.executorSecret}
readonly
disabled
/>
</div>
<!-- <div class="flex space-x-1 py-5 font-bold">
<div class="title">Redis</div>
</div>
<div class="grid grid-cols-2 items-center px-10">
<label for="redisPassword">Password</label>
<CopyPasswordField
name="redisPassword"
id="redisPassword"
isPasswordField
value={service.appwrite.redisPassword}
readonly
disabled
/>
</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="mariadbHost">MariaDB Host</label>
<CopyPasswordField
name="mariadbHost"
id="mariadbHost"
value={service.appwrite.mariadbHost}
readonly
disabled
/>
</div>
<div class="grid grid-cols-2 items-center px-10">
<label for="mariadbPort">MariaDB Port</label>
<CopyPasswordField
name="mariadbPort"
id="mariadbPort"
value={service.appwrite.mariadbPort}
readonly
disabled
/>
</div> -->
<div class="grid grid-cols-2 items-center px-10">
<label for="mariadbUser">{$t('forms.username')}</label>
<CopyPasswordField
name="mariadbUser"
id="mariadbUser"
value={service.appwrite.mariadbUser}
readonly
disabled
/>
</div>
<div class="grid grid-cols-2 items-center px-10">
<label for="mariadbPassword">{$t('forms.password')}</label>
<CopyPasswordField
id="mariadbPassword"
isPasswordField
readonly
disabled
name="mariadbPassword"
value={service.appwrite.mariadbPassword}
/>
</div>
<div class="grid grid-cols-2 items-center px-10">
<label for="mariadbRootUser">Root User</label>
<CopyPasswordField
name="mariadbRootUser"
id="mariadbRootUser"
value={service.appwrite.mariadbRootUser}
readonly
disabled
/>
</div>
<div class="grid grid-cols-2 items-center px-10">
<label for="mariadbRootUserPassword">Root Password</label>
<CopyPasswordField
id="mariadbRootUserPassword"
isPasswordField
readonly
disabled
name="mariadbRootUserPassword"
value={service.appwrite.mariadbRootUserPassword}
/>
</div>
<div class="grid grid-cols-2 items-center px-10">
<label for="mariadbDatabase">{$t('index.database')}</label>
<CopyPasswordField
name="mariadbDatabase"
id="mariadbDatabase"
value={service.appwrite.mariadbDatabase}
readonly
disabled
/>
</div>

View File

@@ -1,12 +1,14 @@
<script lang="ts">
import CopyPasswordField from '$lib/components/CopyPasswordField.svelte';
import Explainer from '$lib/components/Explainer.svelte';
import { t } from '$lib/translations';
export let readOnly: any;
export let service: any;
</script>
<div class="flex space-x-1 py-5 font-bold">
<div class="flex space-x-1 py-5">
<div class="title">Ghost</div>
<Explainer text={'You can change these values in the Ghost admin panel.'} />
</div>
<div class="grid grid-cols-2 items-center px-10">
<label for="email">{$t('forms.default_email_address')}</label>

View File

@@ -0,0 +1,208 @@
<script lang="ts">
import CopyPasswordField from '$lib/components/CopyPasswordField.svelte';
import Explainer from '$lib/components/Explainer.svelte';
import Setting from '$lib/components/Setting.svelte';
import { t } from '$lib/translations';
export let service: any;
function toggleEmailSmtpUseTls() {
service.glitchTip.emailSmtpUseTls = !service.glitchTip.emailSmtpUseTls;
}
function toggleEmailSmtpUseSsl() {
service.glitchTip.emailSmtpUseSsl = !service.glitchTip.emailSmtpUseSsl;
}
function toggleEnableOpenUserRegistration() {
service.glitchTip.enableOpenUserRegistration = !service.glitchTip.enableOpenUserRegistration;
}
</script>
<div class="flex space-x-1 py-5 font-bold">
<div class="title">GlitchTip</div>
</div>
<div class="flex space-x-1 py-2 font-bold">
<div class="subtitle">Settings</div>
</div>
<div class="grid grid-cols-2 items-center px-10">
<Setting
bind:setting={service.glitchTip.enableOpenUserRegistration}
on:click={toggleEnableOpenUserRegistration}
title={'Enable Open User Registration'}
description={''}
/>
</div>
<div class="flex space-x-1 py-2 font-bold">
<div class="subtitle">Email settings</div>
</div>
<div class="grid grid-cols-2 items-center px-10">
<label for="defaultEmailFrom" class="text-base font-bold text-stone-100">Default Email From</label
>
<CopyPasswordField
required
name="defaultEmailFrom"
id="defaultEmailFrom"
value={service.glitchTip.defaultEmailFrom}
/>
</div>
<div class="grid grid-cols-2 items-center px-10">
<label for="emailSmtpHost" class="text-base font-bold text-stone-100">SMTP Host</label>
<CopyPasswordField
name="emailSmtpHost"
id="emailSmtpHost"
value={service.glitchTip.emailSmtpHost}
/>
</div>
<div class="grid grid-cols-2 items-center px-10">
<label for="emailSmtpPort" class="text-base font-bold text-stone-100">SMTP Port</label>
<CopyPasswordField
name="emailSmtpPort"
id="emailSmtpPort"
value={service.glitchTip.emailSmtpPort}
/>
</div>
<div class="grid grid-cols-2 items-center px-10">
<label for="emailSmtpUser" class="text-base font-bold text-stone-100">SMTP User</label>
<CopyPasswordField
name="emailSmtpUser"
id="emailSmtpUser"
value={service.glitchTip.emailSmtpUser}
/>
</div>
<div class="grid grid-cols-2 items-center px-10">
<label for="emailSmtpPassword" class="text-base font-bold text-stone-100">SMTP Password</label>
<CopyPasswordField
name="emailSmtpPassword"
id="emailSmtpPassword"
value={service.glitchTip.emailSmtpPassword}
isPasswordField
/>
</div>
<div class="grid grid-cols-2 items-center px-10">
<Setting
bind:setting={service.glitchTip.emailSmtpUseTls}
on:click={toggleEmailSmtpUseTls}
title={'SMTP Use TLS'}
description={''}
/>
</div>
<div class="grid grid-cols-2 items-center px-10">
<Setting
bind:setting={service.glitchTip.emailSmtpUseSsl}
on:click={toggleEmailSmtpUseSsl}
title={'SMTP Use SSL'}
description={''}
/>
</div>
<div class="grid grid-cols-2 items-center px-10">
<label for="emailBackend" class="text-base font-bold text-stone-100">Email Backend</label>
<CopyPasswordField name="emailBackend" id="emailBackend" value={service.glitchTip.emailBackend} />
</div>
<div class="grid grid-cols-2 items-center px-10">
<label for="mailgunApiKey" class="text-base font-bold text-stone-100">Mailgun API Key</label>
<CopyPasswordField
name="mailgunApiKey"
id="mailgunApiKey"
value={service.glitchTip.mailgunApiKey}
/>
</div>
<div class="grid grid-cols-2 items-center px-10">
<label for="sendgridApiKey" class="text-base font-bold text-stone-100">SendGrid API Key</label>
<CopyPasswordField
name="sendgridApiKey"
id="sendgridApiKey"
value={service.glitchTip.sendgridApiKey}
/>
</div>
<div class="flex space-x-1 py-2 font-bold">
<div class="subtitle">Default User & Superuser</div>
</div>
<div class="grid grid-cols-2 items-center px-10">
<label for="defaultEmail" class="text-base font-bold text-stone-100">{$t('forms.email')}</label>
<CopyPasswordField
name="defaultEmail"
id="defaultEmail"
value={service.glitchTip.defaultEmail}
readonly
disabled
/>
</div>
<div class="grid grid-cols-2 items-center px-10">
<label for="defaultUsername" class="text-base font-bold text-stone-100"
>{$t('forms.username')}</label
>
<CopyPasswordField
name="defaultUsername"
id="defaultUsername"
value={service.glitchTip.defaultUsername}
readonly
disabled
/>
</div>
<div class="grid grid-cols-2 items-center px-10">
<label for="defaultPassword" class="text-base font-bold text-stone-100"
>{$t('forms.password')}</label
>
<CopyPasswordField
name="defaultPassword"
id="defaultPassword"
value={service.glitchTip.defaultPassword}
readonly
disabled
isPasswordField
/>
</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" class="text-base font-bold text-stone-100"
>{$t('forms.username')}</label
>
<CopyPasswordField
name="postgresqlUser"
id="postgresqlUser"
value={service.glitchTip.postgresqlUser}
readonly
disabled
/>
</div>
<div class="grid grid-cols-2 items-center px-10">
<label for="postgresqlPassword" class="text-base font-bold text-stone-100"
>{$t('forms.password')}</label
>
<CopyPasswordField
id="postgresqlPassword"
isPasswordField
readonly
disabled
name="postgresqlPassword"
value={service.glitchTip.postgresqlPassword}
/>
</div>
<div class="grid grid-cols-2 items-center px-10">
<label for="postgresqlDatabase" class="text-base font-bold text-stone-100"
>{$t('index.database')}</label
>
<CopyPasswordField
name="postgresqlDatabase"
id="postgresqlDatabase"
value={service.glitchTip.postgresqlDatabase}
readonly
disabled
/>
</div>

View File

@@ -19,6 +19,7 @@
import Fider from './_Fider.svelte';
import Ghost from './_Ghost.svelte';
import GlitchTip from './_GlitchTip.svelte';
import Hasura from './_Hasura.svelte';
import MeiliSearch from './_MeiliSearch.svelte';
import MinIo from './_MinIO.svelte';
@@ -26,6 +27,7 @@
import Umami from './_Umami.svelte';
import VsCodeServer from './_VSCodeServer.svelte';
import Wordpress from './_Wordpress.svelte';
import Appwrite from './_Appwrite.svelte';
import Moodle from './_Moodle.svelte';
const { id } = $page.params;
@@ -37,7 +39,7 @@
save: false,
verification: false,
cleanup: false
}
};
let dualCerts = service.dualCerts;
let nonWWWDomain = service.fqdn && getDomain(service.fqdn).replace(/^www\./, '');
@@ -321,13 +323,13 @@
<div class="flex-col space-y-2 pt-4 text-center">
{#if isNonWWWDomainOK}
<button
class="bg-green-600 hover:bg-green-500"
class="btn btn-sm bg-green-600 hover:bg-green-500"
on:click|preventDefault={() => isDNSValid(getDomain(nonWWWDomain), false)}
>DNS settings for {nonWWWDomain} is OK, click to recheck.</button
>
{:else}
<button
class="bg-red-600 hover:bg-red-500"
class="btn btn-sm bg-red-600 hover:bg-red-500"
on:click|preventDefault={() => isDNSValid(getDomain(nonWWWDomain), false)}
>DNS settings for {nonWWWDomain} is invalid, click to recheck.</button
>
@@ -335,13 +337,13 @@
{#if dualCerts}
{#if isWWWDomainOK}
<button
class="bg-green-600 hover:bg-green-500"
class="btn btn-sm bg-green-600 hover:bg-green-500"
on:click|preventDefault={() => isDNSValid(getDomain(`www.${nonWWWDomain}`), true)}
>DNS settings for www.{nonWWWDomain} is OK, click to recheck.</button
>
{:else}
<button
class="bg-red-600 hover:bg-red-500"
class="btn btn-sm bg-red-600 hover:bg-red-500"
on:click|preventDefault={() => isDNSValid(getDomain(`www.${nonWWWDomain}`), true)}
>DNS settings for www.{nonWWWDomain} is invalid, click to recheck.</button
>
@@ -394,8 +396,12 @@
<Hasura bind:service />
{:else if service.type === 'fider'}
<Fider bind:service {readOnly} />
{:else if service.type === 'appwrite'}
<Appwrite bind:service {readOnly} />
{:else if service.type === 'moodle'}
<Moodle bind:service {readOnly} />
{:else if service.type === 'glitchTip'}
<GlitchTip bind:service />
{/if}
</div>
</form>

View File

@@ -22,7 +22,7 @@
function generateUrl(publicPort: any) {
return browser
? `sftp://${
settings.fqdn ? getDomain(settings.fqdn) : window.location.hostname
settings?.fqdn ? getDomain(settings.fqdn) : window.location.hostname
}:${publicPort}`
: 'Loading...';
}

View File

@@ -110,7 +110,6 @@
loading = true;
try {
await post(`/services/${service.id}/${service.type}/start`, {});
return window.location.reload();
} catch (error) {
return errorNotification(error);
} finally {

View File

@@ -32,10 +32,16 @@
import { get, post } from '$lib/api';
import { t } from '$lib/translations';
import { errorNotification } from '$lib/common';
import { onMount } from 'svelte';
const { id } = $page.params;
const from = $page.url.searchParams.get('from');
onMount(async () => {
if (destinations.length === 1) {
await handleSubmit(destinations[0].id);
}
});
async function handleSubmit(destinationId: any) {
try {
await post(`/services/${id}/configuration/destination`, { destinationId });
@@ -54,7 +60,9 @@
<div class="flex justify-center">
{#if !destinations || destinations.length === 0}
<div class="flex-col">
<div class="pb-2 text-center font-bold">{$t('application.configuration.no_configurable_destination')}</div>
<div class="pb-2 text-center font-bold">
{$t('application.configuration.no_configurable_destination')}
</div>
<div class="flex justify-center">
<a href="/new/destination" sveltekit:prefetch class="add-icon bg-sky-600 hover:bg-sky-500">
<svg

View File

@@ -32,13 +32,20 @@
import { get, post } from '$lib/api';
import { t } from '$lib/translations';
import { errorNotification, supportedServiceTypesAndVersions } from '$lib/common';
import { onMount } from 'svelte';
const { id } = $page.params;
const from = $page.url.searchParams.get('from');
let recommendedVersion = supportedServiceTypesAndVersions.find(
({ name }) => name === type
)?.recommendedVersion;
onMount(async () => {
if (versions.length === 1) {
await handleSubmit(versions[0]);
}
});
async function handleSubmit(version: any) {
try {
await post(`/services/${id}/configuration/version`, { version });

View File

@@ -102,7 +102,7 @@
{#each otherServices as service}
<a href="/services/{service.id}" class="p-2 no-underline">
<div class="box-selection group relative hover:bg-pink-600">
<Services type={service.type} />
<ServiceIcons type={service.type} />
<div class="truncate text-center text-xl font-bold">
{service.name}
</div>

View File

@@ -31,7 +31,7 @@
let dualCerts = settings.dualCerts;
let isAutoUpdateEnabled = settings.isAutoUpdateEnabled;
let isDNSCheckEnabled = settings.isDNSCheckEnabled;
let DNSServers = settings.DNSServers;
let minPort = settings.minPort;
let maxPort = settings.maxPort;
@@ -105,6 +105,10 @@
settings.minPort = minPort;
settings.maxPort = maxPort;
}
if (DNSServers !== settings.DNSServers) {
await post(`/settings`, { DNSServers });
settings.DNSServers = DNSServers;
}
forceSave = false;
return addToast({
message: 'Configuration saved.',
@@ -275,6 +279,17 @@
on:click={() => changeSettings('isDNSCheckEnabled')}
/>
</div>
<div class="grid grid-cols-2 items-center">
<div class="flex-col">
<div class="pt-2 text-base font-bold text-stone-100">
Custom DNS servers
</div>
<Explainer text="You can specify a custom DNS server to verify your domains all over Coolify.<br><br>By default, the OS defined DNS servers are used." />
</div>
<div class="mx-auto flex-row items-center justify-center space-y-2">
<input placeholder="1.1.1.1,8.8.8.8" bind:value={DNSServers} />
</div>
</div>
<div class="grid grid-cols-2 items-center">
<Setting
dataTooltip={$t('setting.must_remove_domain_before_changing')}

View File

@@ -46,7 +46,7 @@ textarea {
}
#svelte .custom-select-wrapper .selectContainer {
@apply h-12 w-96 rounded border-none bg-coolgray-200 p-2 px-0 text-xs tracking-tight outline-none transition duration-150 hover:bg-coolgray-500 focus:bg-coolgray-500 md:text-sm;
@apply h-12 w-96 rounded border border-coolgray-300 border-dashed bg-coolgray-200 p-2 px-0 text-xs tracking-tight outline-none transition duration-150 hover:bg-coolgray-500 focus:bg-coolgray-500 md:text-sm;
}
#svelte .listContainer {

View File

@@ -5,5 +5,6 @@ stdenv.mkDerivation {
buildInputs = [
git
git-lfs
docker-compose
];
}

View File

@@ -1,7 +1,7 @@
{
"name": "coolify",
"description": "An open-source & self-hostable Heroku / Netlify alternative.",
"version": "3.3.3",
"version": "3.7.0",
"license": "Apache-2.0",
"repository": "github:coollabsio/coolify",
"scripts": {