Update after testing, it is now possible to make a deployment to a self hosted environment. Some work still required, better authentication around MINIO deployment, currently the bucket is set to public read and there is no signing/verification to the upload process, also right now four different URLs are needed for the builder to connect correctly, ideally this shouldn't be the case.
This commit is contained in:
parent
84a16e2dfb
commit
31bc45985f
|
@ -8,14 +8,14 @@ services:
|
|||
ports:
|
||||
- "${APP_PORT}:${APP_PORT}"
|
||||
environment:
|
||||
MINIO_ACCESS_KEY: ${MINIO_ACCESS_KEY}
|
||||
MINIO_SECRET_KEY: ${MINIO_SECRET_KEY}
|
||||
MINIO_URL: http://nginx-service:${MINIO_PORT}
|
||||
SELF_HOSTED: 1
|
||||
COUCH_DB_URL: http://${COUCH_DB_USER}:${COUCH_DB_PASSWORD}@couchdb-service:5984
|
||||
BUDIBASE_ENVIRONMENT: ${BUDIBASE_ENVIRONMENT}
|
||||
LOGO_URL: ${LOGO_URL}
|
||||
PORT: ${APP_PORT}
|
||||
HOSTING_URL: ${HOSTING_URL}
|
||||
MINIO_PORT: ${MINIO_PORT}
|
||||
JWT_SECRET: ${JWT_SECRET}
|
||||
depends_on:
|
||||
- worker-service
|
||||
|
||||
|
@ -29,7 +29,7 @@ services:
|
|||
PORT: ${WORKER_PORT}
|
||||
MINIO_ACCESS_KEY: ${MINIO_ACCESS_KEY}
|
||||
MINIO_SECRET_KEY: ${MINIO_SECRET_KEY}
|
||||
RAW_MINIO_URL: http://nginx-service:${MINIO_PORT}
|
||||
RAW_MINIO_URL: http://nginx-service:5001
|
||||
COUCH_DB_USERNAME: ${COUCH_DB_USER}
|
||||
COUCH_DB_PASSWORD: ${COUCH_DB_PASSWORD}
|
||||
RAW_COUCH_DB_URL: http://couchdb-service:5984
|
||||
|
|
|
@ -4,9 +4,10 @@ COUCH_DB_PASSWORD=budibase
|
|||
COUCH_DB_USER=budibase
|
||||
WORKER_API_KEY=budibase
|
||||
BUDIBASE_ENVIRONMENT=PRODUCTION
|
||||
HOSTING_URL="http://localhost:4001"
|
||||
HOSTING_URL="http://localhost"
|
||||
LOGO_URL=https://logoipsum.com/logo/logo-15.svg
|
||||
APP_PORT=4002
|
||||
MINIO_PORT=4003
|
||||
COUCH_DB_PORT=4004
|
||||
WORKER_PORT=4006
|
||||
WORKER_PORT=4003
|
||||
MINIO_PORT=4004
|
||||
COUCH_DB_PORT=4005
|
||||
JWT_SECRET=testsecret
|
||||
|
|
|
@ -17,7 +17,7 @@ export const getHostingStore = () => {
|
|||
const [info, urls] = await Promise.all(responses.map(resp => resp.json()))
|
||||
store.update(state => {
|
||||
state.hostingInfo = info
|
||||
state.appUrl = urls.appServer
|
||||
state.appUrl = urls.app
|
||||
return state
|
||||
})
|
||||
return info
|
||||
|
|
|
@ -39,8 +39,10 @@
|
|||
</p>
|
||||
<Toggle thin text="Self hosted" bind:checked={selfhosted} />
|
||||
{#if selfhosted}
|
||||
<Input bind:value={hostingInfo.appServerUrl} label="Apps URL" />
|
||||
<Input bind:value={hostingInfo.deploymentServerUrl} label="Deployments URL" />
|
||||
<Input bind:value={hostingInfo.appUrl} label="Apps URL" />
|
||||
<Input bind:value={hostingInfo.workerUrl} label="Workers URL" />
|
||||
<Input bind:value={hostingInfo.minioUrl} label="MINIO URL" />
|
||||
<Input bind:value={hostingInfo.couchUrl} label="CouchDB URL" />
|
||||
<Toggle thin text="HTTPS" bind:checked={hostingInfo.useHttps} />
|
||||
{/if}
|
||||
</ModalContent>
|
||||
|
|
|
@ -35,8 +35,8 @@ exports.authenticate = async ctx => {
|
|||
roleId: dbUser.roleId,
|
||||
version: app.version,
|
||||
}
|
||||
// if in cloud add the user api key
|
||||
if (env.CLOUD) {
|
||||
// if in cloud add the user api key, unless self hosted
|
||||
if (env.CLOUD && !env.SELF_HOSTED) {
|
||||
const { apiKey } = await getAPIKey(ctx.user.appId)
|
||||
payload.apiKey = apiKey
|
||||
}
|
||||
|
|
|
@ -37,10 +37,10 @@ class Deployment {
|
|||
if (!verification) {
|
||||
return
|
||||
}
|
||||
this.verification = verification
|
||||
if (this.verification.quota) {
|
||||
this.quota = this.verification.quota
|
||||
}
|
||||
this.verification = verification
|
||||
}
|
||||
|
||||
getVerification() {
|
||||
|
|
|
@ -24,7 +24,7 @@ async function checkAllDeployments(deployments) {
|
|||
deployment.status === DeploymentStatus.PENDING &&
|
||||
Date.now() - deployment.updatedAt > MAX_PENDING_TIME_MS
|
||||
) {
|
||||
deployment.status = status
|
||||
deployment.status = DeploymentStatus.FAILURE
|
||||
deployment.err = "Timed out"
|
||||
updated = true
|
||||
}
|
||||
|
|
|
@ -5,23 +5,26 @@ const {
|
|||
performReplication,
|
||||
fetchCredentials,
|
||||
} = require("./utils")
|
||||
const { getDeploymentUrl } = require("../../../utilities/builder/hosting")
|
||||
const { join } = require("path")
|
||||
const {
|
||||
getWorkerUrl,
|
||||
getCouchUrl,
|
||||
getMinioUrl,
|
||||
} = require("../../../utilities/builder/hosting")
|
||||
|
||||
exports.preDeployment = async function() {
|
||||
const url = join(await getDeploymentUrl(), "api", "deploy")
|
||||
const url = `${await getWorkerUrl()}/api/deploy`
|
||||
const json = await fetchCredentials(url, {
|
||||
apiKey: env.BUDIBASE_API_KEY,
|
||||
})
|
||||
|
||||
// response contains:
|
||||
// couchDbSession, bucket, objectStoreSession, couchDbUrl, objectStoreUrl
|
||||
// couchDbSession, bucket, objectStoreSession
|
||||
|
||||
// set credentials here, means any time we're verified we're ready to go
|
||||
if (json.objectStoreSession) {
|
||||
AWS.config.update({
|
||||
accessKeyId: json.objectStoreSession.AccessKeyId,
|
||||
secretAccessKey: json.objectStoreSession.SecretAccessKey,
|
||||
accessKeyId: json.objectStoreSession.accessKeyId,
|
||||
secretAccessKey: json.objectStoreSession.secretAccessKey,
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -36,7 +39,7 @@ exports.deploy = async function(deployment) {
|
|||
const appId = deployment.getAppId()
|
||||
const verification = deployment.getVerification()
|
||||
const objClient = new AWS.S3({
|
||||
endpoint: verification.objectStoreUrl,
|
||||
endpoint: await getMinioUrl(),
|
||||
s3ForcePathStyle: true, // needed with minio?
|
||||
signatureVersion: "v4",
|
||||
params: {
|
||||
|
@ -54,6 +57,6 @@ exports.replicateDb = async function(deployment) {
|
|||
return performReplication(
|
||||
appId,
|
||||
verification.couchDbSession,
|
||||
verification.couchDbUrl
|
||||
await getCouchUrl()
|
||||
)
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@ const sanitize = require("sanitize-s3-objectkey")
|
|||
const { walkDir } = require("../../../utilities")
|
||||
const { join } = require("../../../utilities/centralPath")
|
||||
const { budibaseAppsDir } = require("../../../utilities/budibaseDir")
|
||||
const fetch = require("node-fetch")
|
||||
const PouchDB = require("../../../db")
|
||||
const CouchDB = require("pouchdb")
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@ const { BUILDER_CONFIG_DB, HOSTING_DOC } = require("../../constants")
|
|||
const {
|
||||
getHostingInfo,
|
||||
HostingTypes,
|
||||
getAppServerUrl,
|
||||
getAppUrl,
|
||||
} = require("../../utilities/builder/hosting")
|
||||
|
||||
exports.fetchInfo = async ctx => {
|
||||
|
@ -30,6 +30,6 @@ exports.fetch = async ctx => {
|
|||
|
||||
exports.fetchUrls = async ctx => {
|
||||
ctx.body = {
|
||||
appServer: await getAppServerUrl(ctx.appId),
|
||||
app: await getAppUrl(ctx.appId),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,17 +17,10 @@ const setBuilderToken = require("../../../utilities/builder/setBuilderToken")
|
|||
const fileProcessor = require("../../../utilities/fileProcessor")
|
||||
const env = require("../../../environment")
|
||||
|
||||
function appServerUrl(appId) {
|
||||
if (env.SELF_HOSTED) {
|
||||
return env.HOSTING_URL
|
||||
} else {
|
||||
return `https://${appId}.app.budi.live`
|
||||
}
|
||||
}
|
||||
|
||||
function objectStoreUrl() {
|
||||
if (env.SELF_HOSTED) {
|
||||
return env.MINIO_URL
|
||||
// TODO: need a better way to handle this, probably reverse proxy
|
||||
return `${env.HOSTING_URL}:${env.MINIO_PORT}/app-assets/assets`
|
||||
} else {
|
||||
return "https://cdn.app.budi.live/assets"
|
||||
}
|
||||
|
@ -164,7 +157,7 @@ exports.serveApp = async function(ctx) {
|
|||
title: appInfo.name,
|
||||
production: env.CLOUD,
|
||||
appId: ctx.params.appId,
|
||||
appServerUrl: appServerUrl(ctx.params.appId),
|
||||
objectStoreUrl: objectStoreUrl(ctx.params.appId),
|
||||
})
|
||||
|
||||
const template = handlebars.compile(
|
||||
|
@ -232,8 +225,7 @@ exports.serveComponentLibrary = async function(ctx) {
|
|||
}
|
||||
const S3_URL = encodeURI(
|
||||
join(
|
||||
appServerUrl(appId),
|
||||
"assets",
|
||||
objectStoreUrl(appId),
|
||||
componentLib,
|
||||
ctx.query.library,
|
||||
"dist",
|
||||
|
|
|
@ -4,11 +4,11 @@
|
|||
|
||||
export let appId
|
||||
export let production
|
||||
export let appServerUrl
|
||||
export let objectStoreUrl
|
||||
|
||||
function publicPath(path) {
|
||||
if (production) {
|
||||
return `${appServerUrl}/assets/${appId}/${path}`
|
||||
return `${objectStoreUrl}/${appId}/${path}`
|
||||
}
|
||||
|
||||
return `/assets/${path}`
|
||||
|
|
|
@ -37,10 +37,8 @@ module.exports = {
|
|||
DEPLOYMENT_DB_URL: process.env.DEPLOYMENT_DB_URL,
|
||||
LOCAL_TEMPLATES: process.env.LOCAL_TEMPLATES,
|
||||
// self hosting features
|
||||
MINIO_ACCESS_KEY: process.env.MINIO_ACCESS_KEY,
|
||||
MINIO_SECRET_KEY: process.env.MINIO_SECRET_KEY,
|
||||
MINIO_URL: process.MINIO_URL,
|
||||
HOSTING_URL: process.HOSTING_URL,
|
||||
HOSTING_URL: process.env.HOSTING_URL,
|
||||
MINIO_PORT: process.env.MINIO_PORT,
|
||||
LOGO_URL: process.env.LOGO_URL,
|
||||
_set(key, value) {
|
||||
process.env[key] = value
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
const CouchDB = require("../../db")
|
||||
const { BUILDER_CONFIG_DB, HOSTING_DOC } = require("../../constants")
|
||||
const { join } = require("path")
|
||||
|
||||
function getProtocol(hostingInfo) {
|
||||
return hostingInfo.useHttps ? "https://" : "http://"
|
||||
|
@ -23,8 +22,10 @@ exports.getHostingInfo = async () => {
|
|||
doc = {
|
||||
_id: HOSTING_DOC,
|
||||
type: exports.HostingTypes.CLOUD,
|
||||
appServerUrl: "app.budi.live",
|
||||
deploymentServerUrl: "",
|
||||
appUrl: "app.budi.live",
|
||||
workerUrl: "",
|
||||
minioUrl: "",
|
||||
couchUrl: "",
|
||||
templatesUrl: "prod-budi-templates.s3-eu-west-1.amazonaws.com",
|
||||
useHttps: true,
|
||||
}
|
||||
|
@ -32,22 +33,34 @@ exports.getHostingInfo = async () => {
|
|||
return doc
|
||||
}
|
||||
|
||||
exports.getAppServerUrl = async appId => {
|
||||
exports.getAppUrl = async appId => {
|
||||
const hostingInfo = await exports.getHostingInfo()
|
||||
const protocol = getProtocol(hostingInfo)
|
||||
let url
|
||||
if (hostingInfo.type === "cloud") {
|
||||
url = `${protocol}${appId}.${hostingInfo.appServerUrl}`
|
||||
url = `${protocol}${appId}.${hostingInfo.appUrl}`
|
||||
} else {
|
||||
url = `${protocol}${hostingInfo.appServerUrl}`
|
||||
url = `${protocol}${hostingInfo.appUrl}`
|
||||
}
|
||||
return url
|
||||
}
|
||||
|
||||
exports.getDeploymentUrl = async () => {
|
||||
exports.getWorkerUrl = async () => {
|
||||
const hostingInfo = await exports.getHostingInfo()
|
||||
const protocol = getProtocol(hostingInfo)
|
||||
return `${protocol}${hostingInfo.deploymentServerUrl}`
|
||||
return `${protocol}${hostingInfo.workerUrl}`
|
||||
}
|
||||
|
||||
exports.getMinioUrl = async () => {
|
||||
const hostingInfo = await exports.getHostingInfo()
|
||||
const protocol = getProtocol(hostingInfo)
|
||||
return `${protocol}${hostingInfo.minioUrl}`
|
||||
}
|
||||
|
||||
exports.getCouchUrl = async () => {
|
||||
const hostingInfo = await exports.getHostingInfo()
|
||||
const protocol = getProtocol(hostingInfo)
|
||||
return `${protocol}${hostingInfo.couchUrl}`
|
||||
}
|
||||
|
||||
exports.getTemplatesUrl = async (appId, type, name) => {
|
||||
|
@ -55,9 +68,9 @@ exports.getTemplatesUrl = async (appId, type, name) => {
|
|||
const protocol = getProtocol(hostingInfo)
|
||||
let path
|
||||
if (type && name) {
|
||||
path = join("templates", type, `${name}.tar.gz`)
|
||||
path = `templates/type/${name}.tar.gz`
|
||||
} else {
|
||||
path = "manifest.json"
|
||||
}
|
||||
return join(`${protocol}${hostingInfo.templatesUrl}`, path)
|
||||
return `${protocol}${hostingInfo.templatesUrl}/${path}`
|
||||
}
|
||||
|
|
|
@ -5,11 +5,23 @@ const AWS = require("aws-sdk")
|
|||
const APP_BUCKET = "app-assets"
|
||||
// this doesn't matter in self host
|
||||
const REGION = "eu-west-1"
|
||||
const PUBLIC_READ_POLICY = {
|
||||
Version: "2012-10-17",
|
||||
Statement: [
|
||||
{
|
||||
Effect: "Allow",
|
||||
Principal: "*",
|
||||
Action: "s3:GetObject",
|
||||
Resource: `arn:aws:s3:::${APP_BUCKET}/*`,
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
async function getCouchSession() {
|
||||
// fetch session token for the api user
|
||||
const session = await got.post(`${env.RAW_COUCH_DB_URL}/_session`, {
|
||||
responseType: "json",
|
||||
credentials: "include",
|
||||
json: {
|
||||
username: env.COUCH_DB_USERNAME,
|
||||
password: env.COUCH_DB_PASSWORD,
|
||||
|
@ -38,37 +50,25 @@ async function getMinioSession() {
|
|||
})
|
||||
// make sure the bucket exists
|
||||
try {
|
||||
await objClient.headBucket({ Bucket: APP_BUCKET }).promise()
|
||||
await objClient.headBucket({
|
||||
Bucket: APP_BUCKET
|
||||
}).promise()
|
||||
await objClient.putBucketPolicy({
|
||||
Bucket: APP_BUCKET,
|
||||
Policy: JSON.stringify(PUBLIC_READ_POLICY),
|
||||
}).promise()
|
||||
} catch (err) {
|
||||
// bucket doesn't exist create it
|
||||
if (err.statusCode === 404) {
|
||||
await objClient.createBucket({ Bucket: APP_BUCKET }).promise()
|
||||
await objClient.createBucket({
|
||||
Bucket: APP_BUCKET,
|
||||
}).promise()
|
||||
} else {
|
||||
throw err
|
||||
}
|
||||
}
|
||||
// TODO: this doesn't seem to work get an error
|
||||
// TODO: Generating temporary credentials not allowed for this request.
|
||||
// TODO: this should work based on minio documentation
|
||||
// const sts = new AWS.STS({
|
||||
// endpoint: env.RAW_MINIO_URL,
|
||||
// region: REGION,
|
||||
// s3ForcePathStyle: true,
|
||||
// })
|
||||
// // NOTE: In the following commands RoleArn and RoleSessionName are not meaningful for MinIO
|
||||
// const params = {
|
||||
// DurationSeconds: 3600,
|
||||
// ExternalId: "123ABC",
|
||||
// Policy: '{"Version":"2012-10-17","Statement":[{"Sid":"Stmt1","Effect":"Allow","Action":"s3:*","Resource":"arn:aws:s3:::*"}]}',
|
||||
// RoleArn: 'arn:xxx:xxx:xxx:xxxx',
|
||||
// RoleSessionName: 'anything',
|
||||
// };
|
||||
// const assumedRole = await sts.assumeRole(params).promise();
|
||||
// if (!assumedRole) {
|
||||
// throw "Unable to get access to object store."
|
||||
// }
|
||||
// return assumedRole.Credentials
|
||||
// TODO: need to do something better than this
|
||||
// Ideally want to send back some pre-signed URLs for files that are to be uploaded
|
||||
return {
|
||||
accessKeyId: env.MINIO_ACCESS_KEY,
|
||||
secretAccessKey: env.MINIO_SECRET_KEY,
|
||||
|
@ -80,7 +80,5 @@ exports.deploy = async ctx => {
|
|||
couchDbSession: await getCouchSession(),
|
||||
bucket: APP_BUCKET,
|
||||
objectStoreSession: await getMinioSession(),
|
||||
couchDbUrl: env.RAW_COUCH_DB_URL,
|
||||
objectStoreUrl: env.RAW_MINIO_URL,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,10 +4,12 @@ module.exports = {
|
|||
PORT: process.env.PORT,
|
||||
MINIO_ACCESS_KEY: process.env.MINIO_ACCESS_KEY,
|
||||
MINIO_SECRET_KEY: process.env.MINIO_SECRET_KEY,
|
||||
RAW_MINIO_URL: process.env.RAW_MINIO_URL,
|
||||
COUCH_DB_USERNAME: process.env.COUCH_DB_USERNAME,
|
||||
COUCH_DB_PASSWORD: process.env.COUCH_DB_PASSWORD,
|
||||
RAW_COUCH_DB_URL: process.env.RAW_COUCH_DB_URL,
|
||||
RAW_MINIO_URL: process.env.RAW_MINIO_URL,
|
||||
COUCH_DB_PORT: process.env.COUCH_DB_PORT,
|
||||
MINIO_PORT: process.env.MINIO_PORT,
|
||||
_set(key, value) {
|
||||
process.env[key] = value
|
||||
module.exports[key] = value
|
||||
|
|
Loading…
Reference in New Issue