mirror of
https://github.com/ershisan99/coolify.git
synced 2025-12-23 04:59:28 +00:00
Compare commits
231 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e9b852a30e | ||
|
|
1d4e5df5a2 | ||
|
|
5e14b72fe4 | ||
|
|
8ebff72cde | ||
|
|
e16643c48c | ||
|
|
65c8f55ee6 | ||
|
|
fbc81ab3eb | ||
|
|
a4d56fd79a | ||
|
|
ce45cb8aca | ||
|
|
7f8428cd17 | ||
|
|
14d79031c1 | ||
|
|
b8aa7b6d08 | ||
|
|
397ca7f20e | ||
|
|
e10b76a46b | ||
|
|
b46566280d | ||
|
|
3ab6a231eb | ||
|
|
2bc2ae9b6e | ||
|
|
2b28f8bd8f | ||
|
|
dcdac29135 | ||
|
|
591ee29e0d | ||
|
|
625e71ab08 | ||
|
|
b0af54587b | ||
|
|
be3080df08 | ||
|
|
04685c9f9d | ||
|
|
1a83f2635f | ||
|
|
630aa45c87 | ||
|
|
0c3a381d1f | ||
|
|
ffac7c5c87 | ||
|
|
410800e81c | ||
|
|
9481beb61f | ||
|
|
141f2481a7 | ||
|
|
ea18f25adc | ||
|
|
9018184747 | ||
|
|
4fc2dd55f5 | ||
|
|
5ef9a282eb | ||
|
|
93a6518974 | ||
|
|
07aa285b27 | ||
|
|
bf01e9e29f | ||
|
|
d70672ba4b | ||
|
|
5eeb519ed6 | ||
|
|
5f047e4adf | ||
|
|
56b9a376bd | ||
|
|
0a1d31a188 | ||
|
|
64c9fb9a1b | ||
|
|
47aad15cd5 | ||
|
|
260a47a366 | ||
|
|
fd4bbe17f0 | ||
|
|
25ff637703 | ||
|
|
f571453696 | ||
|
|
5cd7533972 | ||
|
|
3a252509d0 | ||
|
|
2bd3802a6f | ||
|
|
ce2757f514 | ||
|
|
8419cdf604 | ||
|
|
907c2414ae | ||
|
|
f82207564f | ||
|
|
991a09838c | ||
|
|
25df4bfd85 | ||
|
|
d2f89d001b | ||
|
|
1971f227fd | ||
|
|
c1adffe260 | ||
|
|
e725887a55 | ||
|
|
5bf79b75b0 | ||
|
|
6926975e40 | ||
|
|
978a01c968 | ||
|
|
f421f5ee84 | ||
|
|
383831c7b8 | ||
|
|
41329facf7 | ||
|
|
7d3c644148 | ||
|
|
7fab9b5930 | ||
|
|
58763ef84c | ||
|
|
0e6abf172b | ||
|
|
9e681ece41 | ||
|
|
28f87a306d | ||
|
|
23e8833208 | ||
|
|
03962663c2 | ||
|
|
cc2ec55c4d | ||
|
|
ff2c38aa16 | ||
|
|
b5a9a2cea8 | ||
|
|
cd3f661f7e | ||
|
|
41bf6b5b86 | ||
|
|
a4e7c85184 | ||
|
|
19aca9ab35 | ||
|
|
08704c289a | ||
|
|
2224c22c6e | ||
|
|
b281889acd | ||
|
|
cfc50a27b0 | ||
|
|
ed5f21da6a | ||
|
|
78f3eb81dd | ||
|
|
6a833934ce | ||
|
|
45bf6f77d1 | ||
|
|
a1b3b7b687 | ||
|
|
7ebcad6abb | ||
|
|
fed6d2bf07 | ||
|
|
bea4943e9f | ||
|
|
1979e431b8 | ||
|
|
9bead1d6b4 | ||
|
|
56c4295e16 | ||
|
|
7c7b5a61e5 | ||
|
|
abaa13fda8 | ||
|
|
042bfeddbb | ||
|
|
f45ab067ce | ||
|
|
97a6f04aaa | ||
|
|
417c01d6e0 | ||
|
|
b2e7435d0f | ||
|
|
73c9cb1d51 | ||
|
|
41c5dd3b53 | ||
|
|
bb0c93dc2f | ||
|
|
7953c1df30 | ||
|
|
c3f4245164 | ||
|
|
369001febb | ||
|
|
7ec296be6b | ||
|
|
d2f5a58f3b | ||
|
|
f4315144af | ||
|
|
e92775887d | ||
|
|
a5f1b4b675 | ||
|
|
157e5fd7aa | ||
|
|
5e7e1c11c7 | ||
|
|
e8516bc831 | ||
|
|
e3f78a1cf9 | ||
|
|
3449e0f8fc | ||
|
|
66af12f9b5 | ||
|
|
13acf09dcc | ||
|
|
ce71dccbc1 | ||
|
|
d9ba1a0b5c | ||
|
|
0b709c93a8 | ||
|
|
1657e5a151 | ||
|
|
a165b21950 | ||
|
|
0d0715a340 | ||
|
|
76754ded79 | ||
|
|
4da27a46a2 | ||
|
|
039953588e | ||
|
|
b8b4f559db | ||
|
|
2b0df270df | ||
|
|
b96c1a23ec | ||
|
|
f779b3bb54 | ||
|
|
6462982d12 | ||
|
|
84b4cc5d54 | ||
|
|
1bd2ccbc16 | ||
|
|
3abe1610bf | ||
|
|
61716738ed | ||
|
|
4e819f6eba | ||
|
|
fedb38f2bc | ||
|
|
aae108032c | ||
|
|
020013683b | ||
|
|
70de2538e2 | ||
|
|
9f581c82a9 | ||
|
|
eb2e07afc5 | ||
|
|
9c47b8495c | ||
|
|
2f8d0ee60c | ||
|
|
5bf14f4639 | ||
|
|
9da08d600b | ||
|
|
4d47eab07c | ||
|
|
f2061c5c25 | ||
|
|
430fc66ed7 | ||
|
|
bcb84b8126 | ||
|
|
dd83e86bc3 | ||
|
|
3e8a8364dc | ||
|
|
be41c0dd02 | ||
|
|
a17b7a564e | ||
|
|
f3cdda29bc | ||
|
|
de37ee9f1c | ||
|
|
8212868b92 | ||
|
|
b44d8578d9 | ||
|
|
0358cf2de2 | ||
|
|
94da008a47 | ||
|
|
456b1b8074 | ||
|
|
78e6a7d1d3 | ||
|
|
76dc7ffb68 | ||
|
|
211aff7170 | ||
|
|
bcacefb841 | ||
|
|
4505ad37d8 | ||
|
|
18cf57f33c | ||
|
|
9f2f5b40c3 | ||
|
|
8a401f50cb | ||
|
|
51a5b3b602 | ||
|
|
68f9bca054 | ||
|
|
e9e92c6e9e | ||
|
|
008cfdba09 | ||
|
|
9973197fa5 | ||
|
|
ec3b94cf96 | ||
|
|
c4cb92c78d | ||
|
|
c390f82246 | ||
|
|
b4f98e24a1 | ||
|
|
e042c5cfde | ||
|
|
faeae8fd6c | ||
|
|
ae4942ba29 | ||
|
|
fd652bfce6 | ||
|
|
3d72167721 | ||
|
|
ba284bef9e | ||
|
|
d18bb9cc74 | ||
|
|
a7ed3e58db | ||
|
|
8405ebd28d | ||
|
|
352bb65125 | ||
|
|
fe2cc5a99a | ||
|
|
7a2f29f6a3 | ||
|
|
9a05bfa899 | ||
|
|
39fa64e20d | ||
|
|
3a835b420e | ||
|
|
82f7633c3a | ||
|
|
9fdac2741a | ||
|
|
8fb5260809 | ||
|
|
e08ec12d26 | ||
|
|
1202e00a21 | ||
|
|
4ba2205af4 | ||
|
|
09841ad4cb | ||
|
|
d2dcd0abc8 | ||
|
|
fe9d0503fb | ||
|
|
8e9e6607e5 | ||
|
|
e1efd9355f | ||
|
|
ca705bbf89 | ||
|
|
b70fe09d17 | ||
|
|
d7d570393f | ||
|
|
41ca265e5a | ||
|
|
03cde08d67 | ||
|
|
5684674bd7 | ||
|
|
4fe919f2ea | ||
|
|
c8c23c53ef | ||
|
|
b1c25e98d7 | ||
|
|
7ab5a4bfcf | ||
|
|
a3ee57995c | ||
|
|
32020fd336 | ||
|
|
f1313b6468 | ||
|
|
1b43976ff0 | ||
|
|
321fb019eb | ||
|
|
f6858a68e0 | ||
|
|
fe17e2eaba | ||
|
|
22ef0b5d29 | ||
|
|
823279fb60 | ||
|
|
f56361c0ca | ||
|
|
4946ca2d91 |
@@ -2,4 +2,5 @@ COOLIFY_APP_ID=
|
|||||||
COOLIFY_SECRET_KEY=12341234123412341234123412341234
|
COOLIFY_SECRET_KEY=12341234123412341234123412341234
|
||||||
COOLIFY_DATABASE_URL=file:../db/dev.db
|
COOLIFY_DATABASE_URL=file:../db/dev.db
|
||||||
COOLIFY_SENTRY_DSN=
|
COOLIFY_SENTRY_DSN=
|
||||||
COOLIFY_IS_ON="docker"
|
COOLIFY_IS_ON="docker"
|
||||||
|
COOLIFY_WHITE_LABELED="false"
|
||||||
@@ -1,12 +1,12 @@
|
|||||||
# Welcome
|
# Welcome
|
||||||
|
|
||||||
First of all, thank you for considering to contribute to my project! It means a lot 💜.
|
First of all, thank you for considering contributing to my project! It means a lot 💜.
|
||||||
|
|
||||||
# Technical skills required
|
# Technical skills required
|
||||||
|
|
||||||
- Node.js / Javascript
|
- Node.js / Javascript
|
||||||
- Svelte / SvelteKit
|
- Svelte / SvelteKit
|
||||||
- Prisma.io
|
- Prisma.io / SQL
|
||||||
|
|
||||||
# Recommended Pull Request Guideline
|
# Recommended Pull Request Guideline
|
||||||
|
|
||||||
@@ -16,11 +16,13 @@ First of all, thank you for considering to contribute to my project! It means a
|
|||||||
- Push to your fork repo
|
- Push to your fork repo
|
||||||
- Create a pull request: https://github.com/coollabsio/compare
|
- Create a pull request: https://github.com/coollabsio/compare
|
||||||
- Write a proper description
|
- Write a proper description
|
||||||
- Click "Change to draft"
|
- Open the pull request to review against `next` branch
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
# How to start after you set up your local fork?
|
# How to start after you set up your local fork?
|
||||||
|
|
||||||
This repository best with [pnpm](https://pnpm.io) due to the lock file. I recommend you should try and use `pnpm` as well, because it is cool and efficient!
|
Due to the lock file, this repository is best with [pnpm](https://pnpm.io). I recommend you try and use `pnpm` because it is cool and efficient!
|
||||||
|
|
||||||
You need to have [Docker Engine](https://docs.docker.com/engine/install/) installed locally.
|
You need to have [Docker Engine](https://docs.docker.com/engine/install/) installed locally.
|
||||||
|
|
||||||
@@ -41,4 +43,75 @@ If the schema is finalized, you need to create a migration file with `pnpm db:mi
|
|||||||
|
|
||||||
## Tricky parts
|
## Tricky parts
|
||||||
|
|
||||||
- BullMQ, the queue system Coolify is using, cannot be hot reloaded. So if you change anything in the files related to it, you need to restart the development process. I'm actively looking of a different queue/scheduler library. I'm open for discussion!
|
- BullMQ, the queue system Coolify uses, cannot be hot reloaded. So if you change anything in the files related to it, you need to restart the development process. I'm actively looking for a different queue/scheduler library. I'm open to discussion!
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# How to add new services
|
||||||
|
|
||||||
|
You can add any open-source and self-hostable software (service/application) to Coolify if the following statements are true:
|
||||||
|
|
||||||
|
- Self-hostable (obviously)
|
||||||
|
- Open-source
|
||||||
|
- Maintained (I do not want to add software full of bugs)
|
||||||
|
|
||||||
|
## Backend
|
||||||
|
|
||||||
|
I use MinIO as an example.
|
||||||
|
|
||||||
|
You need to add a new folder to [src/routes/services/[id]](src/routes/services/[id]) with the low-capital name of the service. It should have three files with the following properties:
|
||||||
|
|
||||||
|
1. `index.json.ts`: A POST endpoint that updates Coolify's database about the service.
|
||||||
|
|
||||||
|
Basic services only require updating the URL(fqdn) and the name of the service.
|
||||||
|
|
||||||
|
2. `start.json.ts`: A start endpoint that setups the docker-compose file (for Local Docker Engines) and starts the service.
|
||||||
|
|
||||||
|
- To start a service, you need to know Coolify supported images and tags of the service. For that you need to update `supportedServiceTypesAndVersions` function at [src/lib/components/common.ts](src/lib/components/common.ts).
|
||||||
|
|
||||||
|
Example JSON:
|
||||||
|
|
||||||
|
```js
|
||||||
|
{
|
||||||
|
// Name used to identify the service in Coolify
|
||||||
|
name: 'minio',
|
||||||
|
// Fancier name to show to the user
|
||||||
|
fancyName: 'MinIO',
|
||||||
|
// Docker base image for the service
|
||||||
|
baseImage: 'minio/minio',
|
||||||
|
// Usable tags
|
||||||
|
versions: ['latest'],
|
||||||
|
// Which tag is the recommended
|
||||||
|
recommendedVersion: 'latest',
|
||||||
|
// Application's default port, MinIO listens on 9001 (and 9000, more details later on)
|
||||||
|
ports: {
|
||||||
|
main: 9001
|
||||||
|
}
|
||||||
|
},
|
||||||
|
```
|
||||||
|
|
||||||
|
- You need to define a compose file as `const composeFile: ComposeFile` found in [src/routes/services/[id]/minio/start.json.ts](src/routes/services/[id]/minio/start.json.ts)
|
||||||
|
|
||||||
|
**IMPORTANT:** It should contain `all the default environment variables` that are required for the service to function correctly and `all the volumes to persist data` in restarts.
|
||||||
|
|
||||||
|
- You could also define an `HTTP` or `TCP` proxy for every other port that should be proxied to your server. (See `startHttpProxy` and `startTcpProxy` functions in [src/lib/haproxy/index.ts](src/lib/haproxy/index.ts))
|
||||||
|
|
||||||
|
3. `stop.json.ts` A stop endpoint that stops the service.
|
||||||
|
|
||||||
|
It needs to stop all the services by their container name and proxies (if applicable).
|
||||||
|
|
||||||
|
4. You need to add the automatically generated variables (passwords, users, etc.) for the new service at [src/lib/database/services.ts](src/lib/database/services.ts), `configureServiceType` function.
|
||||||
|
|
||||||
|
## Frontend
|
||||||
|
|
||||||
|
1. You need to add a custom logo at [src/lib/components/svg/services/](src/lib/components/svg/services/) as a svelte component.
|
||||||
|
|
||||||
|
SVG is recommended, but you can use PNG as well. It should have the `isAbsolute` variable with the suitable CSS classes, primarily for sizing and positioning.
|
||||||
|
|
||||||
|
2. You need to include it the logo at [src/routes/services/index.svelte](src/routes/services/index.svelte) with `isAbsolute` and [src/lib/components/ServiceLinks.svelte](src/lib/components/ServiceLinks.svelte) with a link to the docs/main site of the service.
|
||||||
|
|
||||||
|
3. By default the URL and the name frontend forms are included in [src/routes/services/[id]/\_Services/\_Services.svelte](src/routes/services/[id]/_Services/_Services.svelte).
|
||||||
|
|
||||||
|
If you need to show more details on the frontend, such as users/passwords, you need to add Svelte component to [src/routes/services/[id]/\_Services](src/routes/services/[id]/_Services) with an underscore. For example, see other files in that folder.
|
||||||
|
|
||||||
|
You also need to add the new inputs to the `index.json.ts` file of the specific service, like for MinIO here: [src/routes/services/[id]/minio/index.json.ts](src/routes/services/[id]/minio/index.json.ts)
|
||||||
|
|||||||
55
Dockerfile
55
Dockerfile
@@ -1,31 +1,42 @@
|
|||||||
FROM node:16.14.0-alpine
|
FROM node:16.14.2-alpine as install
|
||||||
RUN apk add --no-cache g++ cmake make python3
|
|
||||||
WORKDIR /app
|
|
||||||
COPY package*.json .
|
|
||||||
RUN yarn install
|
|
||||||
COPY . .
|
|
||||||
RUN yarn build
|
|
||||||
|
|
||||||
FROM node:16.14.0-alpine
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
LABEL coolify.managed true
|
RUN apk add --no-cache curl
|
||||||
|
|
||||||
RUN apk add --no-cache git openssh-client curl jq cmake sqlite
|
|
||||||
|
|
||||||
RUN curl -f https://get.pnpm.io/v6.16.js | node - add --global pnpm@6
|
RUN curl -f https://get.pnpm.io/v6.16.js | node - add --global pnpm@6
|
||||||
RUN pnpm add -g pnpm
|
RUN pnpm add -g pnpm
|
||||||
|
|
||||||
RUN curl -fsSL "https://download.docker.com/linux/static/stable/x86_64/docker-20.10.9.tgz" | tar -xzvf - docker/docker -C . --strip-components 1 && mv docker /usr/bin/docker
|
COPY package*.json .
|
||||||
RUN mkdir -p ~/.docker/cli-plugins/
|
RUN pnpm install
|
||||||
RUN curl -SL https://github.com/docker/compose/releases/download/v2.2.2/docker-compose-linux-x86_64 -o ~/.docker/cli-plugins/docker-compose
|
|
||||||
RUN chmod +x ~/.docker/cli-plugins/docker-compose
|
FROM node:16.14.2-alpine
|
||||||
|
ARG TARGETPLATFORM
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
ENV PRISMA_QUERY_ENGINE_BINARY=/app/prisma-engines/query-engine \
|
||||||
|
PRISMA_MIGRATION_ENGINE_BINARY=/app/prisma-engines/migration-engine \
|
||||||
|
PRISMA_INTROSPECTION_ENGINE_BINARY=/app/prisma-engines/introspection-engine \
|
||||||
|
PRISMA_FMT_BINARY=/app/prisma-engines/prisma-fmt \
|
||||||
|
PRISMA_CLI_QUERY_ENGINE_TYPE=binary \
|
||||||
|
PRISMA_CLIENT_ENGINE_TYPE=binary
|
||||||
|
|
||||||
|
COPY --from=coollabsio/prisma-engine:latest /prisma-engines/query-engine /prisma-engines/migration-engine /prisma-engines/introspection-engine /prisma-engines/prisma-fmt /app/prisma-engines/
|
||||||
|
|
||||||
|
COPY --from=install /app/node_modules ./node_modules
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
RUN apk add --no-cache git git-lfs openssh-client curl jq cmake sqlite openssl
|
||||||
|
RUN curl -f https://get.pnpm.io/v6.16.js | node - add --global pnpm@6
|
||||||
|
RUN pnpm add -g pnpm
|
||||||
|
RUN mkdir -p ~/.docker/cli-plugins/
|
||||||
|
RUN curl -SL https://cdn.coollabs.io/bin/$TARGETPLATFORM/docker-20.10.9 -o /usr/bin/docker
|
||||||
|
RUN curl -SL https://cdn.coollabs.io/bin/$TARGETPLATFORM/docker-compose-linux-2.3.4 -o ~/.docker/cli-plugins/docker-compose
|
||||||
|
RUN chmod +x ~/.docker/cli-plugins/docker-compose /usr/bin/docker
|
||||||
|
|
||||||
|
RUN pnpm prisma generate
|
||||||
|
RUN pnpm build
|
||||||
|
|
||||||
|
|
||||||
COPY --from=0 /app/docker-compose.yaml .
|
|
||||||
COPY --from=0 /app/build .
|
|
||||||
COPY --from=0 /app/package.json .
|
|
||||||
COPY --from=0 /app/node_modules ./node_modules
|
|
||||||
COPY --from=0 /app/prisma ./prisma
|
|
||||||
|
|
||||||
EXPOSE 3000
|
EXPOSE 3000
|
||||||
CMD ["pnpm", "start"]
|
CMD ["pnpm", "start"]
|
||||||
@@ -22,7 +22,7 @@ If you would like no questions during installation
|
|||||||
|
|
||||||
### Git Sources
|
### Git Sources
|
||||||
|
|
||||||
You can use the following Git Sources to be auto-deployed to your Coolifyt instance! (Self hosted versions also supported.)
|
You can use the following Git Sources to be auto-deployed to your Coolifyt instance! (Self-hosted versions are also supported.)
|
||||||
|
|
||||||
- Github
|
- Github
|
||||||
- GitLab
|
- GitLab
|
||||||
@@ -38,7 +38,7 @@ You can deploy your applications to the following destinations:
|
|||||||
|
|
||||||
### Applications
|
### Applications
|
||||||
|
|
||||||
These are the predefined build packs, but with the Docker build pack, you can host basically anything that is hostable with a single Dockerfile.
|
These are the predefined build packs, but with the Docker build pack, you can host anything that is hostable with a single Dockerfile.
|
||||||
|
|
||||||
- Static sites
|
- Static sites
|
||||||
- NodeJS
|
- NodeJS
|
||||||
|
|||||||
6
data/fluentd/Dockerfile-dev
Normal file
6
data/fluentd/Dockerfile-dev
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
FROM fluent/fluent-bit:1.9.0
|
||||||
|
COPY fluentbit-dev.conf /tmp/fluentbit.conf
|
||||||
|
ENTRYPOINT ["/fluent-bit/bin/fluent-bit", "-c", "/tmp/fluentbit.conf"]
|
||||||
|
# USER root
|
||||||
|
# RUN ["gem", "install", "fluent-plugin-mongo"]
|
||||||
|
# USER fluent
|
||||||
24
data/fluentd/fluentbit-dev.conf
Normal file
24
data/fluentd/fluentbit-dev.conf
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
[INPUT]
|
||||||
|
Name forward
|
||||||
|
Listen 0.0.0.0
|
||||||
|
Port 24224
|
||||||
|
Buffer_Chunk_Size 32KB
|
||||||
|
Buffer_Max_Size 64KB
|
||||||
|
|
||||||
|
[OUTPUT]
|
||||||
|
Name influxdb
|
||||||
|
Match *
|
||||||
|
Host coolify-influxdb
|
||||||
|
Port 8086
|
||||||
|
Bucket containerlogs
|
||||||
|
Org organization
|
||||||
|
HTTP_Token supertoken
|
||||||
|
Sequence_Tag _seq
|
||||||
|
Tag_Keys container_name
|
||||||
|
[OUTPUT]
|
||||||
|
Name http
|
||||||
|
Match *
|
||||||
|
Host host.docker.internal
|
||||||
|
Port 3000
|
||||||
|
URI /logs.json
|
||||||
|
Format json
|
||||||
28
data/fluentd/fluentd-dev.conf
Normal file
28
data/fluentd/fluentd-dev.conf
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
<source>
|
||||||
|
@type forward
|
||||||
|
port 24224
|
||||||
|
bind 0.0.0.0
|
||||||
|
</source>
|
||||||
|
|
||||||
|
<match **>
|
||||||
|
@type http
|
||||||
|
endpoint http://host.docker.internal:3000/logs.json
|
||||||
|
<buffer>
|
||||||
|
flush_at_shutdown true
|
||||||
|
flush_mode immediate
|
||||||
|
flush_thread_count 8
|
||||||
|
flush_thread_interval 1
|
||||||
|
flush_thread_burst_interval 1
|
||||||
|
retry_forever true
|
||||||
|
retry_type exponential_backoff
|
||||||
|
</buffer>
|
||||||
|
</match>
|
||||||
|
|
||||||
|
<filter docker.**>
|
||||||
|
@type parser
|
||||||
|
key_name log
|
||||||
|
reserve_data true
|
||||||
|
<parse>
|
||||||
|
@type json
|
||||||
|
</parse>
|
||||||
|
</filter>
|
||||||
@@ -4,10 +4,10 @@ global
|
|||||||
defaults
|
defaults
|
||||||
mode http
|
mode http
|
||||||
log global
|
log global
|
||||||
timeout http-request 60s
|
timeout http-request 120s
|
||||||
timeout connect 10s
|
timeout connect 20s
|
||||||
timeout client 60s
|
timeout client 120s
|
||||||
timeout server 60s
|
timeout server 120s
|
||||||
|
|
||||||
frontend "${APP}"
|
frontend "${APP}"
|
||||||
mode http
|
mode http
|
||||||
|
|||||||
@@ -5,10 +5,10 @@ global
|
|||||||
defaults
|
defaults
|
||||||
mode http
|
mode http
|
||||||
log global
|
log global
|
||||||
timeout http-request 60s
|
timeout http-request 120s
|
||||||
timeout connect 10s
|
timeout connect 20s
|
||||||
timeout client 60s
|
timeout client 120s
|
||||||
timeout server 60s
|
timeout server 120s
|
||||||
|
|
||||||
userlist haproxy-dataplaneapi
|
userlist haproxy-dataplaneapi
|
||||||
user admin insecure-password "${HAPROXY_PASSWORD}"
|
user admin insecure-password "${HAPROXY_PASSWORD}"
|
||||||
|
|||||||
1
data/prisma/build-prisma-engine.sh
Normal file
1
data/prisma/build-prisma-engine.sh
Normal file
@@ -0,0 +1 @@
|
|||||||
|
docker build --platform linux/amd64,linux/arm64 -t coollabsio/prisma-engine -f prisma-engine.Dockerfile --push .
|
||||||
10
data/prisma/prisma-engine.Dockerfile
Normal file
10
data/prisma/prisma-engine.Dockerfile
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
FROM rust:1.58.1-alpine3.14 as prisma
|
||||||
|
WORKDIR /prisma
|
||||||
|
ENV RUSTFLAGS="-C target-feature=-crt-static"
|
||||||
|
RUN apk --no-cache add openssl direnv git musl-dev openssl-dev build-base perl protoc
|
||||||
|
RUN git clone --depth=1 --branch=3.12.x https://github.com/prisma/prisma-engines.git /prisma
|
||||||
|
RUN cargo build --release
|
||||||
|
|
||||||
|
FROM alpine
|
||||||
|
WORKDIR /prisma-engines
|
||||||
|
COPY --from=prisma /prisma/target/release/query-engine /prisma/target/release/migration-engine /prisma/target/release/introspection-engine /prisma/target/release/prisma-fmt /prisma-engines/
|
||||||
@@ -2,10 +2,8 @@ version: '3.8'
|
|||||||
|
|
||||||
services:
|
services:
|
||||||
redis:
|
redis:
|
||||||
image: 'bitnami/redis:6.2'
|
image: redis:6.2-alpine
|
||||||
container_name: coolify-redis
|
container_name: coolify-redis
|
||||||
environment:
|
|
||||||
- ALLOW_EMPTY_PASSWORD=yes
|
|
||||||
networks:
|
networks:
|
||||||
- coolify-infra
|
- coolify-infra
|
||||||
ports:
|
ports:
|
||||||
@@ -13,7 +11,24 @@ services:
|
|||||||
published: 6379
|
published: 6379
|
||||||
protocol: tcp
|
protocol: tcp
|
||||||
mode: host
|
mode: host
|
||||||
|
# fluentbit:
|
||||||
|
# container_name: coolify-fluentbit
|
||||||
|
# build:
|
||||||
|
# context: ./data/fluentd
|
||||||
|
# dockerfile: Dockerfile-dev
|
||||||
|
# ports:
|
||||||
|
# - target: 24224
|
||||||
|
# published: 24224
|
||||||
|
# protocol: tcp
|
||||||
|
# mode: host
|
||||||
|
# - target: 24224
|
||||||
|
# published: 24224
|
||||||
|
# protocol: udp
|
||||||
|
# mode: host
|
||||||
|
# networks:
|
||||||
|
# - coolify-infra
|
||||||
|
# extra_hosts:
|
||||||
|
# - 'host.docker.internal:host-gateway'
|
||||||
networks:
|
networks:
|
||||||
coolify-infra:
|
coolify-infra:
|
||||||
attachable: true
|
attachable: true
|
||||||
|
|||||||
@@ -21,11 +21,9 @@ services:
|
|||||||
- coolify-infra
|
- coolify-infra
|
||||||
depends_on: ['redis']
|
depends_on: ['redis']
|
||||||
redis:
|
redis:
|
||||||
image: bitnami/redis:6.2
|
image: redis:6.2-alpine
|
||||||
restart: always
|
restart: always
|
||||||
container_name: coolify-redis
|
container_name: coolify-redis
|
||||||
environment:
|
|
||||||
- ALLOW_EMPTY_PASSWORD=yes
|
|
||||||
networks:
|
networks:
|
||||||
- coolify-infra
|
- coolify-infra
|
||||||
|
|
||||||
|
|||||||
59
package.json
59
package.json
@@ -1,14 +1,14 @@
|
|||||||
{
|
{
|
||||||
"name": "coolify",
|
"name": "coolify",
|
||||||
"description": "An open-source & self-hostable Heroku / Netlify alternative.",
|
"description": "An open-source & self-hostable Heroku / Netlify alternative.",
|
||||||
"version": "2.3.2",
|
"version": "2.5.0",
|
||||||
"license": "AGPL-3.0",
|
"license": "AGPL-3.0",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "docker-compose -f docker-compose-dev.yaml up -d && cross-env NODE_ENV=development & svelte-kit dev",
|
"dev": "docker-compose -f docker-compose-dev.yaml up -d && cross-env NODE_ENV=development & svelte-kit dev --host 0.0.0.0",
|
||||||
"dev:stop": "docker-compose -f docker-compose-dev.yaml down",
|
"dev:stop": "docker-compose -f docker-compose-dev.yaml down",
|
||||||
"dev:logs": "docker-compose -f docker-compose-dev.yaml logs -f --tail 10",
|
"dev:logs": "docker-compose -f docker-compose-dev.yaml logs -f --tail 10",
|
||||||
"studio": "npx prisma studio",
|
"studio": "npx prisma studio",
|
||||||
"start": "npx prisma migrate deploy && npx prisma generate && npx prisma db seed && node index.js",
|
"start": "npx prisma migrate deploy && npx prisma generate && npx prisma db seed && node build/index.js",
|
||||||
"build": "svelte-kit build",
|
"build": "svelte-kit build",
|
||||||
"preview": "svelte-kit preview",
|
"preview": "svelte-kit preview",
|
||||||
"check": "svelte-check --tsconfig ./tsconfig.json",
|
"check": "svelte-check --tsconfig ./tsconfig.json",
|
||||||
@@ -17,21 +17,23 @@
|
|||||||
"db:push": "prisma db push && prisma generate",
|
"db:push": "prisma db push && prisma generate",
|
||||||
"db:seed": "prisma db seed",
|
"db:seed": "prisma db seed",
|
||||||
"db:migrate": "COOLIFY_DATABASE_URL=file:../db/migration.db prisma migrate dev --skip-seed --name",
|
"db:migrate": "COOLIFY_DATABASE_URL=file:../db/migration.db prisma migrate dev --skip-seed --name",
|
||||||
"release:staging": "cross-var docker build -t coollabsio/coolify:$npm_package_version . && docker push coollabsio/coolify:$npm_package_version",
|
"release:production:all": "cross-var docker build --platform linux/amd64,linux/arm64 -t coollabsio/coolify:$npm_package_version -t coollabsio/coolify:latest --push .",
|
||||||
"release:pre": "cross-var docker build -t coollabsio/coolify:$npm_package_version -t coollabsio/coolify:latest .",
|
"release:production:amd": "cross-var docker build --platform linux/amd64 -t coollabsio/coolify:$npm_package_version -t coollabsio/coolify:latest --push .",
|
||||||
"release:coolify": "cross-var yarn release:pre && docker push coollabsio/coolify:$npm_package_version && docker push coollabsio/coolify:latest",
|
"release:production:arm": "cross-var docker build --platform linux/arm64 -t coollabsio/coolify:$npm_package_version -t coollabsio/coolify:latest --push .",
|
||||||
"release:haproxy": "docker build -f haproxy.Dockerfile -t coollabsio/coolify-haproxy-alpine:1.0.0 -t coollabsio/coolify-haproxy-alpine:latest . && docker image push --all-tags coollabsio/coolify-haproxy-alpine",
|
"release:staging:all": "cross-var docker build --platform linux/amd64,linux/arm64 -t coollabsio/coolify:$npm_package_version --push .",
|
||||||
"release:haproxy:tcp": "docker build -f haproxy-tcp.Dockerfile -t coollabsio/coolify-haproxy-tcp-alpine:1.0.0 -t coollabsio/coolify-haproxy-tcp-alpine:latest . && docker image push --all-tags coollabsio/coolify-haproxy-tcp-alpine",
|
"release:staging:amd": "cross-var docker build --platform linux/amd64 -t coollabsio/coolify:$npm_package_version --push .",
|
||||||
"release:haproxy:http": "docker build -f haproxy-http.Dockerfile -t coollabsio/coolify-haproxy-http-alpine:1.0.0 -t coollabsio/coolify-haproxy-http-alpine:latest . && docker image push --all-tags coollabsio/coolify-haproxy-http-alpine",
|
"release:staging:arm": "cross-var docker build --platform linux/arm64 -t coollabsio/coolify:$npm_package_version --push .",
|
||||||
|
"release:haproxy": "docker build --platform linux/amd64,linux/arm64 -t coollabsio/coolify-haproxy-alpine:latest -t coollabsio/coolify-haproxy-alpine:1.1.0 -f data/haproxy.Dockerfile --push .",
|
||||||
|
"release:haproxy:tcp": "docker build --platform linux/amd64,linux/arm64 -t coollabsio/coolify-haproxy-tcp-alpine:latest -t coollabsio/coolify-haproxy-tcp-alpine:1.1.0 -f data/haproxy-tcp.Dockerfile --push .",
|
||||||
|
"release:haproxy:http": "docker build --platform linux/amd64,linux/arm64 -t coollabsio/coolify-haproxy-http-alpine:latest -t coollabsio/coolify-haproxy-http-alpine:1.1.0 -f data/haproxy-http.Dockerfile --push .",
|
||||||
"prepare": "husky install"
|
"prepare": "husky install"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@sveltejs/adapter-node": "1.0.0-next.73",
|
"@sveltejs/adapter-node": "1.0.0-next.73",
|
||||||
"@sveltejs/kit": "1.0.0-next.303",
|
"@sveltejs/kit": "1.0.0-next.316",
|
||||||
"@types/bcrypt": "5.0.0",
|
|
||||||
"@types/js-cookie": "3.0.1",
|
"@types/js-cookie": "3.0.1",
|
||||||
"@types/js-yaml": "4.0.5",
|
"@types/js-yaml": "4.0.5",
|
||||||
"@types/node": "17.0.23",
|
"@types/node": "17.0.25",
|
||||||
"@types/node-forge": "1.0.1",
|
"@types/node-forge": "1.0.1",
|
||||||
"@typescript-eslint/eslint-plugin": "4.31.1",
|
"@typescript-eslint/eslint-plugin": "4.31.1",
|
||||||
"@typescript-eslint/parser": "4.31.1",
|
"@typescript-eslint/parser": "4.31.1",
|
||||||
@@ -43,17 +45,17 @@
|
|||||||
"eslint-config-prettier": "8.5.0",
|
"eslint-config-prettier": "8.5.0",
|
||||||
"eslint-plugin-svelte3": "3.4.1",
|
"eslint-plugin-svelte3": "3.4.1",
|
||||||
"husky": "7.0.4",
|
"husky": "7.0.4",
|
||||||
"lint-staged": "12.3.7",
|
"lint-staged": "12.4.0",
|
||||||
"postcss": "8.4.12",
|
"postcss": "8.4.12",
|
||||||
"prettier": "2.6.1",
|
"prettier": "2.6.2",
|
||||||
"prettier-plugin-svelte": "2.6.0",
|
"prettier-plugin-svelte": "2.7.0",
|
||||||
"prettier-plugin-tailwindcss": "0.1.8",
|
"prettier-plugin-tailwindcss": "0.1.10",
|
||||||
"prisma": "3.11.1",
|
"prisma": "3.11.1",
|
||||||
"svelte": "3.46.4",
|
"svelte": "3.47.0",
|
||||||
"svelte-check": "2.4.6",
|
"svelte-check": "2.7.0",
|
||||||
"svelte-preprocess": "4.10.4",
|
"svelte-preprocess": "4.10.6",
|
||||||
"svelte-select": "4.4.7",
|
"svelte-select": "4.4.7",
|
||||||
"tailwindcss": "3.0.23",
|
"tailwindcss": "3.0.24",
|
||||||
"ts-node": "10.7.0",
|
"ts-node": "10.7.0",
|
||||||
"tslib": "2.3.1",
|
"tslib": "2.3.1",
|
||||||
"typescript": "4.6.3"
|
"typescript": "4.6.3"
|
||||||
@@ -62,26 +64,25 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@iarna/toml": "2.2.5",
|
"@iarna/toml": "2.2.5",
|
||||||
"@prisma/client": "3.11.1",
|
"@prisma/client": "3.11.1",
|
||||||
"@sentry/node": "6.19.2",
|
"@sentry/node": "6.19.6",
|
||||||
"bcrypt": "5.0.1",
|
"bcryptjs": "2.4.3",
|
||||||
"bullmq": "1.78.1",
|
"bullmq": "1.80.4",
|
||||||
"compare-versions": "4.1.3",
|
"compare-versions": "4.1.3",
|
||||||
"cookie": "0.4.2",
|
"cookie": "0.5.0",
|
||||||
"cooltipz-css": "2.1.0",
|
|
||||||
"cuid": "2.1.8",
|
"cuid": "2.1.8",
|
||||||
"dayjs": "1.11.0",
|
"dayjs": "1.11.1",
|
||||||
"dockerode": "3.3.1",
|
"dockerode": "3.3.1",
|
||||||
"dotenv-extended": "2.9.0",
|
"dotenv-extended": "2.9.0",
|
||||||
"generate-password": "1.7.0",
|
"generate-password": "1.7.0",
|
||||||
"get-port": "6.1.2",
|
"get-port": "6.1.2",
|
||||||
"got": "12.0.2",
|
"got": "12.0.3",
|
||||||
"js-cookie": "3.0.1",
|
"js-cookie": "3.0.1",
|
||||||
"js-yaml": "4.1.0",
|
"js-yaml": "4.1.0",
|
||||||
"jsonwebtoken": "8.5.1",
|
"jsonwebtoken": "8.5.1",
|
||||||
"mustache": "4.2.0",
|
"mustache": "4.2.0",
|
||||||
"node-forge": "1.3.0",
|
"node-forge": "1.3.1",
|
||||||
"p-limit": "4.0.0",
|
"p-limit": "4.0.0",
|
||||||
"svelte-kit-cookie-session": "2.1.2",
|
"svelte-kit-cookie-session": "2.1.3",
|
||||||
"tailwindcss-scrollbar": "0.1.0",
|
"tailwindcss-scrollbar": "0.1.0",
|
||||||
"unique-names-generator": "4.7.1"
|
"unique-names-generator": "4.7.1"
|
||||||
},
|
},
|
||||||
|
|||||||
825
pnpm-lock.yaml
generated
825
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,29 @@
|
|||||||
|
-- RedefineTables
|
||||||
|
PRAGMA foreign_keys=OFF;
|
||||||
|
CREATE TABLE "new_Wordpress" (
|
||||||
|
"id" TEXT NOT NULL PRIMARY KEY,
|
||||||
|
"extraConfig" TEXT,
|
||||||
|
"tablePrefix" TEXT,
|
||||||
|
"mysqlUser" TEXT NOT NULL,
|
||||||
|
"mysqlPassword" TEXT NOT NULL,
|
||||||
|
"mysqlRootUser" TEXT NOT NULL,
|
||||||
|
"mysqlRootUserPassword" TEXT NOT NULL,
|
||||||
|
"mysqlDatabase" TEXT,
|
||||||
|
"mysqlPublicPort" INTEGER,
|
||||||
|
"ftpEnabled" BOOLEAN NOT NULL DEFAULT false,
|
||||||
|
"ftpUser" TEXT,
|
||||||
|
"ftpPassword" TEXT,
|
||||||
|
"ftpPublicPort" INTEGER,
|
||||||
|
"ftpHostKey" TEXT,
|
||||||
|
"ftpHostKeyPrivate" TEXT,
|
||||||
|
"serviceId" TEXT NOT NULL,
|
||||||
|
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"updatedAt" DATETIME NOT NULL,
|
||||||
|
CONSTRAINT "Wordpress_serviceId_fkey" FOREIGN KEY ("serviceId") REFERENCES "Service" ("id") ON DELETE RESTRICT ON UPDATE CASCADE
|
||||||
|
);
|
||||||
|
INSERT INTO "new_Wordpress" ("createdAt", "extraConfig", "id", "mysqlDatabase", "mysqlPassword", "mysqlPublicPort", "mysqlRootUser", "mysqlRootUserPassword", "mysqlUser", "serviceId", "tablePrefix", "updatedAt") SELECT "createdAt", "extraConfig", "id", "mysqlDatabase", "mysqlPassword", "mysqlPublicPort", "mysqlRootUser", "mysqlRootUserPassword", "mysqlUser", "serviceId", "tablePrefix", "updatedAt" FROM "Wordpress";
|
||||||
|
DROP TABLE "Wordpress";
|
||||||
|
ALTER TABLE "new_Wordpress" RENAME TO "Wordpress";
|
||||||
|
CREATE UNIQUE INDEX "Wordpress_serviceId_key" ON "Wordpress"("serviceId");
|
||||||
|
PRAGMA foreign_key_check;
|
||||||
|
PRAGMA foreign_keys=ON;
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
-- DropIndex
|
||||||
|
DROP INDEX "ApplicationPersistentStorage_path_key";
|
||||||
|
|
||||||
|
-- DropIndex
|
||||||
|
DROP INDEX "ApplicationPersistentStorage_applicationId_key";
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "ServicePersistentStorage" (
|
||||||
|
"id" TEXT NOT NULL PRIMARY KEY,
|
||||||
|
"serviceId" TEXT NOT NULL,
|
||||||
|
"path" TEXT NOT NULL,
|
||||||
|
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"updatedAt" DATETIME NOT NULL,
|
||||||
|
CONSTRAINT "ServicePersistentStorage_serviceId_fkey" FOREIGN KEY ("serviceId") REFERENCES "Service" ("id") ON DELETE RESTRICT ON UPDATE CASCADE
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE UNIQUE INDEX "ServicePersistentStorage_serviceId_path_key" ON "ServicePersistentStorage"("serviceId", "path");
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "Application" ADD COLUMN "dockerFileLocation" TEXT;
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "Application" ADD COLUMN "denoMainFile" TEXT;
|
||||||
|
ALTER TABLE "Application" ADD COLUMN "denoOptions" TEXT;
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "Build" ADD COLUMN "branch" TEXT;
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
generator client {
|
generator client {
|
||||||
provider = "prisma-client-js"
|
provider = "prisma-client-js"
|
||||||
|
binaryTargets = ["native", "linux-musl"]
|
||||||
}
|
}
|
||||||
|
|
||||||
datasource db {
|
datasource db {
|
||||||
@@ -90,6 +91,9 @@ model Application {
|
|||||||
pythonWSGI String?
|
pythonWSGI String?
|
||||||
pythonModule String?
|
pythonModule String?
|
||||||
pythonVariable String?
|
pythonVariable String?
|
||||||
|
dockerFileLocation String?
|
||||||
|
denoMainFile String?
|
||||||
|
denoOptions String?
|
||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
updatedAt DateTime @updatedAt
|
updatedAt DateTime @updatedAt
|
||||||
settings ApplicationSettings?
|
settings ApplicationSettings?
|
||||||
@@ -117,14 +121,25 @@ model ApplicationSettings {
|
|||||||
model ApplicationPersistentStorage {
|
model ApplicationPersistentStorage {
|
||||||
id String @id @default(cuid())
|
id String @id @default(cuid())
|
||||||
application Application @relation(fields: [applicationId], references: [id])
|
application Application @relation(fields: [applicationId], references: [id])
|
||||||
applicationId String @unique
|
applicationId String
|
||||||
path String @unique
|
path String
|
||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
updatedAt DateTime @updatedAt
|
updatedAt DateTime @updatedAt
|
||||||
|
|
||||||
@@unique([applicationId, path])
|
@@unique([applicationId, path])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
model ServicePersistentStorage {
|
||||||
|
id String @id @default(cuid())
|
||||||
|
service Service @relation(fields: [serviceId], references: [id])
|
||||||
|
serviceId String
|
||||||
|
path String
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
updatedAt DateTime @updatedAt
|
||||||
|
|
||||||
|
@@unique([serviceId, path])
|
||||||
|
}
|
||||||
|
|
||||||
model Secret {
|
model Secret {
|
||||||
id String @id @default(cuid())
|
id String @id @default(cuid())
|
||||||
name String
|
name String
|
||||||
@@ -168,6 +183,7 @@ model Build {
|
|||||||
githubAppId String?
|
githubAppId String?
|
||||||
gitlabAppId String?
|
gitlabAppId String?
|
||||||
commit String?
|
commit String?
|
||||||
|
branch String?
|
||||||
status String? @default("queued")
|
status String? @default("queued")
|
||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
updatedAt DateTime @updatedAt
|
updatedAt DateTime @updatedAt
|
||||||
@@ -266,17 +282,17 @@ model DatabaseSettings {
|
|||||||
}
|
}
|
||||||
|
|
||||||
model Service {
|
model Service {
|
||||||
id String @id @default(cuid())
|
id String @id @default(cuid())
|
||||||
name String
|
name String
|
||||||
fqdn String?
|
fqdn String?
|
||||||
dualCerts Boolean @default(false)
|
dualCerts Boolean @default(false)
|
||||||
type String?
|
type String?
|
||||||
version String?
|
version String?
|
||||||
teams Team[]
|
teams Team[]
|
||||||
destinationDockerId String?
|
destinationDockerId String?
|
||||||
destinationDocker DestinationDocker? @relation(fields: [destinationDockerId], references: [id])
|
destinationDocker DestinationDocker? @relation(fields: [destinationDockerId], references: [id])
|
||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
updatedAt DateTime @updatedAt
|
updatedAt DateTime @updatedAt
|
||||||
plausibleAnalytics PlausibleAnalytics?
|
plausibleAnalytics PlausibleAnalytics?
|
||||||
minio Minio?
|
minio Minio?
|
||||||
vscodeserver Vscodeserver?
|
vscodeserver Vscodeserver?
|
||||||
@@ -284,6 +300,7 @@ model Service {
|
|||||||
ghost Ghost?
|
ghost Ghost?
|
||||||
serviceSecret ServiceSecret[]
|
serviceSecret ServiceSecret[]
|
||||||
meiliSearch MeiliSearch?
|
meiliSearch MeiliSearch?
|
||||||
|
persistentStorage ServicePersistentStorage[]
|
||||||
}
|
}
|
||||||
|
|
||||||
model PlausibleAnalytics {
|
model PlausibleAnalytics {
|
||||||
@@ -332,6 +349,12 @@ model Wordpress {
|
|||||||
mysqlRootUserPassword String
|
mysqlRootUserPassword String
|
||||||
mysqlDatabase String?
|
mysqlDatabase String?
|
||||||
mysqlPublicPort Int?
|
mysqlPublicPort Int?
|
||||||
|
ftpEnabled Boolean @default(false)
|
||||||
|
ftpUser String?
|
||||||
|
ftpPassword String?
|
||||||
|
ftpPublicPort Int?
|
||||||
|
ftpHostKey String?
|
||||||
|
ftpHostKeyPrivate String?
|
||||||
serviceId String @unique
|
serviceId String @unique
|
||||||
service Service @relation(fields: [serviceId], references: [id])
|
service Service @relation(fields: [serviceId], references: [id])
|
||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
|
|||||||
12
src/app.d.ts
vendored
12
src/app.d.ts
vendored
@@ -6,7 +6,11 @@ declare namespace App {
|
|||||||
cookies: Record<string, string>;
|
cookies: Record<string, string>;
|
||||||
}
|
}
|
||||||
interface Platform {}
|
interface Platform {}
|
||||||
interface Session extends SessionData {}
|
interface Session extends SessionData {
|
||||||
|
whiteLabelDetails: {
|
||||||
|
icon: string | null;
|
||||||
|
};
|
||||||
|
}
|
||||||
interface Stuff {
|
interface Stuff {
|
||||||
service: any;
|
service: any;
|
||||||
application: any;
|
application: any;
|
||||||
@@ -15,18 +19,20 @@ declare namespace App {
|
|||||||
readOnly: boolean;
|
readOnly: boolean;
|
||||||
source: string;
|
source: string;
|
||||||
settings: string;
|
settings: string;
|
||||||
|
database: Record<string, any>;
|
||||||
|
versions: string;
|
||||||
|
privatePort: string;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
interface SessionData {
|
interface SessionData {
|
||||||
|
whiteLabeled: boolean;
|
||||||
version?: string;
|
version?: string;
|
||||||
userId?: string | null;
|
userId?: string | null;
|
||||||
teamId?: string | null;
|
teamId?: string | null;
|
||||||
permission?: string;
|
permission?: string;
|
||||||
isAdmin?: boolean;
|
isAdmin?: boolean;
|
||||||
expires?: string | null;
|
expires?: string | null;
|
||||||
gitlabToken?: string | null;
|
|
||||||
ghToken?: string | null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type DateTimeFormatOptions = {
|
type DateTimeFormatOptions = {
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<link rel="icon" href="/favicon.png" />
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
<title>Coolify</title>
|
<title>Coolify</title>
|
||||||
%svelte.head%
|
%svelte.head%
|
||||||
|
|||||||
@@ -7,6 +7,11 @@ import { version } from '$lib/common';
|
|||||||
import cookie from 'cookie';
|
import cookie from 'cookie';
|
||||||
import { dev } from '$app/env';
|
import { dev } from '$app/env';
|
||||||
|
|
||||||
|
const whiteLabeled = process.env['COOLIFY_WHITE_LABELED'] === 'true';
|
||||||
|
const whiteLabelDetails = {
|
||||||
|
icon: (whiteLabeled && process.env['COOLIFY_WHITE_LABELED_ICON']) || null
|
||||||
|
};
|
||||||
|
|
||||||
export const handle = handleSession(
|
export const handle = handleSession(
|
||||||
{
|
{
|
||||||
secret: process.env['COOLIFY_SECRET_KEY'],
|
secret: process.env['COOLIFY_SECRET_KEY'],
|
||||||
@@ -71,6 +76,8 @@ export const handle = handleSession(
|
|||||||
export const getSession: GetSession = function ({ locals }) {
|
export const getSession: GetSession = function ({ locals }) {
|
||||||
return {
|
return {
|
||||||
version,
|
version,
|
||||||
|
whiteLabeled,
|
||||||
|
whiteLabelDetails,
|
||||||
...locals.session.data
|
...locals.session.data
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,9 +1,15 @@
|
|||||||
async function send({ method, path, data = {}, headers, timeout = 30000 }) {
|
async function send({
|
||||||
|
method,
|
||||||
|
path,
|
||||||
|
data = {},
|
||||||
|
headers,
|
||||||
|
timeout = 120000
|
||||||
|
}): Promise<Record<string, unknown>> {
|
||||||
const controller = new AbortController();
|
const controller = new AbortController();
|
||||||
const id = setTimeout(() => controller.abort(), timeout);
|
const id = setTimeout(() => controller.abort(), timeout);
|
||||||
const opts = { method, headers: {}, body: null, signal: controller.signal };
|
const opts = { method, headers: {}, body: null, signal: controller.signal };
|
||||||
if (Object.keys(data).length > 0) {
|
if (Object.keys(data).length > 0) {
|
||||||
let parsedData = data;
|
const parsedData = data;
|
||||||
for (const [key, value] of Object.entries(data)) {
|
for (const [key, value] of Object.entries(data)) {
|
||||||
if (value === '') {
|
if (value === '') {
|
||||||
parsedData[key] = null;
|
parsedData[key] = null;
|
||||||
@@ -43,18 +49,33 @@ async function send({ method, path, data = {}, headers, timeout = 30000 }) {
|
|||||||
return responseData;
|
return responseData;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function get(path, headers = {}): Promise<any> {
|
export function get(
|
||||||
|
path: string,
|
||||||
|
headers?: Record<string, unknown>
|
||||||
|
): Promise<Record<string, unknown>> {
|
||||||
return send({ method: 'GET', path, headers });
|
return send({ method: 'GET', path, headers });
|
||||||
}
|
}
|
||||||
|
|
||||||
export function del(path, data = {}, headers = {}): Promise<any> {
|
export function del(
|
||||||
|
path: string,
|
||||||
|
data: Record<string, unknown>,
|
||||||
|
headers?: Record<string, unknown>
|
||||||
|
): Promise<Record<string, unknown>> {
|
||||||
return send({ method: 'DELETE', path, data, headers });
|
return send({ method: 'DELETE', path, data, headers });
|
||||||
}
|
}
|
||||||
|
|
||||||
export function post(path, data, headers = {}): Promise<any> {
|
export function post(
|
||||||
|
path: string,
|
||||||
|
data: Record<string, unknown>,
|
||||||
|
headers?: Record<string, unknown>
|
||||||
|
): Promise<Record<string, unknown>> {
|
||||||
return send({ method: 'POST', path, data, headers });
|
return send({ method: 'POST', path, data, headers });
|
||||||
}
|
}
|
||||||
|
|
||||||
export function put(path, data, headers = {}): Promise<any> {
|
export function put(
|
||||||
|
path: string,
|
||||||
|
data: Record<string, unknown>,
|
||||||
|
headers?: Record<string, unknown>
|
||||||
|
): Promise<Record<string, unknown>> {
|
||||||
return send({ method: 'PUT', path, data, headers });
|
return send({ method: 'PUT', path, data, headers });
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -91,7 +91,9 @@ export const setDefaultConfiguration = async (data) => {
|
|||||||
startCommand,
|
startCommand,
|
||||||
buildCommand,
|
buildCommand,
|
||||||
publishDirectory,
|
publishDirectory,
|
||||||
baseDirectory
|
baseDirectory,
|
||||||
|
dockerFileLocation,
|
||||||
|
denoMainFile
|
||||||
} = data;
|
} = data;
|
||||||
const template = scanningTemplates[buildPack];
|
const template = scanningTemplates[buildPack];
|
||||||
if (!port) {
|
if (!port) {
|
||||||
@@ -102,14 +104,25 @@ export const setDefaultConfiguration = async (data) => {
|
|||||||
else if (buildPack === 'php') port = 80;
|
else if (buildPack === 'php') port = 80;
|
||||||
else if (buildPack === 'python') port = 8000;
|
else if (buildPack === 'python') port = 8000;
|
||||||
}
|
}
|
||||||
if (!installCommand) installCommand = template?.installCommand || 'yarn install';
|
if (!installCommand && buildPack !== 'static')
|
||||||
if (!startCommand) startCommand = template?.startCommand || 'yarn start';
|
installCommand = template?.installCommand || 'yarn install';
|
||||||
if (!buildCommand) buildCommand = template?.buildCommand || null;
|
if (!startCommand && buildPack !== 'static')
|
||||||
|
startCommand = template?.startCommand || 'yarn start';
|
||||||
|
if (!buildCommand && buildPack !== 'static') buildCommand = template?.buildCommand || null;
|
||||||
if (!publishDirectory) publishDirectory = template?.publishDirectory || null;
|
if (!publishDirectory) publishDirectory = template?.publishDirectory || null;
|
||||||
if (baseDirectory) {
|
if (baseDirectory) {
|
||||||
if (!baseDirectory.startsWith('/')) baseDirectory = `/${baseDirectory}`;
|
if (!baseDirectory.startsWith('/')) baseDirectory = `/${baseDirectory}`;
|
||||||
if (!baseDirectory.endsWith('/')) baseDirectory = `${baseDirectory}/`;
|
if (!baseDirectory.endsWith('/')) baseDirectory = `${baseDirectory}/`;
|
||||||
}
|
}
|
||||||
|
if (dockerFileLocation) {
|
||||||
|
if (!dockerFileLocation.startsWith('/')) dockerFileLocation = `/${dockerFileLocation}`;
|
||||||
|
if (dockerFileLocation.endsWith('/')) dockerFileLocation = dockerFileLocation.slice(0, -1);
|
||||||
|
} else {
|
||||||
|
dockerFileLocation = '/Dockerfile';
|
||||||
|
}
|
||||||
|
if (!denoMainFile) {
|
||||||
|
denoMainFile = 'main.ts';
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
buildPack,
|
buildPack,
|
||||||
@@ -118,7 +131,9 @@ export const setDefaultConfiguration = async (data) => {
|
|||||||
startCommand,
|
startCommand,
|
||||||
buildCommand,
|
buildCommand,
|
||||||
publishDirectory,
|
publishDirectory,
|
||||||
baseDirectory
|
baseDirectory,
|
||||||
|
dockerFileLocation,
|
||||||
|
denoMainFile
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -184,7 +199,11 @@ export async function copyBaseConfigurationFiles(buildPack, workdir, buildId, ap
|
|||||||
}
|
}
|
||||||
`
|
`
|
||||||
);
|
);
|
||||||
await saveBuildLog({ line: 'Copied default configuration file.', buildId, applicationId });
|
await saveBuildLog({
|
||||||
|
line: 'Copied default configuration file for Nginx.',
|
||||||
|
buildId,
|
||||||
|
applicationId
|
||||||
|
});
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(error);
|
console.log(error);
|
||||||
|
|||||||
54
src/lib/buildPacks/deno.ts
Normal file
54
src/lib/buildPacks/deno.ts
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
import { buildImage } from '$lib/docker';
|
||||||
|
import { promises as fs } from 'fs';
|
||||||
|
|
||||||
|
const createDockerfile = async (data, image): Promise<void> => {
|
||||||
|
const { workdir, port, baseDirectory, secrets, pullmergeRequestId, denoMainFile, denoOptions } =
|
||||||
|
data;
|
||||||
|
const Dockerfile: Array<string> = [];
|
||||||
|
|
||||||
|
let depsFound = false;
|
||||||
|
try {
|
||||||
|
await fs.readFile(`${workdir}${baseDirectory || ''}/deps.ts`);
|
||||||
|
depsFound = true;
|
||||||
|
} catch (error) {}
|
||||||
|
|
||||||
|
Dockerfile.push(`FROM ${image}`);
|
||||||
|
Dockerfile.push('WORKDIR /app');
|
||||||
|
Dockerfile.push(`LABEL coolify.image=true`);
|
||||||
|
if (secrets.length > 0) {
|
||||||
|
secrets.forEach((secret) => {
|
||||||
|
if (secret.isBuildSecret) {
|
||||||
|
if (pullmergeRequestId) {
|
||||||
|
if (secret.isPRMRSecret) {
|
||||||
|
Dockerfile.push(`ARG ${secret.name}=${secret.value}`);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (!secret.isPRMRSecret) {
|
||||||
|
Dockerfile.push(`ARG ${secret.name}=${secret.value}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (depsFound) {
|
||||||
|
Dockerfile.push(`COPY .${baseDirectory || ''}/deps.ts /app`);
|
||||||
|
Dockerfile.push(`RUN deno cache deps.ts`);
|
||||||
|
}
|
||||||
|
Dockerfile.push(`COPY ${denoMainFile} /app`);
|
||||||
|
Dockerfile.push(`RUN deno cache ${denoMainFile}`);
|
||||||
|
Dockerfile.push(`COPY .${baseDirectory || ''} ./`);
|
||||||
|
Dockerfile.push(`ENV NO_COLOR true`);
|
||||||
|
Dockerfile.push(`EXPOSE ${port}`);
|
||||||
|
Dockerfile.push(`CMD deno run ${denoOptions ? denoOptions.split(' ') : ''} ${denoMainFile}`);
|
||||||
|
await fs.writeFile(`${workdir}/Dockerfile`, Dockerfile.join('\n'));
|
||||||
|
};
|
||||||
|
|
||||||
|
export default async function (data) {
|
||||||
|
try {
|
||||||
|
const image = 'denoland/deno:latest';
|
||||||
|
await createDockerfile(data, image);
|
||||||
|
await buildImage(data);
|
||||||
|
} catch (error) {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -10,15 +10,16 @@ export default async function ({
|
|||||||
buildId,
|
buildId,
|
||||||
baseDirectory,
|
baseDirectory,
|
||||||
secrets,
|
secrets,
|
||||||
pullmergeRequestId
|
pullmergeRequestId,
|
||||||
|
dockerFileLocation
|
||||||
}) {
|
}) {
|
||||||
try {
|
try {
|
||||||
let file = `${workdir}/Dockerfile`;
|
const file = `${workdir}${dockerFileLocation}`;
|
||||||
|
let dockerFileOut = `${workdir}`;
|
||||||
if (baseDirectory) {
|
if (baseDirectory) {
|
||||||
file = `${workdir}/${baseDirectory}/Dockerfile`;
|
dockerFileOut = `${workdir}${baseDirectory}`;
|
||||||
workdir = `${workdir}/${baseDirectory}`;
|
workdir = `${workdir}${baseDirectory}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
const Dockerfile: Array<string> = (await fs.readFile(`${file}`, 'utf8'))
|
const Dockerfile: Array<string> = (await fs.readFile(`${file}`, 'utf8'))
|
||||||
.toString()
|
.toString()
|
||||||
.trim()
|
.trim()
|
||||||
@@ -26,20 +27,23 @@ export default async function ({
|
|||||||
if (secrets.length > 0) {
|
if (secrets.length > 0) {
|
||||||
secrets.forEach((secret) => {
|
secrets.forEach((secret) => {
|
||||||
if (secret.isBuildSecret) {
|
if (secret.isBuildSecret) {
|
||||||
if (pullmergeRequestId) {
|
if (
|
||||||
if (secret.isPRMRSecret) {
|
(pullmergeRequestId && secret.isPRMRSecret) ||
|
||||||
Dockerfile.push(`ARG ${secret.name}=${secret.value}`);
|
(!pullmergeRequestId && !secret.isPRMRSecret)
|
||||||
}
|
) {
|
||||||
} else {
|
Dockerfile.unshift(`ARG ${secret.name}=${secret.value}`);
|
||||||
if (!secret.isPRMRSecret) {
|
|
||||||
Dockerfile.push(`ARG ${secret.name}=${secret.value}`);
|
Dockerfile.forEach((line, index) => {
|
||||||
}
|
if (line.startsWith('FROM')) {
|
||||||
|
Dockerfile.splice(index + 1, 0, `ARG ${secret.name}`);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
await fs.writeFile(`${file}`, Dockerfile.join('\n'));
|
await fs.writeFile(`${dockerFileOut}${dockerFileLocation}`, Dockerfile.join('\n'));
|
||||||
await buildImage({ applicationId, tag, workdir, docker, buildId, debug });
|
await buildImage({ applicationId, tag, workdir, docker, buildId, debug, dockerFileLocation });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import rust from './rust';
|
|||||||
import astro from './static';
|
import astro from './static';
|
||||||
import eleventy from './static';
|
import eleventy from './static';
|
||||||
import python from './python';
|
import python from './python';
|
||||||
|
import deno from './deno';
|
||||||
|
|
||||||
export {
|
export {
|
||||||
node,
|
node,
|
||||||
@@ -29,5 +30,6 @@ export {
|
|||||||
rust,
|
rust,
|
||||||
astro,
|
astro,
|
||||||
eleventy,
|
eleventy,
|
||||||
python
|
python,
|
||||||
|
deno
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -4,6 +4,12 @@ import { promises as fs } from 'fs';
|
|||||||
const createDockerfile = async (data, image, htaccessFound): Promise<void> => {
|
const createDockerfile = async (data, image, htaccessFound): Promise<void> => {
|
||||||
const { workdir, baseDirectory } = data;
|
const { workdir, baseDirectory } = data;
|
||||||
const Dockerfile: Array<string> = [];
|
const Dockerfile: Array<string> = [];
|
||||||
|
let composerFound = false;
|
||||||
|
try {
|
||||||
|
await fs.readFile(`${workdir}${baseDirectory || ''}/composer.json`);
|
||||||
|
composerFound = true;
|
||||||
|
} catch (error) {}
|
||||||
|
|
||||||
Dockerfile.push(`FROM ${image}`);
|
Dockerfile.push(`FROM ${image}`);
|
||||||
Dockerfile.push(`LABEL coolify.image=true`);
|
Dockerfile.push(`LABEL coolify.image=true`);
|
||||||
Dockerfile.push('WORKDIR /app');
|
Dockerfile.push('WORKDIR /app');
|
||||||
@@ -11,6 +17,10 @@ const createDockerfile = async (data, image, htaccessFound): Promise<void> => {
|
|||||||
if (htaccessFound) {
|
if (htaccessFound) {
|
||||||
Dockerfile.push(`COPY .${baseDirectory || ''}/.htaccess ./`);
|
Dockerfile.push(`COPY .${baseDirectory || ''}/.htaccess ./`);
|
||||||
}
|
}
|
||||||
|
if (composerFound) {
|
||||||
|
Dockerfile.push(`RUN composer install`);
|
||||||
|
}
|
||||||
|
|
||||||
Dockerfile.push(`COPY /entrypoint.sh /opt/docker/provision/entrypoint.d/30-entrypoint.sh`);
|
Dockerfile.push(`COPY /entrypoint.sh /opt/docker/provision/entrypoint.d/30-entrypoint.sh`);
|
||||||
Dockerfile.push(`EXPOSE 80`);
|
Dockerfile.push(`EXPOSE 80`);
|
||||||
await fs.writeFile(`${workdir}/Dockerfile`, Dockerfile.join('\n'));
|
await fs.writeFile(`${workdir}/Dockerfile`, Dockerfile.join('\n'));
|
||||||
@@ -21,12 +31,14 @@ export default async function (data) {
|
|||||||
try {
|
try {
|
||||||
let htaccessFound = false;
|
let htaccessFound = false;
|
||||||
try {
|
try {
|
||||||
const d = await fs.readFile(`${workdir}${baseDirectory || ''}/.htaccess`);
|
await fs.readFile(`${workdir}${baseDirectory || ''}/.htaccess`);
|
||||||
htaccessFound = true;
|
htaccessFound = true;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
//
|
//
|
||||||
}
|
}
|
||||||
const image = htaccessFound ? 'webdevops/php-apache' : 'webdevops/php-nginx';
|
const image = htaccessFound
|
||||||
|
? 'webdevops/php-apache:8.0-alpine'
|
||||||
|
: 'webdevops/php-nginx:8.0-alpine';
|
||||||
await createDockerfile(data, image, htaccessFound);
|
await createDockerfile(data, image, htaccessFound);
|
||||||
await buildImage(data);
|
await buildImage(data);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
@@ -12,7 +12,8 @@ import { version as currentVersion } from '../../package.json';
|
|||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
import Cookie from 'cookie';
|
import Cookie from 'cookie';
|
||||||
import os from 'os';
|
import os from 'os';
|
||||||
import cuid from 'cuid';
|
import type { RequestEvent } from '@sveltejs/kit/types/internal';
|
||||||
|
import type { Job } from 'bullmq';
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (!dev) {
|
if (!dev) {
|
||||||
@@ -45,30 +46,30 @@ const customConfig: Config = {
|
|||||||
|
|
||||||
export const version = currentVersion;
|
export const version = currentVersion;
|
||||||
export const asyncExecShell = util.promisify(child.exec);
|
export const asyncExecShell = util.promisify(child.exec);
|
||||||
export const asyncSleep = (delay) => new Promise((resolve) => setTimeout(resolve, delay));
|
export const asyncSleep = (delay: number): Promise<unknown> =>
|
||||||
|
new Promise((resolve) => setTimeout(resolve, delay));
|
||||||
export const sentry = Sentry;
|
export const sentry = Sentry;
|
||||||
|
|
||||||
export const uniqueName = () => uniqueNamesGenerator(customConfig);
|
export const uniqueName = (): string => uniqueNamesGenerator(customConfig);
|
||||||
|
|
||||||
export const saveBuildLog = async ({ line, buildId, applicationId }) => {
|
export const saveBuildLog = async ({
|
||||||
|
line,
|
||||||
|
buildId,
|
||||||
|
applicationId
|
||||||
|
}: {
|
||||||
|
line: string;
|
||||||
|
buildId: string;
|
||||||
|
applicationId: string;
|
||||||
|
}): Promise<Job> => {
|
||||||
|
if (line && typeof line === 'string' && line.includes('ghs_')) {
|
||||||
|
const regex = /ghs_.*@/g;
|
||||||
|
line = line.replace(regex, '<SENSITIVE_DATA_DELETED>@');
|
||||||
|
}
|
||||||
const addTimestamp = `${generateTimestamp()} ${line}`;
|
const addTimestamp = `${generateTimestamp()} ${line}`;
|
||||||
return await buildLogQueue.add(buildId, { buildId, line: addTimestamp, applicationId });
|
return await buildLogQueue.add(buildId, { buildId, line: addTimestamp, applicationId });
|
||||||
};
|
};
|
||||||
|
|
||||||
export const isTeamIdTokenAvailable = (request) => {
|
export const getTeam = (event: RequestEvent): string | null => {
|
||||||
const cookie = request.headers.cookie
|
|
||||||
?.split(';')
|
|
||||||
.map((s) => s.trim())
|
|
||||||
.find((s) => s.startsWith('teamId='))
|
|
||||||
?.split('=')[1];
|
|
||||||
if (!cookie) {
|
|
||||||
return getTeam(request);
|
|
||||||
} else {
|
|
||||||
return cookie;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export const getTeam = (event) => {
|
|
||||||
const cookies = Cookie.parse(event.request.headers.get('cookie'));
|
const cookies = Cookie.parse(event.request.headers.get('cookie'));
|
||||||
if (cookies?.teamId) {
|
if (cookies?.teamId) {
|
||||||
return cookies.teamId;
|
return cookies.teamId;
|
||||||
@@ -78,14 +79,28 @@ export const getTeam = (event) => {
|
|||||||
return null;
|
return null;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getUserDetails = async (event, isAdminRequired = true) => {
|
export const getUserDetails = async (
|
||||||
|
event: RequestEvent,
|
||||||
|
isAdminRequired = true
|
||||||
|
): Promise<{
|
||||||
|
teamId: string;
|
||||||
|
userId: string;
|
||||||
|
permission: string;
|
||||||
|
status: number;
|
||||||
|
body: { message: string };
|
||||||
|
}> => {
|
||||||
const teamId = getTeam(event);
|
const teamId = getTeam(event);
|
||||||
const userId = event?.locals?.session?.data?.userId || null;
|
const userId = event?.locals?.session?.data?.userId || null;
|
||||||
const { permission = 'read' } = await db.prisma.permission.findFirst({
|
let permission = 'read';
|
||||||
where: { teamId, userId },
|
if (teamId && userId) {
|
||||||
select: { permission: true },
|
const data = await db.prisma.permission.findFirst({
|
||||||
rejectOnNotFound: true
|
where: { teamId, userId },
|
||||||
});
|
select: { permission: true },
|
||||||
|
rejectOnNotFound: true
|
||||||
|
});
|
||||||
|
if (data.permission) permission = data.permission;
|
||||||
|
}
|
||||||
|
|
||||||
const payload = {
|
const payload = {
|
||||||
teamId,
|
teamId,
|
||||||
userId,
|
userId,
|
||||||
@@ -95,6 +110,7 @@ export const getUserDetails = async (event, isAdminRequired = true) => {
|
|||||||
message: 'OK'
|
message: 'OK'
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if (isAdminRequired && permission !== 'admin' && permission !== 'owner') {
|
if (isAdminRequired && permission !== 'admin' && permission !== 'owner') {
|
||||||
payload.status = 401;
|
payload.status = 401;
|
||||||
payload.body.message =
|
payload.body.message =
|
||||||
@@ -104,11 +120,11 @@ export const getUserDetails = async (event, isAdminRequired = true) => {
|
|||||||
return payload;
|
return payload;
|
||||||
};
|
};
|
||||||
|
|
||||||
export function getEngine(engine) {
|
export function getEngine(engine: string): string {
|
||||||
return engine === '/var/run/docker.sock' ? 'unix:///var/run/docker.sock' : engine;
|
return engine === '/var/run/docker.sock' ? 'unix:///var/run/docker.sock' : engine;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function removeContainer(id, engine) {
|
export async function removeContainer(id: string, engine: string): Promise<void> {
|
||||||
const host = getEngine(engine);
|
const host = getEngine(engine);
|
||||||
try {
|
try {
|
||||||
const { stdout } = await asyncExecShell(
|
const { stdout } = await asyncExecShell(
|
||||||
@@ -124,11 +140,23 @@ export async function removeContainer(id, engine) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const removeDestinationDocker = async ({ id, engine }) => {
|
export const removeDestinationDocker = async ({
|
||||||
|
id,
|
||||||
|
engine
|
||||||
|
}: {
|
||||||
|
id: string;
|
||||||
|
engine: string;
|
||||||
|
}): Promise<void> => {
|
||||||
return await removeContainer(id, engine);
|
return await removeContainer(id, engine);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const createDirectories = async ({ repository, buildId }) => {
|
export const createDirectories = async ({
|
||||||
|
repository,
|
||||||
|
buildId
|
||||||
|
}: {
|
||||||
|
repository: string;
|
||||||
|
buildId: string;
|
||||||
|
}): Promise<{ workdir: string; repodir: string }> => {
|
||||||
const repodir = `/tmp/build-sources/${repository}/`;
|
const repodir = `/tmp/build-sources/${repository}/`;
|
||||||
const workdir = `/tmp/build-sources/${repository}/${buildId}`;
|
const workdir = `/tmp/build-sources/${repository}/${buildId}`;
|
||||||
|
|
||||||
@@ -140,20 +168,10 @@ export const createDirectories = async ({ repository, buildId }) => {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export function generateTimestamp() {
|
export function generateTimestamp(): string {
|
||||||
return `${dayjs().format('HH:mm:ss.SSS')} `;
|
return `${dayjs().format('HH:mm:ss.SSS')} `;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getDomain(domain) {
|
export function getDomain(domain: string): string {
|
||||||
return domain?.replace('https://', '').replace('http://', '');
|
return domain?.replace('https://', '').replace('http://', '');
|
||||||
}
|
}
|
||||||
|
|
||||||
export function dashify(str: string, options?: any): string {
|
|
||||||
if (typeof str !== 'string') return str;
|
|
||||||
return str
|
|
||||||
.trim()
|
|
||||||
.replace(/\W/g, (m) => (/[À-ž]/.test(m) ? m : '-'))
|
|
||||||
.replace(/^-+|-+$/g, '')
|
|
||||||
.replace(/-{2,}/g, (m) => (options && options.condense ? '-' : m))
|
|
||||||
.toLowerCase();
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
export let isCenter = true;
|
export let isCenter = true;
|
||||||
export let disabled = false;
|
export let disabled = false;
|
||||||
export let dataTooltip = null;
|
export let dataTooltip = null;
|
||||||
|
export let loading = false;
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="flex items-center py-4 pr-8">
|
<div class="flex items-center py-4 pr-8">
|
||||||
@@ -26,9 +27,10 @@
|
|||||||
on:click
|
on:click
|
||||||
aria-pressed="false"
|
aria-pressed="false"
|
||||||
class="relative mx-20 inline-flex h-6 w-11 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out"
|
class="relative mx-20 inline-flex h-6 w-11 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out"
|
||||||
class:opacity-50={disabled}
|
class:opacity-50={disabled || loading}
|
||||||
class:bg-green-600={setting}
|
class:bg-green-600={!loading && setting}
|
||||||
class:bg-stone-700={!setting}
|
class:bg-stone-700={!loading && !setting}
|
||||||
|
class:bg-yellow-500={loading}
|
||||||
>
|
>
|
||||||
<span class="sr-only">Use setting</span>
|
<span class="sr-only">Use setting</span>
|
||||||
<span
|
<span
|
||||||
@@ -40,6 +42,7 @@
|
|||||||
class=" absolute inset-0 flex h-full w-full items-center justify-center transition-opacity duration-200 ease-in"
|
class=" absolute inset-0 flex h-full w-full items-center justify-center transition-opacity duration-200 ease-in"
|
||||||
class:opacity-0={setting}
|
class:opacity-0={setting}
|
||||||
class:opacity-100={!setting}
|
class:opacity-100={!setting}
|
||||||
|
class:animate-spin={loading}
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
>
|
>
|
||||||
<svg class="h-3 w-3 bg-white text-red-600" fill="none" viewBox="0 0 12 12">
|
<svg class="h-3 w-3 bg-white text-red-600" fill="none" viewBox="0 0 12 12">
|
||||||
@@ -57,6 +60,7 @@
|
|||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
class:opacity-100={setting}
|
class:opacity-100={setting}
|
||||||
class:opacity-0={!setting}
|
class:opacity-0={!setting}
|
||||||
|
class:animate-spin={loading}
|
||||||
>
|
>
|
||||||
<svg class="h-3 w-3 bg-white text-green-600" fill="currentColor" viewBox="0 0 12 12">
|
<svg class="h-3 w-3 bg-white text-green-600" fill="currentColor" viewBox="0 0 12 12">
|
||||||
<path
|
<path
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ export const staticDeployments = [
|
|||||||
'astro',
|
'astro',
|
||||||
'eleventy'
|
'eleventy'
|
||||||
];
|
];
|
||||||
export const notNodeDeployments = ['php', 'docker', 'rust', 'python'];
|
export const notNodeDeployments = ['php', 'docker', 'rust', 'python', 'deno'];
|
||||||
|
|
||||||
export function getDomain(domain) {
|
export function getDomain(domain) {
|
||||||
return domain?.replace('https://', '').replace('http://', '');
|
return domain?.replace('https://', '').replace('http://', '');
|
||||||
@@ -43,3 +43,142 @@ export function changeQueryParams(buildId) {
|
|||||||
queryParams.set('buildId', buildId);
|
queryParams.set('buildId', buildId);
|
||||||
return history.pushState(null, null, '?' + queryParams.toString());
|
return history.pushState(null, null, '?' + queryParams.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const supportedDatabaseTypesAndVersions = [
|
||||||
|
{
|
||||||
|
name: 'mongodb',
|
||||||
|
fancyName: 'MongoDB',
|
||||||
|
baseImage: 'bitnami/mongodb',
|
||||||
|
versions: ['5.0', '4.4', '4.2']
|
||||||
|
},
|
||||||
|
{ name: 'mysql', fancyName: 'MySQL', baseImage: 'bitnami/mysql', versions: ['8.0', '5.7'] },
|
||||||
|
{
|
||||||
|
name: 'postgresql',
|
||||||
|
fancyName: 'PostgreSQL',
|
||||||
|
baseImage: 'bitnami/postgresql',
|
||||||
|
versions: ['14.2.0', '13.6.0', '12.10.0 ', '11.15.0', '10.20.0']
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'redis',
|
||||||
|
fancyName: 'Redis',
|
||||||
|
baseImage: 'bitnami/redis',
|
||||||
|
versions: ['6.2', '6.0', '5.0']
|
||||||
|
},
|
||||||
|
{ name: 'couchdb', fancyName: 'CouchDB', baseImage: 'bitnami/couchdb', versions: ['3.2.1'] }
|
||||||
|
];
|
||||||
|
export const supportedServiceTypesAndVersions = [
|
||||||
|
{
|
||||||
|
name: 'plausibleanalytics',
|
||||||
|
fancyName: 'Plausible Analytics',
|
||||||
|
baseImage: 'plausible/analytics',
|
||||||
|
images: ['bitnami/postgresql:13.2.0', 'yandex/clickhouse-server:21.3.2.5'],
|
||||||
|
versions: ['latest', 'stable'],
|
||||||
|
recommendedVersion: 'stable',
|
||||||
|
ports: {
|
||||||
|
main: 8000
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'nocodb',
|
||||||
|
fancyName: 'NocoDB',
|
||||||
|
baseImage: 'nocodb/nocodb',
|
||||||
|
versions: ['latest'],
|
||||||
|
recommendedVersion: 'latest',
|
||||||
|
ports: {
|
||||||
|
main: 8080
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'minio',
|
||||||
|
fancyName: 'MinIO',
|
||||||
|
baseImage: 'minio/minio',
|
||||||
|
versions: ['latest'],
|
||||||
|
recommendedVersion: 'latest',
|
||||||
|
ports: {
|
||||||
|
main: 9001
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'vscodeserver',
|
||||||
|
fancyName: 'VSCode Server',
|
||||||
|
baseImage: 'codercom/code-server',
|
||||||
|
versions: ['latest'],
|
||||||
|
recommendedVersion: 'latest',
|
||||||
|
ports: {
|
||||||
|
main: 8080
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'wordpress',
|
||||||
|
fancyName: 'Wordpress',
|
||||||
|
baseImage: 'wordpress',
|
||||||
|
images: ['bitnami/mysql:5.7'],
|
||||||
|
versions: ['latest', 'php8.1', 'php8.0', 'php7.4', 'php7.3'],
|
||||||
|
recommendedVersion: 'latest',
|
||||||
|
ports: {
|
||||||
|
main: 80
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'vaultwarden',
|
||||||
|
fancyName: 'Vaultwarden',
|
||||||
|
baseImage: 'vaultwarden/server',
|
||||||
|
versions: ['latest'],
|
||||||
|
recommendedVersion: 'latest',
|
||||||
|
ports: {
|
||||||
|
main: 80
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'languagetool',
|
||||||
|
fancyName: 'LanguageTool',
|
||||||
|
baseImage: 'silviof/docker-languagetool',
|
||||||
|
versions: ['latest'],
|
||||||
|
recommendedVersion: 'latest',
|
||||||
|
ports: {
|
||||||
|
main: 8010
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'n8n',
|
||||||
|
fancyName: 'n8n',
|
||||||
|
baseImage: 'n8nio/n8n',
|
||||||
|
versions: ['latest'],
|
||||||
|
recommendedVersion: 'latest',
|
||||||
|
ports: {
|
||||||
|
main: 5678
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'uptimekuma',
|
||||||
|
fancyName: 'Uptime Kuma',
|
||||||
|
baseImage: 'louislam/uptime-kuma',
|
||||||
|
versions: ['latest'],
|
||||||
|
recommendedVersion: 'latest',
|
||||||
|
ports: {
|
||||||
|
main: 3001
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'ghost',
|
||||||
|
fancyName: 'Ghost',
|
||||||
|
baseImage: 'bitnami/ghost',
|
||||||
|
images: ['bitnami/mariadb'],
|
||||||
|
versions: ['latest'],
|
||||||
|
recommendedVersion: 'latest',
|
||||||
|
ports: {
|
||||||
|
main: 2368
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'meilisearch',
|
||||||
|
fancyName: 'Meilisearch',
|
||||||
|
baseImage: 'getmeili/meilisearch',
|
||||||
|
images: [],
|
||||||
|
versions: ['latest'],
|
||||||
|
recommendedVersion: 'latest',
|
||||||
|
ports: {
|
||||||
|
main: 7700
|
||||||
|
}
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|||||||
24
src/lib/components/svg/applications/Deno.svelte
Normal file
24
src/lib/components/svg/applications/Deno.svelte
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 6.1 KiB |
@@ -153,6 +153,16 @@ export function findBuildPack(pack, packageManager = 'npm') {
|
|||||||
port: 8000
|
port: 8000
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
if (pack === 'deno') {
|
||||||
|
return {
|
||||||
|
...metaData,
|
||||||
|
installCommand: null,
|
||||||
|
buildCommand: null,
|
||||||
|
startCommand: null,
|
||||||
|
publishDirectory: null,
|
||||||
|
port: 8000
|
||||||
|
};
|
||||||
|
}
|
||||||
return {
|
return {
|
||||||
name: 'node',
|
name: 'node',
|
||||||
fancyName: 'Node.js',
|
fancyName: 'Node.js',
|
||||||
@@ -262,6 +272,12 @@ export const buildPacks = [
|
|||||||
fancyName: 'Python',
|
fancyName: 'Python',
|
||||||
hoverColor: 'hover:bg-green-700',
|
hoverColor: 'hover:bg-green-700',
|
||||||
color: 'bg-green-700'
|
color: 'bg-green-700'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'deno',
|
||||||
|
fancyName: 'Deno',
|
||||||
|
hoverColor: 'hover:bg-green-700',
|
||||||
|
color: 'bg-green-700'
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
export const scanningTemplates = {
|
export const scanningTemplates = {
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
import crypto from 'crypto';
|
import crypto from 'crypto';
|
||||||
const algorithm = 'aes-256-ctr';
|
const algorithm = 'aes-256-ctr';
|
||||||
|
|
||||||
export const base64Encode = (text: string) => {
|
export const base64Encode = (text: string): string => {
|
||||||
return Buffer.from(text).toString('base64');
|
return Buffer.from(text).toString('base64');
|
||||||
};
|
};
|
||||||
export const base64Decode = (text: string) => {
|
export const base64Decode = (text: string): string => {
|
||||||
return Buffer.from(text, 'base64').toString('ascii');
|
return Buffer.from(text, 'base64').toString('ascii');
|
||||||
};
|
};
|
||||||
export const encrypt = (text: string) => {
|
export const encrypt = (text: string): string => {
|
||||||
if (text) {
|
if (text) {
|
||||||
const iv = crypto.randomBytes(16);
|
const iv = crypto.randomBytes(16);
|
||||||
const cipher = crypto.createCipheriv(algorithm, process.env['COOLIFY_SECRET_KEY'], iv);
|
const cipher = crypto.createCipheriv(algorithm, process.env['COOLIFY_SECRET_KEY'], iv);
|
||||||
@@ -19,7 +19,7 @@ export const encrypt = (text: string) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const decrypt = (hashString: string) => {
|
export const decrypt = (hashString: string): string => {
|
||||||
if (hashString) {
|
if (hashString) {
|
||||||
const hash: Hash = JSON.parse(hashString);
|
const hash: Hash = JSON.parse(hashString);
|
||||||
const decipher = crypto.createDecipheriv(
|
const decipher = crypto.createDecipheriv(
|
||||||
|
|||||||
@@ -1,14 +1,35 @@
|
|||||||
import { decrypt, encrypt } from '$lib/crypto';
|
import { decrypt, encrypt } from '$lib/crypto';
|
||||||
import { asyncExecShell, getEngine } from '$lib/common';
|
import { asyncExecShell, getEngine } from '$lib/common';
|
||||||
|
|
||||||
import { getDomain, removeDestinationDocker } from '$lib/common';
|
import { removeDestinationDocker } from '$lib/common';
|
||||||
import { prisma } from './common';
|
import { prisma } from './common';
|
||||||
|
|
||||||
export async function listApplications(teamId) {
|
import type {
|
||||||
return await prisma.application.findMany({ where: { teams: { some: { id: teamId } } } });
|
DestinationDocker,
|
||||||
|
GitSource,
|
||||||
|
Secret,
|
||||||
|
ApplicationSettings,
|
||||||
|
Application,
|
||||||
|
ApplicationPersistentStorage
|
||||||
|
} from '@prisma/client';
|
||||||
|
|
||||||
|
export async function listApplications(teamId: string): Promise<Application[]> {
|
||||||
|
if (teamId === '0') {
|
||||||
|
return await prisma.application.findMany({ include: { teams: true } });
|
||||||
|
}
|
||||||
|
return await prisma.application.findMany({
|
||||||
|
where: { teams: { some: { id: teamId } } },
|
||||||
|
include: { teams: true }
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function newApplication({ name, teamId }) {
|
export async function newApplication({
|
||||||
|
name,
|
||||||
|
teamId
|
||||||
|
}: {
|
||||||
|
name: string;
|
||||||
|
teamId: string;
|
||||||
|
}): Promise<Application> {
|
||||||
return await prisma.application.create({
|
return await prisma.application.create({
|
||||||
data: {
|
data: {
|
||||||
name,
|
name,
|
||||||
@@ -18,34 +39,17 @@ export async function newApplication({ name, teamId }) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function importApplication({
|
export async function removeApplication({
|
||||||
name,
|
id,
|
||||||
teamId,
|
teamId
|
||||||
fqdn,
|
}: {
|
||||||
port,
|
id: string;
|
||||||
buildCommand,
|
teamId: string;
|
||||||
startCommand,
|
}): Promise<void> {
|
||||||
installCommand
|
const { destinationDockerId, destinationDocker } = await prisma.application.findUnique({
|
||||||
}) {
|
|
||||||
return await prisma.application.create({
|
|
||||||
data: {
|
|
||||||
name,
|
|
||||||
fqdn,
|
|
||||||
port,
|
|
||||||
buildCommand,
|
|
||||||
startCommand,
|
|
||||||
installCommand,
|
|
||||||
teams: { connect: { id: teamId } }
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function removeApplication({ id, teamId }) {
|
|
||||||
const { fqdn, destinationDockerId, destinationDocker } = await prisma.application.findUnique({
|
|
||||||
where: { id },
|
where: { id },
|
||||||
include: { destinationDocker: true }
|
include: { destinationDocker: true }
|
||||||
});
|
});
|
||||||
const domain = getDomain(fqdn);
|
|
||||||
if (destinationDockerId) {
|
if (destinationDockerId) {
|
||||||
const host = getEngine(destinationDocker.engine);
|
const host = getEngine(destinationDocker.engine);
|
||||||
const { stdout: containers } = await asyncExecShell(
|
const { stdout: containers } = await asyncExecShell(
|
||||||
@@ -56,7 +60,6 @@ export async function removeApplication({ id, teamId }) {
|
|||||||
for (const container of containersArray) {
|
for (const container of containersArray) {
|
||||||
const containerObj = JSON.parse(container);
|
const containerObj = JSON.parse(container);
|
||||||
const id = containerObj.ID;
|
const id = containerObj.ID;
|
||||||
const preview = containerObj.Image.split('-')[1];
|
|
||||||
await removeDestinationDocker({ id, engine: destinationDocker.engine });
|
await removeDestinationDocker({ id, engine: destinationDocker.engine });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -67,12 +70,30 @@ export async function removeApplication({ id, teamId }) {
|
|||||||
await prisma.build.deleteMany({ where: { applicationId: id } });
|
await prisma.build.deleteMany({ where: { applicationId: id } });
|
||||||
await prisma.secret.deleteMany({ where: { applicationId: id } });
|
await prisma.secret.deleteMany({ where: { applicationId: id } });
|
||||||
await prisma.applicationPersistentStorage.deleteMany({ where: { applicationId: id } });
|
await prisma.applicationPersistentStorage.deleteMany({ where: { applicationId: id } });
|
||||||
await prisma.application.deleteMany({ where: { id, teams: { some: { id: teamId } } } });
|
if (teamId === '0') {
|
||||||
|
await prisma.application.deleteMany({ where: { id } });
|
||||||
|
} else {
|
||||||
|
await prisma.application.deleteMany({ where: { id, teams: { some: { id: teamId } } } });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getApplicationWebhook({ projectId, branch }) {
|
export async function getApplicationWebhook({
|
||||||
|
projectId,
|
||||||
|
branch
|
||||||
|
}: {
|
||||||
|
projectId: number;
|
||||||
|
branch: string;
|
||||||
|
}): Promise<
|
||||||
|
Application & {
|
||||||
|
destinationDocker: DestinationDocker;
|
||||||
|
settings: ApplicationSettings;
|
||||||
|
gitSource: GitSource;
|
||||||
|
secrets: Secret[];
|
||||||
|
persistentStorage: ApplicationPersistentStorage[];
|
||||||
|
}
|
||||||
|
> {
|
||||||
try {
|
try {
|
||||||
let application = await prisma.application.findFirst({
|
const application = await prisma.application.findFirst({
|
||||||
where: { projectId, branch, settings: { autodeploy: true } },
|
where: { projectId, branch, settings: { autodeploy: true } },
|
||||||
include: {
|
include: {
|
||||||
destinationDocker: true,
|
destinationDocker: true,
|
||||||
@@ -121,25 +142,40 @@ export async function getApplicationWebhook({ projectId, branch }) {
|
|||||||
throw { status: 404, body: { message: e.message } };
|
throw { status: 404, body: { message: e.message } };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
export async function getApplicationById({ id }) {
|
|
||||||
const body = await prisma.application.findFirst({
|
|
||||||
where: { id },
|
|
||||||
include: { destinationDocker: true }
|
|
||||||
});
|
|
||||||
|
|
||||||
return { ...body };
|
export async function getApplication({ id, teamId }: { id: string; teamId: string }): Promise<
|
||||||
}
|
Application & {
|
||||||
export async function getApplication({ id, teamId }) {
|
destinationDocker: DestinationDocker;
|
||||||
let body = await prisma.application.findFirst({
|
settings: ApplicationSettings;
|
||||||
where: { id, teams: { some: { id: teamId } } },
|
gitSource: GitSource;
|
||||||
include: {
|
secrets: Secret[];
|
||||||
destinationDocker: true,
|
persistentStorage: ApplicationPersistentStorage[];
|
||||||
settings: true,
|
}
|
||||||
gitSource: { include: { githubApp: true, gitlabApp: true } },
|
> {
|
||||||
secrets: true,
|
let body;
|
||||||
persistentStorage: true
|
if (teamId === '0') {
|
||||||
}
|
body = await prisma.application.findFirst({
|
||||||
});
|
where: { id },
|
||||||
|
include: {
|
||||||
|
destinationDocker: true,
|
||||||
|
settings: true,
|
||||||
|
gitSource: { include: { githubApp: true, gitlabApp: true } },
|
||||||
|
secrets: true,
|
||||||
|
persistentStorage: true
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
body = await prisma.application.findFirst({
|
||||||
|
where: { id, teams: { some: { id: teamId } } },
|
||||||
|
include: {
|
||||||
|
destinationDocker: true,
|
||||||
|
settings: true,
|
||||||
|
gitSource: { include: { githubApp: true, gitlabApp: true } },
|
||||||
|
secrets: true,
|
||||||
|
persistentStorage: true
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
if (body?.gitSource?.githubApp?.clientSecret) {
|
if (body?.gitSource?.githubApp?.clientSecret) {
|
||||||
body.gitSource.githubApp.clientSecret = decrypt(body.gitSource.githubApp.clientSecret);
|
body.gitSource.githubApp.clientSecret = decrypt(body.gitSource.githubApp.clientSecret);
|
||||||
@@ -170,7 +206,14 @@ export async function configureGitRepository({
|
|||||||
projectId,
|
projectId,
|
||||||
webhookToken,
|
webhookToken,
|
||||||
autodeploy
|
autodeploy
|
||||||
}) {
|
}: {
|
||||||
|
id: string;
|
||||||
|
repository: string;
|
||||||
|
branch: string;
|
||||||
|
projectId: number;
|
||||||
|
webhookToken: string;
|
||||||
|
autodeploy: boolean;
|
||||||
|
}): Promise<void> {
|
||||||
if (webhookToken) {
|
if (webhookToken) {
|
||||||
const encryptedWebhookToken = encrypt(webhookToken);
|
const encryptedWebhookToken = encrypt(webhookToken);
|
||||||
await prisma.application.update({
|
await prisma.application.update({
|
||||||
@@ -200,7 +243,10 @@ export async function configureGitRepository({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function configureBuildPack({ id, buildPack }) {
|
export async function configureBuildPack({
|
||||||
|
id,
|
||||||
|
buildPack
|
||||||
|
}: Pick<Application, 'id' | 'buildPack'>): Promise<Application> {
|
||||||
return await prisma.application.update({ where: { id }, data: { buildPack } });
|
return await prisma.application.update({ where: { id }, data: { buildPack } });
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -217,8 +263,28 @@ export async function configureApplication({
|
|||||||
publishDirectory,
|
publishDirectory,
|
||||||
pythonWSGI,
|
pythonWSGI,
|
||||||
pythonModule,
|
pythonModule,
|
||||||
pythonVariable
|
pythonVariable,
|
||||||
}) {
|
dockerFileLocation,
|
||||||
|
denoMainFile,
|
||||||
|
denoOptions
|
||||||
|
}: {
|
||||||
|
id: string;
|
||||||
|
buildPack: string;
|
||||||
|
name: string;
|
||||||
|
fqdn: string;
|
||||||
|
port: number;
|
||||||
|
installCommand: string;
|
||||||
|
buildCommand: string;
|
||||||
|
startCommand: string;
|
||||||
|
baseDirectory: string;
|
||||||
|
publishDirectory: string;
|
||||||
|
pythonWSGI: string;
|
||||||
|
pythonModule: string;
|
||||||
|
pythonVariable: string;
|
||||||
|
dockerFileLocation: string;
|
||||||
|
denoMainFile: string;
|
||||||
|
denoOptions: string;
|
||||||
|
}): Promise<Application> {
|
||||||
return await prisma.application.update({
|
return await prisma.application.update({
|
||||||
where: { id },
|
where: { id },
|
||||||
data: {
|
data: {
|
||||||
@@ -233,16 +299,32 @@ export async function configureApplication({
|
|||||||
publishDirectory,
|
publishDirectory,
|
||||||
pythonWSGI,
|
pythonWSGI,
|
||||||
pythonModule,
|
pythonModule,
|
||||||
pythonVariable
|
pythonVariable,
|
||||||
|
dockerFileLocation,
|
||||||
|
denoMainFile,
|
||||||
|
denoOptions
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function checkDoubleBranch(branch, projectId) {
|
export async function checkDoubleBranch(branch: string, projectId: number): Promise<boolean> {
|
||||||
const applications = await prisma.application.findMany({ where: { branch, projectId } });
|
const applications = await prisma.application.findMany({ where: { branch, projectId } });
|
||||||
return applications.length > 1;
|
return applications.length > 1;
|
||||||
}
|
}
|
||||||
export async function setApplicationSettings({ id, debug, previews, dualCerts, autodeploy }) {
|
|
||||||
|
export async function setApplicationSettings({
|
||||||
|
id,
|
||||||
|
debug,
|
||||||
|
previews,
|
||||||
|
dualCerts,
|
||||||
|
autodeploy
|
||||||
|
}: {
|
||||||
|
id: string;
|
||||||
|
debug: boolean;
|
||||||
|
previews: boolean;
|
||||||
|
dualCerts: boolean;
|
||||||
|
autodeploy: boolean;
|
||||||
|
}): Promise<Application & { destinationDocker: DestinationDocker }> {
|
||||||
return await prisma.application.update({
|
return await prisma.application.update({
|
||||||
where: { id },
|
where: { id },
|
||||||
data: { settings: { update: { debug, previews, dualCerts, autodeploy } } },
|
data: { settings: { update: { debug, previews, dualCerts, autodeploy } } },
|
||||||
@@ -250,29 +332,6 @@ export async function setApplicationSettings({ id, debug, previews, dualCerts, a
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function createBuild({
|
export async function getPersistentStorage(id: string): Promise<ApplicationPersistentStorage[]> {
|
||||||
id,
|
|
||||||
applicationId,
|
|
||||||
destinationDockerId,
|
|
||||||
gitSourceId,
|
|
||||||
githubAppId,
|
|
||||||
gitlabAppId,
|
|
||||||
type
|
|
||||||
}) {
|
|
||||||
return await prisma.build.create({
|
|
||||||
data: {
|
|
||||||
id,
|
|
||||||
applicationId,
|
|
||||||
destinationDockerId,
|
|
||||||
gitSourceId,
|
|
||||||
githubAppId,
|
|
||||||
gitlabAppId,
|
|
||||||
status: 'running',
|
|
||||||
type
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function getPersistentStorage(id) {
|
|
||||||
return await prisma.applicationPersistentStorage.findMany({ where: { applicationId: id } });
|
return await prisma.applicationPersistentStorage.findMany({ where: { applicationId: id } });
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,16 @@
|
|||||||
import { getDomain } from '$lib/common';
|
import { getDomain } from '$lib/common';
|
||||||
import { prisma } from './common';
|
import { prisma } from './common';
|
||||||
|
import type { Application, ServiceSecret, DestinationDocker, Secret } from '@prisma/client';
|
||||||
|
|
||||||
export async function isBranchAlreadyUsed({ repository, branch, id }) {
|
export async function isBranchAlreadyUsed({
|
||||||
|
repository,
|
||||||
|
branch,
|
||||||
|
id
|
||||||
|
}: {
|
||||||
|
id: string;
|
||||||
|
repository: string;
|
||||||
|
branch: string;
|
||||||
|
}): Promise<Application> {
|
||||||
const application = await prisma.application.findUnique({
|
const application = await prisma.application.findUnique({
|
||||||
where: { id },
|
where: { id },
|
||||||
include: { gitSource: true }
|
include: { gitSource: true }
|
||||||
@@ -11,18 +20,42 @@ export async function isBranchAlreadyUsed({ repository, branch, id }) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function isDockerNetworkExists({ network }) {
|
export async function isDockerNetworkExists({
|
||||||
|
network
|
||||||
|
}: {
|
||||||
|
network: string;
|
||||||
|
}): Promise<DestinationDocker> {
|
||||||
return await prisma.destinationDocker.findFirst({ where: { network } });
|
return await prisma.destinationDocker.findFirst({ where: { network } });
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function isServiceSecretExists({ id, name }) {
|
export async function isServiceSecretExists({
|
||||||
|
id,
|
||||||
|
name
|
||||||
|
}: {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
}): Promise<ServiceSecret> {
|
||||||
return await prisma.serviceSecret.findFirst({ where: { name, serviceId: id } });
|
return await prisma.serviceSecret.findFirst({ where: { name, serviceId: id } });
|
||||||
}
|
}
|
||||||
export async function isSecretExists({ id, name, isPRMRSecret }) {
|
export async function isSecretExists({
|
||||||
|
id,
|
||||||
|
name,
|
||||||
|
isPRMRSecret
|
||||||
|
}: {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
isPRMRSecret: boolean;
|
||||||
|
}): Promise<Secret> {
|
||||||
return await prisma.secret.findFirst({ where: { name, applicationId: id, isPRMRSecret } });
|
return await prisma.secret.findFirst({ where: { name, applicationId: id, isPRMRSecret } });
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function isDomainConfigured({ id, fqdn }) {
|
export async function isDomainConfigured({
|
||||||
|
id,
|
||||||
|
fqdn
|
||||||
|
}: {
|
||||||
|
id: string;
|
||||||
|
fqdn: string;
|
||||||
|
}): Promise<boolean> {
|
||||||
const domain = getDomain(fqdn);
|
const domain = getDomain(fqdn);
|
||||||
const nakedDomain = domain.replace('www.', '');
|
const nakedDomain = domain.replace('www.', '');
|
||||||
const foundApp = await prisma.application.findFirst({
|
const foundApp = await prisma.application.findFirst({
|
||||||
@@ -55,6 +88,5 @@ export async function isDomainConfigured({ id, fqdn }) {
|
|||||||
},
|
},
|
||||||
select: { fqdn: true }
|
select: { fqdn: true }
|
||||||
});
|
});
|
||||||
if (foundApp || foundService || coolifyFqdn) return true;
|
return !!(foundApp || foundService || coolifyFqdn);
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,17 @@
|
|||||||
import { dev } from '$app/env';
|
import { dev } from '$app/env';
|
||||||
import { sentry } from '$lib/common';
|
import { sentry } from '$lib/common';
|
||||||
|
import {
|
||||||
|
supportedDatabaseTypesAndVersions,
|
||||||
|
supportedServiceTypesAndVersions
|
||||||
|
} from '$lib/components/common';
|
||||||
import * as Prisma from '@prisma/client';
|
import * as Prisma from '@prisma/client';
|
||||||
import { default as ProdPrisma } from '@prisma/client';
|
import { default as ProdPrisma } from '@prisma/client';
|
||||||
import type { PrismaClientOptions } from '@prisma/client/runtime';
|
import type { Database, DatabaseSettings } from '@prisma/client';
|
||||||
import generator from 'generate-password';
|
import generator from 'generate-password';
|
||||||
import forge from 'node-forge';
|
import forge from 'node-forge';
|
||||||
|
import getPort, { portNumbers } from 'get-port';
|
||||||
|
|
||||||
export function generatePassword(length = 24) {
|
export function generatePassword(length = 24): string {
|
||||||
return generator.generate({
|
return generator.generate({
|
||||||
length,
|
length,
|
||||||
numbers: true,
|
numbers: true,
|
||||||
@@ -26,8 +31,14 @@ export const prisma = new PrismaClient({
|
|||||||
rejectOnNotFound: false
|
rejectOnNotFound: false
|
||||||
});
|
});
|
||||||
|
|
||||||
export function ErrorHandler(e) {
|
export function ErrorHandler(e: {
|
||||||
if (e! instanceof Error) {
|
stdout?;
|
||||||
|
message?: string;
|
||||||
|
status?: number;
|
||||||
|
name?: string;
|
||||||
|
error?: string;
|
||||||
|
}): { status: number; body: { message: string; error: string } } {
|
||||||
|
if (e && e instanceof Error) {
|
||||||
e = new Error(e.toString());
|
e = new Error(e.toString());
|
||||||
}
|
}
|
||||||
let truncatedError = e;
|
let truncatedError = e;
|
||||||
@@ -35,8 +46,7 @@ export function ErrorHandler(e) {
|
|||||||
truncatedError = e.stdout;
|
truncatedError = e.stdout;
|
||||||
}
|
}
|
||||||
if (e.message?.includes('docker run')) {
|
if (e.message?.includes('docker run')) {
|
||||||
let truncatedArray = [];
|
const truncatedArray: string[] = truncatedError.message.split('-').filter((line) => {
|
||||||
truncatedArray = truncatedError.message.split('-').filter((line) => {
|
|
||||||
if (!line.startsWith('e ')) {
|
if (!line.startsWith('e ')) {
|
||||||
return line;
|
return line;
|
||||||
}
|
}
|
||||||
@@ -64,11 +74,11 @@ export function ErrorHandler(e) {
|
|||||||
payload.body.message = 'Already exists. Choose another name.';
|
payload.body.message = 'Already exists. Choose another name.';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// console.error(e)
|
|
||||||
return payload;
|
return payload;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function generateSshKeyPair(): Promise<{ publicKey: string; privateKey: string }> {
|
export async function generateSshKeyPair(): Promise<{ publicKey: string; privateKey: string }> {
|
||||||
return await new Promise(async (resolve, reject) => {
|
return await new Promise((resolve, reject) => {
|
||||||
forge.pki.rsa.generateKeyPair({ bits: 4096, workers: -1 }, function (err, keys) {
|
forge.pki.rsa.generateKeyPair({ bits: 4096, workers: -1 }, function (err, keys) {
|
||||||
if (keys) {
|
if (keys) {
|
||||||
resolve({
|
resolve({
|
||||||
@@ -82,163 +92,93 @@ export async function generateSshKeyPair(): Promise<{ publicKey: string; private
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export const supportedDatabaseTypesAndVersions = [
|
export function getVersions(type: string): string[] {
|
||||||
{
|
|
||||||
name: 'mongodb',
|
|
||||||
fancyName: 'MongoDB',
|
|
||||||
baseImage: 'bitnami/mongodb',
|
|
||||||
versions: ['5.0.5', '4.4.11', '4.2.18', '4.0.27']
|
|
||||||
},
|
|
||||||
{ name: 'mysql', fancyName: 'MySQL', baseImage: 'bitnami/mysql', versions: ['8.0.27', '5.7.36'] },
|
|
||||||
{
|
|
||||||
name: 'postgresql',
|
|
||||||
fancyName: 'PostgreSQL',
|
|
||||||
baseImage: 'bitnami/postgresql',
|
|
||||||
versions: ['14.1.0', '13.5.0', '12.9.0', '11.14.0', '10.19.0', '9.6.24']
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'redis',
|
|
||||||
fancyName: 'Redis',
|
|
||||||
baseImage: 'bitnami/redis',
|
|
||||||
versions: ['6.2.6', '6.0.16', '5.0.14']
|
|
||||||
},
|
|
||||||
{ name: 'couchdb', fancyName: 'CouchDB', baseImage: 'bitnami/couchdb', versions: ['3.2.1'] }
|
|
||||||
];
|
|
||||||
export const supportedServiceTypesAndVersions = [
|
|
||||||
{
|
|
||||||
name: 'plausibleanalytics',
|
|
||||||
fancyName: 'Plausible Analytics',
|
|
||||||
baseImage: 'plausible/analytics',
|
|
||||||
images: ['bitnami/postgresql:13.2.0', 'yandex/clickhouse-server:21.3.2.5'],
|
|
||||||
versions: ['latest'],
|
|
||||||
ports: {
|
|
||||||
main: 8000
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'nocodb',
|
|
||||||
fancyName: 'NocoDB',
|
|
||||||
baseImage: 'nocodb/nocodb',
|
|
||||||
versions: ['latest'],
|
|
||||||
ports: {
|
|
||||||
main: 8080
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'minio',
|
|
||||||
fancyName: 'MinIO',
|
|
||||||
baseImage: 'minio/minio',
|
|
||||||
versions: ['latest'],
|
|
||||||
ports: {
|
|
||||||
main: 9001
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'vscodeserver',
|
|
||||||
fancyName: 'VSCode Server',
|
|
||||||
baseImage: 'codercom/code-server',
|
|
||||||
versions: ['latest'],
|
|
||||||
ports: {
|
|
||||||
main: 8080
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'wordpress',
|
|
||||||
fancyName: 'Wordpress',
|
|
||||||
baseImage: 'wordpress',
|
|
||||||
images: ['bitnami/mysql:5.7'],
|
|
||||||
versions: ['latest', 'php8.1', 'php8.0', 'php7.4', 'php7.3'],
|
|
||||||
ports: {
|
|
||||||
main: 80
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'vaultwarden',
|
|
||||||
fancyName: 'Vaultwarden',
|
|
||||||
baseImage: 'vaultwarden/server',
|
|
||||||
versions: ['latest'],
|
|
||||||
ports: {
|
|
||||||
main: 80
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'languagetool',
|
|
||||||
fancyName: 'LanguageTool',
|
|
||||||
baseImage: 'silviof/docker-languagetool',
|
|
||||||
versions: ['latest'],
|
|
||||||
ports: {
|
|
||||||
main: 8010
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'n8n',
|
|
||||||
fancyName: 'n8n',
|
|
||||||
baseImage: 'n8nio/n8n',
|
|
||||||
versions: ['latest'],
|
|
||||||
ports: {
|
|
||||||
main: 5678
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'uptimekuma',
|
|
||||||
fancyName: 'Uptime Kuma',
|
|
||||||
baseImage: 'louislam/uptime-kuma',
|
|
||||||
versions: ['latest'],
|
|
||||||
ports: {
|
|
||||||
main: 3001
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'ghost',
|
|
||||||
fancyName: 'Ghost',
|
|
||||||
baseImage: 'bitnami/ghost',
|
|
||||||
images: ['bitnami/mariadb'],
|
|
||||||
versions: ['latest'],
|
|
||||||
ports: {
|
|
||||||
main: 2368
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'meilisearch',
|
|
||||||
fancyName: 'Meilisearch',
|
|
||||||
baseImage: 'getmeili/meilisearch',
|
|
||||||
images: [],
|
|
||||||
versions: ['latest'],
|
|
||||||
ports: {
|
|
||||||
main: 7700
|
|
||||||
}
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
export function getVersions(type) {
|
|
||||||
const found = supportedDatabaseTypesAndVersions.find((t) => t.name === type);
|
const found = supportedDatabaseTypesAndVersions.find((t) => t.name === type);
|
||||||
if (found) {
|
if (found) {
|
||||||
return found.versions;
|
return found.versions;
|
||||||
}
|
}
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
export function getDatabaseImage(type) {
|
|
||||||
|
export function getDatabaseImage(type: string): string {
|
||||||
const found = supportedDatabaseTypesAndVersions.find((t) => t.name === type);
|
const found = supportedDatabaseTypesAndVersions.find((t) => t.name === type);
|
||||||
if (found) {
|
if (found) {
|
||||||
return found.baseImage;
|
return found.baseImage;
|
||||||
}
|
}
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
export function getServiceImage(type) {
|
|
||||||
|
export function getServiceImage(type: string): string {
|
||||||
const found = supportedServiceTypesAndVersions.find((t) => t.name === type);
|
const found = supportedServiceTypesAndVersions.find((t) => t.name === type);
|
||||||
if (found) {
|
if (found) {
|
||||||
return found.baseImage;
|
return found.baseImage;
|
||||||
}
|
}
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
export function getServiceImages(type) {
|
|
||||||
|
export function getServiceImages(type: string): string[] {
|
||||||
const found = supportedServiceTypesAndVersions.find((t) => t.name === type);
|
const found = supportedServiceTypesAndVersions.find((t) => t.name === type);
|
||||||
if (found) {
|
if (found) {
|
||||||
return found.images;
|
return found.images;
|
||||||
}
|
}
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
export function generateDatabaseConfiguration(database) {
|
|
||||||
|
export function generateDatabaseConfiguration(database: Database & { settings: DatabaseSettings }):
|
||||||
|
| {
|
||||||
|
volume: string;
|
||||||
|
image: string;
|
||||||
|
ulimits: Record<string, unknown>;
|
||||||
|
privatePort: number;
|
||||||
|
environmentVariables: {
|
||||||
|
MYSQL_DATABASE: string;
|
||||||
|
MYSQL_PASSWORD: string;
|
||||||
|
MYSQL_ROOT_USER: string;
|
||||||
|
MYSQL_USER: string;
|
||||||
|
MYSQL_ROOT_PASSWORD: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
volume: string;
|
||||||
|
image: string;
|
||||||
|
ulimits: Record<string, unknown>;
|
||||||
|
privatePort: number;
|
||||||
|
environmentVariables: {
|
||||||
|
MONGODB_ROOT_USER: string;
|
||||||
|
MONGODB_ROOT_PASSWORD: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
volume: string;
|
||||||
|
image: string;
|
||||||
|
ulimits: Record<string, unknown>;
|
||||||
|
privatePort: number;
|
||||||
|
environmentVariables: {
|
||||||
|
POSTGRESQL_USERNAME: string;
|
||||||
|
POSTGRESQL_PASSWORD: string;
|
||||||
|
POSTGRESQL_DATABASE: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
volume: string;
|
||||||
|
image: string;
|
||||||
|
ulimits: Record<string, unknown>;
|
||||||
|
privatePort: number;
|
||||||
|
environmentVariables: {
|
||||||
|
REDIS_AOF_ENABLED: string;
|
||||||
|
REDIS_PASSWORD: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
volume: string;
|
||||||
|
image: string;
|
||||||
|
ulimits: Record<string, unknown>;
|
||||||
|
privatePort: number;
|
||||||
|
environmentVariables: {
|
||||||
|
COUCHDB_PASSWORD: string;
|
||||||
|
COUCHDB_USER: string;
|
||||||
|
};
|
||||||
|
} {
|
||||||
const {
|
const {
|
||||||
id,
|
id,
|
||||||
dbUser,
|
dbUser,
|
||||||
@@ -253,7 +193,6 @@ export function generateDatabaseConfiguration(database) {
|
|||||||
const baseImage = getDatabaseImage(type);
|
const baseImage = getDatabaseImage(type);
|
||||||
if (type === 'mysql') {
|
if (type === 'mysql') {
|
||||||
return {
|
return {
|
||||||
// url: `mysql://${dbUser}:${dbUserPassword}@${id}:${isPublic ? port : 3306}/${defaultDatabase}`,
|
|
||||||
privatePort: 3306,
|
privatePort: 3306,
|
||||||
environmentVariables: {
|
environmentVariables: {
|
||||||
MYSQL_USER: dbUser,
|
MYSQL_USER: dbUser,
|
||||||
@@ -268,7 +207,6 @@ export function generateDatabaseConfiguration(database) {
|
|||||||
};
|
};
|
||||||
} else if (type === 'mongodb') {
|
} else if (type === 'mongodb') {
|
||||||
return {
|
return {
|
||||||
// url: `mongodb://${dbUser}:${dbUserPassword}@${id}:${isPublic ? port : 27017}/${defaultDatabase}`,
|
|
||||||
privatePort: 27017,
|
privatePort: 27017,
|
||||||
environmentVariables: {
|
environmentVariables: {
|
||||||
MONGODB_ROOT_USER: rootUser,
|
MONGODB_ROOT_USER: rootUser,
|
||||||
@@ -280,7 +218,6 @@ export function generateDatabaseConfiguration(database) {
|
|||||||
};
|
};
|
||||||
} else if (type === 'postgresql') {
|
} else if (type === 'postgresql') {
|
||||||
return {
|
return {
|
||||||
// url: `psql://${dbUser}:${dbUserPassword}@${id}:${isPublic ? port : 5432}/${defaultDatabase}`,
|
|
||||||
privatePort: 5432,
|
privatePort: 5432,
|
||||||
environmentVariables: {
|
environmentVariables: {
|
||||||
POSTGRESQL_PASSWORD: dbUserPassword,
|
POSTGRESQL_PASSWORD: dbUserPassword,
|
||||||
@@ -293,7 +230,6 @@ export function generateDatabaseConfiguration(database) {
|
|||||||
};
|
};
|
||||||
} else if (type === 'redis') {
|
} else if (type === 'redis') {
|
||||||
return {
|
return {
|
||||||
// url: `redis://${dbUser}:${dbUserPassword}@${id}:${isPublic ? port : 6379}/${defaultDatabase}`,
|
|
||||||
privatePort: 6379,
|
privatePort: 6379,
|
||||||
environmentVariables: {
|
environmentVariables: {
|
||||||
REDIS_PASSWORD: dbUserPassword,
|
REDIS_PASSWORD: dbUserPassword,
|
||||||
@@ -305,7 +241,6 @@ export function generateDatabaseConfiguration(database) {
|
|||||||
};
|
};
|
||||||
} else if (type === 'couchdb') {
|
} else if (type === 'couchdb') {
|
||||||
return {
|
return {
|
||||||
// url: `couchdb://${dbUser}:${dbUserPassword}@${id}:${isPublic ? port : 5984}/${defaultDatabase}`,
|
|
||||||
privatePort: 5984,
|
privatePort: 5984,
|
||||||
environmentVariables: {
|
environmentVariables: {
|
||||||
COUCHDB_PASSWORD: dbUserPassword,
|
COUCHDB_PASSWORD: dbUserPassword,
|
||||||
@@ -316,18 +251,30 @@ export function generateDatabaseConfiguration(database) {
|
|||||||
ulimits: {}
|
ulimits: {}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
// } else if (type === 'clickhouse') {
|
}
|
||||||
// return {
|
|
||||||
// url: `clickhouse://${dbUser}:${dbUserPassword}@${id}:${port}/${defaultDatabase}`,
|
export async function getFreePort() {
|
||||||
// privatePort: 9000,
|
const data = await prisma.setting.findFirst();
|
||||||
// image: `bitnami/clickhouse-server:${version}`,
|
const { minPort, maxPort } = data;
|
||||||
// volume: `${id}-${type}-data:/var/lib/clickhouse`,
|
|
||||||
// ulimits: {
|
const dbUsed = await (
|
||||||
// nofile: {
|
await prisma.database.findMany({
|
||||||
// soft: 262144,
|
where: { publicPort: { not: null } },
|
||||||
// hard: 262144
|
select: { publicPort: true }
|
||||||
// }
|
})
|
||||||
// }
|
).map((a) => a.publicPort);
|
||||||
// }
|
const wpFtpUsed = await (
|
||||||
// }
|
await prisma.wordpress.findMany({
|
||||||
|
where: { ftpPublicPort: { not: null } },
|
||||||
|
select: { ftpPublicPort: true }
|
||||||
|
})
|
||||||
|
).map((a) => a.ftpPublicPort);
|
||||||
|
const wpUsed = await (
|
||||||
|
await prisma.wordpress.findMany({
|
||||||
|
where: { mysqlPublicPort: { not: null } },
|
||||||
|
select: { mysqlPublicPort: true }
|
||||||
|
})
|
||||||
|
).map((a) => a.mysqlPublicPort);
|
||||||
|
const usedPorts = [...dbUsed, ...wpFtpUsed, ...wpUsed];
|
||||||
|
return await getPort({ port: portNumbers(minPort, maxPort), exclude: usedPorts });
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,15 +1,28 @@
|
|||||||
import { decrypt, encrypt } from '$lib/crypto';
|
import { decrypt, encrypt } from '$lib/crypto';
|
||||||
import * as db from '$lib/database';
|
|
||||||
import cuid from 'cuid';
|
import cuid from 'cuid';
|
||||||
import { generatePassword } from '.';
|
import { generatePassword } from '.';
|
||||||
import { prisma, ErrorHandler } from './common';
|
import { prisma } from './common';
|
||||||
import getPort, { portNumbers } from 'get-port';
|
|
||||||
import { asyncExecShell, getEngine, removeContainer } from '$lib/common';
|
import { asyncExecShell, getEngine, removeContainer } from '$lib/common';
|
||||||
|
import type { Database, DatabaseSettings, DestinationDocker } from '@prisma/client';
|
||||||
|
|
||||||
export async function listDatabases(teamId) {
|
export async function listDatabases(teamId: string): Promise<Database[]> {
|
||||||
return await prisma.database.findMany({ where: { teams: { some: { id: teamId } } } });
|
if (teamId === '0') {
|
||||||
|
return await prisma.database.findMany({ include: { teams: true } });
|
||||||
|
} else {
|
||||||
|
return await prisma.database.findMany({
|
||||||
|
where: { teams: { some: { id: teamId } } },
|
||||||
|
include: { teams: true }
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
export async function newDatabase({ name, teamId }) {
|
|
||||||
|
export async function newDatabase({
|
||||||
|
name,
|
||||||
|
teamId
|
||||||
|
}: {
|
||||||
|
name: string;
|
||||||
|
teamId: string;
|
||||||
|
}): Promise<Database> {
|
||||||
const dbUser = cuid();
|
const dbUser = cuid();
|
||||||
const dbUserPassword = encrypt(generatePassword());
|
const dbUserPassword = encrypt(generatePassword());
|
||||||
const rootUser = cuid();
|
const rootUser = cuid();
|
||||||
@@ -30,25 +43,44 @@ export async function newDatabase({ name, teamId }) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getDatabase({ id, teamId }) {
|
export async function getDatabase({
|
||||||
const body = await prisma.database.findFirst({
|
id,
|
||||||
where: { id, teams: { some: { id: teamId } } },
|
teamId
|
||||||
include: { destinationDocker: true, settings: true }
|
}: {
|
||||||
});
|
id: string;
|
||||||
|
teamId: string;
|
||||||
|
}): Promise<Database & { destinationDocker: DestinationDocker; settings: DatabaseSettings }> {
|
||||||
|
let body;
|
||||||
|
if (teamId === '0') {
|
||||||
|
body = await prisma.database.findFirst({
|
||||||
|
where: { id },
|
||||||
|
include: { destinationDocker: true, settings: true }
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
body = await prisma.database.findFirst({
|
||||||
|
where: { id, teams: { some: { id: teamId } } },
|
||||||
|
include: { destinationDocker: true, settings: true }
|
||||||
|
});
|
||||||
|
}
|
||||||
if (body.dbUserPassword) body.dbUserPassword = decrypt(body.dbUserPassword);
|
if (body.dbUserPassword) body.dbUserPassword = decrypt(body.dbUserPassword);
|
||||||
if (body.rootUserPassword) body.rootUserPassword = decrypt(body.rootUserPassword);
|
if (body.rootUserPassword) body.rootUserPassword = decrypt(body.rootUserPassword);
|
||||||
|
|
||||||
return { ...body };
|
return body;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function removeDatabase({ id }) {
|
export async function removeDatabase({ id }: { id: string }): Promise<void> {
|
||||||
await prisma.databaseSettings.deleteMany({ where: { databaseId: id } });
|
await prisma.databaseSettings.deleteMany({ where: { databaseId: id } });
|
||||||
await prisma.database.delete({ where: { id } });
|
await prisma.database.delete({ where: { id } });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function configureDatabaseType({ id, type }) {
|
export async function configureDatabaseType({
|
||||||
|
id,
|
||||||
|
type
|
||||||
|
}: {
|
||||||
|
id: string;
|
||||||
|
type: string;
|
||||||
|
}): Promise<Database> {
|
||||||
return await prisma.database.update({
|
return await prisma.database.update({
|
||||||
where: { id },
|
where: { id },
|
||||||
data: { type }
|
data: { type }
|
||||||
@@ -64,7 +96,7 @@ export async function setDatabase({
|
|||||||
version?: string;
|
version?: string;
|
||||||
isPublic?: boolean;
|
isPublic?: boolean;
|
||||||
appendOnly?: boolean;
|
appendOnly?: boolean;
|
||||||
}) {
|
}): Promise<Database> {
|
||||||
return await prisma.database.update({
|
return await prisma.database.update({
|
||||||
where: { id },
|
where: { id },
|
||||||
data: {
|
data: {
|
||||||
@@ -82,7 +114,16 @@ export async function updateDatabase({
|
|||||||
rootUser,
|
rootUser,
|
||||||
rootUserPassword,
|
rootUserPassword,
|
||||||
version
|
version
|
||||||
}) {
|
}: {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
defaultDatabase: string;
|
||||||
|
dbUser: string;
|
||||||
|
dbUserPassword: string;
|
||||||
|
rootUser: string;
|
||||||
|
rootUserPassword: string;
|
||||||
|
version: string;
|
||||||
|
}): Promise<Database> {
|
||||||
const encryptedDbUserPassword = dbUserPassword && encrypt(dbUserPassword);
|
const encryptedDbUserPassword = dbUserPassword && encrypt(dbUserPassword);
|
||||||
const encryptedRootUserPassword = rootUserPassword && encrypt(rootUserPassword);
|
const encryptedRootUserPassword = rootUserPassword && encrypt(rootUserPassword);
|
||||||
return await prisma.database.update({
|
return await prisma.database.update({
|
||||||
@@ -99,7 +140,9 @@ export async function updateDatabase({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function stopDatabase(database) {
|
export async function stopDatabase(
|
||||||
|
database: Database & { destinationDocker: DestinationDocker }
|
||||||
|
): Promise<boolean> {
|
||||||
let everStarted = false;
|
let everStarted = false;
|
||||||
const {
|
const {
|
||||||
id,
|
id,
|
||||||
@@ -122,3 +165,43 @@ export async function stopDatabase(database) {
|
|||||||
}
|
}
|
||||||
return everStarted;
|
return everStarted;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function updatePasswordInDb(database, user, newPassword, isRoot) {
|
||||||
|
const {
|
||||||
|
id,
|
||||||
|
type,
|
||||||
|
rootUser,
|
||||||
|
rootUserPassword,
|
||||||
|
dbUser,
|
||||||
|
dbUserPassword,
|
||||||
|
defaultDatabase,
|
||||||
|
destinationDockerId,
|
||||||
|
destinationDocker: { engine }
|
||||||
|
} = database;
|
||||||
|
if (destinationDockerId) {
|
||||||
|
const host = getEngine(engine);
|
||||||
|
if (type === 'mysql') {
|
||||||
|
await asyncExecShell(
|
||||||
|
`DOCKER_HOST=${host} docker exec ${id} mysql -u ${rootUser} -p${rootUserPassword} -e \"ALTER USER '${user}'@'%' IDENTIFIED WITH caching_sha2_password BY '${newPassword}';\"`
|
||||||
|
);
|
||||||
|
} else if (type === 'postgresql') {
|
||||||
|
if (isRoot) {
|
||||||
|
await asyncExecShell(
|
||||||
|
`DOCKER_HOST=${host} docker exec ${id} psql postgresql://postgres:${rootUserPassword}@${id}:5432/${defaultDatabase} -c "ALTER role postgres WITH PASSWORD '${newPassword}'"`
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
await asyncExecShell(
|
||||||
|
`DOCKER_HOST=${host} docker exec ${id} psql postgresql://${dbUser}:${dbUserPassword}@${id}:5432/${defaultDatabase} -c "ALTER role ${user} WITH PASSWORD '${newPassword}'"`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else if (type === 'mongodb') {
|
||||||
|
await asyncExecShell(
|
||||||
|
`DOCKER_HOST=${host} docker exec ${id} mongo 'mongodb://${rootUser}:${rootUserPassword}@${id}:27017/admin?readPreference=primary&ssl=false' --eval "db.changeUserPassword('${user}','${newPassword}')"`
|
||||||
|
);
|
||||||
|
} else if (type === 'redis') {
|
||||||
|
await asyncExecShell(
|
||||||
|
`DOCKER_HOST=${host} docker exec ${id} redis-cli -u redis://${dbUserPassword}@${id}:6379 --raw CONFIG SET requirepass ${newPassword}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,27 +1,53 @@
|
|||||||
import { asyncExecShell, getEngine } from '$lib/common';
|
import { asyncExecShell, getEngine } from '$lib/common';
|
||||||
import { decrypt, encrypt } from '$lib/crypto';
|
|
||||||
import { dockerInstance } from '$lib/docker';
|
import { dockerInstance } from '$lib/docker';
|
||||||
import { startCoolifyProxy } from '$lib/haproxy';
|
import { startCoolifyProxy } from '$lib/haproxy';
|
||||||
import { getDatabaseImage } from '.';
|
import { getDatabaseImage } from '.';
|
||||||
import { prisma } from './common';
|
import { prisma } from './common';
|
||||||
|
import type { DestinationDocker, Service, Application, Prisma } from '@prisma/client';
|
||||||
|
import type { CreateDockerDestination } from '$lib/types/destinations';
|
||||||
|
|
||||||
export async function listDestinations(teamId) {
|
type DestinationConfigurationObject = {
|
||||||
return await prisma.destinationDocker.findMany({ where: { teams: { some: { id: teamId } } } });
|
id: string;
|
||||||
|
destinationId: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
type FindDestinationFromTeam = {
|
||||||
|
id: string;
|
||||||
|
teamId: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export async function listDestinations(teamId: string): Promise<DestinationDocker[]> {
|
||||||
|
if (teamId === '0') {
|
||||||
|
return await prisma.destinationDocker.findMany({ include: { teams: true } });
|
||||||
|
}
|
||||||
|
return await prisma.destinationDocker.findMany({
|
||||||
|
where: { teams: { some: { id: teamId } } },
|
||||||
|
include: { teams: true }
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function configureDestinationForService({ id, destinationId }) {
|
export async function configureDestinationForService({
|
||||||
|
id,
|
||||||
|
destinationId
|
||||||
|
}: DestinationConfigurationObject): Promise<Service> {
|
||||||
return await prisma.service.update({
|
return await prisma.service.update({
|
||||||
where: { id },
|
where: { id },
|
||||||
data: { destinationDocker: { connect: { id: destinationId } } }
|
data: { destinationDocker: { connect: { id: destinationId } } }
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
export async function configureDestinationForApplication({ id, destinationId }) {
|
export async function configureDestinationForApplication({
|
||||||
|
id,
|
||||||
|
destinationId
|
||||||
|
}: DestinationConfigurationObject): Promise<Application> {
|
||||||
return await prisma.application.update({
|
return await prisma.application.update({
|
||||||
where: { id },
|
where: { id },
|
||||||
data: { destinationDocker: { connect: { id: destinationId } } }
|
data: { destinationDocker: { connect: { id: destinationId } } }
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
export async function configureDestinationForDatabase({ id, destinationId }) {
|
export async function configureDestinationForDatabase({
|
||||||
|
id,
|
||||||
|
destinationId
|
||||||
|
}: DestinationConfigurationObject): Promise<void> {
|
||||||
await prisma.database.update({
|
await prisma.database.update({
|
||||||
where: { id },
|
where: { id },
|
||||||
data: { destinationDocker: { connect: { id: destinationId } } }
|
data: { destinationDocker: { connect: { id: destinationId } } }
|
||||||
@@ -38,13 +64,16 @@ export async function configureDestinationForDatabase({ id, destinationId }) {
|
|||||||
const host = getEngine(engine);
|
const host = getEngine(engine);
|
||||||
if (type && version) {
|
if (type && version) {
|
||||||
const baseImage = getDatabaseImage(type);
|
const baseImage = getDatabaseImage(type);
|
||||||
asyncExecShell(
|
asyncExecShell(`DOCKER_HOST=${host} docker pull ${baseImage}:${version}`);
|
||||||
`DOCKER_HOST=${host} docker pull ${baseImage}:${version} && echo "FROM ${baseImage}:${version}" | docker build --label coolify.image="true" -t "${baseImage}:${version}" -`
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
export async function updateDestination({ id, name, engine, network }) {
|
export async function updateDestination({
|
||||||
|
id,
|
||||||
|
name,
|
||||||
|
engine,
|
||||||
|
network
|
||||||
|
}: Pick<DestinationDocker, 'id' | 'name' | 'engine' | 'network'>): Promise<DestinationDocker> {
|
||||||
return await prisma.destinationDocker.update({ where: { id }, data: { name, engine, network } });
|
return await prisma.destinationDocker.update({ where: { id }, data: { name, engine, network } });
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -54,13 +83,8 @@ export async function newRemoteDestination({
|
|||||||
engine,
|
engine,
|
||||||
network,
|
network,
|
||||||
isCoolifyProxyUsed,
|
isCoolifyProxyUsed,
|
||||||
remoteEngine,
|
remoteEngine
|
||||||
ipAddress,
|
}: CreateDockerDestination): Promise<string> {
|
||||||
user,
|
|
||||||
port,
|
|
||||||
sshPrivateKey
|
|
||||||
}) {
|
|
||||||
const encryptedPrivateKey = encrypt(sshPrivateKey);
|
|
||||||
const destination = await prisma.destinationDocker.create({
|
const destination = await prisma.destinationDocker.create({
|
||||||
data: {
|
data: {
|
||||||
name,
|
name,
|
||||||
@@ -68,16 +92,18 @@ export async function newRemoteDestination({
|
|||||||
engine,
|
engine,
|
||||||
network,
|
network,
|
||||||
isCoolifyProxyUsed,
|
isCoolifyProxyUsed,
|
||||||
remoteEngine,
|
remoteEngine
|
||||||
ipAddress,
|
|
||||||
user,
|
|
||||||
port,
|
|
||||||
sshPrivateKey: encryptedPrivateKey
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
return destination.id;
|
return destination.id;
|
||||||
}
|
}
|
||||||
export async function newLocalDestination({ name, teamId, engine, network, isCoolifyProxyUsed }) {
|
export async function newLocalDestination({
|
||||||
|
name,
|
||||||
|
teamId,
|
||||||
|
engine,
|
||||||
|
network,
|
||||||
|
isCoolifyProxyUsed
|
||||||
|
}: CreateDockerDestination): Promise<string> {
|
||||||
const host = getEngine(engine);
|
const host = getEngine(engine);
|
||||||
const docker = dockerInstance({ destinationDocker: { engine, network } });
|
const docker = dockerInstance({ destinationDocker: { engine, network } });
|
||||||
const found = await docker.engine.listNetworks({ filters: { name: [`^${network}$`] } });
|
const found = await docker.engine.listNetworks({ filters: { name: [`^${network}$`] } });
|
||||||
@@ -95,18 +121,14 @@ export async function newLocalDestination({ name, teamId, engine, network, isCoo
|
|||||||
(destination) => destination.network !== network && destination.isCoolifyProxyUsed === true
|
(destination) => destination.network !== network && destination.isCoolifyProxyUsed === true
|
||||||
);
|
);
|
||||||
if (proxyConfigured) {
|
if (proxyConfigured) {
|
||||||
if (proxyConfigured.isCoolifyProxyUsed) {
|
isCoolifyProxyUsed = !!proxyConfigured.isCoolifyProxyUsed;
|
||||||
isCoolifyProxyUsed = true;
|
|
||||||
} else {
|
|
||||||
isCoolifyProxyUsed = false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
await prisma.destinationDocker.updateMany({ where: { engine }, data: { isCoolifyProxyUsed } });
|
await prisma.destinationDocker.updateMany({ where: { engine }, data: { isCoolifyProxyUsed } });
|
||||||
}
|
}
|
||||||
if (isCoolifyProxyUsed) await startCoolifyProxy(engine);
|
if (isCoolifyProxyUsed) await startCoolifyProxy(engine);
|
||||||
return destination.id;
|
return destination.id;
|
||||||
}
|
}
|
||||||
export async function removeDestination({ id }) {
|
export async function removeDestination({ id }: Pick<DestinationDocker, 'id'>): Promise<void> {
|
||||||
const destination = await prisma.destinationDocker.delete({ where: { id } });
|
const destination = await prisma.destinationDocker.delete({ where: { id } });
|
||||||
if (destination.isCoolifyProxyUsed) {
|
if (destination.isCoolifyProxyUsed) {
|
||||||
const host = getEngine(destination.engine);
|
const host = getEngine(destination.engine);
|
||||||
@@ -123,22 +145,39 @@ export async function removeDestination({ id }) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getDestination({ id, teamId }) {
|
export async function getDestination({
|
||||||
let destination = await prisma.destinationDocker.findFirst({
|
id,
|
||||||
where: { id, teams: { some: { id: teamId } } }
|
teamId
|
||||||
});
|
}: FindDestinationFromTeam): Promise<DestinationDocker & { sshPrivateKey?: string }> {
|
||||||
if (destination.remoteEngine) {
|
let destination;
|
||||||
destination.sshPrivateKey = decrypt(destination.sshPrivateKey);
|
if (teamId === '0') {
|
||||||
|
destination = await prisma.destinationDocker.findFirst({
|
||||||
|
where: { id }
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
destination = await prisma.destinationDocker.findFirst({
|
||||||
|
where: { id, teams: { some: { id: teamId } } }
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return destination;
|
return destination;
|
||||||
}
|
}
|
||||||
export async function getDestinationByApplicationId({ id, teamId }) {
|
export async function getDestinationByApplicationId({
|
||||||
|
id,
|
||||||
|
teamId
|
||||||
|
}: FindDestinationFromTeam): Promise<DestinationDocker> {
|
||||||
return await prisma.destinationDocker.findFirst({
|
return await prisma.destinationDocker.findFirst({
|
||||||
where: { application: { some: { id } }, teams: { some: { id: teamId } } }
|
where: { application: { some: { id } }, teams: { some: { id: teamId } } }
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function setDestinationSettings({ engine, isCoolifyProxyUsed }) {
|
export async function setDestinationSettings({
|
||||||
|
engine,
|
||||||
|
isCoolifyProxyUsed
|
||||||
|
}: {
|
||||||
|
engine: string;
|
||||||
|
isCoolifyProxyUsed: boolean;
|
||||||
|
}): Promise<Prisma.BatchPayload> {
|
||||||
return await prisma.destinationDocker.updateMany({
|
return await prisma.destinationDocker.updateMany({
|
||||||
where: { engine },
|
where: { engine },
|
||||||
data: { isCoolifyProxyUsed }
|
data: { isCoolifyProxyUsed }
|
||||||
|
|||||||
@@ -1,27 +1,36 @@
|
|||||||
import { decrypt, encrypt } from '$lib/crypto';
|
import { decrypt, encrypt } from '$lib/crypto';
|
||||||
import { prisma } from './common';
|
import { prisma } from './common';
|
||||||
|
import type { GithubApp, GitlabApp, GitSource, Prisma, Application } from '@prisma/client';
|
||||||
|
|
||||||
export async function listSources(teamId) {
|
export async function listSources(
|
||||||
|
teamId: string | Prisma.StringFilter
|
||||||
|
): Promise<(GitSource & { githubApp?: GithubApp; gitlabApp?: GitlabApp })[]> {
|
||||||
|
if (teamId === '0') {
|
||||||
|
return await prisma.gitSource.findMany({
|
||||||
|
include: { githubApp: true, gitlabApp: true, teams: true }
|
||||||
|
});
|
||||||
|
}
|
||||||
return await prisma.gitSource.findMany({
|
return await prisma.gitSource.findMany({
|
||||||
where: { teams: { some: { id: teamId } } },
|
where: { teams: { some: { id: teamId } } },
|
||||||
include: { githubApp: true, gitlabApp: true }
|
include: { githubApp: true, gitlabApp: true, teams: true }
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function newSource({ name, teamId, type, htmlUrl, apiUrl, organization }) {
|
export async function newSource({
|
||||||
|
name,
|
||||||
|
teamId
|
||||||
|
}: {
|
||||||
|
name: string;
|
||||||
|
teamId: string;
|
||||||
|
}): Promise<GitSource> {
|
||||||
return await prisma.gitSource.create({
|
return await prisma.gitSource.create({
|
||||||
data: {
|
data: {
|
||||||
teams: { connect: { id: teamId } },
|
|
||||||
name,
|
name,
|
||||||
type,
|
teams: { connect: { id: teamId } }
|
||||||
htmlUrl,
|
|
||||||
apiUrl,
|
|
||||||
organization
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
export async function removeSource({ id }) {
|
export async function removeSource({ id }: { id: string }): Promise<void> {
|
||||||
// TODO: Disconnect application with this sourceId! Maybe not needed?
|
|
||||||
const source = await prisma.gitSource.delete({
|
const source = await prisma.gitSource.delete({
|
||||||
where: { id },
|
where: { id },
|
||||||
include: { githubApp: true, gitlabApp: true }
|
include: { githubApp: true, gitlabApp: true }
|
||||||
@@ -30,11 +39,25 @@ export async function removeSource({ id }) {
|
|||||||
if (source.gitlabAppId) await prisma.gitlabApp.delete({ where: { id: source.gitlabAppId } });
|
if (source.gitlabAppId) await prisma.gitlabApp.delete({ where: { id: source.gitlabAppId } });
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getSource({ id, teamId }) {
|
export async function getSource({
|
||||||
let body = await prisma.gitSource.findFirst({
|
id,
|
||||||
where: { id, teams: { some: { id: teamId } } },
|
teamId
|
||||||
include: { githubApp: true, gitlabApp: true }
|
}: {
|
||||||
});
|
id: string;
|
||||||
|
teamId: string;
|
||||||
|
}): Promise<GitSource & { githubApp: GithubApp; gitlabApp: GitlabApp }> {
|
||||||
|
let body;
|
||||||
|
if (teamId === '0') {
|
||||||
|
body = await prisma.gitSource.findFirst({
|
||||||
|
where: { id },
|
||||||
|
include: { githubApp: true, gitlabApp: true }
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
body = await prisma.gitSource.findFirst({
|
||||||
|
where: { id, teams: { some: { id: teamId } } },
|
||||||
|
include: { githubApp: true, gitlabApp: true }
|
||||||
|
});
|
||||||
|
}
|
||||||
if (body?.githubApp?.clientSecret)
|
if (body?.githubApp?.clientSecret)
|
||||||
body.githubApp.clientSecret = decrypt(body.githubApp.clientSecret);
|
body.githubApp.clientSecret = decrypt(body.githubApp.clientSecret);
|
||||||
if (body?.githubApp?.webhookSecret)
|
if (body?.githubApp?.webhookSecret)
|
||||||
@@ -43,29 +66,69 @@ export async function getSource({ id, teamId }) {
|
|||||||
if (body?.gitlabApp?.appSecret) body.gitlabApp.appSecret = decrypt(body.gitlabApp.appSecret);
|
if (body?.gitlabApp?.appSecret) body.gitlabApp.appSecret = decrypt(body.gitlabApp.appSecret);
|
||||||
return body;
|
return body;
|
||||||
}
|
}
|
||||||
export async function addSource({ id, appId, teamId, oauthId, groupName, appSecret }) {
|
export async function addGitHubSource({ id, teamId, type, name, htmlUrl, apiUrl, organization }) {
|
||||||
const encrptedAppSecret = encrypt(appSecret);
|
await prisma.gitSource.update({
|
||||||
|
where: { id },
|
||||||
|
data: { type, name, htmlUrl, apiUrl, organization }
|
||||||
|
});
|
||||||
|
return await prisma.githubApp.create({
|
||||||
|
data: {
|
||||||
|
teams: { connect: { id: teamId } },
|
||||||
|
gitSource: { connect: { id } }
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
export async function addGitLabSource({
|
||||||
|
id,
|
||||||
|
teamId,
|
||||||
|
type,
|
||||||
|
name,
|
||||||
|
htmlUrl,
|
||||||
|
apiUrl,
|
||||||
|
oauthId,
|
||||||
|
appId,
|
||||||
|
appSecret,
|
||||||
|
groupName
|
||||||
|
}) {
|
||||||
|
const encryptedAppSecret = encrypt(appSecret);
|
||||||
|
await prisma.gitSource.update({ where: { id }, data: { type, apiUrl, htmlUrl, name } });
|
||||||
return await prisma.gitlabApp.create({
|
return await prisma.gitlabApp.create({
|
||||||
data: {
|
data: {
|
||||||
teams: { connect: { id: teamId } },
|
teams: { connect: { id: teamId } },
|
||||||
appId,
|
appId,
|
||||||
oauthId,
|
oauthId,
|
||||||
groupName,
|
groupName,
|
||||||
appSecret: encrptedAppSecret,
|
appSecret: encryptedAppSecret,
|
||||||
gitSource: { connect: { id } }
|
gitSource: { connect: { id } }
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function configureGitsource({ id, gitSourceId }) {
|
export async function configureGitsource({
|
||||||
|
id,
|
||||||
|
gitSourceId
|
||||||
|
}: {
|
||||||
|
id: string;
|
||||||
|
gitSourceId: string;
|
||||||
|
}): Promise<Application> {
|
||||||
return await prisma.application.update({
|
return await prisma.application.update({
|
||||||
where: { id },
|
where: { id },
|
||||||
data: { gitSource: { connect: { id: gitSourceId } } }
|
data: { gitSource: { connect: { id: gitSourceId } } }
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
export async function updateGitsource({ id, name }) {
|
export async function updateGitsource({
|
||||||
|
id,
|
||||||
|
name,
|
||||||
|
htmlUrl,
|
||||||
|
apiUrl
|
||||||
|
}: {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
htmlUrl: string;
|
||||||
|
apiUrl: string;
|
||||||
|
}): Promise<GitSource> {
|
||||||
return await prisma.gitSource.update({
|
return await prisma.gitSource.update({
|
||||||
where: { id },
|
where: { id },
|
||||||
data: { name }
|
data: { name, htmlUrl, apiUrl }
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,15 @@
|
|||||||
import { decrypt, encrypt } from '$lib/crypto';
|
import { decrypt, encrypt } from '$lib/crypto';
|
||||||
import { prisma } from './common';
|
import { prisma } from './common';
|
||||||
|
import type { GithubApp } from '@prisma/client';
|
||||||
|
|
||||||
export async function addInstallation({ gitSourceId, installation_id }) {
|
// TODO: We should change installation_id to be camelCase
|
||||||
|
export async function addInstallation({
|
||||||
|
gitSourceId,
|
||||||
|
installation_id
|
||||||
|
}: {
|
||||||
|
gitSourceId: string;
|
||||||
|
installation_id: string;
|
||||||
|
}): Promise<GithubApp> {
|
||||||
const source = await prisma.gitSource.findUnique({
|
const source = await prisma.gitSource.findUnique({
|
||||||
where: { id: gitSourceId },
|
where: { id: gitSourceId },
|
||||||
include: { githubApp: true }
|
include: { githubApp: true }
|
||||||
@@ -12,8 +20,12 @@ export async function addInstallation({ gitSourceId, installation_id }) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getUniqueGithubApp({ githubAppId }) {
|
export async function getUniqueGithubApp({
|
||||||
let body = await prisma.githubApp.findUnique({ where: { id: githubAppId } });
|
githubAppId
|
||||||
|
}: {
|
||||||
|
githubAppId: string;
|
||||||
|
}): Promise<GithubApp> {
|
||||||
|
const body = await prisma.githubApp.findUnique({ where: { id: githubAppId } });
|
||||||
if (body.privateKey) body.privateKey = decrypt(body.privateKey);
|
if (body.privateKey) body.privateKey = decrypt(body.privateKey);
|
||||||
return body;
|
return body;
|
||||||
}
|
}
|
||||||
@@ -26,7 +38,15 @@ export async function createGithubApp({
|
|||||||
pem,
|
pem,
|
||||||
webhook_secret,
|
webhook_secret,
|
||||||
state
|
state
|
||||||
}) {
|
}: {
|
||||||
|
id: number;
|
||||||
|
client_id: string;
|
||||||
|
slug: string;
|
||||||
|
client_secret: string;
|
||||||
|
pem: string;
|
||||||
|
webhook_secret: string;
|
||||||
|
state: string;
|
||||||
|
}): Promise<GithubApp> {
|
||||||
const encryptedClientSecret = encrypt(client_secret);
|
const encryptedClientSecret = encrypt(client_secret);
|
||||||
const encryptedWebhookSecret = encrypt(webhook_secret);
|
const encryptedWebhookSecret = encrypt(webhook_secret);
|
||||||
const encryptedPem = encrypt(pem);
|
const encryptedPem = encrypt(pem);
|
||||||
|
|||||||
@@ -1,7 +1,14 @@
|
|||||||
import { encrypt } from '$lib/crypto';
|
import { encrypt } from '$lib/crypto';
|
||||||
import { generateSshKeyPair, prisma } from './common';
|
import { generateSshKeyPair, prisma } from './common';
|
||||||
|
import type { GitlabApp } from '@prisma/client';
|
||||||
|
|
||||||
export async function updateDeployKey({ id, deployKeyId }) {
|
export async function updateDeployKey({
|
||||||
|
id,
|
||||||
|
deployKeyId
|
||||||
|
}: {
|
||||||
|
id: string;
|
||||||
|
deployKeyId: number;
|
||||||
|
}): Promise<GitlabApp> {
|
||||||
const application = await prisma.application.findUnique({
|
const application = await prisma.application.findUnique({
|
||||||
where: { id },
|
where: { id },
|
||||||
include: { gitSource: { include: { gitlabApp: true } } }
|
include: { gitSource: { include: { gitlabApp: true } } }
|
||||||
@@ -11,14 +18,24 @@ export async function updateDeployKey({ id, deployKeyId }) {
|
|||||||
data: { deployKeyId }
|
data: { deployKeyId }
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
export async function getSshKey({ id }) {
|
export async function getSshKey({
|
||||||
|
id
|
||||||
|
}: {
|
||||||
|
id: string;
|
||||||
|
}): Promise<{ status: number; body: { publicKey: string } }> {
|
||||||
const application = await prisma.application.findUnique({
|
const application = await prisma.application.findUnique({
|
||||||
where: { id },
|
where: { id },
|
||||||
include: { gitSource: { include: { gitlabApp: true } } }
|
include: { gitSource: { include: { gitlabApp: true } } }
|
||||||
});
|
});
|
||||||
return { status: 200, body: { publicKey: application.gitSource.gitlabApp.publicSshKey } };
|
return { status: 200, body: { publicKey: application.gitSource.gitlabApp.publicSshKey } };
|
||||||
}
|
}
|
||||||
export async function generateSshKey({ id }) {
|
export async function generateSshKey({
|
||||||
|
id
|
||||||
|
}: {
|
||||||
|
id: string;
|
||||||
|
}): Promise<
|
||||||
|
{ status: number; body: { publicKey: string } } | { status: number; body?: undefined }
|
||||||
|
> {
|
||||||
const application = await prisma.application.findUnique({
|
const application = await prisma.application.findUnique({
|
||||||
where: { id },
|
where: { id },
|
||||||
include: { gitSource: { include: { gitlabApp: true } } }
|
include: { gitSource: { include: { gitlabApp: true } } }
|
||||||
|
|||||||
@@ -1,6 +1,13 @@
|
|||||||
|
import type { BuildLog } from '@prisma/client';
|
||||||
import { prisma, ErrorHandler } from './common';
|
import { prisma, ErrorHandler } from './common';
|
||||||
|
|
||||||
export async function listLogs({ buildId, last = 0 }) {
|
export async function listLogs({
|
||||||
|
buildId,
|
||||||
|
last = 0
|
||||||
|
}: {
|
||||||
|
buildId: string;
|
||||||
|
last: number;
|
||||||
|
}): Promise<BuildLog[] | { status: number; body: { message: string; error: string } }> {
|
||||||
try {
|
try {
|
||||||
const body = await prisma.buildLog.findMany({
|
const body = await prisma.buildLog.findMany({
|
||||||
where: { buildId, time: { gt: last } },
|
where: { buildId, time: { gt: last } },
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
import { encrypt, decrypt } from '$lib/crypto';
|
import { encrypt, decrypt } from '$lib/crypto';
|
||||||
import { prisma } from './common';
|
import { prisma } from './common';
|
||||||
|
import type { ServiceSecret, Secret, Prisma } from '@prisma/client';
|
||||||
|
|
||||||
export async function listServiceSecrets(serviceId: string) {
|
export async function listServiceSecrets(serviceId: string): Promise<ServiceSecret[]> {
|
||||||
let secrets = await prisma.serviceSecret.findMany({
|
let secrets = await prisma.serviceSecret.findMany({
|
||||||
where: { serviceId },
|
where: { serviceId },
|
||||||
orderBy: { createdAt: 'desc' }
|
orderBy: { createdAt: 'desc' }
|
||||||
@@ -14,7 +15,7 @@ export async function listServiceSecrets(serviceId: string) {
|
|||||||
return secrets;
|
return secrets;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function listSecrets(applicationId: string) {
|
export async function listSecrets(applicationId: string): Promise<Secret[]> {
|
||||||
let secrets = await prisma.secret.findMany({
|
let secrets = await prisma.secret.findMany({
|
||||||
where: { applicationId },
|
where: { applicationId },
|
||||||
orderBy: { createdAt: 'desc' }
|
orderBy: { createdAt: 'desc' }
|
||||||
@@ -27,20 +28,48 @@ export async function listSecrets(applicationId: string) {
|
|||||||
return secrets;
|
return secrets;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function createServiceSecret({ id, name, value }) {
|
export async function createServiceSecret({
|
||||||
|
id,
|
||||||
|
name,
|
||||||
|
value
|
||||||
|
}: {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
value: string;
|
||||||
|
}): Promise<ServiceSecret> {
|
||||||
value = encrypt(value);
|
value = encrypt(value);
|
||||||
return await prisma.serviceSecret.create({
|
return await prisma.serviceSecret.create({
|
||||||
data: { name, value, service: { connect: { id } } }
|
data: { name, value, service: { connect: { id } } }
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
export async function createSecret({ id, name, value, isBuildSecret, isPRMRSecret }) {
|
export async function createSecret({
|
||||||
|
id,
|
||||||
|
name,
|
||||||
|
value,
|
||||||
|
isBuildSecret,
|
||||||
|
isPRMRSecret
|
||||||
|
}: {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
value: string;
|
||||||
|
isBuildSecret: boolean;
|
||||||
|
isPRMRSecret: boolean;
|
||||||
|
}): Promise<Secret> {
|
||||||
value = encrypt(value);
|
value = encrypt(value);
|
||||||
return await prisma.secret.create({
|
return await prisma.secret.create({
|
||||||
data: { name, value, isBuildSecret, isPRMRSecret, application: { connect: { id } } }
|
data: { name, value, isBuildSecret, isPRMRSecret, application: { connect: { id } } }
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function updateServiceSecret({ id, name, value }) {
|
export async function updateServiceSecret({
|
||||||
|
id,
|
||||||
|
name,
|
||||||
|
value
|
||||||
|
}: {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
value: string;
|
||||||
|
}): Promise<Prisma.BatchPayload | ServiceSecret> {
|
||||||
value = encrypt(value);
|
value = encrypt(value);
|
||||||
const found = await prisma.serviceSecret.findFirst({ where: { serviceId: id, name } });
|
const found = await prisma.serviceSecret.findFirst({ where: { serviceId: id, name } });
|
||||||
|
|
||||||
@@ -55,7 +84,19 @@ export async function updateServiceSecret({ id, name, value }) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
export async function updateSecret({ id, name, value, isBuildSecret, isPRMRSecret }) {
|
export async function updateSecret({
|
||||||
|
id,
|
||||||
|
name,
|
||||||
|
value,
|
||||||
|
isBuildSecret,
|
||||||
|
isPRMRSecret
|
||||||
|
}: {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
value: string;
|
||||||
|
isBuildSecret: boolean;
|
||||||
|
isPRMRSecret: boolean;
|
||||||
|
}): Promise<Prisma.BatchPayload | Secret> {
|
||||||
value = encrypt(value);
|
value = encrypt(value);
|
||||||
const found = await prisma.secret.findFirst({ where: { applicationId: id, name, isPRMRSecret } });
|
const found = await prisma.secret.findFirst({ where: { applicationId: id, name, isPRMRSecret } });
|
||||||
|
|
||||||
@@ -71,10 +112,22 @@ export async function updateSecret({ id, name, value, isBuildSecret, isPRMRSecre
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function removeServiceSecret({ id, name }) {
|
export async function removeServiceSecret({
|
||||||
|
id,
|
||||||
|
name
|
||||||
|
}: {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
}): Promise<Prisma.BatchPayload> {
|
||||||
return await prisma.serviceSecret.deleteMany({ where: { serviceId: id, name } });
|
return await prisma.serviceSecret.deleteMany({ where: { serviceId: id, name } });
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function removeSecret({ id, name }) {
|
export async function removeSecret({
|
||||||
|
id,
|
||||||
|
name
|
||||||
|
}: {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
}): Promise<Prisma.BatchPayload> {
|
||||||
return await prisma.secret.deleteMany({ where: { applicationId: id, name } });
|
return await prisma.secret.deleteMany({ where: { applicationId: id, name } });
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,31 +1,63 @@
|
|||||||
import { asyncExecShell, getEngine } from '$lib/common';
|
|
||||||
import { decrypt, encrypt } from '$lib/crypto';
|
import { decrypt, encrypt } from '$lib/crypto';
|
||||||
|
import type { Minio, Service } from '@prisma/client';
|
||||||
import cuid from 'cuid';
|
import cuid from 'cuid';
|
||||||
import { generatePassword } from '.';
|
import { generatePassword } from '.';
|
||||||
import { prisma } from './common';
|
import { prisma } from './common';
|
||||||
|
|
||||||
export async function listServices(teamId) {
|
export async function listServices(teamId: string): Promise<Service[]> {
|
||||||
return await prisma.service.findMany({ where: { teams: { some: { id: teamId } } } });
|
if (teamId === '0') {
|
||||||
|
return await prisma.service.findMany({ include: { teams: true } });
|
||||||
|
} else {
|
||||||
|
return await prisma.service.findMany({
|
||||||
|
where: { teams: { some: { id: teamId } } },
|
||||||
|
include: { teams: true }
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function newService({ name, teamId }) {
|
export async function newService({
|
||||||
|
name,
|
||||||
|
teamId
|
||||||
|
}: {
|
||||||
|
name: string;
|
||||||
|
teamId: string;
|
||||||
|
}): Promise<Service> {
|
||||||
return await prisma.service.create({ data: { name, teams: { connect: { id: teamId } } } });
|
return await prisma.service.create({ data: { name, teams: { connect: { id: teamId } } } });
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getService({ id, teamId }) {
|
export async function getService({ id, teamId }: { id: string; teamId: string }): Promise<Service> {
|
||||||
const body = await prisma.service.findFirst({
|
let body;
|
||||||
where: { id, teams: { some: { id: teamId } } },
|
if (teamId === '0') {
|
||||||
include: {
|
body = await prisma.service.findFirst({
|
||||||
destinationDocker: true,
|
where: { id },
|
||||||
plausibleAnalytics: true,
|
include: {
|
||||||
minio: true,
|
destinationDocker: true,
|
||||||
vscodeserver: true,
|
plausibleAnalytics: true,
|
||||||
wordpress: true,
|
minio: true,
|
||||||
ghost: true,
|
vscodeserver: true,
|
||||||
serviceSecret: true,
|
wordpress: true,
|
||||||
meiliSearch: true
|
ghost: true,
|
||||||
}
|
serviceSecret: true,
|
||||||
});
|
meiliSearch: true,
|
||||||
|
persistentStorage: true
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
body = await prisma.service.findFirst({
|
||||||
|
where: { id, teams: { some: { id: teamId } } },
|
||||||
|
include: {
|
||||||
|
destinationDocker: true,
|
||||||
|
plausibleAnalytics: true,
|
||||||
|
minio: true,
|
||||||
|
vscodeserver: true,
|
||||||
|
wordpress: true,
|
||||||
|
ghost: true,
|
||||||
|
serviceSecret: true,
|
||||||
|
meiliSearch: true,
|
||||||
|
persistentStorage: true
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
if (body.plausibleAnalytics?.postgresqlPassword)
|
if (body.plausibleAnalytics?.postgresqlPassword)
|
||||||
body.plausibleAnalytics.postgresqlPassword = decrypt(
|
body.plausibleAnalytics.postgresqlPassword = decrypt(
|
||||||
@@ -59,11 +91,21 @@ export async function getService({ id, teamId }) {
|
|||||||
return s;
|
return s;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
if (body.wordpress?.ftpPassword) {
|
||||||
|
body.wordpress.ftpPassword = decrypt(body.wordpress.ftpPassword);
|
||||||
|
}
|
||||||
|
const settings = await prisma.setting.findFirst();
|
||||||
|
|
||||||
return { ...body };
|
return { ...body, settings };
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function configureServiceType({ id, type }) {
|
export async function configureServiceType({
|
||||||
|
id,
|
||||||
|
type
|
||||||
|
}: {
|
||||||
|
id: string;
|
||||||
|
type: string;
|
||||||
|
}): Promise<void> {
|
||||||
if (type === 'plausibleanalytics') {
|
if (type === 'plausibleanalytics') {
|
||||||
const password = encrypt(generatePassword());
|
const password = encrypt(generatePassword());
|
||||||
const postgresqlUser = cuid();
|
const postgresqlUser = cuid();
|
||||||
@@ -145,7 +187,7 @@ export async function configureServiceType({ id, type }) {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else if (type === 'ghost') {
|
} else if (type === 'ghost') {
|
||||||
const defaultEmail = `${cuid()}@coolify.io`;
|
const defaultEmail = `${cuid()}@example.com`;
|
||||||
const defaultPassword = encrypt(generatePassword());
|
const defaultPassword = encrypt(generatePassword());
|
||||||
const mariadbUser = cuid();
|
const mariadbUser = cuid();
|
||||||
const mariadbPassword = encrypt(generatePassword());
|
const mariadbPassword = encrypt(generatePassword());
|
||||||
@@ -179,56 +221,158 @@ export async function configureServiceType({ id, type }) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
export async function setServiceVersion({ id, version }) {
|
|
||||||
|
export async function setServiceVersion({
|
||||||
|
id,
|
||||||
|
version
|
||||||
|
}: {
|
||||||
|
id: string;
|
||||||
|
version: string;
|
||||||
|
}): Promise<Service> {
|
||||||
return await prisma.service.update({
|
return await prisma.service.update({
|
||||||
where: { id },
|
where: { id },
|
||||||
data: { version }
|
data: { version }
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function setServiceSettings({ id, dualCerts }) {
|
export async function setServiceSettings({
|
||||||
|
id,
|
||||||
|
dualCerts
|
||||||
|
}: {
|
||||||
|
id: string;
|
||||||
|
dualCerts: boolean;
|
||||||
|
}): Promise<Service> {
|
||||||
return await prisma.service.update({
|
return await prisma.service.update({
|
||||||
where: { id },
|
where: { id },
|
||||||
data: { dualCerts }
|
data: { dualCerts }
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function updatePlausibleAnalyticsService({ id, fqdn, email, username, name }) {
|
export async function updatePlausibleAnalyticsService({
|
||||||
|
id,
|
||||||
|
fqdn,
|
||||||
|
email,
|
||||||
|
username,
|
||||||
|
name
|
||||||
|
}: {
|
||||||
|
id: string;
|
||||||
|
fqdn: string;
|
||||||
|
name: string;
|
||||||
|
email: string;
|
||||||
|
username: string;
|
||||||
|
}): Promise<void> {
|
||||||
await prisma.plausibleAnalytics.update({ where: { serviceId: id }, data: { email, username } });
|
await prisma.plausibleAnalytics.update({ where: { serviceId: id }, data: { email, username } });
|
||||||
await prisma.service.update({ where: { id }, data: { name, fqdn } });
|
await prisma.service.update({ where: { id }, data: { name, fqdn } });
|
||||||
}
|
}
|
||||||
export async function updateService({ id, fqdn, name }) {
|
|
||||||
|
export async function updateService({
|
||||||
|
id,
|
||||||
|
fqdn,
|
||||||
|
name
|
||||||
|
}: {
|
||||||
|
id: string;
|
||||||
|
fqdn: string;
|
||||||
|
name: string;
|
||||||
|
}): Promise<Service> {
|
||||||
return await prisma.service.update({ where: { id }, data: { fqdn, name } });
|
return await prisma.service.update({ where: { id }, data: { fqdn, name } });
|
||||||
}
|
}
|
||||||
export async function updateLanguageToolService({ id, fqdn, name }) {
|
|
||||||
|
export async function updateLanguageToolService({
|
||||||
|
id,
|
||||||
|
fqdn,
|
||||||
|
name
|
||||||
|
}: {
|
||||||
|
id: string;
|
||||||
|
fqdn: string;
|
||||||
|
name: string;
|
||||||
|
}): Promise<Service> {
|
||||||
return await prisma.service.update({ where: { id }, data: { fqdn, name } });
|
return await prisma.service.update({ where: { id }, data: { fqdn, name } });
|
||||||
}
|
}
|
||||||
export async function updateMeiliSearchService({ id, fqdn, name }) {
|
|
||||||
|
export async function updateMeiliSearchService({
|
||||||
|
id,
|
||||||
|
fqdn,
|
||||||
|
name
|
||||||
|
}: {
|
||||||
|
id: string;
|
||||||
|
fqdn: string;
|
||||||
|
name: string;
|
||||||
|
}): Promise<Service> {
|
||||||
return await prisma.service.update({ where: { id }, data: { fqdn, name } });
|
return await prisma.service.update({ where: { id }, data: { fqdn, name } });
|
||||||
}
|
}
|
||||||
export async function updateVaultWardenService({ id, fqdn, name }) {
|
|
||||||
|
export async function updateVaultWardenService({
|
||||||
|
id,
|
||||||
|
fqdn,
|
||||||
|
name
|
||||||
|
}: {
|
||||||
|
id: string;
|
||||||
|
fqdn: string;
|
||||||
|
name: string;
|
||||||
|
}): Promise<Service> {
|
||||||
return await prisma.service.update({ where: { id }, data: { fqdn, name } });
|
return await prisma.service.update({ where: { id }, data: { fqdn, name } });
|
||||||
}
|
}
|
||||||
export async function updateVsCodeServer({ id, fqdn, name }) {
|
|
||||||
|
export async function updateVsCodeServer({
|
||||||
|
id,
|
||||||
|
fqdn,
|
||||||
|
name
|
||||||
|
}: {
|
||||||
|
id: string;
|
||||||
|
fqdn: string;
|
||||||
|
name: string;
|
||||||
|
}): Promise<Service> {
|
||||||
return await prisma.service.update({ where: { id }, data: { fqdn, name } });
|
return await prisma.service.update({ where: { id }, data: { fqdn, name } });
|
||||||
}
|
}
|
||||||
export async function updateWordpress({ id, fqdn, name, mysqlDatabase, extraConfig }) {
|
|
||||||
|
export async function updateWordpress({
|
||||||
|
id,
|
||||||
|
fqdn,
|
||||||
|
name,
|
||||||
|
mysqlDatabase,
|
||||||
|
extraConfig
|
||||||
|
}: {
|
||||||
|
id: string;
|
||||||
|
fqdn: string;
|
||||||
|
name: string;
|
||||||
|
mysqlDatabase: string;
|
||||||
|
extraConfig: string;
|
||||||
|
}): Promise<Service> {
|
||||||
return await prisma.service.update({
|
return await prisma.service.update({
|
||||||
where: { id },
|
where: { id },
|
||||||
data: { fqdn, name, wordpress: { update: { mysqlDatabase, extraConfig } } }
|
data: { fqdn, name, wordpress: { update: { mysqlDatabase, extraConfig } } }
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
export async function updateMinioService({ id, publicPort }) {
|
|
||||||
|
export async function updateMinioService({
|
||||||
|
id,
|
||||||
|
publicPort
|
||||||
|
}: {
|
||||||
|
id: string;
|
||||||
|
publicPort: number;
|
||||||
|
}): Promise<Minio> {
|
||||||
return await prisma.minio.update({ where: { serviceId: id }, data: { publicPort } });
|
return await prisma.minio.update({ where: { serviceId: id }, data: { publicPort } });
|
||||||
}
|
}
|
||||||
export async function updateGhostService({ id, fqdn, name, mariadbDatabase }) {
|
|
||||||
|
export async function updateGhostService({
|
||||||
|
id,
|
||||||
|
fqdn,
|
||||||
|
name,
|
||||||
|
mariadbDatabase
|
||||||
|
}: {
|
||||||
|
id: string;
|
||||||
|
fqdn: string;
|
||||||
|
name: string;
|
||||||
|
mariadbDatabase: string;
|
||||||
|
}): Promise<Service> {
|
||||||
return await prisma.service.update({
|
return await prisma.service.update({
|
||||||
where: { id },
|
where: { id },
|
||||||
data: { fqdn, name, ghost: { update: { mariadbDatabase } } }
|
data: { fqdn, name, ghost: { update: { mariadbDatabase } } }
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function removeService({ id }) {
|
export async function removeService({ id }: { id: string }): Promise<void> {
|
||||||
|
await prisma.servicePersistentStorage.deleteMany({ where: { serviceId: id } });
|
||||||
await prisma.meiliSearch.deleteMany({ where: { serviceId: id } });
|
await prisma.meiliSearch.deleteMany({ where: { serviceId: id } });
|
||||||
await prisma.ghost.deleteMany({ where: { serviceId: id } });
|
await prisma.ghost.deleteMany({ where: { serviceId: id } });
|
||||||
await prisma.plausibleAnalytics.deleteMany({ where: { serviceId: id } });
|
await prisma.plausibleAnalytics.deleteMany({ where: { serviceId: id } });
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
import { decrypt } from '$lib/crypto';
|
import { decrypt } from '$lib/crypto';
|
||||||
import { prisma } from './common';
|
import { prisma } from './common';
|
||||||
|
import type { Setting } from '@prisma/client';
|
||||||
|
|
||||||
export async function listSettings() {
|
export async function listSettings(): Promise<Setting> {
|
||||||
let settings = await prisma.setting.findFirst({});
|
const settings = await prisma.setting.findFirst({});
|
||||||
if (settings.proxyPassword) settings.proxyPassword = decrypt(settings.proxyPassword);
|
if (settings.proxyPassword) settings.proxyPassword = decrypt(settings.proxyPassword);
|
||||||
return settings;
|
return settings;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
|
import type { Team, Permission } from '@prisma/client';
|
||||||
import { prisma } from './common';
|
import { prisma } from './common';
|
||||||
|
|
||||||
export async function listTeams() {
|
export async function listTeams(): Promise<Team[]> {
|
||||||
return await prisma.team.findMany();
|
return await prisma.team.findMany();
|
||||||
}
|
}
|
||||||
export async function newTeam({ name, userId }) {
|
export async function newTeam({ name, userId }: { name: string; userId: string }): Promise<Team> {
|
||||||
return await prisma.team.create({
|
return await prisma.team.create({
|
||||||
data: {
|
data: {
|
||||||
name,
|
name,
|
||||||
@@ -12,7 +13,11 @@ export async function newTeam({ name, userId }) {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
export async function getMyTeams({ userId }) {
|
export async function getMyTeams({
|
||||||
|
userId
|
||||||
|
}: {
|
||||||
|
userId: string;
|
||||||
|
}): Promise<(Permission & { team: Team & { _count: { users: number } } })[]> {
|
||||||
return await prisma.permission.findMany({
|
return await prisma.permission.findMany({
|
||||||
where: { userId },
|
where: { userId },
|
||||||
include: { team: { include: { _count: { select: { users: true } } } } }
|
include: { team: { include: { _count: { select: { users: true } } } } }
|
||||||
|
|||||||
@@ -1,16 +1,30 @@
|
|||||||
import cuid from 'cuid';
|
import cuid from 'cuid';
|
||||||
import bcrypt from 'bcrypt';
|
import bcrypt from 'bcryptjs';
|
||||||
|
|
||||||
import { prisma } from './common';
|
import { prisma } from './common';
|
||||||
import { asyncExecShell, uniqueName } from '$lib/common';
|
import { asyncExecShell, uniqueName } from '$lib/common';
|
||||||
|
|
||||||
import * as db from '$lib/database';
|
import * as db from '$lib/database';
|
||||||
import { startCoolifyProxy } from '$lib/haproxy';
|
import { startCoolifyProxy } from '$lib/haproxy';
|
||||||
export async function hashPassword(password: string) {
|
import type { User } from '@prisma/client';
|
||||||
|
|
||||||
|
export async function hashPassword(password: string): Promise<string> {
|
||||||
const saltRounds = 15;
|
const saltRounds = 15;
|
||||||
return bcrypt.hash(password, saltRounds);
|
return bcrypt.hash(password, saltRounds);
|
||||||
}
|
}
|
||||||
export async function login({ email, password, isLogin }) {
|
|
||||||
|
export async function login({
|
||||||
|
email,
|
||||||
|
password,
|
||||||
|
isLogin
|
||||||
|
}: {
|
||||||
|
email: string;
|
||||||
|
password: string;
|
||||||
|
isLogin: boolean;
|
||||||
|
}): Promise<{
|
||||||
|
status: number;
|
||||||
|
headers: { 'Set-Cookie': string };
|
||||||
|
body: { userId: string; teamId: string; permission: string; isAdmin: boolean };
|
||||||
|
}> {
|
||||||
const users = await prisma.user.count();
|
const users = await prisma.user.count();
|
||||||
const userFound = await prisma.user.findUnique({
|
const userFound = await prisma.user.findUnique({
|
||||||
where: { email },
|
where: { email },
|
||||||
@@ -32,26 +46,46 @@ export async function login({ email, password, isLogin }) {
|
|||||||
if (users === 0) {
|
if (users === 0) {
|
||||||
await prisma.setting.update({ where: { id }, data: { isRegistrationEnabled: false } });
|
await prisma.setting.update({ where: { id }, data: { isRegistrationEnabled: false } });
|
||||||
// Create default network & start Coolify Proxy
|
// Create default network & start Coolify Proxy
|
||||||
asyncExecShell(`docker network create --attachable coolify`)
|
try {
|
||||||
.then(() => {
|
await asyncExecShell(`docker network create --attachable coolify`);
|
||||||
console.log('Network created');
|
} catch (error) {}
|
||||||
})
|
try {
|
||||||
.catch(() => {
|
await startCoolifyProxy('/var/run/docker.sock');
|
||||||
console.log('Network already exists.');
|
} catch (error) {}
|
||||||
});
|
|
||||||
|
|
||||||
startCoolifyProxy('/var/run/docker.sock')
|
|
||||||
.then(() => {
|
|
||||||
console.log('Coolify Proxy started.');
|
|
||||||
})
|
|
||||||
.catch((err) => {
|
|
||||||
console.log(err);
|
|
||||||
});
|
|
||||||
uid = '0';
|
uid = '0';
|
||||||
}
|
}
|
||||||
|
|
||||||
if (userFound) {
|
if (userFound) {
|
||||||
if (userFound.type === 'email') {
|
if (userFound.type === 'email') {
|
||||||
|
if (userFound.password === 'RESETME') {
|
||||||
|
const hashedPassword = await hashPassword(password);
|
||||||
|
if (userFound.updatedAt < new Date(Date.now() - 1000 * 60 * 10)) {
|
||||||
|
await prisma.user.update({
|
||||||
|
where: { email: userFound.email },
|
||||||
|
data: { password: 'RESETTIMEOUT' }
|
||||||
|
});
|
||||||
|
throw {
|
||||||
|
error: 'Password reset link has expired. Please request a new one.'
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
await prisma.user.update({
|
||||||
|
where: { email: userFound.email },
|
||||||
|
data: { password: hashedPassword }
|
||||||
|
});
|
||||||
|
return {
|
||||||
|
status: 200,
|
||||||
|
headers: {
|
||||||
|
'Set-Cookie': `teamId=${uid}; HttpOnly; Path=/; Max-Age=15778800;`
|
||||||
|
},
|
||||||
|
body: {
|
||||||
|
userId: userFound.id,
|
||||||
|
teamId: userFound.id,
|
||||||
|
permission: userFound.permission,
|
||||||
|
isAdmin: true
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
const passwordMatch = await bcrypt.compare(password, userFound.password);
|
const passwordMatch = await bcrypt.compare(password, userFound.password);
|
||||||
if (!passwordMatch) {
|
if (!passwordMatch) {
|
||||||
throw {
|
throw {
|
||||||
@@ -124,6 +158,6 @@ export async function login({ email, password, isLogin }) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getUser({ userId }) {
|
export async function getUser({ userId }: { userId: string }): Promise<User> {
|
||||||
return await prisma.user.findUnique({ where: { id: userId } });
|
return await prisma.user.findUnique({ where: { id: userId } });
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -85,7 +85,8 @@ export async function buildImage({
|
|||||||
docker,
|
docker,
|
||||||
buildId,
|
buildId,
|
||||||
isCache = false,
|
isCache = false,
|
||||||
debug = false
|
debug = false,
|
||||||
|
dockerFileLocation = '/Dockerfile'
|
||||||
}) {
|
}) {
|
||||||
if (isCache) {
|
if (isCache) {
|
||||||
await saveBuildLog({ line: `Building cache image started.`, buildId, applicationId });
|
await saveBuildLog({ line: `Building cache image started.`, buildId, applicationId });
|
||||||
@@ -103,11 +104,12 @@ export async function buildImage({
|
|||||||
const stream = await docker.engine.buildImage(
|
const stream = await docker.engine.buildImage(
|
||||||
{ src: ['.'], context: workdir },
|
{ src: ['.'], context: workdir },
|
||||||
{
|
{
|
||||||
dockerfile: isCache ? 'Dockerfile-cache' : 'Dockerfile',
|
dockerfile: isCache ? `${dockerFileLocation}-cache` : dockerFileLocation,
|
||||||
t: `${applicationId}:${tag}${isCache ? '-cache' : ''}`
|
t: `${applicationId}:${tag}${isCache ? '-cache' : ''}`
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
await streamEvents({ stream, docker, buildId, applicationId, debug });
|
await streamEvents({ stream, docker, buildId, applicationId, debug });
|
||||||
|
await saveBuildLog({ line: `Building image successful!`, buildId, applicationId });
|
||||||
}
|
}
|
||||||
|
|
||||||
export function dockerInstance({ destinationDocker }): { engine: Dockerode; network: string } {
|
export function dockerInstance({ destinationDocker }): { engine: Dockerode; network: string } {
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { toast } from '@zerodevx/svelte-toast';
|
import { toast } from '@zerodevx/svelte-toast';
|
||||||
export function errorNotification(message: string) {
|
|
||||||
|
export function errorNotification(message: string): void {
|
||||||
console.error(message);
|
console.error(message);
|
||||||
if (typeof message !== 'string') {
|
if (typeof message !== 'string') {
|
||||||
toast.push('Ooops, something is not okay, are you okay?');
|
toast.push('Ooops, something is not okay, are you okay?');
|
||||||
@@ -30,7 +31,7 @@ export function enhance(
|
|||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
let body = new FormData(form);
|
let body = new FormData(form);
|
||||||
let parsedData = body;
|
const parsedData = body;
|
||||||
|
|
||||||
body.forEach((data, key) => {
|
body.forEach((data, key) => {
|
||||||
if (data === '' || data === null) parsedData.delete(key);
|
if (data === '' || data === null) parsedData.delete(key);
|
||||||
|
|||||||
@@ -1,15 +1,15 @@
|
|||||||
import { dev } from '$app/env';
|
import { dev } from '$app/env';
|
||||||
import got from 'got';
|
import got, { type Got } from 'got';
|
||||||
|
import * as db from '$lib/database';
|
||||||
import mustache from 'mustache';
|
import mustache from 'mustache';
|
||||||
import crypto from 'crypto';
|
import crypto from 'crypto';
|
||||||
|
|
||||||
import * as db from '$lib/database';
|
|
||||||
import { checkContainer, checkHAProxy } from '.';
|
import { checkContainer, checkHAProxy } from '.';
|
||||||
import { asyncExecShell, getDomain, getEngine } from '$lib/common';
|
import { asyncExecShell, getDomain, getEngine } from '$lib/common';
|
||||||
|
import { supportedServiceTypesAndVersions } from '$lib/components/common';
|
||||||
|
|
||||||
const url = dev ? 'http://localhost:5555' : 'http://coolify-haproxy:5555';
|
const url = dev ? 'http://localhost:5555' : 'http://coolify-haproxy:5555';
|
||||||
|
|
||||||
let template = `program api
|
const template = `program api
|
||||||
command /usr/bin/dataplaneapi -f /usr/local/etc/haproxy/dataplaneapi.hcl --userlist haproxy-dataplaneapi
|
command /usr/bin/dataplaneapi -f /usr/local/etc/haproxy/dataplaneapi.hcl --userlist haproxy-dataplaneapi
|
||||||
no option start-on-reload
|
no option start-on-reload
|
||||||
|
|
||||||
@@ -20,10 +20,10 @@ global
|
|||||||
defaults
|
defaults
|
||||||
mode http
|
mode http
|
||||||
log global
|
log global
|
||||||
timeout http-request 60s
|
timeout http-request 120s
|
||||||
timeout connect 10s
|
timeout connect 10s
|
||||||
timeout client 60s
|
timeout client 120s
|
||||||
timeout server 60s
|
timeout server 120s
|
||||||
|
|
||||||
userlist haproxy-dataplaneapi
|
userlist haproxy-dataplaneapi
|
||||||
user admin insecure-password "\${HAPROXY_PASSWORD}"
|
user admin insecure-password "\${HAPROXY_PASSWORD}"
|
||||||
@@ -127,7 +127,8 @@ backend {{domain}}
|
|||||||
server {{id}} {{id}}:{{port}} check fall 10
|
server {{id}} {{id}}:{{port}} check fall 10
|
||||||
{{/coolify}}
|
{{/coolify}}
|
||||||
`;
|
`;
|
||||||
export async function haproxyInstance() {
|
|
||||||
|
export async function haproxyInstance(): Promise<Got> {
|
||||||
const { proxyPassword } = await db.listSettings();
|
const { proxyPassword } = await db.listSettings();
|
||||||
return got.extend({
|
return got.extend({
|
||||||
prefixUrl: url,
|
prefixUrl: url,
|
||||||
@@ -136,31 +137,97 @@ export async function haproxyInstance() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function configureHAProxy() {
|
export async function configureHAProxy(): Promise<void> {
|
||||||
const haproxy = await haproxyInstance();
|
const haproxy = await haproxyInstance();
|
||||||
await checkHAProxy(haproxy);
|
await checkHAProxy(haproxy);
|
||||||
|
|
||||||
try {
|
const data = {
|
||||||
const data = {
|
applications: [],
|
||||||
applications: [],
|
services: [],
|
||||||
services: [],
|
coolify: []
|
||||||
coolify: []
|
};
|
||||||
};
|
const applications = await db.prisma.application.findMany({
|
||||||
const applications = await db.prisma.application.findMany({
|
include: { destinationDocker: true, settings: true }
|
||||||
include: { destinationDocker: true, settings: true }
|
});
|
||||||
});
|
for (const application of applications) {
|
||||||
for (const application of applications) {
|
const {
|
||||||
const {
|
fqdn,
|
||||||
fqdn,
|
id,
|
||||||
id,
|
port,
|
||||||
port,
|
destinationDocker,
|
||||||
destinationDocker,
|
destinationDockerId,
|
||||||
destinationDockerId,
|
settings: { previews },
|
||||||
settings: { previews },
|
updatedAt
|
||||||
updatedAt
|
} = application;
|
||||||
} = application;
|
if (destinationDockerId) {
|
||||||
if (destinationDockerId) {
|
const { engine, network } = destinationDocker;
|
||||||
const { engine, network } = destinationDocker;
|
const isRunning = await checkContainer(engine, id);
|
||||||
|
if (fqdn) {
|
||||||
|
const domain = getDomain(fqdn);
|
||||||
|
const isHttps = fqdn.startsWith('https://');
|
||||||
|
const isWWW = fqdn.includes('www.');
|
||||||
|
const redirectValue = `${isHttps ? 'https://' : 'http://'}${domain}%[capture.req.uri]`;
|
||||||
|
if (isRunning) {
|
||||||
|
data.applications.push({
|
||||||
|
id,
|
||||||
|
port: port || 3000,
|
||||||
|
domain,
|
||||||
|
isRunning,
|
||||||
|
isHttps,
|
||||||
|
redirectValue,
|
||||||
|
redirectTo: isWWW ? domain.replace('www.', '') : 'www.' + domain,
|
||||||
|
updatedAt: updatedAt.getTime()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (previews) {
|
||||||
|
const host = getEngine(engine);
|
||||||
|
const { stdout } = await asyncExecShell(
|
||||||
|
`DOCKER_HOST=${host} docker container ls --filter="status=running" --filter="network=${network}" --filter="name=${id}-" --format="{{json .Names}}"`
|
||||||
|
);
|
||||||
|
const containers = stdout
|
||||||
|
.trim()
|
||||||
|
.split('\n')
|
||||||
|
.filter((a) => a)
|
||||||
|
.map((c) => c.replace(/"/g, ''));
|
||||||
|
if (containers.length > 0) {
|
||||||
|
for (const container of containers) {
|
||||||
|
const previewDomain = `${container.split('-')[1]}.${domain}`;
|
||||||
|
data.applications.push({
|
||||||
|
id: container,
|
||||||
|
port: port || 3000,
|
||||||
|
domain: previewDomain,
|
||||||
|
isRunning,
|
||||||
|
isHttps,
|
||||||
|
redirectValue,
|
||||||
|
redirectTo: isWWW ? previewDomain.replace('www.', '') : 'www.' + previewDomain,
|
||||||
|
updatedAt: updatedAt.getTime()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const services = await db.prisma.service.findMany({
|
||||||
|
include: {
|
||||||
|
destinationDocker: true,
|
||||||
|
minio: true,
|
||||||
|
plausibleAnalytics: true,
|
||||||
|
vscodeserver: true,
|
||||||
|
wordpress: true,
|
||||||
|
ghost: true,
|
||||||
|
meiliSearch: true
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
for (const service of services) {
|
||||||
|
const { fqdn, id, type, destinationDocker, destinationDockerId, updatedAt } = service;
|
||||||
|
if (destinationDockerId) {
|
||||||
|
const { engine } = destinationDocker;
|
||||||
|
const found = supportedServiceTypesAndVersions.find((a) => a.name === type);
|
||||||
|
if (found) {
|
||||||
|
const port = found.ports.main;
|
||||||
|
const publicPort = service[type]?.publicPort;
|
||||||
const isRunning = await checkContainer(engine, id);
|
const isRunning = await checkContainer(engine, id);
|
||||||
if (fqdn) {
|
if (fqdn) {
|
||||||
const domain = getDomain(fqdn);
|
const domain = getDomain(fqdn);
|
||||||
@@ -168,9 +235,10 @@ export async function configureHAProxy() {
|
|||||||
const isWWW = fqdn.includes('www.');
|
const isWWW = fqdn.includes('www.');
|
||||||
const redirectValue = `${isHttps ? 'https://' : 'http://'}${domain}%[capture.req.uri]`;
|
const redirectValue = `${isHttps ? 'https://' : 'http://'}${domain}%[capture.req.uri]`;
|
||||||
if (isRunning) {
|
if (isRunning) {
|
||||||
data.applications.push({
|
data.services.push({
|
||||||
id,
|
id,
|
||||||
port: port || 3000,
|
port,
|
||||||
|
publicPort,
|
||||||
domain,
|
domain,
|
||||||
isRunning,
|
isRunning,
|
||||||
isHttps,
|
isHttps,
|
||||||
@@ -179,108 +247,38 @@ export async function configureHAProxy() {
|
|||||||
updatedAt: updatedAt.getTime()
|
updatedAt: updatedAt.getTime()
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (previews) {
|
|
||||||
const host = getEngine(engine);
|
|
||||||
const { stdout } = await asyncExecShell(
|
|
||||||
`DOCKER_HOST=${host} docker container ls --filter="status=running" --filter="network=${network}" --filter="name=${id}-" --format="{{json .Names}}"`
|
|
||||||
);
|
|
||||||
const containers = stdout
|
|
||||||
.trim()
|
|
||||||
.split('\n')
|
|
||||||
.filter((a) => a)
|
|
||||||
.map((c) => c.replace(/"/g, ''));
|
|
||||||
if (containers.length > 0) {
|
|
||||||
for (const container of containers) {
|
|
||||||
let previewDomain = `${container.split('-')[1]}.${domain}`;
|
|
||||||
data.applications.push({
|
|
||||||
id: container,
|
|
||||||
port: port || 3000,
|
|
||||||
domain: previewDomain,
|
|
||||||
isRunning,
|
|
||||||
isHttps,
|
|
||||||
redirectValue,
|
|
||||||
redirectTo: isWWW ? previewDomain.replace('www.', '') : 'www.' + previewDomain,
|
|
||||||
updatedAt: updatedAt.getTime()
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const services = await db.prisma.service.findMany({
|
}
|
||||||
include: {
|
const { fqdn } = await db.prisma.setting.findFirst();
|
||||||
destinationDocker: true,
|
if (fqdn) {
|
||||||
minio: true,
|
const domain = getDomain(fqdn);
|
||||||
plausibleAnalytics: true,
|
const isHttps = fqdn.startsWith('https://');
|
||||||
vscodeserver: true,
|
const isWWW = fqdn.includes('www.');
|
||||||
wordpress: true,
|
const redirectValue = `${isHttps ? 'https://' : 'http://'}${domain}%[capture.req.uri]`;
|
||||||
ghost: true
|
data.coolify.push({
|
||||||
|
id: dev ? 'host.docker.internal' : 'coolify',
|
||||||
|
port: 3000,
|
||||||
|
domain,
|
||||||
|
isHttps,
|
||||||
|
redirectValue,
|
||||||
|
redirectTo: isWWW ? domain.replace('www.', '') : 'www.' + domain
|
||||||
|
});
|
||||||
|
}
|
||||||
|
const output = mustache.render(template, data);
|
||||||
|
const newHash = crypto.createHash('md5').update(output).digest('hex');
|
||||||
|
const { proxyHash, id } = await db.listSettings();
|
||||||
|
if (proxyHash !== newHash) {
|
||||||
|
await db.prisma.setting.update({ where: { id }, data: { proxyHash: newHash } });
|
||||||
|
await haproxy.post(`v2/services/haproxy/configuration/raw`, {
|
||||||
|
searchParams: {
|
||||||
|
skip_version: true
|
||||||
|
},
|
||||||
|
body: output,
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'text/plain'
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
for (const service of services) {
|
|
||||||
const { fqdn, id, type, destinationDocker, destinationDockerId, updatedAt } = service;
|
|
||||||
if (destinationDockerId) {
|
|
||||||
const { engine } = destinationDocker;
|
|
||||||
const found = db.supportedServiceTypesAndVersions.find((a) => a.name === type);
|
|
||||||
if (found) {
|
|
||||||
const port = found.ports.main;
|
|
||||||
const publicPort = service[type]?.publicPort;
|
|
||||||
const isRunning = await checkContainer(engine, id);
|
|
||||||
if (fqdn) {
|
|
||||||
const domain = getDomain(fqdn);
|
|
||||||
const isHttps = fqdn.startsWith('https://');
|
|
||||||
const isWWW = fqdn.includes('www.');
|
|
||||||
const redirectValue = `${isHttps ? 'https://' : 'http://'}${domain}%[capture.req.uri]`;
|
|
||||||
if (isRunning) {
|
|
||||||
data.services.push({
|
|
||||||
id,
|
|
||||||
port,
|
|
||||||
publicPort,
|
|
||||||
domain,
|
|
||||||
isRunning,
|
|
||||||
isHttps,
|
|
||||||
redirectValue,
|
|
||||||
redirectTo: isWWW ? domain.replace('www.', '') : 'www.' + domain,
|
|
||||||
updatedAt: updatedAt.getTime()
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const { fqdn } = await db.prisma.setting.findFirst();
|
|
||||||
if (fqdn) {
|
|
||||||
const domain = getDomain(fqdn);
|
|
||||||
const isHttps = fqdn.startsWith('https://');
|
|
||||||
const isWWW = fqdn.includes('www.');
|
|
||||||
const redirectValue = `${isHttps ? 'https://' : 'http://'}${domain}%[capture.req.uri]`;
|
|
||||||
data.coolify.push({
|
|
||||||
id: dev ? 'host.docker.internal' : 'coolify',
|
|
||||||
port: 3000,
|
|
||||||
domain,
|
|
||||||
isHttps,
|
|
||||||
redirectValue,
|
|
||||||
redirectTo: isWWW ? domain.replace('www.', '') : 'www.' + domain
|
|
||||||
});
|
|
||||||
}
|
|
||||||
const output = mustache.render(template, data);
|
|
||||||
const newHash = crypto.createHash('md5').update(output).digest('hex');
|
|
||||||
const { proxyHash, id } = await db.listSettings();
|
|
||||||
if (proxyHash !== newHash) {
|
|
||||||
await db.prisma.setting.update({ where: { id }, data: { proxyHash: newHash } });
|
|
||||||
await haproxy.post(`v2/services/haproxy/configuration/raw`, {
|
|
||||||
searchParams: {
|
|
||||||
skip_version: true
|
|
||||||
},
|
|
||||||
body: output,
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'text/plain'
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
throw error;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
import { dev } from '$app/env';
|
import { dev } from '$app/env';
|
||||||
import { asyncExecShell, getEngine } from '$lib/common';
|
import { asyncExecShell, getEngine } from '$lib/common';
|
||||||
import got from 'got';
|
import got, { type Got, type Response } from 'got';
|
||||||
import * as db from '$lib/database';
|
import * as db from '$lib/database';
|
||||||
|
import type { DestinationDocker } from '@prisma/client';
|
||||||
|
|
||||||
const url = dev ? 'http://localhost:5555' : 'http://coolify-haproxy:5555';
|
const url = dev ? 'http://localhost:5555' : 'http://coolify-haproxy:5555';
|
||||||
|
|
||||||
@@ -9,7 +10,7 @@ export const defaultProxyImage = `coolify-haproxy-alpine:latest`;
|
|||||||
export const defaultProxyImageTcp = `coolify-haproxy-tcp-alpine:latest`;
|
export const defaultProxyImageTcp = `coolify-haproxy-tcp-alpine:latest`;
|
||||||
export const defaultProxyImageHttp = `coolify-haproxy-http-alpine:latest`;
|
export const defaultProxyImageHttp = `coolify-haproxy-http-alpine:latest`;
|
||||||
|
|
||||||
export async function haproxyInstance() {
|
export async function haproxyInstance(): Promise<Got> {
|
||||||
const { proxyPassword } = await db.listSettings();
|
const { proxyPassword } = await db.listSettings();
|
||||||
return got.extend({
|
return got.extend({
|
||||||
prefixUrl: url,
|
prefixUrl: url,
|
||||||
@@ -17,6 +18,7 @@ export async function haproxyInstance() {
|
|||||||
password: proxyPassword
|
password: proxyPassword
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getRawConfiguration(): Promise<RawHaproxyConfiguration> {
|
export async function getRawConfiguration(): Promise<RawHaproxyConfiguration> {
|
||||||
return await (await haproxyInstance()).get(`v2/services/haproxy/configuration/raw`).json();
|
return await (await haproxyInstance()).get(`v2/services/haproxy/configuration/raw`).json();
|
||||||
}
|
}
|
||||||
@@ -43,11 +45,12 @@ export async function getNextTransactionId(): Promise<string> {
|
|||||||
return newTransaction.id;
|
return newTransaction.id;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function completeTransaction(transactionId) {
|
export async function completeTransaction(transactionId: string): Promise<Response<string>> {
|
||||||
const haproxy = await haproxyInstance();
|
const haproxy = await haproxyInstance();
|
||||||
return await haproxy.put(`v2/services/haproxy/transactions/${transactionId}`);
|
return await haproxy.put(`v2/services/haproxy/transactions/${transactionId}`);
|
||||||
}
|
}
|
||||||
export async function deleteProxy({ id }) {
|
|
||||||
|
export async function deleteProxy({ id }: { id: string }): Promise<void> {
|
||||||
const haproxy = await haproxyInstance();
|
const haproxy = await haproxyInstance();
|
||||||
await checkHAProxy(haproxy);
|
await checkHAProxy(haproxy);
|
||||||
|
|
||||||
@@ -77,11 +80,12 @@ export async function deleteProxy({ id }) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function reloadHaproxy(engine) {
|
export async function reloadHaproxy(engine: string): Promise<{ stdout: string; stderr: string }> {
|
||||||
const host = getEngine(engine);
|
const host = getEngine(engine);
|
||||||
return await asyncExecShell(`DOCKER_HOST=${host} docker exec coolify-haproxy kill -HUP 1`);
|
return await asyncExecShell(`DOCKER_HOST=${host} docker exec coolify-haproxy kill -HUP 1`);
|
||||||
}
|
}
|
||||||
export async function checkHAProxy(haproxy?: any) {
|
|
||||||
|
export async function checkHAProxy(haproxy?: Got): Promise<void> {
|
||||||
if (!haproxy) haproxy = await haproxyInstance();
|
if (!haproxy) haproxy = await haproxyInstance();
|
||||||
try {
|
try {
|
||||||
await haproxy.get('v2/info');
|
await haproxy.get('v2/info');
|
||||||
@@ -93,7 +97,10 @@ export async function checkHAProxy(haproxy?: any) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function stopTcpHttpProxy(destinationDocker, publicPort) {
|
export async function stopTcpHttpProxy(
|
||||||
|
destinationDocker: DestinationDocker,
|
||||||
|
publicPort: number
|
||||||
|
): Promise<{ stdout: string; stderr: string } | Error> {
|
||||||
const { engine } = destinationDocker;
|
const { engine } = destinationDocker;
|
||||||
const host = getEngine(engine);
|
const host = getEngine(engine);
|
||||||
const containerName = `haproxy-for-${publicPort}`;
|
const containerName = `haproxy-for-${publicPort}`;
|
||||||
@@ -108,38 +115,57 @@ export async function stopTcpHttpProxy(destinationDocker, publicPort) {
|
|||||||
return error;
|
return error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
export async function startTcpProxy(destinationDocker, id, publicPort, privatePort) {
|
export async function startTcpProxy(
|
||||||
|
destinationDocker: DestinationDocker,
|
||||||
|
id: string,
|
||||||
|
publicPort: number,
|
||||||
|
privatePort: number,
|
||||||
|
volume?: string
|
||||||
|
): Promise<{ stdout: string; stderr: string } | Error> {
|
||||||
const { network, engine } = destinationDocker;
|
const { network, engine } = destinationDocker;
|
||||||
const host = getEngine(engine);
|
const host = getEngine(engine);
|
||||||
|
|
||||||
const containerName = `haproxy-for-${publicPort}`;
|
const containerName = `haproxy-for-${publicPort}`;
|
||||||
const found = await checkContainer(engine, containerName);
|
const found = await checkContainer(engine, containerName, true);
|
||||||
const foundDB = await checkContainer(engine, id);
|
const foundDependentContainer = await checkContainer(engine, id, true);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (foundDB && !found) {
|
if (foundDependentContainer && !found) {
|
||||||
const { stdout: Config } = await asyncExecShell(
|
const { stdout: Config } = await asyncExecShell(
|
||||||
`DOCKER_HOST="${host}" docker network inspect bridge --format '{{json .IPAM.Config }}'`
|
`DOCKER_HOST="${host}" docker network inspect bridge --format '{{json .IPAM.Config }}'`
|
||||||
);
|
);
|
||||||
const ip = JSON.parse(Config)[0].Gateway;
|
const ip = JSON.parse(Config)[0].Gateway;
|
||||||
return await asyncExecShell(
|
return await asyncExecShell(
|
||||||
`DOCKER_HOST=${host} docker run --restart always -e PORT=${publicPort} -e APP=${id} -e PRIVATE_PORT=${privatePort} --add-host 'host.docker.internal:host-gateway' --add-host 'host.docker.internal:${ip}' --network ${network} -p ${publicPort}:${publicPort} --name ${containerName} -d coollabsio/${defaultProxyImageTcp}`
|
`DOCKER_HOST=${host} docker run --restart always -e PORT=${publicPort} -e APP=${id} -e PRIVATE_PORT=${privatePort} --add-host 'host.docker.internal:host-gateway' --add-host 'host.docker.internal:${ip}' --network ${network} -p ${publicPort}:${publicPort} --name ${containerName} ${
|
||||||
|
volume ? `-v ${volume}` : ''
|
||||||
|
} -d coollabsio/${defaultProxyImageTcp}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (!foundDependentContainer && found) {
|
||||||
|
return await asyncExecShell(
|
||||||
|
`DOCKER_HOST=${host} docker stop -t 0 ${containerName} && docker rm ${containerName}`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return error;
|
return error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
export async function startHttpProxy(destinationDocker, id, publicPort, privatePort) {
|
|
||||||
|
export async function startHttpProxy(
|
||||||
|
destinationDocker: DestinationDocker,
|
||||||
|
id: string,
|
||||||
|
publicPort: number,
|
||||||
|
privatePort: number
|
||||||
|
): Promise<{ stdout: string; stderr: string } | Error> {
|
||||||
const { network, engine } = destinationDocker;
|
const { network, engine } = destinationDocker;
|
||||||
const host = getEngine(engine);
|
const host = getEngine(engine);
|
||||||
|
|
||||||
const containerName = `haproxy-for-${publicPort}`;
|
const containerName = `haproxy-for-${publicPort}`;
|
||||||
const found = await checkContainer(engine, containerName);
|
const found = await checkContainer(engine, containerName, true);
|
||||||
const foundDB = await checkContainer(engine, id);
|
const foundDependentContainer = await checkContainer(engine, id, true);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (foundDB && !found) {
|
if (foundDependentContainer && !found) {
|
||||||
const { stdout: Config } = await asyncExecShell(
|
const { stdout: Config } = await asyncExecShell(
|
||||||
`DOCKER_HOST="${host}" docker network inspect bridge --format '{{json .IPAM.Config }}'`
|
`DOCKER_HOST="${host}" docker network inspect bridge --format '{{json .IPAM.Config }}'`
|
||||||
);
|
);
|
||||||
@@ -148,13 +174,19 @@ export async function startHttpProxy(destinationDocker, id, publicPort, privateP
|
|||||||
`DOCKER_HOST=${host} docker run --restart always -e PORT=${publicPort} -e APP=${id} -e PRIVATE_PORT=${privatePort} --add-host 'host.docker.internal:host-gateway' --add-host 'host.docker.internal:${ip}' --network ${network} -p ${publicPort}:${publicPort} --name ${containerName} -d coollabsio/${defaultProxyImageHttp}`
|
`DOCKER_HOST=${host} docker run --restart always -e PORT=${publicPort} -e APP=${id} -e PRIVATE_PORT=${privatePort} --add-host 'host.docker.internal:host-gateway' --add-host 'host.docker.internal:${ip}' --network ${network} -p ${publicPort}:${publicPort} --name ${containerName} -d coollabsio/${defaultProxyImageHttp}`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
if (!foundDependentContainer && found) {
|
||||||
|
return await asyncExecShell(
|
||||||
|
`DOCKER_HOST=${host} docker stop -t 0 ${containerName} && docker rm ${containerName}`
|
||||||
|
);
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return error;
|
return error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
export async function startCoolifyProxy(engine) {
|
|
||||||
|
export async function startCoolifyProxy(engine: string): Promise<void> {
|
||||||
const host = getEngine(engine);
|
const host = getEngine(engine);
|
||||||
const found = await checkContainer(engine, 'coolify-haproxy');
|
const found = await checkContainer(engine, 'coolify-haproxy', true);
|
||||||
const { proxyPassword, proxyUser, id } = await db.listSettings();
|
const { proxyPassword, proxyUser, id } = await db.listSettings();
|
||||||
if (!found) {
|
if (!found) {
|
||||||
const { stdout: Config } = await asyncExecShell(
|
const { stdout: Config } = await asyncExecShell(
|
||||||
@@ -168,7 +200,26 @@ export async function startCoolifyProxy(engine) {
|
|||||||
}
|
}
|
||||||
await configureNetworkCoolifyProxy(engine);
|
await configureNetworkCoolifyProxy(engine);
|
||||||
}
|
}
|
||||||
export async function checkContainer(engine, container) {
|
|
||||||
|
export async function isContainerExited(engine: string, containerName: string): Promise<boolean> {
|
||||||
|
let isExited = false;
|
||||||
|
const host = getEngine(engine);
|
||||||
|
try {
|
||||||
|
const { stdout } = await asyncExecShell(
|
||||||
|
`DOCKER_HOST="${host}" docker inspect -f '{{.State.Status}}' ${containerName}`
|
||||||
|
);
|
||||||
|
if (stdout.trim() === 'exited') {
|
||||||
|
isExited = true;
|
||||||
|
}
|
||||||
|
} catch (error) {}
|
||||||
|
|
||||||
|
return isExited;
|
||||||
|
}
|
||||||
|
export async function checkContainer(
|
||||||
|
engine: string,
|
||||||
|
container: string,
|
||||||
|
remove: boolean = false
|
||||||
|
): Promise<boolean> {
|
||||||
const host = getEngine(engine);
|
const host = getEngine(engine);
|
||||||
let containerFound = false;
|
let containerFound = false;
|
||||||
|
|
||||||
@@ -178,8 +229,11 @@ export async function checkContainer(engine, container) {
|
|||||||
);
|
);
|
||||||
const parsedStdout = JSON.parse(stdout);
|
const parsedStdout = JSON.parse(stdout);
|
||||||
const status = parsedStdout.Status;
|
const status = parsedStdout.Status;
|
||||||
const isRunning = status === 'running' ? true : false;
|
const isRunning = status === 'running';
|
||||||
if (status === 'exited' || status === 'created') {
|
if (status === 'created') {
|
||||||
|
await asyncExecShell(`DOCKER_HOST="${host}" docker rm ${container}`);
|
||||||
|
}
|
||||||
|
if (remove && status === 'exited') {
|
||||||
await asyncExecShell(`DOCKER_HOST="${host}" docker rm ${container}`);
|
await asyncExecShell(`DOCKER_HOST="${host}" docker rm ${container}`);
|
||||||
}
|
}
|
||||||
if (isRunning) {
|
if (isRunning) {
|
||||||
@@ -191,7 +245,9 @@ export async function checkContainer(engine, container) {
|
|||||||
return containerFound;
|
return containerFound;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function stopCoolifyProxy(engine) {
|
export async function stopCoolifyProxy(
|
||||||
|
engine: string
|
||||||
|
): Promise<{ stdout: string; stderr: string } | Error> {
|
||||||
const host = getEngine(engine);
|
const host = getEngine(engine);
|
||||||
const found = await checkContainer(engine, 'coolify-haproxy');
|
const found = await checkContainer(engine, 'coolify-haproxy');
|
||||||
await db.setDestinationSettings({ engine, isCoolifyProxyUsed: false });
|
await db.setDestinationSettings({ engine, isCoolifyProxyUsed: false });
|
||||||
@@ -208,16 +264,18 @@ export async function stopCoolifyProxy(engine) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function configureNetworkCoolifyProxy(engine) {
|
export async function configureNetworkCoolifyProxy(engine: string): Promise<void> {
|
||||||
const host = getEngine(engine);
|
const host = getEngine(engine);
|
||||||
const destinations = await db.prisma.destinationDocker.findMany({ where: { engine } });
|
const destinations = await db.prisma.destinationDocker.findMany({ where: { engine } });
|
||||||
destinations.forEach(async (destination) => {
|
const { stdout: networks } = await asyncExecShell(
|
||||||
try {
|
`DOCKER_HOST="${host}" docker ps -a --filter name=coolify-haproxy --format '{{json .Networks}}'`
|
||||||
|
);
|
||||||
|
const configuredNetworks = networks.replace(/"/g, '').replace('\n', '').split(',');
|
||||||
|
for (const destination of destinations) {
|
||||||
|
if (!configuredNetworks.includes(destination.network)) {
|
||||||
await asyncExecShell(
|
await asyncExecShell(
|
||||||
`DOCKER_HOST="${host}" docker network connect ${destination.network} coolify-haproxy`
|
`DOCKER_HOST="${host}" docker network connect ${destination.network} coolify-haproxy`
|
||||||
);
|
);
|
||||||
} catch (err) {
|
|
||||||
// TODO: handle error
|
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,11 +2,9 @@ import { asyncExecShell, saveBuildLog } from '$lib/common';
|
|||||||
import got from 'got';
|
import got from 'got';
|
||||||
import jsonwebtoken from 'jsonwebtoken';
|
import jsonwebtoken from 'jsonwebtoken';
|
||||||
import * as db from '$lib/database';
|
import * as db from '$lib/database';
|
||||||
import { ErrorHandler } from '$lib/database';
|
|
||||||
|
|
||||||
export default async function ({
|
export default async function ({
|
||||||
applicationId,
|
applicationId,
|
||||||
debug,
|
|
||||||
workdir,
|
workdir,
|
||||||
githubAppId,
|
githubAppId,
|
||||||
repository,
|
repository,
|
||||||
@@ -14,41 +12,45 @@ export default async function ({
|
|||||||
htmlUrl,
|
htmlUrl,
|
||||||
branch,
|
branch,
|
||||||
buildId
|
buildId
|
||||||
}): Promise<any> {
|
}: {
|
||||||
try {
|
applicationId: string;
|
||||||
const url = htmlUrl.replace('https://', '').replace('http://', '');
|
workdir: string;
|
||||||
await saveBuildLog({ line: 'GitHub importer started.', buildId, applicationId });
|
githubAppId: string;
|
||||||
const { privateKey, appId, installationId } = await db.getUniqueGithubApp({ githubAppId });
|
repository: string;
|
||||||
const githubPrivateKey = privateKey.replace(/\\n/g, '\n').replace(/"/g, '');
|
apiUrl: string;
|
||||||
|
htmlUrl: string;
|
||||||
|
branch: string;
|
||||||
|
buildId: string;
|
||||||
|
}): Promise<string> {
|
||||||
|
const url = htmlUrl.replace('https://', '').replace('http://', '');
|
||||||
|
await saveBuildLog({ line: 'GitHub importer started.', buildId, applicationId });
|
||||||
|
const { privateKey, appId, installationId } = await db.getUniqueGithubApp({ githubAppId });
|
||||||
|
const githubPrivateKey = privateKey.replace(/\\n/g, '\n').replace(/"/g, '');
|
||||||
|
|
||||||
const payload = {
|
const payload = {
|
||||||
iat: Math.round(new Date().getTime() / 1000),
|
iat: Math.round(new Date().getTime() / 1000),
|
||||||
exp: Math.round(new Date().getTime() / 1000 + 60),
|
exp: Math.round(new Date().getTime() / 1000 + 60),
|
||||||
iss: appId
|
iss: appId
|
||||||
};
|
};
|
||||||
const jwtToken = jsonwebtoken.sign(payload, githubPrivateKey, {
|
const jwtToken = jsonwebtoken.sign(payload, githubPrivateKey, {
|
||||||
algorithm: 'RS256'
|
algorithm: 'RS256'
|
||||||
});
|
});
|
||||||
const { token } = await got
|
const { token } = await got
|
||||||
.post(`${apiUrl}/app/installations/${installationId}/access_tokens`, {
|
.post(`${apiUrl}/app/installations/${installationId}/access_tokens`, {
|
||||||
headers: {
|
headers: {
|
||||||
Authorization: `Bearer ${jwtToken}`,
|
Authorization: `Bearer ${jwtToken}`,
|
||||||
Accept: 'application/vnd.github.machine-man-preview+json'
|
Accept: 'application/vnd.github.machine-man-preview+json'
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.json();
|
.json();
|
||||||
await saveBuildLog({
|
await saveBuildLog({
|
||||||
line: `Cloning ${repository}:${branch} branch.`,
|
line: `Cloning ${repository}:${branch} branch.`,
|
||||||
buildId,
|
buildId,
|
||||||
applicationId
|
applicationId
|
||||||
});
|
});
|
||||||
await asyncExecShell(
|
await asyncExecShell(
|
||||||
`git clone -q -b ${branch} https://x-access-token:${token}@${url}/${repository}.git ${workdir}/ && cd ${workdir} && git submodule update --init --recursive && cd ..`
|
`git clone -q -b ${branch} https://x-access-token:${token}@${url}/${repository}.git ${workdir}/ && cd ${workdir} && git submodule update --init --recursive && git lfs pull && cd .. `
|
||||||
);
|
);
|
||||||
const { stdout: commit } = await asyncExecShell(`cd ${workdir}/ && git rev-parse HEAD`);
|
const { stdout: commit } = await asyncExecShell(`cd ${workdir}/ && git rev-parse HEAD`);
|
||||||
return commit.replace('\n', '');
|
return commit.replace('\n', '');
|
||||||
} catch (error) {
|
|
||||||
console.log({ error });
|
|
||||||
return ErrorHandler(error);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,8 +9,17 @@ export default async function ({
|
|||||||
branch,
|
branch,
|
||||||
buildId,
|
buildId,
|
||||||
privateSshKey
|
privateSshKey
|
||||||
}): Promise<any> {
|
}: {
|
||||||
const url = htmlUrl.replace('https://', '').replace('http://', '');
|
applicationId: string;
|
||||||
|
workdir: string;
|
||||||
|
repository: string;
|
||||||
|
htmlUrl: string;
|
||||||
|
branch: string;
|
||||||
|
buildId: string;
|
||||||
|
repodir: string;
|
||||||
|
privateSshKey: string;
|
||||||
|
}): Promise<string> {
|
||||||
|
const url = htmlUrl.replace('https://', '').replace('http://', '').replace(/\/$/, '');
|
||||||
await saveBuildLog({ line: 'GitLab importer started.', buildId, applicationId });
|
await saveBuildLog({ line: 'GitLab importer started.', buildId, applicationId });
|
||||||
await asyncExecShell(`echo '${privateSshKey}' > ${repodir}/id.rsa`);
|
await asyncExecShell(`echo '${privateSshKey}' > ${repodir}/id.rsa`);
|
||||||
await asyncExecShell(`chmod 600 ${repodir}/id.rsa`);
|
await asyncExecShell(`chmod 600 ${repodir}/id.rsa`);
|
||||||
@@ -22,7 +31,7 @@ export default async function ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
await asyncExecShell(
|
await asyncExecShell(
|
||||||
`git clone -q -b ${branch} git@${url}:${repository}.git --config core.sshCommand="ssh -q -i ${repodir}id.rsa -o StrictHostKeyChecking=no" ${workdir}/ && cd ${workdir}/ && git submodule update --init --recursive && cd ..`
|
`git clone -q -b ${branch} git@${url}:${repository}.git --config core.sshCommand="ssh -q -i ${repodir}id.rsa -o StrictHostKeyChecking=no" ${workdir}/ && cd ${workdir}/ && git submodule update --init --recursive && git lfs pull && cd .. `
|
||||||
);
|
);
|
||||||
const { stdout: commit } = await asyncExecShell(`cd ${workdir}/ && git rev-parse HEAD`);
|
const { stdout: commit } = await asyncExecShell(`cd ${workdir}/ && git rev-parse HEAD`);
|
||||||
return commit.replace('\n', '');
|
return commit.replace('\n', '');
|
||||||
|
|||||||
@@ -3,10 +3,16 @@ import { checkContainer, reloadHaproxy } from '$lib/haproxy';
|
|||||||
import * as db from '$lib/database';
|
import * as db from '$lib/database';
|
||||||
import { dev } from '$app/env';
|
import { dev } from '$app/env';
|
||||||
import cuid from 'cuid';
|
import cuid from 'cuid';
|
||||||
|
import fs from 'fs/promises';
|
||||||
import getPort, { portNumbers } from 'get-port';
|
import getPort, { portNumbers } from 'get-port';
|
||||||
|
import { supportedServiceTypesAndVersions } from '$lib/components/common';
|
||||||
|
import { promises as dns } from 'dns';
|
||||||
|
|
||||||
export async function letsEncrypt(domain, id = null, isCoolify = false) {
|
export async function letsEncrypt(domain: string, id?: string, isCoolify = false): Promise<void> {
|
||||||
try {
|
try {
|
||||||
|
const certbotImage =
|
||||||
|
process.arch === 'x64' ? 'certbot/certbot' : 'certbot/certbot:arm64v8-latest';
|
||||||
|
|
||||||
const data = await db.prisma.setting.findFirst();
|
const data = await db.prisma.setting.findFirst();
|
||||||
const { minPort, maxPort } = data;
|
const { minPort, maxPort } = data;
|
||||||
|
|
||||||
@@ -60,7 +66,7 @@ export async function letsEncrypt(domain, id = null, isCoolify = false) {
|
|||||||
if (found) return;
|
if (found) return;
|
||||||
|
|
||||||
await asyncExecShell(
|
await asyncExecShell(
|
||||||
`DOCKER_HOST=${host} docker run --rm --name certbot-${randomCuid} -p 9080:${randomPort} -v "coolify-letsencrypt:/etc/letsencrypt" certbot/certbot --logs-dir /etc/letsencrypt/logs certonly --standalone --preferred-challenges http --http-01-address 0.0.0.0 --http-01-port ${randomPort} -d ${nakedDomain} -d ${wwwDomain} --expand --agree-tos --non-interactive --register-unsafely-without-email ${
|
`DOCKER_HOST=${host} docker run --rm --name certbot-${randomCuid} -p 9080:${randomPort} -v "coolify-letsencrypt:/etc/letsencrypt" ${certbotImage} --logs-dir /etc/letsencrypt/logs certonly --standalone --preferred-challenges http --http-01-address 0.0.0.0 --http-01-port ${randomPort} -d ${nakedDomain} -d ${wwwDomain} --expand --agree-tos --non-interactive --register-unsafely-without-email ${
|
||||||
dev ? '--test-cert' : ''
|
dev ? '--test-cert' : ''
|
||||||
}`
|
}`
|
||||||
);
|
);
|
||||||
@@ -80,7 +86,7 @@ export async function letsEncrypt(domain, id = null, isCoolify = false) {
|
|||||||
}
|
}
|
||||||
if (found) return;
|
if (found) return;
|
||||||
await asyncExecShell(
|
await asyncExecShell(
|
||||||
`DOCKER_HOST=${host} docker run --rm --name certbot-${randomCuid} -p 9080:${randomPort} -v "coolify-letsencrypt:/etc/letsencrypt" certbot/certbot --logs-dir /etc/letsencrypt/logs certonly --standalone --preferred-challenges http --http-01-address 0.0.0.0 --http-01-port ${randomPort} -d ${domain} --expand --agree-tos --non-interactive --register-unsafely-without-email ${
|
`DOCKER_HOST=${host} docker run --rm --name certbot-${randomCuid} -p 9080:${randomPort} -v "coolify-letsencrypt:/etc/letsencrypt" ${certbotImage} --logs-dir /etc/letsencrypt/logs certonly --standalone --preferred-challenges http --http-01-address 0.0.0.0 --http-01-port ${randomPort} -d ${domain} --expand --agree-tos --non-interactive --register-unsafely-without-email ${
|
||||||
dev ? '--test-cert' : ''
|
dev ? '--test-cert' : ''
|
||||||
}`
|
}`
|
||||||
);
|
);
|
||||||
@@ -96,7 +102,7 @@ export async function letsEncrypt(domain, id = null, isCoolify = false) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function generateSSLCerts() {
|
export async function generateSSLCerts(): Promise<void> {
|
||||||
const ssls = [];
|
const ssls = [];
|
||||||
const applications = await db.prisma.application.findMany({
|
const applications = await db.prisma.application.findMany({
|
||||||
include: { destinationDocker: true, settings: true },
|
include: { destinationDocker: true, settings: true },
|
||||||
@@ -129,7 +135,7 @@ export async function generateSSLCerts() {
|
|||||||
.map((c) => c.replace(/"/g, ''));
|
.map((c) => c.replace(/"/g, ''));
|
||||||
if (containers.length > 0) {
|
if (containers.length > 0) {
|
||||||
for (const container of containers) {
|
for (const container of containers) {
|
||||||
let previewDomain = `${container.split('-')[1]}.${domain}`;
|
const previewDomain = `${container.split('-')[1]}.${domain}`;
|
||||||
if (isHttps) ssls.push({ domain: previewDomain, id, isCoolify: false });
|
if (isHttps) ssls.push({ domain: previewDomain, id, isCoolify: false });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -146,7 +152,8 @@ export async function generateSSLCerts() {
|
|||||||
plausibleAnalytics: true,
|
plausibleAnalytics: true,
|
||||||
vscodeserver: true,
|
vscodeserver: true,
|
||||||
wordpress: true,
|
wordpress: true,
|
||||||
ghost: true
|
ghost: true,
|
||||||
|
meiliSearch: true
|
||||||
},
|
},
|
||||||
orderBy: { createdAt: 'desc' }
|
orderBy: { createdAt: 'desc' }
|
||||||
});
|
});
|
||||||
@@ -160,7 +167,7 @@ export async function generateSSLCerts() {
|
|||||||
type,
|
type,
|
||||||
destinationDocker: { engine }
|
destinationDocker: { engine }
|
||||||
} = service;
|
} = service;
|
||||||
const found = db.supportedServiceTypesAndVersions.find((a) => a.name === type);
|
const found = supportedServiceTypesAndVersions.find((a) => a.name === type);
|
||||||
if (found) {
|
if (found) {
|
||||||
const domain = getDomain(fqdn);
|
const domain = getDomain(fqdn);
|
||||||
const isHttps = fqdn.startsWith('https://');
|
const isHttps = fqdn.startsWith('https://');
|
||||||
@@ -181,12 +188,89 @@ export async function generateSSLCerts() {
|
|||||||
if (isHttps) ssls.push({ domain, id: 'coolify', isCoolify: true });
|
if (isHttps) ssls.push({ domain, id: 'coolify', isCoolify: true });
|
||||||
}
|
}
|
||||||
if (ssls.length > 0) {
|
if (ssls.length > 0) {
|
||||||
|
const sslDir = dev ? '/tmp/ssl' : '/app/ssl';
|
||||||
|
if (dev) {
|
||||||
|
try {
|
||||||
|
await asyncExecShell(`mkdir -p ${sslDir}`);
|
||||||
|
} catch (error) {
|
||||||
|
//
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const files = await fs.readdir(sslDir);
|
||||||
|
let certificates = [];
|
||||||
|
if (files.length > 0) {
|
||||||
|
for (const file of files) {
|
||||||
|
file.endsWith('.pem') && certificates.push(file.replace(/\.pem$/, ''));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const resolver = new dns.Resolver({ timeout: 2000 });
|
||||||
|
resolver.setServers(['8.8.8.8', '1.1.1.1']);
|
||||||
|
let ipv4, ipv6;
|
||||||
|
try {
|
||||||
|
ipv4 = await (await asyncExecShell(`curl -4s https://ifconfig.io`)).stdout;
|
||||||
|
} catch (error) {}
|
||||||
|
try {
|
||||||
|
ipv6 = await (await asyncExecShell(`curl -6s https://ifconfig.io`)).stdout;
|
||||||
|
} catch (error) {}
|
||||||
for (const ssl of ssls) {
|
for (const ssl of ssls) {
|
||||||
if (!dev) {
|
if (!dev) {
|
||||||
console.log('Checking SSL for', ssl.domain);
|
if (
|
||||||
await letsEncrypt(ssl.domain, ssl.id, ssl.isCoolify);
|
certificates.includes(ssl.domain) ||
|
||||||
|
certificates.includes(ssl.domain.replace('www.', ''))
|
||||||
|
) {
|
||||||
|
// console.log(`Certificate for ${ssl.domain} already exists`);
|
||||||
|
} else {
|
||||||
|
// Checking DNS entry before generating certificate
|
||||||
|
if (ipv4 || ipv6) {
|
||||||
|
let domains4 = [];
|
||||||
|
let domains6 = [];
|
||||||
|
try {
|
||||||
|
domains4 = await resolver.resolve4(ssl.domain);
|
||||||
|
} catch (error) {}
|
||||||
|
try {
|
||||||
|
domains6 = await resolver.resolve6(ssl.domain);
|
||||||
|
} catch (error) {}
|
||||||
|
if (domains4.length > 0 || domains6.length > 0) {
|
||||||
|
if (
|
||||||
|
(ipv4 && domains4.includes(ipv4.replace('\n', ''))) ||
|
||||||
|
(ipv6 && domains6.includes(ipv6.replace('\n', '')))
|
||||||
|
) {
|
||||||
|
console.log('Generating SSL for', ssl.domain);
|
||||||
|
return await letsEncrypt(ssl.domain, ssl.id, ssl.isCoolify);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
console.log('DNS settings is incorrect for', ssl.domain, 'skipping.');
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
console.log('Checking SSL for', ssl.domain);
|
if (
|
||||||
|
certificates.includes(ssl.domain) ||
|
||||||
|
certificates.includes(ssl.domain.replace('www.', ''))
|
||||||
|
) {
|
||||||
|
console.log(`Certificate for ${ssl.domain} already exists`);
|
||||||
|
} else {
|
||||||
|
// Checking DNS entry before generating certificate
|
||||||
|
if (ipv4 || ipv6) {
|
||||||
|
let domains4 = [];
|
||||||
|
let domains6 = [];
|
||||||
|
try {
|
||||||
|
domains4 = await resolver.resolve4(ssl.domain);
|
||||||
|
} catch (error) {}
|
||||||
|
try {
|
||||||
|
domains6 = await resolver.resolve6(ssl.domain);
|
||||||
|
} catch (error) {}
|
||||||
|
if (domains4.length > 0 || domains6.length > 0) {
|
||||||
|
if (
|
||||||
|
(ipv4 && domains4.includes(ipv4.replace('\n', ''))) ||
|
||||||
|
(ipv6 && domains6.includes(ipv6.replace('\n', '')))
|
||||||
|
) {
|
||||||
|
console.log('Generating SSL for', ssl.domain);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
console.log('DNS settings is incorrect for', ssl.domain, 'skipping.');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,30 +20,22 @@ import {
|
|||||||
setDefaultConfiguration
|
setDefaultConfiguration
|
||||||
} from '$lib/buildPacks/common';
|
} from '$lib/buildPacks/common';
|
||||||
import yaml from 'js-yaml';
|
import yaml from 'js-yaml';
|
||||||
|
import type { Job } from 'bullmq';
|
||||||
|
import type { BuilderJob } from '$lib/types/builderJob';
|
||||||
|
|
||||||
export default async function (job) {
|
import type { ComposeFile } from '$lib/types/composeFile';
|
||||||
/*
|
|
||||||
Edge cases:
|
export default async function (job: Job<BuilderJob, void, string>): Promise<void> {
|
||||||
1 - Change build pack and redeploy, what should happen?
|
const {
|
||||||
*/
|
|
||||||
let {
|
|
||||||
id: applicationId,
|
id: applicationId,
|
||||||
repository,
|
repository,
|
||||||
branch,
|
|
||||||
buildPack,
|
|
||||||
name,
|
name,
|
||||||
destinationDocker,
|
destinationDocker,
|
||||||
destinationDockerId,
|
destinationDockerId,
|
||||||
gitSource,
|
gitSource,
|
||||||
build_id: buildId,
|
build_id: buildId,
|
||||||
configHash,
|
configHash,
|
||||||
port,
|
|
||||||
installCommand,
|
|
||||||
buildCommand,
|
|
||||||
startCommand,
|
|
||||||
fqdn,
|
fqdn,
|
||||||
baseDirectory,
|
|
||||||
publishDirectory,
|
|
||||||
projectId,
|
projectId,
|
||||||
secrets,
|
secrets,
|
||||||
phpModules,
|
phpModules,
|
||||||
@@ -54,14 +46,27 @@ export default async function (job) {
|
|||||||
persistentStorage,
|
persistentStorage,
|
||||||
pythonWSGI,
|
pythonWSGI,
|
||||||
pythonModule,
|
pythonModule,
|
||||||
pythonVariable
|
pythonVariable,
|
||||||
|
denoOptions
|
||||||
|
} = job.data;
|
||||||
|
let {
|
||||||
|
branch,
|
||||||
|
buildPack,
|
||||||
|
port,
|
||||||
|
installCommand,
|
||||||
|
buildCommand,
|
||||||
|
startCommand,
|
||||||
|
baseDirectory,
|
||||||
|
publishDirectory,
|
||||||
|
dockerFileLocation,
|
||||||
|
denoMainFile
|
||||||
} = job.data;
|
} = job.data;
|
||||||
const { debug } = settings;
|
const { debug } = settings;
|
||||||
|
|
||||||
await asyncSleep(500);
|
await asyncSleep(500);
|
||||||
await db.prisma.build.updateMany({
|
await db.prisma.build.updateMany({
|
||||||
where: {
|
where: {
|
||||||
status: 'queued',
|
status: { in: ['queued', 'running'] },
|
||||||
id: { not: buildId },
|
id: { not: buildId },
|
||||||
applicationId,
|
applicationId,
|
||||||
createdAt: { lt: new Date(new Date().getTime() - 60 * 60 * 1000) }
|
createdAt: { lt: new Date(new Date().getTime() - 60 * 60 * 1000) }
|
||||||
@@ -70,7 +75,7 @@ export default async function (job) {
|
|||||||
});
|
});
|
||||||
let imageId = applicationId;
|
let imageId = applicationId;
|
||||||
let domain = getDomain(fqdn);
|
let domain = getDomain(fqdn);
|
||||||
let volumes =
|
const volumes =
|
||||||
persistentStorage?.map((storage) => {
|
persistentStorage?.map((storage) => {
|
||||||
return `${applicationId}${storage.path.replace(/\//gi, '-')}:${
|
return `${applicationId}${storage.path.replace(/\//gi, '-')}:${
|
||||||
buildPack !== 'docker' ? '/app' : ''
|
buildPack !== 'docker' ? '/app' : ''
|
||||||
@@ -105,8 +110,10 @@ export default async function (job) {
|
|||||||
buildCommand = configuration.buildCommand;
|
buildCommand = configuration.buildCommand;
|
||||||
publishDirectory = configuration.publishDirectory;
|
publishDirectory = configuration.publishDirectory;
|
||||||
baseDirectory = configuration.baseDirectory;
|
baseDirectory = configuration.baseDirectory;
|
||||||
|
dockerFileLocation = configuration.dockerFileLocation;
|
||||||
|
denoMainFile = configuration.denoMainFile;
|
||||||
|
|
||||||
let commit = await importers[gitSource.type]({
|
const commit = await importers[gitSource.type]({
|
||||||
applicationId,
|
applicationId,
|
||||||
debug,
|
debug,
|
||||||
workdir,
|
workdir,
|
||||||
@@ -207,15 +214,16 @@ export default async function (job) {
|
|||||||
phpModules,
|
phpModules,
|
||||||
pythonWSGI,
|
pythonWSGI,
|
||||||
pythonModule,
|
pythonModule,
|
||||||
pythonVariable
|
pythonVariable,
|
||||||
|
dockerFileLocation,
|
||||||
|
denoMainFile,
|
||||||
|
denoOptions
|
||||||
});
|
});
|
||||||
else {
|
else {
|
||||||
await saveBuildLog({ line: `Build pack ${buildPack} not found`, buildId, applicationId });
|
await saveBuildLog({ line: `Build pack ${buildPack} not found`, buildId, applicationId });
|
||||||
throw new Error(`Build pack ${buildPack} not found.`);
|
throw new Error(`Build pack ${buildPack} not found.`);
|
||||||
}
|
}
|
||||||
deployNeeded = true;
|
|
||||||
} else {
|
} else {
|
||||||
deployNeeded = false;
|
|
||||||
await saveBuildLog({ line: 'Nothing changed.', buildId, applicationId });
|
await saveBuildLog({ line: 'Nothing changed.', buildId, applicationId });
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -274,7 +282,7 @@ export default async function (job) {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
const compose = {
|
const composeFile: ComposeFile = {
|
||||||
version: '3.8',
|
version: '3.8',
|
||||||
services: {
|
services: {
|
||||||
[imageId]: {
|
[imageId]: {
|
||||||
@@ -283,9 +291,20 @@ export default async function (job) {
|
|||||||
volumes,
|
volumes,
|
||||||
env_file: envFound ? [`${workdir}/.env`] : [],
|
env_file: envFound ? [`${workdir}/.env`] : [],
|
||||||
networks: [docker.network],
|
networks: [docker.network],
|
||||||
labels: labels,
|
labels,
|
||||||
depends_on: [],
|
depends_on: [],
|
||||||
restart: 'always'
|
restart: 'always',
|
||||||
|
// logging: {
|
||||||
|
// driver: 'fluentd',
|
||||||
|
// },
|
||||||
|
deploy: {
|
||||||
|
restart_policy: {
|
||||||
|
condition: 'on-failure',
|
||||||
|
delay: '5s',
|
||||||
|
max_attempts: 3,
|
||||||
|
window: '120s'
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
networks: {
|
networks: {
|
||||||
@@ -295,7 +314,7 @@ export default async function (job) {
|
|||||||
},
|
},
|
||||||
volumes: Object.assign({}, ...composeVolumes)
|
volumes: Object.assign({}, ...composeVolumes)
|
||||||
};
|
};
|
||||||
await fs.writeFile(`${workdir}/docker-compose.yml`, yaml.dump(compose));
|
await fs.writeFile(`${workdir}/docker-compose.yml`, yaml.dump(composeFile));
|
||||||
await asyncExecShell(
|
await asyncExecShell(
|
||||||
`DOCKER_HOST=${host} docker compose --project-directory ${workdir} up -d`
|
`DOCKER_HOST=${host} docker compose --project-directory ${workdir} up -d`
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,11 +1,10 @@
|
|||||||
import { dev } from '$app/env';
|
|
||||||
import { asyncExecShell, getEngine, version } from '$lib/common';
|
import { asyncExecShell, getEngine, version } from '$lib/common';
|
||||||
import { prisma } from '$lib/database';
|
import { prisma } from '$lib/database';
|
||||||
import { defaultProxyImageHttp, defaultProxyImageTcp } from '$lib/haproxy';
|
export default async function (): Promise<void> {
|
||||||
export default async function () {
|
|
||||||
const destinationDockers = await prisma.destinationDocker.findMany();
|
const destinationDockers = await prisma.destinationDocker.findMany();
|
||||||
for (const destinationDocker of destinationDockers) {
|
const engines = [...new Set(destinationDockers.map(({ engine }) => engine))];
|
||||||
const host = getEngine(destinationDocker.engine);
|
for (const engine of engines) {
|
||||||
|
const host = getEngine(engine);
|
||||||
// Cleanup old coolify images
|
// Cleanup old coolify images
|
||||||
try {
|
try {
|
||||||
let { stdout: images } = await asyncExecShell(
|
let { stdout: images } = await asyncExecShell(
|
||||||
@@ -16,56 +15,23 @@ export default async function () {
|
|||||||
await asyncExecShell(`DOCKER_HOST=${host} docker rmi -f ${images}`);
|
await asyncExecShell(`DOCKER_HOST=${host} docker rmi -f ${images}`);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(error);
|
//console.log(error);
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
await asyncExecShell(`DOCKER_HOST=${host} docker container prune -f`);
|
await asyncExecShell(`DOCKER_HOST=${host} docker container prune -f`);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(error);
|
//console.log(error);
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
await asyncExecShell(`DOCKER_HOST=${host} docker image prune -f --filter "until=2h"`);
|
await asyncExecShell(`DOCKER_HOST=${host} docker image prune -f --filter "until=2h"`);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(error);
|
//console.log(error);
|
||||||
|
}
|
||||||
|
// Cleanup old images older than a day
|
||||||
|
try {
|
||||||
|
await asyncExecShell(`DOCKER_HOST=${host} docker image prune --filter "until=72h" -a -f`);
|
||||||
|
} catch (error) {
|
||||||
|
//console.log(error);
|
||||||
}
|
}
|
||||||
// Tagging images with labels
|
|
||||||
// try {
|
|
||||||
// const images = [
|
|
||||||
// `coollabsio/${defaultProxyImageTcp}`,
|
|
||||||
// `coollabsio/${defaultProxyImageHttp}`,
|
|
||||||
// 'certbot/certbot:latest',
|
|
||||||
// 'node:16.14.0-alpine',
|
|
||||||
// 'alpine:latest',
|
|
||||||
// 'nginx:stable-alpine',
|
|
||||||
// 'node:lts',
|
|
||||||
// 'php:apache',
|
|
||||||
// 'rust:latest'
|
|
||||||
// ];
|
|
||||||
// for (const image of images) {
|
|
||||||
// try {
|
|
||||||
// await asyncExecShell(`DOCKER_HOST=${host} docker image inspect ${image}`);
|
|
||||||
// } catch (error) {
|
|
||||||
// await asyncExecShell(
|
|
||||||
// `DOCKER_HOST=${host} docker pull ${image} && echo "FROM ${image}" | docker build --label coolify.image="true" -t "${image}" -`
|
|
||||||
// );
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// } catch (error) {}
|
|
||||||
// if (!dev) {
|
|
||||||
// // Cleanup images that are not managed by coolify
|
|
||||||
// try {
|
|
||||||
// await asyncExecShell(
|
|
||||||
// `DOCKER_HOST=${host} docker image prune --filter 'label!=coolify.image=true' -a -f`
|
|
||||||
// );
|
|
||||||
// } catch (error) {
|
|
||||||
// console.log(error);
|
|
||||||
// }
|
|
||||||
// // Cleanup old images >3 days
|
|
||||||
// try {
|
|
||||||
// await asyncExecShell(`DOCKER_HOST=${host} docker image prune --filter "until=72h" -a -f`);
|
|
||||||
// } catch (error) {
|
|
||||||
// console.log(error);
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import * as Bullmq from 'bullmq';
|
import * as Bullmq from 'bullmq';
|
||||||
import { default as ProdBullmq, Job, QueueEvents, QueueScheduler } from 'bullmq';
|
import { default as ProdBullmq, QueueScheduler } from 'bullmq';
|
||||||
import cuid from 'cuid';
|
|
||||||
import { dev } from '$app/env';
|
import { dev } from '$app/env';
|
||||||
import { prisma } from '$lib/database';
|
import { prisma } from '$lib/database';
|
||||||
|
|
||||||
@@ -8,6 +7,7 @@ import builder from './builder';
|
|||||||
import logger from './logger';
|
import logger from './logger';
|
||||||
import cleanup from './cleanup';
|
import cleanup from './cleanup';
|
||||||
import proxy from './proxy';
|
import proxy from './proxy';
|
||||||
|
import proxyTcpHttp from './proxyTcpHttp';
|
||||||
import ssl from './ssl';
|
import ssl from './ssl';
|
||||||
import sslrenewal from './sslrenewal';
|
import sslrenewal from './sslrenewal';
|
||||||
|
|
||||||
@@ -28,19 +28,22 @@ const connectionOptions = {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const cron = async () => {
|
const cron = async (): Promise<void> => {
|
||||||
new QueueScheduler('proxy', connectionOptions);
|
new QueueScheduler('proxy', connectionOptions);
|
||||||
|
new QueueScheduler('proxyTcpHttp', connectionOptions);
|
||||||
new QueueScheduler('cleanup', connectionOptions);
|
new QueueScheduler('cleanup', connectionOptions);
|
||||||
new QueueScheduler('ssl', connectionOptions);
|
new QueueScheduler('ssl', connectionOptions);
|
||||||
new QueueScheduler('sslRenew', connectionOptions);
|
new QueueScheduler('sslRenew', connectionOptions);
|
||||||
|
|
||||||
const queue = {
|
const queue = {
|
||||||
proxy: new Queue('proxy', { ...connectionOptions }),
|
proxy: new Queue('proxy', { ...connectionOptions }),
|
||||||
|
proxyTcpHttp: new Queue('proxyTcpHttp', { ...connectionOptions }),
|
||||||
cleanup: new Queue('cleanup', { ...connectionOptions }),
|
cleanup: new Queue('cleanup', { ...connectionOptions }),
|
||||||
ssl: new Queue('ssl', { ...connectionOptions }),
|
ssl: new Queue('ssl', { ...connectionOptions }),
|
||||||
sslRenew: new Queue('sslRenew', { ...connectionOptions })
|
sslRenew: new Queue('sslRenew', { ...connectionOptions })
|
||||||
};
|
};
|
||||||
await queue.proxy.drain();
|
await queue.proxy.drain();
|
||||||
|
await queue.proxyTcpHttp.drain();
|
||||||
await queue.cleanup.drain();
|
await queue.cleanup.drain();
|
||||||
await queue.ssl.drain();
|
await queue.ssl.drain();
|
||||||
await queue.sslRenew.drain();
|
await queue.sslRenew.drain();
|
||||||
@@ -55,6 +58,16 @@ const cron = async () => {
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
new Worker(
|
||||||
|
'proxyTcpHttp',
|
||||||
|
async () => {
|
||||||
|
await proxyTcpHttp();
|
||||||
|
},
|
||||||
|
{
|
||||||
|
...connectionOptions
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
new Worker(
|
new Worker(
|
||||||
'ssl',
|
'ssl',
|
||||||
async () => {
|
async () => {
|
||||||
@@ -86,21 +99,10 @@ const cron = async () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
await queue.proxy.add('proxy', {}, { repeat: { every: 10000 } });
|
await queue.proxy.add('proxy', {}, { repeat: { every: 10000 } });
|
||||||
|
await queue.proxyTcpHttp.add('proxyTcpHttp', {}, { repeat: { every: 10000 } });
|
||||||
await queue.ssl.add('ssl', {}, { repeat: { every: dev ? 10000 : 60000 } });
|
await queue.ssl.add('ssl', {}, { repeat: { every: dev ? 10000 : 60000 } });
|
||||||
if (!dev) await queue.cleanup.add('cleanup', {}, { repeat: { every: 300000 } });
|
if (!dev) await queue.cleanup.add('cleanup', {}, { repeat: { every: 300000 } });
|
||||||
await queue.sslRenew.add('sslRenew', {}, { repeat: { every: 1800000 } });
|
await queue.sslRenew.add('sslRenew', {}, { repeat: { every: 1800000 } });
|
||||||
|
|
||||||
const events = {
|
|
||||||
proxy: new QueueEvents('proxy', { ...connectionOptions }),
|
|
||||||
ssl: new QueueEvents('ssl', { ...connectionOptions })
|
|
||||||
};
|
|
||||||
|
|
||||||
events.proxy.on('completed', (data) => {
|
|
||||||
// console.log(data)
|
|
||||||
});
|
|
||||||
events.ssl.on('completed', (data) => {
|
|
||||||
// console.log(data)
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
cron().catch((error) => {
|
cron().catch((error) => {
|
||||||
console.log('cron failed to start');
|
console.log('cron failed to start');
|
||||||
@@ -118,10 +120,14 @@ buildWorker.on('completed', async (job: Bullmq.Job) => {
|
|||||||
try {
|
try {
|
||||||
await prisma.build.update({ where: { id: job.data.build_id }, data: { status: 'success' } });
|
await prisma.build.update({ where: { id: job.data.build_id }, data: { status: 'success' } });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
setTimeout(async () => {
|
||||||
|
await prisma.build.update({ where: { id: job.data.build_id }, data: { status: 'success' } });
|
||||||
|
}, 1234);
|
||||||
console.log(error);
|
console.log(error);
|
||||||
} finally {
|
} finally {
|
||||||
const workdir = `/tmp/build-sources/${job.data.repository}/${job.data.build_id}`;
|
const workdir = `/tmp/build-sources/${job.data.repository}/${job.data.build_id}`;
|
||||||
if (!dev) await asyncExecShell(`rm -fr ${workdir}`);
|
if (!dev) await asyncExecShell(`rm -fr ${workdir}`);
|
||||||
|
await prisma.build.update({ where: { id: job.data.build_id }, data: { status: 'success' } });
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
});
|
});
|
||||||
@@ -130,10 +136,14 @@ buildWorker.on('failed', async (job: Bullmq.Job, failedReason) => {
|
|||||||
try {
|
try {
|
||||||
await prisma.build.update({ where: { id: job.data.build_id }, data: { status: 'failed' } });
|
await prisma.build.update({ where: { id: job.data.build_id }, data: { status: 'failed' } });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
setTimeout(async () => {
|
||||||
|
await prisma.build.update({ where: { id: job.data.build_id }, data: { status: 'failed' } });
|
||||||
|
}, 1234);
|
||||||
console.log(error);
|
console.log(error);
|
||||||
} finally {
|
} finally {
|
||||||
const workdir = `/tmp/build-sources/${job.data.repository}`;
|
const workdir = `/tmp/build-sources/${job.data.repository}`;
|
||||||
if (!dev) await asyncExecShell(`rm -fr ${workdir}`);
|
if (!dev) await asyncExecShell(`rm -fr ${workdir}`);
|
||||||
|
await prisma.build.update({ where: { id: job.data.build_id }, data: { status: 'failed' } });
|
||||||
}
|
}
|
||||||
await saveBuildLog({
|
await saveBuildLog({
|
||||||
line: 'Failed to deploy!',
|
line: 'Failed to deploy!',
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
import { prisma } from '$lib/database';
|
import { prisma } from '$lib/database';
|
||||||
import { dev } from '$app/env';
|
import { dev } from '$app/env';
|
||||||
|
import type { Job } from 'bullmq';
|
||||||
|
|
||||||
export default async function (job) {
|
export default async function (job: Job): Promise<void> {
|
||||||
const { line, applicationId, buildId } = job.data;
|
const { line, applicationId, buildId } = job.data;
|
||||||
if (dev) console.debug(`[${applicationId}] ${line}`);
|
if (dev) console.debug(`[${applicationId}] ${line}`);
|
||||||
await prisma.buildLog.create({ data: { line, buildId, time: Number(job.id), applicationId } });
|
await prisma.buildLog.create({ data: { line, buildId, time: Number(job.id), applicationId } });
|
||||||
|
|||||||
@@ -1,7 +1,10 @@
|
|||||||
import { ErrorHandler } from '$lib/database';
|
import { ErrorHandler } from '$lib/database';
|
||||||
import { configureHAProxy } from '$lib/haproxy/configuration';
|
import { configureHAProxy } from '$lib/haproxy/configuration';
|
||||||
|
|
||||||
export default async function () {
|
export default async function (): Promise<void | {
|
||||||
|
status: number;
|
||||||
|
body: { message: string; error: string };
|
||||||
|
}> {
|
||||||
try {
|
try {
|
||||||
return await configureHAProxy();
|
return await configureHAProxy();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
55
src/lib/queues/proxyTcpHttp.ts
Normal file
55
src/lib/queues/proxyTcpHttp.ts
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
import { ErrorHandler, generateDatabaseConfiguration, prisma } from '$lib/database';
|
||||||
|
import { startCoolifyProxy, startHttpProxy, startTcpProxy } from '$lib/haproxy';
|
||||||
|
|
||||||
|
export default async function (): Promise<void | {
|
||||||
|
status: number;
|
||||||
|
body: { message: string; error: string };
|
||||||
|
}> {
|
||||||
|
try {
|
||||||
|
// Coolify Proxy
|
||||||
|
const localDocker = await prisma.destinationDocker.findFirst({
|
||||||
|
where: { engine: '/var/run/docker.sock' }
|
||||||
|
});
|
||||||
|
if (localDocker && localDocker.isCoolifyProxyUsed) {
|
||||||
|
await startCoolifyProxy('/var/run/docker.sock');
|
||||||
|
}
|
||||||
|
// TCP Proxies
|
||||||
|
const databasesWithPublicPort = await prisma.database.findMany({
|
||||||
|
where: { publicPort: { not: null } },
|
||||||
|
include: { settings: true, destinationDocker: true }
|
||||||
|
});
|
||||||
|
for (const database of databasesWithPublicPort) {
|
||||||
|
const { destinationDockerId, destinationDocker, publicPort, id } = database;
|
||||||
|
if (destinationDockerId) {
|
||||||
|
const { privatePort } = generateDatabaseConfiguration(database);
|
||||||
|
await startTcpProxy(destinationDocker, id, publicPort, privatePort);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const wordpressWithFtp = await prisma.wordpress.findMany({
|
||||||
|
where: { ftpPublicPort: { not: null } },
|
||||||
|
include: { service: { include: { destinationDocker: true } } }
|
||||||
|
});
|
||||||
|
for (const ftp of wordpressWithFtp) {
|
||||||
|
const { service, ftpPublicPort } = ftp;
|
||||||
|
const { destinationDockerId, destinationDocker, id } = service;
|
||||||
|
if (destinationDockerId) {
|
||||||
|
await startTcpProxy(destinationDocker, `${id}-ftp`, ftpPublicPort, 22);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// HTTP Proxies
|
||||||
|
const minioInstances = await prisma.minio.findMany({
|
||||||
|
where: { publicPort: { not: null } },
|
||||||
|
include: { service: { include: { destinationDocker: true } } }
|
||||||
|
});
|
||||||
|
for (const minio of minioInstances) {
|
||||||
|
const { service, publicPort } = minio;
|
||||||
|
const { destinationDockerId, destinationDocker, id } = service;
|
||||||
|
if (destinationDockerId) {
|
||||||
|
await startHttpProxy(destinationDocker, id, publicPort, 9000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
return ErrorHandler(error.response?.body || error);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import { generateSSLCerts } from '$lib/letsencrypt';
|
import { generateSSLCerts } from '$lib/letsencrypt';
|
||||||
|
|
||||||
export default async function () {
|
export default async function (): Promise<void> {
|
||||||
try {
|
try {
|
||||||
return await generateSSLCerts();
|
return await generateSSLCerts();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
@@ -1,13 +1,9 @@
|
|||||||
import { asyncExecShell } from '$lib/common';
|
import { asyncExecShell } from '$lib/common';
|
||||||
import { reloadHaproxy } from '$lib/haproxy';
|
import { reloadHaproxy } from '$lib/haproxy';
|
||||||
|
|
||||||
export default async function () {
|
export default async function (): Promise<void> {
|
||||||
try {
|
await asyncExecShell(
|
||||||
await asyncExecShell(
|
`docker run --rm --name certbot-renewal -v "coolify-letsencrypt:/etc/letsencrypt" certbot/certbot --logs-dir /etc/letsencrypt/logs renew`
|
||||||
`docker run --rm --name certbot-renewal -v "coolify-letsencrypt:/etc/letsencrypt" certbot/certbot --logs-dir /etc/letsencrypt/logs renew`
|
);
|
||||||
);
|
await reloadHaproxy('unix:///var/run/docker.sock');
|
||||||
await reloadHaproxy('unix:///var/run/docker.sock');
|
|
||||||
} catch (error) {
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
export const publicPaths = [
|
export const publicPaths = [
|
||||||
'/login',
|
'/login',
|
||||||
'/register',
|
'/register',
|
||||||
'/reset',
|
|
||||||
'/reset/password',
|
|
||||||
'/webhooks/success',
|
'/webhooks/success',
|
||||||
'/webhooks/github',
|
'/webhooks/github',
|
||||||
'/webhooks/github/install',
|
'/webhooks/github/install',
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
import { writable } from 'svelte/store';
|
import { writable, type Writable } from 'svelte/store';
|
||||||
|
|
||||||
export const gitTokens = writable({
|
export const gitTokens: Writable<{ githubToken: string | null; gitlabToken: string | null }> =
|
||||||
githubToken: null,
|
writable({
|
||||||
gitlabToken: null
|
githubToken: null,
|
||||||
});
|
gitlabToken: null
|
||||||
|
});
|
||||||
|
export const disabledButton: Writable<boolean> = writable(false);
|
||||||
|
|||||||
54
src/lib/types/builderJob.ts
Normal file
54
src/lib/types/builderJob.ts
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
import type { DestinationDocker, GithubApp, GitlabApp, GitSource, Secret } from '@prisma/client';
|
||||||
|
|
||||||
|
export type BuilderJob = {
|
||||||
|
build_id: string;
|
||||||
|
type: BuildType;
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
fqdn: string;
|
||||||
|
repository: string;
|
||||||
|
configHash: unknown;
|
||||||
|
branch: string;
|
||||||
|
buildPack: BuildPackName;
|
||||||
|
projectId: number;
|
||||||
|
port: number;
|
||||||
|
installCommand: string;
|
||||||
|
buildCommand?: string;
|
||||||
|
startCommand?: string;
|
||||||
|
baseDirectory: string;
|
||||||
|
publishDirectory: string;
|
||||||
|
phpModules: string;
|
||||||
|
pythonWSGI: string;
|
||||||
|
pythonModule: string;
|
||||||
|
pythonVariable: string;
|
||||||
|
dockerFileLocation: string;
|
||||||
|
denoMainFile: string;
|
||||||
|
denoOptions: string;
|
||||||
|
createdAt: string;
|
||||||
|
updatedAt: string;
|
||||||
|
destinationDockerId: string;
|
||||||
|
destinationDocker: DestinationDocker;
|
||||||
|
gitSource: GitSource & { githubApp?: GithubApp; gitlabApp?: GitlabApp };
|
||||||
|
settings: BuilderJobSettings;
|
||||||
|
secrets: Secret[];
|
||||||
|
persistentStorage: { path: string }[];
|
||||||
|
pullmergeRequestId?: unknown;
|
||||||
|
sourceBranch?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
// TODO: Add the other build types
|
||||||
|
export type BuildType = 'manual';
|
||||||
|
|
||||||
|
// TODO: Add the other buildpack names
|
||||||
|
export type BuildPackName = 'node' | 'docker';
|
||||||
|
|
||||||
|
export type BuilderJobSettings = {
|
||||||
|
id: string;
|
||||||
|
applicationId: string;
|
||||||
|
dualCerts: boolean;
|
||||||
|
debug: boolean;
|
||||||
|
previews: boolean;
|
||||||
|
autodeploy: boolean;
|
||||||
|
createdAt: string;
|
||||||
|
updatedAt: string;
|
||||||
|
};
|
||||||
61
src/lib/types/composeFile.ts
Normal file
61
src/lib/types/composeFile.ts
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
export type ComposeFile = {
|
||||||
|
version: ComposerFileVersion;
|
||||||
|
services: Record<string, ComposeFileService>;
|
||||||
|
networks: Record<string, ComposeFileNetwork>;
|
||||||
|
volumes?: Record<string, ComposeFileVolume>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type ComposeFileService = {
|
||||||
|
container_name: string;
|
||||||
|
image?: string;
|
||||||
|
networks: string[];
|
||||||
|
environment?: Record<string, unknown>;
|
||||||
|
volumes?: string[];
|
||||||
|
ulimits?: unknown;
|
||||||
|
labels?: string[];
|
||||||
|
env_file?: string[];
|
||||||
|
extra_hosts?: string[];
|
||||||
|
restart: ComposeFileRestartOption;
|
||||||
|
depends_on?: string[];
|
||||||
|
command?: string;
|
||||||
|
build?: {
|
||||||
|
context: string;
|
||||||
|
dockerfile: string;
|
||||||
|
args?: Record<string, unknown>;
|
||||||
|
};
|
||||||
|
deploy?: {
|
||||||
|
restart_policy?: {
|
||||||
|
condition?: string;
|
||||||
|
delay?: string;
|
||||||
|
max_attempts?: number;
|
||||||
|
window?: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export type ComposerFileVersion =
|
||||||
|
| '3.8'
|
||||||
|
| '3.7'
|
||||||
|
| '3.6'
|
||||||
|
| '3.5'
|
||||||
|
| '3.4'
|
||||||
|
| '3.3'
|
||||||
|
| '3.2'
|
||||||
|
| '3.1'
|
||||||
|
| '3.0'
|
||||||
|
| '2.4'
|
||||||
|
| '2.3'
|
||||||
|
| '2.2'
|
||||||
|
| '2.1'
|
||||||
|
| '2.0';
|
||||||
|
|
||||||
|
export type ComposeFileRestartOption = 'no' | 'always' | 'on-failure' | 'unless-stopped';
|
||||||
|
|
||||||
|
export type ComposeFileNetwork = {
|
||||||
|
external: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type ComposeFileVolume = {
|
||||||
|
external?: boolean;
|
||||||
|
name?: string;
|
||||||
|
};
|
||||||
8
src/lib/types/destinations.ts
Normal file
8
src/lib/types/destinations.ts
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
export type CreateDockerDestination = {
|
||||||
|
name: string;
|
||||||
|
engine: string;
|
||||||
|
remoteEngine: boolean;
|
||||||
|
network: string;
|
||||||
|
isCoolifyProxyUsed: boolean;
|
||||||
|
teamId: string;
|
||||||
|
};
|
||||||
@@ -12,7 +12,7 @@
|
|||||||
if (!session.userId) {
|
if (!session.userId) {
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
const endpoint = `/teams.json`;
|
const endpoint = `/dashboard.json`;
|
||||||
const res = await fetch(endpoint);
|
const res = await fetch(endpoint);
|
||||||
|
|
||||||
if (res.ok) {
|
if (res.ok) {
|
||||||
@@ -134,13 +134,18 @@
|
|||||||
|
|
||||||
<svelte:head>
|
<svelte:head>
|
||||||
<title>Coolify</title>
|
<title>Coolify</title>
|
||||||
|
{#if !$session.whiteLabeled}
|
||||||
|
<link rel="icon" href="/favicon.png" />
|
||||||
|
{/if}
|
||||||
</svelte:head>
|
</svelte:head>
|
||||||
<SvelteToast options={{ intro: { y: -64 }, duration: 3000, pausable: true }} />
|
<SvelteToast options={{ intro: { y: -64 }, duration: 3000, pausable: true }} />
|
||||||
{#if $session.userId}
|
{#if $session.userId}
|
||||||
<nav class="nav-main">
|
<nav class="nav-main">
|
||||||
<div class="flex h-screen w-full flex-col items-center transition-all duration-100">
|
<div class="flex h-screen w-full flex-col items-center transition-all duration-100">
|
||||||
<div class="my-4 h-10 w-10"><img src="/favicon.png" alt="coolLabs logo" /></div>
|
{#if !$session.whiteLabeled}
|
||||||
<div class="flex flex-col space-y-4 py-2">
|
<div class="my-4 h-10 w-10"><img src="/favicon.png" alt="coolLabs logo" /></div>
|
||||||
|
{/if}
|
||||||
|
<div class="flex flex-col space-y-4 py-2" class:mt-2={$session.whiteLabeled}>
|
||||||
<a
|
<a
|
||||||
sveltekit:prefetch
|
sveltekit:prefetch
|
||||||
href="/"
|
href="/"
|
||||||
@@ -171,7 +176,7 @@
|
|||||||
<a
|
<a
|
||||||
sveltekit:prefetch
|
sveltekit:prefetch
|
||||||
href="/applications"
|
href="/applications"
|
||||||
class="icons tooltip-right bg-coolgray-200 hover:text-green-500"
|
class="icons tooltip-green-500 tooltip-right bg-coolgray-200 hover:text-green-500"
|
||||||
class:text-green-500={$page.url.pathname.startsWith('/applications') ||
|
class:text-green-500={$page.url.pathname.startsWith('/applications') ||
|
||||||
$page.url.pathname.startsWith('/new/application')}
|
$page.url.pathname.startsWith('/new/application')}
|
||||||
class:bg-coolgray-500={$page.url.pathname.startsWith('/applications') ||
|
class:bg-coolgray-500={$page.url.pathname.startsWith('/applications') ||
|
||||||
@@ -199,7 +204,7 @@
|
|||||||
<a
|
<a
|
||||||
sveltekit:prefetch
|
sveltekit:prefetch
|
||||||
href="/sources"
|
href="/sources"
|
||||||
class="icons tooltip-right bg-coolgray-200 hover:text-orange-500"
|
class="icons tooltip-orange-500 tooltip-right bg-coolgray-200 hover:text-orange-500"
|
||||||
class:text-orange-500={$page.url.pathname.startsWith('/sources') ||
|
class:text-orange-500={$page.url.pathname.startsWith('/sources') ||
|
||||||
$page.url.pathname.startsWith('/new/source')}
|
$page.url.pathname.startsWith('/new/source')}
|
||||||
class:bg-coolgray-500={$page.url.pathname.startsWith('/sources') ||
|
class:bg-coolgray-500={$page.url.pathname.startsWith('/sources') ||
|
||||||
@@ -229,7 +234,7 @@
|
|||||||
<a
|
<a
|
||||||
sveltekit:prefetch
|
sveltekit:prefetch
|
||||||
href="/destinations"
|
href="/destinations"
|
||||||
class="icons tooltip-right bg-coolgray-200 hover:text-sky-500"
|
class="icons tooltip-sky-500 tooltip-right bg-coolgray-200 hover:text-sky-500"
|
||||||
class:text-sky-500={$page.url.pathname.startsWith('/destinations') ||
|
class:text-sky-500={$page.url.pathname.startsWith('/destinations') ||
|
||||||
$page.url.pathname.startsWith('/new/destination')}
|
$page.url.pathname.startsWith('/new/destination')}
|
||||||
class:bg-coolgray-500={$page.url.pathname.startsWith('/destinations') ||
|
class:bg-coolgray-500={$page.url.pathname.startsWith('/destinations') ||
|
||||||
@@ -264,7 +269,7 @@
|
|||||||
<a
|
<a
|
||||||
sveltekit:prefetch
|
sveltekit:prefetch
|
||||||
href="/databases"
|
href="/databases"
|
||||||
class="icons tooltip-right bg-coolgray-200 hover:text-purple-500"
|
class="icons tooltip-purple-500 tooltip-right bg-coolgray-200 hover:text-purple-500"
|
||||||
class:text-purple-500={$page.url.pathname.startsWith('/databases') ||
|
class:text-purple-500={$page.url.pathname.startsWith('/databases') ||
|
||||||
$page.url.pathname.startsWith('/new/database')}
|
$page.url.pathname.startsWith('/new/database')}
|
||||||
class:bg-coolgray-500={$page.url.pathname.startsWith('/databases') ||
|
class:bg-coolgray-500={$page.url.pathname.startsWith('/databases') ||
|
||||||
@@ -291,7 +296,7 @@
|
|||||||
<a
|
<a
|
||||||
sveltekit:prefetch
|
sveltekit:prefetch
|
||||||
href="/services"
|
href="/services"
|
||||||
class="icons tooltip-right bg-coolgray-200 hover:text-pink-500"
|
class="icons tooltip-pink-500 tooltip-right bg-coolgray-200 hover:text-pink-500"
|
||||||
class:text-pink-500={$page.url.pathname.startsWith('/services') ||
|
class:text-pink-500={$page.url.pathname.startsWith('/services') ||
|
||||||
$page.url.pathname.startsWith('/new/service')}
|
$page.url.pathname.startsWith('/new/service')}
|
||||||
class:bg-coolgray-500={$page.url.pathname.startsWith('/services') ||
|
class:bg-coolgray-500={$page.url.pathname.startsWith('/services') ||
|
||||||
@@ -312,7 +317,6 @@
|
|||||||
<path d="M7 18a4.6 4.4 0 0 1 0 -9a5 4.5 0 0 1 11 2h1a3.5 3.5 0 0 1 0 7h-12" />
|
<path d="M7 18a4.6 4.4 0 0 1 0 -9a5 4.5 0 0 1 11 2h1a3.5 3.5 0 0 1 0 7h-12" />
|
||||||
</svg>
|
</svg>
|
||||||
</a>
|
</a>
|
||||||
<div class="border-t border-stone-700" />
|
|
||||||
</div>
|
</div>
|
||||||
<div class="flex-1" />
|
<div class="flex-1" />
|
||||||
|
|
||||||
@@ -344,7 +348,7 @@
|
|||||||
{:else if updateStatus.success === null}
|
{:else if updateStatus.success === null}
|
||||||
<svg
|
<svg
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
class="w-8 h-9"
|
class="h-9 w-8"
|
||||||
viewBox="0 0 24 24"
|
viewBox="0 0 24 24"
|
||||||
stroke-width="1.5"
|
stroke-width="1.5"
|
||||||
stroke="currentColor"
|
stroke="currentColor"
|
||||||
@@ -359,7 +363,7 @@
|
|||||||
<line x1="16" y1="12" x2="12" y2="8" />
|
<line x1="16" y1="12" x2="12" y2="8" />
|
||||||
</svg>
|
</svg>
|
||||||
{:else if updateStatus.success}
|
{:else if updateStatus.success}
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36" class="w-8 h-9"
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36" class="h-9 w-8"
|
||||||
><path
|
><path
|
||||||
fill="#DD2E44"
|
fill="#DD2E44"
|
||||||
d="M11.626 7.488c-.112.112-.197.247-.268.395l-.008-.008L.134 33.141l.011.011c-.208.403.14 1.223.853 1.937.713.713 1.533 1.061 1.936.853l.01.01L28.21 24.735l-.008-.009c.147-.07.282-.155.395-.269 1.562-1.562-.971-6.627-5.656-11.313-4.687-4.686-9.752-7.218-11.315-5.656z"
|
d="M11.626 7.488c-.112.112-.197.247-.268.395l-.008-.008L.134 33.141l.011.011c-.208.403.14 1.223.853 1.937.713.713 1.533 1.061 1.936.853l.01.01L28.21 24.735l-.008-.009c.147-.07.282-.155.395-.269 1.562-1.562-.971-6.627-5.656-11.313-4.687-4.686-9.752-7.218-11.315-5.656z"
|
||||||
@@ -404,7 +408,7 @@
|
|||||||
/></svg
|
/></svg
|
||||||
>
|
>
|
||||||
{:else}
|
{:else}
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36" class="w-8 h-9"
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36" class="h-9 w-8"
|
||||||
><path
|
><path
|
||||||
fill="#FFCC4D"
|
fill="#FFCC4D"
|
||||||
d="M36 18c0 9.941-8.059 18-18 18S0 27.941 0 18 8.059 0 18 0s18 8.059 18 18"
|
d="M36 18c0 9.941-8.059 18-18 18S0 27.941 0 18 8.059 0 18 0s18 8.059 18 18"
|
||||||
@@ -430,13 +434,12 @@
|
|||||||
<div class="flex flex-col space-y-4 py-2">
|
<div class="flex flex-col space-y-4 py-2">
|
||||||
<a
|
<a
|
||||||
sveltekit:prefetch
|
sveltekit:prefetch
|
||||||
href="/teams"
|
href="/iam"
|
||||||
class="icons tooltip-right bg-coolgray-200 hover:text-cyan-500"
|
class="icons tooltip-fuchsia-500 tooltip-right bg-coolgray-200 hover:text-fuchsia-500"
|
||||||
class:text-cyan-500={$page.url.pathname.startsWith('/teams')}
|
class:text-fuchsia-500={$page.url.pathname.startsWith('/iam')}
|
||||||
class:bg-coolgray-500={$page.url.pathname.startsWith('/teams')}
|
class:bg-coolgray-500={$page.url.pathname.startsWith('/iam')}
|
||||||
data-tooltip="Teams"
|
data-tooltip="IAM"
|
||||||
>
|
><svg
|
||||||
<svg
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
class="h-8 w-8"
|
class="h-8 w-8"
|
||||||
viewBox="0 0 24 24"
|
viewBox="0 0 24 24"
|
||||||
@@ -453,11 +456,12 @@
|
|||||||
<path d="M21 21v-2a4 4 0 0 0 -3 -3.85" />
|
<path d="M21 21v-2a4 4 0 0 0 -3 -3.85" />
|
||||||
</svg>
|
</svg>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
{#if $session.teamId === '0'}
|
{#if $session.teamId === '0'}
|
||||||
<a
|
<a
|
||||||
sveltekit:prefetch
|
sveltekit:prefetch
|
||||||
href="/settings"
|
href="/settings"
|
||||||
class="icons tooltip-right bg-coolgray-200 hover:text-yellow-500"
|
class="icons tooltip-yellow-500 tooltip-right bg-coolgray-200 hover:text-yellow-500"
|
||||||
class:text-yellow-500={$page.url.pathname.startsWith('/settings')}
|
class:text-yellow-500={$page.url.pathname.startsWith('/settings')}
|
||||||
class:bg-coolgray-500={$page.url.pathname.startsWith('/settings')}
|
class:bg-coolgray-500={$page.url.pathname.startsWith('/settings')}
|
||||||
data-tooltip="Settings"
|
data-tooltip="Settings"
|
||||||
@@ -480,8 +484,9 @@
|
|||||||
</svg>
|
</svg>
|
||||||
</a>
|
</a>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<div
|
<div
|
||||||
class="icons tooltip-right bg-coolgray-200 hover:text-red-500"
|
class="icons tooltip-red-500 tooltip-right bg-coolgray-200 hover:text-red-500"
|
||||||
data-tooltip="Logout"
|
data-tooltip="Logout"
|
||||||
on:click={logout}
|
on:click={logout}
|
||||||
>
|
>
|
||||||
@@ -514,8 +519,14 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
|
{#if $session.whiteLabeled}
|
||||||
|
<span class="fixed bottom-0 left-[50px] z-50 m-2 px-4 text-xs text-stone-700"
|
||||||
|
>Powered by <a href="https://coolify.io" target="_blank">Coolify</a></span
|
||||||
|
>
|
||||||
|
{/if}
|
||||||
|
|
||||||
<select
|
<select
|
||||||
class="fixed right-0 bottom-0 z-50 m-2 w-64 bg-opacity-30 p-2 px-4"
|
class="fixed right-0 bottom-0 z-50 m-2 w-64 bg-opacity-30 p-2 px-4 hover:bg-opacity-100"
|
||||||
bind:value={selectedTeamId}
|
bind:value={selectedTeamId}
|
||||||
on:change={switchTeam}
|
on:change={switchTeam}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -17,7 +17,7 @@
|
|||||||
const endpoint = `/applications/${params.id}.json`;
|
const endpoint = `/applications/${params.id}.json`;
|
||||||
const res = await fetch(endpoint);
|
const res = await fetch(endpoint);
|
||||||
if (res.ok) {
|
if (res.ok) {
|
||||||
let { application, isRunning, appId, githubToken, gitlabToken } = await res.json();
|
let { application, isRunning, isExited, appId, githubToken, gitlabToken } = await res.json();
|
||||||
if (!application || Object.entries(application).length === 0) {
|
if (!application || Object.entries(application).length === 0) {
|
||||||
return {
|
return {
|
||||||
status: 302,
|
status: 302,
|
||||||
@@ -46,6 +46,7 @@
|
|||||||
props: {
|
props: {
|
||||||
application,
|
application,
|
||||||
isRunning,
|
isRunning,
|
||||||
|
isExited,
|
||||||
githubToken,
|
githubToken,
|
||||||
gitlabToken
|
gitlabToken
|
||||||
},
|
},
|
||||||
@@ -67,21 +68,32 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
export let application;
|
export let application;
|
||||||
export let isRunning;
|
export let isRunning;
|
||||||
|
export let isExited;
|
||||||
export let githubToken;
|
export let githubToken;
|
||||||
export let gitlabToken;
|
export let gitlabToken;
|
||||||
import { page, session } from '$app/stores';
|
import { page, session } from '$app/stores';
|
||||||
import { errorNotification } from '$lib/form';
|
import { errorNotification } from '$lib/form';
|
||||||
import DeleteIcon from '$lib/components/DeleteIcon.svelte';
|
import DeleteIcon from '$lib/components/DeleteIcon.svelte';
|
||||||
import Loading from '$lib/components/Loading.svelte';
|
import Loading from '$lib/components/Loading.svelte';
|
||||||
import { del, post } from '$lib/api';
|
import { del, get, post } from '$lib/api';
|
||||||
import { goto } from '$app/navigation';
|
import { goto } from '$app/navigation';
|
||||||
import { gitTokens } from '$lib/store';
|
import { gitTokens } from '$lib/store';
|
||||||
import { toast } from '@zerodevx/svelte-toast';
|
import { toast } from '@zerodevx/svelte-toast';
|
||||||
|
import { disabledButton } from '$lib/store';
|
||||||
|
import { onDestroy, onMount } from 'svelte';
|
||||||
|
|
||||||
if (githubToken) $gitTokens.githubToken = githubToken;
|
if (githubToken) $gitTokens.githubToken = githubToken;
|
||||||
if (gitlabToken) $gitTokens.gitlabToken = gitlabToken;
|
if (gitlabToken) $gitTokens.gitlabToken = gitlabToken;
|
||||||
|
|
||||||
let loading = false;
|
let loading = false;
|
||||||
|
let statusInterval;
|
||||||
|
$disabledButton =
|
||||||
|
!$session.isAdmin ||
|
||||||
|
!application.fqdn ||
|
||||||
|
!application.gitSource ||
|
||||||
|
!application.repository ||
|
||||||
|
!application.destinationDocker ||
|
||||||
|
!application.buildPack;
|
||||||
const { id } = $page.params;
|
const { id } = $page.params;
|
||||||
|
|
||||||
async function handleDeploySubmit() {
|
async function handleDeploySubmit() {
|
||||||
@@ -121,142 +133,86 @@
|
|||||||
return errorNotification(error);
|
return errorNotification(error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
async function getStatus() {
|
||||||
|
statusInterval = setInterval(async () => {
|
||||||
|
const data = await get(`/applications/${id}.json`);
|
||||||
|
isRunning = data.isRunning;
|
||||||
|
isExited = data.isExited;
|
||||||
|
}, 1000);
|
||||||
|
}
|
||||||
|
onDestroy(() => {
|
||||||
|
clearInterval(statusInterval);
|
||||||
|
});
|
||||||
|
onMount(async () => {
|
||||||
|
await getStatus();
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<nav class="nav-side">
|
<nav class="nav-side">
|
||||||
{#if loading}
|
{#if loading}
|
||||||
<Loading fullscreen cover />
|
<Loading fullscreen cover />
|
||||||
{:else}
|
{:else}
|
||||||
{#if application.fqdn && application.gitSource && application.repository && application.destinationDocker && application.buildPack}
|
{#if isExited}
|
||||||
{#if isRunning}
|
<a
|
||||||
|
href={!$disabledButton ? `/applications/${id}/logs` : null}
|
||||||
|
class=" icons bg-transparent tooltip-bottom text-sm flex items-center text-red-500 tooltip-red-500"
|
||||||
|
data-tooltip="Application exited with an error!"
|
||||||
|
sveltekit:prefetch
|
||||||
|
>
|
||||||
|
<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" />
|
||||||
|
<path
|
||||||
|
d="M8.7 3h6.6c.3 0 .5 .1 .7 .3l4.7 4.7c.2 .2 .3 .4 .3 .7v6.6c0 .3 -.1 .5 -.3 .7l-4.7 4.7c-.2 .2 -.4 .3 -.7 .3h-6.6c-.3 0 -.5 -.1 -.7 -.3l-4.7 -4.7c-.2 -.2 -.3 -.4 -.3 -.7v-6.6c0 -.3 .1 -.5 .3 -.7l4.7 -4.7c.2 -.2 .4 -.3 .7 -.3z"
|
||||||
|
/>
|
||||||
|
<line x1="12" y1="8" x2="12" y2="12" />
|
||||||
|
<line x1="12" y1="16" x2="12.01" y2="16" />
|
||||||
|
</svg>
|
||||||
|
</a>
|
||||||
|
{/if}
|
||||||
|
{#if isRunning}
|
||||||
|
<button
|
||||||
|
on:click={stopApplication}
|
||||||
|
title="Stop application"
|
||||||
|
type="submit"
|
||||||
|
disabled={$disabledButton}
|
||||||
|
class="icons bg-transparent tooltip-bottom text-sm flex items-center space-x-2 text-red-500"
|
||||||
|
data-tooltip={$session.isAdmin
|
||||||
|
? 'Stop application'
|
||||||
|
: 'You do not have permission to stop the application.'}
|
||||||
|
>
|
||||||
|
<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" />
|
||||||
|
<rect x="6" y="5" width="4" height="14" rx="1" />
|
||||||
|
<rect x="14" y="5" width="4" height="14" rx="1" />
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
<form on:submit|preventDefault={handleDeploySubmit}>
|
||||||
<button
|
<button
|
||||||
on:click={stopApplication}
|
title="Rebuild application"
|
||||||
title="Stop application"
|
|
||||||
type="submit"
|
type="submit"
|
||||||
disabled={!$session.isAdmin}
|
disabled={$disabledButton}
|
||||||
class="icons bg-transparent tooltip-bottom text-sm flex items-center space-x-2 text-red-500"
|
class="icons bg-transparent tooltip-bottom text-sm flex items-center space-x-2 hover:text-green-500"
|
||||||
data-tooltip={$session.isAdmin
|
data-tooltip={$session.isAdmin
|
||||||
? 'Stop application'
|
? 'Rebuild application'
|
||||||
: 'You do not have permission to stop the application.'}
|
: 'You do not have permission to rebuild application.'}
|
||||||
>
|
|
||||||
<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" />
|
|
||||||
<rect x="6" y="5" width="4" height="14" rx="1" />
|
|
||||||
<rect x="14" y="5" width="4" height="14" rx="1" />
|
|
||||||
</svg>
|
|
||||||
</button>
|
|
||||||
<form on:submit|preventDefault={handleDeploySubmit}>
|
|
||||||
<button
|
|
||||||
title="Rebuild application"
|
|
||||||
type="submit"
|
|
||||||
disabled={!$session.isAdmin}
|
|
||||||
class="icons bg-transparent tooltip-bottom text-sm flex items-center space-x-2 hover:text-green-500"
|
|
||||||
data-tooltip={$session.isAdmin
|
|
||||||
? 'Rebuild application'
|
|
||||||
: 'You do not have permission to rebuild application.'}
|
|
||||||
>
|
|
||||||
<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" />
|
|
||||||
<path
|
|
||||||
d="M16.3 5h.7a2 2 0 0 1 2 2v10a2 2 0 0 1 -2 2h-10a2 2 0 0 1 -2 -2v-10a2 2 0 0 1 2 -2h5l-2.82 -2.82m0 5.64l2.82 -2.82"
|
|
||||||
transform="rotate(-45 12 12)"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
</button>
|
|
||||||
</form>
|
|
||||||
{:else}
|
|
||||||
<form on:submit|preventDefault={handleDeploySubmit}>
|
|
||||||
<button
|
|
||||||
title="Build and start application"
|
|
||||||
type="submit"
|
|
||||||
disabled={!$session.isAdmin}
|
|
||||||
class="icons bg-transparent tooltip-bottom text-sm flex items-center space-x-2 text-green-500"
|
|
||||||
data-tooltip={$session.isAdmin
|
|
||||||
? 'Build and start application'
|
|
||||||
: 'You do not have permission to Build and start application.'}
|
|
||||||
>
|
|
||||||
<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" />
|
|
||||||
<path d="M7 4v16l13 -8z" />
|
|
||||||
</svg>
|
|
||||||
</button>
|
|
||||||
</form>
|
|
||||||
{/if}
|
|
||||||
|
|
||||||
<div class="border border-stone-700 h-8" />
|
|
||||||
<a
|
|
||||||
href="/applications/{id}"
|
|
||||||
sveltekit:prefetch
|
|
||||||
class="hover:text-yellow-500 rounded"
|
|
||||||
class:text-yellow-500={$page.url.pathname === `/applications/${id}`}
|
|
||||||
class:bg-coolgray-500={$page.url.pathname === `/applications/${id}`}
|
|
||||||
>
|
|
||||||
<button
|
|
||||||
title="Configurations"
|
|
||||||
class="icons bg-transparent tooltip-bottom text-sm disabled:text-red-500"
|
|
||||||
data-tooltip="Configurations"
|
|
||||||
>
|
|
||||||
<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></button
|
|
||||||
></a
|
|
||||||
>
|
|
||||||
<a
|
|
||||||
href="/applications/{id}/secrets"
|
|
||||||
sveltekit:prefetch
|
|
||||||
class="hover:text-pink-500 rounded"
|
|
||||||
class:text-pink-500={$page.url.pathname === `/applications/${id}/secrets`}
|
|
||||||
class:bg-coolgray-500={$page.url.pathname === `/applications/${id}/secrets`}
|
|
||||||
>
|
|
||||||
<button
|
|
||||||
title="Secret"
|
|
||||||
class="icons bg-transparent tooltip-bottom text-sm disabled:text-red-500"
|
|
||||||
data-tooltip="Secret"
|
|
||||||
>
|
>
|
||||||
<svg
|
<svg
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
@@ -270,24 +226,22 @@
|
|||||||
>
|
>
|
||||||
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
||||||
<path
|
<path
|
||||||
d="M12 3a12 12 0 0 0 8.5 3a12 12 0 0 1 -8.5 15a12 12 0 0 1 -8.5 -15a12 12 0 0 0 8.5 -3"
|
d="M16.3 5h.7a2 2 0 0 1 2 2v10a2 2 0 0 1 -2 2h-10a2 2 0 0 1 -2 -2v-10a2 2 0 0 1 2 -2h5l-2.82 -2.82m0 5.64l2.82 -2.82"
|
||||||
|
transform="rotate(-45 12 12)"
|
||||||
/>
|
/>
|
||||||
<circle cx="12" cy="11" r="1" />
|
</svg>
|
||||||
<line x1="12" y1="12" x2="12" y2="14.5" />
|
</button>
|
||||||
</svg></button
|
</form>
|
||||||
></a
|
{:else}
|
||||||
>
|
<form on:submit|preventDefault={handleDeploySubmit}>
|
||||||
<a
|
|
||||||
href="/applications/{id}/storage"
|
|
||||||
sveltekit:prefetch
|
|
||||||
class="hover:text-pink-500 rounded"
|
|
||||||
class:text-pink-500={$page.url.pathname === `/applications/${id}/storage`}
|
|
||||||
class:bg-coolgray-500={$page.url.pathname === `/applications/${id}/storage`}
|
|
||||||
>
|
|
||||||
<button
|
<button
|
||||||
title="Persistent Storage"
|
title="Build and start application"
|
||||||
class="icons bg-transparent tooltip-bottom text-sm disabled:text-red-500"
|
type="submit"
|
||||||
data-tooltip="Persistent Storage"
|
disabled={$disabledButton}
|
||||||
|
class="icons bg-transparent tooltip-bottom text-sm flex items-center space-x-2 text-green-500"
|
||||||
|
data-tooltip={$session.isAdmin
|
||||||
|
? 'Build and start application'
|
||||||
|
: 'You do not have permission to Build and start application.'}
|
||||||
>
|
>
|
||||||
<svg
|
<svg
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
@@ -300,112 +254,213 @@
|
|||||||
stroke-linejoin="round"
|
stroke-linejoin="round"
|
||||||
>
|
>
|
||||||
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
||||||
<ellipse cx="12" cy="6" rx="8" ry="3" />
|
<path d="M7 4v16l13 -8z" />
|
||||||
<path d="M4 6v6a8 3 0 0 0 16 0v-6" />
|
|
||||||
<path d="M4 12v6a8 3 0 0 0 16 0v-6" />
|
|
||||||
</svg>
|
</svg>
|
||||||
</button></a
|
</button>
|
||||||
>
|
</form>
|
||||||
<a
|
|
||||||
href="/applications/{id}/previews"
|
|
||||||
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
|
|
||||||
title="Previews"
|
|
||||||
class="icons bg-transparent tooltip-bottom text-sm disabled:text-red-500"
|
|
||||||
data-tooltip="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"
|
|
||||||
>
|
|
||||||
<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
|
|
||||||
>
|
|
||||||
<div class="border border-stone-700 h-8" />
|
|
||||||
<a
|
|
||||||
href="/applications/{id}/logs"
|
|
||||||
sveltekit:prefetch
|
|
||||||
class="hover:text-sky-500 rounded"
|
|
||||||
class:text-sky-500={$page.url.pathname === `/applications/${id}/logs`}
|
|
||||||
class:bg-coolgray-500={$page.url.pathname === `/applications/${id}/logs`}
|
|
||||||
>
|
|
||||||
<button
|
|
||||||
title="Application Logs"
|
|
||||||
class="icons bg-transparent tooltip-bottom text-sm disabled:text-red-500 "
|
|
||||||
data-tooltip="Application Logs"
|
|
||||||
>
|
|
||||||
<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="M3 19a9 9 0 0 1 9 0a9 9 0 0 1 9 0" />
|
|
||||||
<path d="M3 6a9 9 0 0 1 9 0a9 9 0 0 1 9 0" />
|
|
||||||
<line x1="3" y1="6" x2="3" y2="19" />
|
|
||||||
<line x1="12" y1="6" x2="12" y2="19" />
|
|
||||||
<line x1="21" y1="6" x2="21" y2="19" />
|
|
||||||
</svg>
|
|
||||||
</button></a
|
|
||||||
>
|
|
||||||
<a
|
|
||||||
href="/applications/{id}/logs/build"
|
|
||||||
sveltekit:prefetch
|
|
||||||
class="hover:text-red-500 rounded"
|
|
||||||
class:text-red-500={$page.url.pathname === `/applications/${id}/logs/build`}
|
|
||||||
class:bg-coolgray-500={$page.url.pathname === `/applications/${id}/logs/build`}
|
|
||||||
>
|
|
||||||
<button
|
|
||||||
title="Build Logs"
|
|
||||||
class="icons bg-transparent tooltip-bottom text-sm disabled:text-red-500 "
|
|
||||||
data-tooltip="Build Logs"
|
|
||||||
>
|
|
||||||
<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" />
|
|
||||||
<circle cx="19" cy="13" r="2" />
|
|
||||||
<circle cx="4" cy="17" r="2" />
|
|
||||||
<circle cx="13" cy="17" r="2" />
|
|
||||||
<line x1="13" y1="19" x2="4" y2="19" />
|
|
||||||
<line x1="4" y1="15" x2="13" y2="15" />
|
|
||||||
<path d="M8 12v-5h2a3 3 0 0 1 3 3v5" />
|
|
||||||
<path d="M5 15v-2a1 1 0 0 1 1 -1h7" />
|
|
||||||
<path d="M19 11v-7l-6 7" />
|
|
||||||
</svg>
|
|
||||||
</button></a
|
|
||||||
>
|
|
||||||
<div class="border border-stone-700 h-8" />
|
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
|
<div class="border border-coolgray-500 h-8" />
|
||||||
|
<a
|
||||||
|
href={!$disabledButton ? `/applications/${id}` : null}
|
||||||
|
sveltekit:prefetch
|
||||||
|
class="hover:text-yellow-500 rounded"
|
||||||
|
class:text-yellow-500={$page.url.pathname === `/applications/${id}`}
|
||||||
|
class:bg-coolgray-500={$page.url.pathname === `/applications/${id}`}
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
title="Configurations"
|
||||||
|
disabled={$disabledButton}
|
||||||
|
class="icons bg-transparent tooltip-bottom text-sm"
|
||||||
|
data-tooltip="Configurations"
|
||||||
|
>
|
||||||
|
<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></button
|
||||||
|
></a
|
||||||
|
>
|
||||||
|
<a
|
||||||
|
href={!$disabledButton ? `/applications/${id}/secrets` : null}
|
||||||
|
sveltekit:prefetch
|
||||||
|
class="hover:text-pink-500 rounded"
|
||||||
|
class:text-pink-500={$page.url.pathname === `/applications/${id}/secrets`}
|
||||||
|
class:bg-coolgray-500={$page.url.pathname === `/applications/${id}/secrets`}
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
title="Secret"
|
||||||
|
disabled={$disabledButton}
|
||||||
|
class="icons bg-transparent tooltip-bottom text-sm"
|
||||||
|
data-tooltip="Secret"
|
||||||
|
>
|
||||||
|
<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" />
|
||||||
|
<path
|
||||||
|
d="M12 3a12 12 0 0 0 8.5 3a12 12 0 0 1 -8.5 15a12 12 0 0 1 -8.5 -15a12 12 0 0 0 8.5 -3"
|
||||||
|
/>
|
||||||
|
<circle cx="12" cy="11" r="1" />
|
||||||
|
<line x1="12" y1="12" x2="12" y2="14.5" />
|
||||||
|
</svg></button
|
||||||
|
></a
|
||||||
|
>
|
||||||
|
<a
|
||||||
|
href={!$disabledButton ? `/applications/${id}/storage` : null}
|
||||||
|
sveltekit:prefetch
|
||||||
|
class="hover:text-pink-500 rounded"
|
||||||
|
class:text-pink-500={$page.url.pathname === `/applications/${id}/storage`}
|
||||||
|
class:bg-coolgray-500={$page.url.pathname === `/applications/${id}/storage`}
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
title="Persistent Storage"
|
||||||
|
disabled={$disabledButton}
|
||||||
|
class="icons bg-transparent tooltip-bottom text-sm"
|
||||||
|
data-tooltip="Persistent Storage"
|
||||||
|
>
|
||||||
|
<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" />
|
||||||
|
<ellipse cx="12" cy="6" rx="8" ry="3" />
|
||||||
|
<path d="M4 6v6a8 3 0 0 0 16 0v-6" />
|
||||||
|
<path d="M4 12v6a8 3 0 0 0 16 0v-6" />
|
||||||
|
</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
|
||||||
|
title="Previews"
|
||||||
|
disabled={$disabledButton}
|
||||||
|
class="icons bg-transparent tooltip-bottom text-sm"
|
||||||
|
data-tooltip="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"
|
||||||
|
>
|
||||||
|
<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
|
||||||
|
>
|
||||||
|
<div class="border border-coolgray-500 h-8" />
|
||||||
|
<a
|
||||||
|
href={!$disabledButton ? `/applications/${id}/logs` : null}
|
||||||
|
sveltekit:prefetch
|
||||||
|
class="hover:text-sky-500 rounded"
|
||||||
|
class:text-sky-500={$page.url.pathname === `/applications/${id}/logs`}
|
||||||
|
class:bg-coolgray-500={$page.url.pathname === `/applications/${id}/logs`}
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
title="Application Logs"
|
||||||
|
disabled={$disabledButton}
|
||||||
|
class="icons bg-transparent tooltip-bottom text-sm"
|
||||||
|
data-tooltip="Application Logs"
|
||||||
|
>
|
||||||
|
<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="M3 19a9 9 0 0 1 9 0a9 9 0 0 1 9 0" />
|
||||||
|
<path d="M3 6a9 9 0 0 1 9 0a9 9 0 0 1 9 0" />
|
||||||
|
<line x1="3" y1="6" x2="3" y2="19" />
|
||||||
|
<line x1="12" y1="6" x2="12" y2="19" />
|
||||||
|
<line x1="21" y1="6" x2="21" y2="19" />
|
||||||
|
</svg>
|
||||||
|
</button></a
|
||||||
|
>
|
||||||
|
<a
|
||||||
|
href={!$disabledButton ? `/applications/${id}/logs/build` : null}
|
||||||
|
sveltekit:prefetch
|
||||||
|
class="hover:text-red-500 rounded"
|
||||||
|
class:text-red-500={$page.url.pathname === `/applications/${id}/logs/build`}
|
||||||
|
class:bg-coolgray-500={$page.url.pathname === `/applications/${id}/logs/build`}
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
title="Build Logs"
|
||||||
|
disabled={$disabledButton}
|
||||||
|
class="icons bg-transparent tooltip-bottom text-sm"
|
||||||
|
data-tooltip="Build Logs"
|
||||||
|
>
|
||||||
|
<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" />
|
||||||
|
<circle cx="19" cy="13" r="2" />
|
||||||
|
<circle cx="4" cy="17" r="2" />
|
||||||
|
<circle cx="13" cy="17" r="2" />
|
||||||
|
<line x1="13" y1="19" x2="4" y2="19" />
|
||||||
|
<line x1="4" y1="15" x2="13" y2="15" />
|
||||||
|
<path d="M8 12v-5h2a3 3 0 0 1 3 3v5" />
|
||||||
|
<path d="M5 15v-2a1 1 0 0 1 1 -1h7" />
|
||||||
|
<path d="M19 11v-7l-6 7" />
|
||||||
|
</svg>
|
||||||
|
</button></a
|
||||||
|
>
|
||||||
|
<div class="border border-coolgray-500 h-8" />
|
||||||
|
|
||||||
<button
|
<button
|
||||||
on:click={() => deleteApplication(application.name)}
|
on:click={() => deleteApplication(application.name)}
|
||||||
title="Delete application"
|
title="Delete application"
|
||||||
|
|||||||
@@ -19,13 +19,14 @@
|
|||||||
const tempBuildPack = JSON.parse(
|
const tempBuildPack = JSON.parse(
|
||||||
JSON.stringify(findBuildPack(buildPack.name, packageManager))
|
JSON.stringify(findBuildPack(buildPack.name, packageManager))
|
||||||
);
|
);
|
||||||
|
|
||||||
delete tempBuildPack.name;
|
delete tempBuildPack.name;
|
||||||
delete tempBuildPack.fancyName;
|
delete tempBuildPack.fancyName;
|
||||||
delete tempBuildPack.color;
|
delete tempBuildPack.color;
|
||||||
delete tempBuildPack.hoverColor;
|
delete tempBuildPack.hoverColor;
|
||||||
|
|
||||||
if (foundConfig.buildPack !== name) {
|
if (foundConfig.buildPack !== name) {
|
||||||
await post(`/applications/${id}.json`, { ...tempBuildPack });
|
await post(`/applications/${id}.json`, { ...tempBuildPack, buildPack: name });
|
||||||
}
|
}
|
||||||
await post(`/applications/${id}/configuration/buildpack.json`, { buildPack: name });
|
await post(`/applications/${id}/configuration/buildpack.json`, { buildPack: name });
|
||||||
return await goto(from || `/applications/${id}`);
|
return await goto(from || `/applications/${id}`);
|
||||||
|
|||||||
@@ -36,8 +36,15 @@
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function loadBranchesByPage(page = 0) {
|
||||||
|
return await get(`${apiUrl}/repos/${selected.repository}/branches?per_page=100&page=${page}`, {
|
||||||
|
Authorization: `token ${$gitTokens.githubToken}`
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
let reposSelectOptions;
|
let reposSelectOptions;
|
||||||
let branchSelectOptions;
|
let branchSelectOptions;
|
||||||
|
|
||||||
async function loadRepositories() {
|
async function loadRepositories() {
|
||||||
let page = 1;
|
let page = 1;
|
||||||
let reposCount = 0;
|
let reposCount = 0;
|
||||||
@@ -58,24 +65,28 @@
|
|||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
async function loadBranches(event) {
|
async function loadBranches(event) {
|
||||||
|
branches = [];
|
||||||
selected.repository = event.detail.value;
|
selected.repository = event.detail.value;
|
||||||
loading.branches = true;
|
|
||||||
selected.branch = undefined;
|
|
||||||
selected.projectId = repositories.find((repo) => repo.full_name === selected.repository).id;
|
selected.projectId = repositories.find((repo) => repo.full_name === selected.repository).id;
|
||||||
try {
|
let page = 1;
|
||||||
branches = await get(`${apiUrl}/repos/${selected.repository}/branches`, {
|
let branchCount = 0;
|
||||||
Authorization: `token ${$gitTokens.githubToken}`
|
loading.branches = true;
|
||||||
});
|
const loadedBranches = await loadBranchesByPage();
|
||||||
branchSelectOptions = branches.map((branch) => ({
|
branches = branches.concat(loadedBranches);
|
||||||
value: branch.name,
|
branchCount = branches.length;
|
||||||
label: branch.name
|
if (branchCount === 100) {
|
||||||
}));
|
while (branchCount === 100) {
|
||||||
return;
|
page = page + 1;
|
||||||
} catch ({ error }) {
|
const nextBranches = await loadBranchesByPage(page);
|
||||||
return errorNotification(error);
|
branches = branches.concat(nextBranches);
|
||||||
} finally {
|
branchCount = nextBranches.length;
|
||||||
loading.branches = false;
|
}
|
||||||
}
|
}
|
||||||
|
loading.branches = false;
|
||||||
|
branchSelectOptions = branches.map((branch) => ({
|
||||||
|
value: branch.name,
|
||||||
|
label: branch.name
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
async function isBranchAlreadyUsed(event) {
|
async function isBranchAlreadyUsed(event) {
|
||||||
selected.branch = event.detail.value;
|
selected.branch = event.detail.value;
|
||||||
@@ -166,30 +177,36 @@
|
|||||||
{:else}
|
{:else}
|
||||||
<form on:submit|preventDefault={handleSubmit} class="flex flex-col justify-center text-center">
|
<form on:submit|preventDefault={handleSubmit} class="flex flex-col justify-center text-center">
|
||||||
<div class="flex-col space-y-3 md:space-y-0 space-x-1">
|
<div class="flex-col space-y-3 md:space-y-0 space-x-1">
|
||||||
<div class="flex gap-4">
|
<div class="flex-col md:flex gap-4">
|
||||||
<div class="custom-select-wrapper">
|
<div class="custom-select-wrapper">
|
||||||
<Select
|
<Select
|
||||||
placeholder={loading.repositories
|
placeholder={loading.repositories
|
||||||
? 'Loading repositories ...'
|
? 'Loading repositories...'
|
||||||
: 'Please select a repository'}
|
: 'Please select a repository'}
|
||||||
id="repository"
|
id="repository"
|
||||||
|
showIndicator={true}
|
||||||
|
isWaiting={loading.repositories}
|
||||||
on:select={loadBranches}
|
on:select={loadBranches}
|
||||||
items={reposSelectOptions}
|
items={reposSelectOptions}
|
||||||
isDisabled={loading.repositories}
|
isDisabled={loading.repositories}
|
||||||
|
isClearable={false}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<input class="hidden" bind:value={selected.projectId} name="projectId" />
|
<input class="hidden" bind:value={selected.projectId} name="projectId" />
|
||||||
<div class="custom-select-wrapper">
|
<div class="custom-select-wrapper">
|
||||||
<Select
|
<Select
|
||||||
placeholder={loading.branches
|
placeholder={loading.branches
|
||||||
? 'Loading branches ...'
|
? 'Loading branches...'
|
||||||
: !selected.repository
|
: !selected.repository
|
||||||
? 'Please select a repository first'
|
? 'Please select a repository first'
|
||||||
: 'Please select a branch'}
|
: 'Please select a branch'}
|
||||||
id="repository"
|
isWaiting={loading.branches}
|
||||||
|
showIndicator={selected.repository}
|
||||||
|
id="branches"
|
||||||
on:select={isBranchAlreadyUsed}
|
on:select={isBranchAlreadyUsed}
|
||||||
items={branchSelectOptions}
|
items={branchSelectOptions}
|
||||||
isDisabled={loading.branches || !selected.repository}
|
isDisabled={loading.branches || !selected.repository}
|
||||||
|
isClearable={false}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -202,13 +219,6 @@
|
|||||||
class:bg-orange-600={showSave}
|
class:bg-orange-600={showSave}
|
||||||
class:hover:bg-orange-500={showSave}>Save</button
|
class:hover:bg-orange-500={showSave}>Save</button
|
||||||
>
|
>
|
||||||
<!-- <button class="w-40"
|
|
||||||
><a
|
|
||||||
class="no-underline"
|
|
||||||
href="{apiUrl}/apps/{application.gitSource.githubApp.name}/installations/new"
|
|
||||||
>Modify Repositories</a
|
|
||||||
></button
|
|
||||||
> -->
|
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
{/if}
|
{/if}
|
||||||
|
|||||||
@@ -31,7 +31,7 @@
|
|||||||
|
|
||||||
import { buildPacks, findBuildPack, scanningTemplates } from '$lib/components/templates';
|
import { buildPacks, findBuildPack, scanningTemplates } from '$lib/components/templates';
|
||||||
import BuildPack from './_BuildPack.svelte';
|
import BuildPack from './_BuildPack.svelte';
|
||||||
import { page, session } from '$app/stores';
|
import { page } from '$app/stores';
|
||||||
import { get } from '$lib/api';
|
import { get } from '$lib/api';
|
||||||
import { errorNotification } from '$lib/form';
|
import { errorNotification } from '$lib/form';
|
||||||
import { gitTokens } from '$lib/store';
|
import { gitTokens } from '$lib/store';
|
||||||
@@ -81,6 +81,9 @@
|
|||||||
);
|
);
|
||||||
const indexHtml = files.find((file) => file.name === 'index.html' && file.type === 'blob');
|
const indexHtml = files.find((file) => file.name === 'index.html' && file.type === 'blob');
|
||||||
const indexPHP = files.find((file) => file.name === 'index.php' && file.type === 'blob');
|
const indexPHP = files.find((file) => file.name === 'index.php' && file.type === 'blob');
|
||||||
|
const composerPHP = files.find(
|
||||||
|
(file) => file.name === 'composer.json' && file.type === 'blob'
|
||||||
|
);
|
||||||
|
|
||||||
if (yarnLock) packageManager = 'yarn';
|
if (yarnLock) packageManager = 'yarn';
|
||||||
if (pnpmLock) packageManager = 'pnpm';
|
if (pnpmLock) packageManager = 'pnpm';
|
||||||
@@ -103,7 +106,7 @@
|
|||||||
foundConfig = findBuildPack('python');
|
foundConfig = findBuildPack('python');
|
||||||
} else if (indexHtml) {
|
} else if (indexHtml) {
|
||||||
foundConfig = findBuildPack('static', packageManager);
|
foundConfig = findBuildPack('static', packageManager);
|
||||||
} else if (indexPHP) {
|
} else if (indexPHP || composerPHP) {
|
||||||
foundConfig = findBuildPack('php');
|
foundConfig = findBuildPack('php');
|
||||||
} else {
|
} else {
|
||||||
foundConfig = findBuildPack('node', packageManager);
|
foundConfig = findBuildPack('node', packageManager);
|
||||||
@@ -127,6 +130,9 @@
|
|||||||
);
|
);
|
||||||
const indexHtml = files.find((file) => file.name === 'index.html' && file.type === 'file');
|
const indexHtml = files.find((file) => file.name === 'index.html' && file.type === 'file');
|
||||||
const indexPHP = files.find((file) => file.name === 'index.php' && file.type === 'file');
|
const indexPHP = files.find((file) => file.name === 'index.php' && file.type === 'file');
|
||||||
|
const composerPHP = files.find(
|
||||||
|
(file) => file.name === 'composer.json' && file.type === 'file'
|
||||||
|
);
|
||||||
|
|
||||||
if (yarnLock) packageManager = 'yarn';
|
if (yarnLock) packageManager = 'yarn';
|
||||||
if (pnpmLock) packageManager = 'pnpm';
|
if (pnpmLock) packageManager = 'pnpm';
|
||||||
@@ -146,7 +152,7 @@
|
|||||||
foundConfig = findBuildPack('python');
|
foundConfig = findBuildPack('python');
|
||||||
} else if (indexHtml) {
|
} else if (indexHtml) {
|
||||||
foundConfig = findBuildPack('static', packageManager);
|
foundConfig = findBuildPack('static', packageManager);
|
||||||
} else if (indexPHP) {
|
} else if (indexPHP || composerPHP) {
|
||||||
foundConfig = findBuildPack('php');
|
foundConfig = findBuildPack('php');
|
||||||
} else {
|
} else {
|
||||||
foundConfig = findBuildPack('node', packageManager);
|
foundConfig = findBuildPack('node', packageManager);
|
||||||
@@ -215,7 +221,7 @@
|
|||||||
<div class="max-w-7xl mx-auto flex flex-wrap justify-center">
|
<div class="max-w-7xl mx-auto flex flex-wrap justify-center">
|
||||||
{#each buildPacks as buildPack}
|
{#each buildPacks as buildPack}
|
||||||
<div class="p-2">
|
<div class="p-2">
|
||||||
<BuildPack {buildPack} {scanning} {packageManager} bind:foundConfig />
|
<BuildPack {buildPack} {scanning} bind:foundConfig />
|
||||||
</div>
|
</div>
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -29,7 +29,7 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type Prisma from '@prisma/client';
|
import type Prisma from '@prisma/client';
|
||||||
|
|
||||||
import { page } from '$app/stores';
|
import { page, session } from '$app/stores';
|
||||||
import { errorNotification } from '$lib/form';
|
import { errorNotification } from '$lib/form';
|
||||||
import { goto } from '$app/navigation';
|
import { goto } from '$app/navigation';
|
||||||
import { post } from '$lib/api';
|
import { post } from '$lib/api';
|
||||||
@@ -39,6 +39,16 @@
|
|||||||
|
|
||||||
export let destinations: Prisma.DestinationDocker[];
|
export let destinations: Prisma.DestinationDocker[];
|
||||||
|
|
||||||
|
const ownDestinations = destinations.filter((destination) => {
|
||||||
|
if (destination.teams[0].id === $session.teamId) {
|
||||||
|
return destination;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const otherDestinations = destinations.filter((destination) => {
|
||||||
|
if (destination.teams[0].id !== $session.teamId) {
|
||||||
|
return destination;
|
||||||
|
}
|
||||||
|
});
|
||||||
async function handleSubmit(destinationId) {
|
async function handleSubmit(destinationId) {
|
||||||
try {
|
try {
|
||||||
await post(`/applications/${id}/configuration/destination.json`, { destinationId });
|
await post(`/applications/${id}/configuration/destination.json`, { destinationId });
|
||||||
@@ -52,8 +62,8 @@
|
|||||||
<div class="flex space-x-1 p-6 font-bold">
|
<div class="flex space-x-1 p-6 font-bold">
|
||||||
<div class="mr-4 text-2xl tracking-tight">Configure Destination</div>
|
<div class="mr-4 text-2xl tracking-tight">Configure Destination</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex justify-center">
|
<div class="flex flex-col justify-center">
|
||||||
{#if !destinations || destinations.length === 0}
|
{#if !destinations || ownDestinations.length === 0}
|
||||||
<div class="flex-col">
|
<div class="flex-col">
|
||||||
<div class="pb-2">No configurable Destination found</div>
|
<div class="pb-2">No configurable Destination found</div>
|
||||||
<div class="flex justify-center">
|
<div class="flex justify-center">
|
||||||
@@ -75,8 +85,23 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{:else}
|
{:else}
|
||||||
<div class="flex flex-wrap justify-center">
|
<div class="flex flex-col flex-wrap justify-center px-2 md:flex-row">
|
||||||
{#each destinations as destination}
|
{#each ownDestinations as destination}
|
||||||
|
<div class="p-2">
|
||||||
|
<form on:submit|preventDefault={() => handleSubmit(destination.id)}>
|
||||||
|
<button type="submit" class="box-selection hover:bg-sky-700 font-bold">
|
||||||
|
<div class="font-bold text-xl text-center truncate">{destination.name}</div>
|
||||||
|
<div class="text-center truncate">{destination.network}</div>
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
{#if otherDestinations.length > 0 && $session.teamId === '0'}
|
||||||
|
<div class="px-6 pb-5 pt-10 text-xl font-bold">Other Destinations</div>
|
||||||
|
{/if}
|
||||||
|
<div class="flex flex-col flex-wrap justify-center px-2 md:flex-row">
|
||||||
|
{#each otherDestinations as destination}
|
||||||
<div class="p-2">
|
<div class="p-2">
|
||||||
<form on:submit|preventDefault={() => handleSubmit(destination.id)}>
|
<form on:submit|preventDefault={() => handleSubmit(destination.id)}>
|
||||||
<button type="submit" class="box-selection hover:bg-sky-700 font-bold">
|
<button type="submit" class="box-selection hover:bg-sky-700 font-bold">
|
||||||
|
|||||||
@@ -29,7 +29,7 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type Prisma from '@prisma/client';
|
import type Prisma from '@prisma/client';
|
||||||
|
|
||||||
import { page } from '$app/stores';
|
import { page, session } from '$app/stores';
|
||||||
import { errorNotification } from '$lib/form';
|
import { errorNotification } from '$lib/form';
|
||||||
import { goto } from '$app/navigation';
|
import { goto } from '$app/navigation';
|
||||||
import { post } from '$lib/api';
|
import { post } from '$lib/api';
|
||||||
@@ -46,6 +46,17 @@
|
|||||||
(source.type === 'github' && source.githubAppId && source.githubApp.installationId) ||
|
(source.type === 'github' && source.githubAppId && source.githubApp.installationId) ||
|
||||||
(source.type === 'gitlab' && source.gitlabAppId)
|
(source.type === 'gitlab' && source.gitlabAppId)
|
||||||
);
|
);
|
||||||
|
const ownSources = filteredSources.filter((source) => {
|
||||||
|
if (source.teams[0].id === $session.teamId) {
|
||||||
|
return source;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const otherSources = filteredSources.filter((source) => {
|
||||||
|
if (source.teams[0].id !== $session.teamId) {
|
||||||
|
return source;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
async function handleSubmit(gitSourceId) {
|
async function handleSubmit(gitSourceId) {
|
||||||
try {
|
try {
|
||||||
await post(`/applications/${id}/configuration/source.json`, { gitSourceId });
|
await post(`/applications/${id}/configuration/source.json`, { gitSourceId });
|
||||||
@@ -54,17 +65,21 @@
|
|||||||
return errorNotification(error);
|
return errorNotification(error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
async function newSource() {
|
||||||
|
const { id } = await post('/sources/new', {});
|
||||||
|
return await goto(`/sources/${id}`, { replaceState: true });
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="flex space-x-1 p-6 font-bold">
|
<div class="flex space-x-1 p-6 font-bold">
|
||||||
<div class="mr-4 text-2xl tracking-tight">Select a Git Source</div>
|
<div class="mr-4 text-2xl tracking-tight">Select a Git Source</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-col justify-center">
|
<div class="flex flex-col justify-center">
|
||||||
{#if !filteredSources || filteredSources.length === 0}
|
{#if !filteredSources || ownSources.length === 0}
|
||||||
<div class="flex-col">
|
<div class="flex-col">
|
||||||
<div class="pb-2">No configurable Git Source found</div>
|
<div class="pb-2 text-center">No configurable Git Source found</div>
|
||||||
<div class="flex justify-center">
|
<div class="flex justify-center">
|
||||||
<a href="/new/source" sveltekit:prefetch class="add-icon bg-orange-600 hover:bg-orange-500">
|
<button on:click={newSource} class="add-icon bg-orange-600 hover:bg-orange-500">
|
||||||
<svg
|
<svg
|
||||||
class="w-6"
|
class="w-6"
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
@@ -78,12 +93,39 @@
|
|||||||
d="M12 6v6m0 0v6m0-6h6m-6 0H6"
|
d="M12 6v6m0 0v6m0-6h6m-6 0H6"
|
||||||
/></svg
|
/></svg
|
||||||
>
|
>
|
||||||
</a>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{:else}
|
{:else}
|
||||||
<div class="flex flex-wrap justify-center">
|
<div class="flex flex-col flex-wrap justify-center px-2 md:flex-row">
|
||||||
{#each filteredSources as source}
|
{#each ownSources 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">
|
||||||
|
Configuration missing
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{#if otherSources.length > 0 && $session.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">
|
<div class="p-2">
|
||||||
<form on:submit|preventDefault={() => handleSubmit(source.id)}>
|
<form on:submit|preventDefault={() => handleSubmit(source.id)}>
|
||||||
<button
|
<button
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ export const del: RequestHandler = async (event) => {
|
|||||||
status: 200
|
status: 200
|
||||||
};
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
return ErrorHandler(error);
|
return ErrorHandler(error);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ export const post: RequestHandler = async (event) => {
|
|||||||
data: {
|
data: {
|
||||||
id: buildId,
|
id: buildId,
|
||||||
applicationId: id,
|
applicationId: id,
|
||||||
|
branch: applicationFound.branch,
|
||||||
destinationDockerId: applicationFound.destinationDocker.id,
|
destinationDockerId: applicationFound.destinationDocker.id,
|
||||||
gitSourceId: applicationFound.gitSource.id,
|
gitSourceId: applicationFound.gitSource.id,
|
||||||
githubAppId: applicationFound.gitSource.githubApp?.id,
|
githubAppId: applicationFound.gitSource.githubApp?.id,
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { getUserDetails } from '$lib/common';
|
import { getUserDetails } from '$lib/common';
|
||||||
import * as db from '$lib/database';
|
import * as db from '$lib/database';
|
||||||
import { ErrorHandler } from '$lib/database';
|
import { ErrorHandler } from '$lib/database';
|
||||||
import { checkContainer } from '$lib/haproxy';
|
import { checkContainer, isContainerExited } from '$lib/haproxy';
|
||||||
import type { RequestHandler } from '@sveltejs/kit';
|
import type { RequestHandler } from '@sveltejs/kit';
|
||||||
import jsonwebtoken from 'jsonwebtoken';
|
import jsonwebtoken from 'jsonwebtoken';
|
||||||
import { get as getRequest } from '$lib/api';
|
import { get as getRequest } from '$lib/api';
|
||||||
@@ -15,17 +15,20 @@ export const get: RequestHandler = async (event) => {
|
|||||||
|
|
||||||
const appId = process.env['COOLIFY_APP_ID'];
|
const appId = process.env['COOLIFY_APP_ID'];
|
||||||
let isRunning = false;
|
let isRunning = false;
|
||||||
|
let isExited = false;
|
||||||
let githubToken = event.locals.cookies?.githubToken || null;
|
let githubToken = event.locals.cookies?.githubToken || null;
|
||||||
let gitlabToken = event.locals.cookies?.gitlabToken || null;
|
let gitlabToken = event.locals.cookies?.gitlabToken || null;
|
||||||
try {
|
try {
|
||||||
const application = await db.getApplication({ id, teamId });
|
const application = await db.getApplication({ id, teamId });
|
||||||
if (application.destinationDockerId) {
|
if (application.destinationDockerId) {
|
||||||
isRunning = await checkContainer(application.destinationDocker.engine, id);
|
isRunning = await checkContainer(application.destinationDocker.engine, id);
|
||||||
|
isExited = await isContainerExited(application.destinationDocker.engine, id);
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
status: 200,
|
status: 200,
|
||||||
body: {
|
body: {
|
||||||
isRunning,
|
isRunning,
|
||||||
|
isExited,
|
||||||
application,
|
application,
|
||||||
appId,
|
appId,
|
||||||
githubToken,
|
githubToken,
|
||||||
@@ -56,9 +59,13 @@ export const post: RequestHandler = async (event) => {
|
|||||||
publishDirectory,
|
publishDirectory,
|
||||||
pythonWSGI,
|
pythonWSGI,
|
||||||
pythonModule,
|
pythonModule,
|
||||||
pythonVariable
|
pythonVariable,
|
||||||
|
dockerFileLocation,
|
||||||
|
denoMainFile,
|
||||||
|
denoOptions
|
||||||
} = await event.request.json();
|
} = await event.request.json();
|
||||||
if (port) port = Number(port);
|
if (port) port = Number(port);
|
||||||
|
if (denoOptions) denoOptions = denoOptions.trim();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const defaultConfiguration = await setDefaultConfiguration({
|
const defaultConfiguration = await setDefaultConfiguration({
|
||||||
@@ -68,7 +75,9 @@ export const post: RequestHandler = async (event) => {
|
|||||||
startCommand,
|
startCommand,
|
||||||
buildCommand,
|
buildCommand,
|
||||||
publishDirectory,
|
publishDirectory,
|
||||||
baseDirectory
|
baseDirectory,
|
||||||
|
dockerFileLocation,
|
||||||
|
denoMainFile
|
||||||
});
|
});
|
||||||
await db.configureApplication({
|
await db.configureApplication({
|
||||||
id,
|
id,
|
||||||
@@ -84,6 +93,9 @@ export const post: RequestHandler = async (event) => {
|
|||||||
pythonWSGI,
|
pythonWSGI,
|
||||||
pythonModule,
|
pythonModule,
|
||||||
pythonVariable,
|
pythonVariable,
|
||||||
|
dockerFileLocation,
|
||||||
|
denoMainFile,
|
||||||
|
denoOptions,
|
||||||
...defaultConfiguration
|
...defaultConfiguration
|
||||||
});
|
});
|
||||||
return { status: 201 };
|
return { status: 201 };
|
||||||
|
|||||||
@@ -48,6 +48,7 @@
|
|||||||
import { post } from '$lib/api';
|
import { post } from '$lib/api';
|
||||||
import cuid from 'cuid';
|
import cuid from 'cuid';
|
||||||
import { browser } from '$app/env';
|
import { browser } from '$app/env';
|
||||||
|
import { disabledButton } from '$lib/store';
|
||||||
const { id } = $page.params;
|
const { id } = $page.params;
|
||||||
|
|
||||||
let domainEl: HTMLInputElement;
|
let domainEl: HTMLInputElement;
|
||||||
@@ -68,11 +69,6 @@
|
|||||||
value: 'Gunicorn',
|
value: 'Gunicorn',
|
||||||
label: 'Gunicorn'
|
label: 'Gunicorn'
|
||||||
}
|
}
|
||||||
// },
|
|
||||||
// {
|
|
||||||
// value: 'uWSGI',
|
|
||||||
// label: 'uWSGI'
|
|
||||||
// }
|
|
||||||
];
|
];
|
||||||
|
|
||||||
if (browser && window.location.hostname === 'demo.coolify.io' && !application.fqdn) {
|
if (browser && window.location.hostname === 'demo.coolify.io' && !application.fqdn) {
|
||||||
@@ -127,7 +123,8 @@
|
|||||||
try {
|
try {
|
||||||
await post(`/applications/${id}/check.json`, { fqdn: application.fqdn, forceSave });
|
await post(`/applications/${id}/check.json`, { fqdn: application.fqdn, forceSave });
|
||||||
await post(`/applications/${id}.json`, { ...application });
|
await post(`/applications/${id}.json`, { ...application });
|
||||||
return window.location.reload();
|
$disabledButton = false;
|
||||||
|
return toast.push('Configurations saved.');
|
||||||
} catch ({ error }) {
|
} catch ({ error }) {
|
||||||
if (error?.startsWith('DNS not set')) {
|
if (error?.startsWith('DNS not set')) {
|
||||||
forceSave = true;
|
forceSave = true;
|
||||||
@@ -420,6 +417,48 @@
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
{#if application.buildPack === 'docker'}
|
||||||
|
<div class="grid grid-cols-2 items-center">
|
||||||
|
<label for="dockerFileLocation" class="text-base font-bold text-stone-100"
|
||||||
|
>Dockerfile Location</label
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
readonly={!$session.isAdmin}
|
||||||
|
name="dockerFileLocation"
|
||||||
|
id="dockerFileLocation"
|
||||||
|
bind:value={application.dockerFileLocation}
|
||||||
|
placeholder="default: /Dockerfile"
|
||||||
|
/>
|
||||||
|
<Explainer
|
||||||
|
text="Does not rely on Base Directory. <br>Should be absolute path, like <span class='text-green-500 font-bold'>/data/Dockerfile</span> or <span class='text-green-500 font-bold'>/Dockerfile.</span>"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
{#if application.buildPack === 'deno'}
|
||||||
|
<div class="grid grid-cols-2 items-center">
|
||||||
|
<label for="denoMainFile" class="text-base font-bold text-stone-100">Main File</label>
|
||||||
|
<input
|
||||||
|
readonly={!$session.isAdmin}
|
||||||
|
name="denoMainFile"
|
||||||
|
id="denoMainFile"
|
||||||
|
bind:value={application.denoMainFile}
|
||||||
|
placeholder="default: main.ts"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="grid grid-cols-2 items-center">
|
||||||
|
<label for="denoOptions" class="text-base font-bold text-stone-100">Arguments</label>
|
||||||
|
<input
|
||||||
|
readonly={!$session.isAdmin}
|
||||||
|
name="denoOptions"
|
||||||
|
id="denoOptions"
|
||||||
|
bind:value={application.denoOptions}
|
||||||
|
placeholder="eg: --allow-net --allow-hrtime --config path/to/file.json"
|
||||||
|
/>
|
||||||
|
<Explainer
|
||||||
|
text="List of arguments to pass to <span class='text-green-500 font-bold'>deno run</span> command. Could include permissions, configurations files, etc."
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
<div class="grid grid-cols-2 items-center">
|
<div class="grid grid-cols-2 items-center">
|
||||||
<div class="flex-col">
|
<div class="flex-col">
|
||||||
<label for="baseDirectory" class="pt-2 text-base font-bold text-stone-100"
|
<label for="baseDirectory" class="pt-2 text-base font-bold text-stone-100"
|
||||||
|
|||||||
@@ -21,12 +21,11 @@
|
|||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { page } from '$app/stores';
|
import { page } from '$app/stores';
|
||||||
import { changeQueryParams, dateOptions, getDomain } from '$lib/components/common';
|
import { changeQueryParams, dateOptions } from '$lib/components/common';
|
||||||
|
|
||||||
import BuildLog from './_BuildLog.svelte';
|
import BuildLog from './_BuildLog.svelte';
|
||||||
import { get } from '$lib/api';
|
import { get } from '$lib/api';
|
||||||
import { errorNotification } from '$lib/form';
|
import { errorNotification } from '$lib/form';
|
||||||
import { goto } from '$app/navigation';
|
|
||||||
|
|
||||||
export let builds;
|
export let builds;
|
||||||
export let application;
|
export let application;
|
||||||
@@ -174,7 +173,7 @@
|
|||||||
>
|
>
|
||||||
<div class="flex-col px-2">
|
<div class="flex-col px-2">
|
||||||
<div class="text-sm font-bold">
|
<div class="text-sm font-bold">
|
||||||
{application.branch}
|
{build.branch || application.branch}
|
||||||
</div>
|
</div>
|
||||||
<div class="text-xs">
|
<div class="text-xs">
|
||||||
{build.type}
|
{build.type}
|
||||||
|
|||||||
@@ -22,11 +22,14 @@ export const get: RequestHandler = async (event) => {
|
|||||||
if (container) {
|
if (container) {
|
||||||
return {
|
return {
|
||||||
body: {
|
body: {
|
||||||
logs: (await container.logs({ stdout: true, stderr: true, timestamps: true }))
|
logs: (
|
||||||
|
await container.logs({ stdout: true, stderr: true, timestamps: true, tail: 5000 })
|
||||||
|
)
|
||||||
.toString()
|
.toString()
|
||||||
.split('\n')
|
.split('\n')
|
||||||
.map((l) => l.slice(8))
|
.map((l) => l.slice(8))
|
||||||
.filter((a) => a)
|
.filter((a) => a)
|
||||||
|
.reverse()
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,19 +24,23 @@
|
|||||||
export let application;
|
export let application;
|
||||||
import { page } from '$app/stores';
|
import { page } from '$app/stores';
|
||||||
import LoadingLogs from './_Loading.svelte';
|
import LoadingLogs from './_Loading.svelte';
|
||||||
import { getDomain } from '$lib/components/common';
|
|
||||||
import { get } from '$lib/api';
|
import { get } from '$lib/api';
|
||||||
import { errorNotification } from '$lib/form';
|
import { errorNotification } from '$lib/form';
|
||||||
|
|
||||||
let loadLogsInterval = null;
|
let loadLogsInterval = null;
|
||||||
|
let allLogs = {
|
||||||
|
logs: []
|
||||||
|
};
|
||||||
let logs = [];
|
let logs = [];
|
||||||
let followingBuild;
|
let currentPage = 1;
|
||||||
|
let endOfLogs = false;
|
||||||
|
let startOfLogs = true;
|
||||||
let followingInterval;
|
let followingInterval;
|
||||||
let logsEl;
|
let logsEl;
|
||||||
|
|
||||||
const { id } = $page.params;
|
const { id } = $page.params;
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
loadLogs();
|
loadAllLogs();
|
||||||
loadLogsInterval = setInterval(() => {
|
loadLogsInterval = setInterval(() => {
|
||||||
loadLogs();
|
loadLogs();
|
||||||
}, 1000);
|
}, 1000);
|
||||||
@@ -45,25 +49,55 @@
|
|||||||
clearInterval(loadLogsInterval);
|
clearInterval(loadLogsInterval);
|
||||||
clearInterval(followingInterval);
|
clearInterval(followingInterval);
|
||||||
});
|
});
|
||||||
async function loadLogs() {
|
async function loadAllLogs() {
|
||||||
try {
|
try {
|
||||||
const newLogs = await get(`/applications/${id}/logs.json`);
|
const data: any = await get(`/applications/${id}/logs.json`);
|
||||||
logs = newLogs.logs;
|
allLogs = data.logs;
|
||||||
|
logs = data.logs.slice(0, 100);
|
||||||
|
if (logs.length < 100) {
|
||||||
|
endOfLogs = true;
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
} catch ({ error }) {
|
} catch ({ error }) {
|
||||||
return errorNotification(error);
|
return errorNotification(error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
async function loadLogs() {
|
||||||
function followBuild() {
|
try {
|
||||||
followingBuild = !followingBuild;
|
const newLogs = await get(`/applications/${id}/logs.json`);
|
||||||
if (followingBuild) {
|
logs = newLogs.logs.slice(0, 100);
|
||||||
followingInterval = setInterval(() => {
|
return;
|
||||||
logsEl.scrollTop = logsEl.scrollHeight;
|
} catch ({ error }) {
|
||||||
window.scrollTo(0, document.body.scrollHeight);
|
return errorNotification(error);
|
||||||
}, 100);
|
}
|
||||||
|
}
|
||||||
|
async function loadOlderLogs() {
|
||||||
|
clearInterval(loadLogsInterval);
|
||||||
|
loadLogsInterval = null;
|
||||||
|
logsEl.scrollTop = 0;
|
||||||
|
if (logs.length < 100) {
|
||||||
|
endOfLogs = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
startOfLogs = false;
|
||||||
|
endOfLogs = false;
|
||||||
|
currentPage += 1;
|
||||||
|
logs = allLogs.slice(currentPage * 100 - 100, currentPage * 100);
|
||||||
|
}
|
||||||
|
async function loadNewerLogs() {
|
||||||
|
currentPage -= 1;
|
||||||
|
logsEl.scrollTop = 0;
|
||||||
|
if (currentPage !== 1) {
|
||||||
|
clearInterval(loadLogsInterval);
|
||||||
|
endOfLogs = false;
|
||||||
|
loadLogsInterval = null;
|
||||||
|
logs = allLogs.slice(currentPage * 100 - 100, currentPage * 100);
|
||||||
} else {
|
} else {
|
||||||
window.clearInterval(followingInterval);
|
startOfLogs = true;
|
||||||
|
loadLogs();
|
||||||
|
loadLogsInterval = setInterval(() => {
|
||||||
|
loadLogs();
|
||||||
|
}, 1000);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
@@ -145,13 +179,18 @@
|
|||||||
<div class="text-xl font-bold tracking-tighter">Waiting for the logs...</div>
|
<div class="text-xl font-bold tracking-tighter">Waiting for the logs...</div>
|
||||||
{:else}
|
{:else}
|
||||||
<div class="relative w-full">
|
<div class="relative w-full">
|
||||||
<LoadingLogs />
|
<div class="text-right " />
|
||||||
<div class="flex justify-end sticky top-0 p-2">
|
{#if loadLogsInterval}
|
||||||
|
<LoadingLogs />
|
||||||
|
{/if}
|
||||||
|
<div class="flex justify-end sticky top-0 p-2 mx-1">
|
||||||
<button
|
<button
|
||||||
on:click={followBuild}
|
on:click={loadOlderLogs}
|
||||||
class="bg-transparent"
|
class:text-coolgray-100={endOfLogs}
|
||||||
data-tooltip="Follow logs"
|
class:hover:bg-coolgray-400={!endOfLogs}
|
||||||
class:text-green-500={followingBuild}
|
class="bg-transparent tooltip-bottom"
|
||||||
|
data-tooltip="Older logs"
|
||||||
|
disabled={endOfLogs}
|
||||||
>
|
>
|
||||||
<svg
|
<svg
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
@@ -164,10 +203,33 @@
|
|||||||
stroke-linejoin="round"
|
stroke-linejoin="round"
|
||||||
>
|
>
|
||||||
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
||||||
<circle cx="12" cy="12" r="9" />
|
<path
|
||||||
<line x1="8" y1="12" x2="12" y2="16" />
|
d="M20 15h-8v3.586a1 1 0 0 1 -1.707 .707l-6.586 -6.586a1 1 0 0 1 0 -1.414l6.586 -6.586a1 1 0 0 1 1.707 .707v3.586h8a1 1 0 0 1 1 1v4a1 1 0 0 1 -1 1z"
|
||||||
<line x1="12" y1="8" x2="12" y2="16" />
|
/>
|
||||||
<line x1="16" y1="12" x2="12" y2="16" />
|
</svg>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
on:click={loadNewerLogs}
|
||||||
|
class:text-coolgray-100={startOfLogs}
|
||||||
|
class:hover:bg-coolgray-400={!startOfLogs}
|
||||||
|
class="bg-transparent tooltip-bottom"
|
||||||
|
data-tooltip="Newer logs"
|
||||||
|
disabled={startOfLogs}
|
||||||
|
>
|
||||||
|
<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" />
|
||||||
|
<path
|
||||||
|
d="M4 9h8v-3.586a1 1 0 0 1 1.707 -.707l6.586 6.586a1 1 0 0 1 0 1.414l-6.586 6.586a1 1 0 0 1 -1.707 -.707v-3.586h-8a1 1 0 0 1 -1 -1v-4a1 1 0 0 1 1 -1z"
|
||||||
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -175,7 +237,7 @@
|
|||||||
class="font-mono w-full leading-6 text-left text-md tracking-tighter rounded bg-coolgray-200 py-5 px-6 whitespace-pre-wrap break-words overflow-auto max-h-[80vh] -mt-12 overflow-y-scroll scrollbar-w-1 scrollbar-thumb-coollabs scrollbar-track-coolgray-200"
|
class="font-mono w-full leading-6 text-left text-md tracking-tighter rounded bg-coolgray-200 py-5 px-6 whitespace-pre-wrap break-words overflow-auto max-h-[80vh] -mt-12 overflow-y-scroll scrollbar-w-1 scrollbar-thumb-coollabs scrollbar-track-coolgray-200"
|
||||||
bind:this={logsEl}
|
bind:this={logsEl}
|
||||||
>
|
>
|
||||||
<div class="px-2">
|
<div class="px-2 pr-14">
|
||||||
{#each logs as log}
|
{#each logs as log}
|
||||||
{log + '\n'}
|
{log + '\n'}
|
||||||
{/each}
|
{/each}
|
||||||
|
|||||||
@@ -61,9 +61,6 @@
|
|||||||
await refreshSecrets();
|
await refreshSecrets();
|
||||||
toast.push('Secrets saved');
|
toast.push('Secrets saved');
|
||||||
}
|
}
|
||||||
function asd() {
|
|
||||||
console.log(secrets);
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="flex items-center space-x-2 p-5 px-6 font-bold">
|
<div class="flex items-center space-x-2 p-5 px-6 font-bold">
|
||||||
@@ -164,7 +161,6 @@
|
|||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
<button on:click={asd}>Save</button>
|
|
||||||
<h2 class="title my-6 font-bold">Paste .env file</h2>
|
<h2 class="title my-6 font-bold">Paste .env file</h2>
|
||||||
<form on:submit|preventDefault={getValues} class="mb-12 w-full">
|
<form on:submit|preventDefault={getValues} class="mb-12 w-full">
|
||||||
<textarea bind:value={batchSecrets} class="mb-2 min-h-[200px] w-full" />
|
<textarea bind:value={batchSecrets} class="mb-2 min-h-[200px] w-full" />
|
||||||
|
|||||||
@@ -1,66 +0,0 @@
|
|||||||
<script lang="ts">
|
|
||||||
export let application;
|
|
||||||
import Rust from '$lib/components/svg/applications/Rust.svelte';
|
|
||||||
import Nodejs from '$lib/components/svg/applications/Nodejs.svelte';
|
|
||||||
import React from '$lib/components/svg/applications/React.svelte';
|
|
||||||
import Svelte from '$lib/components/svg/applications/Svelte.svelte';
|
|
||||||
import Vuejs from '$lib/components/svg/applications/Vuejs.svelte';
|
|
||||||
import PHP from '$lib/components/svg/applications/PHP.svelte';
|
|
||||||
import Python from '$lib/components/svg/applications/Python.svelte';
|
|
||||||
import Static from '$lib/components/svg/applications/Static.svelte';
|
|
||||||
import Nestjs from '$lib/components/svg/applications/Nestjs.svelte';
|
|
||||||
import Nuxtjs from '$lib/components/svg/applications/Nuxtjs.svelte';
|
|
||||||
import Nextjs from '$lib/components/svg/applications/Nextjs.svelte';
|
|
||||||
import Gatsby from '$lib/components/svg/applications/Gatsby.svelte';
|
|
||||||
import Docker from '$lib/components/svg/applications/Docker.svelte';
|
|
||||||
import Astro from '$lib/components/svg/applications/Astro.svelte';
|
|
||||||
import Eleventy from '$lib/components/svg/applications/Eleventy.svelte';
|
|
||||||
|
|
||||||
const buildPack = application?.buildPack?.toLowerCase();
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<a href="/applications/{application.id}" class="w-96 p-2 no-underline">
|
|
||||||
<div class="box-selection group relative hover:bg-green-600">
|
|
||||||
{#if buildPack === 'rust'}
|
|
||||||
<Rust />
|
|
||||||
{:else if buildPack === 'node'}
|
|
||||||
<Nodejs />
|
|
||||||
{:else if buildPack === 'react'}
|
|
||||||
<React />
|
|
||||||
{:else if buildPack === 'svelte'}
|
|
||||||
<Svelte />
|
|
||||||
{:else if buildPack === 'vuejs'}
|
|
||||||
<Vuejs />
|
|
||||||
{:else if buildPack === 'php'}
|
|
||||||
<PHP />
|
|
||||||
{:else if buildPack === 'python'}
|
|
||||||
<Python />
|
|
||||||
{:else if buildPack === 'static'}
|
|
||||||
<Static />
|
|
||||||
{:else if buildPack === 'nestjs'}
|
|
||||||
<Nestjs />
|
|
||||||
{:else if buildPack === 'nuxtjs'}
|
|
||||||
<Nuxtjs />
|
|
||||||
{:else if buildPack === 'nextjs'}
|
|
||||||
<Nextjs />
|
|
||||||
{:else if buildPack === 'gatsby'}
|
|
||||||
<Gatsby />
|
|
||||||
{:else if buildPack === 'docker'}
|
|
||||||
<Docker />
|
|
||||||
{:else if buildPack === 'astro'}
|
|
||||||
<Astro />
|
|
||||||
{:else if buildPack === 'eleventy'}
|
|
||||||
<Eleventy />
|
|
||||||
{/if}
|
|
||||||
|
|
||||||
<div class="truncate text-center text-xl font-bold">{application.name}</div>
|
|
||||||
{#if application.fqdn}
|
|
||||||
<div class="truncate text-center">{application.fqdn}</div>
|
|
||||||
{/if}
|
|
||||||
{#if !application.gitSourceId || !application.destinationDockerId}
|
|
||||||
<div class="truncate text-center font-bold text-red-500 group-hover:text-white">
|
|
||||||
Configuration missing
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
</a>
|
|
||||||
@@ -1,13 +1,41 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
export let applications: Array<Application>;
|
export let applications: Array<Application>;
|
||||||
import { session } from '$app/stores';
|
import { session } from '$app/stores';
|
||||||
import Application from './_Application.svelte';
|
|
||||||
import { post } from '$lib/api';
|
import { post } from '$lib/api';
|
||||||
import { goto } from '$app/navigation';
|
import { goto } from '$app/navigation';
|
||||||
|
|
||||||
|
import Rust from '$lib/components/svg/applications/Rust.svelte';
|
||||||
|
import Nodejs from '$lib/components/svg/applications/Nodejs.svelte';
|
||||||
|
import React from '$lib/components/svg/applications/React.svelte';
|
||||||
|
import Svelte from '$lib/components/svg/applications/Svelte.svelte';
|
||||||
|
import Vuejs from '$lib/components/svg/applications/Vuejs.svelte';
|
||||||
|
import PHP from '$lib/components/svg/applications/PHP.svelte';
|
||||||
|
import Python from '$lib/components/svg/applications/Python.svelte';
|
||||||
|
import Static from '$lib/components/svg/applications/Static.svelte';
|
||||||
|
import Nestjs from '$lib/components/svg/applications/Nestjs.svelte';
|
||||||
|
import Nuxtjs from '$lib/components/svg/applications/Nuxtjs.svelte';
|
||||||
|
import Nextjs from '$lib/components/svg/applications/Nextjs.svelte';
|
||||||
|
import Gatsby from '$lib/components/svg/applications/Gatsby.svelte';
|
||||||
|
import Docker from '$lib/components/svg/applications/Docker.svelte';
|
||||||
|
import Astro from '$lib/components/svg/applications/Astro.svelte';
|
||||||
|
import Eleventy from '$lib/components/svg/applications/Eleventy.svelte';
|
||||||
|
import Deno from '$lib/components/svg/applications/Deno.svelte';
|
||||||
|
import { getDomain } from '$lib/components/common';
|
||||||
|
|
||||||
async function newApplication() {
|
async function newApplication() {
|
||||||
const { id } = await post('/applications/new', {});
|
const { id } = await post('/applications/new', {});
|
||||||
return await goto(`/applications/${id}`, { replaceState: true });
|
return await goto(`/applications/${id}`, { replaceState: true });
|
||||||
}
|
}
|
||||||
|
const ownApplications = applications.filter((application) => {
|
||||||
|
if (application.teams[0].id === $session.teamId) {
|
||||||
|
return application;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const otherApplications = applications.filter((application) => {
|
||||||
|
if (application.teams[0].id !== $session.teamId) {
|
||||||
|
return application;
|
||||||
|
}
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="flex space-x-1 p-6 font-bold">
|
<div class="flex space-x-1 p-6 font-bold">
|
||||||
@@ -30,14 +58,129 @@
|
|||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-wrap justify-center">
|
<div class="flex flex-col flex-wrap justify-center">
|
||||||
{#if !applications || applications.length === 0}
|
{#if !applications || ownApplications.length === 0}
|
||||||
<div class="flex-col">
|
<div class="flex-col">
|
||||||
<div class="text-center text-xl font-bold">No applications found</div>
|
<div class="text-center text-xl font-bold">No applications found</div>
|
||||||
</div>
|
</div>
|
||||||
{:else}
|
{/if}
|
||||||
{#each applications as application}
|
{#if ownApplications.length > 0 || otherApplications.length > 0}
|
||||||
<Application {application} />
|
<div class="flex flex-col">
|
||||||
{/each}
|
<div class="flex flex-col flex-wrap justify-center px-2 md:flex-row">
|
||||||
|
{#each ownApplications as application}
|
||||||
|
<a href="/applications/{application.id}" class="w-96 p-2 no-underline">
|
||||||
|
<div class="box-selection group relative hover:bg-green-600">
|
||||||
|
{#if application.buildPack}
|
||||||
|
{#if application.buildPack.toLowerCase() === 'rust'}
|
||||||
|
<Rust />
|
||||||
|
{:else if application.buildPack.toLowerCase() === 'node'}
|
||||||
|
<Nodejs />
|
||||||
|
{:else if application.buildPack.toLowerCase() === 'react'}
|
||||||
|
<React />
|
||||||
|
{:else if application.buildPack.toLowerCase() === 'svelte'}
|
||||||
|
<Svelte />
|
||||||
|
{:else if application.buildPack.toLowerCase() === 'vuejs'}
|
||||||
|
<Vuejs />
|
||||||
|
{:else if application.buildPack.toLowerCase() === 'php'}
|
||||||
|
<PHP />
|
||||||
|
{:else if application.buildPack.toLowerCase() === 'python'}
|
||||||
|
<Python />
|
||||||
|
{:else if application.buildPack.toLowerCase() === 'static'}
|
||||||
|
<Static />
|
||||||
|
{:else if application.buildPack.toLowerCase() === 'nestjs'}
|
||||||
|
<Nestjs />
|
||||||
|
{:else if application.buildPack.toLowerCase() === 'nuxtjs'}
|
||||||
|
<Nuxtjs />
|
||||||
|
{:else if application.buildPack.toLowerCase() === 'nextjs'}
|
||||||
|
<Nextjs />
|
||||||
|
{:else if application.buildPack.toLowerCase() === 'gatsby'}
|
||||||
|
<Gatsby />
|
||||||
|
{:else if application.buildPack.toLowerCase() === 'docker'}
|
||||||
|
<Docker />
|
||||||
|
{:else if application.buildPack.toLowerCase() === 'astro'}
|
||||||
|
<Astro />
|
||||||
|
{:else if application.buildPack.toLowerCase() === 'eleventy'}
|
||||||
|
<Eleventy />
|
||||||
|
{:else if application.buildPack.toLowerCase() === 'deno'}
|
||||||
|
<Deno />
|
||||||
|
{/if}
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
<div class="truncate text-center text-xl font-bold">{application.name}</div>
|
||||||
|
{#if $session.teamId === '0' && otherApplications.length > 0}
|
||||||
|
<div class="truncate text-center">Team {application.teams[0].name}</div>
|
||||||
|
{/if}
|
||||||
|
{#if application.fqdn}
|
||||||
|
<div class="truncate text-center">{getDomain(application.fqdn) || ''}</div>
|
||||||
|
{/if}
|
||||||
|
{#if !application.gitSourceId || !application.destinationDockerId || !application.fqdn}
|
||||||
|
<div class="truncate text-center font-bold text-red-500 group-hover:text-white">
|
||||||
|
Configuration missing
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
{#if otherApplications.length > 0 && $session.teamId === '0'}
|
||||||
|
<div class="px-6 pb-5 pt-10 text-xl font-bold">Other Applications</div>
|
||||||
|
<div class="flex flex-col flex-wrap justify-center px-2 md:flex-row">
|
||||||
|
{#each otherApplications as application}
|
||||||
|
<a href="/applications/{application.id}" class="w-96 p-2 no-underline">
|
||||||
|
<div class="box-selection group relative hover:bg-green-600">
|
||||||
|
{#if application.buildPack}
|
||||||
|
{#if application.buildPack.toLowerCase() === 'rust'}
|
||||||
|
<Rust />
|
||||||
|
{:else if application.buildPack.toLowerCase() === 'node'}
|
||||||
|
<Nodejs />
|
||||||
|
{:else if application.buildPack.toLowerCase() === 'react'}
|
||||||
|
<React />
|
||||||
|
{:else if application.buildPack.toLowerCase() === 'svelte'}
|
||||||
|
<Svelte />
|
||||||
|
{:else if application.buildPack.toLowerCase() === 'vuejs'}
|
||||||
|
<Vuejs />
|
||||||
|
{:else if application.buildPack.toLowerCase() === 'php'}
|
||||||
|
<PHP />
|
||||||
|
{:else if application.buildPack.toLowerCase() === 'python'}
|
||||||
|
<Python />
|
||||||
|
{:else if application.buildPack.toLowerCase() === 'static'}
|
||||||
|
<Static />
|
||||||
|
{:else if application.buildPack.toLowerCase() === 'nestjs'}
|
||||||
|
<Nestjs />
|
||||||
|
{:else if application.buildPack.toLowerCase() === 'nuxtjs'}
|
||||||
|
<Nuxtjs />
|
||||||
|
{:else if application.buildPack.toLowerCase() === 'nextjs'}
|
||||||
|
<Nextjs />
|
||||||
|
{:else if application.buildPack.toLowerCase() === 'gatsby'}
|
||||||
|
<Gatsby />
|
||||||
|
{:else if application.buildPack.toLowerCase() === 'docker'}
|
||||||
|
<Docker />
|
||||||
|
{:else if application.buildPack.toLowerCase() === 'astro'}
|
||||||
|
<Astro />
|
||||||
|
{:else if application.buildPack.toLowerCase() === 'eleventy'}
|
||||||
|
<Eleventy />
|
||||||
|
{:else if application.buildPack.toLowerCase() === 'deno'}
|
||||||
|
<Deno />
|
||||||
|
{/if}
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
<div class="truncate text-center text-xl font-bold">{application.name}</div>
|
||||||
|
{#if $session.teamId === '0'}
|
||||||
|
<div class="truncate text-center">Team {application.teams[0].name}</div>
|
||||||
|
{/if}
|
||||||
|
{#if application.fqdn}
|
||||||
|
<div class="truncate text-center">{getDomain(application.fqdn) || ''}</div>
|
||||||
|
{/if}
|
||||||
|
{#if !application.gitSourceId || !application.destinationDockerId || !application.fqdn}
|
||||||
|
<div class="truncate text-center font-bold text-red-500 group-hover:text-white">
|
||||||
|
Configuration missing
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { getTeam, getUserDetails } from '$lib/common';
|
import { getUserDetails } from '$lib/common';
|
||||||
import * as db from '$lib/database';
|
import * as db from '$lib/database';
|
||||||
import { ErrorHandler } from '$lib/database';
|
import { ErrorHandler } from '$lib/database';
|
||||||
import type { RequestHandler } from '@sveltejs/kit';
|
import type { RequestHandler } from '@sveltejs/kit';
|
||||||
@@ -9,23 +9,28 @@ export const get: RequestHandler = async (event) => {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const applicationsCount = await db.prisma.application.count({
|
const applicationsCount = await db.prisma.application.count({
|
||||||
where: { teams: { some: { id: teamId } } }
|
where: { teams: { some: { id: teamId === '0' ? undefined : teamId } } }
|
||||||
});
|
});
|
||||||
const sourcesCount = await db.prisma.gitSource.count({
|
const sourcesCount = await db.prisma.gitSource.count({
|
||||||
where: { teams: { some: { id: teamId } } }
|
where: { teams: { some: { id: teamId === '0' ? undefined : teamId } } }
|
||||||
});
|
});
|
||||||
const destinationsCount = await db.prisma.destinationDocker.count({
|
const destinationsCount = await db.prisma.destinationDocker.count({
|
||||||
where: { teams: { some: { id: teamId } } }
|
where: { teams: { some: { id: teamId === '0' ? undefined : teamId } } }
|
||||||
});
|
});
|
||||||
const teamsCount = await db.prisma.permission.count({ where: { userId } });
|
const teamsCount = await db.prisma.permission.count({ where: { userId } });
|
||||||
const databasesCount = await db.prisma.database.count({
|
const databasesCount = await db.prisma.database.count({
|
||||||
where: { teams: { some: { id: teamId } } }
|
where: { teams: { some: { id: teamId === '0' ? undefined : teamId } } }
|
||||||
});
|
});
|
||||||
const servicesCount = await db.prisma.service.count({
|
const servicesCount = await db.prisma.service.count({
|
||||||
where: { teams: { some: { id: teamId } } }
|
where: { teams: { some: { id: teamId === '0' ? undefined : teamId } } }
|
||||||
|
});
|
||||||
|
const teams = await db.prisma.permission.findMany({
|
||||||
|
where: { userId },
|
||||||
|
include: { team: { include: { _count: { select: { users: true } } } } }
|
||||||
});
|
});
|
||||||
return {
|
return {
|
||||||
body: {
|
body: {
|
||||||
|
teams,
|
||||||
applicationsCount,
|
applicationsCount,
|
||||||
sourcesCount,
|
sourcesCount,
|
||||||
destinationsCount,
|
destinationsCount,
|
||||||
|
|||||||
@@ -2,6 +2,8 @@
|
|||||||
export let database;
|
export let database;
|
||||||
export let privatePort;
|
export let privatePort;
|
||||||
export let settings;
|
export let settings;
|
||||||
|
export let isRunning;
|
||||||
|
|
||||||
import { page, session } from '$app/stores';
|
import { page, session } from '$app/stores';
|
||||||
import CopyPasswordField from '$lib/components/CopyPasswordField.svelte';
|
import CopyPasswordField from '$lib/components/CopyPasswordField.svelte';
|
||||||
import Setting from '$lib/components/Setting.svelte';
|
import Setting from '$lib/components/Setting.svelte';
|
||||||
@@ -15,27 +17,39 @@
|
|||||||
import { browser } from '$app/env';
|
import { browser } from '$app/env';
|
||||||
import { post } from '$lib/api';
|
import { post } from '$lib/api';
|
||||||
import { getDomain } from '$lib/components/common';
|
import { getDomain } from '$lib/components/common';
|
||||||
|
import { toast } from '@zerodevx/svelte-toast';
|
||||||
|
|
||||||
const { id } = $page.params;
|
const { id } = $page.params;
|
||||||
|
|
||||||
let loading = false;
|
let loading = false;
|
||||||
|
let publicLoading = false;
|
||||||
|
|
||||||
let isPublic = database.settings.isPublic || false;
|
let isPublic = database.settings.isPublic || false;
|
||||||
let appendOnly = database.settings.appendOnly;
|
let appendOnly = database.settings.appendOnly;
|
||||||
|
|
||||||
let databaseDefault = database.defaultDatabase;
|
let databaseDefault;
|
||||||
let databaseDbUser = database.dbUser;
|
let databaseDbUser;
|
||||||
let databaseDbUserPassword = database.dbUserPassword;
|
let databaseDbUserPassword;
|
||||||
if (database.type === 'mongodb') {
|
|
||||||
databaseDefault = '?readPreference=primary&ssl=false';
|
generateDbDetails();
|
||||||
databaseDbUser = database.rootUser;
|
|
||||||
databaseDbUserPassword = database.rootUserPassword;
|
function generateDbDetails() {
|
||||||
} else if (database.type === 'redis') {
|
databaseDefault = database.defaultDatabase;
|
||||||
databaseDefault = '';
|
databaseDbUser = database.dbUser;
|
||||||
databaseDbUser = '';
|
databaseDbUserPassword = database.dbUserPassword;
|
||||||
|
if (database.type === 'mongodb') {
|
||||||
|
databaseDefault = '?readPreference=primary&ssl=false';
|
||||||
|
databaseDbUser = database.rootUser;
|
||||||
|
databaseDbUserPassword = database.rootUserPassword;
|
||||||
|
} else if (database.type === 'redis') {
|
||||||
|
databaseDefault = '';
|
||||||
|
databaseDbUser = '';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
let databaseUrl = generateUrl();
|
$: databaseUrl = generateUrl();
|
||||||
|
|
||||||
function generateUrl() {
|
function generateUrl() {
|
||||||
return browser
|
return (databaseUrl = browser
|
||||||
? `${database.type}://${
|
? `${database.type}://${
|
||||||
databaseDbUser ? databaseDbUser + ':' : ''
|
databaseDbUser ? databaseDbUser + ':' : ''
|
||||||
}${databaseDbUserPassword}@${
|
}${databaseDbUserPassword}@${
|
||||||
@@ -45,32 +59,50 @@
|
|||||||
: window.location.hostname
|
: window.location.hostname
|
||||||
: database.id
|
: database.id
|
||||||
}:${isPublic ? database.publicPort : privatePort}/${databaseDefault}`
|
}:${isPublic ? database.publicPort : privatePort}/${databaseDefault}`
|
||||||
: 'Loading...';
|
: 'Loading...');
|
||||||
}
|
}
|
||||||
|
|
||||||
async function changeSettings(name) {
|
async function changeSettings(name) {
|
||||||
|
if (publicLoading || !isRunning) return;
|
||||||
|
publicLoading = true;
|
||||||
|
let data = {
|
||||||
|
isPublic,
|
||||||
|
appendOnly
|
||||||
|
};
|
||||||
if (name === 'isPublic') {
|
if (name === 'isPublic') {
|
||||||
isPublic = !isPublic;
|
data.isPublic = !isPublic;
|
||||||
}
|
}
|
||||||
if (name === 'appendOnly') {
|
if (name === 'appendOnly') {
|
||||||
appendOnly = !appendOnly;
|
data.appendOnly = !appendOnly;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
const { publicPort } = await post(`/databases/${id}/settings.json`, { isPublic, appendOnly });
|
const { publicPort } = await post(`/databases/${id}/settings.json`, {
|
||||||
|
isPublic: data.isPublic,
|
||||||
|
appendOnly: data.appendOnly
|
||||||
|
});
|
||||||
|
isPublic = data.isPublic;
|
||||||
|
appendOnly = data.appendOnly;
|
||||||
|
databaseUrl = generateUrl();
|
||||||
if (isPublic) {
|
if (isPublic) {
|
||||||
database.publicPort = publicPort;
|
database.publicPort = publicPort;
|
||||||
}
|
}
|
||||||
databaseUrl = generateUrl();
|
|
||||||
} catch ({ error }) {
|
} catch ({ error }) {
|
||||||
return errorNotification(error);
|
return errorNotification(error);
|
||||||
|
} finally {
|
||||||
|
publicLoading = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
async function handleSubmit() {
|
async function handleSubmit() {
|
||||||
try {
|
try {
|
||||||
await post(`/databases/${id}.json`, { ...database });
|
loading = true;
|
||||||
return window.location.reload();
|
await post(`/databases/${id}.json`, { ...database, isRunning });
|
||||||
|
generateDbDetails();
|
||||||
|
databaseUrl = generateUrl();
|
||||||
|
toast.push('Settings saved.');
|
||||||
} catch ({ error }) {
|
} catch ({ error }) {
|
||||||
return errorNotification(error);
|
return errorNotification(error);
|
||||||
|
} finally {
|
||||||
|
loading = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
@@ -142,21 +174,21 @@
|
|||||||
readonly
|
readonly
|
||||||
disabled
|
disabled
|
||||||
name="publicPort"
|
name="publicPort"
|
||||||
value={isPublic ? database.publicPort : privatePort}
|
value={publicLoading ? 'Loading...' : isPublic ? database.publicPort : privatePort}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="grid grid-flow-row gap-2">
|
<div class="grid grid-flow-row gap-2">
|
||||||
{#if database.type === 'mysql'}
|
{#if database.type === 'mysql'}
|
||||||
<MySql bind:database />
|
<MySql bind:database {isRunning} />
|
||||||
{:else if database.type === 'postgresql'}
|
{:else if database.type === 'postgresql'}
|
||||||
<PostgreSql bind:database />
|
<PostgreSql bind:database {isRunning} />
|
||||||
{:else if database.type === 'mongodb'}
|
{:else if database.type === 'mongodb'}
|
||||||
<MongoDb {database} />
|
<MongoDb bind:database {isRunning} />
|
||||||
{:else if database.type === 'redis'}
|
{:else if database.type === 'redis'}
|
||||||
<Redis {database} />
|
<Redis bind:database {isRunning} />
|
||||||
{:else if database.type === 'couchdb'}
|
{:else if database.type === 'couchdb'}
|
||||||
<CouchDb bind:database />
|
<CouchDb {database} />
|
||||||
{/if}
|
{/if}
|
||||||
<div class="grid grid-cols-2 items-center px-10 pb-8">
|
<div class="grid grid-cols-2 items-center px-10 pb-8">
|
||||||
<label for="url" class="text-base font-bold text-stone-100">Connection String</label>
|
<label for="url" class="text-base font-bold text-stone-100">Connection String</label>
|
||||||
@@ -168,7 +200,7 @@
|
|||||||
name="url"
|
name="url"
|
||||||
readonly
|
readonly
|
||||||
disabled
|
disabled
|
||||||
value={databaseUrl}
|
value={publicLoading || loading ? 'Loading...' : generateUrl()}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -179,10 +211,12 @@
|
|||||||
<div class="px-10 pb-10">
|
<div class="px-10 pb-10">
|
||||||
<div class="grid grid-cols-2 items-center">
|
<div class="grid grid-cols-2 items-center">
|
||||||
<Setting
|
<Setting
|
||||||
|
loading={publicLoading}
|
||||||
bind:setting={isPublic}
|
bind:setting={isPublic}
|
||||||
on:click={() => changeSettings('isPublic')}
|
on:click={() => changeSettings('isPublic')}
|
||||||
title="Set it public"
|
title="Set it public"
|
||||||
description="Your database will be reachable over the internet. <br>Take security seriously in this case!"
|
description="Your database will be reachable over the internet. <br>Take security seriously in this case!"
|
||||||
|
disabled={!isRunning}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{#if database.type === 'redis'}
|
{#if database.type === 'redis'}
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
<script>
|
<script>
|
||||||
export let database;
|
export let database;
|
||||||
|
export let isRunning;
|
||||||
import CopyPasswordField from '$lib/components/CopyPasswordField.svelte';
|
import CopyPasswordField from '$lib/components/CopyPasswordField.svelte';
|
||||||
|
import Explainer from '$lib/components/Explainer.svelte';
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="flex space-x-1 py-5 font-bold">
|
<div class="flex space-x-1 py-5 font-bold">
|
||||||
@@ -21,13 +23,14 @@
|
|||||||
<div class="grid grid-cols-2 items-center">
|
<div class="grid grid-cols-2 items-center">
|
||||||
<label for="rootUserPassword" class="text-base font-bold text-stone-100">Root's Password</label>
|
<label for="rootUserPassword" class="text-base font-bold text-stone-100">Root's Password</label>
|
||||||
<CopyPasswordField
|
<CopyPasswordField
|
||||||
|
disabled={!isRunning}
|
||||||
|
readonly={!isRunning}
|
||||||
placeholder="Generated automatically after start"
|
placeholder="Generated automatically after start"
|
||||||
isPasswordField={true}
|
isPasswordField={true}
|
||||||
readonly
|
|
||||||
disabled
|
|
||||||
id="rootUserPassword"
|
id="rootUserPassword"
|
||||||
name="rootUserPassword"
|
name="rootUserPassword"
|
||||||
value={database.rootUserPassword}
|
bind:value={database.rootUserPassword}
|
||||||
/>
|
/>
|
||||||
|
<Explainer text="Could be changed while the database is running." />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
<script>
|
<script>
|
||||||
export let database;
|
export let database;
|
||||||
|
export let isRunning;
|
||||||
import CopyPasswordField from '$lib/components/CopyPasswordField.svelte';
|
import CopyPasswordField from '$lib/components/CopyPasswordField.svelte';
|
||||||
|
import Explainer from '$lib/components/Explainer.svelte';
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="flex space-x-1 py-5 font-bold">
|
<div class="flex space-x-1 py-5 font-bold">
|
||||||
@@ -33,14 +35,15 @@
|
|||||||
<div class="grid grid-cols-2 items-center">
|
<div class="grid grid-cols-2 items-center">
|
||||||
<label for="dbUserPassword" class="text-base font-bold text-stone-100">Password</label>
|
<label for="dbUserPassword" class="text-base font-bold text-stone-100">Password</label>
|
||||||
<CopyPasswordField
|
<CopyPasswordField
|
||||||
readonly
|
disabled={!isRunning}
|
||||||
disabled
|
readonly={!isRunning}
|
||||||
placeholder="Generated automatically after start"
|
placeholder="Generated automatically after start"
|
||||||
isPasswordField
|
isPasswordField
|
||||||
id="dbUserPassword"
|
id="dbUserPassword"
|
||||||
name="dbUserPassword"
|
name="dbUserPassword"
|
||||||
value={database.dbUserPassword}
|
bind:value={database.dbUserPassword}
|
||||||
/>
|
/>
|
||||||
|
<Explainer text="Could be changed while the database is running." />
|
||||||
</div>
|
</div>
|
||||||
<div class="grid grid-cols-2 items-center">
|
<div class="grid grid-cols-2 items-center">
|
||||||
<label for="rootUser" class="text-base font-bold text-stone-100">Root User</label>
|
<label for="rootUser" class="text-base font-bold text-stone-100">Root User</label>
|
||||||
@@ -56,13 +59,14 @@
|
|||||||
<div class="grid grid-cols-2 items-center">
|
<div class="grid grid-cols-2 items-center">
|
||||||
<label for="rootUserPassword" class="text-base font-bold text-stone-100">Root's Password</label>
|
<label for="rootUserPassword" class="text-base font-bold text-stone-100">Root's Password</label>
|
||||||
<CopyPasswordField
|
<CopyPasswordField
|
||||||
readonly
|
disabled={!isRunning}
|
||||||
disabled
|
readonly={!isRunning}
|
||||||
placeholder="Generated automatically after start"
|
placeholder="Generated automatically after start"
|
||||||
isPasswordField
|
isPasswordField
|
||||||
id="rootUserPassword"
|
id="rootUserPassword"
|
||||||
name="rootUserPassword"
|
name="rootUserPassword"
|
||||||
value={database.rootUserPassword}
|
bind:value={database.rootUserPassword}
|
||||||
/>
|
/>
|
||||||
|
<Explainer text="Could be changed while the database is running." />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
<script>
|
<script>
|
||||||
export let database;
|
export let database;
|
||||||
|
export let isRunning;
|
||||||
import CopyPasswordField from '$lib/components/CopyPasswordField.svelte';
|
import CopyPasswordField from '$lib/components/CopyPasswordField.svelte';
|
||||||
|
import Explainer from '$lib/components/Explainer.svelte';
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="flex space-x-1 py-5 font-bold">
|
<div class="flex space-x-1 py-5 font-bold">
|
||||||
@@ -19,6 +21,21 @@
|
|||||||
bind:value={database.defaultDatabase}
|
bind:value={database.defaultDatabase}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="grid grid-cols-2 items-center">
|
||||||
|
<label for="rootUser" class="text-base font-bold text-stone-100"
|
||||||
|
>Root (postgres) User Password</label
|
||||||
|
>
|
||||||
|
<CopyPasswordField
|
||||||
|
disabled={!isRunning}
|
||||||
|
readonly={!isRunning}
|
||||||
|
placeholder="Generated automatically after start"
|
||||||
|
isPasswordField
|
||||||
|
id="rootUserPassword"
|
||||||
|
name="rootUserPassword"
|
||||||
|
bind:value={database.rootUserPassword}
|
||||||
|
/>
|
||||||
|
<Explainer text="Could be changed while the database is running." />
|
||||||
|
</div>
|
||||||
<div class="grid grid-cols-2 items-center">
|
<div class="grid grid-cols-2 items-center">
|
||||||
<label for="dbUser" class="text-base font-bold text-stone-100">User</label>
|
<label for="dbUser" class="text-base font-bold text-stone-100">User</label>
|
||||||
<CopyPasswordField
|
<CopyPasswordField
|
||||||
@@ -33,13 +50,14 @@
|
|||||||
<div class="grid grid-cols-2 items-center">
|
<div class="grid grid-cols-2 items-center">
|
||||||
<label for="dbUserPassword" class="text-base font-bold text-stone-100">Password</label>
|
<label for="dbUserPassword" class="text-base font-bold text-stone-100">Password</label>
|
||||||
<CopyPasswordField
|
<CopyPasswordField
|
||||||
readonly
|
disabled={!isRunning}
|
||||||
disabled
|
readonly={!isRunning}
|
||||||
placeholder="Generated automatically after start"
|
placeholder="Generated automatically after start"
|
||||||
isPasswordField
|
isPasswordField
|
||||||
id="dbUserPassword"
|
id="dbUserPassword"
|
||||||
name="dbUserPassword"
|
name="dbUserPassword"
|
||||||
value={database.dbUserPassword}
|
bind:value={database.dbUserPassword}
|
||||||
/>
|
/>
|
||||||
|
<Explainer text="Could be changed while the database is running." />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
<script>
|
<script>
|
||||||
export let database;
|
export let database;
|
||||||
|
export let isRunning;
|
||||||
import CopyPasswordField from '$lib/components/CopyPasswordField.svelte';
|
import CopyPasswordField from '$lib/components/CopyPasswordField.svelte';
|
||||||
|
import Explainer from '$lib/components/Explainer.svelte';
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="flex space-x-1 py-5 font-bold">
|
<div class="flex space-x-1 py-5 font-bold">
|
||||||
@@ -10,40 +12,14 @@
|
|||||||
<div class="grid grid-cols-2 items-center">
|
<div class="grid grid-cols-2 items-center">
|
||||||
<label for="dbUserPassword" class="text-base font-bold text-stone-100">Password</label>
|
<label for="dbUserPassword" class="text-base font-bold text-stone-100">Password</label>
|
||||||
<CopyPasswordField
|
<CopyPasswordField
|
||||||
disabled
|
disabled={!isRunning}
|
||||||
readonly
|
readonly={!isRunning}
|
||||||
placeholder="Generated automatically after start"
|
placeholder="Generated automatically after start"
|
||||||
isPasswordField
|
isPasswordField
|
||||||
id="dbUserPassword"
|
id="dbUserPassword"
|
||||||
name="dbUserPassword"
|
name="dbUserPassword"
|
||||||
value={database.dbUserPassword}
|
bind:value={database.dbUserPassword}
|
||||||
/>
|
/>
|
||||||
|
<Explainer text="Could be changed while the database is running." />
|
||||||
</div>
|
</div>
|
||||||
<!-- <div class="grid grid-cols-3 items-center">
|
|
||||||
<label for="rootUser">Root User</label>
|
|
||||||
<div class="col-span-2 ">
|
|
||||||
<CopyPasswordField
|
|
||||||
disabled
|
|
||||||
readonly
|
|
||||||
placeholder="Generated automatically after start"
|
|
||||||
id="rootUser"
|
|
||||||
name="rootUser"
|
|
||||||
value={database.rootUser}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="grid grid-cols-3 items-center">
|
|
||||||
<label for="rootUserPassword">Root's Password</label>
|
|
||||||
<div class="col-span-2 ">
|
|
||||||
<CopyPasswordField
|
|
||||||
disabled
|
|
||||||
readonly
|
|
||||||
placeholder="Generated automatically after start"
|
|
||||||
isPasswordField
|
|
||||||
id="rootUserPassword"
|
|
||||||
name="rootUserPassword"
|
|
||||||
value={database.rootUserPassword}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div> -->
|
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -15,7 +15,7 @@
|
|||||||
const endpoint = `/databases/${params.id}.json`;
|
const endpoint = `/databases/${params.id}.json`;
|
||||||
const res = await fetch(endpoint);
|
const res = await fetch(endpoint);
|
||||||
if (res.ok) {
|
if (res.ok) {
|
||||||
const { database, state, versions, privatePort, settings } = await res.json();
|
const { database, isRunning, versions, privatePort, settings } = await res.json();
|
||||||
if (!database || Object.entries(database).length === 0) {
|
if (!database || Object.entries(database).length === 0) {
|
||||||
return {
|
return {
|
||||||
status: 302,
|
status: 302,
|
||||||
@@ -35,13 +35,13 @@
|
|||||||
return {
|
return {
|
||||||
props: {
|
props: {
|
||||||
database,
|
database,
|
||||||
state,
|
isRunning,
|
||||||
versions,
|
versions,
|
||||||
privatePort
|
privatePort
|
||||||
},
|
},
|
||||||
stuff: {
|
stuff: {
|
||||||
database,
|
database,
|
||||||
state,
|
isRunning,
|
||||||
versions,
|
versions,
|
||||||
privatePort,
|
privatePort,
|
||||||
settings
|
settings
|
||||||
@@ -65,7 +65,7 @@
|
|||||||
import { goto } from '$app/navigation';
|
import { goto } from '$app/navigation';
|
||||||
|
|
||||||
export let database;
|
export let database;
|
||||||
export let state;
|
export let isRunning;
|
||||||
let loading = false;
|
let loading = false;
|
||||||
|
|
||||||
async function deleteDatabase() {
|
async function deleteDatabase() {
|
||||||
@@ -91,8 +91,6 @@
|
|||||||
return window.location.reload();
|
return window.location.reload();
|
||||||
} catch ({ error }) {
|
} catch ({ error }) {
|
||||||
return errorNotification(error);
|
return errorNotification(error);
|
||||||
} finally {
|
|
||||||
loading = false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -103,8 +101,6 @@
|
|||||||
return window.location.reload();
|
return window.location.reload();
|
||||||
} catch ({ error }) {
|
} catch ({ error }) {
|
||||||
return errorNotification(error);
|
return errorNotification(error);
|
||||||
} finally {
|
|
||||||
loading = false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
@@ -114,7 +110,7 @@
|
|||||||
<Loading fullscreen cover />
|
<Loading fullscreen cover />
|
||||||
{:else}
|
{:else}
|
||||||
{#if database.type && database.destinationDockerId && database.version && database.defaultDatabase}
|
{#if database.type && database.destinationDockerId && database.version && database.defaultDatabase}
|
||||||
{#if state === 'running'}
|
{#if isRunning}
|
||||||
<button
|
<button
|
||||||
on:click={stopDatabase}
|
on:click={stopDatabase}
|
||||||
title="Stop database"
|
title="Stop database"
|
||||||
@@ -140,7 +136,7 @@
|
|||||||
<rect x="14" y="5" width="4" height="14" rx="1" />
|
<rect x="14" y="5" width="4" height="14" rx="1" />
|
||||||
</svg>
|
</svg>
|
||||||
</button>
|
</button>
|
||||||
{:else if state === 'not started'}
|
{:else}
|
||||||
<button
|
<button
|
||||||
on:click={startDatabase}
|
on:click={startDatabase}
|
||||||
title="Start database"
|
title="Start database"
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { getUserDetails } from '$lib/common';
|
import { getUserDetails } from '$lib/common';
|
||||||
|
import { supportedDatabaseTypesAndVersions } from '$lib/components/common';
|
||||||
import * as db from '$lib/database';
|
import * as db from '$lib/database';
|
||||||
import { ErrorHandler, supportedDatabaseTypesAndVersions } from '$lib/database';
|
import { ErrorHandler } from '$lib/database';
|
||||||
import type { RequestHandler } from '@sveltejs/kit';
|
import type { RequestHandler } from '@sveltejs/kit';
|
||||||
|
|
||||||
export const get: RequestHandler = async (event) => {
|
export const get: RequestHandler = async (event) => {
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { getUserDetails } from '$lib/common';
|
import { getUserDetails } from '$lib/common';
|
||||||
|
import { supportedDatabaseTypesAndVersions } from '$lib/components/common';
|
||||||
import * as db from '$lib/database';
|
import * as db from '$lib/database';
|
||||||
import { ErrorHandler, supportedDatabaseTypesAndVersions } from '$lib/database';
|
import { ErrorHandler } from '$lib/database';
|
||||||
import type { RequestHandler } from '@sveltejs/kit';
|
import type { RequestHandler } from '@sveltejs/kit';
|
||||||
|
|
||||||
export const get: RequestHandler = async (event) => {
|
export const get: RequestHandler = async (event) => {
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user