Files
obsidian-wiki/scripts/deploy-linux-server.sh
nac 777607dc29 chore(repo): establish project baseline
Track the source-of-truth code, skills, bootstrap rules, docs, and packaging files before adding new features.

Exclude local vault config, generated skill mirrors, temporary exports, virtual environments, and business data files from version control.

Validation: inspected staged file list and git ignored status before commit.
2026-07-05 11:03:40 +00:00

352 lines
9.6 KiB
Bash
Executable File

#!/usr/bin/env bash
#
# Deploy an obsidian-wiki Linux migration bundle on a headless server.
#
# Run this from the extracted bundle root:
# sudo bash deploy-linux-server.sh --install-dir /opt/obsidian-wiki --vault-dir /srv/bjd-vault
set -Eeuo pipefail
IFS=$'\n\t'
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
BUNDLE_DIR="$SCRIPT_DIR"
INSTALL_DIR="${INSTALL_DIR:-/opt/obsidian-wiki}"
VAULT_DIR="${VAULT_DIR:-/srv/bjd-vault}"
BOT_VIEW_DIR="${BOT_VIEW_DIR:-/srv/bjd-bot-views}"
QMD_WIKI_COLLECTION="${QMD_WIKI_COLLECTION:-bjd-wiki}"
QMD_CLI_SEARCH_MODE="${QMD_CLI_SEARCH_MODE:-balanced}"
PROFILE_LIST="${PROFILE_LIST:-all}"
SOURCE_FILE_PROFILES="${SOURCE_FILE_PROFILES:-quality-bot,engineering-bot}"
CONFIG_FILE="${CONFIG_FILE:-$HOME/.obsidian-wiki/config}"
RUN_DELETE=0
SKIP_FILE_INDEX=0
SKIP_BOT_VIEWS=0
SKIP_QMD=0
DRY_RUN=0
usage() {
cat <<'EOF'
Usage:
bash deploy-linux-server.sh [options]
Options:
--install-dir DIR Destination for obsidian-wiki repo. Default: /opt/obsidian-wiki
--vault-dir DIR Destination for mother vault. Default: /srv/bjd-vault
--bot-view-dir DIR Destination for derived bot views. Default: /srv/bjd-bot-views
--qmd-collection NAME Mother vault QMD collection name. Default: bjd-wiki
--profiles LIST Comma-separated bot profiles to export, or "all". Default: all
--source-file-profiles LIST Comma-separated profiles exported with --include-files.
Default: quality-bot,engineering-bot
--config-file PATH Config file to write. Default: ~/.obsidian-wiki/config
--delete Use rsync --delete when copying repo/vault.
--skip-file-index Do not rebuild _meta/file-assets.sqlite.
--skip-bot-views Do not export bot views.
--skip-qmd Do not create/update QMD collections.
--dry-run Print planned actions only.
-h, --help Show this help.
Environment variables with the same names as the defaults may also be used:
INSTALL_DIR, VAULT_DIR, BOT_VIEW_DIR, QMD_WIKI_COLLECTION,
QMD_CLI_SEARCH_MODE, PROFILE_LIST, SOURCE_FILE_PROFILES, CONFIG_FILE
EOF
}
die() {
echo "ERROR: $*" >&2
exit 1
}
info() {
echo "[INFO] $*"
}
warn() {
echo "[WARN] $*" >&2
}
expand_path() {
local path="$1"
case "$path" in
"~") printf '%s' "$HOME" ;;
"~/"*) printf '%s/%s' "$HOME" "${path#~/}" ;;
*) printf '%s' "$path" ;;
esac
}
trim() {
local value="$1"
value="${value#"${value%%[![:space:]]*}"}"
value="${value%"${value##*[![:space:]]}"}"
printf '%s' "$value"
}
csv_contains() {
local needle="$1"
local csv="$2"
local item
IFS=',' read -ra items <<< "$csv"
for item in "${items[@]}"; do
item="$(trim "$item")"
[[ "$item" == "$needle" ]] && return 0
done
return 1
}
require_command() {
local cmd="$1"
command -v "$cmd" >/dev/null 2>&1 || die "missing command: $cmd"
}
while [[ $# -gt 0 ]]; do
case "$1" in
--install-dir)
[[ $# -ge 2 ]] || die "--install-dir requires a directory"
INSTALL_DIR="$2"
shift 2
;;
--vault-dir)
[[ $# -ge 2 ]] || die "--vault-dir requires a directory"
VAULT_DIR="$2"
shift 2
;;
--bot-view-dir)
[[ $# -ge 2 ]] || die "--bot-view-dir requires a directory"
BOT_VIEW_DIR="$2"
shift 2
;;
--qmd-collection)
[[ $# -ge 2 ]] || die "--qmd-collection requires a name"
QMD_WIKI_COLLECTION="$2"
shift 2
;;
--profiles)
[[ $# -ge 2 ]] || die "--profiles requires a comma-separated list or all"
PROFILE_LIST="$2"
shift 2
;;
--source-file-profiles)
[[ $# -ge 2 ]] || die "--source-file-profiles requires a comma-separated list"
SOURCE_FILE_PROFILES="$2"
shift 2
;;
--config-file)
[[ $# -ge 2 ]] || die "--config-file requires a path"
CONFIG_FILE="$2"
shift 2
;;
--delete)
RUN_DELETE=1
shift
;;
--skip-file-index)
SKIP_FILE_INDEX=1
shift
;;
--skip-bot-views)
SKIP_BOT_VIEWS=1
shift
;;
--skip-qmd)
SKIP_QMD=1
shift
;;
--dry-run)
DRY_RUN=1
shift
;;
-h|--help)
usage
exit 0
;;
*)
die "unknown option: $1"
;;
esac
done
INSTALL_DIR="$(expand_path "$INSTALL_DIR")"
VAULT_DIR="$(expand_path "$VAULT_DIR")"
BOT_VIEW_DIR="$(expand_path "$BOT_VIEW_DIR")"
CONFIG_FILE="$(expand_path "$CONFIG_FILE")"
BUNDLE_REPO="$BUNDLE_DIR/obsidian-wiki"
BUNDLE_VAULT="$BUNDLE_DIR/vault"
[[ -d "$BUNDLE_REPO/obsidian_wiki" ]] || die "bundle repo missing: $BUNDLE_REPO"
[[ -d "$BUNDLE_VAULT" ]] || die "bundle vault missing: $BUNDLE_VAULT"
require_command python3
require_command rsync
info "bundle: $BUNDLE_DIR"
info "install dir: $INSTALL_DIR"
info "vault dir: $VAULT_DIR"
info "bot views: $BOT_VIEW_DIR"
info "config file: $CONFIG_FILE"
info "profiles: $PROFILE_LIST"
info "source files: $SOURCE_FILE_PROFILES"
if [[ "$DRY_RUN" -eq 1 ]]; then
info "dry run complete; no files copied."
exit 0
fi
mkdir -p "$INSTALL_DIR" "$VAULT_DIR" "$BOT_VIEW_DIR" "$(dirname "$CONFIG_FILE")"
RSYNC_ARGS=(-a)
if [[ "$RUN_DELETE" -eq 1 ]]; then
RSYNC_ARGS+=(--delete)
fi
info "copying obsidian-wiki repo..."
rsync "${RSYNC_ARGS[@]}" "$BUNDLE_REPO/" "$INSTALL_DIR/"
info "copying mother vault..."
rsync "${RSYNC_ARGS[@]}" "$BUNDLE_VAULT/" "$VAULT_DIR/"
cat > "$INSTALL_DIR/.env" <<EOF
OBSIDIAN_VAULT_PATH="$VAULT_DIR"
OBSIDIAN_WIKI_REPO="$INSTALL_DIR"
WIKI_ARCHIVE_SOURCES=true
OBSIDIAN_SOURCE_ARCHIVE_DIR=_sources/archive
WIKI_SOURCE_CATALOG=true
OBSIDIAN_SOURCE_CATALOG_DIR=_sources/catalog
OBSIDIAN_LINK_FORMAT=wikilink
OBSIDIAN_FILE_ASSET_DB=_meta/file-assets.sqlite
QMD_WIKI_COLLECTION=$QMD_WIKI_COLLECTION
QMD_TRANSPORT=cli
QMD_CLI_SEARCH_MODE=$QMD_CLI_SEARCH_MODE
QMD_CLI=qmd
EOF
cat > "$CONFIG_FILE" <<EOF
OBSIDIAN_VAULT_PATH="$VAULT_DIR"
OBSIDIAN_WIKI_REPO="$INSTALL_DIR"
OBSIDIAN_FILE_ASSET_DB="_meta/file-assets.sqlite"
QMD_WIKI_COLLECTION=$QMD_WIKI_COLLECTION
QMD_TRANSPORT=cli
QMD_CLI_SEARCH_MODE=$QMD_CLI_SEARCH_MODE
QMD_CLI=qmd
EOF
mkdir -p "$INSTALL_DIR/bin"
cat > "$INSTALL_DIR/bin/obsidian-wiki-local" <<'EOF'
#!/usr/bin/env bash
INSTALL_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
export PYTHONPATH="$INSTALL_DIR${PYTHONPATH:+:$PYTHONPATH}"
exec python3 -m obsidian_wiki "$@"
EOF
chmod +x "$INSTALL_DIR/bin/obsidian-wiki-local"
ow() {
PYTHONPATH="$INSTALL_DIR${PYTHONPATH:+:$PYTHONPATH}" python3 -m obsidian_wiki "$@"
}
if [[ "$SKIP_FILE_INDEX" -eq 0 ]]; then
info "rebuilding file asset SQLite index..."
ow file-index rebuild --vault "$VAULT_DIR"
fi
PROFILES=()
if [[ "$PROFILE_LIST" == "all" ]]; then
if PROFILE_OUTPUT="$(ow bot-view list --vault "$VAULT_DIR" 2>&1)"; then
while IFS= read -r profile; do
[[ -n "$profile" ]] && PROFILES+=("$profile")
done <<< "$PROFILE_OUTPUT"
else
warn "bot profiles are not available; skipping bot-view audit/export."
[[ -n "$PROFILE_OUTPUT" ]] && warn "$PROFILE_OUTPUT"
SKIP_BOT_VIEWS=1
fi
else
IFS=',' read -ra raw_profiles <<< "$PROFILE_LIST"
for profile in "${raw_profiles[@]}"; do
profile="$(trim "$profile")"
[[ -n "$profile" ]] && PROFILES+=("$profile")
done
fi
if [[ "${#PROFILES[@]}" -gt 0 ]]; then
info "auditing bot-view ACLs..."
ow bot-view audit --vault "$VAULT_DIR" || warn "bot-view audit failed; continuing deployment."
fi
if [[ "$SKIP_BOT_VIEWS" -eq 0 ]]; then
info "exporting bot views..."
for profile in "${PROFILES[@]}"; do
extra_args=()
if csv_contains "$profile" "$SOURCE_FILE_PROFILES"; then
extra_args+=(--include-files)
fi
ow bot-view export --vault "$VAULT_DIR" "$profile" --out "$BOT_VIEW_DIR" "${extra_args[@]}"
done
fi
qmd_collection_exists() {
qmd collection show "$1" >/dev/null 2>&1
}
qmd_remove_if_exists() {
local name="$1"
if qmd_collection_exists "$name"; then
qmd collection remove "$name" >/dev/null 2>&1 || true
fi
}
qmd_add_collection_named() {
local name="$1"
local path="$2"
local pattern="${3:-**/*.md}"
qmd_remove_if_exists "$name"
qmd collection add "$path" --name "$name" --mask "$pattern"
}
if [[ "$SKIP_QMD" -eq 0 ]]; then
if command -v qmd >/dev/null 2>&1; then
info "creating/updating QMD mother-vault collection..."
qmd_add_collection_named "$QMD_WIKI_COLLECTION" "$VAULT_DIR" '**/*.md'
if [[ "$SKIP_BOT_VIEWS" -eq 0 ]]; then
for profile in "${PROFILES[@]}"; do
bot_wiki="$BOT_VIEW_DIR/$profile/wiki"
if [[ -d "$bot_wiki" ]]; then
qmd_add_collection_named "$QMD_WIKI_COLLECTION-$profile" "$bot_wiki" '**/*.md'
fi
done
fi
info "refreshing QMD indexes..."
qmd update
info "refreshing QMD embeddings..."
qmd embed -c "$QMD_WIKI_COLLECTION" || warn "QMD embed failed for $QMD_WIKI_COLLECTION"
if [[ "$SKIP_BOT_VIEWS" -eq 0 ]]; then
for profile in "${PROFILES[@]}"; do
qmd embed -c "$QMD_WIKI_COLLECTION-$profile" || warn "QMD embed failed for $QMD_WIKI_COLLECTION-$profile"
done
fi
else
warn "qmd not found; skipped QMD collection rebuild. Install qmd, then rerun with --skip-file-index --skip-bot-views if only QMD is needed."
fi
fi
cat <<EOF
Deployment complete.
Mother vault:
$VAULT_DIR
obsidian-wiki command wrapper:
$INSTALL_DIR/bin/obsidian-wiki-local
Examples:
$INSTALL_DIR/bin/obsidian-wiki-local file-index search "FH52K drawing" -n 10
$INSTALL_DIR/bin/obsidian-wiki-local bot-view audit
qmd search "FH52K replacement" -c "$QMD_WIKI_COLLECTION-sales-bot" -n 5
Important:
Point each robot at its own bot-view directory or QMD collection, not at the mother vault.
EOF