diff --git a/stack-orchestrator/compose/docker-compose-laconic-console.yml b/stack-orchestrator/compose/docker-compose-laconic-console.yml index b4f2ff0..6dcebb9 100644 --- a/stack-orchestrator/compose/docker-compose-laconic-console.yml +++ b/stack-orchestrator/compose/docker-compose-laconic-console.yml @@ -12,5 +12,16 @@ services: extra_hosts: - "host.docker.internal:host-gateway" + laconic-console: + restart: unless-stopped + image: cerc/laconic-console-host:local + environment: + - CERC_WEBAPP_FILES_DIR=${CERC_WEBAPP_FILES_DIR:-/usr/local/share/.config/yarn/global/node_modules/@cerc-io/console-app/dist/production} + - LACONIC_HOSTED_ENDPOINT=${LACONIC_HOSTED_ENDPOINT:-http://localhost:9473} + volumes: + - ../config/laconic-console/console/config.yml:/config.yml + ports: + - "80" + volumes: laconic-registry-data: diff --git a/stack-orchestrator/config/laconic-console/create-config.sh b/stack-orchestrator/config/laconic-console/cli/create-config.sh similarity index 100% rename from stack-orchestrator/config/laconic-console/create-config.sh rename to stack-orchestrator/config/laconic-console/cli/create-config.sh diff --git a/stack-orchestrator/config/laconic-console/console/config.yml b/stack-orchestrator/config/laconic-console/console/config.yml new file mode 100644 index 0000000..883e34e --- /dev/null +++ b/stack-orchestrator/config/laconic-console/console/config.yml @@ -0,0 +1,6 @@ +# Config for laconic-console + +services: + wns: + server: 'LACONIC_HOSTED_ENDPOINT/api' + webui: 'LACONIC_HOSTED_ENDPOINT/console' diff --git a/stack-orchestrator/container-build/cerc-laconic-console-host/Dockerfile b/stack-orchestrator/container-build/cerc-laconic-console-host/Dockerfile new file mode 100644 index 0000000..2a7f015 --- /dev/null +++ b/stack-orchestrator/container-build/cerc-laconic-console-host/Dockerfile @@ -0,0 +1,12 @@ +FROM cerc/webapp-base:local + +# Configure the cerc-io npm registry +RUN npm config set @cerc-io:registry https://git.vdb.to/api/packages/cerc-io/npm/ + +COPY . . + +RUN echo "Installing dependencies" && yarn +RUN LACONIC_HOSTED_CONFIG_FILE=config-hosted.yml yarn dist + +RUN npm config set @cerc-io:registry https://git.vdb.to/api/packages/cerc-io/npm/ +RUN yarn global add file:$PWD diff --git a/stack-orchestrator/container-build/cerc-laconic-console-host/build.sh b/stack-orchestrator/container-build/cerc-laconic-console-host/build.sh new file mode 100755 index 0000000..ee349e1 --- /dev/null +++ b/stack-orchestrator/container-build/cerc-laconic-console-host/build.sh @@ -0,0 +1,9 @@ +#!/usr/bin/env bash +# Build cerc/laconic-registry-cli + +source ${CERC_CONTAINER_BASE_DIR}/build-base.sh + +# See: https://stackoverflow.com/a/246128/1701505 +SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) + +docker build -t cerc/laconic-console-host:local ${build_command_args} -f ${SCRIPT_DIR}/Dockerfile ${CERC_REPO_BASE_DIR}/laconic-console diff --git a/stack-orchestrator/container-build/cerc-webapp-base/Dockerfile b/stack-orchestrator/container-build/cerc-webapp-base/Dockerfile new file mode 100644 index 0000000..5b119b3 --- /dev/null +++ b/stack-orchestrator/container-build/cerc-webapp-base/Dockerfile @@ -0,0 +1,58 @@ +# Originally from: https://github.com/devcontainers/images/blob/main/src/javascript-node/.devcontainer/Dockerfile +# [Choice] Node.js version (use -bullseye variants on local arm64/Apple Silicon): 18, 16, 14, 18-bullseye, 16-bullseye, 14-bullseye, 18-buster, 16-buster, 14-buster +ARG VARIANT=20-bullseye-slim +FROM node:${VARIANT} + +ARG USERNAME=node +ARG NPM_GLOBAL=/usr/local/share/npm-global + +# Add NPM global to PATH. +ENV PATH=${NPM_GLOBAL}/bin:${PATH} +# Prevents npm from printing version warnings +ENV NPM_CONFIG_UPDATE_NOTIFIER=false + +RUN \ + # Configure global npm install location, use group to adapt to UID/GID changes + if ! cat /etc/group | grep -e "^npm:" > /dev/null 2>&1; then groupadd -r npm; fi \ + && usermod -a -G npm ${USERNAME} \ + && umask 0002 \ + && mkdir -p ${NPM_GLOBAL} \ + && touch /usr/local/etc/npmrc \ + && chown ${USERNAME}:npm ${NPM_GLOBAL} /usr/local/etc/npmrc \ + && chmod g+s ${NPM_GLOBAL} \ + && npm config -g set prefix ${NPM_GLOBAL} \ + && su ${USERNAME} -c "npm config -g set prefix ${NPM_GLOBAL}" \ + # Install eslint + && su ${USERNAME} -c "umask 0002 && npm install -g eslint" \ + # Install semver + && su ${USERNAME} -c "umask 0002 && npm install -g semver" \ + # Install pnpm + && su ${USERNAME} -c "umask 0002 && npm install -g pnpm" \ + && npm cache clean --force > /dev/null 2>&1 + +# [Optional] Uncomment this section to install additional OS packages. +RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \ + && apt-get -y install --no-install-recommends jq gettext-base + +# [Optional] Uncomment if you want to install an additional version of node using nvm +# ARG EXTRA_NODE_VERSION=10 +# RUN su node -c "source /usr/local/share/nvm/nvm.sh && nvm install ${EXTRA_NODE_VERSION}" + +# We do this to get a yq binary from the published container, for the correct architecture we're building here +COPY --from=docker.io/mikefarah/yq:latest /usr/bin/yq /usr/local/bin/yq + +COPY scripts /scripts + +# [Optional] Uncomment if you want to install more global node modules +# RUN su node -c "npm install -g " + +RUN mkdir -p /config + +# Install simple web server for now (use nginx perhaps later) +RUN yarn global add http-server + +# Expose port for http +EXPOSE 80 + +# Default command sleeps forever so docker doesn't kill it +CMD ["/scripts/start-serving-app.sh"] diff --git a/stack-orchestrator/container-build/cerc-webapp-base/Dockerfile.webapp b/stack-orchestrator/container-build/cerc-webapp-base/Dockerfile.webapp new file mode 100644 index 0000000..9eaf46e --- /dev/null +++ b/stack-orchestrator/container-build/cerc-webapp-base/Dockerfile.webapp @@ -0,0 +1,12 @@ +FROM cerc/webapp-base:local as builder + +ARG CERC_BUILD_TOOL +ARG CERC_BUILD_OUTPUT_DIR + +WORKDIR /app +COPY . . +RUN rm -rf node_modules build dist .next* +RUN /scripts/build-app.sh /app /data + +FROM cerc/webapp-base:local +COPY --from=builder /data /data diff --git a/stack-orchestrator/container-build/cerc-webapp-base/build.sh b/stack-orchestrator/container-build/cerc-webapp-base/build.sh new file mode 100755 index 0000000..5fc6cc3 --- /dev/null +++ b/stack-orchestrator/container-build/cerc-webapp-base/build.sh @@ -0,0 +1,35 @@ +#!/usr/bin/env bash +# Build cerc/webapp-base + +source ${CERC_CONTAINER_BASE_DIR}/build-base.sh + +# See: https://stackoverflow.com/a/246128/1701505 +SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) + +CERC_CONTAINER_BUILD_WORK_DIR=${CERC_CONTAINER_BUILD_WORK_DIR:-$SCRIPT_DIR} +CERC_CONTAINER_BUILD_DOCKERFILE=${CERC_CONTAINER_BUILD_DOCKERFILE:-$SCRIPT_DIR/Dockerfile} +CERC_CONTAINER_BUILD_TAG=${CERC_CONTAINER_BUILD_TAG:-cerc/webapp-base:local} + +docker build -t $CERC_CONTAINER_BUILD_TAG ${build_command_args} -f $CERC_CONTAINER_BUILD_DOCKERFILE $CERC_CONTAINER_BUILD_WORK_DIR +rc=$? + +if [ $rc -ne 0 ]; then + echo "BUILD FAILED" 1>&2 + exit $rc +fi + +if [ "$CERC_CONTAINER_BUILD_TAG" != "cerc/webapp-base:local" ]; then + cat < $TMP_ENV + set -a + source .env + source $TMP_ENV + set +a + rm -f $TMP_ENV +fi + +for f in $(find . -type f \( -regex '.*.html?' -or -regex ".*.[tj]s\(x\|on\)?$" \) | grep -v 'node_modules' | grep -v '.git'); do + for e in $(cat "${f}" | tr -s '[:blank:]' '\n' | tr -s '["/\\{},();]' '\n' | tr -s "[']" '\n' | egrep -o -e '^CERC_RUNTIME_ENV_.+$' -e '^LACONIC_HOSTED_CONFIG_.+$'); do + orig_name=$(echo -n "${e}" | sed 's/"//g') + cur_name=$(echo -n "${orig_name}" | sed 's/CERC_RUNTIME_ENV_//g') + cur_val=$(echo -n "\$${cur_name}" | envsubst) + if [ "$CERC_RETAIN_ENV_QUOTES" != "true" ]; then + cur_val=$(sed "s/^[\"']//" <<< "$cur_val" | sed "s/[\"']//") + fi + esc_val=$(sed 's/[&/\]/\\&/g' <<< "$cur_val") + echo "$f: $cur_name=$cur_val" + sed -i "s/$orig_name/$esc_val/g" $f + done +done diff --git a/stack-orchestrator/container-build/cerc-webapp-base/scripts/apply-webapp-config.sh b/stack-orchestrator/container-build/cerc-webapp-base/scripts/apply-webapp-config.sh new file mode 100755 index 0000000..6d36680 --- /dev/null +++ b/stack-orchestrator/container-build/cerc-webapp-base/scripts/apply-webapp-config.sh @@ -0,0 +1,35 @@ +#!/usr/bin/env bash +if [ -n "$CERC_SCRIPT_DEBUG" ]; then + set -x +fi +if [[ $# -ne 2 ]]; then + echo "Illegal number of parameters" >&2 + exit 1 +fi +config_file_name=$1 +webapp_files_dir=$2 +if ! [[ -f ${config_file_name} ]]; then + echo "Config file ${config_file_name} does not exist" >&2 + exit 1 +fi +if ! [[ -d ${webapp_files_dir} ]]; then + echo "Webapp directory ${webapp_files_dir} does not exist" >&2 + exit 1 +fi +# First some magic using yq to translate our yaml config file into an array of key value pairs like: +# LACONIC_HOSTED_CONFIG_= +readarray -t config_kv_pair_array < <( yq '.. | ([path | join("_"), .] | join("=") )' ${config_file_name} | sort -r | sed -e '$ d' | sed 's/^/LACONIC_HOSTED_CONFIG_/' ) +declare -p config_kv_pair_array +# Then iterate over that kv array making the template substitution in our web app files +for kv_pair_string in "${config_kv_pair_array[@]}" +do + kv_pair=(${kv_pair_string//=/ }) + template_string_to_replace=${kv_pair[0]} + template_value_to_substitute=${kv_pair[1]} + template_value_to_substitute_expanded=${template_value_to_substitute//LACONIC_HOSTED_ENDPOINT/${LACONIC_HOSTED_ENDPOINT}} + # Run find and sed to do the substitution of one variable over all files + # See: https://stackoverflow.com/a/21479607/1701505 + echo "Substituting: ${template_string_to_replace} = ${template_value_to_substitute_expanded}" + # Note: we do not escape our strings, on the expectation they do not container the '#' char. + find ${webapp_files_dir} -type f -exec sed -i 's#'${template_string_to_replace}'#'${template_value_to_substitute_expanded}'#g' {} + +done diff --git a/stack-orchestrator/container-build/cerc-webapp-base/scripts/build-app.sh b/stack-orchestrator/container-build/cerc-webapp-base/scripts/build-app.sh new file mode 100755 index 0000000..30358bb --- /dev/null +++ b/stack-orchestrator/container-build/cerc-webapp-base/scripts/build-app.sh @@ -0,0 +1,61 @@ +#!/bin/bash + +SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) + +if [ -n "$CERC_SCRIPT_DEBUG" ]; then + set -x +fi + +CERC_BUILD_TOOL="${CERC_BUILD_TOOL}" +CERC_BUILD_OUTPUT_DIR="${CERC_BUILD_OUTPUT_DIR}" + +WORK_DIR="${1:-/app}" +DEST_DIR="${2:-/data}" + +if [ -f "${WORK_DIR}/build-webapp.sh" ]; then + echo "Building webapp with ${WORK_DIR}/build-webapp.sh ..." + cd "${WORK_DIR}" || exit 1 + + rm -rf "${DEST_DIR}" + ./build-webapp.sh "${DEST_DIR}" || exit 1 +elif [ -f "${WORK_DIR}/package.json" ]; then + echo "Building node-based webapp ..." + cd "${WORK_DIR}" || exit 1 + + if [ -z "$CERC_BUILD_TOOL" ]; then + if [ -f "pnpm-lock.yaml" ]; then + CERC_BUILD_TOOL=pnpm + elif [ -f "yarn.lock" ]; then + CERC_BUILD_TOOL=yarn + else + CERC_BUILD_TOOL=npm + fi + fi + + time $CERC_BUILD_TOOL install || exit 1 + time $CERC_BUILD_TOOL build || exit 1 + + rm -rf "${DEST_DIR}" + if [ -z "${CERC_BUILD_OUTPUT_DIR}" ]; then + if [ -d "${WORK_DIR}/dist" ]; then + CERC_BUILD_OUTPUT_DIR="${WORK_DIR}/dist" + elif [ -d "${WORK_DIR}/build" ]; then + CERC_BUILD_OUTPUT_DIR="${WORK_DIR}/build" + else + echo "ERROR: Unable to locate build output. Set with --extra-build-args \"--build-arg CERC_BUILD_OUTPUT_DIR=path\"" 1>&2 + exit 1 + fi + fi + mv "${CERC_BUILD_OUTPUT_DIR}" "${DEST_DIR}" +else + echo "Copying static app ..." + mv "${WORK_DIR}" "${DEST_DIR}" +fi + +# One special fix ... +cd "${DEST_DIR}" +for f in $(find . -type f -name '*.htm*'); do + sed -i -e 's#/LACONIC_HOSTED_CONFIG_homepage/#LACONIC_HOSTED_CONFIG_homepage/#g' "$f" +done + +exit 0 diff --git a/stack-orchestrator/container-build/cerc-webapp-base/scripts/start-serving-app.sh b/stack-orchestrator/container-build/cerc-webapp-base/scripts/start-serving-app.sh new file mode 100755 index 0000000..3a114ee --- /dev/null +++ b/stack-orchestrator/container-build/cerc-webapp-base/scripts/start-serving-app.sh @@ -0,0 +1,48 @@ +#!/usr/bin/env bash +if [ -n "$CERC_SCRIPT_DEBUG" ]; then + set -x +fi + +CERC_LISTEN_PORT=${CERC_LISTEN_PORT:-80} +CERC_WEBAPP_FILES_DIR="${CERC_WEBAPP_FILES_DIR:-/data}" +CERC_ENABLE_CORS="${CERC_ENABLE_CORS:-false}" +CERC_SINGLE_PAGE_APP="${CERC_SINGLE_PAGE_APP}" + +if [ -z "${CERC_SINGLE_PAGE_APP}" ]; then + # If there is only one HTML file, assume an SPA. + if [ 1 -eq $(find "${CERC_WEBAPP_FILES_DIR}" -name '*.html' | wc -l) ]; then + CERC_SINGLE_PAGE_APP=true + else + CERC_SINGLE_PAGE_APP=false + fi +fi + +# ${var,,} is a lower-case comparison +if [ "true" == "${CERC_ENABLE_CORS,,}" ]; then + CERC_HTTP_EXTRA_ARGS="$CERC_HTTP_EXTRA_ARGS --cors" +fi + +# ${var,,} is a lower-case comparison +if [ "true" == "${CERC_SINGLE_PAGE_APP,,}" ]; then + echo "Serving content as single-page app. If this is wrong, set 'CERC_SINGLE_PAGE_APP=false'" + # Create a catchall redirect back to / + CERC_HTTP_EXTRA_ARGS="$CERC_HTTP_EXTRA_ARGS --proxy http://localhost:${CERC_LISTEN_PORT}?" +else + echo "Serving content normally. If this is a single-page app, set 'CERC_SINGLE_PAGE_APP=true'" +fi + +LACONIC_HOSTED_CONFIG_FILE=${LACONIC_HOSTED_CONFIG_FILE} +if [ -z "${LACONIC_HOSTED_CONFIG_FILE}" ]; then + if [ -f "/config/laconic-hosted-config.yml" ]; then + LACONIC_HOSTED_CONFIG_FILE="/config/laconic-hosted-config.yml" + elif [ -f "/config/config.yml" ]; then + LACONIC_HOSTED_CONFIG_FILE="/config/config.yml" + fi +fi + +if [ -f "${LACONIC_HOSTED_CONFIG_FILE}" ]; then + /scripts/apply-webapp-config.sh $LACONIC_HOSTED_CONFIG_FILE "${CERC_WEBAPP_FILES_DIR}" +fi + +/scripts/apply-runtime-env.sh ${CERC_WEBAPP_FILES_DIR} +http-server $CERC_HTTP_EXTRA_ARGS -p ${CERC_LISTEN_PORT} "${CERC_WEBAPP_FILES_DIR}" diff --git a/stack-orchestrator/stacks/laconic-console/stack.yml b/stack-orchestrator/stacks/laconic-console/stack.yml index 7a2d6db..a25eca7 100644 --- a/stack-orchestrator/stacks/laconic-console/stack.yml +++ b/stack-orchestrator/stacks/laconic-console/stack.yml @@ -3,7 +3,10 @@ name: laconic-console description: "Laconic registry CLI and console" repos: - git.vdb.to/cerc-io/laconic-registry-cli@laconic2 + - git.vdb.to/cerc-io/laconic-console@laconic2 containers: - cerc/laconic2-registry-cli + - cerc/webapp-base + - cerc/laconic-console-host pods: - laconic-console