mirror of
https://github.com/caprover/caprover
synced 2026-07-27 02:41:30 +00:00
@@ -0,0 +1,34 @@
|
||||
name: Build and push the edge image
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
|
||||
jobs:
|
||||
run-pre-checks:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
- uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: 14
|
||||
- run: |
|
||||
sudo mkdir /captain
|
||||
npm ci
|
||||
npm run build
|
||||
npm run tslint
|
||||
npm run formatter
|
||||
sudo npm run test
|
||||
build-publish-docker-hub:
|
||||
needs: run-pre-checks
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: azure/docker-login@v1
|
||||
with:
|
||||
username: ${{ secrets.REGISTRY_USERNAME }}
|
||||
password: ${{ secrets.REGISTRY_PASSWORD }}
|
||||
- uses: actions/checkout@v1
|
||||
- name: Build and Push Edge to DockerHub
|
||||
shell: bash
|
||||
run: ./dev-scripts/build_and_push_edge.sh
|
||||
@@ -1,5 +1,25 @@
|
||||
## [Next Version - available as `edge`]
|
||||
|
||||
- TBD
|
||||
|
||||
## [1.8.0] - 2020-08-16
|
||||
|
||||
- New: Update Docker API to v1.40 - Make sure to have Docker v19.03 or above. [Issue-797](https://github.com/caprover/caprover/issues/797)
|
||||
- New: Change one-click app schema to be more compatible with Docker compose. [Issue-786](https://github.com/caprover/caprover/issues/786)
|
||||
- New: Added hostname support for one-click apps which was necessary for some apps. [Issue-404](https://github.com/caprover/caprover/issues/404)
|
||||
- New: Added ability to override docker service configs that are not present in CapRover (read-only volumes, CPU/RAM limitation and reservation and many more. [See docs](https://caprover.com/docs/service-update-override.html)
|
||||
- New: Allow logs timestamp to be disabled for services. [Issue-602](https://github.com/caprover/caprover/issues/602)
|
||||
- New: Added --advertise-addr for Docker swarm join. [Issue-572](https://github.com/caprover/caprover/issues/572)
|
||||
- New: Added deletion capability to selfhost Docker Registry. [Issue-580](https://github.com/caprover/caprover/issues/580)
|
||||
- New: Remove "Exposed Webapp" in apps table in favor of adding last deployed time. [Issue-47](https://github.com/caprover/caprover-frontend/issues/47)
|
||||
- New: Default to a Apps tab if the instance is fully set up. [Issue-48](https://github.com/caprover/caprover-frontend/issues/48)
|
||||
- Bugfix: Fixed a edge case where dhparam file is empty [Issue-745](https://github.com/caprover/caprover/issues/745)
|
||||
- Bugfix: Fixed an issue with app renaming where the app was deleted [Issue-701](https://github.com/caprover/caprover/issues/701)
|
||||
- Bugfix: Fixed an issue with HTTPS redirection on query parameters [PR-788](https://github.com/caprover/caprover/pull/788)
|
||||
- Security: Updated Node dependencies
|
||||
|
||||
## [1.7.1] - 2020-07-03
|
||||
|
||||
- New: Improved TLS security settings for the built-in docker registry (https://github.com/caprover/caprover/pull/595)
|
||||
- New: Allowing custom ports for git remote repositories. (https://github.com/caprover/caprover/issues/606)
|
||||
- New: Default to http2 (https://github.com/caprover/caprover/pull/667)
|
||||
|
||||
@@ -1,6 +1,40 @@
|
||||
#!/bin/sh
|
||||
#!/bin/bash
|
||||
|
||||
docker build -t caprover/caprover-edge:latest -f dockerfile-captain.edge .
|
||||
docker tag caprover/caprover-edge:latest caprover/caprover-edge:0.0.1
|
||||
docker push caprover/caprover-edge:latest
|
||||
docker push caprover/caprover-edge:0.0.1
|
||||
# Exit early if any command fails
|
||||
set -e
|
||||
|
||||
# Print all commands
|
||||
set -x
|
||||
|
||||
pwd
|
||||
|
||||
CAPROVER_VERSION=0.0.1
|
||||
IMAGE_NAME=caprover/caprover-edge
|
||||
|
||||
if [ ! -f ./package-lock.json ]; then
|
||||
echo "package-lock.json not found!"
|
||||
exit 1;
|
||||
fi
|
||||
|
||||
|
||||
# BRANCH=$(git rev-parse --abbrev-ref HEAD)
|
||||
# On Github the line above does not work, instead:
|
||||
BRANCH=${GITHUB_REF##*/}
|
||||
echo "on branch $BRANCH"
|
||||
if [[ "$BRANCH" != "master" ]]; then
|
||||
echo 'Not on master branch! Aborting script!';
|
||||
exit 1;
|
||||
fi
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
docker build -t $IMAGE_NAME:$CAPROVER_VERSION -t $IMAGE_NAME:latest -f dockerfile-captain.edge .
|
||||
docker push $IMAGE_NAME:$CAPROVER_VERSION
|
||||
docker push $IMAGE_NAME:latest
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
/*jshint esversion: 6 */
|
||||
|
||||
const fs = require('fs-extra');
|
||||
const CONFIG_FILE_PATH = '/captain/data/config-captain.json';
|
||||
|
||||
const fileContent = JSON.parse(fs.readFileSync(CONFIG_FILE_PATH, {
|
||||
encoding: 'utf-8'
|
||||
}));
|
||||
|
||||
fs.writeFileSync(CONFIG_FILE_PATH + '.backup', JSON.stringify(fileContent));
|
||||
|
||||
|
||||
fileContent.nginxBaseConfig = '';
|
||||
fileContent.nginxCaptainConfig = '';
|
||||
|
||||
const apps = fileContent.appDefinitions || {};
|
||||
|
||||
Object.keys(apps).forEach(app => {
|
||||
apps[app].customNginxConfig = '';
|
||||
});
|
||||
|
||||
fs.writeFileSync(CONFIG_FILE_PATH, JSON.stringify(fileContent));
|
||||
@@ -2,15 +2,14 @@
|
||||
# https://github.com/digitalocean/marketplace-partners/blob/master/marketplace_docs/build-an-image.md#cleaning-up-your-build-droplet
|
||||
|
||||
|
||||
apt -y update
|
||||
apt -y upgrade
|
||||
apt -y update && apt -y full-upgrade
|
||||
|
||||
|
||||
|
||||
############################ WAIT
|
||||
|
||||
|
||||
docker pull caprover/caprover:1.4.0
|
||||
docker pull caprover/caprover:1.7.1
|
||||
|
||||
|
||||
cd /var/lib/cloud/scripts/per-instance
|
||||
@@ -24,7 +23,7 @@ cat <<EOT >> z_01_caprover_installation.sh
|
||||
|
||||
ufw allow 80,443,3000,996,7946,4789,2377/tcp; ufw allow 7946,4789,2377/udp;
|
||||
|
||||
docker run -p 80:80 -p 443:443 -p 3000:3000 -v /var/run/docker.sock:/var/run/docker.sock -v /captain:/captain caprover/caprover:1.4.0
|
||||
docker run -p 80:80 -p 443:443 -p 3000:3000 -v /var/run/docker.sock:/var/run/docker.sock -v /captain:/captain caprover/caprover:1.7.1
|
||||
|
||||
|
||||
EOT
|
||||
@@ -49,8 +48,9 @@ cat <<EOF
|
||||
Welcome to CapRover, Open Source PaaS for developers!
|
||||
|
||||
|
||||
Follow the guide here to finish the setup https://caprover.com/docs/get-started.html#step-3-install-caprover-cli
|
||||
|
||||
You can access the dashboard from http://<IP-ADDRESS>:3000
|
||||
Or you can access the dashboard from http://<IP-ADDRESS>:3000
|
||||
|
||||
The default password is captain42
|
||||
|
||||
@@ -70,47 +70,32 @@ EOT
|
||||
|
||||
chmod +x 99-caprover-readme
|
||||
|
||||
############################ WAIT
|
||||
|
||||
|
||||
|
||||
|
||||
apt -y update
|
||||
apt -y upgrade
|
||||
rm -rf /tmp/* /var/tmp/*
|
||||
history -c
|
||||
cat /dev/null > /root/.bash_history
|
||||
unset HISTFILE
|
||||
apt -y autoremove
|
||||
apt -y autoclean
|
||||
find /var/log -mtime -1 -type f -exec truncate -s 0 {} \;
|
||||
rm -rf /var/log/*.gz /var/log/*.[0-9] /var/log/*-????????
|
||||
rm -rf /var/lib/cloud/instances/*
|
||||
rm -f /root/.ssh/authorized_keys /etc/ssh/*key*
|
||||
dd if=/dev/zero of=/zerofile; sync; rm /zerofile; sync
|
||||
cat /dev/null > /var/log/lastlog; cat /dev/null > /var/log/wtmp
|
||||
find /var/log -mtime -1 -type f -exec truncate -s 0 {} \;
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
############################ WAIT
|
||||
|
||||
|
||||
|
||||
apt -y update && apt -y full-upgrade
|
||||
|
||||
|
||||
cd /etc
|
||||
wget https://raw.githubusercontent.com/digitalocean/marketplace-partners/master/scripts/cleanup.sh
|
||||
chmod +x cleanup.sh
|
||||
./cleanup.sh
|
||||
|
||||
|
||||
############################ WAIT
|
||||
|
||||
|
||||
|
||||
cd /etc
|
||||
wget https://raw.githubusercontent.com/digitalocean/marketplace-partners/master/marketplace_validation/img_check.sh
|
||||
|
||||
wget https://raw.githubusercontent.com/digitalocean/marketplace-partners/master/scripts/img_check.sh
|
||||
chmod +x img_check.sh
|
||||
./img_check.sh
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
cat /var/lib/cloud/scripts/per-instance/z_01_caprover_installation.sh
|
||||
cat /etc/update-motd.d/99-caprover-readme
|
||||
history -c
|
||||
|
||||
@@ -7,7 +7,6 @@ RUN mkdir -p /usr/src/app
|
||||
WORKDIR /usr/src/app
|
||||
|
||||
|
||||
|
||||
# Build backend code
|
||||
|
||||
COPY . /usr/src/app
|
||||
|
||||
@@ -15,7 +15,7 @@ RUN npm install --production && \
|
||||
|
||||
# Build frontend code using a fixed hash commit.
|
||||
|
||||
ENV FRONTEND_COMMIT_HASH 08960a9da2e7fbdfc04e1ed79890c6fa9ab1d41b
|
||||
ENV FRONTEND_COMMIT_HASH e95fc5b8ee11d15da9017c5ef47ddb75956cf2a6
|
||||
|
||||
RUN mkdir -p /usr/src/app-frontend && cd /usr/src/app-frontend && \
|
||||
git clone https://github.com/githubsaturn/caprover-frontend.git && \
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
# FROM node:14-alpine
|
||||
# Uncomment the line above if you want to use a Dockerfile instead of templateId
|
||||
|
||||
|
||||
RUN apk update && apk upgrade && \
|
||||
apk add --no-cache git
|
||||
RUN mkdir -p /usr/src/app
|
||||
|
||||
+3
-1
@@ -1,2 +1,4 @@
|
||||
# FROM php:7.3-apache
|
||||
# Uncomment the line above if you want to use a Dockerfile instead of templateId
|
||||
|
||||
COPY ./ /var/www/html/
|
||||
# COPY config/php.ini /usr/local/etc/php/
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
# FROM python:3.8.3-alpine
|
||||
# Uncomment the line above if you want to use a Dockerfile instead of templateId
|
||||
|
||||
|
||||
RUN apk update && apk upgrade && \
|
||||
apk add --no-cache make g++ bash git openssh postgresql-dev curl
|
||||
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
# FROM ruby:2.7.1-alpine
|
||||
# Uncomment the line above if you want to use a Dockerfile instead of templateId
|
||||
|
||||
|
||||
RUN apk update && apk upgrade && \
|
||||
apk add --no-cache make g++ git postgresql-dev
|
||||
|
||||
|
||||
Generated
+1055
-781
File diff suppressed because it is too large
Load Diff
+19
-18
@@ -16,23 +16,23 @@
|
||||
"@types/bcryptjs": "^2.4.2",
|
||||
"@types/configstore": "^4.0.0",
|
||||
"@types/cookie-parser": "^1.4.2",
|
||||
"@types/dockerode": "^2.5.33",
|
||||
"@types/dockerode": "^2.5.34",
|
||||
"@types/ejs": "^3.0.4",
|
||||
"@types/express": "^4.17.6",
|
||||
"@types/express": "^4.17.7",
|
||||
"@types/fs-extra": "^9.0.1",
|
||||
"@types/http-proxy": "^1.17.4",
|
||||
"@types/jest": "^26.0.3",
|
||||
"@types/js-base64": "^2.3.2",
|
||||
"@types/jest": "^26.0.10",
|
||||
"@types/js-base64": "^3.0.0",
|
||||
"@types/jsonwebtoken": "^8.5.0",
|
||||
"@types/morgan": "^1.9.1",
|
||||
"@types/multer": "^1.4.3",
|
||||
"@types/multer": "^1.4.4",
|
||||
"@types/on-finished": "^2.3.1",
|
||||
"@types/request": "^2.48.5",
|
||||
"@types/require-from-string": "^1.2.0",
|
||||
"@types/serve-favicon": "^2.5.0",
|
||||
"@types/ssh2": "^0.5.43",
|
||||
"@types/ssh2": "^0.5.44",
|
||||
"@types/tar": "^4.0.3",
|
||||
"@types/uuid": "^8.0.0",
|
||||
"@types/uuid": "^8.3.0",
|
||||
"@types/validator": "^13.1.0",
|
||||
"axios": "^0.19.2",
|
||||
"bcryptjs": "^2.4.3",
|
||||
@@ -40,13 +40,13 @@
|
||||
"configstore": "^5.0.1",
|
||||
"cookie-parser": "~1.4.5",
|
||||
"debug": "~4.1.1",
|
||||
"dockerode": "^3.2.0",
|
||||
"dockerode": "^3.2.1",
|
||||
"ejs": "^3.1.3",
|
||||
"express": "^4.17.1",
|
||||
"fs-extra": "^9.0.1",
|
||||
"http-proxy": "^1.18.1",
|
||||
"is-valid-path": "^0.1.1",
|
||||
"js-base64": "^2.6.2",
|
||||
"js-base64": "^3.4.5",
|
||||
"jsonwebtoken": "^8.5.1",
|
||||
"madge": "^3.9.2",
|
||||
"moment": "^2.27.0",
|
||||
@@ -54,21 +54,22 @@
|
||||
"multer": "^1.4.2",
|
||||
"on-finished": "^2.3.0",
|
||||
"prettier": "^2.0.5",
|
||||
"public-ip": "^4.0.1",
|
||||
"public-ip": "^4.0.2",
|
||||
"recursive-readdir": "^2.2.2",
|
||||
"request": "^2.88.2",
|
||||
"require-from-string": "^2.0.2",
|
||||
"serve-favicon": "~2.5.0",
|
||||
"simple-git": "^2.11.0",
|
||||
"simple-git": "^2.17.0",
|
||||
"ssh2": "^0.8.9",
|
||||
"tar": "^6.0.2",
|
||||
"tslint": "^6.1.2",
|
||||
"typescript": "^3.9.6",
|
||||
"uuid": "^8.2.0",
|
||||
"validator": "^13.1.1"
|
||||
"tar": "^6.0.5",
|
||||
"tslint": "^6.1.3",
|
||||
"typescript": "^3.9.7",
|
||||
"uuid": "^8.3.0",
|
||||
"validator": "^13.1.1",
|
||||
"yaml": "^1.10.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"jest": "^26.1.0",
|
||||
"ts-jest": "^26.1.1"
|
||||
"jest": "^26.4.0",
|
||||
"ts-jest": "^26.2.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -246,11 +246,11 @@ class AppsDataStore {
|
||||
.then(function (appData) {
|
||||
if (appData.appName) appData.appName = newAppName
|
||||
appData.hasDefaultSubDomainSsl = false
|
||||
self.data.delete(`${APP_DEFINITIONS}.${oldAppName}`)
|
||||
self.saveApp(newAppName, appData)
|
||||
return self.saveApp(newAppName, appData)
|
||||
})
|
||||
.then(function () {
|
||||
Utils.getDelayedPromise(2000)
|
||||
self.data.delete(`${APP_DEFINITIONS}.${oldAppName}`)
|
||||
return Utils.getDelayedPromise(2000)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -616,6 +616,7 @@ class AppsDataStore {
|
||||
authenticator: Authenticator,
|
||||
customNginxConfig: string,
|
||||
preDeployFunction: string,
|
||||
serviceUpdateOverride: string,
|
||||
websocketSupport: boolean
|
||||
) {
|
||||
const self = this
|
||||
@@ -688,6 +689,7 @@ class AppsDataStore {
|
||||
appObj.nodeId = nodeId
|
||||
appObj.customNginxConfig = customNginxConfig
|
||||
appObj.preDeployFunction = preDeployFunction
|
||||
appObj.serviceUpdateOverride = serviceUpdateOverride
|
||||
appObj.description = description
|
||||
|
||||
if (httpAuth && httpAuth.user) {
|
||||
@@ -806,7 +808,7 @@ class AppsDataStore {
|
||||
self.data.delete(`${APP_DEFINITIONS}.${appName}`)
|
||||
resolve()
|
||||
}).then(function () {
|
||||
Utils.getDelayedPromise(2000)
|
||||
return Utils.getDelayedPromise(2000)
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
+29
-14
@@ -163,8 +163,12 @@ class DockerApi {
|
||||
})
|
||||
}
|
||||
|
||||
createJoinCommand(captainIpAddress: string, token: string) {
|
||||
return `docker swarm join --token ${token} ${captainIpAddress}:2377`
|
||||
createJoinCommand(
|
||||
captainIpAddress: string,
|
||||
token: string,
|
||||
workerIp: string
|
||||
) {
|
||||
return `docker swarm join --token ${token} ${captainIpAddress}:2377 --advertise-addr ${workerIp}:2377`
|
||||
}
|
||||
|
||||
getNodesInfo() {
|
||||
@@ -437,12 +441,7 @@ class DockerApi {
|
||||
})
|
||||
})
|
||||
.then(function () {
|
||||
Logger.d('Pruning containers...')
|
||||
|
||||
return self.pruneContainers().catch(function (err) {
|
||||
Logger.d('Prune Containers Failed!')
|
||||
Logger.e(err)
|
||||
})
|
||||
return self.pruneContainers()
|
||||
})
|
||||
.then(function () {
|
||||
Logger.d(`Disconnecting from network: ${nameOrId}`)
|
||||
@@ -1017,6 +1016,7 @@ class DockerApi {
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined
|
||||
)
|
||||
.then(function () {
|
||||
@@ -1133,6 +1133,7 @@ class DockerApi {
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined
|
||||
)
|
||||
})
|
||||
@@ -1218,6 +1219,7 @@ class DockerApi {
|
||||
ports: IAppPort[] | undefined,
|
||||
appObject: IAppDef | undefined,
|
||||
updateOrder: IDockerUpdateOrder | undefined,
|
||||
serviceUpdateOverride: any | undefined,
|
||||
preDeployFunction: Function | undefined
|
||||
) {
|
||||
const self = this
|
||||
@@ -1463,6 +1465,9 @@ class DockerApi {
|
||||
updatedData.Mode.Replicated.Replicas = instanceCount
|
||||
}
|
||||
|
||||
return Utils.mergeObjects(updatedData, serviceUpdateOverride)
|
||||
})
|
||||
.then(function (updatedData) {
|
||||
if (preDeployFunction) {
|
||||
Logger.d('Running preDeployFunction')
|
||||
return preDeployFunction(appObject, updatedData)
|
||||
@@ -1479,10 +1484,7 @@ class DockerApi {
|
||||
// give some time such that the new container is updated.
|
||||
// also we don't want to fail the update just because prune failed.
|
||||
setTimeout(function () {
|
||||
self.pruneContainers().catch(function (err) {
|
||||
Logger.d('Prune Containers Failed!')
|
||||
Logger.e(err)
|
||||
})
|
||||
self.pruneContainers()
|
||||
}, 5000)
|
||||
|
||||
return serviceData
|
||||
@@ -1490,8 +1492,20 @@ class DockerApi {
|
||||
}
|
||||
|
||||
pruneContainers() {
|
||||
Logger.d('Pruning containers...')
|
||||
|
||||
const self = this
|
||||
return self.dockerode.pruneContainers()
|
||||
return self.dockerode
|
||||
.pruneContainers() //
|
||||
.catch(function (error) {
|
||||
// Error: (HTTP code 409) unexpected - a prune operation is already running
|
||||
if (error && error.statusCode === 409) {
|
||||
Logger.d('Skipping prune due to a minor error: ' + error)
|
||||
return
|
||||
}
|
||||
Logger.d('Prune Containers Failed!')
|
||||
Logger.e(error)
|
||||
})
|
||||
}
|
||||
|
||||
isNodeManager(nodeId: string) {
|
||||
@@ -1513,7 +1527,8 @@ class DockerApi {
|
||||
.logs({
|
||||
tail: tailCount,
|
||||
follow: false,
|
||||
timestamps: true,
|
||||
timestamps: !!CaptainConstants.configs
|
||||
.enableDockerLogsTimestamp,
|
||||
stdout: true,
|
||||
stderr: true,
|
||||
})
|
||||
|
||||
@@ -40,7 +40,8 @@ export default class DockerUtils {
|
||||
CaptainConstants.disableFirewallCommand
|
||||
} ${dockerApi.createJoinCommand(
|
||||
captainIpAddress,
|
||||
token
|
||||
token,
|
||||
remoteNodeIpAddress
|
||||
)}`,
|
||||
function (err, stream) {
|
||||
if (err) {
|
||||
|
||||
@@ -61,6 +61,7 @@ interface IAppDefinitionBase {
|
||||
nodeId?: string
|
||||
instanceCount: number
|
||||
preDeployFunction?: string
|
||||
serviceUpdateOverride?: string
|
||||
customNginxConfig?: string
|
||||
networks: string[]
|
||||
customDomain: IAppCustomDomain[]
|
||||
|
||||
@@ -325,6 +325,7 @@ router.post('/update/', function (req, res, next) {
|
||||
let ports = req.body.ports || []
|
||||
let instanceCount = req.body.instanceCount || '0'
|
||||
let preDeployFunction = req.body.preDeployFunction || ''
|
||||
let serviceUpdateOverride = req.body.serviceUpdateOverride || ''
|
||||
let containerHttpPort = Number(req.body.containerHttpPort) || 80
|
||||
let httpAuth = req.body.httpAuth
|
||||
let description = req.body.description || ''
|
||||
@@ -379,6 +380,7 @@ router.post('/update/', function (req, res, next) {
|
||||
repoInfo,
|
||||
customNginxConfig,
|
||||
preDeployFunction,
|
||||
serviceUpdateOverride,
|
||||
websocketSupport
|
||||
)
|
||||
.then(function () {
|
||||
|
||||
@@ -8,10 +8,11 @@ import Logger from '../../../utils/Logger'
|
||||
const router = express.Router()
|
||||
const DEFAULT_ONE_CLICK_BASE_URL = 'https://oneclickapps.caprover.com'
|
||||
|
||||
const VERSION = `v3`
|
||||
const VERSION = `v4`
|
||||
|
||||
interface IOneClickAppIdentifier {
|
||||
baseUrl: string
|
||||
isOfficial: boolean
|
||||
name: string
|
||||
displayName: string
|
||||
description: string
|
||||
@@ -145,6 +146,9 @@ router.get('/template/list', function (req, res, next) {
|
||||
baseUrl: apiBaseUrl,
|
||||
name: element.name,
|
||||
displayName: `${element.displayName}`,
|
||||
isOfficial:
|
||||
(element.isOfficial + '').toLowerCase() ===
|
||||
'true',
|
||||
description: `${element.description}`,
|
||||
logoUrl:
|
||||
element.logoUrl &&
|
||||
|
||||
@@ -4,6 +4,7 @@ import DataStore from '../datastore/DataStore'
|
||||
import DockerApi, { IDockerUpdateOrders } from '../docker/DockerApi'
|
||||
import CaptainConstants from '../utils/CaptainConstants'
|
||||
import Logger from '../utils/Logger'
|
||||
import Utils from '../utils/Utils'
|
||||
import Authenticator from './Authenticator'
|
||||
import DockerRegistryHelper from './DockerRegistryHelper'
|
||||
import ImageMaker, { BuildLogsManager } from './ImageMaker'
|
||||
@@ -649,6 +650,7 @@ class ServiceManager {
|
||||
repoInfo: RepoInfo,
|
||||
customNginxConfig: string,
|
||||
preDeployFunction: string,
|
||||
serviceUpdateOverride: string,
|
||||
websocketSupport: boolean
|
||||
) {
|
||||
const self = this
|
||||
@@ -731,6 +733,22 @@ class ServiceManager {
|
||||
}
|
||||
}
|
||||
})
|
||||
.then(function () {
|
||||
serviceUpdateOverride = !!serviceUpdateOverride
|
||||
? `${serviceUpdateOverride}`.trim()
|
||||
: ''
|
||||
if (!serviceUpdateOverride) {
|
||||
// no override!
|
||||
return
|
||||
}
|
||||
|
||||
if (!Utils.convertYamlOrJsonToObject(serviceUpdateOverride)) {
|
||||
throw ApiStatusCodes.createError(
|
||||
ApiStatusCodes.ILLEGAL_PARAMETER,
|
||||
'serviceUpdateOverride must be either a valid JSON object starting with { or an equivalent yaml'
|
||||
)
|
||||
}
|
||||
})
|
||||
.then(function () {
|
||||
return dataStore
|
||||
.getAppsDataStore()
|
||||
@@ -751,6 +769,7 @@ class ServiceManager {
|
||||
self.authenticator,
|
||||
customNginxConfig,
|
||||
preDeployFunction,
|
||||
serviceUpdateOverride,
|
||||
websocketSupport
|
||||
)
|
||||
})
|
||||
@@ -906,6 +925,7 @@ class ServiceManager {
|
||||
app.ports,
|
||||
app,
|
||||
IDockerUpdateOrders.AUTO,
|
||||
Utils.convertYamlOrJsonToObject(app.serviceUpdateOverride),
|
||||
preDeployFunction
|
||||
)
|
||||
})
|
||||
|
||||
@@ -251,7 +251,7 @@ export default class BackupManager {
|
||||
Logger.d(
|
||||
'waiting 20 seconds for all services to settle'
|
||||
)
|
||||
Utils.getDelayedPromise(20000)
|
||||
return Utils.getDelayedPromise(20000)
|
||||
})
|
||||
.then(function () {
|
||||
return fs.remove(CaptainConstants.restoreDirectoryPath)
|
||||
|
||||
@@ -828,6 +828,7 @@ class CaptainManager {
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined
|
||||
)
|
||||
}, 2000)
|
||||
|
||||
@@ -399,6 +399,7 @@ class CertbotManager {
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined
|
||||
)
|
||||
})
|
||||
|
||||
@@ -484,11 +484,13 @@ class LoadBalancerManager {
|
||||
|
||||
return ejs.render(baseConfigTemplate, {
|
||||
base: {
|
||||
dhparamsFilePath: fs.existsSync(
|
||||
DH_PARAMS_FILE_PATH_ON_HOST
|
||||
)
|
||||
? DH_PARAMS_FILE_PATH_ON_NGINX
|
||||
: '',
|
||||
dhparamsFilePath:
|
||||
fs.existsSync(DH_PARAMS_FILE_PATH_ON_HOST) &&
|
||||
fs
|
||||
.readFileSync(DH_PARAMS_FILE_PATH_ON_HOST)
|
||||
.toString().length > 10 // making sure it's not an buggy file
|
||||
? DH_PARAMS_FILE_PATH_ON_NGINX
|
||||
: '',
|
||||
},
|
||||
})
|
||||
})
|
||||
@@ -504,6 +506,29 @@ class LoadBalancerManager {
|
||||
const self = this
|
||||
return fs
|
||||
.pathExists(DH_PARAMS_FILE_PATH_ON_HOST) //
|
||||
.then(function (dhParamExists) {
|
||||
if (!dhParamExists) {
|
||||
return false
|
||||
}
|
||||
|
||||
const dhFileContent = fs
|
||||
.readFileSync(DH_PARAMS_FILE_PATH_ON_HOST)
|
||||
.toString()
|
||||
|
||||
const contentValid =
|
||||
dhFileContent.indexOf('END DH PARAMETERS') > 0
|
||||
|
||||
if (contentValid) {
|
||||
return true
|
||||
}
|
||||
|
||||
Logger.d(
|
||||
`Invalid dh param content - size of: ${dhFileContent.length}`
|
||||
)
|
||||
fs.removeSync(DH_PARAMS_FILE_PATH_ON_HOST)
|
||||
|
||||
return false
|
||||
})
|
||||
.then(function (dhParamExists) {
|
||||
if (dhParamExists) {
|
||||
return
|
||||
@@ -733,6 +758,7 @@ class LoadBalancerManager {
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined
|
||||
)
|
||||
})
|
||||
|
||||
@@ -96,7 +96,12 @@ class SelfHostedDockerRegistry {
|
||||
undefined,
|
||||
nodeId,
|
||||
undefined,
|
||||
undefined,
|
||||
[
|
||||
{
|
||||
key: 'REGISTRY_STORAGE_DELETE_ENABLED',
|
||||
value: 'true',
|
||||
},
|
||||
],
|
||||
undefined
|
||||
)
|
||||
.then(function () {
|
||||
@@ -222,6 +227,10 @@ class SelfHostedDockerRegistry {
|
||||
key: 'REGISTRY_AUTH_HTPASSWD_PATH',
|
||||
value: '/etc/auth',
|
||||
},
|
||||
{
|
||||
key: 'REGISTRY_STORAGE_DELETE_ENABLED',
|
||||
value: 'true',
|
||||
},
|
||||
],
|
||||
undefined,
|
||||
undefined,
|
||||
@@ -238,6 +247,7 @@ class SelfHostedDockerRegistry {
|
||||
],
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined
|
||||
)
|
||||
})
|
||||
|
||||
@@ -165,6 +165,7 @@ class VersionManager {
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined
|
||||
)
|
||||
})
|
||||
|
||||
@@ -29,9 +29,11 @@ const configs = {
|
||||
|
||||
skipVerifyingDomains: false,
|
||||
|
||||
enableDockerLogsTimestamp: true,
|
||||
|
||||
registrySubDomainPort: 996,
|
||||
|
||||
dockerApiVersion: 'v1.30',
|
||||
dockerApiVersion: 'v1.40',
|
||||
|
||||
netDataImageName: 'titpetric/netdata:1.8',
|
||||
|
||||
|
||||
@@ -334,6 +334,7 @@ export default class MigrateCaptainDuckDuck {
|
||||
self.authenticator,
|
||||
app.customNginxConfig,
|
||||
app.preDeployFunction,
|
||||
'',
|
||||
false
|
||||
)
|
||||
})
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import { remove } from 'fs-extra'
|
||||
import * as yaml from 'yaml'
|
||||
import Logger from './Logger'
|
||||
|
||||
export default class Utils {
|
||||
static removeHttpHttps(input: string) {
|
||||
@@ -14,6 +16,56 @@ export default class Utils {
|
||||
)
|
||||
}
|
||||
|
||||
static mergeObjects(object1: any, object2: any) {
|
||||
const newObject = object1 || {}
|
||||
object2 = object2 || {}
|
||||
|
||||
Object.keys(object2).forEach((k) => {
|
||||
if (
|
||||
!newObject[k] ||
|
||||
Array.isArray(newObject[k]) ||
|
||||
Array.isArray(object2[k])
|
||||
) {
|
||||
newObject[k] = object2[k]
|
||||
} else {
|
||||
if (
|
||||
typeof object2[k] === 'object' &&
|
||||
typeof newObject[k] === 'object'
|
||||
) {
|
||||
newObject[k] = this.mergeObjects(newObject[k], object2[k])
|
||||
} else {
|
||||
newObject[k] = object2[k]
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
return newObject
|
||||
}
|
||||
|
||||
static convertYamlOrJsonToObject(raw: string | undefined) {
|
||||
raw = !!raw ? `${raw}`.trim() : ''
|
||||
if (!raw.length) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
let returnValue = undefined as any
|
||||
if (raw.startsWith('{') || raw.startsWith('[')) {
|
||||
try {
|
||||
returnValue = JSON.parse(raw)
|
||||
} catch (error) {
|
||||
Logger.e(error)
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
returnValue = yaml.parse(raw)
|
||||
} catch (error) {
|
||||
Logger.e(error)
|
||||
}
|
||||
}
|
||||
|
||||
return returnValue
|
||||
}
|
||||
|
||||
static deleteFileQuietly(absFileOrDirPath: string) {
|
||||
return remove(absFileOrDirPath).catch(function (error) {
|
||||
// nom nom
|
||||
|
||||
@@ -18,7 +18,7 @@ if (s.forceSsl) {
|
||||
}
|
||||
|
||||
location / {
|
||||
return 302 https://$http_host$request_uri$is_args$query_string;
|
||||
return 302 https://$http_host$request_uri;
|
||||
}
|
||||
}
|
||||
<%
|
||||
|
||||
+2
-4
@@ -30,7 +30,6 @@ test('Testing dropFirstElements - smaller', () => {
|
||||
.toBe('A0,A1')
|
||||
})
|
||||
|
||||
|
||||
test('Testing dropFirstElements - smaller (1)', () => {
|
||||
const originalArray = []
|
||||
for (let index = 0; index < 1; index++) {
|
||||
@@ -41,7 +40,6 @@ test('Testing dropFirstElements - smaller (1)', () => {
|
||||
.toBe('A0')
|
||||
})
|
||||
|
||||
|
||||
test('Testing dropFirstElements - smaller (0)', () => {
|
||||
const originalArray = []
|
||||
for (let index = 0; index < 0; index++) {
|
||||
@@ -71,7 +69,7 @@ function createTestArray() {
|
||||
test('Testing filter in place - remove 1st', () => {
|
||||
const originalArray = createTestArray()
|
||||
|
||||
Utils.filterInPlace(originalArray, v => v.val1 !== 'e-1-1')
|
||||
Utils.filterInPlace(originalArray, (v) => v.val1 !== 'e-1-1')
|
||||
expect(originalArray.length).toBe(1)
|
||||
expect(originalArray[0].val1).toBe('e-1-2')
|
||||
expect(originalArray[0].val2).toBe('e-2-2')
|
||||
@@ -80,7 +78,7 @@ test('Testing filter in place - remove 1st', () => {
|
||||
test('Testing filter in place - remove 2nd', () => {
|
||||
const originalArray = createTestArray()
|
||||
|
||||
Utils.filterInPlace(originalArray, v => v.val1 !== 'e-1-2')
|
||||
Utils.filterInPlace(originalArray, (v) => v.val1 !== 'e-1-2')
|
||||
expect(originalArray.length).toBe(1)
|
||||
expect(originalArray[0].val1).toBe('e-1-1')
|
||||
expect(originalArray[0].val2).toBe('e-2-1')
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
import Utils from '../src/utils/Utils'
|
||||
|
||||
test('Testing JSON/YAML', () => {
|
||||
const badYaml = `
|
||||
version: "3.3"
|
||||
s
|
||||
|
||||
`
|
||||
const badJSON = `
|
||||
{ something:
|
||||
`
|
||||
|
||||
const goodJSON = `
|
||||
{
|
||||
"version": "3.3",
|
||||
"services": {
|
||||
"$$cap_appname": {
|
||||
"image": "adminer:$$cap_adminer_version",
|
||||
"restart": "always",
|
||||
"environment": {
|
||||
"ADMINER_PLUGINS": "$$cap_adminer_plugins",
|
||||
"ADMINER_DESIGN": "$$cap_adminer_design"
|
||||
},
|
||||
"caproverExtra": {
|
||||
"containerHttpPort": "8080"
|
||||
}
|
||||
}
|
||||
},
|
||||
"captainVersion": 4,
|
||||
"caproverOneClickApp": {
|
||||
"variables": [
|
||||
{
|
||||
"id": "$$cap_adminer_version"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
const goodYAML = `
|
||||
version: "3.3"
|
||||
services:
|
||||
$$cap_appname:
|
||||
image: 'adminer:$$cap_adminer_version'
|
||||
restart: always
|
||||
environment:
|
||||
ADMINER_PLUGINS: $$cap_adminer_plugins
|
||||
ADMINER_DESIGN: $$cap_adminer_design
|
||||
caproverExtra:
|
||||
containerHttpPort: '8080'
|
||||
### ================================
|
||||
### CAPROVER ONE CLICK APP SPECIFICS
|
||||
### ================================
|
||||
captainVersion: 4
|
||||
caproverOneClickApp:
|
||||
variables:
|
||||
- id: $$cap_adminer_version
|
||||
`
|
||||
|
||||
expect(Utils.convertYamlOrJsonToObject(badYaml)).toBeUndefined()
|
||||
expect(Utils.convertYamlOrJsonToObject(badJSON)).toBeUndefined()
|
||||
|
||||
expect(Utils.convertYamlOrJsonToObject(goodJSON)).toStrictEqual(
|
||||
JSON.parse(goodJSON)
|
||||
)
|
||||
expect(Utils.convertYamlOrJsonToObject(goodYAML)).toStrictEqual(
|
||||
JSON.parse(goodJSON)
|
||||
)
|
||||
})
|
||||
@@ -0,0 +1,41 @@
|
||||
import Utils from '../src/utils/Utils'
|
||||
|
||||
test('Testing merge objects', () => {
|
||||
expect(Utils.mergeObjects({ k1: 'v1' }, {})) //
|
||||
.toStrictEqual({ k1: 'v1' })
|
||||
|
||||
expect(Utils.mergeObjects({ k1: 'v1' }, { k2: 'v2' })) //
|
||||
.toStrictEqual({ k1: 'v1', k2: 'v2' })
|
||||
|
||||
expect(Utils.mergeObjects({ k1: 'v1', k2: 'v1' }, { k2: 'v2' })) //
|
||||
.toStrictEqual({ k1: 'v1', k2: 'v2' })
|
||||
|
||||
expect(
|
||||
Utils.mergeObjects(
|
||||
{
|
||||
k1: 'v1',
|
||||
k2: {
|
||||
k21: 'v21',
|
||||
k22: 'v22',
|
||||
k23: ['v23a', 'v23b'],
|
||||
k24: ['v24a', 'v24b'],
|
||||
},
|
||||
},
|
||||
{ k2: { k22: 'v22New', k23: ['v23c'] } }
|
||||
)
|
||||
) //
|
||||
.toStrictEqual({
|
||||
k1: 'v1',
|
||||
k2: {
|
||||
k21: 'v21',
|
||||
k22: 'v22New',
|
||||
k23: ['v23c'],
|
||||
k24: ['v24a', 'v24b'],
|
||||
},
|
||||
})
|
||||
|
||||
expect(
|
||||
Utils.mergeObjects({ k1: 'v1', k2: ['t1', 't2'] }, { k2: ['t3', 't4'] })
|
||||
) //
|
||||
.toStrictEqual({ k1: 'v1', k2: ['t3', 't4'] })
|
||||
})
|
||||
Reference in New Issue
Block a user