#!/usr/bin/env bash # shellcheck disable=SC1091,SC2034 # SC1091: non-constant source, SC2034: unused variables source <(curl -fsSL https://git.bila.li/Proxmox/proxmox-ve-install-scripts/raw/branch/main/misc/build.func) # Updated to main branch # Copyright (c) 2021-2025 community-scripts ORG # Author: GitHub Copilot # License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE # Source: https://github.com/hcengineering/huly-selfhost # App Default Values APP="Huly" var_tags="app;productivity;collaboration" # Used by build.func var_cpu="2" # Minimum recommended, Huly can be resource intensive. Used by build.func var_ram="4096" # Minimum recommended, especially with Rekoni. Used by build.func var_disk="20" # Base disk, consider more for MinIO data and MongoDB. Used by build.func var_os="debian" # Used by build.func var_version="12" # Debian Bookworm. Used by build.func var_unprivileged="1" # Run as unprivileged container. Used by build.func header_info "$APP" variables # This function is from build.func, sets up CTID, IP, etc. color catch_errors # Paths and Configs HULY_CONFIG_DIR="/opt/huly-selfhost" HULY_INSTALL_DIR="/opt/huly" HULY_CONFIG_FILE="$HULY_CONFIG_DIR/native.conf" HULY_CREDS_FILE="/root/huly.creds" # Storing in /root for CT context HULY_VERSION_FILE="$HULY_CONFIG_DIR/version.txt" MINIO_DATA_DIR="/opt/minio/data" _MONGODB_DATA_DIR="/var/lib/mongodb" # Default MongoDB data directory, prefixed to avoid SC2034 if not directly used in this script # Services to manage HULY_SERVICES=("huly-front" "huly-account" "huly-transactor" "huly-collaborator" "huly-rekoni") DEPENDENCY_SERVICES=("minio" "mongod" "nginx") ALL_SERVICES=("${HULY_SERVICES[@]}" "${DEPENDENCY_SERVICES[@]}") # Function to stop all Huly and dependency services stop_all_services() { msg_info "Stopping all Huly related services..." for service in "${ALL_SERVICES[@]}"; do systemctl stop "$service" 2>/dev/null || true # Suppress error if service not found/running done msg_ok "All Huly related services stopped." } # Function to start all Huly and dependency services in order start_all_services() { msg_info "Starting all Huly related services..." systemctl start mongod minio nginx # Start core dependencies sleep 3 # Give them a moment systemctl start huly-account huly-collaborator # Account & Collaborator need Mongo sleep 2 systemctl start huly-transactor huly-rekoni # These need Account, Mongo, Minio sleep 2 systemctl start huly-front # Front needs all backends msg_ok "All Huly related services started." } function update_script() { header_info # These checks are typically for the CT environment, called by build.func # Source build.func to make them available if needed directly, though usually not. # . <(curl -fsSL https://git.bila.li/Proxmox/proxmox-ve-install-scripts/raw/branch/main/misc/build.func) # check_container_storage # check_container_resources if [[ ! -d "$HULY_INSTALL_DIR" ]] || [[ ! -f "$HULY_CONFIG_FILE" ]]; then msg_error "No ${APP} Installation Found (missing $HULY_INSTALL_DIR or $HULY_CONFIG_FILE)!" exit 1 fi msg_info "Updating $APP" CURRENT_DATE=$(date '+%Y-%m-%d %H:%M:%S') stop_all_services msg_info "Installing Docker temporarily for component updates" $STD apt-get update # Using docker.io for simplicity in CT update script, as it's often readily available # Ensure this doesn't conflict with docker-ce if that was used for install and not fully purged $STD apt-get install -y docker.io curl $STD systemctl start docker msg_ok "Docker installed temporarily." # Function to extract from container (similar to install script) extract_component_update() { local component_name="$1" local image_name="hardcoreeng/$component_name:latest" local target_dir="$HULY_INSTALL_DIR/$component_name" local container_name="huly-${component_name}-update-extract" msg_info "Pulling latest Docker image for $component_name: $image_name" if ! $STD docker pull "$image_name"; then msg_error "Failed to pull Docker image $image_name for $component_name." return 1 # Continue with other components if one fails? fi msg_info "Extracting $component_name from $image_name to $target_dir" if ! $STD docker create --name "$container_name" "$image_name"; then msg_error "Failed to create Docker container for $component_name." return 1 fi # Clear old component files before extracting new ones rm -rf "${target_dir:?}/"* # Safety: :? ensures var is set mkdir -p "$target_dir" local extracted=false for path_in_container in "/app/dist" "/app" "/usr/src/app/dist" "/usr/src/app" "/dist" "/opt/app" "/home/app"; do if $STD docker exec "$container_name" ls "${path_in_container}/." >/dev/null 2>&1; then if $STD docker cp "${container_name}:${path_in_container}/." "$target_dir/"; then extracted=true msg_ok "Extracted updated $component_name from ${path_in_container}" break fi fi done if ! $extracted; then msg_warn "Could not find standard app directory in $component_name container. Copying entire root." if ! $STD docker cp "${container_name}:/." "$target_dir/"; then msg_error "Failed to copy any files for updated $component_name." $STD docker rm "$container_name" return 1 fi fi $STD docker rm "$container_name" msg_ok "Extraction update completed for $component_name" } # Update each Huly component for component in "${HULY_SERVICES[@]//huly-/}"; do # Removes "huly-" prefix for image name extract_component_update "$component" done msg_info "Removing Docker" $STD systemctl stop docker $STD apt-get remove -y docker.io docker-ce docker-ce-cli containerd.io --allow-remove-essential $STD apt-get purge -y docker.io docker-ce docker-ce-cli containerd.io --allow-remove-essential $STD apt-get autoremove -y $STD rm -rf /var/lib/docker /var/lib/containerd msg_ok "Docker removed." # Update version information echo "$CURRENT_DATE - Updated Huly Components" >>"$HULY_VERSION_FILE" start_all_services msg_ok "Updated $APP successfully. All services restarted." } function backup_script() { header_info # check_container_storage (if relevant for backup space) if [[ ! -d "$HULY_CONFIG_DIR" ]]; then msg_error "No ${APP} Installation Found (missing $HULY_CONFIG_DIR)!" exit 1 fi BACKUP_DATE=$(date +%Y-%m-%d_%H%M%S) BACKUP_FILENAME_BASE="huly-native-backup-$BACKUP_DATE" BACKUP_TEMP_DIR="/tmp/$BACKUP_FILENAME_BASE" ARCHIVE_DESTINATION="/root" # Standard backup location in these scripts FINAL_ARCHIVE_PATH="$ARCHIVE_DESTINATION/$BACKUP_FILENAME_BASE.tar.gz" mkdir -p "$BACKUP_TEMP_DIR/config" mkdir -p "$BACKUP_TEMP_DIR/mongodb" mkdir -p "$BACKUP_TEMP_DIR/minio_data" msg_info "Backing up Huly configuration..." cp "$HULY_CONFIG_FILE" "$BACKUP_TEMP_DIR/config/" if [ -f "$HULY_CREDS_FILE" ]; then cp "$HULY_CREDS_FILE" "$BACKUP_TEMP_DIR/config/" else msg_warn "Credentials file $HULY_CREDS_FILE not found. Skipping." fi if [ -f "$HULY_VERSION_FILE" ]; then cp "$HULY_VERSION_FILE" "$BACKUP_TEMP_DIR/config/" fi # Backup entire /opt/huly-selfhost for any other files? # cp -r "$HULY_CONFIG_DIR" "$BACKUP_TEMP_DIR/config_full_dir" msg_ok "Huly configuration backed up." msg_info "Backing up MongoDB database (huly)..." # Ensure mongodump is available if command -v mongodump >/dev/null 2>&1; then if mongodump --db=huly --archive="$BACKUP_TEMP_DIR/mongodb/huly.gz" --gzip; then msg_ok "MongoDB backup successful." else msg_warn "MongoDB backup (mongodump) failed. Archive might be incomplete." fi else msg_warn "mongodump command not found. Skipping MongoDB backup." fi msg_info "Backing up MinIO data..." if [ -d "$MINIO_DATA_DIR" ]; then # Ensure MinIO service is stopped or data is consistent before copying # For simplicity, assuming data is quiesced if services are stopped during a full restore scenario # For live backup, MinIO has mc mirror/backup tools, but that adds complexity here. # Tarring MinIO data to preserve permissions and structure within the backup. tar -czf "$BACKUP_TEMP_DIR/minio_data/minio_data.tar.gz" -C "$(dirname "$MINIO_DATA_DIR")" "$(basename "$MINIO_DATA_DIR")" msg_ok "MinIO data backed up." else msg_warn "MinIO data directory $MINIO_DATA_DIR not found. Skipping MinIO data backup." fi msg_info "Creating final backup archive: $FINAL_ARCHIVE_PATH" if tar -czf "$FINAL_ARCHIVE_PATH" -C "/tmp" "$BACKUP_FILENAME_BASE"; then msg_ok "Backup archive created successfully." else msg_error "Failed to create final backup archive." rm -rf "$BACKUP_TEMP_DIR" exit 1 fi msg_info "Cleaning up temporary backup files..." rm -rf "$BACKUP_TEMP_DIR" msg_ok "Cleanup complete." echo -e "${INFO} Backup created at: ${GN}$FINAL_ARCHIVE_PATH${CL}" } function restore_script() { header_info # check_container_storage (ensure enough space for restore) LATEST_BACKUP=$(ls -t $ARCHIVE_DESTINATION/huly-native-backup-*.tar.gz 2>/dev/null | head -n1) if [[ -z "$LATEST_BACKUP" ]]; then msg_error "No Huly native backup archive found in $ARCHIVE_DESTINATION/!" exit 1 fi msg_info "Found latest backup: $LATEST_BACKUP" RESTORE_TEMP_DIR="/tmp/huly-restore-extract-$(date +%s)" mkdir -p "$RESTORE_TEMP_DIR" msg_info "Extracting backup archive to $RESTORE_TEMP_DIR..." if ! tar -xzf "$LATEST_BACKUP" -C "$RESTORE_TEMP_DIR"; then msg_error "Failed to extract backup archive." rm -rf "$RESTORE_TEMP_DIR" exit 1 fi # The archive was created with -C /tmp backup_base_name. So files are in $RESTORE_TEMP_DIR/backup_base_name/ EXTRACTED_CONTENT_DIR_NAME=$(ls "$RESTORE_TEMP_DIR") ACTUAL_RESTORE_DATA_DIR="$RESTORE_TEMP_DIR/$EXTRACTED_CONTENT_DIR_NAME" if [ ! -d "$ACTUAL_RESTORE_DATA_DIR/config" ]; then msg_error "Extracted archive does not contain expected 'config' directory. Restore aborted." rm -rf "$RESTORE_TEMP_DIR" exit 1 fi stop_all_services msg_info "Restoring Huly configuration..." # Restore native.conf, huly.creds, version.txt cp -f "$ACTUAL_RESTORE_DATA_DIR/config/native.conf" "$HULY_CONFIG_FILE" 2>/dev/null || msg_warn "native.conf not found in backup." cp -f "$ACTUAL_RESTORE_DATA_DIR/config/huly.creds" "$HULY_CREDS_FILE" 2>/dev/null || msg_warn "huly.creds not found in backup." cp -f "$ACTUAL_RESTORE_DATA_DIR/config/version.txt" "$HULY_VERSION_FILE" 2>/dev/null || msg_warn "version.txt not found in backup." # chown/chmod if necessary, e.g., chmod 600 $HULY_CREDS_FILE msg_ok "Huly configuration restored." msg_info "Restoring MongoDB database (huly)..." if [ -f "$ACTUAL_RESTORE_DATA_DIR/mongodb/huly.gz" ]; then if command -v mongorestore >/dev/null 2>&1; then # Stop mongod if it was auto-restarted by systemd after the global stop systemctl stop mongod 2>/dev/null # It is critical that mongod is running for mongorestore to connect. # However, we need to ensure it's clean. A --drop is often used. # For simplicity, start it, restore, then it will be part of start_all_services. systemctl start mongod sleep 2 # Give MongoDB a moment to start if mongorestore --db=huly --archive="$ACTUAL_RESTORE_DATA_DIR/mongodb/huly.gz" --gzip --drop; then msg_ok "MongoDB restore successful." else msg_warn "MongoDB restore (mongorestore) failed. Check MongoDB logs." fi else msg_warn "mongorestore command not found. Skipping MongoDB restore." fi else msg_warn "MongoDB backup file (huly.gz) not found in archive. Skipping MongoDB restore." fi msg_info "Restoring MinIO data..." if [ -f "$ACTUAL_RESTORE_DATA_DIR/minio_data/minio_data.tar.gz" ]; then # Stop minio if it was auto-restarted systemctl stop minio 2>/dev/null rm -rf "${MINIO_DATA_DIR:?}/"* # Clear existing MinIO data mkdir -p "$MINIO_DATA_DIR" if tar -xzf "$ACTUAL_RESTORE_DATA_DIR/minio_data/minio_data.tar.gz" -C "$(dirname "$MINIO_DATA_DIR")"; then # Ensure correct ownership for MinIO data dir (user minio) chown -R minio:minio "$MINIO_DATA_DIR" msg_ok "MinIO data restored." else msg_warn "Failed to extract MinIO data from archive." fi else msg_warn "MinIO data archive (minio_data.tar.gz) not found in backup. Skipping MinIO data restore." fi # Note: Application files in /opt/huly/* are NOT part of this backup/restore. # The update script is responsible for fetching/extracting these. # This backup focuses on user data and configurations. msg_info "Cleaning up temporary restore files..." rm -rf "$RESTORE_TEMP_DIR" msg_ok "Cleanup complete." start_all_services msg_ok "Restore completed from $LATEST_BACKUP. All services restarted." } # Standard Proxmox VE script functions (start, build_container, description) start # This function is from build.func build_container # This function is from build.func description # This function is from build.func # Final messages to user after CT creation msg_ok "Completed Successfully!\n" echo -e "${CREATING}${GN}${APP} LXC container has been successfully created!${CL}" echo -e "${INFO}${YW} Access Huly via Nginx reverse proxy at:${CL}" echo -e "${TAB}${GATEWAY}${BGN}http://${IP}${CL} (or your configured domain name)" if [ -f "$HULY_CREDS_FILE" ]; then echo -e "${INFO}${YW} Initial admin credentials and keys are in: ${HULY_CREDS_FILE} (inside the CT). Secure them!${CL}" fi echo -e "${INFO}${YW} MinIO Console (if needed directly): http://${IP}:9001${CL}" echo -e "${INFO}${YW} Refer to Huly documentation for first-time setup and usage: https://github.com/hcengineering/huly-selfhost${CL}"