feat: interactive CF Apps install menu with oem/collaborator/orchard profiles
Adds a manifest-driven "Install CircuitForge Apps" menu to install.sh, alongside the existing extras-menu pattern. - cf-apps/*.manifest: one file per product (circuitforge-core, peregrine, kiwi, snipe, turnstone, pagepiper, linnet - the beta/alpha menagerie products), declaring repo URLs, supported install types, conda env, .env template, and per-install-type setup hooks. Hooks prefer shelling out to each product's own install.sh/Makefile/docker-compose rather than reimplementing their setup logic. - lib/cf-apps.functions: registry loader, provisioning-profile prompt (oem/collaborator/orchard), app multiselect menu, and the install dispatcher (clone, .env bootstrap, install-type selection, hook dispatch). circuitforge-core installs automatically as a dependency for apps that declare app_needs_core=true. Provisioning profiles gate both which apps are offered and which remote credentials are used: - oem: public CircuitForgeLLC GitHub mirrors only, no Forgejo access, no circuitforge-orch (product install menu only) - collaborator: private Circuit-Forge Forgejo, same product menu - orchard: narrow flow, not the product menu - clones circuitforge-orch (Forgejo-only, no public mirror exists) and hands off interactively to its own install.sh, which gathers agent/coordinator topology itself (it has no --topology/--coordinator-url flags to script around). NOTE: circuitforge-orch has no model-sync/cache-sync mechanism today (checked its install.sh and README); this wrapper doesn't invent one. Design rationale and survey of each product's real install story: circuitforge-plans/cf-node-bootstrap/superpowers/plans/2026-07-17-cf-apps-install-menu.md
This commit is contained in:
parent
d72d8fb89c
commit
2a325b55bd
10 changed files with 468 additions and 0 deletions
39
cf-apps/README.md
Normal file
39
cf-apps/README.md
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
# CF Apps manifests
|
||||
|
||||
One `.manifest` file per CircuitForge product, sourced by
|
||||
`lib/cf-apps.functions`. Each is a plain bash file defining these
|
||||
variables (see any existing manifest for a concrete example):
|
||||
|
||||
```bash
|
||||
app_name="" # matches the filename minus .manifest
|
||||
app_desc="" # one line, shown in the selection menu
|
||||
app_repo_url_github="" # public CircuitForgeLLC mirror, used for the oem profile
|
||||
app_repo_url_forgejo="" # private Circuit-Forge Forgejo, used for the collaborator profile
|
||||
app_available_profiles=() # which of: oem collaborator
|
||||
app_install_types=() # which of: bare-metal docker podman, in menu display order
|
||||
app_conda_env="" # empty string if not applicable
|
||||
app_env_template="" # path relative to repo root, empty if none
|
||||
app_needs_core=false # true if circuitforge-core must be installed first
|
||||
```
|
||||
|
||||
Optionally define hook functions for whichever install types the app
|
||||
actually supports (skip the ones it doesn't):
|
||||
|
||||
```bash
|
||||
app_setup_bare_metal() { local dir="$1"; ... }
|
||||
app_setup_docker() { local dir="$1"; ... }
|
||||
app_setup_podman() { local dir="$1"; ... }
|
||||
```
|
||||
|
||||
`$1` is the app's clone directory. Prefer shelling out to the app's own
|
||||
`install.sh`/`Makefile`/`docker compose` rather than reimplementing its
|
||||
setup steps here — most CF products already have one.
|
||||
|
||||
If a hook is left undefined for an install type the app declares in
|
||||
`app_install_types`, the dispatcher clones the repo and prints a warning
|
||||
telling the user to finish setup manually, rather than silently doing
|
||||
nothing.
|
||||
|
||||
See `circuitforge-plans/cf-node-bootstrap/superpowers/plans/2026-07-17-cf-apps-install-menu.md`
|
||||
for the full design rationale, including the OEM/collaborator/orchard
|
||||
provisioning-profile split.
|
||||
20
cf-apps/circuitforge-core.manifest
Normal file
20
cf-apps/circuitforge-core.manifest
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
app_name="circuitforge-core"
|
||||
app_desc="Shared scaffold (db/llm/tiers/config) used by other CF products"
|
||||
app_repo_url_github="https://github.com/CircuitForgeLLC/circuitforge-core.git"
|
||||
app_repo_url_forgejo="https://git.opensourcesolarpunk.com/Circuit-Forge/circuitforge-core.git"
|
||||
# Not directly user-selectable in the app menu (installed automatically as a
|
||||
# dependency by install-cf-core-dependency), but still needs a profile list
|
||||
# so its own clone step honors the oem/collaborator remote split.
|
||||
app_available_profiles=(oem collaborator)
|
||||
app_install_types=(bare-metal)
|
||||
app_conda_env="cf"
|
||||
app_env_template=""
|
||||
app_needs_core=false
|
||||
|
||||
app_setup_bare_metal() {
|
||||
local dir="$1"
|
||||
run "conda run -n ${app_conda_env} pip install -e \"${dir}\""
|
||||
if [[ $dry_run != true ]]; then
|
||||
conda run -n "${app_conda_env}" pip install -e "${dir}"
|
||||
fi
|
||||
}
|
||||
32
cf-apps/kiwi.manifest
Normal file
32
cf-apps/kiwi.manifest
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
app_name="kiwi"
|
||||
app_desc="Pantry tracker + leftover recipe suggestions (beta)"
|
||||
app_repo_url_github="https://github.com/CircuitForgeLLC/kiwi.git"
|
||||
app_repo_url_forgejo="https://git.opensourcesolarpunk.com/Circuit-Forge/kiwi.git"
|
||||
app_available_profiles=(oem collaborator)
|
||||
app_install_types=(docker bare-metal)
|
||||
app_conda_env="kiwi"
|
||||
app_env_template=".env.example"
|
||||
app_needs_core=true
|
||||
|
||||
# kiwi has a single Dockerfile (no compose), listening on 8512. No
|
||||
# persistent-storage volume is assumed here since that isn't documented;
|
||||
# follow up manually if kiwi needs one for your data to survive a restart.
|
||||
app_setup_docker() {
|
||||
local dir="$1"
|
||||
boxline "Building and running ${app_name} (port 8512)..."
|
||||
(cd "$dir" && docker build -t "${app_name}" .)
|
||||
(cd "$dir" && docker run -d --name "${app_name}" --env-file .env -p 8512:8512 "${app_name}")
|
||||
}
|
||||
|
||||
# kiwi's bare-metal path is documented (docs/getting-started/installation.md,
|
||||
# llm-setup.md - BYOK: Ollama/vLLM/OpenAI/Anthropic) but not scripted end to
|
||||
# end. This creates the conda env from environment.yml; point the user at
|
||||
# the docs for the LLM backend choice rather than guessing one.
|
||||
app_setup_bare_metal() {
|
||||
local dir="$1"
|
||||
run "conda env create -f \"${dir}/environment.yml\" -n \"${app_conda_env}\""
|
||||
if [[ $dry_run != true ]]; then
|
||||
conda env create -f "${dir}/environment.yml" -n "${app_conda_env}"
|
||||
fi
|
||||
boxline "${app_name}: conda env '${app_conda_env}' created. See docs/getting-started/installation.md and llm-setup.md for the remaining BYOK LLM backend setup (Ollama/vLLM/OpenAI/Anthropic)."
|
||||
}
|
||||
17
cf-apps/linnet.manifest
Normal file
17
cf-apps/linnet.manifest
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
app_name="linnet"
|
||||
app_desc="Real-time tone annotation product (alpha)"
|
||||
app_repo_url_github="https://github.com/CircuitForgeLLC/linnet.git"
|
||||
app_repo_url_forgejo="https://git.opensourcesolarpunk.com/Circuit-Forge/linnet.git"
|
||||
app_available_profiles=(oem collaborator)
|
||||
app_install_types=(docker)
|
||||
app_conda_env=""
|
||||
app_env_template=".env.example"
|
||||
app_needs_core=true
|
||||
|
||||
# linnet also has compose.cloud.yml/compose.demo.yml/compose.test.yml;
|
||||
# plain compose.yml is the fresh-node default.
|
||||
app_setup_docker() {
|
||||
local dir="$1"
|
||||
boxline "Running docker compose up for ${app_name}..."
|
||||
(cd "$dir" && docker compose -f compose.yml up -d)
|
||||
}
|
||||
17
cf-apps/pagepiper.manifest
Normal file
17
cf-apps/pagepiper.manifest
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
app_name="pagepiper"
|
||||
app_desc="Document/content pipeline tool (beta)"
|
||||
app_repo_url_github="https://github.com/CircuitForgeLLC/pagepiper.git"
|
||||
app_repo_url_forgejo="https://git.opensourcesolarpunk.com/Circuit-Forge/pagepiper.git"
|
||||
app_available_profiles=(oem collaborator)
|
||||
app_install_types=(docker)
|
||||
app_conda_env="pagepiper"
|
||||
app_env_template=".env.example"
|
||||
app_needs_core=true
|
||||
|
||||
# pagepiper also has compose.cloud.yml/compose.override.yml(.example) for
|
||||
# cloud/dev variants; plain compose.yml is the fresh-node default.
|
||||
app_setup_docker() {
|
||||
local dir="$1"
|
||||
boxline "Running docker compose up for ${app_name}..."
|
||||
(cd "$dir" && docker compose -f compose.yml up -d)
|
||||
}
|
||||
29
cf-apps/peregrine.manifest
Normal file
29
cf-apps/peregrine.manifest
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
app_name="peregrine"
|
||||
app_desc="LLM-powered job discovery and application pipeline (beta)"
|
||||
app_repo_url_github="https://github.com/CircuitForgeLLC/peregrine.git"
|
||||
app_repo_url_forgejo="https://git.opensourcesolarpunk.com/Circuit-Forge/peregrine.git"
|
||||
app_available_profiles=(oem collaborator)
|
||||
app_install_types=(bare-metal docker podman)
|
||||
app_conda_env="cf"
|
||||
app_env_template=".env.example"
|
||||
app_needs_core=true
|
||||
|
||||
# peregrine ships its own install.sh (installs Docker/Podman + NVIDIA
|
||||
# toolkit per-distro) and a Makefile (setup -> preflight -> start, with
|
||||
# PROFILE=remote|cpu|single-gpu|dual-gpu). Shell out rather than
|
||||
# reimplementing that logic here.
|
||||
app_setup_bare_metal() {
|
||||
local dir="$1"
|
||||
boxline "Handing off to ${app_name}'s own install.sh..."
|
||||
(cd "$dir" && ./install.sh)
|
||||
}
|
||||
|
||||
app_setup_docker() {
|
||||
local dir="$1"
|
||||
boxline "Handing off to ${app_name}'s own install.sh (choose a docker/podman GPU profile when prompted)..."
|
||||
(cd "$dir" && ./install.sh)
|
||||
}
|
||||
|
||||
app_setup_podman() {
|
||||
app_setup_docker "$1"
|
||||
}
|
||||
15
cf-apps/snipe.manifest
Normal file
15
cf-apps/snipe.manifest
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
app_name="snipe"
|
||||
app_desc="eBay trust-scoring tool (beta)"
|
||||
app_repo_url_github="https://github.com/CircuitForgeLLC/snipe.git"
|
||||
app_repo_url_forgejo="https://git.opensourcesolarpunk.com/Circuit-Forge/snipe.git"
|
||||
app_available_profiles=(oem collaborator)
|
||||
app_install_types=(docker)
|
||||
app_conda_env=""
|
||||
app_env_template=".env.example"
|
||||
app_needs_core=true
|
||||
|
||||
app_setup_docker() {
|
||||
local dir="$1"
|
||||
boxline "Handing off to ${app_name}'s own install.sh..."
|
||||
(cd "$dir" && ./install.sh)
|
||||
}
|
||||
30
cf-apps/turnstone.manifest
Normal file
30
cf-apps/turnstone.manifest
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
app_name="turnstone"
|
||||
app_desc="Log diagnostics tool (beta)"
|
||||
app_repo_url_github="https://github.com/CircuitForgeLLC/turnstone.git"
|
||||
app_repo_url_forgejo="https://git.opensourcesolarpunk.com/Circuit-Forge/turnstone.git"
|
||||
app_available_profiles=(oem collaborator)
|
||||
app_install_types=(bare-metal docker)
|
||||
app_conda_env="cf"
|
||||
app_env_template=""
|
||||
app_needs_core=true
|
||||
|
||||
# turnstone's own install.sh finds/installs conda and sets up the "cf" env
|
||||
# + pip deps; shell out rather than reimplementing that.
|
||||
app_setup_bare_metal() {
|
||||
local dir="$1"
|
||||
boxline "Handing off to ${app_name}'s own install.sh..."
|
||||
(cd "$dir" && ./install.sh)
|
||||
}
|
||||
|
||||
# turnstone ships docker-standalone.sh alongside docker-compose.yml for a
|
||||
# single-container run; prefer that as the simpler default entry point.
|
||||
app_setup_docker() {
|
||||
local dir="$1"
|
||||
if [ -x "${dir}/docker-standalone.sh" ]; then
|
||||
boxline "Running ${app_name}'s docker-standalone.sh..."
|
||||
(cd "$dir" && ./docker-standalone.sh)
|
||||
else
|
||||
boxline "Running docker compose up for ${app_name}..."
|
||||
(cd "$dir" && docker compose -f docker-compose.yml up -d)
|
||||
fi
|
||||
}
|
||||
15
install.sh
15
install.sh
|
|
@ -32,6 +32,11 @@ else
|
|||
fi
|
||||
fi
|
||||
|
||||
# Source CF Apps registry/installer (see cf-apps/README.md)
|
||||
if [ -f "${rundir}/lib/cf-apps.functions" ] ; then
|
||||
source "${rundir}/lib/cf-apps.functions"
|
||||
fi
|
||||
|
||||
rundir_absolute=$(pushd $rundir ; pwd ; popd)
|
||||
escape_dir=$(printf %q "${rundir_absolute}")
|
||||
logfile="${rundir}/${pretty_date}_${scriptname}.log"
|
||||
|
|
@ -416,6 +421,11 @@ userinstall(){
|
|||
# launch extra installs
|
||||
extras-menu
|
||||
|
||||
# Optionally provision this node with CircuitForge apps
|
||||
if declare -F cf-provision-menu >/dev/null; then
|
||||
cf-provision-menu
|
||||
fi
|
||||
|
||||
if [[ $dry_run != true ]] ; then
|
||||
boxborder "${grn}Please be sure to run ${lyl}sensors-detect --auto${grn} after installation completes${dfl}"
|
||||
fi
|
||||
|
|
@ -484,6 +494,11 @@ globalinstall(){
|
|||
|
||||
# Download and install any other extras
|
||||
extras-menu
|
||||
|
||||
# Optionally provision this node with CircuitForge apps
|
||||
if declare -F cf-provision-menu >/dev/null; then
|
||||
cf-provision-menu
|
||||
fi
|
||||
#clear
|
||||
}
|
||||
|
||||
|
|
|
|||
254
lib/cf-apps.functions
Normal file
254
lib/cf-apps.functions
Normal file
|
|
@ -0,0 +1,254 @@
|
|||
#!/bin/bash
|
||||
# CF Apps registry + interactive install dispatcher for cf-node-bootstrap.
|
||||
#
|
||||
# Manifests live in cf-apps/*.manifest (see cf-apps/README.md for the
|
||||
# schema). Design rationale, including the oem/collaborator/orchard
|
||||
# provisioning-profile split, is in:
|
||||
# circuitforge-plans/cf-node-bootstrap/superpowers/plans/2026-07-17-cf-apps-install-menu.md
|
||||
|
||||
cf_apps_manifest_dir="${cf_apps_manifest_dir:-${rundir}/cf-apps}"
|
||||
|
||||
# Resets the app_* manifest variables/functions so sourcing one manifest
|
||||
# can't leak stale values into the next.
|
||||
_cf-apps-reset-manifest-vars() {
|
||||
unset app_name app_desc app_repo_url_github app_repo_url_forgejo \
|
||||
app_available_profiles app_install_types app_conda_env \
|
||||
app_env_template app_needs_core
|
||||
unset -f app_setup_bare_metal app_setup_docker app_setup_podman 2>/dev/null
|
||||
}
|
||||
|
||||
# Prompts for a base directory to clone CF app repos into; sets $cf_apps_dir.
|
||||
prompt-cf-apps-dir() {
|
||||
local default_dir="${HOME}/CircuitForge"
|
||||
boxborder "${lyl}Where should CircuitForge app repos be cloned?${dfl} ${gry}(default: ${default_dir})${dfl}"
|
||||
local input_dir
|
||||
read -r input_dir
|
||||
cf_apps_dir="${input_dir:-$default_dir}"
|
||||
}
|
||||
|
||||
# Yes/no gate + entry point, mirroring install.sh's extras-menu pattern.
|
||||
# Call this from install.sh's userinstall/globalinstall alongside extras-menu.
|
||||
cf-provision-menu() {
|
||||
boxborder "${lyl}Set up this node for CircuitForge apps?${dfl} ${gry}(product installs or joining an orchard)${dfl}"
|
||||
local cfapps_menu=(
|
||||
"$(boxline "${green_check} Yes")"
|
||||
"$(boxline "${red_x} No")"
|
||||
)
|
||||
case `select_opt "${cfapps_menu[@]}"` in
|
||||
0) prompt-cf-apps-dir
|
||||
cf-provision-node
|
||||
;;
|
||||
1) boxline "Skipping CircuitForge app provisioning" ;;
|
||||
esac
|
||||
}
|
||||
|
||||
# Populates parallel arrays describing every available app manifest.
|
||||
# Call before cf-apps-menu.
|
||||
load-cf-apps() {
|
||||
cf_app_names=()
|
||||
cf_app_descs=()
|
||||
cf_app_profiles=()
|
||||
local manifest
|
||||
for manifest in "${cf_apps_manifest_dir}"/*.manifest; do
|
||||
[ -f "$manifest" ] || continue
|
||||
_cf-apps-reset-manifest-vars
|
||||
source "$manifest"
|
||||
cf_app_names+=("$app_name")
|
||||
cf_app_descs+=("$app_desc")
|
||||
cf_app_profiles+=("${app_available_profiles[*]}")
|
||||
done
|
||||
}
|
||||
|
||||
# Returns success if $1 (a space-joined profile list) contains $2
|
||||
_cf-apps-profile-allows() {
|
||||
local profiles=" $1 " want="$2"
|
||||
[[ "$profiles" == *" $want "* ]]
|
||||
}
|
||||
|
||||
# Prompts for what kind of node this is; sets $cf_provision_profile to
|
||||
# oem|collaborator|orchard. Gates both which apps are offered and which
|
||||
# remote (public GitHub vs private Forgejo) they're cloned from.
|
||||
select-provision-profile() {
|
||||
boxborder "${lyl}${unl}What kind of node is this?${dfl}"
|
||||
profile_menu=(
|
||||
"$(boxline "OEM / customer node ${gry}(public GitHub only, licensed products)${dfl}")"
|
||||
"$(boxline "Collaborator node ${gry}(private Forgejo, full app list)${dfl}")"
|
||||
"$(boxline "Orchard-join node ${gry}(cf-orch agent only)${dfl}")"
|
||||
)
|
||||
case `select_opt "${profile_menu[@]}"` in
|
||||
0) cf_provision_profile="oem" ;;
|
||||
1) cf_provision_profile="collaborator" ;;
|
||||
2) cf_provision_profile="orchard" ;;
|
||||
esac
|
||||
}
|
||||
|
||||
# Top-level entry point: prompts for a profile (unless already set), then
|
||||
# either runs the orchard-join flow or offers the app multiselect menu.
|
||||
cf-provision-node() {
|
||||
if [ -z "$cf_provision_profile" ]; then
|
||||
select-provision-profile
|
||||
fi
|
||||
|
||||
if [[ "$cf_provision_profile" == "orchard" ]]; then
|
||||
provision-orchard-node
|
||||
return
|
||||
fi
|
||||
|
||||
cf-apps-menu
|
||||
}
|
||||
|
||||
cf-apps-menu() {
|
||||
load-cf-apps
|
||||
|
||||
local menu_display=() menu_app_idx=() preselect=()
|
||||
local i
|
||||
for ((i=0; i<${#cf_app_names[@]}; i++)); do
|
||||
if _cf-apps-profile-allows "${cf_app_profiles[$i]}" "$cf_provision_profile"; then
|
||||
menu_display+=("${cf_app_names[$i]} ${gry}- ${cf_app_descs[$i]}${dfl}")
|
||||
menu_app_idx+=("$i")
|
||||
preselect+=("false")
|
||||
fi
|
||||
done
|
||||
|
||||
if [ "${#menu_display[@]}" -eq 0 ]; then
|
||||
warn "No CF apps are available for the '${cf_provision_profile}' profile."
|
||||
return
|
||||
fi
|
||||
|
||||
boxborder "${lyl}${unl}Select CircuitForge apps to install:${dfl}"
|
||||
multiselect result menu_display preselect
|
||||
|
||||
local idx=0 selected_i
|
||||
for selected_i in "${menu_app_idx[@]}"; do
|
||||
if [[ "${result[idx]}" == "true" ]]; then
|
||||
install-cf-app "${cf_app_names[$selected_i]}"
|
||||
fi
|
||||
((idx++))
|
||||
done
|
||||
}
|
||||
|
||||
# Installs circuitforge-core as an editable dependency, once per run.
|
||||
install-cf-core-dependency() {
|
||||
if [[ "$cf_core_installed" == true ]]; then
|
||||
return
|
||||
fi
|
||||
install-cf-app "circuitforge-core"
|
||||
cf_core_installed=true
|
||||
}
|
||||
|
||||
# Clones (if needed) and runs setup for a single app by name, honoring
|
||||
# $cf_provision_profile for which remote to clone from.
|
||||
install-cf-app() {
|
||||
local name="$1"
|
||||
local manifest="${cf_apps_manifest_dir}/${name}.manifest"
|
||||
if [ ! -f "$manifest" ]; then
|
||||
warn "No manifest found for '$name'"
|
||||
return 1
|
||||
fi
|
||||
_cf-apps-reset-manifest-vars
|
||||
source "$manifest"
|
||||
|
||||
if [[ "$app_needs_core" == true ]]; then
|
||||
install-cf-core-dependency
|
||||
fi
|
||||
|
||||
local repo_url
|
||||
if [[ "$cf_provision_profile" == "oem" ]]; then
|
||||
repo_url="$app_repo_url_github"
|
||||
else
|
||||
repo_url="$app_repo_url_forgejo"
|
||||
fi
|
||||
|
||||
local apps_base_dir="${cf_apps_dir:?cf_apps_dir must be set before calling install-cf-app}"
|
||||
local clone_dir="${apps_base_dir}/${app_name}"
|
||||
if [ -d "$clone_dir" ]; then
|
||||
warn "${clone_dir} already exists, skipping clone (will not overwrite)"
|
||||
else
|
||||
boxline "Cloning ${app_name} from ${repo_url}"
|
||||
run "git clone --recurse-submodules \"${repo_url}\" \"${clone_dir}\""
|
||||
if [[ $dry_run != true ]]; then
|
||||
mkdir -p "$apps_base_dir"
|
||||
git clone --recurse-submodules "$repo_url" "$clone_dir"
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ -n "$app_env_template" ] && [ -f "${clone_dir}/${app_env_template}" ] && [ ! -f "${clone_dir}/.env" ]; then
|
||||
cp "${clone_dir}/${app_env_template}" "${clone_dir}/.env"
|
||||
boxline "Created ${app_name}/.env from ${app_env_template}"
|
||||
fi
|
||||
|
||||
local install_type
|
||||
if [ "${#app_install_types[@]}" -gt 1 ]; then
|
||||
boxborder "${lyl}Install type for ${app_name}?${dfl}"
|
||||
local type_menu=() t
|
||||
for t in "${app_install_types[@]}"; do
|
||||
type_menu+=("$(boxline "$t")")
|
||||
done
|
||||
install_type="${app_install_types[$(select_opt "${type_menu[@]}")]}"
|
||||
else
|
||||
install_type="${app_install_types[0]}"
|
||||
fi
|
||||
|
||||
case "$install_type" in
|
||||
bare-metal)
|
||||
if declare -F app_setup_bare_metal >/dev/null; then
|
||||
app_setup_bare_metal "$clone_dir"
|
||||
else
|
||||
warn "${app_name}: no bare-metal setup hook defined yet; clone is at ${clone_dir}, follow its README manually"
|
||||
fi
|
||||
;;
|
||||
docker)
|
||||
if declare -F app_setup_docker >/dev/null; then
|
||||
app_setup_docker "$clone_dir"
|
||||
else
|
||||
warn "${app_name}: no docker setup hook defined yet; clone is at ${clone_dir}, follow its README manually"
|
||||
fi
|
||||
;;
|
||||
podman)
|
||||
if declare -F app_setup_podman >/dev/null; then
|
||||
app_setup_podman "$clone_dir"
|
||||
else
|
||||
warn "${app_name}: no podman setup hook defined yet; clone is at ${clone_dir}, follow its README manually"
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
# Narrow, single-purpose flow for adding a GPU worker node to an existing
|
||||
# cf-orch coordinator. circuitforge-orch has no public GitHub mirror (it's
|
||||
# CircuitForge's internal coordinator/agent product), so this always clones
|
||||
# from Forgejo regardless of provisioning profile.
|
||||
#
|
||||
# circuitforge-orch's own install.sh has no --topology/--coordinator-url
|
||||
# flags - topology and coordinator URL are gathered via its own interactive
|
||||
# prompts (read -rp). So this hands off to it interactively rather than
|
||||
# trying to script around those prompts.
|
||||
#
|
||||
# NOTE: circuitforge-orch's installer and README have no model-sync/
|
||||
# model-cache-sync mechanism today (checked both, no hits). This wrapper
|
||||
# does not invent one - if a node needs that, raise it with circuitforge-orch
|
||||
# directly rather than expecting it here.
|
||||
provision-orchard-node() {
|
||||
boxborder "${lyl}${unl}Join an existing CircuitForge orchard${dfl}"
|
||||
boxline "This will clone circuitforge-orch and hand off to its own"
|
||||
boxline "installer. When prompted for topology, choose 'agent' and have"
|
||||
boxline "your coordinator's URL ready."
|
||||
|
||||
local apps_base_dir="${cf_apps_dir:?cf_apps_dir must be set before calling provision-orchard-node}"
|
||||
local clone_dir="${apps_base_dir}/circuitforge-orch"
|
||||
local repo_url="https://git.opensourcesolarpunk.com/Circuit-Forge/circuitforge-orch.git"
|
||||
|
||||
if [ -d "$clone_dir" ]; then
|
||||
warn "${clone_dir} already exists, skipping clone (will not overwrite)"
|
||||
else
|
||||
boxline "Cloning circuitforge-orch from Forgejo"
|
||||
run "git clone \"${repo_url}\" \"${clone_dir}\""
|
||||
if [[ $dry_run != true ]]; then
|
||||
mkdir -p "$apps_base_dir"
|
||||
git clone "$repo_url" "$clone_dir"
|
||||
fi
|
||||
fi
|
||||
|
||||
boxline "Handing off to circuitforge-orch's own install.sh..."
|
||||
(cd "$clone_dir" && ./install.sh)
|
||||
}
|
||||
Loading…
Reference in a new issue