parent
a64b095c13
commit
460ae85226
@ -1,7 +1,11 @@
|
||||
.DS_Store
|
||||
node_modules
|
||||
/.svelte
|
||||
/build
|
||||
/functions
|
||||
.pnpm-store
|
||||
.pnpm-debug.log
|
||||
/.svelte-kit
|
||||
/package
|
||||
|
||||
.env
|
||||
.env.stag
|
||||
/db/*.db
|
||||
/db/*.db-journal
|
||||
/data/haproxy/haproxy.cfg
|
@ -1,35 +1,5 @@
|
||||
####################################
|
||||
# Domain where your Coolify instance will be available and reachable.
|
||||
# It's the same as you set in Github OAuth App and Github App as <domain>.
|
||||
DOMAIN=
|
||||
## Let's Encrypt contact email required
|
||||
EMAIL=
|
||||
|
||||
# JWT Token Sign Key for logging you in to Coolify's frontend
|
||||
JWT_SIGN_KEY=
|
||||
# Encryption key for SECRETS - do NOT share it with others!
|
||||
SECRETS_ENCRYPTION_KEY=
|
||||
|
||||
# Docker Engine
|
||||
DOCKER_ENGINE=/var/run/docker.sock
|
||||
# Docker network to use internally between the proxy and your apps
|
||||
DOCKER_NETWORK=coollabs
|
||||
|
||||
# Mongodb
|
||||
# Values in case if you are using our Mongodb installation - CHANGE user and password fields!
|
||||
MONGODB_HOST=coollabs-mongodb
|
||||
MONGODB_PORT=27017
|
||||
MONGODB_USER=supercooldbuser
|
||||
MONGODB_PASSWORD=developmentPassword4db
|
||||
MONGODB_DB=coolLabs-prod
|
||||
|
||||
# Frontend only variables
|
||||
VITE_GITHUB_APP_CLIENTID=
|
||||
VITE_GITHUB_APP_NAME=
|
||||
|
||||
# Github OAuth & App secrets and private key - you can get it from Github.
|
||||
GITHUB_APP_CLIENT_SECRET=
|
||||
GITHUP_APP_WEBHOOK_SECRET=
|
||||
|
||||
# It should look like this. Newlines breaks with \n
|
||||
GITHUB_APP_PRIVATE_KEY="-----BEGIN RSA PRIVATE KEY-----\nMIIEowIBAAKCAQEA7Y+Uwkd8FINSwFktWGdtwCaOAazTDYR8ucEzGyR9r+ooJZhF\nOc32qgDSps6Q5DsqPOzvfhiviqU+et9VF+bJhfdzwJ+Le86QZH1RgsDMoY049XvI\nKSwP........"
|
||||
COOLIFY_APP_ID=
|
||||
COOLIFY_SECRET_KEY=
|
||||
COOLIFY_DATABASE_URL=file:../db/prod.db
|
||||
COOLIFY_SENTRY_DSN=
|
||||
COOLIFY_IS_ON="docker"
|
@ -10,7 +10,7 @@ module.exports = {
|
||||
},
|
||||
parserOptions: {
|
||||
sourceType: 'module',
|
||||
ecmaVersion: 2019
|
||||
ecmaVersion: 2020
|
||||
},
|
||||
env: {
|
||||
browser: true,
|
||||
|
20
.gitignore
vendored
20
.gitignore
vendored
@ -1,9 +1,13 @@
|
||||
/node_modules
|
||||
/.svelte
|
||||
/.svelte-kit
|
||||
/.pnpm-store
|
||||
/build
|
||||
/functions
|
||||
.env
|
||||
.DS_Store
|
||||
.pnpm-debug.log
|
||||
node_modules
|
||||
/build
|
||||
/.svelte-kit
|
||||
/package
|
||||
|
||||
.env
|
||||
.env.prod
|
||||
.env.stag
|
||||
/db/*.db
|
||||
/db/*.db-journal
|
||||
/data/haproxy/haproxy.cfg
|
||||
/data/haproxy/haproxy.cfg.lkg
|
||||
|
1
.husky/_/.gitignore
vendored
Normal file
1
.husky/_/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
*
|
4
.husky/pre-commit
Executable file
4
.husky/pre-commit
Executable file
@ -0,0 +1,4 @@
|
||||
#!/bin/sh
|
||||
. "$(dirname "$0")/_/husky.sh"
|
||||
|
||||
yarn lint-staged
|
5
.lintstagedrc.json
Normal file
5
.lintstagedrc.json
Normal file
@ -0,0 +1,5 @@
|
||||
{
|
||||
"**/*.{js,jsx,ts,tsx,cjs,svelte,json,css,scss,md,yaml}": [
|
||||
"prettier --ignore-path .gitignore --write --plugin-search-dir=."
|
||||
]
|
||||
}
|
@ -1,5 +0,0 @@
|
||||
.svelte/**
|
||||
static/**
|
||||
build/**
|
||||
node_modules/**
|
||||
.svelte-kit/**
|
28
Dockerfile
Normal file
28
Dockerfile
Normal file
@ -0,0 +1,28 @@
|
||||
FROM node:16.14.0-alpine
|
||||
WORKDIR /app
|
||||
COPY package*.json .
|
||||
RUN yarn install
|
||||
COPY . .
|
||||
RUN yarn build
|
||||
|
||||
FROM node:16.14.0-alpine
|
||||
WORKDIR /app
|
||||
|
||||
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 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
|
||||
RUN mkdir -p ~/.docker/cli-plugins/
|
||||
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
|
||||
|
||||
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
|
||||
CMD ["pnpm", "start"]
|
661
LICENSE
661
LICENSE
@ -1,661 +0,0 @@
|
||||
GNU AFFERO GENERAL PUBLIC LICENSE
|
||||
Version 3, 19 November 2007
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
The GNU Affero General Public License is a free, copyleft license for
|
||||
software and other kinds of works, specifically designed to ensure
|
||||
cooperation with the community in the case of network server software.
|
||||
|
||||
The licenses for most software and other practical works are designed
|
||||
to take away your freedom to share and change the works. By contrast,
|
||||
our General Public Licenses are intended to guarantee your freedom to
|
||||
share and change all versions of a program--to make sure it remains free
|
||||
software for all its users.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
them if you wish), that you receive source code or can get it if you
|
||||
want it, that you can change the software or use pieces of it in new
|
||||
free programs, and that you know you can do these things.
|
||||
|
||||
Developers that use our General Public Licenses protect your rights
|
||||
with two steps: (1) assert copyright on the software, and (2) offer
|
||||
you this License which gives you legal permission to copy, distribute
|
||||
and/or modify the software.
|
||||
|
||||
A secondary benefit of defending all users' freedom is that
|
||||
improvements made in alternate versions of the program, if they
|
||||
receive widespread use, become available for other developers to
|
||||
incorporate. Many developers of free software are heartened and
|
||||
encouraged by the resulting cooperation. However, in the case of
|
||||
software used on network servers, this result may fail to come about.
|
||||
The GNU General Public License permits making a modified version and
|
||||
letting the public access it on a server without ever releasing its
|
||||
source code to the public.
|
||||
|
||||
The GNU Affero General Public License is designed specifically to
|
||||
ensure that, in such cases, the modified source code becomes available
|
||||
to the community. It requires the operator of a network server to
|
||||
provide the source code of the modified version running there to the
|
||||
users of that server. Therefore, public use of a modified version, on
|
||||
a publicly accessible server, gives the public access to the source
|
||||
code of the modified version.
|
||||
|
||||
An older license, called the Affero General Public License and
|
||||
published by Affero, was designed to accomplish similar goals. This is
|
||||
a different license, not a version of the Affero GPL, but Affero has
|
||||
released a new version of the Affero GPL which permits relicensing under
|
||||
this license.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
TERMS AND CONDITIONS
|
||||
|
||||
0. Definitions.
|
||||
|
||||
"This License" refers to version 3 of the GNU Affero General Public License.
|
||||
|
||||
"Copyright" also means copyright-like laws that apply to other kinds of
|
||||
works, such as semiconductor masks.
|
||||
|
||||
"The Program" refers to any copyrightable work licensed under this
|
||||
License. Each licensee is addressed as "you". "Licensees" and
|
||||
"recipients" may be individuals or organizations.
|
||||
|
||||
To "modify" a work means to copy from or adapt all or part of the work
|
||||
in a fashion requiring copyright permission, other than the making of an
|
||||
exact copy. The resulting work is called a "modified version" of the
|
||||
earlier work or a work "based on" the earlier work.
|
||||
|
||||
A "covered work" means either the unmodified Program or a work based
|
||||
on the Program.
|
||||
|
||||
To "propagate" a work means to do anything with it that, without
|
||||
permission, would make you directly or secondarily liable for
|
||||
infringement under applicable copyright law, except executing it on a
|
||||
computer or modifying a private copy. Propagation includes copying,
|
||||
distribution (with or without modification), making available to the
|
||||
public, and in some countries other activities as well.
|
||||
|
||||
To "convey" a work means any kind of propagation that enables other
|
||||
parties to make or receive copies. Mere interaction with a user through
|
||||
a computer network, with no transfer of a copy, is not conveying.
|
||||
|
||||
An interactive user interface displays "Appropriate Legal Notices"
|
||||
to the extent that it includes a convenient and prominently visible
|
||||
feature that (1) displays an appropriate copyright notice, and (2)
|
||||
tells the user that there is no warranty for the work (except to the
|
||||
extent that warranties are provided), that licensees may convey the
|
||||
work under this License, and how to view a copy of this License. If
|
||||
the interface presents a list of user commands or options, such as a
|
||||
menu, a prominent item in the list meets this criterion.
|
||||
|
||||
1. Source Code.
|
||||
|
||||
The "source code" for a work means the preferred form of the work
|
||||
for making modifications to it. "Object code" means any non-source
|
||||
form of a work.
|
||||
|
||||
A "Standard Interface" means an interface that either is an official
|
||||
standard defined by a recognized standards body, or, in the case of
|
||||
interfaces specified for a particular programming language, one that
|
||||
is widely used among developers working in that language.
|
||||
|
||||
The "System Libraries" of an executable work include anything, other
|
||||
than the work as a whole, that (a) is included in the normal form of
|
||||
packaging a Major Component, but which is not part of that Major
|
||||
Component, and (b) serves only to enable use of the work with that
|
||||
Major Component, or to implement a Standard Interface for which an
|
||||
implementation is available to the public in source code form. A
|
||||
"Major Component", in this context, means a major essential component
|
||||
(kernel, window system, and so on) of the specific operating system
|
||||
(if any) on which the executable work runs, or a compiler used to
|
||||
produce the work, or an object code interpreter used to run it.
|
||||
|
||||
The "Corresponding Source" for a work in object code form means all
|
||||
the source code needed to generate, install, and (for an executable
|
||||
work) run the object code and to modify the work, including scripts to
|
||||
control those activities. However, it does not include the work's
|
||||
System Libraries, or general-purpose tools or generally available free
|
||||
programs which are used unmodified in performing those activities but
|
||||
which are not part of the work. For example, Corresponding Source
|
||||
includes interface definition files associated with source files for
|
||||
the work, and the source code for shared libraries and dynamically
|
||||
linked subprograms that the work is specifically designed to require,
|
||||
such as by intimate data communication or control flow between those
|
||||
subprograms and other parts of the work.
|
||||
|
||||
The Corresponding Source need not include anything that users
|
||||
can regenerate automatically from other parts of the Corresponding
|
||||
Source.
|
||||
|
||||
The Corresponding Source for a work in source code form is that
|
||||
same work.
|
||||
|
||||
2. Basic Permissions.
|
||||
|
||||
All rights granted under this License are granted for the term of
|
||||
copyright on the Program, and are irrevocable provided the stated
|
||||
conditions are met. This License explicitly affirms your unlimited
|
||||
permission to run the unmodified Program. The output from running a
|
||||
covered work is covered by this License only if the output, given its
|
||||
content, constitutes a covered work. This License acknowledges your
|
||||
rights of fair use or other equivalent, as provided by copyright law.
|
||||
|
||||
You may make, run and propagate covered works that you do not
|
||||
convey, without conditions so long as your license otherwise remains
|
||||
in force. You may convey covered works to others for the sole purpose
|
||||
of having them make modifications exclusively for you, or provide you
|
||||
with facilities for running those works, provided that you comply with
|
||||
the terms of this License in conveying all material for which you do
|
||||
not control copyright. Those thus making or running the covered works
|
||||
for you must do so exclusively on your behalf, under your direction
|
||||
and control, on terms that prohibit them from making any copies of
|
||||
your copyrighted material outside their relationship with you.
|
||||
|
||||
Conveying under any other circumstances is permitted solely under
|
||||
the conditions stated below. Sublicensing is not allowed; section 10
|
||||
makes it unnecessary.
|
||||
|
||||
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
|
||||
|
||||
No covered work shall be deemed part of an effective technological
|
||||
measure under any applicable law fulfilling obligations under article
|
||||
11 of the WIPO copyright treaty adopted on 20 December 1996, or
|
||||
similar laws prohibiting or restricting circumvention of such
|
||||
measures.
|
||||
|
||||
When you convey a covered work, you waive any legal power to forbid
|
||||
circumvention of technological measures to the extent such circumvention
|
||||
is effected by exercising rights under this License with respect to
|
||||
the covered work, and you disclaim any intention to limit operation or
|
||||
modification of the work as a means of enforcing, against the work's
|
||||
users, your or third parties' legal rights to forbid circumvention of
|
||||
technological measures.
|
||||
|
||||
4. Conveying Verbatim Copies.
|
||||
|
||||
You may convey verbatim copies of the Program's source code as you
|
||||
receive it, in any medium, provided that you conspicuously and
|
||||
appropriately publish on each copy an appropriate copyright notice;
|
||||
keep intact all notices stating that this License and any
|
||||
non-permissive terms added in accord with section 7 apply to the code;
|
||||
keep intact all notices of the absence of any warranty; and give all
|
||||
recipients a copy of this License along with the Program.
|
||||
|
||||
You may charge any price or no price for each copy that you convey,
|
||||
and you may offer support or warranty protection for a fee.
|
||||
|
||||
5. Conveying Modified Source Versions.
|
||||
|
||||
You may convey a work based on the Program, or the modifications to
|
||||
produce it from the Program, in the form of source code under the
|
||||
terms of section 4, provided that you also meet all of these conditions:
|
||||
|
||||
a) The work must carry prominent notices stating that you modified
|
||||
it, and giving a relevant date.
|
||||
|
||||
b) The work must carry prominent notices stating that it is
|
||||
released under this License and any conditions added under section
|
||||
7. This requirement modifies the requirement in section 4 to
|
||||
"keep intact all notices".
|
||||
|
||||
c) You must license the entire work, as a whole, under this
|
||||
License to anyone who comes into possession of a copy. This
|
||||
License will therefore apply, along with any applicable section 7
|
||||
additional terms, to the whole of the work, and all its parts,
|
||||
regardless of how they are packaged. This License gives no
|
||||
permission to license the work in any other way, but it does not
|
||||
invalidate such permission if you have separately received it.
|
||||
|
||||
d) If the work has interactive user interfaces, each must display
|
||||
Appropriate Legal Notices; however, if the Program has interactive
|
||||
interfaces that do not display Appropriate Legal Notices, your
|
||||
work need not make them do so.
|
||||
|
||||
A compilation of a covered work with other separate and independent
|
||||
works, which are not by their nature extensions of the covered work,
|
||||
and which are not combined with it such as to form a larger program,
|
||||
in or on a volume of a storage or distribution medium, is called an
|
||||
"aggregate" if the compilation and its resulting copyright are not
|
||||
used to limit the access or legal rights of the compilation's users
|
||||
beyond what the individual works permit. Inclusion of a covered work
|
||||
in an aggregate does not cause this License to apply to the other
|
||||
parts of the aggregate.
|
||||
|
||||
6. Conveying Non-Source Forms.
|
||||
|
||||
You may convey a covered work in object code form under the terms
|
||||
of sections 4 and 5, provided that you also convey the
|
||||
machine-readable Corresponding Source under the terms of this License,
|
||||
in one of these ways:
|
||||
|
||||
a) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by the
|
||||
Corresponding Source fixed on a durable physical medium
|
||||
customarily used for software interchange.
|
||||
|
||||
b) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by a
|
||||
written offer, valid for at least three years and valid for as
|
||||
long as you offer spare parts or customer support for that product
|
||||
model, to give anyone who possesses the object code either (1) a
|
||||
copy of the Corresponding Source for all the software in the
|
||||
product that is covered by this License, on a durable physical
|
||||
medium customarily used for software interchange, for a price no
|
||||
more than your reasonable cost of physically performing this
|
||||
conveying of source, or (2) access to copy the
|
||||
Corresponding Source from a network server at no charge.
|
||||
|
||||
c) Convey individual copies of the object code with a copy of the
|
||||
written offer to provide the Corresponding Source. This
|
||||
alternative is allowed only occasionally and noncommercially, and
|
||||
only if you received the object code with such an offer, in accord
|
||||
with subsection 6b.
|
||||
|
||||
d) Convey the object code by offering access from a designated
|
||||
place (gratis or for a charge), and offer equivalent access to the
|
||||
Corresponding Source in the same way through the same place at no
|
||||
further charge. You need not require recipients to copy the
|
||||
Corresponding Source along with the object code. If the place to
|
||||
copy the object code is a network server, the Corresponding Source
|
||||
may be on a different server (operated by you or a third party)
|
||||
that supports equivalent copying facilities, provided you maintain
|
||||
clear directions next to the object code saying where to find the
|
||||
Corresponding Source. Regardless of what server hosts the
|
||||
Corresponding Source, you remain obligated to ensure that it is
|
||||
available for as long as needed to satisfy these requirements.
|
||||
|
||||
e) Convey the object code using peer-to-peer transmission, provided
|
||||
you inform other peers where the object code and Corresponding
|
||||
Source of the work are being offered to the general public at no
|
||||
charge under subsection 6d.
|
||||
|
||||
A separable portion of the object code, whose source code is excluded
|
||||
from the Corresponding Source as a System Library, need not be
|
||||
included in conveying the object code work.
|
||||
|
||||
A "User Product" is either (1) a "consumer product", which means any
|
||||
tangible personal property which is normally used for personal, family,
|
||||
or household purposes, or (2) anything designed or sold for incorporation
|
||||
into a dwelling. In determining whether a product is a consumer product,
|
||||
doubtful cases shall be resolved in favor of coverage. For a particular
|
||||
product received by a particular user, "normally used" refers to a
|
||||
typical or common use of that class of product, regardless of the status
|
||||
of the particular user or of the way in which the particular user
|
||||
actually uses, or expects or is expected to use, the product. A product
|
||||
is a consumer product regardless of whether the product has substantial
|
||||
commercial, industrial or non-consumer uses, unless such uses represent
|
||||
the only significant mode of use of the product.
|
||||
|
||||
"Installation Information" for a User Product means any methods,
|
||||
procedures, authorization keys, or other information required to install
|
||||
and execute modified versions of a covered work in that User Product from
|
||||
a modified version of its Corresponding Source. The information must
|
||||
suffice to ensure that the continued functioning of the modified object
|
||||
code is in no case prevented or interfered with solely because
|
||||
modification has been made.
|
||||
|
||||
If you convey an object code work under this section in, or with, or
|
||||
specifically for use in, a User Product, and the conveying occurs as
|
||||
part of a transaction in which the right of possession and use of the
|
||||
User Product is transferred to the recipient in perpetuity or for a
|
||||
fixed term (regardless of how the transaction is characterized), the
|
||||
Corresponding Source conveyed under this section must be accompanied
|
||||
by the Installation Information. But this requirement does not apply
|
||||
if neither you nor any third party retains the ability to install
|
||||
modified object code on the User Product (for example, the work has
|
||||
been installed in ROM).
|
||||
|
||||
The requirement to provide Installation Information does not include a
|
||||
requirement to continue to provide support service, warranty, or updates
|
||||
for a work that has been modified or installed by the recipient, or for
|
||||
the User Product in which it has been modified or installed. Access to a
|
||||
network may be denied when the modification itself materially and
|
||||
adversely affects the operation of the network or violates the rules and
|
||||
protocols for communication across the network.
|
||||
|
||||
Corresponding Source conveyed, and Installation Information provided,
|
||||
in accord with this section must be in a format that is publicly
|
||||
documented (and with an implementation available to the public in
|
||||
source code form), and must require no special password or key for
|
||||
unpacking, reading or copying.
|
||||
|
||||
7. Additional Terms.
|
||||
|
||||
"Additional permissions" are terms that supplement the terms of this
|
||||
License by making exceptions from one or more of its conditions.
|
||||
Additional permissions that are applicable to the entire Program shall
|
||||
be treated as though they were included in this License, to the extent
|
||||
that they are valid under applicable law. If additional permissions
|
||||
apply only to part of the Program, that part may be used separately
|
||||
under those permissions, but the entire Program remains governed by
|
||||
this License without regard to the additional permissions.
|
||||
|
||||
When you convey a copy of a covered work, you may at your option
|
||||
remove any additional permissions from that copy, or from any part of
|
||||
it. (Additional permissions may be written to require their own
|
||||
removal in certain cases when you modify the work.) You may place
|
||||
additional permissions on material, added by you to a covered work,
|
||||
for which you have or can give appropriate copyright permission.
|
||||
|
||||
Notwithstanding any other provision of this License, for material you
|
||||
add to a covered work, you may (if authorized by the copyright holders of
|
||||
that material) supplement the terms of this License with terms:
|
||||
|
||||
a) Disclaiming warranty or limiting liability differently from the
|
||||
terms of sections 15 and 16 of this License; or
|
||||
|
||||
b) Requiring preservation of specified reasonable legal notices or
|
||||
author attributions in that material or in the Appropriate Legal
|
||||
Notices displayed by works containing it; or
|
||||
|
||||
c) Prohibiting misrepresentation of the origin of that material, or
|
||||
requiring that modified versions of such material be marked in
|
||||
reasonable ways as different from the original version; or
|
||||
|
||||
d) Limiting the use for publicity purposes of names of licensors or
|
||||
authors of the material; or
|
||||
|
||||
e) Declining to grant rights under trademark law for use of some
|
||||
trade names, trademarks, or service marks; or
|
||||
|
||||
f) Requiring indemnification of licensors and authors of that
|
||||
material by anyone who conveys the material (or modified versions of
|
||||
it) with contractual assumptions of liability to the recipient, for
|
||||
any liability that these contractual assumptions directly impose on
|
||||
those licensors and authors.
|
||||
|
||||
All other non-permissive additional terms are considered "further
|
||||
restrictions" within the meaning of section 10. If the Program as you
|
||||
received it, or any part of it, contains a notice stating that it is
|
||||
governed by this License along with a term that is a further
|
||||
restriction, you may remove that term. If a license document contains
|
||||
a further restriction but permits relicensing or conveying under this
|
||||
License, you may add to a covered work material governed by the terms
|
||||
of that license document, provided that the further restriction does
|
||||
not survive such relicensing or conveying.
|
||||
|
||||
If you add terms to a covered work in accord with this section, you
|
||||
must place, in the relevant source files, a statement of the
|
||||
additional terms that apply to those files, or a notice indicating
|
||||
where to find the applicable terms.
|
||||
|
||||
Additional terms, permissive or non-permissive, may be stated in the
|
||||
form of a separately written license, or stated as exceptions;
|
||||
the above requirements apply either way.
|
||||
|
||||
8. Termination.
|
||||
|
||||
You may not propagate or modify a covered work except as expressly
|
||||
provided under this License. Any attempt otherwise to propagate or
|
||||
modify it is void, and will automatically terminate your rights under
|
||||
this License (including any patent licenses granted under the third
|
||||
paragraph of section 11).
|
||||
|
||||
However, if you cease all violation of this License, then your
|
||||
license from a particular copyright holder is reinstated (a)
|
||||
provisionally, unless and until the copyright holder explicitly and
|
||||
finally terminates your license, and (b) permanently, if the copyright
|
||||
holder fails to notify you of the violation by some reasonable means
|
||||
prior to 60 days after the cessation.
|
||||
|
||||
Moreover, your license from a particular copyright holder is
|
||||
reinstated permanently if the copyright holder notifies you of the
|
||||
violation by some reasonable means, this is the first time you have
|
||||
received notice of violation of this License (for any work) from that
|
||||
copyright holder, and you cure the violation prior to 30 days after
|
||||
your receipt of the notice.
|
||||
|
||||
Termination of your rights under this section does not terminate the
|
||||
licenses of parties who have received copies or rights from you under
|
||||
this License. If your rights have been terminated and not permanently
|
||||
reinstated, you do not qualify to receive new licenses for the same
|
||||
material under section 10.
|
||||
|
||||
9. Acceptance Not Required for Having Copies.
|
||||
|
||||
You are not required to accept this License in order to receive or
|
||||
run a copy of the Program. Ancillary propagation of a covered work
|
||||
occurring solely as a consequence of using peer-to-peer transmission
|
||||
to receive a copy likewise does not require acceptance. However,
|
||||
nothing other than this License grants you permission to propagate or
|
||||
modify any covered work. These actions infringe copyright if you do
|
||||
not accept this License. Therefore, by modifying or propagating a
|
||||
covered work, you indicate your acceptance of this License to do so.
|
||||
|
||||
10. Automatic Licensing of Downstream Recipients.
|
||||
|
||||
Each time you convey a covered work, the recipient automatically
|
||||
receives a license from the original licensors, to run, modify and
|
||||
propagate that work, subject to this License. You are not responsible
|
||||
for enforcing compliance by third parties with this License.
|
||||
|
||||
An "entity transaction" is a transaction transferring control of an
|
||||
organization, or substantially all assets of one, or subdividing an
|
||||
organization, or merging organizations. If propagation of a covered
|
||||
work results from an entity transaction, each party to that
|
||||
transaction who receives a copy of the work also receives whatever
|
||||
licenses to the work the party's predecessor in interest had or could
|
||||
give under the previous paragraph, plus a right to possession of the
|
||||
Corresponding Source of the work from the predecessor in interest, if
|
||||
the predecessor has it or can get it with reasonable efforts.
|
||||
|
||||
You may not impose any further restrictions on the exercise of the
|
||||
rights granted or affirmed under this License. For example, you may
|
||||
not impose a license fee, royalty, or other charge for exercise of
|
||||
rights granted under this License, and you may not initiate litigation
|
||||
(including a cross-claim or counterclaim in a lawsuit) alleging that
|
||||
any patent claim is infringed by making, using, selling, offering for
|
||||
sale, or importing the Program or any portion of it.
|
||||
|
||||
11. Patents.
|
||||
|
||||
A "contributor" is a copyright holder who authorizes use under this
|
||||
License of the Program or a work on which the Program is based. The
|
||||
work thus licensed is called the contributor's "contributor version".
|
||||
|
||||
A contributor's "essential patent claims" are all patent claims
|
||||
owned or controlled by the contributor, whether already acquired or
|
||||
hereafter acquired, that would be infringed by some manner, permitted
|
||||
by this License, of making, using, or selling its contributor version,
|
||||
but do not include claims that would be infringed only as a
|
||||
consequence of further modification of the contributor version. For
|
||||
purposes of this definition, "control" includes the right to grant
|
||||
patent sublicenses in a manner consistent with the requirements of
|
||||
this License.
|
||||
|
||||
Each contributor grants you a non-exclusive, worldwide, royalty-free
|
||||
patent license under the contributor's essential patent claims, to
|
||||
make, use, sell, offer for sale, import and otherwise run, modify and
|
||||
propagate the contents of its contributor version.
|
||||
|
||||
In the following three paragraphs, a "patent license" is any express
|
||||
agreement or commitment, however denominated, not to enforce a patent
|
||||
(such as an express permission to practice a patent or covenant not to
|
||||
sue for patent infringement). To "grant" such a patent license to a
|
||||
party means to make such an agreement or commitment not to enforce a
|
||||
patent against the party.
|
||||
|
||||
If you convey a covered work, knowingly relying on a patent license,
|
||||
and the Corresponding Source of the work is not available for anyone
|
||||
to copy, free of charge and under the terms of this License, through a
|
||||
publicly available network server or other readily accessible means,
|
||||
then you must either (1) cause the Corresponding Source to be so
|
||||
available, or (2) arrange to deprive yourself of the benefit of the
|
||||
patent license for this particular work, or (3) arrange, in a manner
|
||||
consistent with the requirements of this License, to extend the patent
|
||||
license to downstream recipients. "Knowingly relying" means you have
|
||||
actual knowledge that, but for the patent license, your conveying the
|
||||
covered work in a country, or your recipient's use of the covered work
|
||||
in a country, would infringe one or more identifiable patents in that
|
||||
country that you have reason to believe are valid.
|
||||
|
||||
If, pursuant to or in connection with a single transaction or
|
||||
arrangement, you convey, or propagate by procuring conveyance of, a
|
||||
covered work, and grant a patent license to some of the parties
|
||||
receiving the covered work authorizing them to use, propagate, modify
|
||||
or convey a specific copy of the covered work, then the patent license
|
||||
you grant is automatically extended to all recipients of the covered
|
||||
work and works based on it.
|
||||
|
||||
A patent license is "discriminatory" if it does not include within
|
||||
the scope of its coverage, prohibits the exercise of, or is
|
||||
conditioned on the non-exercise of one or more of the rights that are
|
||||
specifically granted under this License. You may not convey a covered
|
||||
work if you are a party to an arrangement with a third party that is
|
||||
in the business of distributing software, under which you make payment
|
||||
to the third party based on the extent of your activity of conveying
|
||||
the work, and under which the third party grants, to any of the
|
||||
parties who would receive the covered work from you, a discriminatory
|
||||
patent license (a) in connection with copies of the covered work
|
||||
conveyed by you (or copies made from those copies), or (b) primarily
|
||||
for and in connection with specific products or compilations that
|
||||
contain the covered work, unless you entered into that arrangement,
|
||||
or that patent license was granted, prior to 28 March 2007.
|
||||
|
||||
Nothing in this License shall be construed as excluding or limiting
|
||||
any implied license or other defenses to infringement that may
|
||||
otherwise be available to you under applicable patent law.
|
||||
|
||||
12. No Surrender of Others' Freedom.
|
||||
|
||||
If conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot convey a
|
||||
covered work so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you may
|
||||
not convey it at all. For example, if you agree to terms that obligate you
|
||||
to collect a royalty for further conveying from those to whom you convey
|
||||
the Program, the only way you could satisfy both those terms and this
|
||||
License would be to refrain entirely from conveying the Program.
|
||||
|
||||
13. Remote Network Interaction; Use with the GNU General Public License.
|
||||
|
||||
Notwithstanding any other provision of this License, if you modify the
|
||||
Program, your modified version must prominently offer all users
|
||||
interacting with it remotely through a computer network (if your version
|
||||
supports such interaction) an opportunity to receive the Corresponding
|
||||
Source of your version by providing access to the Corresponding Source
|
||||
from a network server at no charge, through some standard or customary
|
||||
means of facilitating copying of software. This Corresponding Source
|
||||
shall include the Corresponding Source for any work covered by version 3
|
||||
of the GNU General Public License that is incorporated pursuant to the
|
||||
following paragraph.
|
||||
|
||||
Notwithstanding any other provision of this License, you have
|
||||
permission to link or combine any covered work with a work licensed
|
||||
under version 3 of the GNU General Public License into a single
|
||||
combined work, and to convey the resulting work. The terms of this
|
||||
License will continue to apply to the part which is the covered work,
|
||||
but the work with which it is combined will remain governed by version
|
||||
3 of the GNU General Public License.
|
||||
|
||||
14. Revised Versions of this License.
|
||||
|
||||
The Free Software Foundation may publish revised and/or new versions of
|
||||
the GNU Affero General Public License from time to time. Such new versions
|
||||
will be similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the
|
||||
Program specifies that a certain numbered version of the GNU Affero General
|
||||
Public License "or any later version" applies to it, you have the
|
||||
option of following the terms and conditions either of that numbered
|
||||
version or of any later version published by the Free Software
|
||||
Foundation. If the Program does not specify a version number of the
|
||||
GNU Affero General Public License, you may choose any version ever published
|
||||
by the Free Software Foundation.
|
||||
|
||||
If the Program specifies that a proxy can decide which future
|
||||
versions of the GNU Affero General Public License can be used, that proxy's
|
||||
public statement of acceptance of a version permanently authorizes you
|
||||
to choose that version for the Program.
|
||||
|
||||
Later license versions may give you additional or different
|
||||
permissions. However, no additional obligations are imposed on any
|
||||
author or copyright holder as a result of your choosing to follow a
|
||||
later version.
|
||||
|
||||
15. Disclaimer of Warranty.
|
||||
|
||||
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
|
||||
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
|
||||
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
|
||||
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
|
||||
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
|
||||
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
|
||||
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||
|
||||
16. Limitation of Liability.
|
||||
|
||||
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
|
||||
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
|
||||
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
|
||||
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
|
||||
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
|
||||
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
|
||||
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
|
||||
SUCH DAMAGES.
|
||||
|
||||
17. Interpretation of Sections 15 and 16.
|
||||
|
||||
If the disclaimer of warranty and limitation of liability provided
|
||||
above cannot be given local legal effect according to their terms,
|
||||
reviewing courts shall apply local law that most closely approximates
|
||||
an absolute waiver of all civil liability in connection with the
|
||||
Program, unless a warranty or assumption of liability accompanies a
|
||||
copy of the Program in return for a fee.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Programs
|
||||
|
||||
If you develop a new program, and you want it to be of the greatest
|
||||
possible use to the public, the best way to achieve this is to make it
|
||||
free software which everyone can redistribute and change under these terms.
|
||||
|
||||
To do so, attach the following notices to the program. It is safest
|
||||
to attach them to the start of each source file to most effectively
|
||||
state the exclusion of warranty; and each file should have at least
|
||||
the "copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
<one line to give the program's name and a brief idea of what it does.>
|
||||
Copyright (C) <year> <name of author>
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published
|
||||
by the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If your software can interact with users remotely through a computer
|
||||
network, you should also make sure that it provides a way for users to
|
||||
get its source. For example, if your program is a web application, its
|
||||
interface could display a "Source" link that leads users to an archive
|
||||
of the code. There are many ways you could offer source, and different
|
||||
solutions will be better for different programs; see section 13 for the
|
||||
specific requirements.
|
||||
|
||||
You should also get your employer (if you work as a programmer) or school,
|
||||
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
||||
For more information on this, and how to apply and follow the GNU AGPL, see
|
||||
<https://www.gnu.org/licenses/>.
|
52
README.md
52
README.md
@ -2,57 +2,11 @@ # Coolify
|
||||
|
||||
An open-source, hassle-free, self-hostable Heroku & Netlify alternative.
|
||||
|
||||
## Demo
|
||||
## Status
|
||||
|
||||
[Small video](https://cdn.coollabs.io/assets/coolify/video/coolify.webm)
|
||||
This version is not usable yet. But it will be:
|
||||
|
||||
## Installation
|
||||
|
||||
Installation is automated with the following command:
|
||||
|
||||
```bash
|
||||
/bin/bash -c "$(curl -fsSL https://get.coollabs.io/coolify/install.sh)"
|
||||
```
|
||||
|
||||
## Features
|
||||
|
||||
- Deploy Pull Request automatically, so you can review them quickly!
|
||||
- You can deploy any of the following applications, databases and services easily.
|
||||
|
||||
(constantly growing lists)
|
||||
|
||||
### Applications
|
||||
|
||||
With Github integration
|
||||
|
||||
- Static sites
|
||||
- NodeJS
|
||||
- VueJS
|
||||
- NuxtJS
|
||||
- NextJS
|
||||
- React/Preact
|
||||
- NextJS
|
||||
- Gatsby
|
||||
- Svelte
|
||||
- PHP
|
||||
- Rust
|
||||
- or any custom dockerfile
|
||||
|
||||
### Databases
|
||||
|
||||
- MongoDB
|
||||
- MySQL
|
||||
- PostgreSQL
|
||||
- CouchDB
|
||||
- Redis
|
||||
|
||||
### Services
|
||||
|
||||
- [WordPress](https://wordpress.org)
|
||||
- [Plausible Analytics](https://plausible.io)
|
||||
- [NocoDB](https://nocodb.com)
|
||||
- [VSCode Server](https://github.com/cdr/code-server)
|
||||
- [MinIO](https://min.io)
|
||||
![](https://media.tenor.com/images/4554562afdb531e5fe62e301afd22ea2/tenor.gif)
|
||||
|
||||
## Support
|
||||
|
||||
|
11
data/docker/daemon.json
Normal file
11
data/docker/daemon.json
Normal file
@ -0,0 +1,11 @@
|
||||
{
|
||||
"log-driver": "json-file",
|
||||
"log-opts": {
|
||||
"max-size": "100m",
|
||||
"max-file": "5"
|
||||
},
|
||||
"features": {
|
||||
"buildkit": true
|
||||
},
|
||||
"live-restore": true
|
||||
}
|
29
data/haproxy/dataplaneapi.hcl
Normal file
29
data/haproxy/dataplaneapi.hcl
Normal file
@ -0,0 +1,29 @@
|
||||
config_version = 2
|
||||
name = "easy_gar"
|
||||
mode = "single"
|
||||
status = "null"
|
||||
|
||||
dataplaneapi {
|
||||
host = "0.0.0.0"
|
||||
port = 5555
|
||||
|
||||
transaction {
|
||||
transaction_dir = "/tmp/haproxy"
|
||||
}
|
||||
|
||||
advertised {
|
||||
api_address = ""
|
||||
api_port = 0
|
||||
}
|
||||
}
|
||||
|
||||
haproxy {
|
||||
config_file = "/usr/local/etc/haproxy/haproxy.cfg"
|
||||
haproxy_bin = "/usr/local/sbin/haproxy"
|
||||
|
||||
reload {
|
||||
reload_delay = 2
|
||||
reload_cmd = "kill -HUP 1"
|
||||
restart_cmd = "kill -SIGUSR2 1"
|
||||
}
|
||||
}
|
19
data/haproxy/haproxy.cfg-http.template
Normal file
19
data/haproxy/haproxy.cfg-http.template
Normal file
@ -0,0 +1,19 @@
|
||||
global
|
||||
log stdout format raw local0 debug
|
||||
|
||||
defaults
|
||||
mode http
|
||||
log global
|
||||
timeout http-request 60s
|
||||
timeout connect 10s
|
||||
timeout client 60s
|
||||
timeout server 60s
|
||||
|
||||
frontend "${APP}"
|
||||
mode http
|
||||
bind *:"${PORT}" name "${APP}"
|
||||
default_backend "${APP}"
|
||||
|
||||
backend "${APP}"
|
||||
mode http
|
||||
server "${APP}" "${APP}":"${PRIVATE_PORT}" check
|
15
data/haproxy/haproxy.cfg-tcp.template
Normal file
15
data/haproxy/haproxy.cfg-tcp.template
Normal file
@ -0,0 +1,15 @@
|
||||
global
|
||||
log stdout format raw local0 debug
|
||||
|
||||
defaults
|
||||
mode tcp
|
||||
log global
|
||||
|
||||
frontend "${APP}"
|
||||
mode tcp
|
||||
bind *:"${PORT}" name "${APP}"
|
||||
default_backend "${APP}"
|
||||
|
||||
backend "${APP}"
|
||||
mode tcp
|
||||
server "${APP}" "${APP}":"${PRIVATE_PORT}" check
|
38
data/haproxy/haproxy.cfg.template
Normal file
38
data/haproxy/haproxy.cfg.template
Normal file
@ -0,0 +1,38 @@
|
||||
global
|
||||
stats socket /var/run/api.sock user haproxy group haproxy mode 660 level admin expose-fd listeners
|
||||
log stdout format raw local0 debug
|
||||
|
||||
defaults
|
||||
mode http
|
||||
log global
|
||||
timeout http-request 60s
|
||||
timeout connect 10s
|
||||
timeout client 60s
|
||||
timeout server 60s
|
||||
|
||||
userlist haproxy-dataplaneapi
|
||||
user admin insecure-password "${HAPROXY_PASSWORD}"
|
||||
|
||||
frontend http
|
||||
mode http
|
||||
bind :80
|
||||
bind :443 ssl crt /usr/local/etc/haproxy/ssl/ alpn h2,http/1.1
|
||||
acl is_certbot path_beg /.well-known/acme-challenge/
|
||||
use_backend backend-certbot if is_certbot
|
||||
use_backend %[req.hdr(host),lower]
|
||||
|
||||
frontend stats
|
||||
bind *:8404
|
||||
stats enable
|
||||
stats uri /
|
||||
stats refresh 5s
|
||||
stats admin if TRUE
|
||||
stats auth "${HAPROXY_USERNAME}:${HAPROXY_PASSWORD}"
|
||||
|
||||
backend backend-certbot
|
||||
mode http
|
||||
server certbot host.docker.internal:9080
|
||||
|
||||
program api
|
||||
command /usr/bin/dataplaneapi -f /usr/local/etc/haproxy/dataplaneapi.hcl --userlist haproxy-dataplaneapi
|
||||
no option start-on-reload
|
81
data/haproxy/ssl/default.pem
Normal file
81
data/haproxy/ssl/default.pem
Normal file
@ -0,0 +1,81 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIFETCCAvkCFE/5JtU5geT5hOjFuQPiLgCYHwsOMA0GCSqGSIb3DQEBCwUAMEUx
|
||||
CzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRl
|
||||
cm5ldCBXaWRnaXRzIFB0eSBMdGQwHhcNMjExMDExMDkwNzQ1WhcNMzExMDA5MDkw
|
||||
NzQ1WjBFMQswCQYDVQQGEwJBVTETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UE
|
||||
CgwYSW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIICIjANBgkqhkiG9w0BAQEFAAOC
|
||||
Ag8AMIICCgKCAgEArEZDhvc3ew2Gb8pvJlUrh5x+L5iqNxDYU3cOcOgeELCmOyAS
|
||||
cH+/1xrsHQI05xWPpz6VAja2NKl4OP90getPPkiQV1xAg5/gsOsRL8Pi/MwvQKfZ
|
||||
ObyW3t+sfFb1K3sVnm8bgk5F9OIVyAtzAx+Y53muEJsHOHpaEidnwbY2VE0zQB/G
|
||||
DBQovrMefAwmH4RPqFor6NzFMKVRi33pQjYmcfCVFZylrDeCn8T7llV0lrnWqv6z
|
||||
sGKfL3E4nHvyh/RsGNOXy+XQMxB9SA3j6hFTNtgQIPO/lxptz/+BLZoUt48nHZtr
|
||||
sc5j+3sn8c1O9e6MjI/1q8lvZsk7ZsWCGSwCOvJ9LnxCWOEQUUfqIvGLsk7NJQgf
|
||||
IkodZH9sW5Sjlro21+WBf3nvqlZ8g7r6K1RJOA8AtUiCaN/+o65t86WkwCSwQXcm
|
||||
+nArcwddOx2HN9sFrjJ59N1eYEDGmyK3BdppYuVXay705PmxotR1hCBvnXOb34dn
|
||||
gZxsxFTohr97JvEdNtGSNz4USyZPjgIMF/Gu8ruh0gQ1byhmayRqMGEqMAh58Lvb
|
||||
3HYsd3Bf+LB9PpaXLAdKzsTZ8a28zyDYo8a70h7iBRxhmFwa+Df+pSmUEdzhejfx
|
||||
7jEslhBQSQDmllaHrHc1G6H/w/u+04vi1joaLeLEGQclinKLeU88s9j3zzUCAwEA
|
||||
ATANBgkqhkiG9w0BAQsFAAOCAgEAGQED96wBGzbMUlk9mIvZeLerzEAB3YfgfAYa
|
||||
EAi79QHxM8UX06xmA2xtGvJSvlU8Xods9vxpBmIUnbDRTIAHNDApT19+vPg/iSfQ
|
||||
1J9Fo4b5kjmWL6SalEdYcxqH9V/QndHta4MXP91u/ZsJ/exwDTZFatXsfGkPjUmN
|
||||
Xp+Ip6iQg7+kV3JpRnMSbevj2Oujs7qTAdQedH38ZTNS0AaM5gvZyQkccCTKNBQ4
|
||||
3O8MhCau7U0EUirndqsQXa0D3o78FpKztLNXSM7919jU2y36kMrWXfArfrBKHJ9b
|
||||
nZeO7nkbHgvmVS8NTg9pR7L7u+YXTa2p1H2ZnpMQvruV7iL/Pb1H2N68UdvnQScL
|
||||
sgacGSzM6b6PVdWRbECiuzC0UyWLZo/LoU3DQFGoiDQ4e/B3+TMrvgFI0CnpAQ4w
|
||||
qiaVFJlRQeF4GaS4qHsN28OBliFATB3TXONFnz1aVkQlEHuh2+JbuL1b1lxvlX5t
|
||||
gBbu/GgAcP4Uy2z4PoDmempAvNi2kCcLB98m+jbFSMSB3nkrdj6MzyN7kW9bhk3T
|
||||
ClimxDmc23seprwLcxJUPP5q+HRB1VLKXLwIYxu+Up3g29d4k1Iy9nUUP9lITLTk
|
||||
blJxZ2BPuQqTLzyqmAEWa1HxljFC1b7oMp9a98PbxC3MxUggM7zx/rgXWxM8osib
|
||||
uwSZmw0=
|
||||
-----END CERTIFICATE-----
|
||||
-----BEGIN RSA PRIVATE KEY-----
|
||||
MIIJKAIBAAKCAgEArEZDhvc3ew2Gb8pvJlUrh5x+L5iqNxDYU3cOcOgeELCmOyAS
|
||||
cH+/1xrsHQI05xWPpz6VAja2NKl4OP90getPPkiQV1xAg5/gsOsRL8Pi/MwvQKfZ
|
||||
ObyW3t+sfFb1K3sVnm8bgk5F9OIVyAtzAx+Y53muEJsHOHpaEidnwbY2VE0zQB/G
|
||||
DBQovrMefAwmH4RPqFor6NzFMKVRi33pQjYmcfCVFZylrDeCn8T7llV0lrnWqv6z
|
||||
sGKfL3E4nHvyh/RsGNOXy+XQMxB9SA3j6hFTNtgQIPO/lxptz/+BLZoUt48nHZtr
|
||||
sc5j+3sn8c1O9e6MjI/1q8lvZsk7ZsWCGSwCOvJ9LnxCWOEQUUfqIvGLsk7NJQgf
|
||||
IkodZH9sW5Sjlro21+WBf3nvqlZ8g7r6K1RJOA8AtUiCaN/+o65t86WkwCSwQXcm
|
||||
+nArcwddOx2HN9sFrjJ59N1eYEDGmyK3BdppYuVXay705PmxotR1hCBvnXOb34dn
|
||||
gZxsxFTohr97JvEdNtGSNz4USyZPjgIMF/Gu8ruh0gQ1byhmayRqMGEqMAh58Lvb
|
||||
3HYsd3Bf+LB9PpaXLAdKzsTZ8a28zyDYo8a70h7iBRxhmFwa+Df+pSmUEdzhejfx
|
||||
7jEslhBQSQDmllaHrHc1G6H/w/u+04vi1joaLeLEGQclinKLeU88s9j3zzUCAwEA
|
||||
AQKCAgEAm1/z33Jwk4crTQAjJ0uBqxm1pW/ndSq4MO8cEzEGjL8F7iWK+/P8LiGV
|
||||
+sPWuuRzX7/N3OVDiFOgnqeniNWV7vK7XE9T0GMN4ALiyVW/D4mIxKOeA7jXycOq
|
||||
aap0DPdCFFbZVLkL10Vhp77LyHFjEsJn/4oTBRk0y1LG/as9bOMD6j29/X7hEL20
|
||||
LOU4LQzEW26YU7lqD+nKlijFjHYSTolRrOBPe/fE1BxxXLFOKfMKbcaygc8xCzTu
|
||||
fhQ8Nep45BtSuQ9Yq/WfSLFecemWR8yvH0k37yxjBknHVD23maZ+/PEEPKWM/2+g
|
||||
IzGsmZrBILVmOb2/v9CWxqY0JEfQ6aU/nLW1ZiXSOIPmKEooK/hPVxFIyQ1yET4G
|
||||
kQZ5RroY/QDrI14ms8P0iDzZ8K3EFKUyjiBbc83Mb0YIZ4hKd76gioOUIPeEQB+y
|
||||
QLZ8Cb9YS3V8uIOJg+F4xlpJSePAZphfSxRLojSiKUeCs8gNUxGz0zwiMNf1p76F
|
||||
8CaLgvSwT/cgQjWitMeeE1Ha+8lY8VzESmd10gPk2uES/qdrmMhwFwovPqqrtMqj
|
||||
kMrFKNy7Crka6me3dhKEtryRTk5ho2IS/VCy/eXQ7lUW8Cl4uFxmjpHYSJMqDWvC
|
||||
vu1p3/B1psSZIy2V2M9QqwZCysHqvGJMOCvYmnc6T62+kDRKQ7ECggEBAOS/1ptA
|
||||
75OBAsHLovkspiCvn3gb/VTvH6LOvxYTohjr5iBeX137vg0aR1rg0jwcdf8EEJYw
|
||||
4YxOid7KmV7O25ujzduQgwpVgujnJAeBLeLDC5dVbq3PQah61AvR2O/7t+Ls3oxi
|
||||
cWh/OHC6SeZ/n406cxSCCUpVwtgHTaNFzaSmpDdEOSbjvXjQQjiRsG7j/1u64riq
|
||||
RlJ/hIUlcys0g94yeN/5lPaNfsq0+vTSAYuTVVXVbEntwWcZVZxnQJviZVgJ99zM
|
||||
RzE3sprvvr+I5QQ4FRMn0W9U7gblSJd5FGEL8gye4SRd+LxoUL4DR6pfuwd0vlXA
|
||||
g+dgiOKoHm2Bb8cCggEBAMDMHMNR6uipdMivPjBTlklnaYd9SY3c5x65yNtx4CNh
|
||||
rXyvy/6YvME7PPnKQZ8TQ4DkbVDUCAF7wnyAJJ7eWMav3bNlqWWjzaBvQz4Fn0XG
|
||||
/1W5R1CoJ9DW5FY3f9efJzQTmfn6dIlCx1gW7XfVBZQqI1LORMWUYenk0KAvjlg2
|
||||
UHYYl/BT1RhtYyzOJHto1PaUvCNDiOiDWAkTpLigYm7hGgVmcSwbo4F54SNUHdV7
|
||||
yz3CorCM4VsEYYSL80WHYxf/Zc+mcIDoWOdog0iEeK/Zu++yG5lPRxC1862GmsZA
|
||||
J06BMqX+NVGOfiGcVaGH+SZJXeFcrr7F8ZWp6y38QSMCggEAJwzo4hAv1gqMIfFV
|
||||
nRwWMDZLDwIYOUupJu4MiQRJA+AhpRz3QuAbDbmSvNzshv6E1kgnXLxzhLRTrQkB
|
||||
LcI6k1NfbUA6XqVCd+gdqnpPDwslC2y2PE3Jc62kTXBBjJZ4SfEN/QFBQwmU5Qmo
|
||||
XAUlg8KaqsGYPGxvmtmEU38zIAyitByddRoj2mATLf0RFZ0ulsZMtiG7Z5IFWYWP
|
||||
J60LZf9Py0ycNYrqPkivHuRLBzzbsI+CsQw5nBQjHVQzH2mCy4jIG5V0Ad70Sqbq
|
||||
9V+1WQcJ8f82Lb9d8ydpQRKWfArCA42L+d1g/SkBv65nqZo2H4u6goEfA3zjYW45
|
||||
44/ZOQKCAQB440MhwYqe6ioc76z51l+ElUAZQZjOR/XvUSS9XHDjHosOhJhPgmvQ
|
||||
aZl5MrXkzcpk1lYo+Vovu+8d66eKqfZWVs2XgCYwYf48G6e5CwNsWDOgB7XMwDN/
|
||||
Ak9YNCKIC/Yj9Cp3EPDjZCjkdjPeEIcX+Tf+4vFCRiEC7INX/ZmufBgFhLQ4cAhM
|
||||
8cHexT8g1oG6P1acces1h626u0NstLwjtCeBvVM3CfmC5O4jHco7Iw00I4epVhyz
|
||||
2lJfLvWR4itjT7QB+OXQHmAocWLoJJAcC1WJHU+q2IfB1aT+aElCB9XdpqsgY/4A
|
||||
rm0uG/2hdEXoGNaxyVCUtD8fzdR2GBarAoIBACCYXXREMYb6i1TbR5Q2LvVQUPsO
|
||||
Hgnbr+PLmx93rfUzDcr5r+cJgryjYQDKJTRleDJhg80M3RYOq+IOdl6yxOmRATmJ
|
||||
ZDgwRVD1F6VFxBJePcAW30FI5CoBogsHaZQDKGsopEaDRLK5E3QHUVG5qj323RdI
|
||||
Unf1++wI4nw+qwsVf1gSTcAdzq29v3NIWUyvvrmTNO4MxFTt0/lqkCsdT/2EFQDB
|
||||
/yQ1HCtQQjXE1xlYh0BnMZp9+4FmrlMC9Oj5H0dDSWmInPION0ft8/SjBj4TQ5Qi
|
||||
2DUo1WOWQnVR8Bxz0B8McXS+dOmgLe8ws4/ez7DoEVqHTgirKqBg5qRFQKw=
|
||||
-----END RSA PRIVATE KEY-----
|
0
db/.gitkeep
Normal file
0
db/.gitkeep
Normal file
20
docker-compose-dev.yaml
Normal file
20
docker-compose-dev.yaml
Normal file
@ -0,0 +1,20 @@
|
||||
version: '3.8'
|
||||
|
||||
services:
|
||||
redis:
|
||||
image: 'bitnami/redis:6.2'
|
||||
container_name: coolify-redis
|
||||
environment:
|
||||
- ALLOW_EMPTY_PASSWORD=yes
|
||||
networks:
|
||||
- coolify-infra
|
||||
ports:
|
||||
- target: 6379
|
||||
published: 6379
|
||||
protocol: tcp
|
||||
mode: host
|
||||
|
||||
networks:
|
||||
coolify-infra:
|
||||
attachable: true
|
||||
name: coolify-infra
|
@ -1,84 +0,0 @@
|
||||
version: '3.8'
|
||||
|
||||
services:
|
||||
proxy:
|
||||
image: traefik:v2.4
|
||||
hostname: coollabs-proxy
|
||||
ports:
|
||||
- target: 80
|
||||
published: 80
|
||||
protocol: tcp
|
||||
mode: host
|
||||
- target: 443
|
||||
published: 443
|
||||
protocol: tcp
|
||||
mode: host
|
||||
- target: 8080
|
||||
published: 8080
|
||||
protocol: tcp
|
||||
mode: host
|
||||
command:
|
||||
- --api.insecure=true
|
||||
- --api.dashboard=true
|
||||
- --api.debug=true
|
||||
- --log.level=ERROR
|
||||
- --providers.docker=true
|
||||
- --providers.docker.swarmMode=true
|
||||
- --providers.docker.exposedbydefault=false
|
||||
- --providers.docker.network=coollabs
|
||||
- --providers.docker.swarmModeRefreshSeconds=1s
|
||||
- --entrypoints.web.address=:80
|
||||
- --entrypoints.websecure.address=:443
|
||||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
networks:
|
||||
- coollabs
|
||||
deploy:
|
||||
update_config:
|
||||
parallelism: 1
|
||||
delay: 10s
|
||||
order: start-first
|
||||
replicas: 1
|
||||
placement:
|
||||
constraints:
|
||||
- node.role == manager
|
||||
labels:
|
||||
- 'traefik.enable=true'
|
||||
- 'traefik.http.routers.api.entrypoints=websecure'
|
||||
- 'traefik.http.routers.api.service=api@internal'
|
||||
- 'traefik.http.routers.api.middlewares=auth'
|
||||
- 'traefik.http.services.traefik.loadbalancer.server.port=80'
|
||||
|
||||
# Global redirect www to non-www
|
||||
- 'traefik.http.routers.www-catchall.rule=hostregexp(`{host:www.(.+)}`)'
|
||||
- 'traefik.http.routers.www-catchall.entrypoints=web'
|
||||
- 'traefik.http.routers.www-catchall.middlewares=redirect-www-to-nonwww'
|
||||
- "traefik.http.middlewares.redirect-www-to-nonwww.redirectregex.regex=^http://(?:www\\.)?(.+)"
|
||||
- 'traefik.http.middlewares.redirect-www-to-nonwww.redirectregex.replacement=http://$${1}'
|
||||
mongodb:
|
||||
image: bitnami/mongodb:4.4
|
||||
hostname: coollabs-mongodb
|
||||
ports:
|
||||
- target: 27017
|
||||
published: 27017
|
||||
protocol: tcp
|
||||
mode: host
|
||||
environment:
|
||||
- MONGODB_DISABLE_SYSTEM_LOG=true
|
||||
- MONGODB_ROOT_PASSWORD=developmentPassword4db
|
||||
- MONGODB_USERNAME=supercooldbuser
|
||||
- MONGODB_PASSWORD=developmentPassword4db
|
||||
- MONGODB_DATABASE=coolify
|
||||
volumes:
|
||||
- coollabs-mongodb-data:/bitnami/mongodb
|
||||
networks:
|
||||
- coollabs
|
||||
|
||||
volumes:
|
||||
coollabs-mongodb-data: {}
|
||||
|
||||
networks:
|
||||
coollabs:
|
||||
driver: overlay
|
||||
name: coollabs
|
||||
external: true
|
23
docker-compose-haproxy.yaml
Normal file
23
docker-compose-haproxy.yaml
Normal file
@ -0,0 +1,23 @@
|
||||
version: '3.8'
|
||||
|
||||
services:
|
||||
haproxy:
|
||||
image: coollabsio/coolify-haproxy-alpine:latest
|
||||
container_name: coolify-haproxy
|
||||
extra_hosts:
|
||||
- 'host.docker.internal:host-gateway'
|
||||
networks:
|
||||
- coolify
|
||||
volumes:
|
||||
- './data/haproxy/:/usr/local/etc/haproxy/'
|
||||
ports:
|
||||
- '80:80'
|
||||
- '443:443'
|
||||
- '8404:8404'
|
||||
- '5555:5555'
|
||||
- '3306:3306'
|
||||
|
||||
networks:
|
||||
coolify:
|
||||
attachable: true
|
||||
name: coolify
|
43
docker-compose.yaml
Normal file
43
docker-compose.yaml
Normal file
@ -0,0 +1,43 @@
|
||||
version: '3.8'
|
||||
|
||||
services:
|
||||
coolify:
|
||||
image: coollabsio/coolify:latest
|
||||
restart: always
|
||||
container_name: coolify
|
||||
ports:
|
||||
- target: 3000
|
||||
published: 3000
|
||||
protocol: tcp
|
||||
mode: host
|
||||
volumes:
|
||||
- 'coolify-db:/app/db'
|
||||
- 'coolify-ssl-certs:/app/ssl'
|
||||
- 'coolify-letsencrypt:/etc/letsencrypt'
|
||||
- '/var/run/docker.sock:/var/run/docker.sock'
|
||||
env_file:
|
||||
- '.env'
|
||||
networks:
|
||||
- coolify-infra
|
||||
depends_on: ['redis']
|
||||
redis:
|
||||
image: bitnami/redis:6.2
|
||||
restart: always
|
||||
container_name: coolify-redis
|
||||
environment:
|
||||
- ALLOW_EMPTY_PASSWORD=yes
|
||||
networks:
|
||||
- coolify-infra
|
||||
|
||||
networks:
|
||||
coolify-infra:
|
||||
attachable: true
|
||||
name: coolify-infra
|
||||
|
||||
volumes:
|
||||
coolify-db:
|
||||
name: coolify-db
|
||||
coolify-ssl-certs:
|
||||
name: coolify-ssl-certs
|
||||
coolify-letsencrypt:
|
||||
name: coolify-letsencrypt
|
6
haproxy-http.Dockerfile
Normal file
6
haproxy-http.Dockerfile
Normal file
@ -0,0 +1,6 @@
|
||||
FROM haproxytech/haproxy-alpine:2.5
|
||||
RUN mkdir -p /usr/local/etc/haproxy/ssl /usr/local/etc/haproxy/maps /usr/local/etc/haproxy/spoe
|
||||
|
||||
COPY data/haproxy/haproxy.cfg-http.template /usr/local/etc/haproxy/haproxy.cfg
|
||||
COPY data/haproxy/dataplaneapi.hcl /usr/local/etc/haproxy/dataplaneapi.hcl
|
||||
COPY data/haproxy/ssl/default.pem /usr/local/etc/haproxy/ssl/default.pem
|
6
haproxy-tcp.Dockerfile
Normal file
6
haproxy-tcp.Dockerfile
Normal file
@ -0,0 +1,6 @@
|
||||
FROM haproxytech/haproxy-alpine:2.5
|
||||
RUN mkdir -p /usr/local/etc/haproxy/ssl /usr/local/etc/haproxy/maps /usr/local/etc/haproxy/spoe
|
||||
|
||||
COPY data/haproxy/haproxy.cfg-tcp.template /usr/local/etc/haproxy/haproxy.cfg
|
||||
COPY data/haproxy/dataplaneapi.hcl /usr/local/etc/haproxy/dataplaneapi.hcl
|
||||
COPY data/haproxy/ssl/default.pem /usr/local/etc/haproxy/ssl/default.pem
|
6
haproxy.Dockerfile
Normal file
6
haproxy.Dockerfile
Normal file
@ -0,0 +1,6 @@
|
||||
FROM haproxytech/haproxy-alpine:2.5
|
||||
RUN mkdir -p /usr/local/etc/haproxy/ssl /usr/local/etc/haproxy/maps /usr/local/etc/haproxy/spoe
|
||||
|
||||
COPY data/haproxy/haproxy.cfg.template /usr/local/etc/haproxy/haproxy.cfg
|
||||
COPY data/haproxy/dataplaneapi.hcl /usr/local/etc/haproxy/dataplaneapi.hcl
|
||||
COPY data/haproxy/ssl/default.pem /usr/local/etc/haproxy/ssl/default.pem
|
88
install.sh
88
install.sh
@ -1,88 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
preTasks() {
|
||||
echo '
|
||||
##############################
|
||||
#### Pulling Git Updates #####
|
||||
##############################'
|
||||
GIT_SSH_COMMAND="ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no" git pull
|
||||
|
||||
if [ $? -ne 0 ]; then
|
||||
echo '
|
||||
####################################
|
||||
#### Ooops something not okay! #####
|
||||
####################################'
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo '
|
||||
##############################
|
||||
#### Building Base Image #####
|
||||
##############################'
|
||||
docker build --label coolify-reserve=true -t coolify-base -f install/Dockerfile-base .
|
||||
|
||||
if [ $? -ne 0 ]; then
|
||||
echo '
|
||||
####################################
|
||||
#### Ooops something not okay! #####
|
||||
####################################'
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo '
|
||||
##################################
|
||||
#### Checking configuration. #####
|
||||
##################################'
|
||||
docker run --rm -w /usr/src/app coolify-base node install/install.js --check
|
||||
if [ $? -ne 0 ]; then
|
||||
echo '
|
||||
##################################
|
||||
#### Missing configuration ! #####
|
||||
##################################'
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
case "$1" in
|
||||
"all")
|
||||
preTasks
|
||||
echo '
|
||||
#################################
|
||||
#### Rebuilding everything. #####
|
||||
#################################'
|
||||
docker run --rm -v /var/run/docker.sock:/var/run/docker.sock -v /data/coolify:/data/coolify -u root -w /usr/src/app coolify-base node install/install.js --type all
|
||||
;;
|
||||
"coolify")
|
||||
preTasks
|
||||
echo '
|
||||
##############################
|
||||
#### Rebuilding Coolify. #####
|
||||
##############################'
|
||||
docker run --rm -v /var/run/docker.sock:/var/run/docker.sock -v /data/coolify:/data/coolify -u root -w /usr/src/app coolify-base node install/install.js --type coolify
|
||||
;;
|
||||
"proxy")
|
||||
preTasks
|
||||
echo '
|
||||
############################
|
||||
#### Rebuilding Proxy. #####
|
||||
############################'
|
||||
docker run --rm -v /var/run/docker.sock:/var/run/docker.sock -v /data/coolify:/data/coolify -u root -w /usr/src/app coolify-base node install/install.js --type proxy
|
||||
;;
|
||||
"upgrade-phase-1")
|
||||
preTasks
|
||||
echo '
|
||||
################################
|
||||
#### Upgrading Coolify P1. #####
|
||||
################################'
|
||||
docker run --rm -v /var/run/docker.sock:/var/run/docker.sock -v /data/coolify:/data/coolify -u root -w /usr/src/app coolify-base node install/install.js --type upgrade
|
||||
;;
|
||||
"upgrade-phase-2")
|
||||
echo '
|
||||
################################
|
||||
#### Upgrading Coolify P2. #####
|
||||
################################'
|
||||
docker run --rm -v /var/run/docker.sock:/var/run/docker.sock -v /data/coolify:/data/coolify -u root -w /usr/src/app coolify-base node install/update.js --type upgrade
|
||||
;;
|
||||
*)
|
||||
exit 1
|
||||
;;
|
||||
esac
|
@ -1,5 +0,0 @@
|
||||
FROM coolify-base
|
||||
WORKDIR /usr/src/app
|
||||
RUN pnpm build
|
||||
CMD ["pnpm", "start"]
|
||||
EXPOSE 3000
|
@ -1,19 +0,0 @@
|
||||
FROM ubuntu:20.04 as binaries
|
||||
RUN apt update && apt install -y curl gnupg2 ca-certificates
|
||||
RUN curl -fsSL https://download.docker.com/linux/ubuntu/gpg | apt-key add -
|
||||
RUN echo 'deb [arch=amd64] https://download.docker.com/linux/ubuntu focal stable' >> /etc/apt/sources.list
|
||||
RUN curl -L https://github.com/a8m/envsubst/releases/download/v1.2.0/envsubst-`uname -s`-`uname -m` -o /usr/bin/envsubst
|
||||
RUN chmod +x /usr/bin/envsubst
|
||||
RUN apt update && apt install -y docker-ce-cli && apt clean all
|
||||
|
||||
FROM node:14 as modules
|
||||
COPY --from=binaries /usr/bin/docker /usr/bin/docker
|
||||
COPY --from=binaries /usr/bin/envsubst /usr/bin/envsubst
|
||||
RUN curl -L https://pnpm.js.org/pnpm.js | node - add --global pnpm
|
||||
WORKDIR /usr/src/app
|
||||
COPY ./package*.json .
|
||||
RUN pnpm install
|
||||
|
||||
FROM modules
|
||||
WORKDIR /usr/src/app
|
||||
COPY . .
|
@ -1,10 +0,0 @@
|
||||
FROM node:latest
|
||||
LABEL coolify-preserve=true
|
||||
WORKDIR /usr/src/app
|
||||
RUN curl -fsSL https://download.docker.com/linux/static/stable/x86_64/docker-20.10.6.tgz | tar -xzvf - docker/docker -C . --strip-components 1
|
||||
RUN mv /usr/src/app/docker /usr/bin/docker
|
||||
RUN curl -L https://github.com/a8m/envsubst/releases/download/v1.2.0/envsubst-`uname -s`-`uname -m` -o /usr/bin/envsubst
|
||||
RUN curl -L https://github.com/stedolan/jq/releases/download/jq-1.6/jq-linux64 -o /usr/bin/jq
|
||||
RUN chmod +x /usr/bin/envsubst /usr/bin/jq /usr/bin/docker
|
||||
ADD "https://www.random.org/cgi-bin/randbyte?nbytes=10&format=h" skipcache
|
||||
RUN curl -f https://get.pnpm.io/v6.js | node - add --global pnpm
|
@ -1,15 +0,0 @@
|
||||
FROM node:lts
|
||||
LABEL coolify-preserve=true
|
||||
WORKDIR /usr/src/app
|
||||
RUN curl -fsSL https://download.docker.com/linux/static/stable/x86_64/docker-20.10.6.tgz | tar -xzvf - docker/docker -C . --strip-components 1
|
||||
RUN mv /usr/src/app/docker /usr/bin/docker
|
||||
RUN curl -L https://github.com/a8m/envsubst/releases/download/v1.2.0/envsubst-`uname -s`-`uname -m` -o /usr/bin/envsubst
|
||||
RUN curl -L https://github.com/stedolan/jq/releases/download/jq-1.6/jq-linux64 -o /usr/bin/jq
|
||||
RUN chmod +x /usr/bin/envsubst /usr/bin/jq /usr/bin/docker
|
||||
RUN curl -f https://get.pnpm.io/v6.js | node - add --global pnpm
|
||||
COPY ./*package.json .
|
||||
RUN pnpm install
|
||||
COPY . .
|
||||
RUN pnpm build
|
||||
CMD ["pnpm", "start"]
|
||||
EXPOSE 3000
|
@ -1,10 +0,0 @@
|
||||
Some of the files are here for backwards compatibility.
|
||||
|
||||
I will do things after 2 months:
|
||||
|
||||
- rm ./install.js and ./update.js
|
||||
- rm ../install.sh
|
||||
- rm ./Dockerfile-base
|
||||
- rm ./obs
|
||||
- rm ./check.js "No need to check env file. During installation, it is checked by the installer. If you change it between to upgrades: 🤷♂️"
|
||||
- Rename Dockerfile-new to Dockerfile
|
@ -1,97 +0,0 @@
|
||||
version: '3.8'
|
||||
|
||||
services:
|
||||
proxy:
|
||||
image: traefik:v2.4
|
||||
hostname: coollabs-proxy
|
||||
ports:
|
||||
- target: 80
|
||||
published: 80
|
||||
protocol: tcp
|
||||
mode: host
|
||||
- target: 443
|
||||
published: 443
|
||||
protocol: tcp
|
||||
mode: host
|
||||
command:
|
||||
- --api.insecure=false
|
||||
- --api.dashboard=false
|
||||
- --api.debug=false
|
||||
- --log.level=ERROR
|
||||
- --providers.docker=true
|
||||
- --providers.docker.swarmMode=true
|
||||
- --providers.docker.exposedbydefault=false
|
||||
- --providers.docker.network=${DOCKER_NETWORK}
|
||||
- --providers.docker.swarmModeRefreshSeconds=1s
|
||||
- --entrypoints.web.address=:80
|
||||
- --entrypoints.websecure.address=:443
|
||||
- --certificatesresolvers.letsencrypt.acme.httpchallenge=true
|
||||
- --certificatesresolvers.letsencrypt.acme.httpchallenge.entrypoint=web
|
||||
- --certificatesresolvers.letsencrypt.acme.email=${EMAIL}
|
||||
- --certificatesresolvers.letsencrypt.acme.storage=/data/coolify/acme.json
|
||||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
- /data/coolify:/data/coolify
|
||||
networks:
|
||||
- ${DOCKER_NETWORK}
|
||||
deploy:
|
||||
update_config:
|
||||
parallelism: 1
|
||||
delay: 10s
|
||||
order: start-first
|
||||
replicas: 1
|
||||
placement:
|
||||
constraints:
|
||||
- node.role == manager
|
||||
labels:
|
||||
- 'traefik.enable=true'
|
||||
- 'traefik.http.routers.api.entrypoints=websecure'
|
||||
- 'traefik.http.routers.api.service=api@internal'
|
||||
- 'traefik.http.routers.api.middlewares=auth'
|
||||
- 'traefik.http.services.traefik.loadbalancer.server.port=80'
|
||||
- 'traefik.http.services.traefik.loadbalancer.server.port=443'
|
||||
|
||||
# Global redirect www to non-www
|
||||
- 'traefik.http.routers.www-catchall.rule=hostregexp(`{host:www.(.+)}`)'
|
||||
- 'traefik.http.routers.www-catchall.entrypoints=web'
|
||||
- 'traefik.http.routers.www-catchall.middlewares=redirect-www-to-nonwww'
|
||||
- "traefik.http.middlewares.redirect-www-to-nonwww.redirectregex.regex=^http://(?:www\\.)?(.+)"
|
||||
- 'traefik.http.middlewares.redirect-www-to-nonwww.redirectregex.replacement=http://$$$${1}'
|
||||
|
||||
# Global redirect http to https
|
||||
- 'traefik.http.routers.http-catchall.rule=hostregexp(`{host:.+}`)'
|
||||
- 'traefik.http.routers.http-catchall.entrypoints=web'
|
||||
- 'traefik.http.routers.http-catchall.middlewares=redirect-to-https'
|
||||
|
||||
- 'traefik.http.middlewares.redirect-to-https.redirectscheme.scheme=https'
|
||||
- 'traefik.http.middlewares.global-compress.compress=true'
|
||||
|
||||
coolify:
|
||||
image: coolify
|
||||
hostname: coollabs-coolify
|
||||
env_file:
|
||||
- .env
|
||||
networks:
|
||||
- ${DOCKER_NETWORK}
|
||||
command: 'yarn start'
|
||||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock:ro
|
||||
deploy:
|
||||
update_config:
|
||||
parallelism: 1
|
||||
delay: 10s
|
||||
order: start-first
|
||||
replicas: 1
|
||||
labels:
|
||||
- 'traefik.enable=true'
|
||||
- 'traefik.http.routers.coolify.entrypoints=websecure'
|
||||
- 'traefik.http.routers.coolify.tls.certresolver=letsencrypt'
|
||||
- 'traefik.http.routers.coolify.rule=Host(`${DOMAIN}`) && PathPrefix(`/`)'
|
||||
- 'traefik.http.services.coolify.loadbalancer.server.port=3000'
|
||||
- 'traefik.http.routers.coolify.middlewares=global-compress'
|
||||
|
||||
networks:
|
||||
${DOCKER_NETWORK}:
|
||||
driver: overlay
|
||||
name: ${DOCKER_NETWORK}
|
||||
external: true
|
@ -1,36 +0,0 @@
|
||||
require('dotenv').config();
|
||||
const { program } = require('commander');
|
||||
const shell = require('shelljs');
|
||||
const user = shell.exec('whoami', { silent: true }).stdout.replace('\n', '');
|
||||
|
||||
program.version('0.0.1');
|
||||
program
|
||||
.option('-d, --debug', 'Debug outputs.')
|
||||
.option('-c, --check', 'Only checks configuration.')
|
||||
.option('-t, --type <type>', 'Deploy type.');
|
||||
|
||||
program.parse(process.argv);
|
||||
|
||||
const options = program.opts();
|
||||
|
||||
if (user !== 'root') {
|
||||
console.error(`Please run as root! Current user: ${user}`);
|
||||
process.exit(1);
|
||||
}
|
||||
shell.exec(`docker network create ${process.env.DOCKER_NETWORK} --driver overlay`, {
|
||||
silent: !options.debug
|
||||
});
|
||||
shell.exec('docker build -t coolify -f install/Dockerfile .');
|
||||
if (options.type === 'all') {
|
||||
shell.exec('docker stack rm coollabs-coolify', { silent: !options.debug });
|
||||
} else if (options.type === 'coolify') {
|
||||
shell.exec('docker service rm coollabs-coolify_coolify');
|
||||
} else if (options.type === 'proxy') {
|
||||
shell.exec('docker service rm coollabs-coolify_proxy');
|
||||
}
|
||||
if (options.type !== 'upgrade') {
|
||||
shell.exec(
|
||||
'set -a && source .env && set +a && envsubst < install/coolify-template.yml | docker stack deploy -c - coollabs-coolify',
|
||||
{ silent: !options.debug, shell: '/bin/bash' }
|
||||
);
|
||||
}
|
@ -1,24 +0,0 @@
|
||||
require('dotenv').config();
|
||||
const { program } = require('commander');
|
||||
const shell = require('shelljs');
|
||||
const user = shell.exec('whoami', { silent: true }).stdout.replace('\n', '');
|
||||
program.version('0.0.1');
|
||||
program
|
||||
.option('-d, --debug', 'Debug outputs.')
|
||||
.option('-c, --check', 'Only checks configuration.')
|
||||
.option('-t, --type <type>', 'Deploy type.');
|
||||
|
||||
program.parse(process.argv);
|
||||
const options = program.opts();
|
||||
if (user !== 'root') {
|
||||
console.error(`Please run as root! Current user: ${user}`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
if (options.type === 'upgrade') {
|
||||
shell.exec('docker service rm coollabs-coolify_coolify');
|
||||
shell.exec(
|
||||
'set -a && source .env && set +a && envsubst < install/coolify-template.yml | docker stack deploy -c - coollabs-coolify',
|
||||
{ silent: !options.debug, shell: '/bin/bash' }
|
||||
);
|
||||
}
|
120
package.json
120
package.json
@ -1,61 +1,83 @@
|
||||
{
|
||||
"name": "coolify",
|
||||
"description": "An open-source, hassle-free, self-hostable Heroku & Netlify alternative.",
|
||||
"version": "1.0.24",
|
||||
"description": "An open-source & self-hostable Heroku / Netlify alternative.",
|
||||
"version": "2.0.0",
|
||||
"license": "AGPL-3.0",
|
||||
"scripts": {
|
||||
"dev:docker:start": "docker-compose -f docker-compose-dev.yml up -d",
|
||||
"dev:docker:stop": "docker-compose -f docker-compose-dev.yml down",
|
||||
"dev": "TAILWIND_MODE=watch NODE_ENV=development svelte-kit dev --host 0.0.0.0",
|
||||
"build": "NODE_ENV=production svelte-kit build",
|
||||
"dev": "docker-compose -f docker-compose-dev.yaml up -d && NODE_ENV=development svelte-kit dev --host 0.0.0.0",
|
||||
"dev:stop": "docker-compose -f docker-compose-dev.yaml down",
|
||||
"dev:logs": "docker-compose -f docker-compose-dev.yaml logs -f --tail 10",
|
||||
"studio": "npx prisma studio",
|
||||
"start": "npx prisma migrate deploy && npx prisma generate && npx prisma db seed && node index.js",
|
||||
"build": "svelte-kit build",
|
||||
"preview": "svelte-kit preview",
|
||||
"start": "node build",
|
||||
"lint": "prettier --check . && eslint --ignore-path .gitignore .",
|
||||
"format": "prettier --write ."
|
||||
"check": "svelte-check --tsconfig ./tsconfig.json",
|
||||
"check:watch": "svelte-check --tsconfig ./tsconfig.json --watch",
|
||||
"db:generate": "prisma generate",
|
||||
"db:push": "prisma db push && prisma generate",
|
||||
"db:seed": "prisma db seed",
|
||||
"prerelease": "cross-var docker build -t coollabsio/coolify:$npm_package_version -t coollabsio/coolify:latest .",
|
||||
"release:coolify": "cross-var yarn prerelease && docker push coollabsio/coolify:$npm_package_version && docker image push coollabsio/coolify:$npm_package_version && docker push coollabsio/coolify:latest",
|
||||
"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: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: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",
|
||||
"prepare": "husky install"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@sveltejs/adapter-node": "1.0.0-next.39",
|
||||
"@sveltejs/kit": "1.0.0-next.142",
|
||||
"@types/dockerode": "^3.2.3",
|
||||
"@typescript-eslint/eslint-plugin": "^4.26.1",
|
||||
"@typescript-eslint/parser": "^4.26.1",
|
||||
"autoprefixer": "^10.2.6",
|
||||
"cssnano": "^5.0.5",
|
||||
"eslint": "^7.28.0",
|
||||
"eslint-config-prettier": "^8.3.0",
|
||||
"eslint-plugin-svelte3": "^3.2.0",
|
||||
"postcss": "^8.3.0",
|
||||
"postcss-load-config": "^3.0.1",
|
||||
"prettier": "~2.3.1",
|
||||
"prettier-plugin-svelte": "^2.3.0",
|
||||
"svelte": "^3.38.2",
|
||||
"svelte-preprocess": "^4.7.3",
|
||||
"tailwindcss": "2.2.4",
|
||||
"tslib": "^2.2.0",
|
||||
"typescript": "^4.3.2",
|
||||
"vite": "^2.3.6"
|
||||
"@sveltejs/adapter-node": "1.0.0-next.67",
|
||||
"@sveltejs/adapter-static": "1.0.0-next.27",
|
||||
"@sveltejs/kit": "1.0.0-next.259",
|
||||
"@types/bcrypt": "5.0.0",
|
||||
"@types/js-cookie": "3.0.1",
|
||||
"@types/node": "17.0.16",
|
||||
"@types/node-forge": "1.0.0",
|
||||
"@typescript-eslint/eslint-plugin": "4.31.1",
|
||||
"@typescript-eslint/parser": "4.31.1",
|
||||
"@zerodevx/svelte-toast": "0.6.3",
|
||||
"autoprefixer": "10.4.2",
|
||||
"cross-var": "1.1.0",
|
||||
"eslint": "7.32.0",
|
||||
"eslint-config-prettier": "8.3.0",
|
||||
"eslint-plugin-svelte3": "3.2.1",
|
||||
"husky": "7.0.4",
|
||||
"lint-staged": "12.3.3",
|
||||
"postcss": "8.4.6",
|
||||
"prettier": "2.5.1",
|
||||
"prettier-plugin-svelte": "2.6.0",
|
||||
"prettier-plugin-tailwindcss": "0.1.7",
|
||||
"prisma": "3.9.1",
|
||||
"svelte": "3.46.4",
|
||||
"svelte-check": "2.4.3",
|
||||
"svelte-preprocess": "4.10.3",
|
||||
"tailwindcss": "3.0.19",
|
||||
"ts-node": "10.5.0",
|
||||
"tslib": "2.3.1",
|
||||
"typescript": "4.5.5"
|
||||
},
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
"@iarna/toml": "^2.2.5",
|
||||
"@zerodevx/svelte-toast": "^0.3.0",
|
||||
"bcrypt": "^5.0.1",
|
||||
"commander": "^7.2.0",
|
||||
"compare-versions": "^3.6.0",
|
||||
"cookie": "^0.4.1",
|
||||
"cuid": "^2.1.8",
|
||||
"dayjs": "^1.10.5",
|
||||
"dockerode": "^3.3.0",
|
||||
"dotenv-extended": "^2.9.0",
|
||||
"generate-password": "^1.6.0",
|
||||
"js-yaml": "^4.1.0",
|
||||
"jsonwebtoken": "^8.5.1",
|
||||
"microtip": "^0.2.2",
|
||||
"mongoose": "^5.12.13",
|
||||
"shelljs": "^0.8.4",
|
||||
"svelte-kit-cookie-session": "^1.3.1",
|
||||
"svelte-select": "^3.17.0",
|
||||
"systeminformation": "^5.7.7",
|
||||
"unique-names-generator": "^4.5.0"
|
||||
"@iarna/toml": "2.2.5",
|
||||
"@prisma/client": "3.9.1",
|
||||
"@sentry/node": "6.17.6",
|
||||
"bcrypt": "5.0.1",
|
||||
"bullmq": "1.69.0",
|
||||
"compare-versions": "4.1.3",
|
||||
"cookie": "0.4.2",
|
||||
"cuid": "2.1.8",
|
||||
"dayjs": "1.10.7",
|
||||
"dockerode": "3.3.1",
|
||||
"dotenv-extended": "2.9.0",
|
||||
"generate-password": "1.7.0",
|
||||
"get-port": "6.0.0",
|
||||
"got": "12.0.1",
|
||||
"js-cookie": "3.0.1",
|
||||
"js-yaml": "4.1.0",
|
||||
"jsonwebtoken": "8.5.1",
|
||||
"node-forge": "1.2.1",
|
||||
"svelte-kit-cookie-session": "2.0.3",
|
||||
"unique-names-generator": "4.6.0"
|
||||
},
|
||||
"prisma": {
|
||||
"seed": "node prisma/seed.cjs"
|
||||
}
|
||||
}
|
||||
|
6043
pnpm-lock.yaml
6043
pnpm-lock.yaml
File diff suppressed because it is too large
Load Diff
@ -1,18 +1,6 @@
|
||||
const tailwindcss = require('tailwindcss');
|
||||
const autoprefixer = require('autoprefixer');
|
||||
const cssnano = require('cssnano');
|
||||
|
||||
const mode = process.env.NODE_ENV;
|
||||
const dev = mode === 'development';
|
||||
|
||||
module.exports = {
|
||||
plugins: [
|
||||
tailwindcss,
|
||||
autoprefixer,
|
||||
|
||||
!dev &&
|
||||
cssnano({
|
||||
preset: 'default'
|
||||
})
|
||||
]
|
||||
plugins: {
|
||||
tailwindcss: {},
|
||||
autoprefixer: {}
|
||||
}
|
||||
};
|
||||
|
443
prisma/migrations/20220131142425_init/migration.sql
Normal file
443
prisma/migrations/20220131142425_init/migration.sql
Normal file
@ -0,0 +1,443 @@
|
||||
-- CreateTable
|
||||
CREATE TABLE "Setting" (
|
||||
"id" TEXT NOT NULL PRIMARY KEY,
|
||||
"fqdn" TEXT,
|
||||
"isRegistrationEnabled" BOOLEAN NOT NULL DEFAULT false,
|
||||
"proxyPassword" TEXT NOT NULL,
|
||||
"proxyUser" TEXT NOT NULL,
|
||||
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" DATETIME NOT NULL
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "User" (
|
||||
"id" TEXT NOT NULL PRIMARY KEY,
|
||||
"email" TEXT NOT NULL,
|
||||
"type" TEXT NOT NULL,
|
||||
"password" TEXT,
|
||||
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" DATETIME NOT NULL
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "Permission" (
|
||||
"id" TEXT NOT NULL PRIMARY KEY,
|
||||
"userId" TEXT NOT NULL,
|
||||
"teamId" TEXT NOT NULL,
|
||||
"permission" TEXT NOT NULL,
|
||||
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" DATETIME NOT NULL,
|
||||
CONSTRAINT "Permission_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User" ("id") ON DELETE RESTRICT ON UPDATE CASCADE,
|
||||
CONSTRAINT "Permission_teamId_fkey" FOREIGN KEY ("teamId") REFERENCES "Team" ("id") ON DELETE RESTRICT ON UPDATE CASCADE
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "Team" (
|
||||
"id" TEXT NOT NULL PRIMARY KEY,
|
||||
"name" TEXT,
|
||||
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" DATETIME NOT NULL,
|
||||
"databaseId" TEXT,
|
||||
"serviceId" TEXT,
|
||||
FOREIGN KEY ("databaseId") REFERENCES "Database" ("id") ON DELETE SET NULL ON UPDATE CASCADE,
|
||||
FOREIGN KEY ("serviceId") REFERENCES "Service" ("id") ON DELETE SET NULL ON UPDATE CASCADE
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "TeamInvitation" (
|
||||
"id" TEXT NOT NULL PRIMARY KEY,
|
||||
"uid" TEXT NOT NULL,
|
||||
"email" TEXT NOT NULL,
|
||||
"teamId" TEXT NOT NULL,
|
||||
"teamName" TEXT NOT NULL,
|
||||
"permission" TEXT NOT NULL,
|
||||
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "Application" (
|
||||
"id" TEXT NOT NULL PRIMARY KEY,
|
||||
"name" TEXT NOT NULL,
|
||||
"fqdn" TEXT,
|
||||
"repository" TEXT,
|
||||
"configHash" TEXT,
|
||||
"branch" TEXT,
|
||||
"buildPack" TEXT,
|
||||
"projectId" INTEGER,
|
||||
"port" INTEGER,
|
||||
"installCommand" TEXT,
|
||||
"buildCommand" TEXT,
|
||||
"startCommand" TEXT,
|
||||
"baseDirectory" TEXT,
|
||||
"publishDirectory" TEXT,
|
||||
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" DATETIME NOT NULL,
|
||||
"destinationDockerId" TEXT,
|
||||
"gitSourceId" TEXT,
|
||||
CONSTRAINT "Application_destinationDockerId_fkey" FOREIGN KEY ("destinationDockerId") REFERENCES "DestinationDocker" ("id") ON DELETE SET NULL ON UPDATE CASCADE,
|
||||
CONSTRAINT "Application_gitSourceId_fkey" FOREIGN KEY ("gitSourceId") REFERENCES "GitSource" ("id") ON DELETE SET NULL ON UPDATE CASCADE
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "ApplicationSettings" (
|
||||
"id" TEXT NOT NULL PRIMARY KEY,
|
||||
"applicationId" TEXT NOT NULL,
|
||||
"debug" BOOLEAN NOT NULL DEFAULT false,
|
||||
"previews" BOOLEAN NOT NULL DEFAULT false,
|
||||
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" DATETIME NOT NULL,
|
||||
CONSTRAINT "ApplicationSettings_applicationId_fkey" FOREIGN KEY ("applicationId") REFERENCES "Application" ("id") ON DELETE RESTRICT ON UPDATE CASCADE
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "Secret" (
|
||||
"id" TEXT NOT NULL PRIMARY KEY,
|
||||
"name" TEXT NOT NULL,
|
||||
"value" TEXT NOT NULL,
|
||||
"isBuildSecret" BOOLEAN NOT NULL DEFAULT false,
|
||||
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" DATETIME NOT NULL,
|
||||
"applicationId" TEXT NOT NULL,
|
||||
CONSTRAINT "Secret_applicationId_fkey" FOREIGN KEY ("applicationId") REFERENCES "Application" ("id") ON DELETE RESTRICT ON UPDATE CASCADE
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "BuildLog" (
|
||||
"id" TEXT NOT NULL PRIMARY KEY,
|
||||
"applicationId" TEXT,
|
||||
"buildId" TEXT NOT NULL,
|
||||
"line" TEXT NOT NULL,
|
||||
"time" INTEGER NOT NULL
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "Build" (
|
||||
"id" TEXT NOT NULL PRIMARY KEY,
|
||||
"type" TEXT NOT NULL,
|
||||
"applicationId" TEXT,
|
||||
"destinationDockerId" TEXT,
|
||||
"gitSourceId" TEXT,
|
||||
"githubAppId" TEXT,
|
||||
"gitlabAppId" TEXT,
|
||||
"commit" TEXT,
|
||||
"status" TEXT DEFAULT 'queued',
|
||||
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" DATETIME NOT NULL
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "DestinationDocker" (
|
||||
"id" TEXT NOT NULL PRIMARY KEY,
|
||||
"network" TEXT NOT NULL,
|
||||
"name" TEXT NOT NULL,
|
||||
"engine" TEXT NOT NULL,
|
||||
"remoteEngine" BOOLEAN NOT NULL DEFAULT false,
|
||||
"isCoolifyProxyUsed" BOOLEAN DEFAULT false,
|
||||
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" DATETIME NOT NULL
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "GitSource" (
|
||||
"id" TEXT NOT NULL PRIMARY KEY,
|
||||
"name" TEXT NOT NULL,
|
||||
"type" TEXT,
|
||||
"apiUrl" TEXT,
|
||||
"htmlUrl" TEXT,
|
||||
"organization" TEXT,
|
||||
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" DATETIME NOT NULL,
|
||||
"githubAppId" TEXT,
|
||||
"gitlabAppId" TEXT,
|
||||
CONSTRAINT "GitSource_githubAppId_fkey" FOREIGN KEY ("githubAppId") REFERENCES "GithubApp" ("id") ON DELETE SET NULL ON UPDATE CASCADE,
|
||||
CONSTRAINT "GitSource_gitlabAppId_fkey" FOREIGN KEY ("gitlabAppId") REFERENCES "GitlabApp" ("id") ON DELETE SET NULL ON UPDATE CASCADE
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "GithubApp" (
|
||||
"id" TEXT NOT NULL PRIMARY KEY,
|
||||
"name" TEXT,
|
||||
"appId" INTEGER,
|
||||
"installationId" INTEGER,
|
||||
"clientId" TEXT,
|
||||
"clientSecret" TEXT,
|
||||
"webhookSecret" TEXT,
|
||||
"privateKey" TEXT,
|
||||
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" DATETIME NOT NULL
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "GitlabApp" (
|
||||
"id" TEXT NOT NULL PRIMARY KEY,
|
||||
"oauthId" INTEGER NOT NULL,
|
||||
"groupName" TEXT,
|
||||
"deployKeyId" INTEGER,
|
||||
"privateSshKey" TEXT,
|
||||
"publicSshKey" TEXT,
|
||||
"webhookToken" TEXT,
|
||||
"appId" TEXT,
|
||||
"appSecret" TEXT,
|
||||
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" DATETIME NOT NULL
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "Database" (
|
||||
"id" TEXT NOT NULL PRIMARY KEY,
|
||||
"name" TEXT NOT NULL,
|
||||
"publicPort" INTEGER,
|
||||
"defaultDatabase" TEXT,
|
||||
"type" TEXT,
|
||||
"version" TEXT,
|
||||
"dbUser" TEXT,
|
||||
"dbUserPassword" TEXT,
|
||||
"rootUser" TEXT,
|
||||
"rootUserPassword" TEXT,
|
||||
"destinationDockerId" TEXT,
|
||||
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" DATETIME NOT NULL,
|
||||
CONSTRAINT "Database_destinationDockerId_fkey" FOREIGN KEY ("destinationDockerId") REFERENCES "DestinationDocker" ("id") ON DELETE SET NULL ON UPDATE CASCADE
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "DatabaseSettings" (
|
||||
"id" TEXT NOT NULL PRIMARY KEY,
|
||||
"databaseId" TEXT NOT NULL,
|
||||
"isPublic" BOOLEAN NOT NULL DEFAULT false,
|
||||
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" DATETIME NOT NULL,
|
||||
CONSTRAINT "DatabaseSettings_databaseId_fkey" FOREIGN KEY ("databaseId") REFERENCES "Database" ("id") ON DELETE RESTRICT ON UPDATE CASCADE
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "Service" (
|
||||
"id" TEXT NOT NULL PRIMARY KEY,
|
||||
"name" TEXT NOT NULL,
|
||||
"fqdn" TEXT,
|
||||
"type" TEXT,
|
||||
"version" TEXT,
|
||||
"destinationDockerId" TEXT,
|
||||
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" DATETIME NOT NULL,
|
||||
CONSTRAINT "Service_destinationDockerId_fkey" FOREIGN KEY ("destinationDockerId") REFERENCES "DestinationDocker" ("id") ON DELETE SET NULL ON UPDATE CASCADE
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "PlausibleAnalytics" (
|
||||
"id" TEXT NOT NULL PRIMARY KEY,
|
||||
"email" TEXT,
|
||||
"username" TEXT,
|
||||
"password" TEXT NOT NULL,
|
||||
"postgresqlUser" TEXT NOT NULL,
|
||||
"postgresqlPassword" TEXT NOT NULL,
|
||||
"postgresqlDatabase" TEXT NOT NULL,
|
||||
"postgresqlPublicPort" INTEGER,
|
||||
"secretKeyBase" TEXT,
|
||||
"serviceId" TEXT NOT NULL,
|
||||
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" DATETIME NOT NULL,
|
||||
CONSTRAINT "PlausibleAnalytics_serviceId_fkey" FOREIGN KEY ("serviceId") REFERENCES "Service" ("id") ON DELETE RESTRICT ON UPDATE CASCADE
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "Minio" (
|
||||
"id" TEXT NOT NULL PRIMARY KEY,
|
||||
"rootUser" TEXT NOT NULL,
|
||||
"rootUserPassword" TEXT NOT NULL,
|
||||
"publicPort" INTEGER,
|
||||
"serviceId" TEXT NOT NULL,
|
||||
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" DATETIME NOT NULL,
|
||||
CONSTRAINT "Minio_serviceId_fkey" FOREIGN KEY ("serviceId") REFERENCES "Service" ("id") ON DELETE RESTRICT ON UPDATE CASCADE
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "Vscodeserver" (
|
||||
"id" TEXT NOT NULL PRIMARY KEY,
|
||||
"password" TEXT NOT NULL,
|
||||
"serviceId" TEXT NOT NULL,
|
||||
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" DATETIME NOT NULL,
|
||||
CONSTRAINT "Vscodeserver_serviceId_fkey" FOREIGN KEY ("serviceId") REFERENCES "Service" ("id") ON DELETE RESTRICT ON UPDATE CASCADE
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "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,
|
||||
"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
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "_TeamToUser" (
|
||||
"A" TEXT NOT NULL,
|
||||
"B" TEXT NOT NULL,
|
||||
FOREIGN KEY ("A") REFERENCES "Team" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
|
||||
FOREIGN KEY ("B") REFERENCES "User" ("id") ON DELETE CASCADE ON UPDATE CASCADE
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "_ApplicationToTeam" (
|
||||
"A" TEXT NOT NULL,
|
||||
"B" TEXT NOT NULL,
|
||||
FOREIGN KEY ("A") REFERENCES "Application" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
|
||||
FOREIGN KEY ("B") REFERENCES "Team" ("id") ON DELETE CASCADE ON UPDATE CASCADE
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "_GitSourceToTeam" (
|
||||
"A" TEXT NOT NULL,
|
||||
"B" TEXT NOT NULL,
|
||||
FOREIGN KEY ("A") REFERENCES "GitSource" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
|
||||
FOREIGN KEY ("B") REFERENCES "Team" ("id") ON DELETE CASCADE ON UPDATE CASCADE
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "_GithubAppToTeam" (
|
||||
"A" TEXT NOT NULL,
|
||||
"B" TEXT NOT NULL,
|
||||
FOREIGN KEY ("A") REFERENCES "GithubApp" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
|
||||
FOREIGN KEY ("B") REFERENCES "Team" ("id") ON DELETE CASCADE ON UPDATE CASCADE
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "_GitlabAppToTeam" (
|
||||
"A" TEXT NOT NULL,
|
||||
"B" TEXT NOT NULL,
|
||||
FOREIGN KEY ("A") REFERENCES "GitlabApp" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
|
||||
FOREIGN KEY ("B") REFERENCES "Team" ("id") ON DELETE CASCADE ON UPDATE CASCADE
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "_DestinationDockerToTeam" (
|
||||
"A" TEXT NOT NULL,
|
||||
"B" TEXT NOT NULL,
|
||||
FOREIGN KEY ("A") REFERENCES "DestinationDocker" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
|
||||
FOREIGN KEY ("B") REFERENCES "Team" ("id") ON DELETE CASCADE ON UPDATE CASCADE
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "_DatabaseToTeam" (
|
||||
"A" TEXT NOT NULL,
|
||||
"B" TEXT NOT NULL,
|
||||
FOREIGN KEY ("A") REFERENCES "Database" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
|
||||
FOREIGN KEY ("B") REFERENCES "Team" ("id") ON DELETE CASCADE ON UPDATE CASCADE
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "_ServiceToTeam" (
|
||||
"A" TEXT NOT NULL,
|
||||
"B" TEXT NOT NULL,
|
||||
FOREIGN KEY ("A") REFERENCES "Service" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
|
||||
FOREIGN KEY ("B") REFERENCES "Team" ("id") ON DELETE CASCADE ON UPDATE CASCADE
|
||||
);
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "Setting_fqdn_key" ON "Setting"("fqdn");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "User_id_key" ON "User"("id");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "User_email_key" ON "User"("email");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "Application_fqdn_key" ON "Application"("fqdn");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "ApplicationSettings_applicationId_key" ON "ApplicationSettings"("applicationId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "Secret_name_key" ON "Secret"("name");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "DestinationDocker_network_key" ON "DestinationDocker"("network");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "GitSource_githubAppId_key" ON "GitSource"("githubAppId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "GitSource_gitlabAppId_key" ON "GitSource"("gitlabAppId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "GithubApp_name_key" ON "GithubApp"("name");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "GitlabApp_oauthId_key" ON "GitlabApp"("oauthId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "GitlabApp_groupName_key" ON "GitlabApp"("groupName");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "DatabaseSettings_databaseId_key" ON "DatabaseSettings"("databaseId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "PlausibleAnalytics_serviceId_key" ON "PlausibleAnalytics"("serviceId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "Minio_serviceId_key" ON "Minio"("serviceId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "Vscodeserver_serviceId_key" ON "Vscodeserver"("serviceId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "Wordpress_serviceId_key" ON "Wordpress"("serviceId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "_TeamToUser_AB_unique" ON "_TeamToUser"("A", "B");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "_TeamToUser_B_index" ON "_TeamToUser"("B");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "_ApplicationToTeam_AB_unique" ON "_ApplicationToTeam"("A", "B");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "_ApplicationToTeam_B_index" ON "_ApplicationToTeam"("B");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "_GitSourceToTeam_AB_unique" ON "_GitSourceToTeam"("A", "B");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "_GitSourceToTeam_B_index" ON "_GitSourceToTeam"("B");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "_GithubAppToTeam_AB_unique" ON "_GithubAppToTeam"("A", "B");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "_GithubAppToTeam_B_index" ON "_GithubAppToTeam"("B");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "_GitlabAppToTeam_AB_unique" ON "_GitlabAppToTeam"("A", "B");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "_GitlabAppToTeam_B_index" ON "_GitlabAppToTeam"("B");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "_DestinationDockerToTeam_AB_unique" ON "_DestinationDockerToTeam"("A", "B");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "_DestinationDockerToTeam_B_index" ON "_DestinationDockerToTeam"("B");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "_DatabaseToTeam_AB_unique" ON "_DatabaseToTeam"("A", "B");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "_DatabaseToTeam_B_index" ON "_DatabaseToTeam"("B");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "_ServiceToTeam_AB_unique" ON "_ServiceToTeam"("A", "B");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "_ServiceToTeam_B_index" ON "_ServiceToTeam"("B");
|
28
prisma/migrations/20220210104005_redis_aol/migration.sql
Normal file
28
prisma/migrations/20220210104005_redis_aol/migration.sql
Normal file
@ -0,0 +1,28 @@
|
||||
-- RedefineTables
|
||||
PRAGMA foreign_keys=OFF;
|
||||
CREATE TABLE "new_Team" (
|
||||
"id" TEXT NOT NULL PRIMARY KEY,
|
||||
"name" TEXT,
|
||||
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" DATETIME NOT NULL,
|
||||
"databaseId" TEXT,
|
||||
"serviceId" TEXT
|
||||
);
|
||||
INSERT INTO "new_Team" ("createdAt", "databaseId", "id", "name", "serviceId", "updatedAt") SELECT "createdAt", "databaseId", "id", "name", "serviceId", "updatedAt" FROM "Team";
|
||||
DROP TABLE "Team";
|
||||
ALTER TABLE "new_Team" RENAME TO "Team";
|
||||
CREATE TABLE "new_DatabaseSettings" (
|
||||
"id" TEXT NOT NULL PRIMARY KEY,
|
||||
"databaseId" TEXT NOT NULL,
|
||||
"isPublic" BOOLEAN NOT NULL DEFAULT false,
|
||||
"appendOnly" BOOLEAN NOT NULL DEFAULT true,
|
||||
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" DATETIME NOT NULL,
|
||||
CONSTRAINT "DatabaseSettings_databaseId_fkey" FOREIGN KEY ("databaseId") REFERENCES "Database" ("id") ON DELETE RESTRICT ON UPDATE CASCADE
|
||||
);
|
||||
INSERT INTO "new_DatabaseSettings" ("createdAt", "databaseId", "id", "isPublic", "updatedAt") SELECT "createdAt", "databaseId", "id", "isPublic", "updatedAt" FROM "DatabaseSettings";
|
||||
DROP TABLE "DatabaseSettings";
|
||||
ALTER TABLE "new_DatabaseSettings" RENAME TO "DatabaseSettings";
|
||||
CREATE UNIQUE INDEX "DatabaseSettings_databaseId_key" ON "DatabaseSettings"("databaseId");
|
||||
PRAGMA foreign_key_check;
|
||||
PRAGMA foreign_keys=ON;
|
3
prisma/migrations/migration_lock.toml
Normal file
3
prisma/migrations/migration_lock.toml
Normal file
@ -0,0 +1,3 @@
|
||||
# Please do not edit this file manually
|
||||
# It should be added in your version-control system (i.e. Git)
|
||||
provider = "sqlite"
|
298
prisma/schema.prisma
Normal file
298
prisma/schema.prisma
Normal file
@ -0,0 +1,298 @@
|
||||
generator client {
|
||||
provider = "prisma-client-js"
|
||||
}
|
||||
|
||||
datasource db {
|
||||
provider = "sqlite"
|
||||
url = env("COOLIFY_DATABASE_URL")
|
||||
}
|
||||
|
||||
model Setting {
|
||||
id String @id @default(cuid())
|
||||
fqdn String? @unique
|
||||
isRegistrationEnabled Boolean @default(false)
|
||||
proxyPassword String
|
||||
proxyUser String
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
}
|
||||
|
||||
model User {
|
||||
id String @id @unique @default(cuid())
|
||||
email String @unique
|
||||
type String
|
||||
password String?
|
||||
teams Team[]
|
||||
permission Permission[]
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
}
|
||||
|
||||
model Permission {
|
||||
id String @id @default(cuid())
|
||||
user User @relation(fields: [userId], references: [id])
|
||||
userId String
|
||||
team Team @relation(fields: [teamId], references: [id])
|
||||
teamId String
|
||||
permission String
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
}
|
||||
|
||||
model Team {
|
||||
id String @id @default(cuid())
|
||||
users User[]
|
||||
name String?
|
||||
applications Application[]
|
||||
gitSources GitSource[]
|
||||
gitHubApps GithubApp[]
|
||||
gitLabApps GitlabApp[]
|
||||
destinationDocker DestinationDocker[]
|
||||
permissions Permission[]
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
database Database[] @relation(fields: [databaseId], references: [id])
|
||||
databaseId String?
|
||||
service Service[] @relation(fields: [serviceId], references: [id])
|
||||
serviceId String?
|
||||
}
|
||||
|
||||
model TeamInvitation {
|
||||
id String @id @default(cuid())
|
||||
uid String
|
||||
email String
|
||||
teamId String
|
||||
teamName String
|
||||
permission String
|
||||
createdAt DateTime @default(now())
|
||||
}
|
||||
|
||||
model Application {
|
||||
id String @id @default(cuid())
|
||||
name String
|
||||
fqdn String? @unique
|
||||
repository String?
|
||||
configHash String?
|
||||
branch String?
|
||||
buildPack String?
|
||||
projectId Int?
|
||||
port Int?
|
||||
installCommand String?
|
||||
buildCommand String?
|
||||
startCommand String?
|
||||
baseDirectory String?
|
||||
publishDirectory String?
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
settings ApplicationSettings?
|
||||
teams Team[]
|
||||
destinationDockerId String?
|
||||
destinationDocker DestinationDocker? @relation(fields: [destinationDockerId], references: [id])
|
||||
gitSourceId String?
|
||||
gitSource GitSource? @relation(fields: [gitSourceId], references: [id])
|
||||
secrets Secret[]
|
||||
}
|
||||
|
||||
model ApplicationSettings {
|
||||
id String @id @default(cuid())
|
||||
application Application @relation(fields: [applicationId], references: [id])
|
||||
applicationId String @unique
|
||||
debug Boolean @default(false)
|
||||
previews Boolean @default(false)
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
}
|
||||
|
||||
model Secret {
|
||||
id String @id @default(cuid())
|
||||
name String @unique
|
||||
value String
|
||||
isBuildSecret Boolean @default(false)
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
application Application @relation(fields: [applicationId], references: [id])
|
||||
applicationId String
|
||||
}
|
||||
|
||||
model BuildLog {
|
||||
id String @id @default(cuid())
|
||||
applicationId String?
|
||||
buildId String
|
||||
line String
|
||||
time Int
|
||||
}
|
||||
|
||||
model Build {
|
||||
id String @id @default(cuid())
|
||||
type String
|
||||
applicationId String?
|
||||
destinationDockerId String?
|
||||
gitSourceId String?
|
||||
githubAppId String?
|
||||
gitlabAppId String?
|
||||
commit String?
|
||||
status String? @default("queued")
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
}
|
||||
|
||||
model DestinationDocker {
|
||||
id String @id @default(cuid())
|
||||
network String @unique
|
||||
name String
|
||||
engine String
|
||||
remoteEngine Boolean @default(false)
|
||||
isCoolifyProxyUsed Boolean? @default(false)
|
||||
teams Team[]
|
||||
application Application[]
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
database Database[]
|
||||
service Service[]
|
||||
}
|
||||
|
||||
model GitSource {
|
||||
id String @id @default(cuid())
|
||||
name String
|
||||
teams Team[]
|
||||
type String?
|
||||
apiUrl String?
|
||||
htmlUrl String?
|
||||
organization String?
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
githubAppId String? @unique
|
||||
githubApp GithubApp? @relation(fields: [githubAppId], references: [id])
|
||||
application Application[]
|
||||
gitlabAppId String? @unique
|
||||
gitlabApp GitlabApp? @relation(fields: [gitlabAppId], references: [id])
|
||||
}
|
||||
|
||||
model GithubApp {
|
||||
id String @id @default(cuid())
|
||||
name String? @unique
|
||||
teams Team[]
|
||||
appId Int?
|
||||
installationId Int?
|
||||
clientId String?
|
||||
clientSecret String?
|
||||
webhookSecret String?
|
||||
privateKey String?
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
gitSource GitSource?
|
||||
}
|
||||
|
||||
model GitlabApp {
|
||||
id String @id @default(cuid())
|
||||
oauthId Int @unique
|
||||
groupName String? @unique
|
||||
teams Team[]
|
||||
deployKeyId Int?
|
||||
privateSshKey String?
|
||||
publicSshKey String?
|
||||
webhookToken String?
|
||||
appId String?
|
||||
appSecret String?
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
gitSource GitSource?
|
||||
}
|
||||
|
||||
model Database {
|
||||
id String @id @default(cuid())
|
||||
name String
|
||||
publicPort Int?
|
||||
defaultDatabase String?
|
||||
type String?
|
||||
version String?
|
||||
dbUser String?
|
||||
dbUserPassword String?
|
||||
rootUser String?
|
||||
rootUserPassword String?
|
||||
settings DatabaseSettings?
|
||||
destinationDocker DestinationDocker? @relation(fields: [destinationDockerId], references: [id])
|
||||
destinationDockerId String?
|
||||
teams Team[]
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
}
|
||||
|
||||
model DatabaseSettings {
|
||||
id String @id @default(cuid())
|
||||
database Database @relation(fields: [databaseId], references: [id])
|
||||
databaseId String @unique
|
||||
isPublic Boolean @default(false)
|
||||
appendOnly Boolean @default(true)
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
}
|
||||
|
||||
model Service {
|
||||
id String @id @default(cuid())
|
||||
name String
|
||||
fqdn String?
|
||||
type String?
|
||||
version String?
|
||||
teams Team[]
|
||||
destinationDockerId String?
|
||||
destinationDocker DestinationDocker? @relation(fields: [destinationDockerId], references: [id])
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
plausibleAnalytics PlausibleAnalytics?
|
||||
minio Minio?
|
||||
vscodeserver Vscodeserver?
|
||||
wordpress Wordpress?
|
||||
}
|
||||
|
||||
model PlausibleAnalytics {
|
||||
id String @id @default(cuid())
|
||||
email String?
|
||||
username String?
|
||||
password String
|
||||
postgresqlUser String
|
||||
postgresqlPassword String
|
||||
postgresqlDatabase String
|
||||
postgresqlPublicPort Int?
|
||||
secretKeyBase String?
|
||||
serviceId String @unique
|
||||
service Service @relation(fields: [serviceId], references: [id])
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
}
|
||||
|
||||
model Minio {
|
||||
id String @id @default(cuid())
|
||||
rootUser String
|
||||
rootUserPassword String
|
||||
publicPort Int?
|
||||
serviceId String @unique
|
||||
service Service @relation(fields: [serviceId], references: [id])
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
}
|
||||
|
||||
model Vscodeserver {
|
||||
id String @id @default(cuid())
|
||||
password String
|
||||
serviceId String @unique
|
||||
service Service @relation(fields: [serviceId], references: [id])
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
}
|
||||
|
||||
model Wordpress {
|
||||
id String @id @default(cuid())
|
||||
extraConfig String?
|
||||
tablePrefix String?
|
||||
mysqlUser String
|
||||
mysqlPassword String
|
||||
mysqlRootUser String
|
||||
mysqlRootUserPassword String
|
||||
mysqlDatabase String?
|
||||
mysqlPublicPort Int?
|
||||
serviceId String @unique
|
||||
service Service @relation(fields: [serviceId], references: [id])
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
}
|
80
prisma/seed.cjs
Normal file
80
prisma/seed.cjs
Normal file
@ -0,0 +1,80 @@
|
||||
const dotEnvExtended = require('dotenv-extended');
|
||||
dotEnvExtended.load();
|
||||
const { PrismaClient } = require('@prisma/client');
|
||||
const prisma = new PrismaClient();
|
||||
const crypto = require('crypto');
|
||||
const generator = require('generate-password');
|
||||
const cuid = require('cuid');
|
||||
|
||||
function generatePassword(length = 24) {
|
||||
return generator.generate({
|
||||
length,
|
||||
numbers: true,
|
||||
strict: true
|
||||
});
|
||||
}
|
||||
const algorithm = 'aes-256-ctr';
|
||||
|
||||
async function main() {
|
||||
// Enable registration for the first user
|
||||
// Set initial HAProxy password
|
||||
const settingsFound = await prisma.setting.findFirst({});
|
||||
if (!settingsFound) {
|
||||
await prisma.setting.create({
|
||||
data: {
|
||||
isRegistrationEnabled: true,
|
||||
proxyPassword: encrypt(generatePassword()),
|
||||
proxyUser: cuid()
|
||||
}
|
||||
});
|
||||
}
|
||||
const localDocker = await prisma.destinationDocker.findFirst({
|
||||
where: { engine: '/var/run/docker.sock' }
|
||||
});
|
||||
if (!localDocker) {
|
||||
await prisma.destinationDocker.create({
|
||||
data: {
|
||||
engine: '/var/run/docker.sock',
|
||||
name: 'Local Docker',
|
||||
isCoolifyProxyUsed: true,
|
||||
network: 'coolify'
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
main()
|
||||
.catch((e) => {
|
||||
console.error(e);
|
||||
process.exit(1);
|
||||
})
|
||||
.finally(async () => {
|
||||
await prisma.$disconnect();
|
||||
});
|
||||
|
||||
const encrypt = (text) => {
|
||||
if (text) {
|
||||
const iv = crypto.randomBytes(16);
|
||||
const cipher = crypto.createCipheriv(algorithm, process.env['COOLIFY_SECRET_KEY'], iv);
|
||||
const encrypted = Buffer.concat([cipher.update(text), cipher.final()]);
|
||||
return JSON.stringify({
|
||||
iv: iv.toString('hex'),
|
||||
content: encrypted.toString('hex')
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const decrypt = (hashString) => {
|
||||
if (hashString) {
|
||||
const hash = JSON.parse(hashString);
|
||||
const decipher = crypto.createDecipheriv(
|
||||
algorithm,
|
||||
process.env['COOLIFY_SECRET_KEY'],
|
||||
Buffer.from(hash.iv, 'hex')
|
||||
);
|
||||
const decrpyted = Buffer.concat([
|
||||
decipher.update(Buffer.from(hash.content, 'hex')),
|
||||
decipher.final()
|
||||
]);
|
||||
return decrpyted.toString();
|
||||
}
|
||||
};
|
112
scripts/install.sh
Normal file
112
scripts/install.sh
Normal file
@ -0,0 +1,112 @@
|
||||
#!/usr/bin/env bash
|
||||
clear
|
||||
ARG1=$1
|
||||
WHO=$(whoami)
|
||||
APP_ID=$(cat /proc/sys/kernel/random/uuid)
|
||||
RANDOM_SECRET=$(echo $(($(date +%s%N) / 1000000)) | sha256sum | base64 | head -c 32)
|
||||
SENTRY_DSN="https://9e7a74326f29422584d2d0bebdc8b7d3@o1082494.ingest.sentry.io/6091062"
|
||||
DOCKER_MAJOR=20
|
||||
DOCKER_MINOR=10
|
||||
DOCKER_VERSION_OK="nok"
|
||||
|
||||
set -eou pipefail
|
||||
|
||||
if [ $ARG1 ] && [ $ARG1 == "-d" ]; then
|
||||
set -x
|
||||
fi
|
||||
|
||||
function errorchecker() {
|
||||
exitCode=$?
|
||||
if [ $exitCode -ne "0" ]; then
|
||||
echo "$0 exited unexpectedly with status: $exitCode"
|
||||
exit $exitCode
|
||||
fi
|
||||
}
|
||||
trap 'errorchecker' EXIT
|
||||
|
||||
echo -e "Welcome to Coolify installer! \n"
|
||||
echo "This script will install all the required packages and services to run Coolify."
|
||||
echo -e "If you want to install Coolify on a different OS, please open an issue on Github to get supported version.\n\n"
|
||||
|
||||
echo -e "To see what I'm doing, please check:"
|
||||
echo -e "https://github.com/coollabsio/get.coollabs.io/blob/main/static/coolify/install_v2.sh\n\n"
|
||||
|
||||
if [ $WHO != 'root' ]; then
|
||||
echo 'Run as root please: sudo sh -c "$(curl -fsSL https://get.coollabs.io/coolify/install.sh)"'
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ ! -x "$(command -v docker)" ]; then
|
||||
while true; do
|
||||
read -p "Docker Engine not found, should I install it automatically? [Yy/Nn] " yn
|
||||
case $yn in
|
||||
[Yy]*)
|
||||
sh -c "$(curl -fsSL https://get.docker.com)"
|
||||
break
|
||||
;;
|
||||
[Nn]*)
|
||||
echo "Please install docker manually and update it to the latest, but at least to $DOCKER_MAJOR.$DOCKER_MINOR"
|
||||
exit 0
|
||||
;;
|
||||
*) echo "Please answer Y or N." ;;
|
||||
esac
|
||||
done
|
||||
fi
|
||||
|
||||
SERVER_VERSION=$(docker version -f "{{.Server.Version}}")
|
||||
SERVER_VERSION_MAJOR=$(echo "$SERVER_VERSION" | cut -d'.' -f 1)
|
||||
SERVER_VERSION_MINOR=$(echo "$SERVER_VERSION" | cut -d'.' -f 2)
|
||||
|
||||
if [ "$SERVER_VERSION_MAJOR" -ge "$DOCKER_MAJOR" ] &&
|
||||
[ "$SERVER_VERSION_MINOR" -ge "$DOCKER_MINOR" ]; then
|
||||
DOCKER_VERSION_OK="ok"
|
||||
fi
|
||||
|
||||
if [ $DOCKER_VERSION_OK == 'nok' ]; then
|
||||
echo "Docker version less than $DOCKER_MAJOR.$DOCKER_MINOR, please update it to at least to $DOCKER_MAJOR.$DOCKER_MINOR"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Adding docker daemon configuration
|
||||
cat <<EOF >/etc/docker/daemon.json
|
||||
{
|
||||
"log-driver": "json-file",
|
||||
"log-opts": {
|
||||
"max-size": "100m",
|
||||
"max-file": "5"
|
||||
},
|
||||
"features": {
|
||||
"buildkit": true
|
||||
},
|
||||
"live-restore": true
|
||||
}
|
||||
EOF
|
||||
|
||||
# Restarting docker daemon
|
||||
sh -c "systemctl daemon-reload && systemctl restart docker"
|
||||
|
||||
# Downloading docker compose cli plugin
|
||||
mkdir -p ~/.docker/cli-plugins/
|
||||
curl -SL https://github.com/docker/compose/releases/download/v2.2.2/docker-compose-linux-x86_64 -o ~/.docker/cli-plugins/docker-compose
|
||||
chmod +x ~/.docker/cli-plugins/docker-compose
|
||||
|
||||
# Making base directory for coolify
|
||||
if [ ! -d coolify ]; then
|
||||
mkdir coolify
|
||||
fi
|
||||
|
||||
if [ -f coolify/.env ]; then
|
||||
echo -e "Coolify is already installed, using some of the existing settings."
|
||||
else
|
||||
echo "COOLIFY_APP_ID=$APP_ID
|
||||
COOLIFY_SECRET_KEY=$RANDOM_SECRET
|
||||
COOLIFY_DATABASE_URL=file:../db/prod.db
|
||||
COOLIFY_SENTRY_DSN=$SENTRY_DSN
|
||||
COOLIFY_HOSTED_ON=docker" > coolify/.env
|
||||
fi
|
||||
|
||||
cd coolify && docker run -tid --env-file .env -v /var/run/docker.sock:/var/run/docker.sock -v coolify-db-sqlite coollabsio/coolify:latest /bin/sh -c "env | grep COOLIFY > .env && docker compose up -d --force-recreate"
|
||||
|
||||
echo -e "Congratulations! Your coolify is ready to use.\n"
|
||||
echo "Please visit http://<Your Public IP Address>:3000/ to get started."
|
||||
echo "It will take a few minutes to start up, don't worry."
|
114
scripts/install_podman_experiment.sh
Normal file
114
scripts/install_podman_experiment.sh
Normal file
@ -0,0 +1,114 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
clear
|
||||
ARG1=$1
|
||||
WHO=$(whoami)
|
||||
APP_ID=$(cat /proc/sys/kernel/random/uuid)
|
||||
RANDOM_SECRET=$(echo $(($(date +%s%N) / 1000000)) | sha256sum | base64 | head -c 32)
|
||||
SENTRY_DSN="https://9e7a74326f29422584d2d0bebdc8b7d3@o1082494.ingest.sentry.io/6091062"
|
||||
|
||||
UBUNTU_MAJOR_MIN=20
|
||||
UBUNTU_MINOR_MIN=04
|
||||
OS_OK="nok"
|
||||
|
||||
set -eou pipefail
|
||||
|
||||
if [ $ARG1 ] && [ $ARG1 == "-d" ]; then
|
||||
set -x
|
||||
fi
|
||||
|
||||
function errorchecker() {
|
||||
exitCode=$?
|
||||
if [ $exitCode -ne "0" ]; then
|
||||
echo "$0 exited unexpectedly with status: $exitCode"
|
||||
exit $exitCode
|
||||
fi
|
||||
}
|
||||
trap 'errorchecker' EXIT
|
||||
|
||||
if [ $WHO != 'root' ]; then
|
||||
echo 'Run as root please: sudo sh -c "$(curl -fsSL https://get.coollabs.io/coolify/install.sh)"'
|
||||
exit 1
|
||||
fi
|
||||
|
||||
. /etc/lsb-release
|
||||
if [ $DISTRIB_ID != 'Ubuntu' ]; then
|
||||
echo 'Not supported OS, please open an issue on Github to get supported version.'
|
||||
exit 1
|
||||
fi
|
||||
|
||||
DISTRIB_RELEASE_MAJOR=$(echo "$DISTRIB_RELEASE" | cut -d'.' -f 1)
|
||||
DISTRIB_RELEASE_MINOR=$(echo "$DISTRIB_RELEASE" | cut -d'.' -f 2)
|
||||
|
||||
if [ "$DISTRIB_RELEASE_MAJOR" -ge "$UBUNTU_MAJOR_MIN" ] &&
|
||||
[ "$DISTRIB_RELEASE_MINOR" -ge "$UBUNTU_MINOR_MIN" ]; then
|
||||
OS_OK="ok"
|
||||
fi
|
||||
|
||||
if [ $OS_OK == 'nok' ]; then
|
||||
echo "Ubuntu version less than $UBUNTU_MAJOR_MIN.$UBUNTU_MINOR_MIN."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
function installPodman() {
|
||||
apt-get update -y
|
||||
apt-get install curl wget gnupg2 -y
|
||||
if [ "$DISTRIB_RELEASE_MAJOR" -eq "20" ] && [ "$DISTRIB_RELEASE_MINOR" -eq "04" ]; then
|
||||
echo 'Installing on 20.04'
|
||||
source /etc/os-release
|
||||
sh -c "echo 'deb http://download.opensuse.org/repositories/devel:/kubic:/libcontainers:/stable/xUbuntu_${VERSION_ID}/ /' > /etc/apt/sources.list.d/devel:kubic:libcontainers:stable.list"
|
||||
wget -nv https://download.opensuse.org/repositories/devel:kubic:libcontainers:stable/xUbuntu_${VERSION_ID}/Release.key -O- | apt-key add -
|
||||
apt-get update -y
|
||||
apt-get -y install podman
|
||||
return 0
|
||||
elif [ "$DISTRIB_RELEASE_MAJOR" -eq "20" ] && [ "$DISTRIB_RELEASE_MINOR" -eq "10" ]; then
|
||||
apt-get -y install podman
|
||||
return 0
|
||||
elif [ "$DISTRIB_RELEASE_MAJOR" -gt "20" ]; then
|
||||
apt-get -y install podman
|
||||
return 0
|
||||
else
|
||||
exit 1
|
||||
fi
|
||||
|
||||
}
|
||||
|
||||
if [ ! -x "$(command -v podman)" ]; then
|
||||
while true; do
|
||||
read -p "Podman not found, should I install it automatically? [Yy/Nn] " yn
|
||||
case $yn in
|
||||
[Yy]*)
|
||||
installPodman
|
||||
break
|
||||
;;
|
||||
[Nn]*)
|
||||
echo "Please install docker manually and update it to the latest, but at least to $DOCKER_MAJOR.$DOCKER_MINOR"
|
||||
exit 0
|
||||
;;
|
||||
*) echo "Please answer Yy or Nn." ;;
|
||||
esac
|
||||
done
|
||||
fi
|
||||
|
||||
# Making base directory for coolify
|
||||
if [ ! -d coolify ]; then
|
||||
mkdir coolify
|
||||
fi
|
||||
|
||||
echo "COOLIFY_APP_ID=$APP_ID
|
||||
COOLIFY_SECRET_KEY=$RANDOM_SECRET
|
||||
COOLIFY_DATABASE_URL=file:../db/prod.db
|
||||
COOLIFY_SENTRY_DSN=$SENTRY_DSN
|
||||
COOLIFY_HOSTED_ON=docker" >coolify/.env
|
||||
|
||||
systemctl start podman.socket
|
||||
systemctl enable podman.socket
|
||||
|
||||
podman volume create coolify-db
|
||||
podman volume create coolify-ssl-certs
|
||||
podman volume create coolify-letsencrypt
|
||||
|
||||
|
||||
cd coolify && podman run --privileged -tid --env-file .env -v /var/run/podman/podman.sock:/var/run/podman/podman.sock -v coolify-db-sqlite:/app/db docker.io/coollabsio/coolify:latest /bin/sh -c "env | grep COOLIFY > .env && docker-compose up -d --force-recreate"
|
||||
echo "Done"
|
||||
exit 0
|
@ -4,10 +4,10 @@
|
||||
<meta charset="utf-8" />
|
||||
<link rel="icon" href="/favicon.png" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>Coolify</title>
|
||||
<link rel="dns-prefetch" href="https://cdn.coollabs.io/" />
|
||||
<link rel="preconnect" href="https://cdn.coollabs.io/" crossorigin="" />
|
||||
<link rel="stylesheet" href="https://cdn.coollabs.io/fonts/montserrat/montserrat.css" />
|
||||
<link rel="stylesheet" href="https://cdn.coollabs.io/fonts/poppins/poppins.css" />
|
||||
<title>Coolify</title>
|
||||
%svelte.head%
|
||||
</head>
|
||||
<body>
|
||||
|
149
src/app.postcss
149
src/app.postcss
@ -1,149 +0,0 @@
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
html {
|
||||
height: 100%;
|
||||
}
|
||||
body {
|
||||
background-color: rgb(22, 22, 22);
|
||||
min-height: 100vh;
|
||||
overflow-x: hidden;
|
||||
@apply text-white;
|
||||
}
|
||||
|
||||
:root {
|
||||
--toastBackground: rgba(41, 37, 36, 0.8);
|
||||
--toastProgressBackground: transparent;
|
||||
--toastFont: 'Inter';
|
||||
}
|
||||
|
||||
.border-gradient {
|
||||
border-bottom: 2px solid transparent;
|
||||
border-image: linear-gradient(
|
||||
0.25turn,
|
||||
rgba(255, 249, 34),
|
||||
rgba(255, 0, 128),
|
||||
rgba(56, 2, 155, 0)
|
||||
);
|
||||
border-image-slice: 1;
|
||||
}
|
||||
.border-gradient-full {
|
||||
border: 4px solid transparent;
|
||||
border-image: linear-gradient(
|
||||
0.25turn,
|
||||
rgba(255, 249, 34),
|
||||
rgba(255, 0, 128),
|
||||
rgba(56, 2, 155, 0)
|
||||
);
|
||||
border-image-slice: 1;
|
||||
}
|
||||
|
||||
[aria-label][role~='tooltip']::after {
|
||||
background: rgba(41, 37, 36, 0.9);
|
||||
color: white;
|
||||
font-family: 'Inter';
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
white-space: normal;
|
||||
}
|
||||
|
||||
[role~='tooltip'][data-microtip-position|='bottom']::before {
|
||||
background: url('data:image/svg+xml;charset=utf-8,%3Csvg%20xmlns%3D%22http%3A//www.w3.org/2000/svg%22%20width%3D%2236px%22%20height%3D%2212px%22%3E%3Cpath%20fill%3D%22rgba%2841,%2037,%2036,%200.9%29%22%20transform%3D%22rotate%28180%2018%206%29%22%20d%3D%22M2.658,0.000%20C-13.615,0.000%2050.938,0.000%2034.662,0.000%20C28.662,0.000%2023.035,12.002%2018.660,12.002%20C14.285,12.002%208.594,0.000%202.658,0.000%20Z%22/%3E%3C/svg%3E')
|
||||
no-repeat;
|
||||
}
|
||||
|
||||
[role~='tooltip'][data-microtip-position|='top']::before {
|
||||
background: url('data:image/svg+xml;charset=utf-8,%3Csvg%20xmlns%3D%22http%3A//www.w3.org/2000/svg%22%20width%3D%2236px%22%20height%3D%2212px%22%3E%3Cpath%20fill%3D%22rgba%2841,%2037,%2036,%200.9%29%22%20transform%3D%22rotate%280%29%22%20d%3D%22M2.658,0.000%20C-13.615,0.000%2050.938,0.000%2034.662,0.000%20C28.662,0.000%2023.035,12.002%2018.660,12.002%20C14.285,12.002%208.594,0.000%202.658,0.000%20Z%22/%3E%3C/svg%3E')
|
||||
no-repeat;
|
||||
}
|
||||
[role~='tooltip'][data-microtip-position='right']::before {
|
||||
background: url('data:image/svg+xml;charset=utf-8,%3Csvg%20xmlns%3D%22http%3A//www.w3.org/2000/svg%22%20width%3D%2212px%22%20height%3D%2236px%22%3E%3Cpath%20fill%3D%22rgba%2841,%2037,%2036,%200.9%29%22%20transform%3D%22rotate%2890%206%206%29%22%20d%3D%22M2.658,0.000%20C-13.615,0.000%2050.938,0.000%2034.662,0.000%20C28.662,0.000%2023.035,12.002%2018.660,12.002%20C14.285,12.002%208.594,0.000%202.658,0.000%20Z%22/%3E%3C/svg%3E')
|
||||
no-repeat;
|
||||
}
|
||||
|
||||
[role~='tooltip'][data-microtip-position='left']::before {
|
||||
background: url('data:image/svg+xml;charset=utf-8,%3Csvg%20xmlns%3D%22http%3A//www.w3.org/2000/svg%22%20width%3D%2212px%22%20height%3D%2236px%22%3E%3Cpath%20fill%3D%22rgba%2841,%2037,%2036,%200.9%29%22%20transform%3D%22rotate%28-90%2018%2018%29%22%20d%3D%22M2.658,0.000%20C-13.615,0.000%2050.938,0.000%2034.662,0.000%20C28.662,0.000%2023.035,12.002%2018.660,12.002%20C14.285,12.002%208.594,0.000%202.658,0.000%20Z%22/%3E%3C/svg%3E')
|
||||
no-repeat;
|
||||
}
|
||||
|
||||
.main {
|
||||
width: calc(100% - 4rem);
|
||||
margin-left: 4rem;
|
||||
}
|
||||
._toastMsg {
|
||||
@apply text-sm font-bold !important;
|
||||
}
|
||||
._toastItem {
|
||||
@apply w-full border-l-2 border-green-600 !important;
|
||||
}
|
||||
._toastBtn {
|
||||
@apply text-xs !important;
|
||||
}
|
||||
._toastBtn:hover {
|
||||
@apply bg-gray-500 !important;
|
||||
}
|
||||
.icon {
|
||||
@apply text-white rounded p-2 transition duration-100;
|
||||
}
|
||||
.icon:hover {
|
||||
@apply bg-warmGray-700;
|
||||
}
|
||||
input {
|
||||
@apply text-sm rounded py-2 px-6 font-bold bg-warmGray-800 text-white transition duration-150 outline-none border border-transparent !important;
|
||||
}
|
||||
input:hover,
|
||||
input:focus-visible,
|
||||
input:focus {
|
||||
@apply bg-warmGray-700 !important;
|
||||
}
|
||||
textarea {
|
||||
@apply text-sm rounded py-2 px-6 font-bold bg-warmGray-800 text-white transition duration-150 outline-none resize-none !important;
|
||||
}
|
||||
textarea:hover {
|
||||
@apply bg-warmGray-700 !important;
|
||||
}
|
||||
select {
|
||||
@apply text-sm rounded py-2 px-6 font-bold bg-warmGray-800 text-white transition duration-150 outline-none !important;
|
||||
}
|
||||
select:hover {
|
||||
@apply bg-warmGray-700 !important;
|
||||
}
|
||||
|
||||
label {
|
||||
@apply text-left text-base font-bold text-warmGray-400 !important;
|
||||
}
|
||||
button {
|
||||
@apply outline-none !important;
|
||||
}
|
||||
.button {
|
||||
@apply rounded text-sm font-bold transition-all duration-100 !important;
|
||||
}
|
||||
|
||||
.button:focus-visible,
|
||||
.button:focus {
|
||||
@apply bg-warmGray-700 !important;
|
||||
}
|
||||
.h-271 {
|
||||
min-height: 271px !important;
|
||||
}
|
||||
.repository-select-search .listItem .item,
|
||||
.repository-select-search .empty {
|
||||
@apply text-sm py-3 font-bold bg-warmGray-800 text-white cursor-pointer border-none hover:bg-warmGray-700 !important;
|
||||
}
|
||||
|
||||
.repository-select-search .listContainer {
|
||||
@apply bg-transparent !important;
|
||||
}
|
||||
|
||||
.repository-select-search .clearSelect {
|
||||
@apply text-white cursor-pointer !important;
|
||||
}
|
||||
|
||||
.repository-select-search .selectedItem {
|
||||
@apply text-white relative cursor-pointer font-bold text-sm flex items-center !important;
|
||||
}
|
||||
.selectContainer {
|
||||
background: transparent !important;
|
||||
@apply border-0 !important;
|
||||
}
|
@ -1,610 +0,0 @@
|
||||
<script>
|
||||
import { VITE_GITHUB_APP_NAME } from '$lib/consts';
|
||||
import { application, isPullRequestPermissionsGranted, originalDomain } from '$store';
|
||||
import { onMount } from 'svelte';
|
||||
import TooltipInfo from '$components/TooltipInfo.svelte';
|
||||
import { request } from '$lib/request';
|
||||
import { page, session } from '$app/stores';
|
||||
import { browser } from '$app/env';
|
||||
|
||||
let domainInput;
|
||||
let loading = {
|
||||
previewDeployment: false
|
||||
};
|
||||
let howToActivate = false;
|
||||
const buildpacks = {
|
||||
static: {
|
||||
port: {
|
||||
active: false,
|
||||
number: 80
|
||||
},
|
||||
build: true,
|
||||
start: false
|
||||
},
|
||||
nodejs: {
|
||||
port: {
|
||||
active: true,
|
||||
number: 3000
|
||||
},
|
||||
build: true,
|
||||
start: true
|
||||
},
|
||||
nestjs: {
|
||||
port: {
|
||||
active: true,
|
||||
number: 3000
|
||||
},
|
||||
build: true,
|
||||
start: true
|
||||
},
|
||||
vuejs: {
|
||||
port: {
|
||||
active: false,
|
||||
number: 80
|
||||
},
|
||||
build: true,
|
||||
start: false
|
||||
},
|
||||
nuxtjs: {
|
||||
port: {
|
||||
active: true,
|
||||
number: 3000
|
||||
},
|
||||
build: true,
|
||||
start: true
|
||||
},
|
||||
react: {
|
||||
port: {
|
||||
active: false,
|
||||
number: 80
|
||||
},
|
||||
build: true,
|
||||
start: false
|
||||
},
|
||||
nextjs: {
|
||||
port: {
|
||||
active: true,
|
||||
number: 3000
|
||||
},
|
||||
build: true,
|
||||
start: true
|
||||
},
|
||||
gatsby: {
|
||||
port: {
|
||||
active: true,
|
||||
number: 3000
|
||||
},
|
||||
build: true,
|
||||
start: false
|
||||
},
|
||||
svelte: {
|
||||
port: {
|
||||
active: false,
|
||||
number: 80
|
||||
},
|
||||
build: true,
|
||||
start: false
|
||||
},
|
||||
php: {
|
||||
port: {
|
||||
active: false,
|
||||
number: 80
|
||||
},
|
||||
build: false,
|
||||
start: false
|
||||
},
|
||||
rust: {
|
||||
port: {
|
||||
active: true,
|
||||
number: 3000
|
||||
},
|
||||
build: false,
|
||||
start: false
|
||||
},
|
||||
docker: {
|
||||
port: {
|
||||
active: true,
|
||||
number: 3000
|
||||
},
|
||||
build: false,
|
||||
start: false
|
||||
},
|
||||
python: {
|
||||
port: {
|
||||
active: true,
|
||||
number: 4000
|
||||
},
|
||||
build: false,
|
||||
start: false,
|
||||
custom: true
|
||||
}
|
||||
};
|
||||
async function setPreviewDeployment() {
|
||||
if ($application.general.isPreviewDeploymentEnabled) {
|
||||
const result = window.confirm(
|
||||
"DANGER ZONE! It will delete all PR deployments. It's NOT reversible! Are you sure?"
|
||||
);
|
||||
if (result) {
|
||||
loading.previewDeployment = true;
|
||||
$application.general.isPreviewDeploymentEnabled =
|
||||
!$application.general.isPreviewDeploymentEnabled;
|
||||
if ($page.path !== '/application/new') {
|
||||
const config = await request(`/api/v1/application/config/previewDeployment`, $session, {
|
||||
body: {
|
||||
name: $application.repository.name,
|
||||
organization: $application.repository.organization,
|
||||
branch: $application.repository.branch,
|
||||
isPreviewDeploymentEnabled: $application.general.isPreviewDeploymentEnabled
|
||||
}
|
||||
});
|
||||
}
|
||||
loading.previewDeployment = false;
|
||||
}
|
||||
} else {
|
||||
loading.previewDeployment = true;
|
||||
$application.general.isPreviewDeploymentEnabled =
|
||||
!$application.general.isPreviewDeploymentEnabled;
|
||||
$application.general.pullRequest = 0;
|
||||
if ($page.path !== '/application/new') {
|
||||
const config = await request(`/api/v1/application/config/previewDeployment`, $session, {
|
||||
body: {
|
||||
name: $application.repository.name,
|
||||
organization: $application.repository.organization,
|
||||
branch: $application.repository.branch,
|
||||
isPreviewDeploymentEnabled: $application.general.isPreviewDeploymentEnabled
|
||||
}
|
||||
});
|
||||
}
|
||||
loading.previewDeployment = false;
|
||||
}
|
||||
}
|
||||
function selectBuildPack(event) {
|
||||
if (event.target.innerText === 'React/Preact') {
|
||||
$application.build.pack = 'react';
|
||||
} else {
|
||||
$application.build.pack = event.target.innerText.replace(/\./g, '').toLowerCase();
|
||||
}
|
||||
}
|
||||
async function openGithub() {
|
||||
if (browser) {
|
||||
const config = await request(`https://api.github.com/apps/${VITE_GITHUB_APP_NAME}`, $session);
|
||||
|
||||
let url = `https://github.com/settings/apps/${VITE_GITHUB_APP_NAME}/permissions`;
|
||||
if (config.owner.type === 'Organization') {
|
||||
url = `https://github.com/organizations/${config.owner.login}/settings/apps/${VITE_GITHUB_APP_NAME}/permissions`;
|
||||
}
|
||||
|
||||
const left = screen.width / 2 - 1020 / 2;
|
||||
const top = screen.height / 2 - 618 / 2;
|
||||
const newWindow = open(
|
||||
url,
|
||||
'Permission Update',
|
||||
'resizable=1, scrollbars=1, fullscreen=1, height=1000, width=1220,top=' +
|
||||
top +
|
||||
', left=' +
|
||||
left +
|
||||
', toolbar=0, menubar=0, status=0'
|
||||
);
|
||||
const timer = setInterval(async () => {
|
||||
if (newWindow?.closed) {
|
||||
clearInterval(timer);
|
||||
location.reload();
|
||||
}
|
||||
}, 100);
|
||||
}
|
||||
}
|
||||
onMount(() => {
|
||||
if (!$application.publish.domain) {
|
||||
domainInput.focus();
|
||||
} else {
|
||||
$originalDomain = $application.publish.domain;
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<div>
|
||||
<div class="grid grid-cols-1 text-sm max-w-4xl md:mx-auto mx-6 pb-16 auto-cols-max ">
|
||||
<div class="text-2xl font-bold border-gradient w-40">Build Packs</div>
|
||||
<div class="flex font-bold flex-wrap justify-center pt-10">
|
||||
<div
|
||||
class={$application.build.pack === 'static'
|
||||
? 'buildpack bg-red-500'
|
||||
: 'buildpack hover:border-red-500'}
|
||||
on:click={selectBuildPack}
|
||||
>
|
||||
Static
|
||||
</div>
|
||||
<div
|
||||
class={$application.build.pack === 'nodejs'
|
||||
? 'buildpack bg-emerald-600'
|
||||
: 'buildpack hover:border-emerald-600'}
|
||||
on:click={selectBuildPack}
|
||||
>
|
||||
NodeJS
|
||||
</div>
|
||||
<div
|
||||
class={$application.build.pack === 'vuejs'
|
||||
? 'buildpack bg-green-500'
|
||||
: 'buildpack hover:border-green-500'}
|
||||
on:click={selectBuildPack}
|
||||
>
|
||||
VueJS
|
||||
</div>
|
||||
<div
|
||||
class={$application.build.pack === 'nuxtjs'
|
||||
? 'buildpack bg-green-500'
|
||||
: 'buildpack hover:border-green-500'}
|
||||
on:click={selectBuildPack}
|
||||
>
|
||||
NuxtJS
|
||||
</div>
|
||||
<div
|
||||
class={$application.build.pack === 'react'
|
||||
? 'buildpack bg-gradient-to-r from-blue-500 to-purple-500'
|
||||
: 'buildpack hover:border-blue-500'}
|
||||
on:click={selectBuildPack}
|
||||
>
|
||||
React/Preact
|
||||
</div>
|
||||
<div
|
||||
class={$application.build.pack === 'nextjs'
|
||||
? 'buildpack bg-blue-500'
|
||||
: 'buildpack hover:border-blue-500'}
|
||||
on:click={selectBuildPack}
|
||||
>
|
||||
NextJS
|
||||
</div>
|
||||
<div
|
||||
class={$application.build.pack === 'gatsby'
|
||||
? 'buildpack bg-blue-500'
|
||||
: 'buildpack hover:border-blue-500'}
|
||||
on:click={selectBuildPack}
|
||||
>
|
||||
Gatsby
|
||||
</div>
|
||||
<div
|
||||
class={$application.build.pack === 'svelte'
|
||||
? 'buildpack bg-orange-600'
|
||||
: 'buildpack hover:border-orange-600'}
|
||||
on:click={selectBuildPack}
|
||||
>
|
||||
Svelte
|
||||
</div>
|
||||
<div
|
||||
class={$application.build.pack === 'php'
|
||||
? 'buildpack bg-indigo-500'
|
||||
: 'buildpack hover:border-indigo-500'}
|
||||
on:click={selectBuildPack}
|
||||
>
|
||||
PHP
|
||||
</div>
|
||||
<div
|
||||
class={$application.build.pack === 'rust'
|
||||
? 'buildpack bg-pink-500'
|
||||
: 'buildpack hover:border-pink-500'}
|
||||
on:click={selectBuildPack}
|
||||
>
|
||||
Rust
|
||||
</div>
|
||||
<div
|
||||
class={$application.build.pack === 'nestjs'
|
||||
? 'buildpack bg-red-500'
|
||||
: 'buildpack hover:border-red-500'}
|
||||
on:click={selectBuildPack}
|
||||
>
|
||||
NestJS
|
||||
</div>
|
||||
<div
|
||||
class={$application.build.pack === 'docker'
|
||||
? 'buildpack bg-purple-500'
|
||||
: 'buildpack hover:border-purple-500'}
|
||||
on:click={selectBuildPack}
|
||||
>
|
||||
Docker
|
||||
</div>
|
||||
<div
|
||||
class={$application.build.pack === 'python'
|
||||
? 'buildpack bg-green-500'
|
||||
: 'buildpack hover:border-green-500'}
|
||||
on:click={selectBuildPack}
|
||||
>
|
||||
Python
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-2xl font-bold border-gradient w-52">General settings</div>
|
||||
<div class="grid grid-cols-1 max-w-2xl md:mx-auto mx-6 justify-center items-center pt-10">
|
||||
<div>
|
||||
<ul class="divide-y divide-warmGray-800">
|
||||
<li class="pb-6 flex items-center justify-between">
|
||||
<div class="flex flex-col">
|
||||
<p class="text-base font-bold text-warmGray-100">Preview deployments</p>
|
||||
<p class="text-sm font-medium text-warmGray-400">
|
||||
PR's will be deployed so you could review them easily
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{#if $isPullRequestPermissionsGranted}
|
||||
<div
|
||||
class="relative"
|
||||
class:animate-wiggle={loading.previewDeployment}
|
||||
class:opacity-25={loading.previewDeployment}
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
disabled={loading.previewDeployment}
|
||||
on:click={setPreviewDeployment}
|
||||
aria-pressed="false"
|
||||
class="relative inline-flex flex-shrink-0 h-6 w-11 border-2 border-transparent rounded-full cursor-pointer transition-colors ease-in-out duration-200"
|
||||
class:bg-green-600={$application.general.isPreviewDeploymentEnabled}
|
||||
class:bg-warmGray-700={!$application.general.isPreviewDeploymentEnabled}
|
||||
class:cursor-not-allowed={loading.previewDeployment}
|
||||
>
|
||||
<span class="sr-only">Use setting</span>
|
||||
<span
|
||||
class="pointer-events-none relative inline-block h-5 w-5 rounded-full bg-white shadow transition ease-in-out duration-200 transform"
|
||||
class:translate-x-5={$application.general.isPreviewDeploymentEnabled}
|
||||
class:translate-x-0={!$application.general.isPreviewDeploymentEnabled}
|
||||
>
|
||||
<span
|
||||
class=" ease-in duration-200 absolute inset-0 h-full w-full flex items-center justify-center transition-opacity"
|
||||
class:opacity-0={$application.general.isPreviewDeploymentEnabled}
|
||||
class:opacity-100={!$application.general.isPreviewDeploymentEnabled}
|
||||
aria-hidden="true"
|
||||
>
|
||||
<svg class="bg-white h-3 w-3 text-red-600" fill="none" viewBox="0 0 12 12">
|
||||
<path
|
||||
d="M4 8l2-2m0 0l2-2M6 6L4 4m2 2l2 2"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
<span
|
||||
class="ease-out duration-100 absolute inset-0 h-full w-full flex items-center justify-center transition-opacity"
|
||||
aria-hidden="true"
|
||||
class:opacity-100={$application.general.isPreviewDeploymentEnabled}
|
||||
class:opacity-0={!$application.general.isPreviewDeploymentEnabled}
|
||||
>
|
||||
<svg
|
||||
class="bg-white h-3 w-3 text-green-600"
|
||||
fill="currentColor"
|
||||
viewBox="0 0 12 12"
|
||||
>
|
||||
<path
|
||||
d="M3.707 5.293a1 1 0 00-1.414 1.414l1.414-1.414zM5 8l-.707.707a1 1 0 001.414 0L5 8zm4.707-3.293a1 1 0 00-1.414-1.414l1.414 1.414zm-7.414 2l2 2 1.414-1.414-2-2-1.414 1.414zm3.414 2l4-4-1.414-1.414-4 4 1.414 1.414z"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
</span>
|
||||
</button>
|
||||
{#if loading.previewDeployment}
|
||||
<div class="absolute left-0 bottom-0 -mb-4 -ml-2 text-xs font-bold">
|
||||
{$application.general.isPreviewDeploymentEnabled ? 'Enabling...' : 'Disabling...'}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{:else}
|
||||
<div class="relative">
|
||||
{#if !howToActivate}
|
||||
<button
|
||||
class="button py-2 px-2 bg-warmGray-800 hover:bg-warmGray-700 text-white"
|
||||
on:click={() => (howToActivate = !howToActivate)}>How to active this?</button
|
||||
>
|
||||
{:else}
|
||||
<button
|
||||
class="button py-2 px-2 bg-green-600 hover:bg-green-500 text-white"
|
||||
on:click={openGithub}>Open Github</button
|
||||
>
|
||||
{/if}
|
||||
{#if howToActivate}
|
||||
<div class="absolute right-0 w-64 z-10">
|
||||
<div class="bg-warmGray-800 p-4 my-2 rounded text-white">
|
||||
<div
|
||||
class="absolute right-0 top-0 p-2 my-3 mx-1 text-xs font-bold cursor-pointer hover:bg-warmGray-700"
|
||||
on:click={() => (howToActivate = false)}
|
||||
>
|
||||
X
|
||||
</div>
|
||||
<p class="text-sm font-medium text-warmGray-400">
|
||||
You need to add <span class="text-white">two new permissions</span> to your GitHub
|
||||
App:
|
||||
</p>
|
||||
<br />
|
||||
<p class="text-sm font-medium text-warmGray-400">
|
||||
1. In <span class="text-white">Repository permissions</span>, add
|
||||
<span class="text-white">Read-only</span>
|
||||
access to <span class="text-white">Pull requests</span>.
|
||||
</p>
|
||||
<br />
|
||||
<p class="text-sm font-medium text-warmGray-400">
|
||||
2. In <span class="text-white">Subscribe to events</span> section,
|
||||
<span class="text-white"> check Pull request</span> field.
|
||||
</p>
|
||||
<br />
|
||||
<p class="text-sm font-medium text-warmGray-400">
|
||||
3. You <span class="text-white">receive an email</span> where you need to
|
||||
<span class="text-white">accept the new permissions</span>.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-flow-col gap-2 items-center pb-6">
|
||||
<div class="grid grid-flow-row">
|
||||
<label for="Domain" class="">Domain</label>
|
||||
<input
|
||||
bind:this={domainInput}
|
||||
class="border-2"
|
||||
disabled={$page.path !== '/application/new'}
|
||||
class:cursor-not-allowed={$page.path !== '/application/new'}
|
||||
class:bg-warmGray-900={$page.path !== '/application/new'}
|
||||
class:hover:bg-warmGray-900={$page.path !== '/application/new'}
|
||||
class:placeholder-red-500={$application.publish.domain == null ||
|
||||
$application.publish.domain == ''}
|
||||
class:border-red-500={$application.publish.domain == null ||
|
||||
$application.publish.domain == ''}
|
||||
id="Domain"
|
||||
bind:value={$application.publish.domain}
|
||||
placeholder="eg: coollabs.io (without www)"
|
||||
/>
|
||||
</div>
|
||||
<div class="grid grid-flow-row">
|
||||
<label for="Path"
|
||||
>Path <TooltipInfo
|
||||
label={`Path to deploy your application on your domain. eg: /api means it will be deployed to -> https://${
|
||||
$application.publish.domain || '<yourdomain>'
|
||||
}/api`}
|
||||
/></label
|
||||
>
|
||||
<input
|
||||
id="Path"
|
||||
bind:value={$application.publish.path}
|
||||
disabled={$page.path !== '/application/new'}
|
||||
class:cursor-not-allowed={$page.path !== '/application/new'}
|
||||
class:bg-warmGray-900={$page.path !== '/application/new'}
|
||||
class:hover:bg-warmGray-900={$page.path !== '/application/new'}
|
||||
placeholder="/"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<label for="Port" class:text-warmGray-800={!buildpacks[$application.build.pack].port.active}
|
||||
>Port</label
|
||||
>
|
||||
<input
|
||||
disabled={!buildpacks[$application.build.pack].port.active}
|
||||
id="Port"
|
||||
class:bg-warmGray-900={!buildpacks[$application.build.pack].port.active}
|
||||
class:text-warmGray-900={!buildpacks[$application.build.pack].port.active}
|
||||
class:placeholder-warmGray-800={!buildpacks[$application.build.pack].port.active}
|
||||
class:hover:bg-warmGray-900={!buildpacks[$application.build.pack].port.active}
|
||||
class:cursor-not-allowed={!buildpacks[$application.build.pack].port.active}
|
||||
bind:value={$application.publish.port}
|
||||
placeholder={buildpacks[$application.build.pack].port.number}
|
||||
/>
|
||||
<div class="grid grid-flow-col gap-2 items-center pt-6 pb-12">
|
||||
<div class="grid grid-flow-row">
|
||||
<label for="baseDir"
|
||||
>Base Directory <TooltipInfo
|
||||
label="The directory to use as base for every command (could be useful if you have a monorepo)."
|
||||
/></label
|
||||
>
|
||||
<input id="baseDir" bind:value={$application.build.directory} placeholder="eg: sourcedir" />
|
||||
</div>
|
||||
<div class="grid grid-flow-row">
|
||||
<label for="publishDir"
|
||||
>Publish Directory <TooltipInfo
|
||||
label="The directory to deploy after running the build command. eg: dist, _site, public."
|
||||
/></label
|
||||
>
|
||||
<input
|
||||
id="publishDir"
|
||||
bind:value={$application.publish.directory}
|
||||
placeholder="eg: dist, _site, public"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-2xl font-bold w-40 border-gradient">Commands</div>
|
||||
<div class=" max-w-2xl md:mx-auto mx-6 justify-center items-center pt-10 pb-32">
|
||||
<div class="grid grid-flow-col gap-2 items-center">
|
||||
<div class="grid grid-flow-row">
|
||||
{#if $application.build.pack === 'python'}
|
||||
<label for="ModulePackageName"
|
||||
>Module/Package Name<TooltipInfo
|
||||
label="The module/package name to start (eg: the entry filename [main], without the py extension. See gunicorn.org for more details)"
|
||||
/>
|
||||
</label>
|
||||
|
||||
<input
|
||||
class="mb-6"
|
||||
id="ModulePackageName"
|
||||
bind:value={$application.build.command.python.module}
|
||||
placeholder="main"
|
||||
/>
|
||||
<label for="ApplicationInstance"
|
||||
>Application Instance<TooltipInfo
|
||||
label="The instance name (the main function name. See gunicorn.org for more details)"
|
||||
/>
|
||||
</label>
|
||||
|
||||
<input
|
||||
class="mb-6"
|
||||
id="ApplicationInstance"
|
||||
bind:value={$application.build.command.python.instance}
|
||||
placeholder="app"
|
||||
/>
|
||||
{:else}
|
||||
<label
|
||||
for="installCommand"
|
||||
class:text-warmGray-800={!buildpacks[$application.build.pack].build}
|
||||
>Install Command <TooltipInfo
|
||||
label="Command to run for installing dependencies. eg: yarn install"
|
||||
/>
|
||||
</label>
|
||||
|
||||
<input
|
||||
class="mb-6"
|
||||
class:bg-warmGray-900={!buildpacks[$application.build.pack].build}
|
||||
class:text-warmGray-900={!buildpacks[$application.build.pack].build}
|
||||
class:placeholder-warmGray-800={!buildpacks[$application.build.pack].build}
|
||||
class:hover:bg-warmGray-900={!buildpacks[$application.build.pack].build}
|
||||
class:cursor-not-allowed={!buildpacks[$application.build.pack].build}
|
||||
id="installCommand"
|
||||
bind:value={$application.build.command.installation}
|
||||
placeholder="eg: yarn install"
|
||||
/>
|
||||
<label
|
||||
for="buildCommand"
|
||||
class:text-warmGray-800={!buildpacks[$application.build.pack].build}
|
||||
>Build Command <TooltipInfo
|
||||
label="Command to run for building your application. If empty, no build phase initiated in the deploy process."
|
||||
/></label
|
||||
>
|
||||
<input
|
||||
class="mb-6"
|
||||
class:bg-warmGray-900={!buildpacks[$application.build.pack].build}
|
||||
class:text-warmGray-900={!buildpacks[$application.build.pack].build}
|
||||
class:placeholder-warmGray-800={!buildpacks[$application.build.pack].build}
|
||||
class:hover:bg-warmGray-900={!buildpacks[$application.build.pack].build}
|
||||
class:cursor-not-allowed={!buildpacks[$application.build.pack].build}
|
||||
id="buildCommand"
|
||||
bind:value={$application.build.command.build}
|
||||
placeholder="eg: yarn build"
|
||||
/>
|
||||
<label
|
||||
for="startCommand"
|
||||
class:text-warmGray-800={!buildpacks[$application.build.pack].start}
|
||||
>Start Command <TooltipInfo
|
||||
label="Command to start the application. eg: yarn start"
|
||||
/></label
|
||||
>
|
||||
<input
|
||||
class="mb-6"
|
||||
class:bg-warmGray-900={!buildpacks[$application.build.pack].start}
|
||||
class:text-warmGray-900={!buildpacks[$application.build.pack].start}
|
||||
class:placeholder-warmGray-800={!buildpacks[$application.build.pack].start}
|
||||
class:hover:bg-warmGray-900={!buildpacks[$application.build.pack].start}
|
||||
class:cursor-not-allowed={!buildpacks[$application.build.pack].start}
|
||||
id="startcommand"
|
||||
bind:value={$application.build.command.start}
|
||||
placeholder="eg: yarn start"
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style lang="postcss">
|
||||
.buildpack {
|
||||
@apply px-6 py-2 mx-2 my-2 bg-warmGray-800 w-48 ease-in-out hover:scale-105 text-center rounded border-2 border-transparent border-dashed cursor-pointer transition duration-100;
|
||||
}
|
||||
</style>
|
@ -1,102 +0,0 @@
|
||||
<script>
|
||||
import { browser } from '$app/env';
|
||||
import { goto } from '$app/navigation';
|
||||
import { onDestroy, onMount } from 'svelte';
|
||||
import { session } from '$app/stores';
|
||||
import { request } from '$lib/request';
|
||||
import { toast } from '@zerodevx/svelte-toast';
|
||||
import { application, prApplication } from '$store';
|
||||
let loadPRDeployments = null;
|
||||
onMount(async () => {
|
||||
await getPRDeployments();
|
||||
loadPRDeployments = setInterval(async () => {
|
||||
await getPRDeployments();
|
||||
}, 1000);
|
||||
});
|
||||
onDestroy(() => {
|
||||
clearInterval(loadPRDeployments);
|
||||
});
|
||||
async function getPRDeployments() {
|
||||
const { configuration } = await request(`/api/v1/application/config`, $session, {
|
||||
body: {
|
||||
nickname: $application.general.nickname
|
||||
}
|
||||
});
|
||||
$prApplication = configuration.filter((c) => c.general.pullRequest !== 0);
|
||||
}
|
||||
async function removePR(prConfiguration) {
|
||||
const result = window.confirm("DANGER ZONE! It's NOT reversible! Are you sure?");
|
||||
if (result) {
|
||||
await request(`/api/v1/application/remove`, $session, {
|
||||
body: {
|
||||
nickname: prConfiguration.general.nickname
|
||||
}
|
||||
});
|
||||
|
||||
browser && toast.push('PR deployment removed.');
|
||||
const { configuration } = await request(`/api/v1/application/config`, $session, {
|
||||
body: {
|
||||
nickname: prConfiguration.general.nickname
|
||||
}
|
||||
});
|
||||
$prApplication = configuration.filter((c) => c.general.pullRequest !== 0);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="text-2xl font-bold border-gradient w-48">Pull Requests</div>
|
||||
<div class="text-center pt-4">
|
||||
{#if $prApplication.length > 0}
|
||||
<div class="py-4 ">
|
||||
{#each $prApplication as pr}
|
||||
<div class="flex space-x-4 justify-center items-center">
|
||||
<div class="text-left font-bold tracking-tight ">
|
||||
{pr.publish.domain}
|
||||
</div>
|
||||
<a
|
||||
target="_blank"
|
||||
class="icon mx-2 "
|
||||
href={'https://' + pr.publish.domain + pr.publish.path}
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="h-5 w-5"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14"
|
||||
/>
|
||||
</svg></a
|
||||
>
|
||||
<!-- <div class="flex-1" /> -->
|
||||
<button
|
||||
class="icon hover:text-red-500 hover:bg-warmGray-800"
|
||||
on:click={() => removePR(pr)}
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="h-5 w-5"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
{:else}
|
||||
<div class="font-bold text-center">No PR deployments found</div>
|
||||
{/if}
|
||||
</div>
|
@ -1,128 +0,0 @@
|
||||
<script>
|
||||
import { application } from '$store';
|
||||
import BuildEnv from '../BuildEnv.svelte';
|
||||
|
||||
let secret = {
|
||||
name: null,
|
||||
value: null,
|
||||
isBuild: false
|
||||
};
|
||||
let foundSecret = null;
|
||||
async function saveSecret() {
|
||||
if (secret.name && secret.value) {
|
||||
const found = $application.publish.secrets.find((s) => s.name === secret.name);
|
||||
if (!found) {
|
||||
$application.publish.secrets = [
|
||||
...$application.publish.secrets,
|
||||
{
|
||||
name: secret.name,
|
||||
value: secret.value,
|
||||
isBuild: secret.isBuild
|
||||
}
|
||||
];
|
||||
secret = {
|
||||
name: null,
|
||||
value: null,
|
||||
isBuild: false
|
||||
};
|
||||
} else {
|
||||
foundSecret = found;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function removeSecret(name) {
|
||||
foundSecret = null;
|
||||
$application.publish.secrets = [...$application.publish.secrets.filter((s) => s.name !== name)];
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="text-2xl font-bold border-gradient w-24">Secrets</div>
|
||||
<div class="max-w-3xl mx-auto text-center pt-4">
|
||||
<div class="flex space-x-4">
|
||||
<div class="grid grid-flow-row">
|
||||
<label for="secretName">Secret Name</label>
|
||||
<input
|
||||
id="secretName"
|
||||
bind:value={secret.name}
|
||||
placeholder="Name"
|
||||
class="w-64 border-2 border-transparent"
|
||||
/>
|
||||
</div>
|
||||
<div class="grid grid-flow-row">
|
||||
<label for="secretValue">Secret Value</label>
|
||||
<input
|
||||
id="secretValue"
|
||||
bind:value={secret.value}
|
||||
placeholder="Value"
|
||||
class="w-64 border-2 border-transparent"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-flow-row">
|
||||
<label for="buildVariable">Is build variable?</label>
|
||||
<div class="mt-2 w-full">
|
||||
<BuildEnv {secret} />
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-6">
|
||||
<button class="icon hover:text-green-500" on:click={saveSecret}>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="h-6 w-6"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M12 6v6m0 0v6m0-6h6m-6 0H6"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{#if $application.publish.secrets.length > 0}
|
||||
<div class="pt-1">
|
||||
{#each $application.publish.secrets as secret}
|
||||
<div class="flex space-x-4 space-y-2">
|
||||
<input
|
||||
id={secret.name}
|
||||
value={secret.name}
|
||||
disabled
|
||||
class="border-2 bg-transparent border-transparent w-64 hover:bg-transparent"
|
||||
class:border-red-600={foundSecret && foundSecret.name === secret.name}
|
||||
/>
|
||||
<input
|
||||
id={secret.createdAt}
|
||||
value="SAVED"
|
||||
disabled
|
||||
class="border-2 bg-transparent border-transparent w-64 hover:bg-transparent"
|
||||
/>
|
||||
<div class="flex justify-center items-center px-12">
|
||||
<BuildEnv {secret} readOnly />
|
||||
</div>
|
||||
<button class="icon hover:text-red-500" on:click={() => removeSecret(secret.name)}>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="h-6 w-6"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
@ -1,46 +0,0 @@
|
||||
<script>
|
||||
import { page } from '$app/stores';
|
||||
|
||||
export let loading, branches;
|
||||
import { application } from '$store';
|
||||
import Select from 'svelte-select';
|
||||
|
||||
const selectedValue = $page.path !== '/application/new' && $application.repository.branch;
|
||||
|
||||
function handleSelect(event) {
|
||||
$application.repository.branch = null;
|
||||
setTimeout(() => {
|
||||
$application.repository.branch = event.detail.value;
|
||||
}, 1);
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if loading}
|
||||
<div class="grid grid-cols-1">
|
||||
<label for="branch">Branch</label>
|
||||
<div class="repository-select-search col-span-2">
|
||||
<Select
|
||||
containerClasses="w-full border-none bg-transparent"
|
||||
placeholder="Loading branches..."
|
||||
isDisabled
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{:else}
|
||||
<div class="grid grid-cols-1">
|
||||
<label for="branch">Branch</label>
|
||||
<div class="repository-select-search col-span-2">
|
||||
<Select
|
||||
containerClasses="w-full border-none bg-transparent"
|
||||
on:select={handleSelect}
|
||||
{selectedValue}
|
||||
isClearable={false}
|
||||
items={branches.map((b) => ({ label: b.name, value: b.name }))}
|
||||
showIndicator={$page.path === '/application/new'}
|
||||
noOptionsMessage="No branches found"
|
||||
placeholder="Select a branch"
|
||||
isDisabled={$page.path !== '/application/new'}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
@ -1,55 +0,0 @@
|
||||
<script>
|
||||
export let secret;
|
||||
export let readOnly = false;
|
||||
function isBuildSet() {
|
||||
if (!readOnly) secret.isBuild = !secret.isBuild;
|
||||
}
|
||||
</script>
|
||||
|
||||
<button
|
||||
id="buildVariable"
|
||||
type="button"
|
||||
aria-pressed="false"
|
||||
on:click={isBuildSet}
|
||||
class="relative inline-flex flex-shrink-0 h-6 w-11 border-2 border-transparent rounded-full cursor-pointer transition-colors ease-in-out duration-100"
|
||||
class:bg-green-600={secret.isBuild}
|
||||
class:bg-warmGray-700={!secret.isBuild}
|
||||
class:opacity-50={readOnly}
|
||||
class:cursor-not-allowed={readOnly}
|
||||
>
|
||||
<span class="sr-only">Use setting</span>
|
||||
<span
|
||||
class="pointer-events-none relative inline-block h-5 w-5 rounded-full bg-white shadow transition ease-in-out duration-200 transform"
|
||||
class:translate-x-5={secret.isBuild}
|
||||
class:translate-x-0={!secret.isBuild}
|
||||
>
|
||||
<span
|
||||
class=" ease-in duration-200 absolute inset-0 h-full w-full flex items-center justify-center transition-opacity"
|
||||
class:opacity-0={secret.isBuild}
|
||||
class:opacity-100={!secret.isBuild}
|
||||
aria-hidden="true"
|
||||
>
|
||||
<svg class="bg-white h-3 w-3 text-red-600" fill="none" viewBox="0 0 12 12">
|
||||
<path
|
||||
d="M4 8l2-2m0 0l2-2M6 6L4 4m2 2l2 2"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
<span
|
||||
class="ease-out duration-100 absolute inset-0 h-full w-full flex items-center justify-center transition-opacity"
|
||||
aria-hidden="true"
|
||||
class:opacity-100={secret.isBuild}
|
||||
class:opacity-0={!secret.isBuild}
|
||||
>
|
||||
<svg class="bg-white h-3 w-3 text-green-600" fill="currentColor" viewBox="0 0 12 12">
|
||||
<path
|
||||
d="M3.707 5.293a1 1 0 00-1.414 1.414l1.414-1.414zM5 8l-.707.707a1 1 0 001.414 0L5 8zm4.707-3.293a1 1 0 00-1.414-1.414l1.414 1.414zm-7.414 2l2 2 1.414-1.414-2-2-1.414 1.414zm3.414 2l4-4-1.414-1.414-4 4 1.414 1.414z"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
</span>
|
||||
</button>
|
@ -1,198 +0,0 @@
|
||||
<script lang="ts">
|
||||
import { goto } from '$app/navigation';
|
||||
import { request } from '$lib/request';
|
||||
import { session } from '$app/stores';
|
||||
import {
|
||||
githubRepositories,
|
||||
application,
|
||||
githubInstallations,
|
||||
prApplication,
|
||||
initConf,
|
||||
isPullRequestPermissionsGranted
|
||||
} from '$store';
|
||||
|
||||
import { fade } from 'svelte/transition';
|
||||
import Loading from '$components/Loading.svelte';
|
||||
|
||||
import { browser } from '$app/env';
|
||||
import Branches from '$components/Application/Branches.svelte';
|
||||
import Tabs from '$components/Application/Tabs.svelte';
|
||||
import Repositories from '$components/Application/Repositories.svelte';
|
||||
import Login from '$components/Application/Login.svelte';
|
||||
import { dashify } from '$lib/common';
|
||||
let loading = {
|
||||
github: false,
|
||||
branches: false
|
||||
};
|
||||
let branches = [];
|
||||
let relogin = false;
|
||||
let permissions = {};
|
||||
|
||||
async function getGithubRepos(id, page) {
|
||||
return await request(
|
||||
`https://api.github.com/user/installations/${id}/repositories?per_page=100&page=${page}`,
|
||||
$session
|
||||
);
|
||||
}
|
||||
async function loadGithubRepositories(force) {
|
||||
if ($githubRepositories.length > 0 && !force) {
|
||||
$application.github.installation.id = $githubInstallations.id;
|
||||
$application.github.app.id = $githubInstallations.app_id;
|
||||
const foundRepositoryOnGithub = $githubRepositories.find(
|
||||
(r) =>
|
||||
r.full_name === `${$application.repository.organization}/${$application.repository.name}`
|
||||
);
|
||||
|
||||
if (foundRepositoryOnGithub) {
|
||||
$application.repository.id = foundRepositoryOnGithub.id;
|
||||
$application.repository.organization = foundRepositoryOnGithub.owner.login;
|
||||
$application.repository.name = foundRepositoryOnGithub.name;
|
||||
}
|
||||
return;
|
||||
} else {
|
||||
loading.github = true;
|
||||
let installations = [];
|
||||
try {
|
||||
const data = await request('https://api.github.com/user/installations', $session);
|
||||
installations = data.installations;
|
||||
} catch (error) {
|
||||
relogin = true;
|
||||
console.log(error);
|
||||
return false;
|
||||
}
|
||||
if (installations.length === 0) {
|
||||
loading.github = false;
|
||||
return false;
|
||||
}
|
||||
$application.github.installation.id = installations[0].id;
|
||||
$application.github.app.id = installations[0].app_id;
|
||||
$githubInstallations = installations[0];
|
||||
|
||||
try {
|
||||
let page = 1;
|
||||
let userRepos = 0;
|
||||
const data = await getGithubRepos($application.github.installation.id, page);
|
||||
$githubRepositories = $githubRepositories.concat(data.repositories);
|
||||
userRepos = data.total_count;
|
||||
if (userRepos > $githubRepositories.length) {
|
||||
while (userRepos > $githubRepositories.length) {
|
||||
page = page + 1;
|
||||
const repos = await getGithubRepos($application.github.installation.id, page);
|
||||
$githubRepositories = $githubRepositories.concat(repos.repositories);
|
||||
}
|
||||
}
|
||||
const foundRepositoryOnGithub = $githubRepositories.find(
|
||||
(r) =>
|
||||
r.full_name ===
|
||||
`${$application.repository.organization}/${$application.repository.name}`
|
||||
);
|
||||
if (foundRepositoryOnGithub) {
|
||||
$application.repository.id = foundRepositoryOnGithub.id;
|
||||
await loadBranches();
|
||||
}
|
||||
} catch (error) {
|
||||
return false;
|
||||
} finally {
|
||||
loading.github = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
async function loadBranches() {
|
||||
loading.branches = true;
|
||||
const selectedRepository = $githubRepositories.find((r) => r.id === $application.repository.id);
|
||||
|
||||
if (selectedRepository) {
|
||||
$application.repository.organization = selectedRepository.owner.login;
|
||||
$application.repository.name = selectedRepository.name;
|
||||
}
|
||||
|
||||
branches = await request(
|
||||
`https://api.github.com/repos/${$application.repository.organization}/${$application.repository.name}/branches`,
|
||||
$session
|
||||
);
|
||||
loading.branches = false;
|
||||
await loadPermissions();
|
||||
}
|
||||
async function loadPermissions() {
|
||||
const config = await request(
|
||||
`https://api.github.com/apps/${dashify(import.meta.env.VITE_GITHUB_APP_NAME)}`,
|
||||
$session
|
||||
);
|
||||
if (config.permissions['pull_requests'] && config.events.includes('pull_request')) {
|
||||
$isPullRequestPermissionsGranted = true;
|
||||
}
|
||||
}
|
||||
async function modifyGithubAppConfig() {
|
||||
if (browser) {
|
||||
const left = screen.width / 2 - 1020 / 2;
|
||||
const top = screen.height / 2 - 618 / 2;
|
||||
const newWindow = open(
|
||||
`https://github.com/apps/${dashify(
|
||||
import.meta.env.VITE_GITHUB_APP_NAME
|
||||
)}/installations/new`,
|
||||
'Install App',
|
||||
'resizable=1, scrollbars=1, fullscreen=0, height=1000, width=1020,top=' +
|
||||
top +
|
||||
', left=' +
|
||||
left +
|
||||
', toolbar=0, menubar=0, status=0'
|
||||
);
|
||||
const timer = setInterval(async () => {
|
||||
if (newWindow?.closed) {
|
||||
clearInterval(timer);
|
||||
loading.github = true;
|
||||
|
||||
if ($application.repository.name) {
|
||||
try {
|
||||
const { configuration } = await request(`/api/v1/application/config`, $session, {
|
||||
body: {
|
||||
name: $application.repository.name,
|
||||
organization: $application.repository.organization,
|
||||
branch: $application.repository.branch
|
||||
}
|
||||
});
|
||||
|
||||
$prApplication = configuration.filter((c) => c.general.pullRequest !== 0);
|
||||
$application = configuration.find((c) => c.general.pullRequest === 0);
|
||||
$initConf = JSON.parse(JSON.stringify($application));
|
||||
} catch (error) {
|
||||
browser && goto('/dashboard/applications', { replaceState: true });
|
||||
}
|
||||
}
|
||||
|
||||
branches = [];
|
||||
$githubRepositories = [];
|
||||
await loadGithubRepositories(true);
|
||||
}
|
||||
}, 100);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<div in:fade={{ duration: 100 }}>
|
||||
{#if relogin}
|
||||
<Login />
|
||||
{:else}
|
||||
{#await loadGithubRepositories(false)}
|
||||
<Loading github githubLoadingText="Loading repositories..." />
|
||||
{:then}
|
||||
{#if loading.github}
|
||||
<Loading github githubLoadingText="Loading repositories..." />
|
||||
{:else}
|
||||
<div class="space-y-2 max-w-4xl mx-auto px-6" in:fade={{ duration: 100 }}>
|
||||
<Repositories
|
||||
on:loadBranches={loadBranches}
|
||||
on:modifyGithubAppConfig={modifyGithubAppConfig}
|
||||
/>
|
||||
{#if $application.repository.organization}
|
||||
<Branches loading={loading.branches} {branches} />
|
||||
{/if}
|
||||
|
||||
{#if $application.repository.branch}
|
||||
<Tabs />
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
{/await}
|
||||
{/if}
|
||||
</div>
|
@ -1,42 +0,0 @@
|
||||
<script>
|
||||
function login() {
|
||||
const left = screen.width / 2 - 1020 / 2;
|
||||
const top = screen.height / 2 - 618 / 2;
|
||||
const newWindow = open(
|
||||
`https://github.com/login/oauth/authorize?client_id=${
|
||||
import.meta.env.VITE_GITHUB_APP_CLIENTID
|
||||
}`,
|
||||
'Authenticate',
|
||||
'resizable=1, scrollbars=1, fullscreen=0, height=618, width=1020,top=' +
|
||||
top +
|
||||
', left=' +
|
||||
left +
|
||||
', toolbar=0, menubar=0, status=0'
|
||||
);
|
||||
const timer = setInterval(() => {
|
||||
if (newWindow?.closed) {
|
||||
clearInterval(timer);
|
||||
location.reload()
|
||||
}
|
||||
}, 100);
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="text-center text-white">
|
||||
<div class="text-2xl font-bold text-center pb-4">Choose your Git provider</div>
|
||||
<button on:click={login} class="hover:scale-110 duration-100 transition">
|
||||
<svg
|
||||
class="w-16"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
><path
|
||||
d="M9 19c-5 1.5-5-2.5-7-3m14 6v-3.87a3.37 3.37 0 0 0-.94-2.61c3.14-.35 6.44-1.54 6.44-7A5.44 5.44 0 0 0 20 4.77 5.07 5.07 0 0 0 19.91 1S18.73.65 16 2.48a13.38 13.38 0 0 0-7 0C6.27.65 5.09 1 5.09 1A5.07 5.07 0 0 0 5 4.77a5.44 5.44 0 0 0-1.5 3.78c0 5.42 3.3 6.61 6.44 7A3.37 3.37 0 0 0 9 18.13V22"
|
||||
/></svg
|
||||
>
|
||||
</button>
|
||||
</div>
|
@ -1,173 +0,0 @@
|
||||
<script>
|
||||
import { application, initialApplication, initConf, originalDomain } from '$store';
|
||||
import { onDestroy } from 'svelte';
|
||||
import { toast } from '@zerodevx/svelte-toast';
|
||||
import Tooltip from '$components/Tooltip.svelte';
|
||||
import { request } from '$lib/request';
|
||||
import { page, session } from '$app/stores';
|
||||
import { goto } from '$app/navigation';
|
||||
import { browser } from '$app/env';
|
||||
async function removeApplication() {
|
||||
const result = window.confirm(
|
||||
"DANGER ZONE! It will delete all deployments, including PR's. It's NOT reversible! Are you sure?"
|
||||
);
|
||||
if (result) {
|
||||
await request(`/api/v1/application/remove`, $session, {
|
||||
body: {
|
||||
nickname: $application.general.nickname
|
||||
}
|
||||
});
|
||||
|
||||
browser && toast.push('Application removed.');
|
||||
$application = JSON.parse(JSON.stringify(initialApplication));
|
||||
browser && goto(`/dashboard/applications`, { replaceState: true });
|
||||
}
|
||||
}
|
||||
|
||||
onDestroy(() => {
|
||||
$application = JSON.parse(JSON.stringify(initialApplication));
|
||||
});
|
||||
|
||||
async function deploy() {
|
||||
try {
|
||||
browser && toast.push('Checking configuration.');
|
||||
await request(`/api/v1/application/check`, $session, {
|
||||
body: $application
|
||||
});
|
||||
const { nickname, name, deployId } = await request(`/api/v1/application/deploy`, $session, {
|
||||
body: $application
|
||||
});
|
||||
$application.general.nickname = nickname;
|
||||
$application.build.container.name = name;
|
||||
$application.general.deployId = deployId;
|
||||
$initConf = JSON.parse(JSON.stringify($application));
|
||||
if (browser) {
|
||||
toast.push('Application deployment queued.');
|
||||
goto(`/application/${$application.general.nickname}/logs/${$application.general.deployId}`, {
|
||||
replaceState: true
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
// browser && toast.push(error.error || error || 'Ooops something went wrong.');
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<nav class="flex text-white justify-end items-center m-4 fixed right-0 top-0 space-x-4 z-50">
|
||||
<Tooltip position="bottom" label="Deploy">
|
||||
<button
|
||||
disabled={$application.publish.domain === '' || $application.publish.domain === null}
|
||||
class:cursor-not-allowed={$application.publish.domain === '' ||
|
||||
$application.publish.domain === null}
|
||||
class:hover:bg-green-500={$application.publish.domain}
|
||||
class:bg-green-600={$application.publish.domain}
|
||||
class:hover:bg-transparent={!$application.publish.domain && $page.path === '/application/new'}
|
||||
class:text-warmGray-700={$application.publish.domain === '' ||
|
||||
$application.publish.domain === null}
|
||||
class="icon"
|
||||
on:click={deploy}
|
||||
>
|
||||
<svg
|
||||
class="w-6"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
><polyline points="16 16 12 12 8 16" /><line x1="12" y1="12" x2="12" y2="21" /><path
|
||||
d="M20.39 18.39A5 5 0 0 0 18 9h-1.26A8 8 0 1 0 3 16.3"
|
||||
/><polyline points="16 16 12 12 8 16" /></svg
|
||||
>
|
||||
</button>
|
||||
</Tooltip>
|
||||
<Tooltip position="bottom" label="Delete">
|
||||
<button
|
||||
disabled={$application.publish.domain === '' ||
|
||||
$application.publish.domain === null ||
|
||||
$page.path === '/application/new'}
|
||||
class:cursor-not-allowed={$application.publish.domain === '' ||
|
||||
$application.publish.domain === null ||
|
||||
$page.path === '/application/new'}
|
||||
class:hover:text-red-500={$application.publish.domain && $page.path !== '/application/new'}
|
||||
class:hover:bg-warmGray-700={$application.publish.domain && $page.path !== '/application/new'}
|
||||
class:hover:bg-transparent={$page.path === '/application/new'}
|
||||
class:text-warmGray-700={$application.publish.domain === '' ||
|
||||
$application.publish.domain === null ||
|
||||
$page.path === '/application/new'}
|
||||
class="icon"
|
||||
on:click={removeApplication}
|
||||
>
|
||||
<svg
|
||||
class="w-6"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
</Tooltip>
|
||||
<div class="border border-warmGray-700 h-8" />
|
||||
<Tooltip position="bottom" label="Logs">
|
||||
<button
|
||||
class="icon"
|
||||
class:text-warmGray-700={$page.path === '/application/new'}
|
||||
disabled={$page.path === '/application/new'}
|
||||
class:hover:text-blue-400={$page.path !== '/application/new'}
|
||||
class:hover:bg-transparent={$page.path === '/application/new'}
|
||||
class:cursor-not-allowed={$page.path === '/application/new'}
|
||||
class:text-blue-400={/logs\/*/.test($page.path)}
|
||||
class:bg-warmGray-700={/logs\/*/.test($page.path)}
|
||||
on:click={() => goto(`/application/${$application.general.nickname}/logs`)}
|
||||
>
|
||||
<svg
|
||||
class="w-6"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M12 6.253v13m0-13C10.832 5.477 9.246 5 7.5 5S4.168 5.477 3 6.253v13C4.168 18.477 5.754 18 7.5 18s3.332.477 4.5 1.253m0-13C13.168 5.477 14.754 5 16.5 5c1.747 0 3.332.477 4.5 1.253v13C19.832 18.477 18.247 18 16.5 18c-1.746 0-3.332.477-4.5 1.253"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
</Tooltip>
|
||||
<Tooltip position="bottom-left" label="Configuration">
|
||||
<button
|
||||
class="icon hover:text-yellow-400"
|
||||
disabled={$page.path === '/application/new'}
|
||||
class:text-yellow-400={$page.path.endsWith('configuration') ||
|
||||
$page.path === '/application/new'}
|
||||
class:bg-warmGray-700={$page.path.endsWith('configuration') ||
|
||||
$page.path === '/application/new'}
|
||||
on:click={() => goto(`/application/${$application.general.nickname}/configuration`)}
|
||||
>
|
||||
<svg
|
||||
class="w-6"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M12 6V4m0 2a2 2 0 100 4m0-4a2 2 0 110 4m-6 8a2 2 0 100-4m0 4a2 2 0 110-4m0 4v2m0-6V4m6 6v10m6-2a2 2 0 100-4m0 4a2 2 0 110-4m0 4v2m0-6V4"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
</Tooltip>
|
||||
</nav>
|
@ -1,54 +0,0 @@
|
||||
<script>
|
||||
import { createEventDispatcher } from 'svelte';
|
||||
import { application, githubRepositories } from '$store';
|
||||
import Select from 'svelte-select';
|
||||
import { page } from '$app/stores';
|
||||
function handleSelect(event) {
|
||||
$application.build.pack = 'static';
|
||||
$application.repository.id = parseInt(event.detail.value, 10);
|
||||
$application.repository.branch = null
|
||||
dispatch('loadBranches');
|
||||
}
|
||||
const path = $page.path === '/application/new';
|
||||
let items = $githubRepositories.map((repo) => ({
|
||||
label: `${repo.owner.login}/${repo.name}`,
|
||||
value: repo.id.toString()
|
||||
}));
|
||||
|
||||
const selectedValue =
|
||||
!path && `${$application.repository.organization}/${$application.repository.name}`;
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
const modifyGithubAppConfig = () => dispatch('modifyGithubAppConfig');
|
||||
</script>
|
||||
|
||||
<div class="grid grid-cols-1 pt-4">
|
||||
{#if $githubRepositories.length !== 0}
|
||||
<label for="repository">Organization / Repository</label>
|
||||
<div class="grid grid-cols-3 ">
|
||||
<div class="repository-select-search col-span-2">
|
||||
<Select
|
||||
isFocused="{true}"
|
||||
containerClasses="w-full border-none bg-transparent"
|
||||
on:select={handleSelect}
|
||||
{selectedValue}
|
||||
isClearable={false}
|
||||
{items}
|
||||
showIndicator={path}
|
||||
noOptionsMessage="No Repositories found"
|
||||
placeholder="Select a Repository"
|
||||
isDisabled={!path}
|
||||
/>
|
||||
</div>
|
||||
<button
|
||||
class="button col-span-1 ml-2 bg-warmGray-800 hover:bg-warmGray-700 text-white"
|
||||
on:click={modifyGithubAppConfig}>Configure on Github</button
|
||||
>
|
||||
</div>
|
||||
{:else}
|
||||
<button
|
||||
class="button col-span-1 ml-2 bg-warmGray-800 hover:bg-warmGray-700 text-white py-2"
|
||||
on:click={modifyGithubAppConfig}>Add repositories on Github</button
|
||||
>
|
||||
{/if}
|
||||
</div>
|
@ -1,131 +0,0 @@
|
||||
<script>
|
||||
import { toast } from '@zerodevx/svelte-toast';
|
||||
import templates from '$lib/api/applications/packs/templates';
|
||||
import { application, dashboard, initConf, prApplication } from '$store';
|
||||
import General from '$components/Application/ActiveTab/General.svelte';
|
||||
import Secrets from '$components/Application/ActiveTab/Secrets.svelte';
|
||||
import Loading from '$components/Loading.svelte';
|
||||
import { goto } from '$app/navigation';
|
||||
import { page, session } from '$app/stores';
|
||||
import { request } from '$lib/request';
|
||||
import { browser } from '$app/env';
|
||||
import PullRequests from './ActiveTab/PullRequests.svelte';
|
||||
|
||||
let activeTab = {
|
||||
general: true,
|
||||
secrets: false,
|
||||
pullRequests: false
|
||||
};
|
||||
function activateTab(tab) {
|
||||
if (activeTab.hasOwnProperty(tab)) {
|
||||
activeTab = {
|
||||
general: false,
|
||||
pullRequests: false,
|
||||
secrets: false
|
||||
};
|
||||
activeTab[tab] = true;
|
||||
}
|
||||
}
|
||||
async function load() {
|
||||
if ($page.path === '/application/new') {
|
||||
try {
|
||||
const dir = await request(
|
||||
`https://api.github.com/repos/${$application.repository.organization}/${$application.repository.name}/contents/?ref=${$application.repository.branch}`,
|
||||
$session
|
||||
);
|
||||
const packageJson = dir.find((f) => f.type === 'file' && f.name === 'package.json');
|
||||
const Dockerfile = dir.find((f) => f.type === 'file' && f.name === 'Dockerfile');
|
||||
const CargoToml = dir.find((f) => f.type === 'file' && f.name === 'Cargo.toml');
|
||||
const requirementsTXT = dir.find((f) => f.type === 'file' && f.name === 'requirements.txt');
|
||||
if (packageJson) {
|
||||
const { content } = await request(packageJson.git_url, $session);
|
||||
const packageJsonContent = JSON.parse(atob(content));
|
||||
const checkPackageJSONContents = (dep) => {
|
||||
return (
|
||||
packageJsonContent?.dependencies?.hasOwnProperty(dep) ||
|
||||
packageJsonContent?.devDependencies?.hasOwnProperty(dep)
|
||||
);
|
||||
};
|
||||
Object.keys(templates).map((dep) => {
|
||||
if (checkPackageJSONContents(dep)) {
|
||||
const config = templates[dep];
|
||||
$application.build.pack = config.pack;
|
||||
if (config.installation) {
|
||||
$application.build.command.installation = config.installation;
|
||||
}
|
||||
if (config.start) {
|
||||
$application.build.command.start = config.start;
|
||||
}
|
||||
if (config.port) {
|
||||
$application.publish.port = config.port;
|
||||
}
|
||||
if (config.directory) {
|
||||
$application.publish.directory = config.directory;
|
||||
}
|
||||
|
||||
if (packageJsonContent.scripts.hasOwnProperty('build') && config.build) {
|
||||
$application.build.command.build = config.build;
|
||||
}
|
||||
browser && toast.push(`${config.name} detected. Default values set.`);
|
||||
}
|
||||
});
|
||||
} else if (CargoToml) {
|
||||
$application.build.pack = 'rust';
|
||||
browser && toast.push(`Rust language detected. Default values set.`);
|
||||
} else if (requirementsTXT) {
|
||||
$application.build.pack = 'python';
|
||||
browser && toast.push('Python language detected. Default values set.');
|
||||
} else if (Dockerfile) {
|
||||
$application.build.pack = 'docker';
|
||||
browser && toast.push('Custom Dockerfile found. Build pack set to docker.');
|
||||
}
|
||||
} catch (error) {
|
||||
// Nothing detected
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
{#await load()}
|
||||
<Loading github githubLoadingText="Scanning repository..." />
|
||||
{:then}
|
||||
<div class="block text-center py-8">
|
||||
<nav class="flex space-x-4 justify-center font-bold text-md text-white" aria-label="Tabs">
|
||||
<div
|
||||
on:click={() => activateTab('general')}
|
||||
class:text-green-500={activeTab.general}
|
||||
class="px-3 py-2 cursor-pointer hover:bg-warmGray-700 rounded-lg transition duration-100"
|
||||
>
|
||||
General
|
||||
</div>
|
||||
<div
|
||||
on:click={() => activateTab('secrets')}
|
||||
class:text-green-500={activeTab.secrets}
|
||||
class="px-3 py-2 cursor-pointer hover:bg-warmGray-700 rounded-lg transition duration-100"
|
||||
>
|
||||
Secrets
|
||||
</div>
|
||||
{#if $application.general.isPreviewDeploymentEnabled}
|
||||
<div
|
||||
on:click={() => activateTab('pullRequests')}
|
||||
class:text-green-500={activeTab.pullRequests}
|
||||
class="px-3 py-2 cursor-pointer hover:bg-warmGray-700 rounded-lg transition duration-100"
|
||||
>
|
||||
Pull Requests
|
||||
</div>
|
||||
{/if}
|
||||
</nav>
|
||||
</div>
|
||||
<div class="max-w-4xl mx-auto">
|
||||
<div class="h-full">
|
||||
{#if activeTab.general}
|
||||
<General />
|
||||
{:else if activeTab.secrets}
|
||||
<Secrets />
|
||||
{:else if activeTab.pullRequests && $page.path !== '/application/new'}
|
||||
<PullRequests />
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
{/await}
|
@ -1,136 +0,0 @@
|
||||
<script>
|
||||
import { fade } from 'svelte/transition';
|
||||
import { toast } from '@zerodevx/svelte-toast';
|
||||
import MongoDb from './SVGs/MongoDb.svelte';
|
||||
import Postgresql from './SVGs/Postgresql.svelte';
|
||||
import Mysql from './SVGs/Mysql.svelte';
|
||||
import CouchDb from './SVGs/CouchDb.svelte';
|
||||
import Redis from './SVGs/Redis.svelte';
|
||||
import { page, session } from '$app/stores';
|
||||
import { goto } from '$app/navigation';
|
||||
import { request } from '$lib/request';
|
||||
import { browser } from '$app/env';
|
||||
import Loading from '$components/Loading.svelte';
|
||||
|
||||
let type;
|
||||
let defaultDatabaseName;
|
||||
let loading = false;
|
||||
async function deploy() {
|
||||
try {
|
||||
loading = true;
|
||||
await request(`/api/v1/databases/deploy`, $session, {
|
||||
body: {
|
||||
type,
|
||||
defaultDatabaseName
|
||||
}
|
||||
});
|
||||
|
||||
if (browser) {
|
||||
toast.push('Database deployment queued.');
|
||||
goto(`/dashboard/databases`, { replaceState: true });
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
} finally {
|
||||
loading = false;
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
{#if loading}
|
||||
<Loading />
|
||||
{:else}
|
||||
<div class="text-center space-y-2 max-w-4xl mx-auto px-6" in:fade={{ duration: 100 }}>
|
||||
{#if $page.path === '/database/new'}
|
||||
<div class="flex justify-center space-x-4 font-bold pb-6">
|
||||
<div
|
||||
class="text-center flex-col items-center cursor-pointer ease-in-out hover:scale-105 duration-100 border-2 border-dashed border-transparent hover:border-green-600 p-2 rounded bg-warmGray-800 w-32"
|
||||
class:border-green-600={type === 'mongodb'}
|
||||
on:click={() => (type = 'mongodb')}
|
||||
>
|
||||
<div class="flex items-center justify-center my-2">
|
||||
<MongoDb customClass="w-6" />
|
||||
</div>
|
||||
<div class="text-white">MongoDB</div>
|
||||
</div>
|
||||
<div
|
||||
class="text-center flex-col items-center cursor-pointer ease-in-out hover:scale-105 duration-100 border-2 border-dashed border-transparent hover:border-red-600 p-2 rounded bg-warmGray-800 w-32"
|
||||
class:border-red-600={type === 'couchdb'}
|
||||
on:click={() => (type = 'couchdb')}
|
||||
>
|
||||
<div class="flex items-center justify-center my-2">
|
||||
<CouchDb customClass="w-12 text-red-600 fill-current" />
|
||||
</div>
|
||||
<div class="text-white">Couchdb</div>
|
||||
</div>
|
||||
<div
|
||||
class="text-center flex-col items-center cursor-pointer ease-in-out hover:scale-105 duration-100 border-2 border-dashed border-transparent hover:border-blue-600 p-2 rounded bg-warmGray-800 w-32"
|
||||
class:border-blue-600={type === 'postgresql'}
|
||||
on:click={() => (type = 'postgresql')}
|
||||
>
|
||||
<div class="flex items-center justify-center my-2">
|
||||
<Postgresql customClass="w-12" />
|
||||
</div>
|
||||
<div class="text-white">PostgreSQL</div>
|
||||
</div>
|
||||
<div
|
||||
class="text-center flex-col items-center cursor-pointer ease-in-out hover:scale-105 duration-100 border-2 border-dashed border-transparent hover:border-orange-600 p-2 rounded bg-warmGray-800 w-32"
|
||||
class:border-orange-600={type === 'mysql'}
|
||||
on:click={() => (type = 'mysql')}
|
||||
>
|
||||
<div class="flex items-center justify-center">
|
||||
<Mysql customClass="w-10" />
|
||||
</div>
|
||||
<div class="text-white">MySQL</div>
|
||||
</div>
|
||||
<div
|
||||
class="text-center flex-col items-center cursor-pointer ease-in-out hover:scale-105 duration-100 border-2 border-dashed border-transparent hover:border-red-600 p-2 rounded bg-warmGray-800 w-32"
|
||||
class:border-red-600={type === 'redis'}
|
||||
on:click={() => (type = 'redis')}
|
||||
>
|
||||
<div class="flex items-center justify-center">
|
||||
<Redis customClass="w-12" />
|
||||
</div>
|
||||
<div class="text-white">Redis</div>
|
||||
</div>
|
||||
|
||||
<!-- <button
|
||||
class="button bg-gray-500 p-2 text-white hover:bg-yellow-500 cursor-pointer w-32"
|
||||
on:click="{() => (type = 'clickhouse')}"
|
||||
class:bg-yellow-500="{type === 'clickhouse'}"
|
||||
>
|
||||
Clickhouse
|
||||
</button> -->
|
||||
</div>
|
||||
{#if type}
|
||||
<div class="flex justify-center space-x-4 items-center">
|
||||
{#if type !== 'redis'}
|
||||
<label for="defaultDB">Default database</label>
|
||||
<input
|
||||
id="defaultDB"
|
||||
class="w-64"
|
||||
placeholder="random"
|
||||
bind:value={defaultDatabaseName}
|
||||
/>
|
||||
{/if}
|
||||
|
||||
<button
|
||||
class:bg-green-600={type === 'mongodb'}
|
||||
class:hover:bg-green-500={type === 'mongodb'}
|
||||
class:bg-blue-600={type === 'postgresql'}
|
||||
class:hover:bg-blue-500={type === 'postgresql'}
|
||||
class:bg-orange-600={type === 'mysql'}
|
||||
class:hover:bg-orange-500={type === 'mysql'}
|
||||
class:bg-red-600={type === 'couchdb' || type === 'redis'}
|
||||
class:hover:bg-red-500={type === 'couchdb' || type === 'redis'}
|
||||
class:bg-yellow-500={type === 'clickhouse'}
|
||||
class:hover:bg-yellow-400={type === 'clickhouse'}
|
||||
class="button p-2 w-32 text-white"
|
||||
on:click={deploy}>Deploy</button
|
||||
>
|
||||
</div>
|
||||
{/if}
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
@ -1,9 +0,0 @@
|
||||
<script>
|
||||
export let customClass;
|
||||
</script>
|
||||
|
||||
<svg class="{customClass}" viewBox="0 0 9 8" xmlns="http://www.w3.org/2000/svg"
|
||||
><path d="m0 7h1v1h-1z" fill="#f00"></path><path
|
||||
d="m0 0h1v7h-1zm2 0h1v8h-1zm2 0h1v8h-1zm2 0h1v8h-1zm2 3.25h1v1.5h-1z"
|
||||
fill="#fc0"></path></svg
|
||||
>
|
@ -1,16 +0,0 @@
|
||||
<script>
|
||||
export let customClass;
|
||||
</script>
|
||||
|
||||
<svg
|
||||
class={customClass}
|
||||
id="CouchDB"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 128 128"
|
||||
><g id="original"
|
||||
><path
|
||||
class=""
|
||||
d="M101.4,77.2c0,5-2.7,7.5-7.6,7.7H33.9c-4.9,0-7.6-2.5-7.6-7.7,0-5,2.7-7.5,7.6-7.7H94.1C99,69.7,101.4,72.2,101.4,77.2ZM94.1,88.7H33.9c-4.9,0-7.6,2.4-7.6,7.7,0,5,2.7,7.4,7.6,7.7H94.1c4.9,0,7.6-2.5,7.6-7.7C101.4,91.1,99,88.7,94.1,88.7Zm18.6-42.1h0c-4.9,0-7.6,2.5-7.6,7.4V96.1c0,5,2.7,7.5,7.6,7.7h0c7.4-.2,11.3-7.7,11.3-22.9V62C124,51.8,120.1,46.8,112.7,46.6Zm-97.4,0h0C7.9,46.8,4,51.8,4,62V80.9c0,15.2,3.9,22.7,11.3,22.9h0c4.9,0,7.6-2.4,7.6-7.7V54.3C22.7,49.3,20.2,46.8,15.3,46.6Zm97.4-3.8c0-12.7-6.6-18.7-18.6-18.9H33.9c-12.2.2-18.6,6.5-18.6,18.9h0c7.4,0,11.3,4,11.3,11.5s3.9,11.4,11.3,11.4H90.4c7.3,0,11.3-3.9,11.3-11.4-.3-7.7,3.9-11.2,11-11.5Z"
|
||||
></path></g
|
||||
></svg
|
||||
>
|
@ -1,32 +0,0 @@
|
||||
<script>
|
||||
export let customClass;
|
||||
</script>
|
||||
<svg
|
||||
class={customClass}
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
id="Layer_1"
|
||||
data-name="Layer 1"
|
||||
viewBox="0 0 216.56 448.5"
|
||||
><defs
|
||||
><style>
|
||||
.cls-1 {
|
||||
fill: #10aa50;
|
||||
}
|
||||
.cls-2 {
|
||||
fill: #b8c4c2;
|
||||
}
|
||||
.cls-3 {
|
||||
fill: #12924f;
|
||||
}
|
||||
</style></defs
|
||||
><title>MongoDB_Leaf_FullColor_RGB</title><path
|
||||
class="cls-1"
|
||||
d="M202.8,179.68c-23-101.47-71-128.49-83.18-147.59C113,21.7,106.25,5.91,106.25,5.91c-.66,9-1.83,14.7-9.51,21.54C81.36,41.16,16,94.42,10.51,209.72c-5.12,107.5,79,173.8,90.18,180.65,8.54,4.2,19,.08,24-3.77,40.54-27.84,96-102.07,78.06-206.92"
|
||||
></path><path
|
||||
class="cls-2"
|
||||
d="M109.73,333.11c-2.11,26.62-3.63,42.11-9,57.29,0,0,3.54,25.33,6,52.17l8.77,0a488.62,488.62,0,0,1,9.57-56.2C113.71,380.8,110.16,356.46,109.73,333.11Z"
|
||||
></path><path
|
||||
class="cls-3"
|
||||
d="M125.06,386.39h0c-11.48-5.3-14.8-30.13-15.31-53.28A1090.8,1090.8,0,0,0,112.2,218.4c-.6-20.07.3-185.92-4.94-210.2,2.12,4.75,7.24,15.91,12.36,23.88,12.23,19.11,60.19,46.13,83.17,147.61C220.7,284.27,165.57,358.37,125.06,386.39Z"
|
||||
></path>
|
||||
</svg>
|
@ -1,60 +0,0 @@
|
||||
<script>
|
||||
export let customClass;
|
||||
</script>
|
||||
|
||||
<svg
|
||||
class={customClass}
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 432.071 445.383"
|
||||
xml:space="preserve"
|
||||
>
|
||||
<g
|
||||
id="orginal"
|
||||
style="fill-rule:nonzero;clip-rule:nonzero;stroke:#000000;stroke-miterlimit:4;"
|
||||
>
|
||||
</g>
|
||||
<g
|
||||
id="Layer_x0020_3"
|
||||
style="fill-rule:nonzero;clip-rule:nonzero;fill:none;stroke:#FFFFFF;stroke-width:12.4651;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;"
|
||||
>
|
||||
<path
|
||||
style="fill:#000000;stroke:#000000;stroke-width:37.3953;stroke-linecap:butt;stroke-linejoin:miter;"
|
||||
d="M323.205,324.227c2.833-23.601,1.984-27.062,19.563-23.239l4.463,0.392c13.517,0.615,31.199-2.174,41.587-7c22.362-10.376,35.622-27.7,13.572-23.148c-50.297,10.376-53.755-6.655-53.755-6.655c53.111-78.803,75.313-178.836,56.149-203.322 C352.514-5.534,262.036,26.049,260.522,26.869l-0.482,0.089c-9.938-2.062-21.06-3.294-33.554-3.496c-22.761-0.374-40.032,5.967-53.133,15.904c0,0-161.408-66.498-153.899,83.628c1.597,31.936,45.777,241.655,98.47,178.31 c19.259-23.163,37.871-42.748,37.871-42.748c9.242,6.14,20.307,9.272,31.912,8.147l0.897-0.765c-0.281,2.876-0.157,5.689,0.359,9.019c-13.572,15.167-9.584,17.83-36.723,23.416c-27.457,5.659-11.326,15.734-0.797,18.367c12.768,3.193,42.305,7.716,62.268-20.224 l-0.795,3.188c5.325,4.26,4.965,30.619,5.72,49.452c0.756,18.834,2.017,36.409,5.856,46.771c3.839,10.36,8.369,37.05,44.036,29.406c29.809-6.388,52.6-15.582,54.677-101.107"
|
||||
></path>
|
||||
<path
|
||||
style="fill:#336791;stroke:none;"
|
||||
d="M402.395,271.23c-50.302,10.376-53.76-6.655-53.76-6.655c53.111-78.808,75.313-178.843,56.153-203.326c-52.27-66.785-142.752-35.2-144.262-34.38l-0.486,0.087c-9.938-2.063-21.06-3.292-33.56-3.496c-22.761-0.373-40.026,5.967-53.127,15.902 c0,0-161.411-66.495-153.904,83.63c1.597,31.938,45.776,241.657,98.471,178.312c19.26-23.163,37.869-42.748,37.869-42.748c9.243,6.14,20.308,9.272,31.908,8.147l0.901-0.765c-0.28,2.876-0.152,5.689,0.361,9.019c-13.575,15.167-9.586,17.83-36.723,23.416 c-27.459,5.659-11.328,15.734-0.796,18.367c12.768,3.193,42.307,7.716,62.266-20.224l-0.796,3.188c5.319,4.26,9.054,27.711,8.428,48.969c-0.626,21.259-1.044,35.854,3.147,47.254c4.191,11.4,8.368,37.05,44.042,29.406c29.809-6.388,45.256-22.942,47.405-50.555 c1.525-19.631,4.976-16.729,5.194-34.28l2.768-8.309c3.192-26.611,0.507-35.196,18.872-31.203l4.463,0.392c13.517,0.615,31.208-2.174,41.591-7c22.358-10.376,35.618-27.7,13.573-23.148z"
|
||||
></path>
|
||||
<path
|
||||
d="M215.866,286.484c-1.385,49.516,0.348,99.377,5.193,111.495c4.848,12.118,15.223,35.688,50.9,28.045c29.806-6.39,40.651-18.756,45.357-46.051c3.466-20.082,10.148-75.854,11.005-87.281"
|
||||
></path>
|
||||
<path
|
||||
d="M173.104,38.256c0,0-161.521-66.016-154.012,84.109c1.597,31.938,45.779,241.664,98.473,178.316c19.256-23.166,36.671-41.335,36.671-41.335"
|
||||
></path>
|
||||
<path
|
||||
d="M260.349,26.207c-5.591,1.753,89.848-34.889,144.087,34.417c19.159,24.484-3.043,124.519-56.153,203.329"
|
||||
></path>
|
||||
<path
|
||||
style="stroke-linejoin:bevel;"
|
||||
d="M348.282,263.953c0,0,3.461,17.036,53.764,6.653c22.04-4.552,8.776,12.774-13.577,23.155c-18.345,8.514-59.474,10.696-60.146-1.069c-1.729-30.355,21.647-21.133,19.96-28.739c-1.525-6.85-11.979-13.573-18.894-30.338 c-6.037-14.633-82.796-126.849,21.287-110.183c3.813-0.789-27.146-99.002-124.553-100.599c-97.385-1.597-94.19,119.762-94.19,119.762"
|
||||
></path>
|
||||
<path
|
||||
d="M188.604,274.334c-13.577,15.166-9.584,17.829-36.723,23.417c-27.459,5.66-11.326,15.733-0.797,18.365c12.768,3.195,42.307,7.718,62.266-20.229c6.078-8.509-0.036-22.086-8.385-25.547c-4.034-1.671-9.428-3.765-16.361,3.994z"
|
||||
></path>
|
||||
<path
|
||||
d="M187.715,274.069c-1.368-8.917,2.93-19.528,7.536-31.942c6.922-18.626,22.893-37.255,10.117-96.339c-9.523-44.029-73.396-9.163-73.436-3.193c-0.039,5.968,2.889,30.26-1.067,58.548c-5.162,36.913,23.488,68.132,56.479,64.938"
|
||||
></path>
|
||||
<path
|
||||
style="fill:#FFFFFF;stroke-width:4.155;stroke-linecap:butt;stroke-linejoin:miter;"
|
||||
d="M172.517,141.7c-0.288,2.039,3.733,7.48,8.976,8.207c5.234,0.73,9.714-3.522,9.998-5.559c0.284-2.039-3.732-4.285-8.977-5.015c-5.237-0.731-9.719,0.333-9.996,2.367z"
|
||||
></path>
|
||||
<path
|
||||
style="fill:#FFFFFF;stroke-width:2.0775;stroke-linecap:butt;stroke-linejoin:miter;"
|
||||
d="M331.941,137.543c0.284,2.039-3.732,7.48-8.976,8.207c-5.238,0.73-9.718-3.522-10.005-5.559c-0.277-2.039,3.74-4.285,8.979-5.015c5.239-0.73,9.718,0.333,10.002,2.368z"
|
||||
></path>
|
||||
<path
|
||||
d="M350.676,123.432c0.863,15.994-3.445,26.888-3.988,43.914c-0.804,24.748,11.799,53.074-7.191,81.435"
|
||||
></path>
|
||||
<path style="stroke-width:3;" d="M0,60.232"></path>
|
||||
</g>
|
||||
</svg>
|
@ -1,83 +0,0 @@
|
||||
<script>
|
||||
export let github = false;
|
||||
export let githubLoadingText = 'Loading GitHub...';
|
||||
export let fullscreen = true;
|
||||
</script>
|
||||
|
||||
{#if fullscreen}
|
||||
{#if github}
|
||||
<div class="fixed left-0 top-0 flex flex-wrap content-center h-full w-full">
|
||||
<div class="main flex justify-center items-center">
|
||||
<div class="w-64">
|
||||
<svg
|
||||
class=" w-28 animate-bounce mx-auto"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
><path
|
||||
d="M9 19c-5 1.5-5-2.5-7-3m14 6v-3.87a3.37 3.37 0 0 0-.94-2.61c3.14-.35 6.44-1.54 6.44-7A5.44 5.44 0 0 0 20 4.77 5.07 5.07 0 0 0 19.91 1S18.73.65 16 2.48a13.38 13.38 0 0 0-7 0C6.27.65 5.09 1 5.09 1A5.07 5.07 0 0 0 5 4.77a5.44 5.44 0 0 0-1.5 3.78c0 5.42 3.3 6.61 6.44 7A3.37 3.37 0 0 0 9 18.13V22"
|
||||
/></svg
|
||||
>
|
||||
<div class="text-xl font-bold text-center">
|
||||
{githubLoadingText}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{:else}
|
||||
<div class="main fixed left-0 top-0 flex flex-wrap content-center h-full">
|
||||
<span class="loader" />
|
||||
</div>
|
||||
{/if}
|
||||
{:else}
|
||||
<div class="main h-64 py-24 left-0 top-0 flex flex-wrap content-center mx-auto">
|
||||
<span class="loader" />
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<style lang="postcss">
|
||||
.loader {
|
||||
width: 8px;
|
||||
height: 40px;
|
||||
border-radius: 4px;
|
||||
display: block;
|
||||
margin: 20px auto;
|
||||
position: relative;
|
||||
background: currentColor;
|
||||
color: #fff;
|
||||
box-sizing: border-box;
|
||||
animation: animloader 0.3s 0.3s linear infinite alternate;
|
||||
}
|
||||
|
||||
.loader::after,
|
||||
.loader::before {
|
||||
content: '';
|
||||
width: 8px;
|
||||
height: 40px;
|
||||
border-radius: 4px;
|
||||
background: currentColor;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
left: 20px;
|
||||
box-sizing: border-box;
|
||||
animation: animloader 0.3s 0.45s linear infinite alternate;
|
||||
}
|
||||
.loader::before {
|
||||
left: -20px;
|
||||
animation-delay: 0s;
|
||||
}
|
||||
|
||||
@keyframes animloader {
|
||||
0% {
|
||||
height: 48px;
|
||||
}
|
||||
100% {
|
||||
height: 4px;
|
||||
}
|
||||
}
|
||||
</style>
|
@ -1,56 +0,0 @@
|
||||
<script>
|
||||
export let value;
|
||||
let showPassword = false;
|
||||
export let isEditable = false;
|
||||
</script>
|
||||
|
||||
<div class="relative w-full">
|
||||
{#if showPassword}
|
||||
<input type="text" class="w-full" bind:value disabled={!isEditable} />
|
||||
{:else}
|
||||
<input type="password" class="w-full" bind:value disabled={!isEditable} />
|
||||
{/if}
|
||||
|
||||
<div
|
||||
class="absolute top-0 my-2 mx-2 right-0 cursor-pointer text-warmGray-600 hover:text-white"
|
||||
on:click={() => (showPassword = !showPassword)}
|
||||
>
|
||||
{#if showPassword}
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="h-6 w-6"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M13.875 18.825A10.05 10.05 0 0112 19c-4.478 0-8.268-2.943-9.543-7a9.97 9.97 0 011.563-3.029m5.858.908a3 3 0 114.243 4.243M9.878 9.878l4.242 4.242M9.88 9.88l-3.29-3.29m7.532 7.532l3.29 3.29M3 3l3.59 3.59m0 0A9.953 9.953 0 0112 5c4.478 0 8.268 2.943 9.543 7a10.025 10.025 0 01-4.132 5.411m0 0L21 21"
|
||||
/>
|
||||
</svg>
|
||||
{:else}
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="h-6 w-6"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"
|
||||
/>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z"
|
||||
/>
|
||||
</svg>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
@ -1,35 +0,0 @@
|
||||
<script>
|
||||
import { fade } from 'svelte/transition';
|
||||
import { toast } from '@zerodevx/svelte-toast';
|
||||
import Loading from '../Loading.svelte';
|
||||
import Tooltip from '$components/Tooltip.svelte';
|
||||
import { request } from '$lib/request';
|
||||
import { page, session } from '$app/stores';
|
||||
import PasswordField from '$components/PasswordField.svelte';
|
||||
import { browser } from '$app/env';
|
||||
export let service;
|
||||
|
||||
async function getPassword() {
|
||||
try {
|
||||
const { password } = await request(`/api/v1/services/deploy/${$page.params.name}/password`, $session);
|
||||
service.config.password = password
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
browser && toast.push(`Ooops, there was an error activating users for VSCode Server?!`);
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
{#await getPassword()}
|
||||
<Loading />
|
||||
{:then}
|
||||
<div class="text-left max-w-5xl mx-auto px-6" in:fade={{ duration: 100 }}>
|
||||
<div class="pb-2 pt-5 space-y-4">
|
||||
<div class="flex items-center">
|
||||
<div class="font-bold w-64 text-warmGray-400">Password</div>
|
||||
<PasswordField value={service.config.password} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/await}
|
@ -1,26 +0,0 @@
|
||||
<script>
|
||||
import { fade } from 'svelte/transition';
|
||||
import { toast } from '@zerodevx/svelte-toast';
|
||||
import Loading from '../Loading.svelte';
|
||||
import Tooltip from '$components/Tooltip.svelte';
|
||||
import { request } from '$lib/request';
|
||||
import { page, session } from '$app/stores';
|
||||
import PasswordField from '$components/PasswordField.svelte';
|
||||
import { browser } from '$app/env';
|
||||
export let service;
|
||||
|
||||
</script>
|
||||
|
||||
<div class="text-left max-w-5xl mx-auto px-6" in:fade={{ duration: 100 }}>
|
||||
<div class="pb-2 pt-5 space-y-4">
|
||||
<div class="flex items-center pt-4">
|
||||
<div class="font-bold w-64 text-warmGray-400">Root User</div>
|
||||
<input class="w-full" value={service.config.generateEnvsMinIO.MINIO_ROOT_USER} disabled />
|
||||
</div>
|
||||
|
||||
<div class="flex items-center">
|
||||
<div class="font-bold w-64 text-warmGray-400">Root Password</div>
|
||||
<PasswordField value={service.config.generateEnvsMinIO.MINIO_ROOT_PASSWORD} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
@ -1,88 +0,0 @@
|
||||
<script>
|
||||
import { fade } from 'svelte/transition';
|
||||
import { toast } from '@zerodevx/svelte-toast';
|
||||
import Loading from '../Loading.svelte';
|
||||
import Tooltip from '$components/Tooltip.svelte';
|
||||
import { request } from '$lib/request';
|
||||
import { page, session } from '$app/stores';
|
||||
import PasswordField from '$components/PasswordField.svelte';
|
||||
import { browser } from '$app/env';
|
||||
export let service;
|
||||
let loading = false;
|
||||
async function activate() {
|
||||
try {
|
||||
loading = true;
|
||||
await request(`/api/v1/services/deploy/${$page.params.name}/activate`, $session, {
|
||||
method: 'PATCH',
|
||||
body: {}
|
||||
});
|
||||
browser && toast.push(`All users are activated for Plausible.`);
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
browser && toast.push(`Ooops, there was an error activating users for Plausible?!`);
|
||||
} finally {
|
||||
loading = false;
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
{#if loading}
|
||||
<Loading />
|
||||
{:else}
|
||||
<div class="text-left max-w-5xl mx-auto px-6" in:fade={{ duration: 100 }}>
|
||||
<div class="pb-2 pt-5 space-y-4">
|
||||
<div class="flex space-x-5 items-center">
|
||||
<div class="text-2xl font-bold border-gradient">General</div>
|
||||
<div class="flex-1" />
|
||||
<Tooltip
|
||||
position="bottom"
|
||||
size="large"
|
||||
label="Activate all users in Plausible database, so you can login without the email verification."
|
||||
>
|
||||
<button class="button bg-blue-500 hover:bg-blue-400 px-2" on:click={activate}
|
||||
>Activate All Users</button
|
||||
>
|
||||
</Tooltip>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center pt-4">
|
||||
<div class="font-bold w-64 text-warmGray-400">Domain</div>
|
||||
<input class="w-full" value={service.config.baseURL} disabled />
|
||||
</div>
|
||||
<div class="flex items-center">
|
||||
<div class="font-bold w-64 text-warmGray-400">Email address</div>
|
||||
<input class="w-full" value={service.config.email} disabled />
|
||||
</div>
|
||||
<div class="flex items-center">
|
||||
<div class="font-bold w-64 text-warmGray-400">Username</div>
|
||||
<input class="w-full" value={service.config.userName} disabled />
|
||||
</div>
|
||||
<div class="flex items-center">
|
||||
<div class="font-bold w-64 text-warmGray-400">Password</div>
|
||||
<PasswordField value={service.config.userPassword} />
|
||||
</div>
|
||||
<div class="text-2xl font-bold pt-4 border-gradient w-32">PostgreSQL</div>
|
||||
<div class="flex items-center pt-4">
|
||||
<div class="font-bold w-64 text-warmGray-400">Username</div>
|
||||
<input
|
||||
class="w-full"
|
||||
value={service.config.generateEnvsPostgres.POSTGRESQL_USERNAME}
|
||||
disabled
|
||||
/>
|
||||
</div>
|
||||
<div class="flex items-center">
|
||||
<div class="font-bold w-64 text-warmGray-400">Password</div>
|
||||
<PasswordField value={service.config.generateEnvsPostgres.POSTGRESQL_PASSWORD} />
|
||||
</div>
|
||||
<div class="flex items-center">
|
||||
<div class="font-bold w-64 text-warmGray-400">Database</div>
|
||||
<input
|
||||
class="w-full"
|
||||
value={service.config.generateEnvsPostgres.POSTGRESQL_DATABASE}
|
||||
disabled
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
@ -1,15 +0,0 @@
|
||||
<script>
|
||||
export let position = "bottom";
|
||||
export let label;
|
||||
export let size = "fit";
|
||||
</script>
|
||||
|
||||
<span
|
||||
aria-label="{label}"
|
||||
data-microtip-position="{position}"
|
||||
data-microtip-size="{size}"
|
||||
role="tooltip"
|
||||
>
|
||||
<slot></slot>
|
||||
</span>
|
||||
|
@ -1,25 +0,0 @@
|
||||
<script>
|
||||
export let position = "top";
|
||||
export let label;
|
||||
export let size = "large";
|
||||
</script>
|
||||
|
||||
<span
|
||||
class="absolute px-1 py-1"
|
||||
aria-label="{label}"
|
||||
data-microtip-position="{position}"
|
||||
data-microtip-size="{size}"
|
||||
role="tooltip"
|
||||
>
|
||||
<svg
|
||||
class="w-4 text-warmGray-600 hover:text-white"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 20 20"
|
||||
fill="currentColor"
|
||||
>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a1 1 0 000 2v3a1 1 0 001 1h1a1 1 0 100-2v-3a1 1 0 00-1-1H9z"
|
||||
clip-rule="evenodd"></path>
|
||||
</svg>
|
||||
</span>
|
176
src/global.d.ts
vendored
176
src/global.d.ts
vendored
@ -1,8 +1,75 @@
|
||||
/// <reference types="@sveltejs/kit" />
|
||||
/// <reference types="svelte" />
|
||||
/// <reference types="vite/client" />
|
||||
interface Cookies {
|
||||
teamId?: string;
|
||||
gitlabToken?: string;
|
||||
'kit.session'?: string;
|
||||
}
|
||||
interface Locals {
|
||||
gitlabToken?: string;
|
||||
user: {
|
||||
teamId: string;
|
||||
permission: string;
|
||||
isAdmin: boolean;
|
||||
};
|
||||
session: {
|
||||
data: {
|
||||
uid?: string;
|
||||
teams?: string[];
|
||||
expires?: string;
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
export type DateTimeFormatOptions = {
|
||||
type Applications = {
|
||||
name: string;
|
||||
domain: string;
|
||||
};
|
||||
|
||||
interface Hash {
|
||||
iv: string;
|
||||
content: string;
|
||||
}
|
||||
|
||||
interface BuildPack {
|
||||
name: string;
|
||||
}
|
||||
|
||||
// TODO: Not used, not working what?!
|
||||
enum GitSource {
|
||||
Github = 'github',
|
||||
Gitlab = 'gitlab',
|
||||
Bitbucket = 'bitbucket'
|
||||
}
|
||||
|
||||
type RawHaproxyConfiguration = {
|
||||
_version: number;
|
||||
data: string;
|
||||
};
|
||||
|
||||
type NewTransaction = {
|
||||
_version: number;
|
||||
id: string;
|
||||
status: string;
|
||||
};
|
||||
|
||||
type HttpRequestRuleForceSSL = {
|
||||
return_hdrs: null;
|
||||
cond: string;
|
||||
cond_test: string;
|
||||
index: number;
|
||||
redir_code: number;
|
||||
redir_type: string;
|
||||
redir_value: string;
|
||||
type: string;
|
||||
};
|
||||
|
||||
// TODO: No any please
|
||||
type HttpRequestRule = {
|
||||
_version: number;
|
||||
data: Array<any>;
|
||||
};
|
||||
|
||||
type DateTimeFormatOptions = {
|
||||
localeMatcher?: 'lookup' | 'best fit';
|
||||
weekday?: 'long' | 'short' | 'narrow';
|
||||
era?: 'long' | 'short' | 'narrow';
|
||||
@ -17,106 +84,3 @@ export type DateTimeFormatOptions = {
|
||||
hour12?: boolean;
|
||||
timeZone?: string;
|
||||
};
|
||||
export type Application = {
|
||||
github: {
|
||||
installation: {
|
||||
id: number;
|
||||
};
|
||||
app: {
|
||||
id: number;
|
||||
};
|
||||
};
|
||||
repository: {
|
||||
id: number;
|
||||
organization: string;
|
||||
name: string;
|
||||
branch: string;
|
||||
};
|
||||
general: {
|
||||
deployId: string;
|
||||
nickname: string;
|
||||
workdir: string;
|
||||
isPreviewDeploymentEnabled: boolean;
|
||||
pullRequest: number;
|
||||
};
|
||||
build: {
|
||||
pack: string;
|
||||
directory: string;
|
||||
command: {
|
||||
build: string | null;
|
||||
installation: string;
|
||||
start: string;
|
||||
python: {
|
||||
module?: string;
|
||||
instance?: string;
|
||||
};
|
||||
};
|
||||
container: {
|
||||
name: string;
|
||||
tag: string;
|
||||
baseSHA: string;
|
||||
};
|
||||
};
|
||||
publish: {
|
||||
directory: string;
|
||||
domain: string;
|
||||
path: string;
|
||||
port: number;
|
||||
secrets: Array<Record<string, unknown>>;
|
||||
};
|
||||
};
|
||||
export type Database = {
|
||||
config:
|
||||
| {
|
||||
general: {
|
||||
deployId: string;
|
||||
nickname: string;
|
||||
workdir: string;
|
||||
type: string;
|
||||
};
|
||||
database: {
|
||||
usernames: Array;
|
||||
passwords: Array;
|
||||
defaultDatabaseName: string;
|
||||
};
|
||||
deploy: {
|
||||
name: string;
|
||||
};
|
||||
}
|
||||
| Record<string, unknown>;
|
||||
envs: Array;
|
||||
};
|
||||
export type Dashboard = {
|
||||
databases: {
|
||||
deployed:
|
||||
| [
|
||||
{
|
||||
configuration: Database;
|
||||
}
|
||||
]
|
||||
| [];
|
||||
};
|
||||
services: {
|
||||
deployed:
|
||||
| [
|
||||
{
|
||||
configuration: any;
|
||||
}
|
||||
]
|
||||
| [];
|
||||
};
|
||||
applications: {
|
||||
deployed:
|
||||
| [
|
||||
{
|
||||
configuration: Application;
|
||||
UpdatedAt: any;
|
||||
}
|
||||
]
|
||||
| [];
|
||||
};
|
||||
};
|
||||
export type GithubInstallations = {
|
||||
id: number;
|
||||
app_id: number;
|
||||
};
|
||||
|
77
src/hooks.ts
Normal file
77
src/hooks.ts
Normal file
@ -0,0 +1,77 @@
|
||||
import dotEnvExtended from 'dotenv-extended';
|
||||
dotEnvExtended.load();
|
||||
import type { GetSession } from '@sveltejs/kit';
|
||||
import { handleSession } from 'svelte-kit-cookie-session';
|
||||
import { getUserDetails, isTeamIdTokenAvailable, sentry } from '$lib/common';
|
||||
import { version } from '$lib/common';
|
||||
import cookie from 'cookie';
|
||||
import { dev } from '$app/env';
|
||||
|
||||
export const handle = handleSession(
|
||||
{
|
||||
secret: process.env['COOLIFY_SECRET_KEY'],
|
||||
expires: 30
|
||||
},
|
||||
async function ({ event, resolve }) {
|
||||
let response;
|
||||
try {
|
||||
const cookies: Cookies = cookie.parse(event.request.headers.get('cookie') || '');
|
||||
if (cookies['kit.session']) {
|
||||
const { permission, teamId } = await getUserDetails(event, false);
|
||||
event.locals.user = {
|
||||
teamId,
|
||||
permission,
|
||||
isAdmin: permission === 'admin' || permission === 'owner'
|
||||
};
|
||||
}
|
||||
if (cookies.gitlabToken) {
|
||||
event.locals.gitlabToken = cookies.gitlabToken;
|
||||
}
|
||||
response = await resolve(event, {
|
||||
ssr: !event.url.pathname.startsWith('/webhooks/success')
|
||||
});
|
||||
} catch (error) {
|
||||
response = await resolve(event, {
|
||||
ssr: !event.url.pathname.startsWith('/webhooks/success')
|
||||
});
|
||||
response.headers.append(
|
||||
'Set-Cookie',
|
||||
cookie.serialize('kit.session', '', {
|
||||
path: '/',
|
||||
expires: new Date('Thu, 01 Jan 1970 00:00:01 GMT')
|
||||
})
|
||||
);
|
||||
response.headers.append(
|
||||
'Set-Cookie',
|
||||
cookie.serialize('teamId', '', {
|
||||
path: '/',
|
||||
expires: new Date('Thu, 01 Jan 1970 00:00:01 GMT')
|
||||
})
|
||||
);
|
||||
response.headers.append(
|
||||
'Set-Cookie',
|
||||
cookie.serialize('gitlabToken', '', {
|
||||
path: '/',
|
||||
expires: new Date('Thu, 01 Jan 1970 00:00:01 GMT')
|
||||
})
|
||||
);
|
||||
} finally {
|
||||
return response;
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
export const getSession: GetSession = function (request) {
|
||||
return {
|
||||
version,
|
||||
gitlabToken: request.locals?.gitlabToken || null,
|
||||
uid: request.locals.session.data?.uid || null,
|
||||
teamId: request.locals.user?.teamId || null,
|
||||
permission: request.locals.user?.permission,
|
||||
isAdmin: request.locals.user?.isAdmin || false
|
||||
};
|
||||
};
|
||||
|
||||
export async function handleError({ error, event }) {
|
||||
if (!dev) sentry.captureException(error, { event });
|
||||
}
|
@ -1,151 +0,0 @@
|
||||
import dotEnvExtended from 'dotenv-extended';
|
||||
dotEnvExtended.load();
|
||||
import { publicPages } from '$lib/consts';
|
||||
import mongoose from 'mongoose';
|
||||
import { verifyUserId } from '$lib/api/common';
|
||||
import { initializeSession } from 'svelte-kit-cookie-session';
|
||||
import { cleanupStuckedDeploymentsInDB } from '$lib/api/applications/cleanup';
|
||||
import { docker } from '$lib/api/docker';
|
||||
import Configuration from '$models/Configuration';
|
||||
|
||||
process.on('SIGINT', function () {
|
||||
mongoose.connection.close(function () {
|
||||
console.log('Mongoose default connection disconnected through app termination');
|
||||
process.exit(0);
|
||||
});
|
||||
});
|
||||
|
||||
async function connectMongoDB() {
|
||||
// TODO: Save configurations on start?
|
||||
const { MONGODB_USER, MONGODB_PASSWORD, MONGODB_HOST, MONGODB_PORT, MONGODB_DB } = process.env;
|
||||
try {
|
||||
if (process.env.NODE_ENV === 'production') {
|
||||
await mongoose.connect(
|
||||
`mongodb://${MONGODB_USER}:${MONGODB_PASSWORD}@${MONGODB_HOST}:${MONGODB_PORT}/${MONGODB_DB}?authSource=${MONGODB_DB}&readPreference=primary&ssl=false`,
|
||||
{ useNewUrlParser: true, useUnifiedTopology: true, useFindAndModify: false }
|
||||
);
|
||||
} else {
|
||||
await mongoose.connect(
|
||||
'mongodb://supercooldbuser:developmentPassword4db@localhost:27017/coolify?&readPreference=primary&ssl=false',
|
||||
{ useNewUrlParser: true, useUnifiedTopology: true, useFindAndModify: false }
|
||||
);
|
||||
}
|
||||
console.log('Connected to mongodb.');
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
}
|
||||
|
||||
(async () => {
|
||||
if (mongoose.connection.readyState !== 1) await connectMongoDB();
|
||||
try {
|
||||
await mongoose.connection.db.dropCollection('logs-servers');
|
||||
} catch (error) {
|
||||
//
|
||||
}
|
||||
try {
|
||||
await cleanupStuckedDeploymentsInDB();
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
try {
|
||||
const dockerServices = await docker.engine.listServices();
|
||||
let applications: any = dockerServices.filter(
|
||||
(r) =>
|
||||
r.Spec.Labels.managedBy === 'coolify' &&
|
||||
r.Spec.Labels.type === 'application' &&
|
||||
r.Spec.Labels.configuration
|
||||
);
|
||||
applications = applications.map((r) => {
|
||||
if (JSON.parse(r.Spec.Labels.configuration)) {
|
||||
return {
|
||||
configuration: JSON.parse(r.Spec.Labels.configuration),
|
||||
UpdatedAt: r.UpdatedAt
|
||||
};
|
||||
}
|
||||
return {};
|
||||
});
|
||||
applications = [
|
||||
...new Map(
|
||||
applications.map((item) => [
|
||||
item.configuration.publish.domain + item.configuration.publish.path,
|
||||
item
|
||||
])
|
||||
).values()
|
||||
];
|
||||
for (const application of applications) {
|
||||
await Configuration.findOneAndUpdate(
|
||||
{
|
||||
'repository.name': application.configuration.repository.name,
|
||||
'repository.organization': application.configuration.repository.organization,
|
||||
'repository.branch': application.configuration.repository.branch,
|
||||
'publish.domain': application.configuration.publish.domain
|
||||
},
|
||||
{
|
||||
...application.configuration
|
||||
},
|
||||
{ upsert: true, new: true }
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
})();
|
||||
|
||||
export async function handle({ request, resolve }) {
|
||||
const { SECRETS_ENCRYPTION_KEY } = process.env;
|
||||
let session;
|
||||
try {
|
||||
session = initializeSession(request.headers, {
|
||||
secret: SECRETS_ENCRYPTION_KEY,
|
||||
cookie: { path: '/', secure: true }
|
||||
});
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
return {
|
||||
status: 302,
|
||||
headers: {
|
||||
'set-cookie': 'kit.session=deleted;path=/;expires=Wed, 21 Oct 2015 07:28:00 GMT',
|
||||
location: '/'
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
request.locals.session = session;
|
||||
if (session?.data?.coolToken) {
|
||||
try {
|
||||
await verifyUserId(session.data.coolToken);
|
||||
request.locals.session = session;
|
||||
} catch (error) {
|
||||
request.locals.session.destroy = true;
|
||||
}
|
||||
}
|
||||
const response = await resolve(request);
|
||||
if (!session['set-cookie']) {
|
||||
if (!session?.data?.coolToken && !publicPages.includes(request.path)) {
|
||||
return {
|
||||
status: 302,
|
||||
headers: {
|
||||
location: '/'
|
||||
}
|
||||
};
|
||||
}
|
||||
return response;
|
||||
}
|
||||
return {
|
||||
...response,
|
||||
headers: {
|
||||
...response.headers,
|
||||
...session
|
||||
}
|
||||
};
|
||||
}
|
||||
export function getSession(request) {
|
||||
const { data } = request.locals.session;
|
||||
return {
|
||||
isLoggedIn: data && Object.keys(data).length !== 0 ? true : false,
|
||||
expires: data.expires,
|
||||
coolToken: data.coolToken,
|
||||
ghToken: data.ghToken || null
|
||||
};
|
||||
}
|
60
src/lib/api.ts
Normal file
60
src/lib/api.ts
Normal file
@ -0,0 +1,60 @@
|
||||
async function send({ method, path, data = {}, headers, timeout = 30000 }) {
|
||||
const controller = new AbortController();
|
||||
const id = setTimeout(() => controller.abort(), timeout);
|
||||
const opts = { method, headers: {}, body: null, signal: controller.signal };
|
||||
if (Object.keys(data).length > 0) {
|
||||
let parsedData = data;
|
||||
for (const [key, value] of Object.entries(data)) {
|
||||
if (value === '') {
|
||||
parsedData[key] = null;
|
||||
}
|
||||
}
|
||||
if (parsedData) {
|
||||
opts.headers['Content-Type'] = 'application/json';
|
||||
opts.body = JSON.stringify(parsedData);
|
||||
}
|
||||
}
|
||||
|
||||
if (headers) {
|
||||
opts.headers = {
|
||||
...opts.headers,
|
||||
...headers
|
||||
};
|
||||
}
|
||||
const response = await fetch(`${path}`, opts);
|
||||
|
||||
clearTimeout(id);
|
||||
|
||||
const contentType = response.headers.get('content-type');
|
||||
|
||||
let responseData = {};
|
||||
if (contentType) {
|
||||
if (contentType?.indexOf('application/json') !== -1) {
|
||||
responseData = await response.json();
|
||||
} else if (contentType?.indexOf('text/plain') !== -1) {
|
||||
responseData = await response.text();
|
||||
} else {
|
||||
return {};
|
||||
}
|
||||
} else {
|
||||
return {};
|
||||
}
|
||||
if (!response.ok) throw responseData;
|
||||
return responseData;
|
||||
}
|
||||
|
||||
export function get(path, headers = {}): Promise<any> {
|
||||
return send({ method: 'GET', path, headers });
|
||||
}
|
||||
|
||||
export function del(path, data = {}, headers = {}): Promise<any> {
|
||||
return send({ method: 'DELETE', path, data, headers });
|
||||
}
|
||||
|
||||
export function post(path, data, headers = {}): Promise<any> {
|
||||
return send({ method: 'POST', path, data, headers });
|
||||
}
|
||||
|
||||
export function put(path, data, headers = {}): Promise<any> {
|
||||
return send({ method: 'PUT', path, data, headers });
|
||||
}
|
@ -1,29 +0,0 @@
|
||||
import Deployment from '$models/Deployment';
|
||||
import { saveAppLog } from './logging';
|
||||
import * as packs from './packs';
|
||||
|
||||
export default async function (configuration) {
|
||||
const { id, organization, name, branch } = configuration.repository;
|
||||
const { domain } = configuration.publish;
|
||||
const deployId = configuration.general.deployId;
|
||||
const execute = packs[configuration.build.pack];
|
||||
if (execute) {
|
||||
await Deployment.findOneAndUpdate(
|
||||
{ repoId: id, branch, deployId, organization, name, domain },
|
||||
{ repoId: id, branch, deployId, organization, name, domain, progress: 'inprogress' }
|
||||
);
|
||||
await saveAppLog('### Building application.', configuration);
|
||||
await execute(configuration);
|
||||
await saveAppLog('### Building done.', configuration);
|
||||
} else {
|
||||
try {
|
||||
await Deployment.findOneAndUpdate(
|
||||
{ repoId: id, branch, deployId, organization, name, domain },
|
||||
{ repoId: id, branch, deployId, organization, name, domain, progress: 'failed' }
|
||||
);
|
||||
} catch (error) {
|
||||
// Hmm.
|
||||
}
|
||||
throw new Error('No buildpack found.');
|
||||
}
|
||||
}
|
@ -1,66 +0,0 @@
|
||||
import { docker } from '$lib/api/docker';
|
||||
import Deployment from '$models/Deployment';
|
||||
import { execShellAsync } from '../common';
|
||||
import crypto from 'crypto';
|
||||
export async function deleteSameDeployments(configuration, originalDomain = null) {
|
||||
await (
|
||||
await docker.engine.listServices()
|
||||
)
|
||||
.filter((r) => r.Spec.Labels.managedBy === 'coolify' && r.Spec.Labels.type === 'application')
|
||||
.map(async (s) => {
|
||||
const running = JSON.parse(s.Spec.Labels.configuration);
|
||||
if (
|
||||
running.repository.id === configuration.repository.id &&
|
||||
running.repository.branch === configuration.repository.branch &&
|
||||
running.publish.domain === originalDomain || configuration.publish.domain
|
||||
) {
|
||||
await execShellAsync(`docker stack rm ${s.Spec.Labels['com.docker.stack.namespace']}`);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export async function cleanupStuckedDeploymentsInDB() {
|
||||
// Cleanup stucked deployments.
|
||||
await Deployment.updateMany(
|
||||
{ progress: { $in: ['queued', 'inprogress'] } },
|
||||
{ progress: 'failed' }
|
||||
);
|
||||
}
|
||||
export async function purgeImagesContainers(configuration, deleteAll = false) {
|
||||
const { name, tag } = configuration.build.container;
|
||||
try {
|
||||
await execShellAsync('docker container prune -f');
|
||||
} catch (error) {
|
||||
//
|
||||
}
|
||||
try {
|
||||
if (deleteAll) {
|
||||
const IDsToDelete = (
|
||||
await execShellAsync(
|
||||
`docker images ls --filter=reference='${name}' --format '{{json .ID }}'`
|
||||
)
|
||||
)
|
||||
.trim()
|
||||
.replace(/"/g, '')
|
||||
.split('\n');
|
||||
if (IDsToDelete.length > 0) await execShellAsync(`docker rmi -f ${IDsToDelete.join(' ')}`);
|
||||
} else {
|
||||
const IDsToDelete = (
|
||||
await execShellAsync(
|
||||
`docker images ls --filter=reference='${name}' --filter=before='${name}:${tag}' --format '{{json .ID }}'`
|
||||
)
|
||||
)
|
||||
.trim()
|
||||
.replace(/"/g, '')
|
||||
.split('\n');
|
||||
if (IDsToDelete.length > 1) await execShellAsync(`docker rmi -f ${IDsToDelete.join(' ')}`);
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
try {
|
||||
await execShellAsync('docker image prune -f');
|
||||
} catch (error) {
|
||||
//
|
||||
}
|
||||
}
|
@ -1,54 +0,0 @@
|
||||
import jsonwebtoken from 'jsonwebtoken';
|
||||
import { execShellAsync } from '../common';
|
||||
|
||||
export default async function (configuration) {
|
||||
try {
|
||||
const { GITHUB_APP_PRIVATE_KEY } = process.env;
|
||||
const { workdir, isPreviewDeploymentEnabled, pullRequest } = configuration.general;
|
||||
const { organization, name, branch } = configuration.repository;
|
||||
const github = configuration.github;
|
||||
if (!github.installation.id || !github.app.id) {
|
||||
throw new Error('Github installation ID is invalid.');
|
||||
}
|
||||
const githubPrivateKey = GITHUB_APP_PRIVATE_KEY.replace(/\\n/g, '\n').replace(/"/g, '');
|
||||
|
||||
const payload = {
|
||||
iat: Math.round(new Date().getTime() / 1000),
|
||||
exp: Math.round(new Date().getTime() / 1000 + 60),
|
||||
iss: parseInt(github.app.id)
|
||||
};
|
||||
|
||||
const jwtToken = jsonwebtoken.sign(payload, githubPrivateKey, {
|
||||
algorithm: 'RS256'
|
||||
});
|
||||
|
||||
const { token } = await (
|
||||
await fetch(
|
||||
`https://api.github.com/app/installations/${github.installation.id}/access_tokens`,
|
||||
{
|
||||
method: 'POST',
|
||||
headers: {
|
||||
Authorization: 'Bearer ' + jwtToken,
|
||||
Accept: 'application/vnd.github.machine-man-preview+json'
|
||||
}
|
||||
}
|
||||
)
|
||||
).json();
|
||||
await execShellAsync(
|
||||
`mkdir -p ${workdir} && git clone -q -b ${branch} https://x-access-token:${token}@github.com/${organization}/${name}.git ${workdir}/`
|
||||
);
|
||||
|
||||
if (isPreviewDeploymentEnabled && pullRequest && pullRequest !== 0) {
|
||||
await execShellAsync(
|
||||
`cd ${workdir} && git fetch origin pull/${pullRequest}/head:pull_${pullRequest} && git checkout pull_${pullRequest}`
|
||||
);
|
||||
}
|
||||
configuration.build.container.tag = (
|
||||
await execShellAsync(`cd ${workdir}/ && git rev-parse HEAD`)
|
||||
)
|
||||
.replace('\n', '')
|
||||
.slice(0, 7);
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
}
|
@ -1,18 +0,0 @@
|
||||
export const baseServiceConfiguration = {
|
||||
replicas: 1,
|
||||
restart_policy: {
|
||||
condition: 'any',
|
||||
max_attempts: 6
|
||||
},
|
||||
update_config: {
|
||||
parallelism: 1,
|
||||
delay: '10s',
|
||||
order: 'start-first'
|
||||
},
|
||||
rollback_config: {
|
||||
parallelism: 1,
|
||||
delay: '10s',
|
||||
order: 'start-first',
|
||||
failure_action: 'rollback'
|
||||
}
|
||||
};
|
@ -1,198 +0,0 @@
|
||||
import cuid from 'cuid';
|
||||
import crypto from 'crypto';
|
||||
import { uniqueNamesGenerator, adjectives, colors, animals } from 'unique-names-generator';
|
||||
import { docker } from '$lib/api/docker';
|
||||
import { baseServiceConfiguration } from './common';
|
||||
import { execShellAsync } from '../common';
|
||||
import { promises as fs } from 'fs';
|
||||
import Configuration from '$models/Configuration';
|
||||
function getUniq() {
|
||||
return uniqueNamesGenerator({ dictionaries: [adjectives, animals, colors], length: 2 });
|
||||
}
|
||||
|
||||
export function setDefaultConfiguration(configuration) {
|
||||
const nickname = configuration.general.nickname || getUniq();
|
||||
const deployId = cuid();
|
||||
const shaBase = JSON.stringify({ path: configuration.publish.path, domain: configuration.publish.domain });
|
||||
const sha256 = crypto.createHash('sha256').update(shaBase).digest('hex');
|
||||
|
||||
configuration.build.container.name = sha256.slice(0, 15);
|
||||
|
||||
configuration.general.nickname = nickname;
|
||||
configuration.general.deployId = deployId;
|
||||
configuration.general.workdir = `/tmp/${deployId}`;
|
||||
if (configuration.general.isPreviewDeploymentEnabled && configuration.general.pullRequest !== 0) {
|
||||
configuration.build.container.name = `pr${configuration.general.pullRequest}-${sha256.slice(
|
||||
0,
|
||||
8
|
||||
)}`;
|
||||
configuration.publish.domain = `pr${configuration.general.pullRequest}.${configuration.publish.domain}`;
|
||||
}
|
||||
if (!configuration.publish.path) configuration.publish.path = '/';
|
||||
if (!configuration.publish.port) {
|
||||
if (
|
||||
configuration.build.pack === 'nodejs' ||
|
||||
configuration.build.pack === 'nuxtjs' ||
|
||||
configuration.build.pack === 'rust' ||
|
||||
configuration.build.pack === 'nextjs' ||
|
||||
configuration.build.pack === 'nestjs'
|
||||
) {
|
||||
configuration.publish.port = 3000;
|
||||
} else if (configuration.build.pack === 'python') {
|
||||
configuration.publish.port = 4000;
|
||||
} else {
|
||||
configuration.publish.port = 80;
|
||||
}
|
||||
}
|
||||
if (!configuration.build.directory) configuration.build.directory = '';
|
||||
if (configuration.build.directory.startsWith('/'))
|
||||
configuration.build.directory = configuration.build.directory.replace('/', '');
|
||||
|
||||
if (!configuration.publish.directory) configuration.publish.directory = '';
|
||||
if (configuration.publish.directory.startsWith('/'))
|
||||
configuration.publish.directory = configuration.publish.directory.replace('/', '');
|
||||
|
||||
if (configuration.build.pack === 'nodejs') {
|
||||
if (!configuration.build.command.installation)
|
||||
configuration.build.command.installation = 'yarn install';
|
||||
}
|
||||
if (
|
||||
configuration.build.pack === 'nodejs' ||
|
||||
configuration.build.pack === 'vuejs' ||
|
||||
configuration.build.pack === 'nuxtjs' ||
|
||||
configuration.build.pack === 'nextjs' ||
|
||||
configuration.build.pack === 'nestjs'
|
||||
) {
|
||||
if (!configuration.build.command.start) configuration.build.command.start = 'yarn start';
|
||||
}
|
||||
if (configuration.build.pack === 'python') {
|
||||
if (!configuration.build.command.python.module)
|
||||
configuration.build.command.python.module = 'main';
|
||||
if (!configuration.build.command.python.instance)
|
||||
configuration.build.command.python.instance = 'app';
|
||||
}
|
||||
|
||||
configuration.build.container.baseSHA = crypto
|
||||
.createHash('sha256')
|
||||
.update(JSON.stringify(baseServiceConfiguration))
|
||||
.digest('hex');
|
||||
configuration.baseServiceConfiguration = baseServiceConfiguration;
|
||||
|
||||
return configuration;
|
||||
}
|
||||
|
||||
export async function precheckDeployment(configuration) {
|
||||
const services = await Configuration.find({
|
||||
'publish.domain': configuration.publish.domain,
|
||||
'publish.path': configuration.publish.path
|
||||
})
|
||||
// const services = (await docker.engine.listServices()).filter(
|
||||
// (r) =>
|
||||
// r.Spec.Labels.managedBy === 'coolify' &&
|
||||
// r.Spec.Labels.type === 'application' &&
|
||||
// JSON.parse(r.Spec.Labels.configuration).publish.domain === configuration.publish.domain
|
||||
// );
|
||||
let foundService = false;
|
||||
let configChanged = false;
|
||||
let imageChanged = false;
|
||||
let forceUpdate = false;
|
||||
for (const service of services) {
|
||||
// const running = JSON.parse(service.Spec.Labels.configuration);
|
||||
if (
|
||||
service.repository.id === configuration.repository.id &&
|
||||
service.repository.branch === configuration.repository.branch
|
||||
) {
|
||||
foundService = true;
|
||||
// Base service configuration changed
|
||||
if (
|
||||
!service.build.container.baseSHA ||
|
||||
service.build.container.baseSHA !== configuration.build.container.baseSHA
|
||||
) {
|
||||
forceUpdate = true;
|
||||
}
|
||||
// If the deployment is in error state, forceUpdate
|
||||
const state = await execShellAsync(
|
||||
`docker stack ps ${service.build.container.name} --format '{{ json . }}'`
|
||||
);
|
||||
const isError = state
|
||||
.split('\n')
|
||||
.filter((n) => n)
|
||||
.map((s) => JSON.parse(s))
|
||||
.filter(
|
||||
(n) =>
|
||||
n.DesiredState !== 'Running' && n.Image.split(':')[1] === service.build.container.tag
|
||||
);
|
||||
if (isError.length > 0) {
|
||||
forceUpdate = true;
|
||||
}
|
||||
|
||||
const compareObjects = (a, b) => {
|
||||
if (a === b) return true;
|
||||
|
||||
if (typeof a != 'object' || typeof b != 'object' || a == null || b == null) return false;
|
||||
|
||||
const keysA = Object.keys(a),
|
||||
keysB = Object.keys(b);
|
||||
|
||||
if (keysA.length != keysB.length) return false;
|
||||
|
||||
for (const key of keysA) {
|
||||
if (!keysB.includes(key)) return false;
|
||||
|
||||
if (typeof a[key] === 'function' || typeof b[key] === 'function') {
|
||||
if (a[key].toString() != b[key].toString()) return false;
|
||||
} else {
|
||||
if (!compareObjects(a[key], b[key])) return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
const runningWithoutContainer = JSON.parse(JSON.stringify(service));
|
||||
delete runningWithoutContainer.build.container;
|
||||
|
||||
const configurationWithoutContainer = JSON.parse(JSON.stringify(configuration));
|
||||
delete configurationWithoutContainer.build.container;
|
||||
|
||||
// If only the configuration changed
|
||||
if (
|
||||
!compareObjects(runningWithoutContainer.build, configurationWithoutContainer.build) ||
|
||||
!compareObjects(runningWithoutContainer.publish, configurationWithoutContainer.publish) ||
|
||||
runningWithoutContainer.general.isPreviewDeploymentEnabled !==
|
||||
configurationWithoutContainer.general.isPreviewDeploymentEnabled
|
||||
) {
|
||||
configChanged = true;
|
||||
}
|
||||
|
||||
// If only the image changed
|
||||
if (service.build.container.tag !== configuration.build.container.tag) imageChanged = true;
|
||||
// If build pack changed, forceUpdate the service
|
||||
if (service.build.pack !== configuration.build.pack) forceUpdate = true;
|
||||
if (
|
||||
configuration.general.isPreviewDeploymentEnabled &&
|
||||
configuration.general.pullRequest !== 0
|
||||
)
|
||||
forceUpdate = true;
|
||||
}
|
||||
|
||||
}
|
||||
if (forceUpdate) {
|
||||
imageChanged = false;
|
||||
configChanged = false;
|
||||
}
|
||||
return {
|
||||
foundService,
|
||||
imageChanged,
|
||||
configChanged,
|
||||
forceUpdate
|
||||
};
|
||||
}
|
||||
|
||||
export async function updateServiceLabels(configuration) {
|
||||
return await execShellAsync(
|
||||
`docker service update --label-add configuration='${JSON.stringify(configuration)}' ${
|
||||
configuration.build.container.name
|
||||
}_${configuration.build.container.name}`
|
||||
);
|
||||
}
|
@ -1,69 +0,0 @@
|
||||
import { promises as fs } from 'fs';
|
||||
export default async function (configuration) {
|
||||
const staticDeployments = ['react', 'vuejs', 'static', 'svelte', 'gatsby'];
|
||||
try {
|
||||
// TODO: Write full .dockerignore for all deployments!!
|
||||
if (configuration.build.pack === 'php') {
|
||||
await fs.writeFile(
|
||||
`${configuration.general.workdir}/.htaccess`,
|
||||
`
|
||||
RewriteEngine On
|
||||
RewriteBase /
|
||||
RewriteCond %{REQUEST_FILENAME} !-d
|
||||
RewriteCond %{REQUEST_FILENAME} !-f
|
||||
RewriteRule ^(.+)$ index.php [QSA,L]
|
||||
`
|
||||
);
|
||||
}
|
||||
// await fs.writeFile(`${configuration.general.workdir}/.dockerignore`, 'node_modules')
|
||||
if (staticDeployments.includes(configuration.build.pack)) {
|
||||
await fs.writeFile(
|
||||
`${configuration.general.workdir}/nginx.conf`,
|
||||
`user nginx;
|
||||
worker_processes auto;
|
||||
|
||||
error_log /var/log/nginx/error.log warn;
|
||||
pid /var/run/nginx.pid;
|
||||
|
||||
events {
|
||||
worker_connections 1024;
|
||||
}
|
||||
|
||||
http {
|
||||
include /etc/nginx/mime.types;
|
||||
|
||||
access_log off;
|
||||
sendfile on;
|
||||
#tcp_nopush on;
|
||||
keepalive_timeout 65;
|
||||
|
||||
server {
|
||||
listen 80;
|
||||
server_name localhost;
|
||||
|
||||
location / {
|
||||
root /usr/share/nginx/html;
|
||||
index index.html;
|
||||
try_files $uri $uri/index.html $uri/ /index.html =404;
|
||||
}
|
||||
|
||||
error_page 404 /50x.html;
|
||||
|
||||
# redirect server error pages to the static page /50x.html
|
||||
#
|
||||
error_page 500 502 503 504 /50x.html;
|
||||
location = /50x.html {
|
||||
root /usr/share/nginx/html;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
`
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
throw new Error(error);
|
||||
}
|
||||
}
|
@ -1,80 +0,0 @@
|
||||
import { docker } from '$lib/api/docker';
|
||||
import { saveAppLog } from './logging';
|
||||
import { promises as fs } from 'fs';
|
||||
import { deleteSameDeployments, purgeImagesContainers } from './cleanup';
|
||||
import yaml from 'js-yaml';
|
||||
import { delay, execShellAsync } from '../common';
|
||||
|
||||
export default async function (configuration, nextStep) {
|
||||
const generateEnvs = {};
|
||||
for (const secret of configuration.publish.secrets) {
|
||||
generateEnvs[secret.name] = secret.value;
|
||||
}
|
||||
const containerName = configuration.build.container.name;
|
||||
const containerTag = configuration.build.container.tag;
|
||||
|
||||
// Only save SHA256 of it in the configuration label
|
||||
const baseServiceConfiguration = configuration.baseServiceConfiguration;
|
||||
delete configuration.baseServiceConfiguration;
|
||||
|
||||
const stack = {
|
||||
version: '3.8',
|
||||
services: {
|
||||
[containerName]: {
|
||||
image: `${containerName}:${containerTag}`,
|
||||
networks: [`${docker.network}`],
|
||||
environment: generateEnvs,
|
||||
deploy: {
|
||||
...baseServiceConfiguration,
|
||||
labels: [
|
||||
'managedBy=coolify',
|
||||
'type=application',
|
||||
'configuration=' + JSON.stringify(configuration),
|
||||
'traefik.enable=true',
|
||||
'traefik.http.services.' +
|
||||
containerName +
|
||||
`.loadbalancer.server.port=${configuration.publish.port}`,
|
||||
'traefik.http.routers.' + containerName + '.entrypoints=websecure',
|
||||
'traefik.http.routers.' +
|
||||
containerName +
|
||||
'.rule=Host(`' +
|
||||
configuration.publish.domain +
|
||||
'`) && PathPrefix(`' +
|
||||
configuration.publish.path +
|
||||
'`)',
|
||||
'traefik.http.routers.' + containerName + '.tls.certresolver=letsencrypt',
|
||||
'traefik.http.routers.' + containerName + '.middlewares=global-compress'
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
networks: {
|
||||
[`${docker.network}`]: {
|
||||
external: true
|
||||
}
|
||||
}
|
||||
};
|
||||
await saveAppLog('### Publishing.', configuration);
|
||||
await fs.writeFile(`${configuration.general.workdir}/stack.yml`, yaml.dump(stack));
|
||||
if (nextStep === 2) {
|
||||
// console.log('image changed')
|
||||
await execShellAsync(
|
||||
`docker service update --image ${containerName}:${containerTag} ${containerName}_${containerName}`
|
||||
);
|
||||
} else {
|
||||
// console.log('new deployment or force deployment or config changed')
|
||||
// if (originalDomain !== configuration.publish.domain) {
|
||||
// await deleteSameDeployments(configuration, originalDomain);
|
||||
// } else {
|
||||
// await deleteSameDeployments(configuration);
|
||||
// }
|
||||
// await deleteSameDeployments(configuration);
|
||||
await execShellAsync(
|
||||
`cat ${configuration.general.workdir}/stack.yml | docker stack deploy --prune -c - ${containerName}`
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
|
||||
await saveAppLog('### Published done!', configuration);
|
||||
}
|
@ -1,58 +0,0 @@
|
||||
import Settings from '$models/Settings';
|
||||
import ServerLog from '$models/ServerLog';
|
||||
import ApplicationLog from '$models/ApplicationLog';
|
||||
import dayjs from 'dayjs';
|
||||
import { version } from '../../../../package.json';
|
||||
|
||||
function generateTimestamp() {
|
||||
return `${dayjs().format('YYYY-MM-DD HH:mm:ss.SSS')} `;
|
||||
}
|
||||
const patterns = [
|
||||
'[\\u001B\\u009B][[\\]()#;?]*(?:(?:(?:[a-zA-Z\\d]*(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]*)*)?\\u0007)',
|
||||
'(?:(?:\\d{1,4}(?:;\\d{0,4})*)?[\\dA-PR-TZcf-ntqry=><~]))'
|
||||
].join('|');
|
||||
|
||||
export async function saveAppLog(event, configuration, isError?: boolean) {
|
||||
try {
|
||||
const deployId = configuration.general.deployId;
|
||||
const repoId = configuration.repository.id;
|
||||
const branch = configuration.repository.branch;
|
||||
if (isError) {
|
||||
const clearedEvent =
|
||||
'[ERROR 😱] ' +
|
||||
generateTimestamp() +
|
||||
event.replace(new RegExp(patterns, 'g'), '').replace(/(\r\n|\n|\r)/gm, '');
|
||||
await new ApplicationLog({ repoId, branch, deployId, event: clearedEvent }).save();
|
||||
} else {
|
||||
if (event && event !== '\n') {
|
||||
const clearedEvent =
|
||||
'[INFO] ' +
|
||||
generateTimestamp() +
|
||||
event.replace(new RegExp(patterns, 'g'), '').replace(/(\r\n|\n|\r)/gm, '');
|
||||
await new ApplicationLog({ repoId, branch, deployId, event: clearedEvent }).save();
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
return error;
|
||||
}
|
||||
}
|
||||
|
||||
export async function saveServerLog(error) {
|
||||
const settings = await Settings.findOne({ applicationName: 'coolify' });
|
||||
const payload = {
|
||||
message: error.message,
|
||||
stack: error.stack,
|
||||
type: error.type || 'spaghetticode',
|
||||
version
|
||||
};
|
||||
|
||||
const found = await ServerLog.find(payload);
|
||||
if (found.length === 0 && error.message) await new ServerLog(payload).save();
|
||||
if (settings && settings.sendErrors && process.env.NODE_ENV === 'production') {
|
||||
await fetch('https://errors.coollabs.io/api/error', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ ...payload })
|
||||
});
|
||||
}
|
||||
}
|
@ -1,17 +0,0 @@
|
||||
import { docker, streamEvents } from '$lib/api/docker';
|
||||
import { promises as fs } from 'fs';
|
||||
|
||||
export default async function (configuration) {
|
||||
const path = `${configuration.general.workdir}/${
|
||||
configuration.build.directory ? configuration.build.directory : ''
|
||||
}`;
|
||||
if (fs.stat(`${path}/Dockerfile`)) {
|
||||
const stream = await docker.engine.buildImage(
|
||||
{ src: ['.'], context: path },
|
||||
{ t: `${configuration.build.container.name}:${configuration.build.container.tag}` }
|
||||
);
|
||||
await streamEvents(stream, configuration);
|
||||
} else {
|
||||
throw new Error('No custom dockerfile found.');
|
||||
}
|
||||
}
|
@ -1,28 +0,0 @@
|
||||
import { docker, streamEvents } from '$lib/api/docker';
|
||||
import { promises as fs } from 'fs';
|
||||
import { buildImage } from '../helpers';
|
||||
|
||||
// 'HEALTHCHECK --timeout=10s --start-period=10s --interval=5s CMD curl -I -s -f http://localhost/ || exit 1',
|
||||
const publishStaticDocker = (configuration) => {
|
||||
return [
|
||||
'FROM nginx:stable-alpine',
|
||||
'COPY nginx.conf /etc/nginx/nginx.conf',
|
||||
'WORKDIR /usr/share/nginx/html',
|
||||
`COPY --from=${configuration.build.container.name}:${configuration.build.container.tag}-cache /usr/src/app/${configuration.publish.directory} ./`,
|
||||
'EXPOSE 80',
|
||||
'CMD ["nginx", "-g", "daemon off;"]'
|
||||
].join('\n');
|
||||
};
|
||||
|
||||
export default async function (configuration) {
|
||||
await buildImage(configuration, true);
|
||||
await fs.writeFile(
|
||||
`${configuration.general.workdir}/Dockerfile`,
|
||||
publishStaticDocker(configuration)
|
||||
);
|
||||
const stream = await docker.engine.buildImage(
|
||||
{ src: ['.'], context: configuration.general.workdir },
|
||||
{ t: `${configuration.build.container.name}:${configuration.build.container.tag}` }
|
||||
);
|
||||
await streamEvents(stream, configuration);
|
||||
}
|
@ -1,43 +0,0 @@
|
||||
import { docker, streamEvents } from '$lib/api/docker';
|
||||
import { promises as fs } from 'fs';
|
||||
|
||||
const buildImageNodeDocker = (configuration, prodBuild, generateEnvs) => {
|
||||
return [
|
||||
'FROM node:lts',
|
||||
...generateEnvs,
|
||||
'WORKDIR /usr/src/app',
|
||||
`COPY ${configuration.build.directory}/package*.json ./`,
|
||||
configuration.build.command.installation && `RUN ${configuration.build.command.installation}`,
|
||||
`COPY ./${configuration.build.directory} ./`,
|
||||
`RUN ${configuration.build.command.build}`,
|
||||
prodBuild && `RUN rm -fr node_modules && ${configuration.build.command.installation} --prod`
|
||||
].join('\n');
|
||||
};
|
||||
export async function buildImage(configuration, cacheBuild?: boolean, prodBuild?: boolean) {
|
||||
// TODO: Edit secrets
|
||||
// TODO: Add secret from .env file / json
|
||||
const generateEnvs = [];
|
||||
const dotEnv = []
|
||||
for (const secret of configuration.publish.secrets) {
|
||||
dotEnv.push(`${secret.name}=${secret.value}`)
|
||||
if (secret.isBuild) generateEnvs.push(`ENV ${secret.name}=${secret.value}`)
|
||||
}
|
||||
await fs.writeFile(
|
||||
`${configuration.general.workdir}/.env`,
|
||||
dotEnv.join('\n')
|
||||
)
|
||||
await fs.writeFile(
|
||||
`${configuration.general.workdir}/Dockerfile`,
|
||||
buildImageNodeDocker(configuration, prodBuild, generateEnvs)
|
||||
);
|
||||
const stream = await docker.engine.buildImage(
|
||||
{ src: ['.'], context: configuration.general.workdir },
|
||||
{
|
||||
t: `${configuration.build.container.name}:${cacheBuild
|
||||
? `${configuration.build.container.tag}-cache`
|
||||
: configuration.build.container.tag
|
||||
}`
|
||||
}
|
||||
);
|
||||
await streamEvents(stream, configuration);
|
||||
}
|
@ -1,31 +0,0 @@
|
||||
import { docker, streamEvents } from '$lib/api/docker';
|
||||
import { promises as fs } from 'fs';
|
||||
import { buildImage } from '../helpers';
|
||||
// `HEALTHCHECK --timeout=10s --start-period=10s --interval=5s CMD curl -I -s -f http://localhost:${configuration.publish.port}${configuration.publish.path} || exit 1`,
|
||||
const publishNodejsDocker = (configuration) => {
|
||||
return [
|
||||
'FROM node:lts',
|
||||
'WORKDIR /usr/src/app',
|
||||
configuration.build.command.build
|
||||
? `COPY --from=${configuration.build.container.name}:${configuration.build.container.tag} /usr/src/app/${configuration.publish.directory} ./`
|
||||
: `
|
||||
COPY ${configuration.build.directory}/package*.json ./
|
||||
RUN ${configuration.build.command.installation}
|
||||
COPY ./${configuration.build.directory} ./`,
|
||||
`EXPOSE ${configuration.publish.port}`,
|
||||
`CMD ${configuration.build.command.start}`
|
||||
].join('\n');
|
||||
};
|
||||
|
||||
export default async function (configuration) {
|
||||
if (configuration.build.command.build) await buildImage(configuration, false, true);
|
||||
await fs.writeFile(
|
||||
`${configuration.general.workdir}/Dockerfile`,
|
||||
publishNodejsDocker(configuration)
|
||||
);
|
||||
const stream = await docker.engine.buildImage(
|
||||
{ src: ['.'], context: configuration.general.workdir },
|
||||
{ t: `${configuration.build.container.name}:${configuration.build.container.tag}` }
|
||||
);
|
||||
await streamEvents(stream, configuration);
|
||||
}
|
@ -1,30 +0,0 @@
|
||||
import { docker, streamEvents } from '$lib/api/docker';
|
||||
import { promises as fs } from 'fs';
|
||||
import { buildImage } from '../helpers';
|
||||
// `HEALTHCHECK --timeout=10s --start-period=10s --interval=5s CMD curl -I -s -f http://localhost:${configuration.publish.port}${configuration.publish.path} || exit 1`,
|
||||
const publishNodejsDocker = (configuration) => {
|
||||
return [
|
||||
'FROM node:lts',
|
||||
'WORKDIR /usr/src/app',
|
||||
configuration.build.command.build
|
||||
? `COPY --from=${configuration.build.container.name}:${configuration.build.container.tag} /usr/src/app/${configuration.publish.directory} ./`
|
||||
: `
|
||||
COPY ${configuration.build.directory}/package*.json ./
|
||||
RUN ${configuration.build.command.installation}
|
||||
COPY ./${configuration.build.directory} ./`,
|
||||
`EXPOSE ${configuration.publish.port}`,
|
||||
`CMD ${configuration.build.command.start}`
|
||||
].join('\n');
|
||||
};
|
||||
export default async function (configuration) {
|
||||
await buildImage(configuration);
|
||||
await fs.writeFile(
|
||||
`${configuration.general.workdir}/Dockerfile`,
|
||||
publishNodejsDocker(configuration)
|
||||
);
|
||||
const stream = await docker.engine.buildImage(
|
||||
{ src: ['.'], context: configuration.general.workdir },
|
||||
{ t: `${configuration.build.container.name}:${configuration.build.container.tag}` }
|
||||
);
|
||||
await streamEvents(stream, configuration);
|
||||
}
|
@ -1,31 +0,0 @@
|
||||
import { docker, streamEvents } from '$lib/api/docker';
|
||||
import { promises as fs } from 'fs';
|
||||
import { buildImage } from '../helpers';
|
||||
// `HEALTHCHECK --timeout=10s --start-period=10s --interval=5s CMD curl -I -s -f http://localhost:${configuration.publish.port}${configuration.publish.path} || exit 1`,
|
||||
const publishNodejsDocker = (configuration) => {
|
||||
return [
|
||||
'FROM node:lts',
|
||||
'WORKDIR /usr/src/app',
|
||||
configuration.build.command.build
|
||||
? `COPY --from=${configuration.build.container.name}:${configuration.build.container.tag} /usr/src/app/${configuration.publish.directory} ./`
|
||||
: `
|
||||
COPY ${configuration.build.directory}/package*.json ./
|
||||
RUN ${configuration.build.command.installation}
|
||||
COPY ./${configuration.build.directory} ./`,
|
||||
`EXPOSE ${configuration.publish.port}`,
|
||||
`CMD ${configuration.build.command.start}`
|
||||
].join('\n');
|
||||
};
|
||||
|
||||
export default async function (configuration) {
|
||||
if (configuration.build.command.build) await buildImage(configuration);
|
||||
await fs.writeFile(
|
||||
`${configuration.general.workdir}/Dockerfile`,
|
||||
publishNodejsDocker(configuration)
|
||||
);
|
||||
const stream = await docker.engine.buildImage(
|
||||
{ src: ['.'], context: configuration.general.workdir },
|
||||
{ t: `${configuration.build.container.name}:${configuration.build.container.tag}` }
|
||||
);
|
||||
await streamEvents(stream, configuration);
|
||||
}
|
@ -1,31 +0,0 @@
|
||||
import { docker, streamEvents } from '$lib/api/docker';
|
||||
import { promises as fs } from 'fs';
|
||||
import { buildImage } from '../helpers';
|
||||
// `HEALTHCHECK --timeout=10s --start-period=10s --interval=5s CMD curl -I -s -f http://localhost:${configuration.publish.port}${configuration.publish.path} || exit 1`,
|
||||
const publishNodejsDocker = (configuration) => {
|
||||
return [
|
||||
'FROM node:lts',
|
||||
'WORKDIR /usr/src/app',
|
||||
configuration.build.command.build
|
||||
? `COPY --from=${configuration.build.container.name}:${configuration.build.container.tag} /usr/src/app/${configuration.publish.directory} ./`
|
||||
: `
|
||||
COPY ${configuration.build.directory}/package*.json ./
|
||||
RUN ${configuration.build.command.installation}
|
||||
COPY ./${configuration.build.directory} ./`,
|
||||
`EXPOSE ${configuration.publish.port}`,
|
||||
`CMD ${configuration.build.command.start}`
|
||||
].join('\n');
|
||||
};
|
||||
|
||||
export default async function (configuration) {
|
||||
await buildImage(configuration);
|
||||
await fs.writeFile(
|
||||
`${configuration.general.workdir}/Dockerfile`,
|
||||
publishNodejsDocker(configuration)
|
||||
);
|
||||
const stream = await docker.engine.buildImage(
|
||||
{ src: ['.'], context: configuration.general.workdir },
|
||||
{ t: `${configuration.build.container.name}:${configuration.build.container.tag}` }
|
||||
);
|
||||
await streamEvents(stream, configuration);
|
||||
}
|
@ -1,25 +0,0 @@
|
||||
import { docker, streamEvents } from '$lib/api/docker';
|
||||
import { promises as fs } from 'fs';
|
||||
// 'HEALTHCHECK --timeout=10s --start-period=10s --interval=5s CMD curl -I -s -f http://localhost/ || exit 1',
|
||||
const publishPHPDocker = (configuration) => {
|
||||
return [
|
||||
'FROM php:apache',
|
||||
'RUN a2enmod rewrite',
|
||||
'WORKDIR /usr/src/app',
|
||||
`COPY ./${configuration.build.directory} /var/www/html`,
|
||||
'EXPOSE 80',
|
||||
'CMD ["apache2-foreground"]'
|
||||
].join('\n');
|
||||
};
|
||||
|
||||
export default async function (configuration) {
|
||||
await fs.writeFile(
|
||||
`${configuration.general.workdir}/Dockerfile`,
|
||||
publishPHPDocker(configuration)
|
||||
);
|
||||
const stream = await docker.engine.buildImage(
|
||||
{ src: ['.'], context: configuration.general.workdir },
|
||||
{ t: `${configuration.build.container.name}:${configuration.build.container.tag}` }
|
||||
);
|
||||
await streamEvents(stream, configuration);
|
||||
}
|
@ -1,24 +0,0 @@
|
||||
import { docker, streamEvents } from '$lib/api/docker';
|
||||
import { promises as fs } from 'fs';
|
||||
// `HEALTHCHECK --timeout=10s --start-period=10s --interval=5s CMD curl -I -s -f http://localhost:${configuration.publish.port}${configuration.publish.path} || exit 1`,
|
||||
const publishPython = (configuration) => {
|
||||
return [
|
||||
'FROM python:3-alpine',
|
||||
'WORKDIR /usr/src/app',
|
||||
'RUN pip install gunicorn',
|
||||
`COPY ./${configuration.build.directory}/requirements.txt ./`,
|
||||
`RUN pip install --no-cache-dir -r ./${configuration.build.directory}/requirements.txt`,
|
||||
`COPY ./${configuration.build.directory}/ .`,
|
||||
`EXPOSE ${configuration.publish.port}`,
|
||||
`CMD gunicorn -w=4 ${configuration.build.command.python.module}:${configuration.build.command.python.instance}`
|
||||
].join('\n');
|
||||
};
|
||||
|
||||
export default async function (configuration) {
|
||||
await fs.writeFile(`${configuration.general.workdir}/Dockerfile`, publishPython(configuration));
|
||||
const stream = await docker.engine.buildImage(
|
||||
{ src: ['.'], context: configuration.general.workdir },
|
||||
{ t: `${configuration.build.container.name}:${configuration.build.container.tag}` }
|
||||
);
|
||||
await streamEvents(stream, configuration);
|
||||
}
|
@ -1,28 +0,0 @@
|
||||
import { docker, streamEvents } from '$lib/api/docker';
|
||||
import { promises as fs } from 'fs';
|
||||
import { buildImage } from '../helpers';
|
||||
|
||||
// 'HEALTHCHECK --timeout=10s --start-period=10s --interval=5s CMD curl -I -s -f http://localhost/ || exit 1',
|
||||
const publishStaticDocker = (configuration) => {
|
||||
return [
|
||||
'FROM nginx:stable-alpine',
|
||||
'COPY nginx.conf /etc/nginx/nginx.conf',
|
||||
'WORKDIR /usr/share/nginx/html',
|
||||
`COPY --from=${configuration.build.container.name}:${configuration.build.container.tag}-cache /usr/src/app/${configuration.publish.directory} ./`,
|
||||
'EXPOSE 80',
|
||||
'CMD ["nginx", "-g", "daemon off;"]'
|
||||
].join('\n');
|
||||
};
|
||||
|
||||
export default async function (configuration) {
|
||||
await buildImage(configuration, true);
|
||||
await fs.writeFile(
|
||||
`${configuration.general.workdir}/Dockerfile`,
|
||||
publishStaticDocker(configuration)
|
||||
);
|
||||
const stream = await docker.engine.buildImage(
|
||||
{ src: ['.'], context: configuration.general.workdir },
|
||||
{ t: `${configuration.build.container.name}:${configuration.build.container.tag}` }
|
||||
);
|
||||
await streamEvents(stream, configuration);
|
||||
}
|
@ -1,66 +0,0 @@
|
||||
import { docker, streamEvents } from '$lib/api/docker';
|
||||
import { promises as fs } from 'fs';
|
||||
import TOML from '@iarna/toml';
|
||||
import { execShellAsync } from '$lib/api/common';
|
||||
|
||||
const publishRustDocker = (configuration, custom) => {
|
||||
return [
|
||||
'FROM rust:latest',
|
||||
'WORKDIR /app',
|
||||
`COPY --from=${configuration.build.container.name}:cache /app/target target`,
|
||||
`COPY --from=${configuration.build.container.name}:cache /usr/local/cargo /usr/local/cargo`,
|
||||
'COPY . .',
|
||||
`RUN cargo build --release --bin ${custom.name}`,
|
||||
'FROM debian:buster-slim',
|
||||
'WORKDIR /app',
|
||||
'RUN apt-get update -y && apt-get install -y --no-install-recommends openssl libcurl4 ca-certificates && apt-get autoremove -y && apt-get clean -y && rm -rf /var/lib/apt/lists/*',
|
||||
'RUN update-ca-certificates',
|
||||
`COPY --from=${configuration.build.container.name}:cache /app/target/release/${custom.name} ${custom.name}`,
|
||||
`EXPOSE ${configuration.publish.port}`,
|
||||
`CMD ["/app/${custom.name}"]`
|
||||
].join('\n');
|
||||
};
|
||||
|
||||
const cacheRustDocker = (configuration, custom) => {
|
||||
return [
|
||||
`FROM rust:latest AS planner-${configuration.build.container.name}`,
|
||||
'WORKDIR /app',
|
||||
'RUN cargo install cargo-chef',
|
||||
'COPY . .',
|
||||
'RUN cargo chef prepare --recipe-path recipe.json',
|
||||
'FROM rust:latest',
|
||||
'WORKDIR /app',
|
||||
'RUN cargo install cargo-chef',
|
||||
`COPY --from=planner-${configuration.build.container.name} /app/recipe.json recipe.json`,
|
||||
'RUN cargo chef cook --release --recipe-path recipe.json'
|
||||
].join('\n');
|
||||
};
|
||||
|
||||
export default async function (configuration) {
|
||||
const cargoToml = await execShellAsync(`cat ${configuration.general.workdir}/Cargo.toml`);
|
||||
const parsedToml = TOML.parse(cargoToml);
|
||||
const custom = {
|
||||
name: parsedToml.package.name
|
||||
};
|
||||
await fs.writeFile(
|
||||
`${configuration.general.workdir}/Dockerfile`,
|
||||
cacheRustDocker(configuration, custom)
|
||||
);
|
||||
|
||||
let stream = await docker.engine.buildImage(
|
||||
{ src: ['.'], context: configuration.general.workdir },
|
||||
{ t: `${configuration.build.container.name}:cache` }
|
||||
);
|
||||
await streamEvents(stream, configuration);
|
||||
|
||||
await fs.writeFile(
|
||||
`${configuration.general.workdir}/Dockerfile`,
|
||||
publishRustDocker(configuration, custom)
|
||||
);
|
||||
|
||||
stream = await docker.engine.buildImage(
|
||||
{ src: ['.'], context: configuration.general.workdir },
|
||||
{ t: `${configuration.build.container.name}:${configuration.build.container.tag}` }
|
||||
);
|
||||
await streamEvents(stream, configuration);
|
||||
}
|
@ -1,30 +0,0 @@
|
||||
import { docker, streamEvents } from '$lib/api/docker';
|
||||
import { promises as fs } from 'fs';
|
||||
import { buildImage } from '../helpers';
|
||||
|
||||
// 'HEALTHCHECK --timeout=10s --start-period=10s --interval=5s CMD curl -I -s -f http://localhost/ || exit 1',
|
||||
const publishStaticDocker = (configuration) => {
|
||||
return [
|
||||
'FROM nginx:stable-alpine',
|
||||
'COPY nginx.conf /etc/nginx/nginx.conf',
|
||||
'WORKDIR /usr/share/nginx/html',
|
||||
configuration.build.command.build
|
||||
? `COPY --from=${configuration.build.container.name}:${configuration.build.container.tag}-cache /usr/src/app/${configuration.publish.directory} ./`
|
||||
: `COPY ./${configuration.build.directory} ./`,
|
||||
'EXPOSE 80',
|
||||
'CMD ["nginx", "-g", "daemon off;"]'
|
||||
].join('\n');
|
||||
};
|
||||
|
||||
export default async function (configuration) {
|
||||
if (configuration.build.command.build) await buildImage(configuration, true);
|
||||
await fs.writeFile(
|
||||
`${configuration.general.workdir}/Dockerfile`,
|
||||
publishStaticDocker(configuration)
|
||||
);
|
||||
const stream = await docker.engine.buildImage(
|
||||
{ src: ['.'], context: configuration.general.workdir },
|
||||
{ t: `${configuration.build.container.name}:${configuration.build.container.tag}` }
|
||||
);
|
||||
await streamEvents(stream, configuration);
|
||||
}
|
@ -1,28 +0,0 @@
|
||||
import { docker, streamEvents } from '$lib/api/docker';
|
||||
import { promises as fs } from 'fs';
|
||||
import { buildImage } from '../helpers';
|
||||
|
||||
// 'HEALTHCHECK --timeout=10s --start-period=10s --interval=5s CMD curl -I -s -f http://localhost/ || exit 1',
|
||||
const publishStaticDocker = (configuration) => {
|
||||
return [
|
||||
'FROM nginx:stable-alpine',
|
||||
'COPY nginx.conf /etc/nginx/nginx.conf',
|
||||
'WORKDIR /usr/share/nginx/html',
|
||||
`COPY --from=${configuration.build.container.name}:${configuration.build.container.tag}-cache /usr/src/app/${configuration.publish.directory} ./`,
|
||||
'EXPOSE 80',
|
||||
'CMD ["nginx", "-g", "daemon off;"]'
|
||||
].join('\n');
|
||||
};
|
||||
|
||||
export default async function (configuration) {
|
||||
await buildImage(configuration, true);
|
||||
await fs.writeFile(
|
||||
`${configuration.general.workdir}/Dockerfile`,
|
||||
publishStaticDocker(configuration)
|
||||
);
|
||||
const stream = await docker.engine.buildImage(
|
||||
{ src: ['.'], context: configuration.general.workdir },
|
||||
{ t: `${configuration.build.container.name}:${configuration.build.container.tag}` }
|
||||
);
|
||||
await streamEvents(stream, configuration);
|
||||
}
|
@ -1,66 +0,0 @@
|
||||
const defaultBuildAndDeploy = {
|
||||
installation: 'yarn install',
|
||||
build: 'yarn build',
|
||||
start: 'yarn start'
|
||||
};
|
||||
|
||||
const templates = {
|
||||
svelte: {
|
||||
pack: 'svelte',
|
||||
...defaultBuildAndDeploy,
|
||||
directory: 'public',
|
||||
name: 'Svelte'
|
||||
},
|
||||
'@nestjs/core': {
|
||||
pack: 'nestjs',
|
||||
...defaultBuildAndDeploy,
|
||||
start: 'yarn start:prod',
|
||||
port: 3000,
|
||||
name: 'NestJS'
|
||||
},
|
||||
next: {
|
||||
pack: 'nextjs',
|
||||
...defaultBuildAndDeploy,
|
||||
port: 3000,
|
||||
name: 'NextJS'
|
||||
},
|
||||
nuxt: {
|
||||
pack: 'nuxtjs',
|
||||
...defaultBuildAndDeploy,
|
||||
port: 3000,
|
||||
name: 'NuxtJS'
|
||||
},
|
||||
'react-scripts': {
|
||||
pack: 'react',
|
||||
...defaultBuildAndDeploy,
|
||||
directory: 'build',
|
||||
name: 'React'
|
||||
},
|
||||
'parcel-bundler': {
|
||||
pack: 'static',
|
||||
...defaultBuildAndDeploy,
|
||||
directory: 'dist',
|
||||
name: 'Parcel'
|
||||
},
|
||||
'@vue/cli-service': {
|
||||
pack: 'vuejs',
|
||||
...defaultBuildAndDeploy,
|
||||
directory: 'dist',
|
||||
port: 80,
|
||||
name: 'Vue'
|
||||
},
|
||||
gatsby: {
|
||||
pack: 'gatsby',
|
||||
...defaultBuildAndDeploy,
|
||||
directory: 'public',
|
||||
name: 'Gatsby'
|
||||
},
|
||||
'preact-cli': {
|
||||
pack: 'react',
|
||||
...defaultBuildAndDeploy,
|
||||
directory: 'build',
|
||||
name: 'Preact'
|
||||
}
|
||||
};
|
||||
|
||||
export default templates;
|
@ -1,28 +0,0 @@
|
||||
import { docker, streamEvents } from '$lib/api/docker';
|
||||
import { promises as fs } from 'fs';
|
||||
import { buildImage } from '../helpers';
|
||||
|
||||
// 'HEALTHCHECK --timeout=10s --start-period=10s --interval=5s CMD curl -I -s -f http://localhost/ || exit 1',
|
||||
const publishStaticDocker = (configuration) => {
|
||||
return [
|
||||
'FROM nginx:stable-alpine',
|
||||
'COPY nginx.conf /etc/nginx/nginx.conf',
|
||||
'WORKDIR /usr/share/nginx/html',
|
||||
`COPY --from=${configuration.build.container.name}:${configuration.build.container.tag}-cache /usr/src/app/${configuration.publish.directory} ./`,
|
||||
'EXPOSE 80',
|
||||
'CMD ["nginx", "-g", "daemon off;"]'
|
||||
].join('\n');
|
||||
};
|
||||
|
||||
export default async function (configuration) {
|
||||
await buildImage(configuration, true);
|
||||
await fs.writeFile(
|
||||
`${configuration.general.workdir}/Dockerfile`,
|
||||
publishStaticDocker(configuration)
|
||||
);
|
||||
const stream = await docker.engine.buildImage(
|
||||
{ src: ['.'], context: configuration.general.workdir },
|
||||
{ t: `${configuration.build.container.name}:${configuration.build.container.tag}` }
|
||||
);
|
||||
await streamEvents(stream, configuration);
|
||||
}
|
@ -1,76 +0,0 @@
|
||||
import Configuration from "$models/Configuration";
|
||||
import { compareObjects, execShellAsync } from "../common";
|
||||
|
||||
export default async function (configuration) {
|
||||
/*
|
||||
0 => nothing changed, no need to redeploy
|
||||
1 => force update
|
||||
2 => configuration changed
|
||||
3 => continue normally
|
||||
*/
|
||||
const currentConfiguration = await Configuration.findOne({
|
||||
'general.nickname': configuration.general.nickname
|
||||
})
|
||||
if (currentConfiguration) {
|
||||
// Base service configuration changed
|
||||
if (
|
||||
!currentConfiguration.build.container.baseSHA ||
|
||||
currentConfiguration.build.container.baseSHA !== configuration.build.container.baseSHA
|
||||
) {
|
||||
return 1
|
||||
}
|
||||
|
||||
// If the deployment is in error state, forceUpdate
|
||||
try {
|
||||
const state = await execShellAsync(
|
||||
`docker stack ps ${currentConfiguration.build.container.name} --format '{{ json . }}'`
|
||||
);
|
||||
const isError = state
|
||||
.split('\n')
|
||||
.filter((n) => n)
|
||||
.map((s) => JSON.parse(s))
|
||||
.filter(
|
||||
(n) =>
|
||||
n.DesiredState !== 'Running' && n.Image.split(':')[1] === currentConfiguration.build.container.tag
|
||||
);
|
||||
if (isError.length > 0) {
|
||||
return 1
|
||||
}
|
||||
} catch(error) {
|
||||
console.log(error)
|
||||
}
|
||||
|
||||
|
||||
// If previewDeployments enabled
|
||||
if (
|
||||
currentConfiguration.general.isPreviewDeploymentEnabled &&
|
||||
currentConfiguration.general.pullRequest !== 0
|
||||
) {
|
||||
return 1
|
||||
}
|
||||
// If build pack changed, forceUpdate the service
|
||||
if (currentConfiguration.build.pack !== configuration.build.pack) {
|
||||
return 1
|
||||
}
|
||||
|
||||
const currentConfigurationCompare = JSON.parse(JSON.stringify(currentConfiguration));
|
||||
const configurationCompare = JSON.parse(JSON.stringify(configuration));
|
||||
delete currentConfigurationCompare.build.container;
|
||||
delete configurationCompare.build.container;
|
||||
|
||||
if (
|
||||
!compareObjects(currentConfigurationCompare.build, configurationCompare.build) ||
|
||||
!compareObjects(currentConfigurationCompare.publish, configurationCompare.publish) ||
|
||||
currentConfigurationCompare.general.isPreviewDeploymentEnabled !==
|
||||
configurationCompare.general.isPreviewDeploymentEnabled
|
||||
) {
|
||||
return 1
|
||||
}
|
||||
|
||||
if (currentConfiguration.build.container.tag !== configuration.build.container.tag) {
|
||||
return 2
|
||||
}
|
||||
return 0
|
||||
}
|
||||
return 3
|
||||
}
|
@ -1,44 +0,0 @@
|
||||
import Configuration from "$models/Configuration";
|
||||
import Deployment from "$models/Deployment";
|
||||
|
||||
export default async function (configuration) {
|
||||
// Check if deployment is already queued
|
||||
const alreadyQueued = await Deployment.find({
|
||||
path: configuration.publish.path,
|
||||
domain: configuration.publish.domain,
|
||||
progress: { $in: ['queued', 'inprogress'] }
|
||||
});
|
||||
if (alreadyQueued.length > 0) {
|
||||
return {
|
||||
status: 200,
|
||||
body: {
|
||||
success: false,
|
||||
message: 'Deployment already queued.'
|
||||
}
|
||||
};
|
||||
}
|
||||
const { id, organization, name, branch } = configuration.repository;
|
||||
const { domain, path } = configuration.publish;
|
||||
const { deployId, nickname } = configuration.general;
|
||||
// Save new deployment
|
||||
await new Deployment({
|
||||
repoId: id,
|
||||
branch,
|
||||
deployId,
|
||||
domain,
|
||||
organization,
|
||||
name,
|
||||
nickname
|
||||
}).save();
|
||||
|
||||
await Configuration.findOneAndUpdate(
|
||||
{
|
||||
'publish.domain': domain,
|
||||
'publish.path': path,
|
||||
'general.pullRequest': { $in: [null, 0] }
|
||||
},
|
||||
{ ...configuration },
|
||||
{ upsert: true, new: true }
|
||||
);
|
||||
return
|
||||
}
|
@ -1,32 +0,0 @@
|
||||
import Deployment from '$models/Deployment';
|
||||
import dayjs from 'dayjs';
|
||||
import buildContainer from './buildContainer';
|
||||
import { purgeImagesContainers } from './cleanup';
|
||||
import { updateServiceLabels } from './configuration';
|
||||
import copyFiles from './copyFiles';
|
||||
import deploy from './deploy';
|
||||
import { saveAppLog } from './logging';
|
||||
|
||||
export default async function (configuration, nextStep) {
|
||||
const { id, organization, name, branch } = configuration.repository;
|
||||
const { domain } = configuration.publish;
|
||||
const { deployId } = configuration.general;
|
||||
try {
|
||||
await saveAppLog(`### Successfully queued.`, configuration);
|
||||
await copyFiles(configuration);
|
||||
await buildContainer(configuration);
|
||||
await deploy(configuration, nextStep);
|
||||
await Deployment.findOneAndUpdate(
|
||||
{ repoId: id, branch, deployId, organization, name, domain },
|
||||
{ repoId: id, branch, deployId, organization, name, domain, progress: 'done' }
|
||||
);
|
||||
|
||||
await updateServiceLabels(configuration);
|
||||
await purgeImagesContainers(configuration);
|
||||
} catch (error) {
|
||||
await Deployment.findOneAndUpdate(
|
||||
{ repoId: id, branch, deployId, organization, name, domain },
|
||||
{ repoId: id, branch, deployId, organization, name, domain, progress: 'failed' }
|
||||
);
|
||||
}
|
||||
}
|
@ -1,72 +0,0 @@
|
||||
import shell from 'shelljs';
|
||||
import User from '$models/User';
|
||||
import jsonwebtoken from 'jsonwebtoken';
|
||||
import { saveServerLog } from './applications/logging';
|
||||
|
||||
export function execShellAsync(cmd, opts = {}) {
|
||||
try {
|
||||
return new Promise(function (resolve, reject) {
|
||||
shell.config.silent = true;
|
||||
shell.exec(cmd, opts, async function (code, stdout, stderr) {
|
||||
if (code !== 0) {
|
||||
await saveServerLog({ message: JSON.stringify({ cmd, opts, code, stdout, stderr }) });
|
||||
return reject(new Error(stderr));
|
||||
}
|
||||
return resolve(stdout);
|
||||
});
|
||||
});
|
||||
} catch (error) {
|
||||
return new Error('Oops');
|
||||
}
|
||||
}
|
||||
export function cleanupTmp(dir) {
|
||||
if (dir !== '/') shell.rm('-fr', dir);
|
||||
}
|
||||
|
||||
export async function verifyUserId(token) {
|
||||
const { JWT_SIGN_KEY } = process.env;
|
||||
try {
|
||||
const verify = jsonwebtoken.verify(token, JWT_SIGN_KEY);
|
||||
const found = await User.findOne({ uid: verify.jti });
|
||||
if (found) {
|
||||
return Promise.resolve(true);
|
||||
} else {
|
||||
return Promise.reject(false);
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
return Promise.reject(false);
|
||||
}
|
||||
}
|
||||
|
||||
export function delay(t) {
|
||||
return new Promise(function (resolve) {
|
||||
setTimeout(function () {
|
||||
resolve('OK');
|
||||
}, t);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
export function compareObjects(a, b) {
|
||||
if (a === b) return true;
|
||||
|
||||
if (typeof a != 'object' || typeof b != 'object' || a == null || b == null) return false;
|
||||
|
||||
const keysA = Object.keys(a),
|
||||
keysB = Object.keys(b);
|
||||
|
||||
if (keysA.length != keysB.length) return false;
|
||||
|
||||
for (const key of keysA) {
|
||||
if (!keysB.includes(key)) return false;
|
||||
|
||||
if (typeof a[key] === 'function' || typeof b[key] === 'function') {
|
||||
if (a[key].toString() != b[key].toString()) return false;
|
||||
} else {
|
||||
if (!compareObjects(a[key], b[key])) return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user