Merge pull request #1049 from Budibase/feature/selfhosting-info

Quick update to self hosting to make next steps clear
This commit is contained in:
Michael Drury 2021-02-01 13:24:50 +00:00 committed by GitHub
commit 4a8bc72888
11 changed files with 266 additions and 26 deletions

View File

@ -1,8 +1,11 @@
version: "3" version: "3"
# optional ports are specified throughout for more advanced use cases.
services: services:
app-service: app-service:
restart: always restart: always
#build: ./build/server
image: budibase/budibase-apps image: budibase/budibase-apps
ports: ports:
- "${APP_PORT}:4002" - "${APP_PORT}:4002"
@ -20,6 +23,7 @@ services:
worker-service: worker-service:
restart: always restart: always
#build: ./build/worker
image: budibase/budibase-worker image: budibase/budibase-worker
ports: ports:
- "${WORKER_PORT}:4003" - "${WORKER_PORT}:4003"
@ -62,7 +66,7 @@ services:
- ./envoy.yaml:/etc/envoy/envoy.yaml - ./envoy.yaml:/etc/envoy/envoy.yaml
ports: ports:
- "${MAIN_PORT}:10000" - "${MAIN_PORT}:10000"
- "9901:9901" #- "9901:9901"
depends_on: depends_on:
- minio-service - minio-service
- worker-service - worker-service
@ -77,8 +81,8 @@ services:
- COUCHDB_USER=${COUCH_DB_USER} - COUCHDB_USER=${COUCH_DB_USER}
ports: ports:
- "${COUCH_DB_PORT}:5984" - "${COUCH_DB_PORT}:5984"
- "4369:4369" #- "4369:4369"
- "9100:9100" #- "9100:9100"
volumes: volumes:
- couchdb_data:/couchdb - couchdb_data:/couchdb

View File

@ -21,6 +21,11 @@ static_resources:
cluster: app-service cluster: app-service
prefix_rewrite: "/" prefix_rewrite: "/"
# special case for presenting our static self hosting page
- match: { path: "/" }
route:
cluster: app-service
# special case for when API requests are made, can just forward, not to minio # special case for when API requests are made, can just forward, not to minio
- match: { prefix: "/api/" } - match: { prefix: "/api/" }
route: route:

View File

@ -45,7 +45,7 @@ exports.authenticate = async ctx => {
expiresIn: "1 day", expiresIn: "1 day",
}) })
setCookie(ctx, appId, token) setCookie(ctx, token, appId)
delete dbUser.password delete dbUser.password
ctx.body = { ctx.body = {

View File

@ -49,6 +49,17 @@ exports.serveBuilder = async function(ctx) {
await send(ctx, ctx.file, { root: ctx.devPath || builderPath }) await send(ctx, ctx.file, { root: ctx.devPath || builderPath })
} }
exports.serveSelfHostPage = async function(ctx) {
const logo = fs.readFileSync(resolve(__dirname, "selfhost/logo.svg"), "utf8")
const hostingHbs = fs.readFileSync(
resolve(__dirname, "selfhost/index.hbs"),
"utf8"
)
ctx.body = await processString(hostingHbs, {
logo,
})
}
exports.uploadFile = async function(ctx) { exports.uploadFile = async function(ctx) {
let files let files
files = files =

View File

@ -0,0 +1,173 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Budibase self hosting</title>
<style>
body {
font-family: Inter, -apple-system, BlinkMacSystemFont, Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
height: 100%;
width: 100%;
margin: 0;
padding: 0;
background: #fafafa;
}
.main {
padding: 0 20px;
margin: 30px auto;
width: 60%;
}
h2 {
font-size: clamp(24px, 1.5vw, 30px);
text-align: center;
line-height: 1.3;
font-weight: bold;
}
.card-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 3rem;
}
.card {
display: grid;
background-color: #222222;
grid-template-columns: 1fr;
align-items: center;
padding: 2.5rem 1.75rem;
border-radius: 12px;
color: white;
}
.card h3 {
margin: 0;
font-size: 24px;
font-family: sans-serif;
font-weight: 600;
}
.card h3 b {
text-wrap: normal;
font-size: 36px;
padding-right: 14px;
}
.card p {
color: #ffffff;
opacity: 0.8;
font-size: 18px;
text-align: left;
line-height: 1.3;
margin-top: 1rem;
}
.logo {
width: 60px;
height: 60px;
margin: auto;
}
.top-text {
text-align: center;
color: #707070;
margin: 0 0 1.5rem 0;
}
.button {
cursor: pointer;
display: block;
background: #4285f4;
color: white;
padding: 12px 16px;
font-size: 16px;
font-weight: 600;
border-radius: 6px;
max-width: 120px;
text-align: center;
transition: 200ms background ease;
text-decoration: none;
}
.info {
background: #f5f5f5;
padding: 1rem 1rem 1rem 1rem;
border: #ccc 1px solid;
border-radius: 6px;
margin-top: 40px;
display: flex;
align-items: center;
}
.info p {
margin-left: 20px;
color: #222222;
font-family: sans-serif;
}
.info p {
margin-right: 20px;
}
.info svg {
margin-left: 20px;
}
.info a {
color: #4285f4;
}
</style>
</head>
<body>
<div class="main">
<div class="logo">
{{logo}}
</div>
<h2>Get started with Budibase Self Hosting</h2>
<p class="top-text">Use the address <b id="url"></b> in your Builder</p>
<div class="card-grid">
<div class="card">
<h3><b>📚</b>Documentation</h3>
<p>
Find out more about your self hosted platform.
</p>
<a class="button"
href="https://docs.budibase.com/self-hosting/introduction-to-self-hosting">
Documentation
</a>
</div>
<div class="card">
<h3><b>💻</b>Next steps</h3>
<p>
Find out how to make use of your self hosted Budibase platform.
</p>
<a class="button"
href="https://docs.budibase.com/self-hosting/builder-settings">
Next steps
</a>
</div>
</div>
<div class="info">
<svg preserveAspectRatio="xMidYMid meet" height="28px" width="28px" fill="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink" stroke="none" class="icon-7f6730be--text-3f89f380">
<g>
<path d="M12.2 8.98c.06-.01.12-.03.18-.06.06-.02.12-.05.18-.09l.15-.12c.18-.19.29-.45.29-.71 0-.06-.01-.13-.02-.19a.603.603 0 0 0-.06-.19.757.757 0 0 0-.09-.18c-.03-.05-.08-.1-.12-.15-.28-.27-.72-.37-1.09-.21-.13.05-.23.12-.33.21-.04.05-.09.1-.12.15-.04.06-.07.12-.09.18-.03.06-.05.12-.06.19-.01.06-.02.13-.02.19 0 .26.11.52.29.71.1.09.2.16.33.21.12.05.25.08.38.08.06 0 .13-.01.2-.02M13 16v-4a1 1 0 1 0-2 0v4a1 1 0 1 0 2 0M12 3c-4.962 0-9 4.038-9 9 0 4.963 4.038 9 9 9 4.963 0 9-4.037 9-9 0-4.962-4.037-9-9-9m0 20C5.935 23 1 18.065 1 12S5.935 1 12 1c6.066 0 11 4.935 11 11s-4.934 11-11 11" fill-rule="evenodd">
</path>
</g>
</svg>
<p>A <b>Hosting Key</b> will also be required, this can be found in your hosting properties, info found <a href="https://docs.budibase.com/self-hosting/hosting-settings">here</a>.</p>
</div>
</div>
<script>
window.addEventListener("load", () => {
let url = document.URL.split("//")[1]
if (url.substring(url.length - 1) === "/") {
url = url.substring(0, url.length - 1)
}
document.getElementById("url").innerHTML = url
})
</script>
</body>
</html>

View File

@ -0,0 +1,17 @@
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" x="0" y="0" viewBox="0 0 48 48" xml:space="preserve">
<style>
.st0{fill:#393c44}.st1{fill:#fff}
</style>
<path class="st0" d="M-152.17-24.17H-56V72h-96.17z"/>
<path class="st1" d="M-83.19 48h-41.79c-1.76 0-3.19-1.43-3.19-3.19V3.02c0-1.76 1.43-3.19 3.19-3.19h41.79c1.76 0 3.19 1.43 3.19 3.19v41.79c0 1.76-1.43 3.19-3.19 3.19z"/>
<path class="st0" d="M-99.62 12.57v9.94c1.15-1.21 2.59-1.81 4.32-1.81 1.03 0 1.97.19 2.82.58.86.39 1.59.91 2.19 1.57.6.66 1.08 1.43 1.42 2.32.34.89.51 1.84.51 2.85 0 1.03-.18 1.99-.53 2.89-.35.9-.84 1.68-1.47 2.35-.63.67-1.37 1.19-2.23 1.58-.86.39-1.78.58-2.77.58-1.8 0-3.22-.66-4.27-1.97V35h-4.89V12.57h4.9zm6.16 15.54c0-.43-.08-.84-.24-1.23-.16-.39-.39-.72-.68-1.01-.29-.29-.62-.52-1-.69-.38-.17-.79-.26-1.24-.26-.43 0-.84.08-1.22.24-.38.16-.71.39-.99.68-.28.29-.5.63-.68 1.01-.17.39-.26.8-.26 1.23 0 .43.08.84.24 1.22.16.38.39.71.68.99.29.28.63.5 1.01.68.39.17.8.26 1.23.26.43 0 .84-.08 1.22-.24.38-.16.71-.39.99-.68.28-.29.5-.62.68-1 .17-.39.26-.79.26-1.2z"/>
<path class="st0" d="M-114.76 12.57v9.94c1.15-1.21 2.59-1.81 4.32-1.81 1.03 0 1.97.19 2.82.58.86.39 1.59.91 2.19 1.57.6.66 1.08 1.43 1.42 2.32.34.89.51 1.84.51 2.85 0 1.03-.18 1.99-.53 2.89-.35.9-.84 1.68-1.47 2.35-.63.67-1.37 1.19-2.23 1.58-.86.39-1.78.58-2.77.58-1.8 0-3.22-.66-4.27-1.97V35h-4.89V12.57h4.9zm6.16 15.54c0-.43-.08-.84-.24-1.23-.16-.39-.39-.72-.68-1.01-.29-.29-.62-.52-1-.69-.38-.17-.79-.26-1.24-.26-.43 0-.84.08-1.22.24-.38.16-.71.39-.99.68-.28.29-.5.63-.68 1.01-.17.39-.26.8-.26 1.23 0 .43.08.84.24 1.22.16.38.39.71.68.99.29.28.63.5 1.01.68.39.17.8.26 1.23.26.43 0 .84-.08 1.22-.24.38-.16.71-.39.99-.68.28-.29.5-.62.68-1 .18-.39.26-.79.26-1.2z"/>
<path d="M44.81 159H3.02c-1.76 0-3.19-1.43-3.19-3.19v-41.79c0-1.76 1.43-3.19 3.19-3.19h41.79c1.76 0 3.19 1.43 3.19 3.19v41.79c0 1.76-1.43 3.19-3.19 3.19z" fill="#4285f4"/>
<path class="st1" d="M28.38 123.57v9.94c1.15-1.21 2.59-1.81 4.32-1.81 1.03 0 1.97.19 2.82.58.86.39 1.59.91 2.19 1.57.6.66 1.08 1.43 1.42 2.32.34.89.51 1.84.51 2.85 0 1.03-.18 1.99-.53 2.89-.35.9-.84 1.68-1.47 2.35-.63.67-1.37 1.19-2.23 1.58-.86.39-1.78.58-2.77.58-1.8 0-3.22-.66-4.27-1.97V146h-4.89v-22.43h4.9zm6.16 15.54c0-.43-.08-.84-.24-1.23-.16-.39-.39-.72-.68-1.01-.29-.29-.62-.52-1-.69-.38-.17-.79-.26-1.24-.26-.43 0-.84.08-1.22.24-.38.16-.71.39-.99.68-.28.29-.5.63-.68 1.01-.17.39-.26.8-.26 1.23 0 .43.08.84.24 1.22.16.38.39.71.68.99.29.28.63.5 1.01.68.39.17.8.26 1.23.26.43 0 .84-.08 1.22-.24.38-.16.71-.39.99-.68.28-.29.5-.62.68-1 .17-.39.26-.79.26-1.2z"/>
<path class="st1" d="M13.24 123.57v9.94c1.15-1.21 2.59-1.81 4.32-1.81 1.03 0 1.97.19 2.82.58.86.39 1.59.91 2.19 1.57.6.66 1.08 1.43 1.42 2.32.34.89.51 1.84.51 2.85 0 1.03-.18 1.99-.53 2.89-.35.9-.84 1.68-1.47 2.35-.63.67-1.37 1.19-2.23 1.58-.86.39-1.78.58-2.77.58-1.8 0-3.22-.66-4.27-1.97V146H8.35v-22.43h4.89zm6.16 15.54c0-.43-.08-.84-.24-1.23-.16-.39-.39-.72-.68-1.01-.29-.29-.62-.52-1-.69-.38-.17-.79-.26-1.24-.26-.43 0-.84.08-1.22.24-.38.16-.71.39-.99.68-.28.29-.5.63-.68 1.01-.17.39-.26.8-.26 1.23 0 .43.08.84.24 1.22.16.38.39.71.68.99.29.28.63.5 1.01.68.39.17.8.26 1.23.26.43 0 .84-.08 1.22-.24.38-.16.71-.39.99-.68.28-.29.5-.62.68-1 .18-.39.26-.79.26-1.2z"/>
<g>
<path class="st0" d="M44 48H4c-2.21 0-4-1.79-4-4V4c0-2.21 1.79-4 4-4h40c2.21 0 4 1.79 4 4v40c0 2.21-1.79 4-4 4z"/>
<path class="st1" d="M28.48 12v10.44c1.18-1.27 2.65-1.9 4.42-1.9 1.05 0 2.01.2 2.89.61.87.41 1.62.96 2.24 1.65.62.69 1.1 1.5 1.45 2.44.35.94.52 1.93.52 2.99 0 1.08-.18 2.09-.54 3.04-.36.95-.86 1.77-1.51 2.47-.64.7-1.4 1.25-2.28 1.66-.87.4-1.81.6-2.83.6-1.84 0-3.3-.69-4.37-2.07v1.62h-5V12h5.01zm6.3 16.31c0-.45-.08-.88-.25-1.29-.17-.41-.4-.76-.69-1.06-.3-.3-.64-.54-1.02-.72-.39-.18-.81-.27-1.27-.27-.44 0-.86.09-1.24.26-.39.17-.72.41-1.01.71-.29.3-.52.66-.69 1.06-.18.41-.26.84-.26 1.29s.08.88.25 1.28c.17.4.4.74.69 1.04.29.29.64.53 1.04.71.4.18.82.27 1.26.27.44 0 .86-.09 1.24-.26.39-.17.72-.41 1.01-.71.29-.3.52-.65.69-1.05.16-.41.25-.82.25-1.26z"/>
<path class="st1" d="M13 12v10.44c1.18-1.27 2.65-1.9 4.42-1.9 1.05 0 2.01.2 2.89.61.87.41 1.62.96 2.24 1.65.62.69 1.1 1.5 1.45 2.44.35.94.52 1.93.52 2.99 0 1.08-.18 2.09-.54 3.04-.36.95-.86 1.77-1.51 2.47-.64.7-1.4 1.25-2.28 1.66-.87.4-1.81.6-2.82.6-1.84 0-3.3-.69-4.37-2.07v1.62H8V12h5zm6.3 16.31c0-.45-.08-.88-.25-1.29-.17-.41-.4-.76-.69-1.06-.3-.3-.64-.54-1.02-.72-.39-.18-.81-.27-1.27-.27-.44 0-.86.09-1.24.26-.39.17-.72.41-1.01.71-.29.3-.52.66-.69 1.06-.18.41-.26.84-.26 1.29s.08.88.25 1.28c.17.4.4.74.69 1.04.29.29.64.53 1.04.71.4.18.82.27 1.26.27.44 0 .86-.09 1.24-.26.39-.17.72-.41 1.01-.71.29-.3.52-.65.69-1.05.16-.41.25-.82.25-1.26z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 4.6 KiB

View File

@ -65,6 +65,8 @@ for (let route of mainRoutes) {
router.use(staticRoutes.routes()) router.use(staticRoutes.routes())
router.use(staticRoutes.allowedMethods()) router.use(staticRoutes.allowedMethods())
if (!env.SELF_HOSTED && !env.CLOUD) {
router.redirect("/", "/_builder") router.redirect("/", "/_builder")
}
module.exports = router module.exports = router

View File

@ -23,6 +23,10 @@ if (env.NODE_ENV !== "production") {
router.get("/_builder/:file*", controller.serveBuilder) router.get("/_builder/:file*", controller.serveBuilder)
} }
if (env.SELF_HOSTED) {
router.get("/", controller.serveSelfHostPage)
}
router router
.post( .post(
"/api/attachments/process", "/api/attachments/process",

View File

@ -2,7 +2,13 @@ const jwt = require("jsonwebtoken")
const STATUS_CODES = require("../utilities/statusCodes") const STATUS_CODES = require("../utilities/statusCodes")
const { getRole, BUILTIN_ROLES } = require("../utilities/security/roles") const { getRole, BUILTIN_ROLES } = require("../utilities/security/roles")
const { AuthTypes } = require("../constants") const { AuthTypes } = require("../constants")
const { getAppId, getCookieName, setCookie, isClient } = require("../utilities") const {
getAppId,
getCookieName,
clearCookie,
setCookie,
isClient,
} = require("../utilities")
module.exports = async (ctx, next) => { module.exports = async (ctx, next) => {
if (ctx.path === "/_builder") { if (ctx.path === "/_builder") {
@ -15,16 +21,18 @@ module.exports = async (ctx, next) => {
let appId = getAppId(ctx) let appId = getAppId(ctx)
const cookieAppId = ctx.cookies.get(getCookieName("currentapp")) const cookieAppId = ctx.cookies.get(getCookieName("currentapp"))
if (appId && cookieAppId !== appId) { if (appId && cookieAppId !== appId) {
setCookie(ctx, "currentapp", appId) setCookie(ctx, appId, "currentapp")
} else if (cookieAppId) { } else if (cookieAppId) {
appId = cookieAppId appId = cookieAppId
} }
let token, authType
let token = ctx.cookies.get(getCookieName(appId)) if (!isClient(ctx)) {
let authType = AuthTypes.APP
if (!token && !isClient(ctx)) {
authType = AuthTypes.BUILDER
token = ctx.cookies.get(getCookieName()) token = ctx.cookies.get(getCookieName())
authType = AuthTypes.BUILDER
}
if (!token && appId) {
token = ctx.cookies.get(getCookieName(appId))
authType = AuthTypes.APP
} }
if (!token) { if (!token) {
@ -49,10 +57,14 @@ module.exports = async (ctx, next) => {
role: await getRole(appId, jwtPayload.roleId), role: await getRole(appId, jwtPayload.roleId),
} }
} catch (err) { } catch (err) {
// TODO - this can happen if the JWT secret is changed and can never login if (authType === AuthTypes.BUILDER) {
// TODO: wipe cookies if they exist clearCookie(ctx)
ctx.status = 200
return
} else {
ctx.throw(err.status || STATUS_CODES.FORBIDDEN, err.text) ctx.throw(err.status || STATUS_CODES.FORBIDDEN, err.text)
} }
}
await next() await next()
} }

View File

@ -3,7 +3,7 @@ const env = require("../../environment")
const CouchDB = require("../../db") const CouchDB = require("../../db")
const jwt = require("jsonwebtoken") const jwt = require("jsonwebtoken")
const { DocumentTypes, SEPARATOR } = require("../../db/utils") const { DocumentTypes, SEPARATOR } = require("../../db/utils")
const { setCookie } = require("../index") const { setCookie, clearCookie } = require("../index")
const APP_PREFIX = DocumentTypes.APP + SEPARATOR const APP_PREFIX = DocumentTypes.APP + SEPARATOR
module.exports = async (ctx, appId, version) => { module.exports = async (ctx, appId, version) => {
@ -20,13 +20,13 @@ module.exports = async (ctx, appId, version) => {
}) })
// set the builder token // set the builder token
setCookie(ctx, "builder", token) setCookie(ctx, token, "builder")
setCookie(ctx, "currentapp", appId) setCookie(ctx, appId, "currentapp")
// need to clear all app tokens or else unable to use the app in the builder // need to clear all app tokens or else unable to use the app in the builder
let allDbNames = await CouchDB.allDbs() let allDbNames = await CouchDB.allDbs()
allDbNames.map(dbName => { allDbNames.map(dbName => {
if (dbName.startsWith(APP_PREFIX)) { if (dbName.startsWith(APP_PREFIX)) {
setCookie(ctx, dbName, "") clearCookie(ctx, dbName)
} }
}) })
} }

View File

@ -111,17 +111,29 @@ exports.getCookieName = (name = "builder") => {
* @param {string} name The name of the cookie to set. * @param {string} name The name of the cookie to set.
* @param {string|object} value The value of cookie which will be set. * @param {string|object} value The value of cookie which will be set.
*/ */
exports.setCookie = (ctx, name, value) => { exports.setCookie = (ctx, value, name = "builder") => {
const expires = new Date() const expires = new Date()
expires.setDate(expires.getDate() + 1) expires.setDate(expires.getDate() + 1)
ctx.cookies.set(exports.getCookieName(name), value, { const cookieName = exports.getCookieName(name)
if (!value) {
ctx.cookies.set(cookieName)
} else {
ctx.cookies.set(cookieName, value, {
expires, expires,
path: "/", path: "/",
httpOnly: false, httpOnly: false,
overwrite: true, overwrite: true,
}) })
} }
}
/**
* Utility function, simply calls setCookie with an empty string for value
*/
exports.clearCookie = (ctx, name) => {
exports.setCookie(ctx, "", name)
}
exports.isClient = ctx => { exports.isClient = ctx => {
return ctx.headers["x-budibase-type"] === "client" return ctx.headers["x-budibase-type"] === "client"