diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
index 0000000..2d563e0
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,65 @@
+# Changelog
+
+All notable changes to this project will be documented in this file.
+
+---
+
+## [0.4.0] - 2026-03-18
+
+### Added
+
+* Structured logging system with timestamps
+* Log levels: INFO, OK, ERR
+* Central log file: `/opt/update-manager/log/update-manager.log`
+* Log submenu in UI
+* Log viewing options:
+
+ * Full log
+ * Live log (tail -f)
+ * Last 20 lines
+ * Log file location
+
+### Changed
+
+* Refactored UI to group log features into submenu
+* Improved menu structure and usability
+
+### Improved
+
+* Better error visibility for SSH failures
+* Consistent output across hosts
+* README updated with UI screenshots and logging documentation
+
+---
+
+## [0.3.0] - 2026-03-18
+
+### Added
+
+* CLI menu (`update-manager-ui.sh`)
+* Interactive host management
+* Dialog-based UI navigation
+
+### Changed
+
+* Project renamed from `lanx-update` to `update-manager`
+* Moved runtime files to `/opt/update-manager`
+
+---
+
+## [0.2.0] - 2026-03-18
+
+### Added
+
+* SSH-based update checking across multiple hosts
+* Support for `hosts.conf`
+* Config file support (`/etc/update-manager.conf`)
+
+---
+
+## [0.1.0] - 2026-03-17
+
+### Added
+
+* Initial version
+* Basic apt update check functionality
diff --git a/README.md b/README.md
index 36fcbfb..dc3900b 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,7 @@
-# 🖥️ Update Manager
+
+
+
+
Simple CLI tool to check and manage updates across multiple Ubuntu systems over SSH.
@@ -26,10 +29,10 @@ Built for Lanx environments – lightweight, fast and no unnecessary dependencie
## Features
* Check updates on multiple hosts
-* Run checks remotely over SSH
+* Run updates remotely over SSH
+* Interactive CLI menu (dialog-based UI)
+* Centralized logging
* Simple config files
-* Built-in logging (file + terminal)
-* Dialog-based UI menu
* No agents required
* Works with existing SSH setup
@@ -37,12 +40,20 @@ Built for Lanx environments – lightweight, fast and no unnecessary dependencie
## Update Manager UI
+### Main menu
+
-
+
+
+
+### Log menu
+
+
+
- Lightweight • Terminal-based • Works over SSH
+ Lightweight • No dependencies • Works over SSH
---
@@ -57,7 +68,7 @@ sudo apt update
sudo apt install dialog openssh-client
sudo mkdir -p /opt/update-manager
-sudo cp update-manager.sh update-manager-ui.sh /opt/update-manager/
+sudo cp update-manager.sh update-manager-ui.sh dialogrc /opt/update-manager/
sudo chmod +x /opt/update-manager/update-manager.sh
sudo chmod +x /opt/update-manager/update-manager-ui.sh
@@ -134,6 +145,12 @@ server3 192.168.1.30 user
## Usage
+### Check updates
+
+```bash
+update-manager check
+```
+
### Start UI
```bash
@@ -142,42 +159,25 @@ update-manager-ui
---
-### Check updates (CLI)
-
-```bash
-update-manager check
-```
-
----
-
## Logging
-The tool logs both to terminal and file.
-
-### Primary location
+Log file location:
```bash
/opt/update-manager/log/update-manager.log
```
-### Fallback location
+View log:
```bash
-~/update-manager/log/update-manager.log
+less /opt/update-manager/log/update-manager.log
```
-### Notes
+Follow log:
-* Log directory is created automatically
-* Log file is created automatically
-* Output is written to both terminal and file
-* Log levels:
-
- * INFO
- * WARN
- * ERROR
-
-Logs can be viewed directly from the UI.
+```bash
+tail -f /opt/update-manager/log/update-manager.log
+```
---
@@ -189,8 +189,8 @@ Logs can be viewed directly from the UI.
├── update-manager-ui.sh
├── update-manager.conf
├── hosts.conf
-└── log/
- └── update-manager.log
+├── log/
+│ └── update-manager.log
```
---
@@ -199,6 +199,7 @@ Logs can be viewed directly from the UI.
* Uses SSH to connect to each host
* Runs `apt` commands remotely
+* Logs results locally
* No agents or services needed
* Designed for simple and efficient operations
@@ -209,7 +210,6 @@ Logs can be viewed directly from the UI.
* SSH access to all hosts
* SSH keys recommended (no password prompts)
* Ubuntu/Debian-based systems
-* `dialog` (for UI)
---
@@ -218,7 +218,6 @@ Logs can be viewed directly from the UI.
* 🔔 Notifications (ntfy / push alerts)
* 🌐 Web interface
* 📧 Email reporting
-* 📜 Advanced logging / audit trail
* 📊 Basic monitoring (status, last check, pending updates)
* 🧩 Plugin system (extensible modules)
* 🔐 Security & compliance checks
@@ -226,6 +225,15 @@ Logs can be viewed directly from the UI.
---
+## ❤️ Credits
+
+Built with ❤️ for Lanx by [NodeFox 🦊](https://nodefox.lanx.dk)
+Maintained by [Eddie Nielsen](https://lanx.dk)
+
+> Learn. Adopt. Survive. Share.
+
+---
+
## License
This project is licensed under the GNU GPL v3 License.
@@ -233,10 +241,3 @@ This project is licensed under the GNU GPL v3 License.
See the LICENSE file for full details.
---
-
-## Author
-
-Built with ❤️ for [Lanx](https://lanx.dk) by **NodeFox** 🦊
-
-Maintained by Eddie Nielsen
-Feel free to contribute, suggest improvements or fork the project.
diff --git a/dialogrc b/dialogrc
new file mode 100644
index 0000000..250e64a
--- /dev/null
+++ b/dialogrc
@@ -0,0 +1,32 @@
+use_shadow = OFF
+use_colors = ON
+
+screen_color = (WHITE,BLUE,OFF)
+dialog_color = (WHITE,BLUE,OFF)
+title_color = (YELLOW,BLUE,ON)
+border_color = (WHITE,BLUE,OFF)
+
+button_active_color = (BLACK,WHITE,ON)
+button_inactive_color = (WHITE,BLUE,OFF)
+
+button_key_active_color = (BLACK,WHITE,ON)
+button_key_inactive_color = (WHITE,BLUE,OFF)
+
+button_label_active_color = (BLACK,WHITE,ON)
+button_label_inactive_color = (WHITE,BLUE,OFF)
+
+menubox_color = (WHITE,BLUE,OFF)
+menubox_border_color = (WHITE,BLUE,OFF)
+
+item_color = (WHITE,BLUE,OFF)
+item_selected_color = (BLACK,WHITE,ON)
+
+tag_color = (WHITE,BLUE,OFF)
+tag_selected_color = (BLACK,WHITE,ON)
+
+tag_key_color = (YELLOW,BLUE,OFF)
+tag_key_selected_color = (BLACK,WHITE,ON)
+
+position_indicator_color = (WHITE,BLUE,OFF)
+uarrow_color = (WHITE,BLUE,OFF)
+darrow_color = (WHITE,BLUE,OFF)
diff --git a/docs/architecture.md b/docs/architecture.md
new file mode 100644
index 0000000..3c9771c
--- /dev/null
+++ b/docs/architecture.md
@@ -0,0 +1,211 @@
+
+
+
+
+# 🧠 Update Manager Architecture
+
+This document describes how Update Manager is structured internally and how data flows through the system.
+
+---
+
+## Overview
+
+Update Manager is a lightweight, SSH-based system for managing updates across multiple Linux hosts.
+
+It consists of two main layers:
+
+* **UI layer** – interactive menu (`update-manager-ui.sh`)
+* **Core engine** – update logic (`update-manager.sh`)
+
+The system is designed to be simple, transparent, and dependency-light.
+
+---
+
+## High-Level Flow
+
+```text
+User
+ ↓
+UI (update-manager-ui.sh)
+ ↓
+Core (update-manager.sh)
+ ↓
+SSH → Remote Hosts
+ ↓
+Results
+ ↓
+Logging (/opt/update-manager/log)
+ ↓
+UI / User
+```
+
+---
+
+## Components
+
+### UI Layer (`update-manager-ui.sh`)
+
+Responsible for:
+
+* Displaying menu (via `dialog`)
+* Handling user input
+* Managing hosts (add/remove/edit)
+* Providing access to logs
+* Delegating actions to core engine
+
+The UI does **not perform updates directly**.
+
+---
+
+### Core Engine (`update-manager.sh`)
+
+Responsible for:
+
+* Reading configuration
+* Parsing hosts file
+* Executing SSH commands
+* Checking for updates (`apt list --upgradable`)
+* Handling connection errors
+* Writing structured logs
+
+This is the **execution layer** of the system.
+
+---
+
+### Hosts Configuration
+
+File:
+
+```bash
+/opt/update-manager/hosts.conf
+```
+
+Format:
+
+```text
+name ip user
+```
+
+Example:
+
+```text
+server1 192.168.1.10 user
+server2 192.168.1.20 user
+server3 192.168.1.30 user
+```
+
+Each line represents a target system accessed via SSH.
+
+---
+
+### Logging System
+
+Log file:
+
+```bash
+/opt/update-manager/log/update-manager.log
+```
+
+Log format:
+
+```text
+YYYY-MM-DD HH:MM:SS [LEVEL] host - message
+```
+
+Example:
+
+```text
+2026-03-18 20:45:01 [INFO] lanx-www - Checking updates
+2026-03-18 20:45:03 [OK] lanx-www - 3 updates available
+2026-03-18 20:45:05 [ERR] lanx-db - Connection failed
+```
+
+#### Log Levels
+
+* `INFO` – informational messages
+* `OK` – successful operations
+* `ERR` – errors or failures
+
+Logging is centralized and written locally.
+
+---
+
+## Execution Flow
+
+For each host:
+
+1. Read host entry from `hosts.conf`
+2. Establish SSH connection
+3. Execute:
+
+```bash
+apt list --upgradable
+```
+
+4. Parse output
+5. Determine:
+
+ * Up-to-date
+ * Updates available
+ * Connection failure
+6. Write result to:
+
+ * terminal output
+ * log file
+
+---
+
+## Configuration
+
+Primary config:
+
+```bash
+/etc/update-manager.conf
+```
+
+Fallback:
+
+```bash
+./update-manager.conf
+```
+
+Supported options:
+
+* `HOSTS_FILE`
+* `SSH_OPTIONS`
+
+---
+
+## Design Principles
+
+* **No agents** – uses standard SSH only
+* **Simple over complex** – minimal dependencies
+* **Transparent behavior** – everything is visible/logged
+* **CLI-first** – designed for terminal environments
+* **Modular growth** – prepared for future extensions
+
+---
+
+## Future Architecture Direction
+
+Planned extensions:
+
+* Plugin system (modular features)
+* Web interface (optional UI layer)
+* Notification system (alerts)
+* Metrics / monitoring
+* Integration with Lanx AI
+
+---
+
+## Summary
+
+Update Manager follows a clean separation:
+
+* UI = interaction
+* Core = execution
+* Config = data
+* Log = output
+
+This keeps the system predictable, debuggable, and easy to extend.
+
diff --git a/docs/images/architecture-diagram.png b/docs/images/architecture-diagram.png
new file mode 100644
index 0000000..d2b79f2
Binary files /dev/null and b/docs/images/architecture-diagram.png differ
diff --git a/docs/images/menu-logs.png b/docs/images/menu-logs.png
new file mode 100644
index 0000000..1bffefe
Binary files /dev/null and b/docs/images/menu-logs.png differ
diff --git a/docs/images/menu-main.png b/docs/images/menu-main.png
new file mode 100644
index 0000000..80e1e80
Binary files /dev/null and b/docs/images/menu-main.png differ
diff --git a/docs/images/update-manager-ui.png b/docs/images/update-manager-ui.png
new file mode 100644
index 0000000..a9dd924
Binary files /dev/null and b/docs/images/update-manager-ui.png differ
diff --git a/images/updatemanager-logo.png b/images/updatemanager-logo.png
new file mode 100644
index 0000000..bd5a27a
Binary files /dev/null and b/images/updatemanager-logo.png differ
diff --git a/log/update-manager.sh b/log/update-manager.sh
new file mode 100644
index 0000000..d3e9635
--- /dev/null
+++ b/log/update-manager.sh
@@ -0,0 +1,117 @@
+#!/usr/bin/env bash
+
+UPDATE_MANAGER_SCRIPT="/opt/update-manager/update-manager.sh"
+
+get_log_file() {
+ local primary="/opt/update-manager/log/update-manager.log"
+ local fallback="$HOME/update-manager/log/update-manager.log"
+
+ if [[ -f "$primary" ]]; then
+ echo "$primary"
+ else
+ echo "$fallback"
+ fi
+}
+
+run_check() {
+ echo
+ bash "$UPDATE_MANAGER_SCRIPT" check
+ echo
+ read -rp "Press Enter to continue..."
+}
+
+view_log() {
+ local log_file
+ log_file="$(get_log_file)"
+
+ echo
+ if [[ -f "$log_file" ]]; then
+ less "$log_file"
+ else
+ echo "Log file not found: $log_file"
+ echo
+ read -rp "Press Enter to continue..."
+ fi
+}
+
+follow_log() {
+ local log_file
+ log_file="$(get_log_file)"
+
+ echo
+ if [[ -f "$log_file" ]]; then
+ echo "Following log: $log_file"
+ echo "Press Ctrl+C to stop."
+ echo
+ tail -f "$log_file"
+ else
+ echo "Log file not found: $log_file"
+ fi
+
+ echo
+ read -rp "Press Enter to continue..."
+}
+
+show_log_location() {
+ local log_file
+ log_file="$(get_log_file)"
+
+ echo
+ echo "Log file location:"
+ echo "$log_file"
+ echo
+ read -rp "Press Enter to continue..."
+}
+
+show_menu() {
+ clear
+ echo "=================================="
+ echo " Update Manager UI"
+ echo "=================================="
+ echo
+ echo "1) Run update check"
+ echo "2) View full log"
+ echo "3) Follow log live"
+ echo "4) Show log location"
+ echo "0) Exit"
+ echo
+}
+
+main() {
+ if [[ ! -f "$UPDATE_MANAGER_SCRIPT" ]]; then
+ echo "Update manager script not found: $UPDATE_MANAGER_SCRIPT"
+ exit 1
+ fi
+
+ while true; do
+ show_menu
+ read -rp "Choose an option: " choice
+
+ case "$choice" in
+ 1)
+ run_check
+ ;;
+ 2)
+ view_log
+ ;;
+ 3)
+ follow_log
+ ;;
+ 4)
+ show_log_location
+ ;;
+ 0)
+ echo
+ echo "Bye."
+ exit 0
+ ;;
+ *)
+ echo
+ echo "Invalid choice."
+ read -rp "Press Enter to continue..."
+ ;;
+ esac
+ done
+}
+
+main
diff --git a/update-manager-ui.png b/update-manager-ui.png
deleted file mode 100644
index 33929c8..0000000
Binary files a/update-manager-ui.png and /dev/null differ
diff --git a/update-manager-ui.sh b/update-manager-ui.sh
new file mode 100755
index 0000000..af17391
--- /dev/null
+++ b/update-manager-ui.sh
@@ -0,0 +1,140 @@
+#!/usr/bin/env bash
+
+export DIALOGRC="/opt/update-manager/dialogrc"
+
+UPDATE_MANAGER_SCRIPT="$HOME/update-manager/update-manager.sh"
+HOSTS_FILE="/opt/update-manager/hosts.conf"
+
+get_log_file() {
+ local primary="/opt/update-manager/log/update-manager.log"
+ local fallback="$HOME/update-manager/log/update-manager.log"
+
+ if [[ -f "$primary" ]]; then
+ echo "$primary"
+ elif [[ -f "$fallback" ]]; then
+ echo "$fallback"
+ else
+ echo ""
+ fi
+}
+
+show_log_menu() {
+ while true; do
+ log_choice=$(dialog --clear \
+ --backtitle "Update Manager" \
+ --title "Log menu" \
+ --menu "Select log action:" 18 60 10 \
+ 1 "View full log" \
+ 2 "Follow log live" \
+ 3 "Show log location" \
+ 4 "Show last 20 log lines" \
+ 0 "Back" \
+ 3>&1 1>&2 2>&3)
+
+ clear
+
+ case "$log_choice" in
+ 1)
+ log_file="$(get_log_file)"
+ if [[ -n "$log_file" ]]; then
+ less "$log_file"
+ else
+ echo "No log file found yet."
+ read -rp "Press Enter to continue..."
+ fi
+ ;;
+ 2)
+ log_file="$(get_log_file)"
+ if [[ -n "$log_file" ]]; then
+ echo "Press Ctrl+C to stop"
+ tail -n 20 -f "$log_file"
+ else
+ echo "No log file found yet."
+ fi
+ read -rp "Press Enter to continue..."
+ ;;
+ 3)
+ log_file="$(get_log_file)"
+ if [[ -n "$log_file" ]]; then
+ echo "Log file:"
+ echo "$log_file"
+ else
+ echo "No log file found yet."
+ fi
+ read -rp "Press Enter to continue..."
+ ;;
+ 4)
+ log_file="$(get_log_file)"
+ if [[ -n "$log_file" ]]; then
+ tail -n 20 "$log_file"
+ else
+ echo "No log file found yet."
+ fi
+ read -rp "Press Enter to continue..."
+ ;;
+ 0|"")
+ break
+ ;;
+ esac
+ done
+}
+
+while true; do
+ choice=$(dialog --clear \
+ --backtitle "Update Manager" \
+ --title "Choose an action" \
+ --menu "Select option:" 20 60 10 \
+ 1 "Check all hosts" \
+ 2 "View hosts file" \
+ 3 "Edit hosts file" \
+ 4 "Add host" \
+ 5 "Remove host" \
+ 6 "Log menu" \
+ 0 "Exit" \
+ 3>&1 1>&2 2>&3)
+
+ clear
+
+ case "$choice" in
+ 1)
+ bash "$UPDATE_MANAGER_SCRIPT" check
+ read -rp "Press Enter to continue..."
+ ;;
+ 2)
+ less "$HOSTS_FILE"
+ ;;
+ 3)
+ nano "$HOSTS_FILE"
+ ;;
+ 4)
+ read -rp "Name: " name
+ read -rp "IP: " ip
+ read -rp "User: " user
+ if [[ -z "$name" || -z "$ip" || -z "$user" ]]; then
+ echo "All fields are required."
+ else
+ echo "$name $ip $user" >> "$HOSTS_FILE"
+ echo "Host added."
+ fi
+ read -rp "Press Enter to continue..."
+ ;;
+ 5)
+ nl -w2 -s'. ' "$HOSTS_FILE"
+ read -rp "Line to remove: " line
+ if [[ "$line" =~ ^[0-9]+$ ]]; then
+ sed -i "${line}d" "$HOSTS_FILE"
+ echo "Host removed."
+ else
+ echo "Invalid line number."
+ fi
+ read -rp "Press Enter to continue..."
+ ;;
+ 6)
+ show_log_menu
+ ;;
+ 0|"")
+ clear
+ exit 0
+ ;;
+ esac
+done
diff --git a/update-manager.sh b/update-manager.sh
index 89b5ef9..679fea0 100755
--- a/update-manager.sh
+++ b/update-manager.sh
@@ -1,127 +1,79 @@
#!/usr/bin/env bash
-CONFIG_FILE="/etc/update-manager.conf"
-[[ -f "$CONFIG_FILE" ]] || CONFIG_FILE="./update-manager.conf"
+UPDATE_MANAGER_SCRIPT="$HOME/update-manager/update-manager.sh"
+HOSTS_FILE="/opt/update-manager/hosts.conf"
-if [[ -f "$CONFIG_FILE" ]]; then
- # shellcheck disable=SC1090
- source "$CONFIG_FILE"
-fi
+get_log_file() {
+ local primary="/opt/update-manager/log/update-manager.log"
+ local fallback="$HOME/update-manager/log/update-manager.log"
-DEFAULT_HOSTS_FILE="${HOSTS_FILE:-/opt/update-manager/hosts.conf}"
-SSH_OPTIONS="${SSH_OPTIONS:--o BatchMode=yes -o ConnectTimeout=5}"
-
-########################################
-# Ensure hosts file exists
-########################################
-
-ensure_hosts_file() {
- if [[ ! -f "$DEFAULT_HOSTS_FILE" ]]; then
- echo "Creating example hosts file: $DEFAULT_HOSTS_FILE"
-
- mkdir -p "$(dirname "$DEFAULT_HOSTS_FILE")"
-
- cat > "$DEFAULT_HOSTS_FILE" <<'EOF'
-# Example hosts file
-# Format:
-# name ip user
-#
-# server1 192.168.1.10 user
-# server2 192.168.1.20 user
-# server3 10.0.0.5 root
-EOF
- fi
-}
-
-########################################
-# Helpers
-########################################
-
-get_hosts_file() {
- if [[ -n "$2" ]]; then
- echo "$2"
+ if [[ -f "$primary" ]]; then
+ echo "$primary"
else
- echo "$DEFAULT_HOSTS_FILE"
+ echo "$fallback"
fi
}
-usage() {
- cat <&1 1>&2 2>&3)
-Examples:
- $0 check
- $0 check /path/to/hosts.conf
- HOSTS_FILE=/path/to/hosts.conf $0 check
-USAGE
-}
+ clear
-########################################
-# Check single host
-########################################
-
-check_host() {
- local name="$1"
- local ip="$2"
- local user="$3"
- local result
- local rc
-
- result=$(ssh -n $SSH_OPTIONS "${user}@${ip}" \
- "apt list --upgradable 2>/dev/null | sed '/^Listing/d'" 2>&1)
- rc=$?
-
- printf "===== %s (%s) =====\n" "$name" "$ip"
-
- if [[ $rc -ne 0 ]]; then
- echo "❌ connection failed"
- echo "$result"
- echo
- return
- fi
-
- if [[ -z "$result" ]]; then
- echo "Up-to-date"
- else
- echo "$result"
- fi
-
- echo
-}
-
-########################################
-# Check all hosts
-########################################
-
-check_all() {
- local hosts_file="$1"
-
- ensure_hosts_file
-
- while IFS= read -r line; do
- [[ -z "$line" || "$line" =~ ^[[:space:]]*# ]] && continue
-
- local name ip user
- read -r name ip user <<< "$line"
-
- [[ -z "$name" || -z "$ip" || -z "$user" ]] && continue
-
- check_host "$name" "$ip" "$user"
- done < "$hosts_file"
-}
-
-########################################
-# Main
-########################################
-
-case "${1:-}" in
- check)
- HOSTS_FILE_TO_USE="$(get_hosts_file "$@")"
- check_all "$HOSTS_FILE_TO_USE"
- ;;
- *)
- usage
- exit 1
- ;;
-esac
+ case $choice in
+ 1)
+ bash "$UPDATE_MANAGER_SCRIPT" check
+ read -rp "Press Enter to continue..."
+ ;;
+ 2)
+ less "$HOSTS_FILE"
+ ;;
+ 3)
+ nano "$HOSTS_FILE"
+ ;;
+ 4)
+ read -rp "Name: " name
+ read -rp "IP: " ip
+ read -rp "User: " user
+ echo "$name $ip $user" >> "$HOSTS_FILE"
+ echo "Host added."
+ read -rp "Press Enter to continue..."
+ ;;
+ 5)
+ nl -w2 -s'. ' "$HOSTS_FILE"
+ read -rp "Line to remove: " line
+ sed -i "${line}d" "$HOSTS_FILE"
+ echo "Host removed."
+ read -rp "Press Enter to continue..."
+ ;;
+ 6)
+ less "$(get_log_file)"
+ ;;
+ 7)
+ echo "Press Ctrl+C to stop"
+ tail -f "$(get_log_file)"
+ read -rp "Press Enter to continue..."
+ ;;
+ 8)
+ echo "Log file:"
+ echo "$(get_log_file)"
+ read -rp "Press Enter to continue..."
+ ;;
+ 0)
+ clear
+ exit 0
+ ;;
+ esac
+done