Skip to content

Commit

Permalink
add options to launch local versions of Concierge
Browse files Browse the repository at this point in the history
  • Loading branch information
sebovzeoueb committed Feb 13, 2025
1 parent fa184fd commit 88c4365
Show file tree
Hide file tree
Showing 65 changed files with 115 additions and 1,749 deletions.
Binary file modified bun_installer/assets/docker_compose.zip
Binary file not shown.
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ services:
file: ./docker_compose_dependencies/docker-compose-concierge.yml
service: ${CONCIERGE_SERVICE:-concierge}
build:
context: ../../..
dockerfile: ../../../Dockerfile.local
context: ../..
dockerfile: Dockerfile.local

networks:
default:
Expand Down
File renamed without changes.
55 changes: 37 additions & 18 deletions bun_installer/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ import streamHtml from "./server/streamHtml.js"
import { WebUILink } from "./server/webUiLink.js"
import getVersion from "./server/getVersion.js"
import { parseArgs } from "node:util"
import dockerComposeZip from "./assets/docker_compose.zip" with { type: "file" }
import AdmZip from "adm-zip"
import type { stream } from "hono/streaming"

const { values } = parseArgs({
args: Bun.argv,
Expand Down Expand Up @@ -56,7 +59,7 @@ app.get('/', async c => {
<WebUILink></WebUILink>
<p>If the link above isn't working, try (re)launching using the button below.</p>
<p>Bear in mind that if you just installed Concierge it can take a few minutes before it's up and running.</p>
<RelaunchForm></RelaunchForm>
<RelaunchForm devMode={devMode}></RelaunchForm>
</section> : null}
<ExistingRemover></ExistingRemover>
<section>
Expand All @@ -83,26 +86,42 @@ app.post("/install", c => c.req.formData()
})
})
)
app.post("/remove_concierge", c => streamHtml(c, "Removing Concierge service", async _ => {
await $`docker container rm --force concierge`
}))
app.post("/remove_ollama", c => streamHtml(c, "Removing Ollama service", async _ => {
await $`docker container rm --force ollama`
await $`docker volume rm --force concierge_ollama`
}))
app.post("/remove_opensearch", c => streamHtml(c, "Removing OpenSearch service", async _ => {
await $`docker container rm --force opensearch-node1`
await $`docker volume rm --force concierge_opensearch-data1`
}))
app.post("/remove_keycloak", c => streamHtml(c, "Removing Keycloak service", async _ => {
await $`docker container rm --force keycloak postgres`
await $`docker volume rm --force concierge_postgres_data`
app.post("/remove", c => c.req.formData()
.then(data => {
const service = data.get("service")
if (service == "concierge") return streamHtml(c, "Removing Concierge service", async _ => {
await $`docker container rm --force concierge`
})
if (service == "ollama") return streamHtml(c, "Removing Ollama service", async _ => {
await $`docker container rm --force ollama`
await $`docker volume rm --force concierge_ollama`
})
if (service == "opensearch") return streamHtml(c, "Removing OpenSearch service", async _ => {
await $`docker container rm --force opensearch-node1`
await $`docker volume rm --force concierge_opensearch-data1`
})
if (service == "keycloak") return streamHtml(c, "Removing Keycloak service", async _ => {
await $`docker container rm --force keycloak postgres`
await $`docker volume rm --force concierge_postgres_data`
})
return c.html(<p>Invalid service name was provided!</p>)
}))
app.post("/launch", c => c.req.formData()
.then(data => streamHtml(c, "Launching Concierge", async _ => await doLaunch(data)))
.then(data => streamHtml(c, "Launching Concierge", async stream => {
for await (const message of doLaunch(data)) {
await stream.writeln(await <p>{message}</p>)
}
}))
)

console.log("Concierge Configurator")
console.log(`${getVersion()}\n`)
console.log("visit http://localhost:3000 to install or manage Concierge")
Bun.serve({...app, idleTimeout: 0})

// we need the compose files to be available outside of the executable bundle so the shell can use them
const buf = await file(dockerComposeZip).arrayBuffer()
const zip = new AdmZip(Buffer.from(buf))
zip.extractAllTo(".", true)
console.log("Extracted docker compose files.\n")

Bun.serve({...app, idleTimeout: 0})
console.log("visit http://localhost:3000 to install or manage Concierge")
2 changes: 1 addition & 1 deletion bun_installer/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
"dev_server": "bun run --watch index.tsx",
"dev_client": "bun build ./client/index.ts --outfile ./assets/index.js --watch",
"dev_install": "bun run build_zip && bun run build_client && bun run index.tsx --dev-mode",
"build_zip": "bun run ./build/zipDockerCompose.ts",
"build_zip": "bun run ./docker_assets/zipDockerCompose.ts",
"build_client": "bun build ./client/index.ts --outfile ./assets/index.js --minify",
"build_win": "bun run build_zip && bun run build_client && bun build ./index.tsx --compile --target=bun-windows-x64 --outfile dist/windows/concierge",
"build_linux": "bun run build_zip && bun run build_client && bun build ./index.tsx --compile --target=bun-linux-x64 --outfile dist/linux/concierge",
Expand Down
27 changes: 9 additions & 18 deletions bun_installer/server/doInstall.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
import dockerComposeZip from "../assets/docker_compose.zip" with { type: "file" }
import { file, $ } from "bun"
import AdmZip from "adm-zip"
import { $ } from "bun"
import createCertificates from "./createCertificates"
import path from "node:path"
import { keycloakExists } from "./dockerItemsExist"
Expand All @@ -11,20 +9,11 @@ import * as envfile from "envfile"
import getVersion from "./getVersion"
import runPython from "./runPython"
import util from "node:util"
import logMessage from "./logMessage"
const exec = util.promisify(await import("node:child_process").then(child_process => child_process.exec))

const logMessage = (message: string) => {
console.log(message)
return message
}

export default async function* (options: FormData) {
const environment = options.get("dev_mode")?.toString() == "True" ? "development" : "production"
// we need the compose files to be available outside of the executable bundle so the shell can use them
const buf = await file(dockerComposeZip).arrayBuffer()
const zip = new AdmZip(Buffer.from(buf))
zip.extractAllTo(".", true)
yield logMessage("unzipped Docker Compose files.")
// we keep track of the environment variables so we can write to the .env file and the process environment as needed
const envs: {[key: string]: string} = {}
const updateEnv = () => {
Expand Down Expand Up @@ -52,7 +41,7 @@ export default async function* (options: FormData) {
envs.WEB_CERT = path.join(certDir, "concierge-cert.pem")
envs.WEB_KEY = path.join(certDir, "concierge-key.pem")
yield logMessage("configured TLS certificates.")
if (!keycloakExists() || !process.env.POSTGRES_DB_PASSWORD || !process.env.KEYCLOAK_CLIENT_ID || !process.env.KEYCLOAK_CLIENT_SECRET) {
if (!keycloakExists()) {
const keycloakPassword = options.get("keycloak_password")?.toString()
// TODO: create error type for this
if (!keycloakPassword) throw new Error("Keycloak admin password is invalid!")
Expand Down Expand Up @@ -109,19 +98,21 @@ export default async function* (options: FormData) {
await updateEnv()
yield logMessage("launching Docker containers...")
if (environment == "development") {
await $`docker compose -f ./docker_compose/docker-compose-dev.yml pull`
await $`docker compose -f ./docker_compose/docker-compose-dev.yml up -d`
yield logMessage("configuring Python environment...")
await exec("python3 -m venv ..")
await runPython("pip install -r dev_requirements.txt")
}
else await $`docker compose -f ./docker_compose/docker-compose.yml up -d`
else {
await $`docker compose -f ./docker_compose/docker-compose.yml pull`
await $`docker compose -f ./docker_compose/docker-compose.yml up -d`
}
if (securityLevel == "demo") {
yield logMessage("adding demo users")
envs.IS_SECURITY_DEMO = "True"
await updateEnv()
if (environment == "development") {
await runPython("concierge_scripts.add_keycloak_demo_users")
}
if (environment == "development") await runPython("concierge_scripts.add_keycloak_demo_users")
else await $`docker exec -d concierge python -m concierge_scripts.add_keycloak_demo_users`
}
console.log("Installation done\n")
Expand Down
22 changes: 20 additions & 2 deletions bun_installer/server/doLaunch.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,28 @@
import * as envfile from "envfile"
import getEnvPath from "./getEnvPath"
import { $ } from "bun"
import runPython from "./runPython"
import logMessage from "./logMessage"

export default async (options: FormData, environment = "production") => {
export default async function* (options: FormData) {
const envs = envfile.parse(await Bun.file(getEnvPath()).text())
envs.OLLAMA_SERVICE = options.has("use_gpu") ? "ollama-gpu" : "ollama"
yield logMessage(`Launching Concierge ${envs.OLLAMA_SERVICE.endsWith("gpu") ? "with" : "without"} GPU acceleration.`)
await Bun.write(getEnvPath(), envfile.stringify(envs))
await $`docker compose -f ./docker_compose/docker-compose.yml up -d`
const environment = options.get("environment")
if (environment == "local") {
yield logMessage("Building local code files to Docker image. This can take a while depending on your internet connection...")
await $`docker compose -f ./docker_compose/docker-compose-local.yml build`
yield logMessage("Launching Docker Compose configuration with locally built image...")
await $`docker compose -f ./docker_compose/docker-compose-local.yml up -d`
}
else if (environment == "development") {
yield logMessage("Launching Docker Compose configuration to run Concierge code locally...")
await $`docker compose -f ./docker_compose/docker-compose-dev.yml up -d`
await runPython("dev_launcher")
}
else {
yield logMessage("Launching Concierge Docker Compose configuration...")
await $`docker compose -f ./docker_compose/docker-compose.yml up -d`
}
}
27 changes: 7 additions & 20 deletions bun_installer/server/existingRemover.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,25 +23,12 @@ export const ExistingRemover = async () => {
<p>This can help you if your installation appears to be broken or you want to create a fresh install.</p>
<p>If you're switching between having security enabled and disabled or vice-versa, it's strongly recommended that you remove all existing containers except for Ollama.</p>
<p>Be aware that if you remove Ollama you will have to redownload the LLM models which are quite large.</p>
{conciergeExists && <>
<form action="/remove_concierge" method="post">
<button type="submit">Remove Concierge service</button>
</form>
</>}
{ollamaExists && <>
<form action="/remove_ollama" method="post">
<button type="submit">Remove Ollama service</button>
</form>
</>}
{keycloakExists && <>
<form action="/remove_keycloak" method="post">
<button type="submit">Remove Keycloak service</button>
</form>
</>}
{opensearchExists && <>
<form action="/remove_opensearch" method="post">
<button type="submit">Remove OpenSearch service</button>
</form>
</>}
<form action="/remove" method="post">
{conciergeExists && <button type="submit" name="service" value="concierge">Remove Concierge service</button>}
{ollamaExists && <button type="submit" name="service" value="ollama">Remove Ollama service</button>}
{keycloakExists && <button type="submit" name="service" value="keycloak">Remove Keycloak service</button>}
{opensearchExists && <button type="submit" name="service" value="opensearch">Remove OpenSearch service</button>}
</form>

</section>)
}
10 changes: 8 additions & 2 deletions bun_installer/server/installOptionsForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,15 @@ import path from "node:path"
import getDefaultDirectory from "./getDefaultDirectory"
import * as envfile from "envfile"
import getEnvPath from "./getEnvPath"
import { keycloakExists } from "./dockerItemsExist"

export const InstallOptionsForm = async (props: {devMode: boolean}) => {
const envFile = Bun.file(getEnvPath())
const envs = await envFile.exists() && await envFile.text().then(body => envfile.parse(body))
const securityEnabled = envs && envs.CONCIERGE_SECURITY_ENABLED == "True"
const demoEnabled = securityEnabled && envs.IS_SECURITY_DEMO == "True"
const gpuEnabled = envs && envs.OLLAMA_SERVICE == "ollama-gpu"
const keycloakEnabled = await keycloakExists()
return (
<form action="/install" method="post" id="install_form">
<fieldset>
Expand Down Expand Up @@ -70,12 +72,16 @@ export const InstallOptionsForm = async (props: {devMode: boolean}) => {
</p>
<p>If you don't enable security anyone who can access the web UI will have full privileges to interact with your Concierge instance!</p>
<p>The demo configuration should never be used for production as it is a very insecure configuration designed to show off the different access levels using test users.</p>
<p id="keycloak_config">
{keycloakEnabled ? <p id="keycloak_config">
<label for="keycloak_password_first">Keycloak Admin Password</label>
<input type="password" id="keycloak_password_first"></input>
<label for="keycloak_password">Confirm Keycloak Admin Password</label>
<input type="password" id="keycloak_password" name="keycloak_password"></input>
</p>
</p> : <>
<p>There is an existing Keycloak installation.</p>
<p>We strongly recommend removing that and OpenSearch using the buttons above before proceeding.</p>
<p>If not the installer will attempt to keep the existing configuration but be aware that this isn't well supported and you may end up with an unusable setup.</p>
</>}
<div id="password_status" class="error"></div>
</fieldset>
<div id="form_errors" class="error"></div>
Expand Down
4 changes: 4 additions & 0 deletions bun_installer/server/logMessage.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export default (message: string) => {
console.log(message)
return message
}
6 changes: 5 additions & 1 deletion bun_installer/server/relaunchForm.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import * as envfile from "envfile"
import getEnvPath from "./getEnvPath"

export const RelaunchForm = async () => {
export const RelaunchForm = async (props: {devMode: boolean}) => {
const envs = envfile.parse(await Bun.file(getEnvPath()).text())
return (
<form action="/launch" method="post">
Expand All @@ -10,6 +10,10 @@ export const RelaunchForm = async () => {
<label for="launch_with_gpu">Enable GPU Acceleration</label>
</p>
<button type="submit">Launch Concierge</button>
{props.devMode && <>
<button type="submit" name="environment" value="local">Launch Local Code (Docker)</button>
<button type="submit" name="environment" value="development">Launch Local Code (Python)</button>
</>}
</form>
)
}
2 changes: 1 addition & 1 deletion bun_installer/server/validateInstallForm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ export default (formData: FormData) => {
const securityLevel = formData.get("security_level")?.toString()
if (!securityLevel || securityLevel == "none") return true // if you don't have security enabled Concierge will install fine with no options set
// if keycloak will be installed, we need a valid password
if (!keycloakExists() || !process.env.POSTGRES_DB_PASSWORD || !process.env.KEYCLOAK_CLIENT_ID || !process.env.KEYCLOAK_CLIENT_SECRET) {
if (!keycloakExists()) {
const keycloakPassword = formData.get("keycloak_password")?.toString()
if (!keycloakPassword || zxcvbn(keycloakPassword).score < 4) return false
}
Expand Down
17 changes: 0 additions & 17 deletions concierge_packages/installer/pyproject.toml

This file was deleted.

Empty file.
30 changes: 0 additions & 30 deletions concierge_packages/installer/src/install_concierge/install.py

This file was deleted.

Loading

0 comments on commit 88c4365

Please sign in to comment.