Files
nextcloud-collabora-nodered/scripts/integrate-into-nodered.sh
T
root 4fb614be87 Add node docs, collabora purple UI, and portable install env
Human-readable help in all six palette nodes, docs/NODES.md with usage examples, expanded example flows, and auto-detect NODERED_DIR plus CODE bridge IP from .env on any VM.
2026-08-22 17:10:34 +02:00

408 lines
14 KiB
Bash
Executable File

#!/usr/bin/env bash
# integrate-into-nodered.sh — lägg Collabora-noder i BEFINTLIG nextcloud-node-red (:1880)
# Anropas av install.sh / bootstrap-install.sh (standardläge).
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PKG_SRC="${PKG_SRC:-$(cd "${SCRIPT_DIR}/.." && pwd)}"
NODERED_DIR="${NODERED_DIR:-/home/ncadmin/nextcloud-node-red}"
NODERED_CONTAINER="${NODERED_CONTAINER:-nextcloud-node-red}"
NODERED_PORT="${NODERED_PORT:-1880}"
CODE_CONTAINER="${CODE_CONTAINER:-code}"
COLLABORA_IP="${COLLABORA_IP:-}"
NEXTCLOUD_URL="${NEXTCLOUD_URL:-https://cloud.hannesahlin.se}"
MARKER="nextcloud-collabora-nodered"
log() { printf '[integrate] %s\n' "$*"; }
warn() { printf '[integrate] VARNING: %s\n' "$*" >&2; }
die() { printf '[integrate] FEL: %s\n' "$*" >&2; exit 1; }
run_as_root() {
if [ "$(id -u)" -eq 0 ]; then
"$@"
elif command -v sudo >/dev/null 2>&1; then
sudo "$@"
else
die "sudo/root krävs för: $*"
fi
}
runtime_user() {
if [ -n "${INSTALL_USER:-}" ]; then
echo "${INSTALL_USER}"
elif id ncadmin >/dev/null 2>&1; then
echo "ncadmin"
else
id -un
fi
}
# Docker build context — .dockerignore + rättigheter (undviker permission denied)
DOCKERIGNORE_MARKER="nextcloud-collabora-nodered"
patch_dockerignore() {
local di="${NODERED_DIR}/.dockerignore"
local snippet="${PKG_SRC}/integrate/dockerignore.snippet"
if [ ! -f "${di}" ]; then
log "Skapar .dockerignore"
touch "${di}"
fi
if ! grep -q "${DOCKERIGNORE_MARKER}" "${di}" 2>/dev/null; then
log "Patchar .dockerignore (exkluderar oläsbara lokala artefakter)"
{
echo ""
echo "# ${DOCKERIGNORE_MARKER}"
if [ -f "${snippet}" ]; then
cat "${snippet}"
else
echo "nodes/odds-scraper/odds-scraper"
echo ".git"
echo ".vscode"
echo "**/*.deb"
echo "winboat*"
echo "**/node_modules"
fi
} >> "${di}"
fi
# Dynamiskt: alla filer under nodes/ som inte ens root kan läsa → ignorera
local unreadable
while IFS= read -r relpath; do
[ -n "${relpath}" ] || continue
if ! grep -qxF "${relpath}" "${di}" 2>/dev/null; then
log " + .dockerignore: ${relpath} (oläsbar)"
echo "${relpath}" >> "${di}"
fi
done < <(run_as_root find "${NODERED_DIR}/nodes" -type f ! -readable -printf 'nodes/%P\n' 2>/dev/null || true)
}
fix_nodered_build_permissions() {
local user group
user="$(runtime_user)"
group="$(id -gn "${user}" 2>/dev/null || echo "${user}")"
if [ ! -d "${NODERED_DIR}/nodes" ]; then
return 0
fi
log "Fixar ägande och läsrättigheter för ${NODERED_DIR} (innehåll orört)"
run_as_root chown -R "${user}:${group}" "${NODERED_DIR}"
run_as_root find "${NODERED_DIR}/nodes" -type d -exec chmod u+rwx,go+rx {} + 2>/dev/null || true
run_as_root find "${NODERED_DIR}/nodes" -type f -exec chmod u+rw,go+r {} + 2>/dev/null || true
# Binärer som ska vara körbara
run_as_root find "${NODERED_DIR}/nodes" -type f \( -name "*.sh" -o -name "odds-scraper" \) -exec chmod u+rx,go+rx {} + 2>/dev/null || true
}
# Simulera Docker build context — måste lyckas innan build
verify_build_context() {
local di="${NODERED_DIR}/.dockerignore"
local errfile
errfile="$(mktemp)"
log "Verifierar Docker build context..."
patch_dockerignore
if ! run_as_root tar -cf /dev/null \
--exclude-from="${di}" \
-C "${NODERED_DIR}" . 2>"${errfile}"; then
warn "Build context-fel:"
sed 's/^/[integrate] /' "${errfile}" >&2 || true
# Sista utväg: tvinga bort kända problemfil
if [ -f "${NODERED_DIR}/nodes/odds-scraper/odds-scraper" ]; then
echo "nodes/odds-scraper/odds-scraper" >> "${di}"
log "Tvingade nodes/odds-scraper/odds-scraper in i .dockerignore"
fi
if ! run_as_root tar -cf /dev/null \
--exclude-from="${di}" \
-C "${NODERED_DIR}" . 2>"${errfile}"; then
rm -f "${errfile}"
die "Build context oläsbar efter .dockerignore — se ${di}"
fi
fi
rm -f "${errfile}"
log "Build context OK"
}
compose_cmd() {
if docker compose version >/dev/null 2>&1; then
echo "docker compose"
else
echo "docker-compose"
fi
}
# Alltid root för compose build — samma användare som kan läsa hela trädet + .dockerignore
compose_run() {
local compose
compose="$(compose_cmd)"
cd "${NODERED_DIR}"
run_as_root env DOCKER_BUILDKIT=1 ${compose} "$@"
}
code_bridge_ip() {
docker inspect -f '{{range $k,$v := .NetworkSettings.Networks}}{{if eq $k "bridge"}}{{$v.IPAddress}}{{end}}{{end}}' "${CODE_CONTAINER}" 2>/dev/null
}
detect_code_ip() {
local ip
ip="$(code_bridge_ip)"
if [ -n "${ip}" ]; then
COLLABORA_IP="${ip}"
log "CODE bridge-IP: ${COLLABORA_IP} (container ${CODE_CONTAINER})"
return 0
fi
if [ -n "${COLLABORA_IP}" ]; then
warn "Kunde inte läsa bridge-IP — använder COLLABORA_IP=${COLLABORA_IP}"
return 0
fi
die "CODE bridge-IP saknas — sätt COLLABORA_IP eller kontrollera att ${CODE_CONTAINER} kör på Docker bridge"
}
detect_nodered_dir() {
if [ -d "${NODERED_DIR}" ] && [ -f "${NODERED_DIR}/Dockerfile" ]; then
return 0
fi
local from_compose
from_compose="$(docker inspect -f '{{index .Config.Labels "com.docker.compose.project.working_dir"}}' "${NODERED_CONTAINER}" 2>/dev/null || true)"
if [ -n "${from_compose}" ] && [ -f "${from_compose}/Dockerfile" ]; then
NODERED_DIR="${from_compose}"
log "Node-RED-katalog från compose-label: ${NODERED_DIR}"
return 0
fi
for candidate in \
"/home/ncadmin/nextcloud-node-red" \
"/opt/nextcloud-node-red"; do
if [ -f "${candidate}/Dockerfile" ]; then
NODERED_DIR="${candidate}"
log "Node-RED-katalog hittad: ${NODERED_DIR}"
return 0
fi
done
die "NODERED_DIR saknas eller ogiltig — sätt sökväg till befintlig nextcloud-node-red (Dockerfile + entrypoint.sh)"
}
resolve_paths() {
if [ -f "${PKG_SRC}/.env" ]; then
set -a
# shellcheck disable=SC1091
. "${PKG_SRC}/.env"
set +a
fi
if [ "$(id -u)" -eq 0 ] && [ -d "/home/ncadmin/nextcloud-node-red" ]; then
NODERED_DIR="${NODERED_DIR:-/home/ncadmin/nextcloud-node-red}"
PKG_SRC="${PKG_SRC:-/home/ncadmin/nextcloud-collabora-nodered}"
fi
detect_nodered_dir
detect_code_ip
}
stop_sidecar_if_any() {
if docker ps -a --format '{{.Names}}' | grep -qx "nextcloud-collabora-nodered"; then
warn "Stoppar felaktig sidecar-container nextcloud-collabora-nodered (skulle inte skapas)"
docker stop nextcloud-collabora-nodered 2>/dev/null || true
docker rm nextcloud-collabora-nodered 2>/dev/null || true
fi
}
preflight() {
[ -d "${PKG_SRC}" ] || die "Paketkälla saknas: ${PKG_SRC}"
[ -f "${PKG_SRC}/package.json" ] || die "package.json saknas i ${PKG_SRC}"
[ -d "${NODERED_DIR}" ] || die "nextcloud-node-red saknas: ${NODERED_DIR}"
[ -f "${NODERED_DIR}/Dockerfile" ] || die "Dockerfile saknas i ${NODERED_DIR}"
[ -f "${NODERED_DIR}/entrypoint.sh" ] || die "entrypoint.sh saknas i ${NODERED_DIR}"
if ! docker ps --format '{{.Names}}' | grep -qx "${NODERED_CONTAINER}"; then
die "Container '${NODERED_CONTAINER}' körs inte — starta befintlig nextcloud-node-red först"
fi
if ! docker ps --format '{{.Names}}' | grep -qx "${CODE_CONTAINER}"; then
die "CODE-container '${CODE_CONTAINER}' körs inte"
fi
}
assert_package_layout() {
local dest="$1"
local missing=0
if [ ! -f "${dest}/package.json" ]; then
warn "Saknas: ${dest}/package.json"
missing=1
fi
if [ ! -d "${dest}/lib" ] || [ -z "$(ls -A "${dest}/lib" 2>/dev/null)" ]; then
warn "Saknas eller tom: ${dest}/lib/"
missing=1
fi
if [ ! -d "${dest}/nodes" ] || [ -z "$(ls -A "${dest}/nodes" 2>/dev/null)" ]; then
warn "Saknas eller tom: ${dest}/nodes/"
missing=1
fi
if [ "${missing}" -ne 0 ]; then
die "Paketlayout fel efter kopiering — förväntat package.json + lib/ + nodes/ under ${dest}"
fi
}
copy_node_package() {
local dest="${NODERED_DIR}/nodes/nextcloud-collabora"
log "Kopierar paket → ${dest} (lib/ + nodes/, inte platt)"
rm -rf "${dest}"
mkdir -p "${dest}/lib" "${dest}/nodes"
cp -a "${PKG_SRC}/package.json" "${dest}/"
cp -a "${PKG_SRC}/lib/." "${dest}/lib/"
cp -a "${PKG_SRC}/nodes/." "${dest}/nodes/"
if [ -d "${PKG_SRC}/locales" ] && [ -n "$(ls -A "${PKG_SRC}/locales" 2>/dev/null)" ]; then
mkdir -p "${dest}/locales"
cp -a "${PKG_SRC}/locales/." "${dest}/locales/"
fi
assert_package_layout "${dest}"
log "Paketlayout OK: ${dest}/{package.json,lib/,nodes/}"
}
patch_dockerfile() {
local df="${NODERED_DIR}/Dockerfile"
if grep -q "${MARKER}" "${df}"; then
log "Dockerfile redan patchad"
return 0
fi
log "Patchar Dockerfile"
cat >> "${df}" <<'EOF'
# nextcloud-collabora-nodered — Collabora/Office-noder (WebDAV + CODE REST)
RUN mkdir -p /opt/nextcloud-nodes/node-red-contrib-nextcloud-collabora
COPY nodes/nextcloud-collabora/package.json /opt/nextcloud-nodes/node-red-contrib-nextcloud-collabora/
COPY nodes/nextcloud-collabora/lib /opt/nextcloud-nodes/node-red-contrib-nextcloud-collabora/lib
COPY nodes/nextcloud-collabora/nodes /opt/nextcloud-nodes/node-red-contrib-nextcloud-collabora/nodes
RUN cd /opt/nextcloud-nodes/node-red-contrib-nextcloud-collabora && npm install --omit=dev
EOF
}
patch_entrypoint() {
local ep="${NODERED_DIR}/entrypoint.sh"
# Rensa trasig tidigare patch (rå cp-rader utan install_nodes)
if grep -q 'cp -a /opt/nextcloud-nodes/node-red-contrib-nextcloud-collabora' "${ep}"; then
log "Rensar trasig collabora-patch i entrypoint.sh"
sed -i '/cp -a \/opt\/nextcloud-nodes\/node-red-contrib-nextcloud-collabora/d' "${ep}"
fi
if grep -q 'install_nodes.*Nextcloud Collabora' "${ep}"; then
log "entrypoint.sh redan patchad (Collabora install_nodes)"
return 0
fi
log "Patchar entrypoint.sh (install_nodes för Collabora)"
if grep -q 'install_nodes.*Nextcloud OCS' "${ep}"; then
sed -i '/install_nodes.*Nextcloud OCS/a\
install_nodes "/opt/nextcloud-nodes/node-red-contrib-nextcloud-collabora" "/data/node_modules/node-red-contrib-nextcloud-collabora" "Nextcloud Collabora"' "${ep}"
elif grep -q 'install_nodes.*Odds Scraper' "${ep}"; then
sed -i '/install_nodes.*Odds Scraper/a\
install_nodes "/opt/nextcloud-nodes/node-red-contrib-nextcloud-collabora" "/data/node_modules/node-red-contrib-nextcloud-collabora" "Nextcloud Collabora"' "${ep}"
else
cat >> "${ep}" <<'EOF'
# nextcloud-collabora-nodered
install_nodes "/opt/nextcloud-nodes/node-red-contrib-nextcloud-collabora" "/data/node_modules/node-red-contrib-nextcloud-collabora" "Nextcloud Collabora"
EOF
fi
}
connect_code_network() {
log "Ansluter ${NODERED_CONTAINER} till Docker bridge (CODE)"
if docker network inspect bridge --format '{{range .Containers}}{{.Name}} {{end}}' 2>/dev/null | grep -q "${NODERED_CONTAINER}"; then
log "Redan ansluten till bridge"
else
docker network connect bridge "${NODERED_CONTAINER}" 2>/dev/null || warn "Kunde inte ansluta bridge — kontrollera manuellt"
fi
}
rebuild_nodered() {
log "Bygger om ${NODERED_CONTAINER} (samma instans, port ${NODERED_PORT})..."
fix_nodered_build_permissions
verify_build_context
if [ "${FORCE_REBUILD:-0}" = "1" ]; then
compose_run build --no-cache
else
compose_run build
fi
compose_run up -d
}
wait_nodered() {
log "Väntar på Node-RED :${NODERED_PORT}..."
for i in $(seq 1 30); do
if curl -sf "http://127.0.0.1:${NODERED_PORT}" >/dev/null 2>&1; then
log "Node-RED svarar på http://127.0.0.1:${NODERED_PORT}"
return 0
fi
sleep 2
done
warn "Node-RED svarade inte inom 60 s"
}
verify_code_from_nodered() {
local code_ip
code_ip="$(code_bridge_ip)"
[ -n "${code_ip}" ] || code_ip="${COLLABORA_IP}"
# CODE lyssnar på HTTPS internt (HTTP ger tom svar)
local url="https://${code_ip}:9980/hosting/capabilities"
log "Testar CODE från ${NODERED_CONTAINER}: ${url}"
if docker exec "${NODERED_CONTAINER}" curl -sfk "${url}" >/dev/null 2>&1; then
log "OK: CODE nås från befintlig Node-RED-container"
else
warn "CODE nås inte — kör: docker network connect bridge ${NODERED_CONTAINER}"
warn "nc-collab-config collaboraUrl=https://${code_ip}:9980 (verifyTls=false)"
fi
}
main() {
resolve_paths
log "Integrerar Collabora-noder i befintlig nextcloud-node-red"
log "Paket: ${PKG_SRC}"
log "Node-RED: ${NODERED_DIR} (container ${NODERED_CONTAINER})"
stop_sidecar_if_any
preflight
copy_node_package
patch_dockerfile
patch_entrypoint
rebuild_nodered
connect_code_network
wait_nodered
verify_code_from_nodered
local host_ip code_ip nc_url
host_ip="$(hostname -I 2>/dev/null | awk '{print $1}' || echo "127.0.0.1")"
code_ip="$(code_bridge_ip)"
[ -n "${code_ip}" ] || code_ip="${COLLABORA_IP}"
nc_url="${NEXTCLOUD_URL:-https://cloud.hannesahlin.se}"
cat <<EOF
Integrering klar — samma Node-RED på port ${NODERED_PORT}
UI: http://${host_ip}:${NODERED_PORT}
Noder: palett "nextcloud collabora" (6 st + config)
Skapa config först (kugghjul → nc-collab-config):
Nextcloud: ${nc_url}
Collabora: https://${code_ip}:9980 (verifyTls=false)
Publik WOPI: ${nc_url}
Auth: användarnamn + app-lösenord
Testa: collabora-wopi (capabilities) → collabora-bridge (ncFileToPdf)
Exempelflöde: flows/example-flows.json
EOF
}
main "$@"