From 80961f9aec68402d56c9fb8d01b0227eea3e4f3f Mon Sep 17 00:00:00 2001 From: emrothenberg Date: Sat, 1 Nov 2025 17:00:41 +0200 Subject: [PATCH] Added docker support --- gui/engine.txt | 1 + gui/installer/installer.py | 6 +- gui/linoffice.py | 9 +- gui/mainwindow.py | 7 +- linoffice.sh | 440 +++++++++++++++++++++++----------- quickstart.sh | 151 +++++++++--- setup.sh | 475 ++++++++++++++++++++++++++----------- uninstall.sh | 71 +++--- 8 files changed, 789 insertions(+), 371 deletions(-) create mode 100644 gui/engine.txt diff --git a/gui/engine.txt b/gui/engine.txt new file mode 100644 index 0000000..6d0eac4 --- /dev/null +++ b/gui/engine.txt @@ -0,0 +1 @@ +docker \ No newline at end of file diff --git a/gui/installer/installer.py b/gui/installer/installer.py index 6591a25..05fb50e 100644 --- a/gui/installer/installer.py +++ b/gui/installer/installer.py @@ -383,7 +383,7 @@ def confirm_abort(self): # Ask about removing the container msg_box = QMessageBox() msg_box.setWindowTitle("Remove Container?") - msg_box.setText("Do you want to remove the 'LinOffice' podman container and all its data? This action cannot be undone.\n\nIf you are running this installer for the first time, you can select 'Yes'. If you have previously set up LinOffice and are running this installer again, you should select 'No' unless you explicitly want your Windows container including all its data to be deleted.") + msg_box.setText("Do you want to remove the 'LinOffice' container and all its data? This action cannot be undone.\n\nIf you are running this installer for the first time, you can select 'Yes'. If you have previously set up LinOffice and are running this installer again, you should select 'No' unless you explicitly want your Windows container including all its data to be deleted.") msg_box.setStandardButtons(QMessageBox.Yes | QMessageBox.No) msg_box.setDefaultButton(QMessageBox.No) msg_box.setIcon(QMessageBox.Warning) @@ -400,7 +400,7 @@ def confirm_abort(self): if not os.path.isfile(script_path): QMessageBox.critical( self, "Error", - f"Error. Podman container and volume have not been removed. Script not found: {script_path}", + f"Error. Container and volume have not been removed. Script not found: {script_path}", QMessageBox.Ok ) return @@ -428,7 +428,7 @@ def confirm_abort(self): if not success: QMessageBox.critical( self, "Error", - "Error. Podman container and volume have not been removed. Could not load terminal.", + "Error. Container and volume have not been removed. Could not load terminal.", QMessageBox.Ok ) diff --git a/gui/linoffice.py b/gui/linoffice.py index e24ade1..6bf7744 100644 --- a/gui/linoffice.py +++ b/gui/linoffice.py @@ -5,10 +5,10 @@ from PySide6.QtCore import QFileInfo from pathlib import Path -def container_exists(container_name="LinOffice"): +def container_exists(container_name="LinOffice", engine="podman"): try: result = subprocess.run( - ["podman", "ps", "-a", "--format", "{{.Names}}"], + [engine, "ps", "-a", "--format", "{{.Names}}"], capture_output=True, text=True ) @@ -44,8 +44,9 @@ def main(): mainwindow_path = os.path.join(base_dir, "mainwindow.py") installer_path = os.path.join(base_dir, "installer", "installer.py") log_path = "~/.local/share/linoffice/setup_progress.log" - - container_found = container_exists("LinOffice") + engine = open(os.path.join(os.path.dirname(__file__), "engine.txt")).read().strip() + + container_found = container_exists("LinOffice", engine) success = setup_successful(log_path) if container_found: diff --git a/gui/mainwindow.py b/gui/mainwindow.py index abf721f..e0b2bfb 100644 --- a/gui/mainwindow.py +++ b/gui/mainwindow.py @@ -93,6 +93,7 @@ def __init__(self, parent=None): super().__init__(parent) # Ensure registry config exists before loading UI ensure_registry_config_exists() + self.engine = open(os.path.join(os.path.dirname(__file__), "engine.txt")).read().strip() self.load_ui('main.ui') self.setWindowTitle(self.ui.windowTitle()) self.connect_buttons() @@ -141,7 +142,7 @@ def launch_linoffice_app(self, *args): def update_container_status(self): try: - result = subprocess.run(['podman', 'ps', '--filter', 'name=LinOffice', '--format', '{{.Status}}'], capture_output=True, text=True, check=True) + result = subprocess.run([self.engine, 'ps', '--filter', 'name=LinOffice', '--format', '{{.Status}}'], capture_output=True, text=True, check=True) status = result.stdout.strip() if status: status_text = f"Container: running ({status})" @@ -153,7 +154,7 @@ def update_container_status(self): def check_and_prompt_container(self): try: - result = subprocess.run(['podman', 'ps', '--filter', 'name=LinOffice', '--format', '{{.Status}}'], capture_output=True, text=True, check=True) + result = subprocess.run([self.engine, 'ps', '--filter', 'name=LinOffice', '--format', '{{.Status}}'], capture_output=True, text=True, check=True) if not result.stdout.strip(): # Container is not running, show dialog dialog = QMessageBox(self) @@ -166,7 +167,7 @@ def check_and_prompt_container(self): # Run linoffice.sh --startcontainer in the background threading.Thread(target=lambda: subprocess.Popen([LINOFFICE_SCRIPT, '--startcontainer'])).start() except subprocess.CalledProcessError as e: - print(f"DEBUG: podman ps error: {e.stderr}") + print(f"DEBUG: {self.engine} ps error: {e.stderr}") QMessageBox.critical(self, "Error", "Could not check container status.") # Defining secondary windows diff --git a/linoffice.sh b/linoffice.sh index 3c3bee2..c349065 100755 --- a/linoffice.sh +++ b/linoffice.sh @@ -36,7 +36,7 @@ readonly CONTAINER_NAME="LinOffice" readonly RDP_IP="127.0.0.1" readonly RDP_PORT="3388" readonly RUNID="${RANDOM}" -readonly WAFLAVOR="podman" +WAFLAVOR="podman" COMPOSE_COMMAND="podman-compose" ### GLOBAL VARIABLES ### @@ -65,6 +65,10 @@ SCRIPT_START_TIME=0 USE_VENV=0 VENV_PATH="" +# Container engine support (read from config) +CONTAINER_ENGINE="podman" +COMPOSE_COMMAND="podman-compose" + ### TRAPS ### # Catch SIGINT (CTRL+C) to call 'waCleanUp'. trap waCleanupInstance SIGINT SIGTERM SIGHUP EXIT @@ -596,12 +600,14 @@ function waCheckContainerRunning() { local MAX_WAIT_TIME=120 # Maximum time to wait for container to be ready # If the container does not exist at all, (re)create it - if ! podman container exists "$CONTAINER_NAME" 2>/dev/null; then + if ! ($WAFLAVOR container exists "$CONTAINER_NAME" 2>/dev/null || \ + $WAFLAVOR inspect "$CONTAINER_NAME" >/dev/null 2>&1); then + dprint "WINDOWS CONTAINER MISSING. RECREATING." echo -e "Creating Windows container." $COMPOSE_COMMAND --file "$COMPOSE_PATH" up -d &>/dev/null NEEDED_BOOT=true - # Give podman a moment to register the container before inspecting + # Give container engine a moment to register the container before inspecting sleep 2 fi @@ -796,20 +802,37 @@ function waRunCommand() { # Open Windows RDP session. dprint "WINDOWS" - podman unshare --rootless-netns "${FREERDP_COMMAND[@]}" \ - /u:$RDP_USER \ - /p:$RDP_PASS \ - /scale:$RDP_SCALE \ - +dynamic-resolution \ - +auto-reconnect \ - +home-drive \ - +clipboard \ - -wallpaper \ - $RDP_KBD \ - /wm-class:"Microsoft Windows" \ - /t:"Windows RDP Session [$RDP_IP]" \ - $RDP_FLAGS \ - /v:"$RDP_IP:$RDP_PORT" &>/dev/null & + if [ "$USE_DOCKER" -eq 1 ]; then + "${FREERDP_COMMAND[@]}" \ + /u:$RDP_USER \ + /p:$RDP_PASS \ + /scale:$RDP_SCALE \ + +dynamic-resolution \ + +auto-reconnect \ + +home-drive \ + +clipboard \ + -wallpaper \ + $RDP_KBD \ + /wm-class:"Microsoft Windows" \ + /t:"Windows RDP Session [$RDP_IP]" \ + $RDP_FLAGS \ + /v:"$RDP_IP:$RDP_PORT" &>/dev/null & + else + podman unshare --rootless-netns "${FREERDP_COMMAND[@]}" \ + /u:$RDP_USER \ + /p:$RDP_PASS \ + /scale:$RDP_SCALE \ + +dynamic-resolution \ + +auto-reconnect \ + +home-drive \ + +clipboard \ + -wallpaper \ + $RDP_KBD \ + /wm-class:"Microsoft Windows" \ + /t:"Windows RDP Session [$RDP_IP]" \ + $RDP_FLAGS \ + /v:"$RDP_IP:$RDP_PORT" &>/dev/null & + fi # Capture the process ID. FREERDP_PID=$! @@ -817,18 +840,33 @@ function waRunCommand() { elif [ "$1" = "manual" ]; then # Open specified application. dprint "MANUAL: ${2}" - podman unshare --rootless-netns "${FREERDP_COMMAND[@]}" \ - /u:$RDP_USER \ - /p:$RDP_PASS \ - /scale:$RDP_SCALE \ - +auto-reconnect \ - +home-drive \ - +clipboard \ - -wallpaper \ - $RDP_KBD \ - $RDP_FLAGS \ - /app:program:"$2",hidef:"$HIDEF" \ - /v:"$RDP_IP:$RDP_PORT" &>/dev/null & + if [ "$USE_DOCKER" -eq 1 ]; then + "${FREERDP_COMMAND[@]}" \ + /u:$RDP_USER \ + /p:$RDP_PASS \ + /scale:$RDP_SCALE \ + +auto-reconnect \ + +home-drive \ + +clipboard \ + -wallpaper \ + $RDP_KBD \ + $RDP_FLAGS \ + /app:program:"$2",hidef:"$HIDEF" \ + /v:"$RDP_IP:$RDP_PORT" &>/dev/null & + else + podman unshare --rootless-netns "${FREERDP_COMMAND[@]}" \ + /u:$RDP_USER \ + /p:$RDP_PASS \ + /scale:$RDP_SCALE \ + +auto-reconnect \ + +home-drive \ + +clipboard \ + -wallpaper \ + $RDP_KBD \ + $RDP_FLAGS \ + /app:program:"$2",hidef:"$HIDEF" \ + /v:"$RDP_IP:$RDP_PORT" &>/dev/null & + fi # Capture the process ID. FREERDP_PID=$! @@ -836,18 +874,33 @@ function waRunCommand() { elif [ "$1" = "update" ]; then # Run the script dprint "UPDATE" - podman unshare --rootless-netns "${FREERDP_COMMAND[@]}" \ - /u:$RDP_USER \ - /p:$RDP_PASS \ - /scale:$RDP_SCALE \ - +auto-reconnect \ - +home-drive \ - +clipboard \ - -wallpaper \ - $RDP_KBD \ - $RDP_FLAGS \ - /app:program:powershell.exe,cmd:'-ExecutionPolicy Bypass -File C:\\OEM\\UpdateWindows.ps1' \ - /v:"$RDP_IP:$RDP_PORT" &>/dev/null & + if [ "$USE_DOCKER" -eq 1 ]; then + "${FREERDP_COMMAND[@]}" \ + /u:$RDP_USER \ + /p:$RDP_PASS \ + /scale:$RDP_SCALE \ + +auto-reconnect \ + +home-drive \ + +clipboard \ + -wallpaper \ + $RDP_KBD \ + $RDP_FLAGS \ + /app:program:powershell.exe,cmd:'-ExecutionPolicy Bypass -File C:\\OEM\\UpdateWindows.ps1' \ + /v:"$RDP_IP:$RDP_PORT" &>/dev/null & + else + podman unshare --rootless-netns "${FREERDP_COMMAND[@]}" \ + /u:$RDP_USER \ + /p:$RDP_PASS \ + /scale:$RDP_SCALE \ + +auto-reconnect \ + +home-drive \ + +clipboard \ + -wallpaper \ + $RDP_KBD \ + $RDP_FLAGS \ + /app:program:powershell.exe,cmd:'-ExecutionPolicy Bypass -File C:\\OEM\\UpdateWindows.ps1' \ + /v:"$RDP_IP:$RDP_PORT" &>/dev/null & + fi # Capture the process ID. FREERDP_PID=$! @@ -856,18 +909,33 @@ function waRunCommand() { elif [ "$1" = "registry_override" ]; then # Run the script dprint "UPDATE" - podman unshare --rootless-netns "${FREERDP_COMMAND[@]}" \ - /u:$RDP_USER \ - /p:$RDP_PASS \ - /scale:$RDP_SCALE \ - +auto-reconnect \ - +home-drive \ - +clipboard \ - -wallpaper \ - $RDP_KBD \ - $RDP_FLAGS \ - /app:program:powershell.exe,cmd:'-ExecutionPolicy Bypass -File C:\\OEM\\RegistryOverride.ps1' \ - /v:"$RDP_IP:$RDP_PORT" &>/dev/null & + if [ "$USE_DOCKER" -eq 1 ]; then + "${FREERDP_COMMAND[@]}" \ + /u:$RDP_USER \ + /p:$RDP_PASS \ + /scale:$RDP_SCALE \ + +auto-reconnect \ + +home-drive \ + +clipboard \ + -wallpaper \ + $RDP_KBD \ + $RDP_FLAGS \ + /app:program:powershell.exe,cmd:'-ExecutionPolicy Bypass -File C:\\OEM\\RegistryOverride.ps1' \ + /v:"$RDP_IP:$RDP_PORT" &>/dev/null & + else + podman unshare --rootless-netns "${FREERDP_COMMAND[@]}" \ + /u:$RDP_USER \ + /p:$RDP_PASS \ + /scale:$RDP_SCALE \ + +auto-reconnect \ + +home-drive \ + +clipboard \ + -wallpaper \ + $RDP_KBD \ + $RDP_FLAGS \ + /app:program:powershell.exe,cmd:'-ExecutionPolicy Bypass -File C:\\OEM\\RegistryOverride.ps1' \ + /v:"$RDP_IP:$RDP_PORT" &>/dev/null & + fi # Capture the process ID. FREERDP_PID=$! @@ -876,18 +944,33 @@ function waRunCommand() { elif [ "$1" = "internet_off" ]; then # Run the script dprint "UPDATE" - podman unshare --rootless-netns "${FREERDP_COMMAND[@]}" \ - /u:$RDP_USER \ - /p:$RDP_PASS \ - /scale:$RDP_SCALE \ - +auto-reconnect \ - +home-drive \ - +clipboard \ - -wallpaper \ - $RDP_KBD \ - $RDP_FLAGS \ - /app:program:cmd.exe,cmd:'/c C:\\OEM\\dns_off.bat' \ - /v:"$RDP_IP:$RDP_PORT" &>/dev/null & + if [ "$USE_DOCKER" -eq 1 ]; then + "${FREERDP_COMMAND[@]}" \ + /u:$RDP_USER \ + /p:$RDP_PASS \ + /scale:$RDP_SCALE \ + +auto-reconnect \ + +home-drive \ + +clipboard \ + -wallpaper \ + $RDP_KBD \ + $RDP_FLAGS \ + /app:program:cmd.exe,cmd:'/c C:\\OEM\\dns_off.bat' \ + /v:"$RDP_IP:$RDP_PORT" &>/dev/null & + else + podman unshare --rootless-netns "${FREERDP_COMMAND[@]}" \ + /u:$RDP_USER \ + /p:$RDP_PASS \ + /scale:$RDP_SCALE \ + +auto-reconnect \ + +home-drive \ + +clipboard \ + -wallpaper \ + $RDP_KBD \ + $RDP_FLAGS \ + /app:program:cmd.exe,cmd:'/c C:\\OEM\\dns_off.bat' \ + /v:"$RDP_IP:$RDP_PORT" &>/dev/null & + fi # Capture the process ID. FREERDP_PID=$! @@ -896,18 +979,33 @@ function waRunCommand() { elif [ "$1" = "internet_on" ]; then # Run the script dprint "UPDATE" - podman unshare --rootless-netns "${FREERDP_COMMAND[@]}" \ - /u:$RDP_USER \ - /p:$RDP_PASS \ - /scale:$RDP_SCALE \ - +auto-reconnect \ - +home-drive \ - +clipboard \ - -wallpaper \ - $RDP_KBD \ - $RDP_FLAGS \ - /app:program:cmd.exe,cmd:'/c C:\\OEM\\dns_on.bat' \ - /v:"$RDP_IP:$RDP_PORT" &>/dev/null & + if [ "$USE_DOCKER" -eq 1 ]; then + "${FREERDP_COMMAND[@]}" \ + /u:$RDP_USER \ + /p:$RDP_PASS \ + /scale:$RDP_SCALE \ + +auto-reconnect \ + +home-drive \ + +clipboard \ + -wallpaper \ + $RDP_KBD \ + $RDP_FLAGS \ + /app:program:cmd.exe,cmd:'/c C:\\OEM\\dns_on.bat' \ + /v:"$RDP_IP:$RDP_PORT" &>/dev/null & + else + podman unshare --rootless-netns "${FREERDP_COMMAND[@]}" \ + /u:$RDP_USER \ + /p:$RDP_PASS \ + /scale:$RDP_SCALE \ + +auto-reconnect \ + +home-drive \ + +clipboard \ + -wallpaper \ + $RDP_KBD \ + $RDP_FLAGS \ + /app:program:cmd.exe,cmd:'/c C:\\OEM\\dns_on.bat' \ + /v:"$RDP_IP:$RDP_PORT" &>/dev/null & + fi # Capture the process ID. FREERDP_PID=$! @@ -936,19 +1034,35 @@ function waRunCommand() { if [ -z "$2" ]; then # No file path specified. dprint "LAUNCHING OFFICE APP: $FULL_NAME" - podman unshare --rootless-netns "${FREERDP_COMMAND[@]}" \ - /u:$RDP_USER \ - /p:$RDP_PASS \ - /scale:$RDP_SCALE \ - +auto-reconnect \ - +home-drive \ - +clipboard \ - -wallpaper \ - $RDP_KBD \ - $RDP_FLAGS \ - /wm-class:"$FULL_NAME" \ - /app:program:"$EXE",hidef:"$HIDEF",icon:"$ICON",name:"$FULL_NAME" \ - /v:"$RDP_IP:$RDP_PORT" &>/dev/null & + if [ "$USE_DOCKER" -eq 1 ]; then + "${FREERDP_COMMAND[@]}" \ + /u:$RDP_USER \ + /p:$RDP_PASS \ + /scale:$RDP_SCALE \ + +auto-reconnect \ + +home-drive \ + +clipboard \ + -wallpaper \ + $RDP_KBD \ + $RDP_FLAGS \ + /wm-class:"$FULL_NAME" \ + /app:program:"$EXE",hidef:"$HIDEF",icon:"$ICON",name:"$FULL_NAME" \ + /v:"$RDP_IP:$RDP_PORT" &>/dev/null & + else + podman unshare --rootless-netns "${FREERDP_COMMAND[@]}" \ + /u:$RDP_USER \ + /p:$RDP_PASS \ + /scale:$RDP_SCALE \ + +auto-reconnect \ + +home-drive \ + +clipboard \ + -wallpaper \ + $RDP_KBD \ + $RDP_FLAGS \ + /wm-class:"$FULL_NAME" \ + /app:program:"$EXE",hidef:"$HIDEF",icon:"$ICON",name:"$FULL_NAME" \ + /v:"$RDP_IP:$RDP_PORT" &>/dev/null & + fi # Capture the process ID. FREERDP_PID=$! @@ -967,20 +1081,37 @@ function waRunCommand() { dprint "WINDOWS_FILE_PATH: ${FILE_PATH}" dprint "LAUNCHING OFFICE APP WITH FILE: $FULL_NAME" - podman unshare --rootless-netns "${FREERDP_COMMAND[@]}" \ - /u:$RDP_USER \ - /p:$RDP_PASS \ - /scale:$RDP_SCALE \ - +auto-reconnect \ - +home-drive \ - +clipboard \ - /drive:media,"$REMOVABLE_MEDIA" \ - -wallpaper \ - $RDP_KBD \ - $RDP_FLAGS \ - /wm-class:"$FULL_NAME" \ - /app:program:"$EXE",hidef:"$HIDEF",icon:"$ICON",name:"$FULL_NAME",cmd:\""$FILE_PATH"\" \ - /v:"$RDP_IP:$RDP_PORT" &>/dev/null & + if [ "$USE_DOCKER" -eq 1 ]; then + "${FREERDP_COMMAND[@]}" \ + /u:$RDP_USER \ + /p:$RDP_PASS \ + /scale:$RDP_SCALE \ + +auto-reconnect \ + +home-drive \ + +clipboard \ + /drive:media,"$REMOVABLE_MEDIA" \ + -wallpaper \ + $RDP_KBD \ + $RDP_FLAGS \ + /wm-class:"$FULL_NAME" \ + /app:program:"$EXE",hidef:"$HIDEF",icon:"$ICON",name:"$FULL_NAME",cmd:\""$FILE_PATH"\" \ + /v:"$RDP_IP:$RDP_PORT" &>/dev/null & + else + podman unshare --rootless-netns "${FREERDP_COMMAND[@]}" \ + /u:$RDP_USER \ + /p:$RDP_PASS \ + /scale:$RDP_SCALE \ + +auto-reconnect \ + +home-drive \ + +clipboard \ + /drive:media,"$REMOVABLE_MEDIA" \ + -wallpaper \ + $RDP_KBD \ + $RDP_FLAGS \ + /wm-class:"$FULL_NAME" \ + /app:program:"$EXE",hidef:"$HIDEF",icon:"$ICON",name:"$FULL_NAME",cmd:\""$FILE_PATH"\" \ + /v:"$RDP_IP:$RDP_PORT" &>/dev/null & + fi # Capture the process ID. FREERDP_PID=$! @@ -1015,24 +1146,7 @@ function waRunCommand() { exit 1 fi - # Wait for the process to terminate with timeout - local wait_timeout=30 - local wait_elapsed=0 - local wait_interval=1 - - while kill -0 "$FREERDP_PID" 2>/dev/null && [ $wait_elapsed -lt $wait_timeout ]; do - sleep $wait_interval - wait_elapsed=$((wait_elapsed + wait_interval)) - done - - # If process is still running after timeout, force kill it - if kill -0 "$FREERDP_PID" 2>/dev/null; then - dprint "FreeRDP process $FREERDP_PID still running after timeout, force killing" - kill -TERM "$FREERDP_PID" 2>/dev/null - sleep 2 - kill -KILL "$FREERDP_PID" 2>/dev/null - fi - + wait "$FREERDP_PID" # Remove the file with the process ID rm "${APPDATA_PATH}/FreeRDP_Process_${FREERDP_PID}.cproc" &>/dev/null @@ -1085,18 +1199,33 @@ use_venv() { VENV_PATH="$venv_dir" USE_VENV=1 - # Only use venv podman-compose if system version is not available - if [[ ! -x "/usr/bin/podman-compose" ]] && ! command -v podman-compose &>/dev/null; then - PYTHON_PATH="$venv_dir/bin/python3" - USER_SITE_PATH=$($PYTHON_PATH -m site | grep USER_SITE | awk -F"'" '{print $2}') - PODMAN_BIN=$USER_SITE_PATH/podman_compose.py + # Only use venv compose command if system version is not available + if [ "$USE_DOCKER" -eq 1 ]; then + if [[ ! -x "/usr/bin/docker-compose" ]] && ! command -v docker-compose &>/dev/null; then + PYTHON_PATH="$venv_dir/bin/python3" + USER_SITE_PATH=$($PYTHON_PATH -m site | grep USER_SITE | awk -F"'" '{print $2}') + DOCKER_BIN=$USER_SITE_PATH/docker_compose.py - if [[ -f "$PODMAN_BIN" ]]; then - echo "using podman-compose from venv (system version not available)" - COMPOSE_COMMAND="$PYTHON_PATH $PODMAN_BIN" + if [[ -f "$DOCKER_BIN" ]]; then + echo "using Docker Compose from venv (system version not available)" + COMPOSE_COMMAND="$PYTHON_PATH $DOCKER_BIN" + fi + else + echo "system Docker Compose available (will be used instead of venv version)" fi else - echo "system podman-compose available (will be used instead of venv version)" + if [[ ! -x "/usr/bin/podman-compose" ]] && ! command -v podman-compose &>/dev/null; then + PYTHON_PATH="$venv_dir/bin/python3" + USER_SITE_PATH=$($PYTHON_PATH -m site | grep USER_SITE | awk -F"'" '{print $2}') + PODMAN_BIN=$USER_SITE_PATH/podman_compose.py + + if [[ -f "$PODMAN_BIN" ]]; then + echo "using Podman Compose from venv (system version not available)" + COMPOSE_COMMAND="$PYTHON_PATH $PODMAN_BIN" + fi + else + echo "system Podman Compose available (will be used instead of venv version)" + fi fi return 0 else @@ -1113,7 +1242,7 @@ if [[ "$1" == "--stopcontainer" ]]; then echo "Attempting graceful shutdown of LinOffice container..." # Check the current status of the container - CONTAINER_STATUS=$(podman inspect --format='{{.State.Status}}' "$CONTAINER_NAME" 2>/dev/null) + CONTAINER_STATUS=$($WAFLAVOR inspect --format='{{.State.Status}}' "$CONTAINER_NAME" 2>/dev/null) # If the container is paused, it must be un-paused first to shut down cleanly if [[ "$CONTAINER_STATUS" == "paused" ]]; then @@ -1125,7 +1254,7 @@ if [[ "$1" == "--stopcontainer" ]]; then # Now, if the container is running (or was just un-paused), stop it if [[ "$CONTAINER_STATUS" == "running" || "$CONTAINER_STATUS" == "paused" ]]; then echo "Sending stop command... (this may take up to 2 minutes)" - podman stop "$CONTAINER_NAME" &>/dev/null + $WAFLAVOR stop "$CONTAINER_NAME" &>/dev/null else echo "Container is not running." fi @@ -1146,20 +1275,45 @@ waLastRun waLoadConfig waGetFreeRDPCommand +# Read container engine choice from config file +if [ -f "$CONFIG_PATH" ]; then + # Source the config file to get CONTAINER_ENGINE and COMPOSE_COMMAND + source "$CONFIG_PATH" + + # Set WAFLAVOR based on CONTAINER_ENGINE from config + if [ "$CONTAINER_ENGINE" = "docker" ]; then + WAFLAVOR="docker" + USE_DOCKER=1 + echo "Using Docker (from setup configuration)" + else + WAFLAVOR="podman" + USE_DOCKER=0 + echo "Using Podman (from setup configuration)" + fi + + # Use COMPOSE_COMMAND from config if available + if [ -n "$COMPOSE_COMMAND" ]; then + echo "Using compose command: $COMPOSE_COMMAND" + fi +else + # Fallback to default if config file doesn't exist + WAFLAVOR="podman" + USE_DOCKER=0 + echo "Using Podman (default - no config file found)" +fi + # Check for virtual environment echo "Checking for virtual environment..." use_venv || echo "Using system Python" -# Ensure COMPOSE_COMMAND is set to a working value -# First try the system podman-compose if it exists and is executable -if [[ -x "/usr/bin/podman-compose" ]]; then - COMPOSE_COMMAND="/usr/bin/podman-compose" - echo "Using system podman-compose from /usr/bin/" -elif command -v podman-compose &>/dev/null; then - COMPOSE_COMMAND="podman-compose" - echo "Using podman-compose from PATH" -else - echo "ERROR: No working podman-compose found" +# Verify COMPOSE_COMMAND is available +if ! command -v "$COMPOSE_COMMAND" &>/dev/null; then + if [ "$USE_DOCKER" -eq 1 ]; then + echo "ERROR: Docker Compose command '$COMPOSE_COMMAND' not found" + else + echo "ERROR: Podman Compose command '$COMPOSE_COMMAND' not found" + fi + echo "Please run setup.sh first to configure the container engine" exit 1 fi diff --git a/quickstart.sh b/quickstart.sh index aeea05d..4e47d60 100755 --- a/quickstart.sh +++ b/quickstart.sh @@ -10,6 +10,7 @@ TMPDIR=$(mktemp -d) GITHUB_API_URL="https://api.github.com/repos/${REPO_OWNER}/${REPO_NAME}/releases" LINOFFICE_SCRIPT="$TARGET_DIR/gui/linoffice.py" +ENGINE_FILE="$TARGET_DIR/gui/engine.txt" APPDATA_DIR="$HOME/.local/share/linoffice" INSTALLED_PM_PACKAGES=() @@ -25,6 +26,9 @@ VENV_PATH="" # Immutable system support USE_IMMUTABLE=0 +# Docker support +USE_DOCKER=0 + ################################################## # PART 1: INSTALL DEPENDENCIES ################################################## @@ -333,9 +337,16 @@ dependencies_main() { echo "Detected distro: $DISTRO_ID, package manager: $PKG_MGR" fi - echo "Checking podman..." - if ! pkg_exists podman; then - try_install_any podman || { echo "Failed to install podman"; exit 1; } + if [ "$USE_DOCKER" -eq 1 ]; then + echo "Checking Docker..." + if ! pkg_exists docker; then + try_install_any docker docker.io || { echo "Failed to install Docker"; exit 1; } + fi + else + echo "Checking Podman..." + if ! pkg_exists podman; then + try_install_any podman || { echo "Failed to install Podman"; exit 1; } + fi fi echo "Checking Python 3..." @@ -362,22 +373,41 @@ dependencies_main() { fi fi - # Check if podman-compose needs to be installed via pip - NEED_PODMAN_COMPOSE_PIP=false - if ! pkg_exists podman-compose; then - if [ "$USE_IMMUTABLE" -eq 1 ]; then - echo "podman-compose not available, will install via pip for immutable system" - NEED_PODMAN_COMPOSE_PIP=true + # Check if compose command needs to be installed via pip + NEED_COMPOSE_PIP=false + if [ "$USE_DOCKER" -eq 1 ]; then + if ! pkg_exists docker-compose; then + if [ "$USE_IMMUTABLE" -eq 1 ]; then + echo "Docker Compose not available, will install via pip for immutable system" + NEED_COMPOSE_PIP=true + else + try_install_any docker-compose || true + if ! pkg_exists docker-compose; then + echo "Docker Compose not available via package manager, will install via pip" + NEED_COMPOSE_PIP=true + fi + fi else - try_install_any podman-compose || true - if ! pkg_exists podman-compose; then - echo "podman-compose not available via package manager, will install via pip" - NEED_PODMAN_COMPOSE_PIP=true + if [ "$USE_IMMUTABLE" -eq 1 ]; then + echo "Docker Compose already available via system package manager" fi fi else - if [ "$USE_IMMUTABLE" -eq 1 ]; then - echo "podman-compose already available via system package manager" + if ! pkg_exists podman-compose; then + if [ "$USE_IMMUTABLE" -eq 1 ]; then + echo "Podman Compose not available, will install via pip for immutable system" + NEED_COMPOSE_PIP=true + else + try_install_any podman-compose || true + if ! pkg_exists podman-compose; then + echo "Podman Compose not available via package manager, will install via pip" + NEED_COMPOSE_PIP=true + fi + fi + else + if [ "$USE_IMMUTABLE" -eq 1 ]; then + echo "Podman Compose already available via system package manager" + fi fi fi @@ -401,20 +431,29 @@ dependencies_main() { fi # Install Python dependencies via pip if needed - if [ "$NEED_PODMAN_COMPOSE_PIP" = true ] || [ "$NEED_PYSIDE6_PIP" = true ]; then + if [ "$NEED_COMPOSE_PIP" = true ] || [ "$NEED_PYSIDE6_PIP" = true ]; then echo "Setting up Python environment for pip installations..." if use_venv; then echo "Using virtual environment for Python dependencies" source "$VENV_PATH/bin/activate" - if [ "$NEED_PODMAN_COMPOSE_PIP" = true ]; then - echo "Installing podman-compose via pip in virtual environment" - ensure_pip - pre_has_dotenv=0 - if is_python_dotenv_installed; then pre_has_dotenv=1; fi - pip3 install --user podman-compose || pip install --user podman-compose - INSTALLED_PIP_PACKAGES+=("podman-compose") + if [ "$NEED_COMPOSE_PIP" = true ]; then + if [ "$USE_DOCKER" -eq 1 ]; then + echo "Installing Docker Compose via pip in virtual environment" + ensure_pip + pre_has_dotenv=0 + if is_python_dotenv_installed; then pre_has_dotenv=1; fi + pip3 install --user docker-compose || pip install --user docker-compose + INSTALLED_PIP_PACKAGES+=("docker-compose") + else + echo "Installing Podman Compose via pip in virtual environment" + ensure_pip + pre_has_dotenv=0 + if is_python_dotenv_installed; then pre_has_dotenv=1; fi + pip3 install --user podman-compose || pip install --user podman-compose + INSTALLED_PIP_PACKAGES+=("podman-compose") + fi PIP_VENV=1 export PATH="$HOME/.local/bin:$PATH" if [ $pre_has_dotenv -eq 0 ] && is_python_dotenv_installed; then @@ -434,13 +473,22 @@ dependencies_main() { else echo "Using system Python" - if [ "$NEED_PODMAN_COMPOSE_PIP" = true ]; then - echo "Installing podman-compose via pip with --break-system-packages" - ensure_pip - pre_has_dotenv=0 - if is_python_dotenv_installed; then pre_has_dotenv=1; fi - pip3 install --user --break-system-packages podman-compose || pip install --user --break-system-packages podman-compose - INSTALLED_PIP_PACKAGES+=("podman-compose") + if [ "$NEED_COMPOSE_PIP" = true ]; then + if [ "$USE_DOCKER" -eq 1 ]; then + echo "Installing Docker Compose via pip with --break-system-packages" + ensure_pip + pre_has_dotenv=0 + if is_python_dotenv_installed; then pre_has_dotenv=1; fi + pip3 install --user --break-system-packages docker-compose || pip install --user --break-system-packages docker-compose + INSTALLED_PIP_PACKAGES+=("docker-compose") + else + echo "Installing Podman Compose via pip with --break-system-packages" + ensure_pip + pre_has_dotenv=0 + if is_python_dotenv_installed; then pre_has_dotenv=1; fi + pip3 install --user --break-system-packages podman-compose || pip install --user --break-system-packages podman-compose + INSTALLED_PIP_PACKAGES+=("podman-compose") + fi export PATH="$HOME/.local/bin:$PATH" if [ $pre_has_dotenv -eq 0 ] && is_python_dotenv_installed; then INSTALLED_PIP_PACKAGES+=("python-dotenv") @@ -687,6 +735,33 @@ start_linoffice() { nohup "$PYTHON_CMD" "$LINOFFICE_SCRIPT" > /dev/null 2>&1 & } +################################################## +# Parse command line arguments +################################################## + +# Parse command line arguments +while [[ $# -gt 0 ]]; do + case $1 in + --docker) + USE_DOCKER=1 + echo "docker" > "$ENGINE_FILE" + shift + ;; + --help) + echo "Usage: $0 [--docker]" + echo "Options:" + echo " --docker Use Docker instead of Podman for container management" + echo " --help Show this help message" + exit 0 + ;; + *) + echo "Unknown option: $1" + echo "Use --help for usage information" + exit 1 + ;; + esac +done + ################################################## # Main logic ################################################## @@ -695,7 +770,21 @@ read -p "Welcome to the LinOffice installer. We will check and install dependenc if [[ "$confirmation" == "y" || "$confirmation" == "Y" ]]; then dependencies_main "$@" download_latest - start_linoffice + + # Pass --docker flag to setup.sh if it was used + SETUP_ARGS="" + if [ "$USE_DOCKER" -eq 1 ]; then + SETUP_ARGS="--docker" + fi + + # Run setup.sh with the appropriate flags + if [ -f "./setup.sh" ]; then + echo "Running setup with container engine choice..." + ./setup.sh $SETUP_ARGS + else + echo "setup.sh not found in current directory" + start_linoffice + fi else echo "Cancelled." exit 1 diff --git a/setup.sh b/setup.sh index cca98f5..a62dc8f 100755 --- a/setup.sh +++ b/setup.sh @@ -13,6 +13,7 @@ mkdir -p "$APPDATA_PATH" SUCCESS_FILE="${APPDATA_PATH}/success" PROGRESS_FILE="${APPDATA_PATH}/setup_progress.log" OUTPUT_LOG="${APPDATA_PATH}/setup_output.log" +ENGINE_FILE="$TARGET_DIR/gui/engine.txt" # Relative filepaths SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" @@ -49,6 +50,7 @@ DESKTOP_ONLY=false FIRSTRUN=false INSTALL_OFFICE_ONLY=false HEALTHCHECK=false +USE_DOCKER=false # Colors for output RED='\033[0;31m' @@ -59,6 +61,7 @@ NC='\033[0m' # No Color USE_VENV=0 COMPOSE_COMMAND="podman-compose" +CONTAINER_ENGINE="podman" # Redirect all output to the log file exec > >(stdbuf -oL -eL sed -u 's/\x1b\[[0-9;]*m//g' | tee -a "$OUTPUT_LOG") 2>&1 @@ -135,13 +138,14 @@ use_venv # Function to display usage information print_usage() { - print_info "Usage: $0 [--desktop] [--firstrun] [--installoffice] [--healthcheck]" + print_info "Usage: $0 [--desktop] [--firstrun] [--installoffice] [--healthcheck] [--docker]" print_info "Options:" print_info " (no flag) Run the installation script from the beginning" print_info " --desktop Only recreate the desktop files (.desktop launchers)" print_info " --firstrun Force RDP and Office installation checks" print_info " --installoffice Only run the Office installation script script (in case the Windows installation has finished but Office is not installed)" print_info " --healthcheck Check that the system requirements are met and dependencies are installed and the container is healthy" + print_info " --docker Use Docker instead of Podman for container management" exit 1 } @@ -163,7 +167,12 @@ while [[ $# -gt 0 ]]; do --healthcheck) HEALTHCHECK=true shift - ;; + ;; + --docker) + USE_DOCKER=true + echo "docker" > "$ENGINE_FILE" + shift + ;; --help) print_usage ;; @@ -174,6 +183,43 @@ while [[ $# -gt 0 ]]; do esac done +# Set container engine and compose command based on --docker flag +if [ "$USE_DOCKER" = true ]; then + CONTAINER_ENGINE="docker" + COMPOSE_COMMAND="docker-compose" + print_info "Using Docker instead of Podman" +else + CONTAINER_ENGINE="podman" + COMPOSE_COMMAND="podman-compose" + print_info "Using Podman (default)" +fi + +# Store container engine choice in config file for linoffice.sh to use +print_info "Storing container engine choice in configuration file" +if [ ! -f "$LINOFFICE_CONF" ]; then + if [ -f "$LINOFFICE_CONF.default" ]; then + cp "$LINOFFICE_CONF.default" "$LINOFFICE_CONF" + print_info "Created configuration file from default template" + else + exit_with_error "Configuration file not found: $LINOFFICE_CONF.default" + fi +fi + +# Update the configuration file with container engine choice +if grep -q "^CONTAINER_ENGINE=" "$LINOFFICE_CONF"; then + sed -i "s/^CONTAINER_ENGINE=.*/CONTAINER_ENGINE=\"$CONTAINER_ENGINE\"/" "$LINOFFICE_CONF" +else + echo "CONTAINER_ENGINE=\"$CONTAINER_ENGINE\"" >> "$LINOFFICE_CONF" +fi + +if grep -q "^COMPOSE_COMMAND=" "$LINOFFICE_CONF"; then + sed -i "s/^COMPOSE_COMMAND=.*/COMPOSE_COMMAND=\"$COMPOSE_COMMAND\"/" "$LINOFFICE_CONF" +else + echo "COMPOSE_COMMAND=\"$COMPOSE_COMMAND\"" >> "$LINOFFICE_CONF" +fi + +print_success "Container engine choice stored: $CONTAINER_ENGINE" + # Function to exit with error exit_with_error() { print_error "$1" @@ -208,7 +254,7 @@ function clear_progress() { function check_linoffice_container() { print_info "Checking if LinOffice container exists already" - if podman container exists "$CONTAINER_NAME"; then + if $CONTAINER_ENGINE container exists "$CONTAINER_NAME"; then print_info "Container exists already." CONTAINER_EXISTS=1 else @@ -321,11 +367,34 @@ function check_requirements() { print_success "Virtualization support detected: $VIRT_SUPPORT" - # Check if podman is installed - print_info "Checking if podman is installed" + # Check if container engine is installed + if [ "$USE_DOCKER" = true ]; then + print_info "Checking if Docker is installed" + + if ! command -v docker &> /dev/null; then + exit_with_error "Docker is not installed. + + HOW TO FIX: + Ubuntu/Debian: sudo apt update && sudo apt install docker.io + Fedora/RHEL: sudo dnf install docker + OpenSUSE: sudo zypper install docker + Arch Linux: sudo pacman -S docker + openSUSE: sudo zypper install docker + + Or visit: https://docs.docker.com/get-docker/" + fi + + if ! docker info >/dev/null 2>&1; then + exit_with_error "Docker is not configured correctly or you lack sufficient permissions. Run 'docker info' to diagnose the issue." + fi + + DOCKER_VERSION=$(docker --version) + print_success "Docker is installed: $DOCKER_VERSION" + else + print_info "Checking if Podman is installed" - if ! command -v podman &> /dev/null; then - exit_with_error "podman is not installed. + if ! command -v podman &> /dev/null; then + exit_with_error "Podman is not installed. HOW TO FIX: Ubuntu/Debian: sudo apt update && sudo apt install podman @@ -335,17 +404,22 @@ function check_requirements() { openSUSE: sudo zypper install podman Or visit: https://podman.io/getting-started/installation" - fi - - if ! podman info >/dev/null 2>&1; then - exit_with_error "Podman is not configured correctly or you lack sufficient permissions. Run 'podman info' to diagnose the issue." - fi + fi + + if ! podman info >/dev/null 2>&1; then + exit_with_error "Podman is not configured correctly or you lack sufficient permissions. Run 'podman info' to diagnose the issue." + fi - PODMAN_VERSION=$(podman --version) - print_success "podman is installed: $PODMAN_VERSION" + PODMAN_VERSION=$(podman --version) + print_success "Podman is installed: $PODMAN_VERSION" + fi - # Check if podman-compose is installed - print_info "Checking if podman-compose is installed" + # Check if compose command is installed + if [ "$USE_DOCKER" = true ]; then + print_info "Checking if Docker Compose is installed" + else + print_info "Checking if Podman Compose is installed" + fi print_info "Python environment: $(if [[ "$USE_VENV" -eq 1 ]]; then echo "Virtual environment at $VENV_PATH"; else echo "System Python"; fi)" # Determine which Python to use for dependency checks @@ -358,15 +432,37 @@ function check_requirements() { fi if [[ "$USE_VENV" -eq 0 ]]; then - # Use system podman-compose, not the one in ~/.local/bin which might be broken - if [[ -x "/usr/bin/podman-compose" ]]; then - COMPOSE_COMMAND="/usr/bin/podman-compose" - print_success "Using system podman-compose: /usr/bin/podman-compose" - elif command -v podman-compose &> /dev/null; then - COMPOSE_COMMAND="podman-compose" - print_success "Using podman-compose from PATH: $(command -v podman-compose)" + if [ "$USE_DOCKER" = true ]; then + # Use system docker-compose + if [[ -x "/usr/bin/docker-compose" ]]; then + COMPOSE_COMMAND="/usr/bin/docker-compose" + print_success "Using system Docker Compose: /usr/bin/docker-compose" + elif command -v docker-compose &> /dev/null; then + COMPOSE_COMMAND="docker-compose" + print_success "Using Docker Compose from PATH: $(command -v docker-compose)" + else + exit_with_error "Docker Compose is not installed. + + HOW TO FIX: + Option 1 - Using pip: pip3 install docker-compose + Option 2 - Using package manager: + Ubuntu/Debian: sudo apt install docker-compose + Fedora: sudo dnf install docker-compose + OpenSUSE: sudo zypper install docker-compose + Arch Linux: sudo pacman -S docker-compose + + Or visit: https://docs.docker.com/compose/install/" + fi else - exit_with_error "podman-compose is not installed. + # Use system podman-compose, not the one in ~/.local/bin which might be broken + if [[ -x "/usr/bin/podman-compose" ]]; then + COMPOSE_COMMAND="/usr/bin/podman-compose" + print_success "Using system Podman Compose: /usr/bin/podman-compose" + elif command -v podman-compose &> /dev/null; then + COMPOSE_COMMAND="podman-compose" + print_success "Using Podman Compose from PATH: $(command -v podman-compose)" + else + exit_with_error "Podman Compose is not installed. HOW TO FIX: Option 1 - Using pip: pip3 install podman-compose @@ -377,8 +473,9 @@ function check_requirements() { Arch Linux: sudo pacman -S podman-compose Or visit: https://github.com/containers/podman-compose" + fi fi - # Check if python-dotenv is installed (dependency of podman-compose) + # Check if python-dotenv is installed (dependency of compose command) if ! command -v $PYTHON_CMD &> /dev/null; then exit_with_error "$PYTHON_CMD command not found. Please install Python 3." fi @@ -452,7 +549,11 @@ function check_requirements() { fi COMPOSE_VERSION=$($COMPOSE_COMMAND --version) - print_success "podman-compose is installed: $COMPOSE_VERSION" + if [ "$USE_DOCKER" = true ]; then + print_success "Docker Compose is installed: $COMPOSE_VERSION" + else + print_success "Podman Compose is installed: $COMPOSE_VERSION" + fi # Check if FreeRDP is available print_info "Checking if FreeRDP is available" @@ -581,125 +682,177 @@ function check_requirements() { fi print_success "subUID/subGID mappings verified." - # Check Podman storage configuration and whether overlay storage driver is used, as some users had problems here - print_info "Checking Podman storage configuration" - driver=$(podman info --format '{{.Store.GraphDriverName}}' 2>/dev/null) - error_msg="Podman is not using overlay storage driver or there's a configuration issue. - HOW TO FIX: - - mkdir -p ~/.config/containers - - cat > ~/.config/containers/storage.conf </dev/null) + error_msg="Podman is not using overlay storage driver or there's a configuration issue. + HOW TO FIX: + + mkdir -p ~/.config/containers + + cat > ~/.config/containers/storage.conf < ~/.config/containers/storage.conf < ~/.config/containers/storage.conf </dev/null) - if [ -n "$running_containers" ] && ! [[ "$running_containers" == "LinOffice" ]]; then - # Multiple or non-LinOffice: Prompt user - echo "PROMPT:PODMAN_MIGRATE" - while true; do - read -p "WARNING: podman system migrate will stop ALL running containers and reconfigure storage. This may disrupt other workloads. Continue? (y/n): " -n 1 -r - echo - if [[ $REPLY =~ ^[Yy]$ ]]; then - break - elif [[ $REPLY =~ ^[Nn]$ ]] || [ -z "$REPLY" ]; then - exit_with_error "Setup aborted by user. Please run the script again when ready to migrate." - fi - done - fi - - podman system migrate - new_driver=$(podman info --format '{{.Store.GraphDriverName}}' 2>/dev/null) - if ! echo "$new_driver" | grep -qE "overlay|btrfs"; then - exit_with_error "$error_msg" - fi - else - exit_with_error "$error_msg" - fi + + # Check running containers before migrate + running_containers=$($CONTAINER_ENGINE ps --format '{{.Names}}' 2>/dev/null) + if [ -n "$running_containers" ] && ! [[ "$running_containers" == "LinOffice" ]]; then + # Multiple or non-LinOffice: Prompt user + echo "PROMPT:PODMAN_MIGRATE" + while true; do + read -p "WARNING: podman system migrate will stop ALL running containers and reconfigure storage. This may disrupt other workloads. Continue? (y/n): " -n 1 -r + echo + if [[ $REPLY =~ ^[Yy]$ ]]; then + break + elif [[ $REPLY =~ ^[Nn]$ ]] || [ -z "$REPLY" ]; then + exit_with_error "Setup aborted by user. Please run the script again when ready to migrate." + fi + done + fi + + podman system migrate + new_driver=$(podman info --format '{{.Store.GraphDriverName}}' 2>/dev/null) + if ! echo "$new_driver" | grep -qE "overlay|btrfs"; then + exit_with_error "$error_msg" + fi + else + exit_with_error "$error_msg" + fi + fi fi # Determine if running rootless or rootful - if podman info --format '{{.Host.Security.Rootless}}' | grep -q true; then - IS_ROOTLESS=true - STORAGE_DIR="$HOME/.local/share/containers/storage" - else + if [ "$USE_DOCKER" = true ]; then + # Docker doesn't have rootless/rootful concept like Podman IS_ROOTLESS=false - STORAGE_DIR="/var/lib/containers/storage" - fi - print_info "Podman running in $( $IS_ROOTLESS && echo 'rootless' || echo 'rootful' ) mode" - - # Set network directory paths based on rootless status first - if [ "$IS_ROOTLESS" = true ]; then - NETAVARK_DIR="$HOME/.local/share/containers/networks" - CNI_DIR="$HOME/.config/cni/net.d" + STORAGE_DIR="/var/lib/docker" + print_info "Docker storage directory: $STORAGE_DIR" else - NETAVARK_DIR="/var/lib/containers/networks" - CNI_DIR="/etc/cni/net.d" + if podman info --format '{{.Host.Security.Rootless}}' | grep -q true; then + IS_ROOTLESS=true + STORAGE_DIR="$HOME/.local/share/containers/storage" + else + IS_ROOTLESS=false + STORAGE_DIR="/var/lib/containers/storage" + fi + print_info "Podman running in $( $IS_ROOTLESS && echo 'rootless' || echo 'rootful' ) mode" fi - # Now select the correct directory based on the network backend - if [ "$NETWORK_BACKEND" = "netavark" ]; then - NETWORK_DIR="$NETAVARK_DIR" + # Set network directory paths based on container engine + if [ "$USE_DOCKER" = true ]; then + # Docker uses different network directory structure + NETWORK_DIR="/var/lib/docker/network" + print_info "Docker network directory: $NETWORK_DIR" else - # Default to CNI backend - NETWORK_DIR="$CNI_DIR" + # Set network directory paths based on rootless status first + if [ "$IS_ROOTLESS" = true ]; then + NETAVARK_DIR="$HOME/.local/share/containers/networks" + CNI_DIR="$HOME/.config/cni/net.d" + else + NETAVARK_DIR="/var/lib/containers/networks" + CNI_DIR="/etc/cni/net.d" + fi + + # Now select the correct directory based on the network backend + if [ "$NETWORK_BACKEND" = "netavark" ]; then + NETWORK_DIR="$NETAVARK_DIR" + else + # Default to CNI backend + NETWORK_DIR="$CNI_DIR" + fi + print_info "Podman network directory: $NETWORK_DIR" fi - print_info "Podman network directory: $NETWORK_DIR" # Check if storage directory is accessible if [ ! -d "$STORAGE_DIR" ] || [ ! -w "$STORAGE_DIR" ]; then - exit_with_error "Podman storage directory inaccessible: $STORAGE_DIR - - 1. Check if directory exists: ls -ld \"$STORAGE_DIR\" - 2. If it exists, fix permissions: $( $IS_ROOTLESS && echo "chmod -R u+rwX \"$STORAGE_DIR\"" || echo "sudo chmod -R u+rwX \"$STORAGE_DIR\"" ) - 3. If it does not exist, initialize Podman: podman info" + if [ "$USE_DOCKER" = true ]; then + exit_with_error "Docker storage directory inaccessible: $STORAGE_DIR + + 1. Check if directory exists: ls -ld \"$STORAGE_DIR\" + 2. If it exists, fix permissions: sudo chmod -R u+rwX \"$STORAGE_DIR\" + 3. If it does not exist, initialize Docker: docker info" + else + exit_with_error "Podman storage directory inaccessible: $STORAGE_DIR + + 1. Check if directory exists: ls -ld \"$STORAGE_DIR\" + 2. If it exists, fix permissions: $( $IS_ROOTLESS && echo "chmod -R u+rwX \"$STORAGE_DIR\"" || echo "sudo chmod -R u+rwX \"$STORAGE_DIR\"" ) + 3. If it does not exist, initialize Podman: podman info" + fi + fi + if [ "$USE_DOCKER" = true ]; then + print_success "Docker storage directory verified: $STORAGE_DIR" + else + print_success "Podman storage directory verified: $STORAGE_DIR" fi - print_success "Podman storage directory verified: $STORAGE_DIR" # Check which networking backend is in use - print_info "Checking Podman networking is working" - NETWORK_BACKEND=$(podman info --format '{{.Host.NetworkBackend}}' 2>/dev/null) - if [ -z "$NETWORK_BACKEND" ]; then - exit_with_error "Failed to detect Podman's network backend. Make sure Podman is correctly installed and accessible to your user. Run 'podman info' to diagnose." + if [ "$USE_DOCKER" = true ]; then + print_info "Checking Docker networking is working" + # Docker doesn't have network backend concept like Podman + print_success "Docker networking is handled automatically" + else + print_info "Checking Podman networking is working" + NETWORK_BACKEND=$(podman info --format '{{.Host.NetworkBackend}}' 2>/dev/null) + if [ -z "$NETWORK_BACKEND" ]; then + exit_with_error "Failed to detect Podman's network backend. Make sure Podman is correctly installed and accessible to your user. Run 'podman info' to diagnose." + fi + print_info "Podman is using network backend: $NETWORK_BACKEND" fi - print_info "Podman is using network backend: $NETWORK_BACKEND" # Test network creation for all backends TEST_NET_NAME="linoffice_net_test_$(date +%s)" - print_info "Testing network creation with backend: $NETWORK_BACKEND" - if ! podman network create "$TEST_NET_NAME" >/dev/null 2>&1; then - exit_with_error "Failed to create test network '$TEST_NET_NAME'. - - HOW TO FIX: - 1. Check Podman logs: journalctl -u podman - 2. $( $IS_ROOTLESS && echo 'Ensure user has sufficient permissions.' || echo 'Run as root or check sudo permissions.' ) - 3. Reinstall network backend: - - For netavark: $( $IS_ROOTLESS && echo 'podman system reset && podman info' || echo 'sudo dnf reinstall netavark || sudo apt install netavark' ) - - For CNI: Ensure CNI plugins are installed (e.g., sudo dnf install containernetworking-plugins) - 4. Verify SELinux/AppArmor settings if enabled." + if [ "$USE_DOCKER" = true ]; then + print_info "Testing Docker network creation" + if ! docker network create "$TEST_NET_NAME" >/dev/null 2>&1; then + exit_with_error "Failed to create test network '$TEST_NET_NAME'. + + HOW TO FIX: + 1. Check Docker logs: journalctl -u docker + 2. Ensure user has sufficient permissions (add to docker group) + 3. Restart Docker service: sudo systemctl restart docker + 4. If it does not exist, initialize Docker: docker info" + fi + print_success "Docker test network '$TEST_NET_NAME' created successfully." + else + print_info "Testing network creation with backend: $NETWORK_BACKEND" + if ! podman network create "$TEST_NET_NAME" >/dev/null 2>&1; then + exit_with_error "Failed to create test network '$TEST_NET_NAME'. + + HOW TO FIX: + 1. Check Podman logs: journalctl -u podman + 2. $( $IS_ROOTLESS && echo 'Ensure user has sufficient permissions.' || echo 'Run as root or check sudo permissions.' ) + 3. Reinstall network backend: + - For netavark: $( $IS_ROOTLESS && echo 'podman system reset && podman info' || echo 'sudo dnf reinstall netavark || sudo apt install netavark' ) + - For CNI: Ensure CNI plugins are installed (e.g., sudo dnf install containernetworking-plugins) + 4. Verify SELinux/AppArmor settings if enabled." + fi + print_success "Test network '$TEST_NET_NAME' created successfully." fi - print_success "Test network '$TEST_NET_NAME' created successfully." # Check that network directory exists if [ ! -d "$NETWORK_DIR" ]; then @@ -712,23 +865,46 @@ EOF fi # Clean up test network - if podman network exists "$TEST_NET_NAME" >/dev/null 2>&1; then - podman network rm "$TEST_NET_NAME" >/dev/null 2>&1 || print_info "Note: Failed to remove test network '$TEST_NET_NAME', you may remove it manually." + if [ "$USE_DOCKER" = true ]; then + if docker network ls --format '{{.Name}}' | grep -q "^$TEST_NET_NAME$"; then + docker network rm "$TEST_NET_NAME" >/dev/null 2>&1 || print_info "Note: Failed to remove test network '$TEST_NET_NAME', you may remove it manually." + fi + else + if podman network exists "$TEST_NET_NAME" >/dev/null 2>&1; then + podman network rm "$TEST_NET_NAME" >/dev/null 2>&1 || print_info "Note: Failed to remove test network '$TEST_NET_NAME', you may remove it manually." + fi + fi + if [ "$USE_DOCKER" = true ]; then + print_success "Docker networking check completed." + else + print_success "Podman networking check completed." fi - print_success "Podman networking check completed." # Test basic container creation to catch storage issues early print_info "Testing basic container functionality..." - if ! timeout 60 podman run --rm alpine:latest echo "test" >/dev/null 2>&1; then - exit_with_error "Basic container test failed. This could indicate storage driver issues. - - HOW TO FIX: - 1. Check Podman logs: journalctl --user -u podman - 2. Try: podman system reset (WARNING: removes all containers/images) - 3. Ensure /run/containers and storage directories have correct permissions - 4. Check if your filesystem supports overlay mounts" + if [ "$USE_DOCKER" = true ]; then + if ! timeout 60 docker run --rm alpine:latest echo "test" >/dev/null 2>&1; then + exit_with_error "Basic container test failed. This could indicate Docker issues. + + HOW TO FIX: + 1. Check Docker logs: journalctl -u docker + 2. Try: docker system prune (WARNING: removes unused containers/images) + 3. Ensure Docker daemon is running: sudo systemctl start docker + 4. Check if your filesystem supports overlay mounts" + fi + print_success "Docker test container created and removed successfully." + else + if ! timeout 60 podman run --rm alpine:latest echo "test" >/dev/null 2>&1; then + exit_with_error "Basic container test failed. This could indicate storage driver issues. + + HOW TO FIX: + 1. Check Podman logs: journalctl --user -u podman + 2. Try: podman system reset (WARNING: removes all containers/images) + 3. Ensure /run/containers and storage directories have correct permissions + 4. Check if your filesystem supports overlay mounts" + fi + print_success "Podman test container created and removed successfully." fi - print_success "Podman test container created and removed successfully." # Check connectivity to microsoft.com print_info "Checking connectivity to Microsoft" @@ -803,17 +979,30 @@ function create_container() { local download_finished=false local install_started=false local timeout_counter=0 - local max_timeout=3600 # 60 minutes maximum wait time between podman-compose log output + local max_timeout=3600 # 60 minutes maximum wait time between compose log output local last_activity_time=$(date +%s) local windows_version="" - # Start podman-compose in the background with unbuffered output and strip ANSI codes - print_info "Starting podman-compose in detached mode..." + # Start compose command in the background with unbuffered output and strip ANSI codes + if [ "$USE_DOCKER" = true ]; then + print_info "Starting Docker Compose in detached mode..." + else + print_info "Starting Podman Compose in detached mode..." + fi # If the compose file doesn't exist yet, initialize it from the default template if [ ! -f "$COMPOSE_FILE" ]; then if [ -f "$COMPOSE_FILE.default" ]; then print_info "Creating $COMPOSE_FILE from default template" cp "$COMPOSE_FILE.default" "$COMPOSE_FILE" || exit_with_error "Failed to copy $COMPOSE_FILE.default to $COMPOSE_FILE" + sed -i 's/:Z//g; s/:z//g' "$COMPOSE_FILE" + sed -i '/^[[:space:]]*annotations:/,/^[[:space:]]*[^[:space:]]/{ +/^[[:space:]]*annotations:/d +/^[[:space:]]*run\.oci\.keep_original_groups:/d +}' "$COMPOSE_FILE" + sed -i '/^[[:space:]]*group_add:/,/^[[:space:]]*[^[:space:]]/{ +/^[[:space:]]*group_add:/d +/^[[:space:]]*-[[:space:]]*keep-groups/d +}' "$COMPOSE_FILE" else exit_with_error "Compose file missing: $COMPOSE_FILE and $COMPOSE_FILE.default not found" fi @@ -824,12 +1013,12 @@ function create_container() { # Check if container was actually created sleep 5 - if ! podman ps -a --filter "name=$CONTAINER_NAME" --format "{{.Names}}" | grep -q "^$CONTAINER_NAME$"; then + if ! $CONTAINER_ENGINE ps -a --filter "name=$CONTAINER_NAME" --format "{{.Names}}" | grep -q "^$CONTAINER_NAME$"; then exit_with_error "Container $CONTAINER_NAME was not created successfully. Check $LOGFILE for detailed error messages." fi print_info "Tailing logs from container: $CONTAINER_NAME" - podman logs -f --timestamps "$CONTAINER_NAME" 2>&1 | \ + $CONTAINER_ENGINE logs -f --timestamps "$CONTAINER_NAME" 2>&1 | \ stdbuf -oL -eL sed -u 's/\x1b\[[0-9;]*m//g' >> "$LOGFILE" & log_pid=$! @@ -947,7 +1136,7 @@ function create_container() { # Then check success/failure if [ "$result" -eq 0 ]; then sleep 5 - if ! podman ps -q --filter "name=$CONTAINER_NAME" | grep -q .; then + if ! $CONTAINER_ENGINE ps -q --filter "name=$CONTAINER_NAME" | grep -q .; then exit_with_error "Container setup completed but container is not running. Check $LOGFILE for details." else print_success "Container setup completed successfully" @@ -973,7 +1162,7 @@ function verify_container_health() { fi # Check if container is running, otherwise start it - if ! podman ps -q --filter "name=$CONTAINER_NAME" | grep -q .; then + if ! $CONTAINER_ENGINE ps -q --filter "name=$CONTAINER_NAME" | grep -q .; then print_info "Container is not running. Attempting to start it..." if ! $COMPOSE_COMMAND --file "$COMPOSE_FILE" start; then print_error "Failed to start container" @@ -1681,7 +1870,7 @@ fi if ! check_progress "$PROGRESS_OFFICE" || [ "$FIRSTRUN" = true ]; then # If --firstrun, ensure the container is running before checking RDP if [ "$FIRSTRUN" = true ]; then - if ! podman ps -q --filter "name=$CONTAINER_NAME" | grep -q .; then + if ! $CONTAINER_ENGINE ps -q --filter "name=$CONTAINER_NAME" | grep -q .; then print_info "Container is not running. Starting LinOffice container for --firstrun..." if ! $COMPOSE_COMMAND --file "$COMPOSE_FILE" start; then exit_with_error "Failed to start LinOffice container for --firstrun." diff --git a/uninstall.sh b/uninstall.sh index cf43e4e..4461d65 100755 --- a/uninstall.sh +++ b/uninstall.sh @@ -215,70 +215,53 @@ else fi # Ask to delete the Windows container and its data +# Detect container engine (Docker or Podman) +ENGINE_FILE="$SCRIPT_DIR/gui/engine.txt" +if [[ -f "$ENGINE_FILE" ]]; then + ENGINE=$(cat "$ENGINE_FILE" | tr -d '[:space:]') +else + ENGINE="podman" +fi + +# Ask to delete the container and its data read -p "Do you want to delete the Windows container and all its data as well? (y/n): " confirm if [[ "$confirm" == "y" || "$confirm" == "Y" ]]; then - if ! command -v podman &> /dev/null; then - echo "Error: Podman is not installed or not accessible." + if ! command -v "$ENGINE" &> /dev/null; then + echo "Error: $ENGINE is not installed or not accessible." else - # Stop and remove the LinOffice container - # Check the current status of the container - CONTAINER_STATUS=$(podman inspect --format='{{.State.Status}}' "$CONTAINER_NAME" 2>/dev/null) - - # Ensure COMPOSE_COMMAND is set to a working value - # First try the system podman-compose if it exists and is executable - if [[ -x "/usr/bin/podman-compose" ]]; then - COMPOSE_COMMAND="/usr/bin/podman-compose" - elif command -v podman-compose &>/dev/null; then - COMPOSE_COMMAND="podman-compose" - else - echo "ERROR: No working podman-compose found" - fi + CONTAINER_STATUS=$($ENGINE inspect --format='{{.State.Status}}' "$CONTAINER_NAME" 2>/dev/null) - # If the container is paused, it must be un-paused first to shut down cleanly if [[ "$CONTAINER_STATUS" == "paused" ]]; then - echo "Container is paused, unpausing to allow clean shutdown..." - if [[ -n "$COMPOSE_COMMAND" ]]; then - "$COMPOSE_COMMAND" --file "$COMPOSE_PATH" unpause &>/dev/null - else - echo "Skipping podman-compose unpause: no podman-compose available." - fi - sleep 2 # Give it a moment to wake up before stopping + echo "Container is paused, unpausing..." + $ENGINE unpause "$CONTAINER_NAME" &>/dev/null + sleep 2 fi - # Now, if the container is running (or was just un-paused), stop it if [[ "$CONTAINER_STATUS" == "running" || "$CONTAINER_STATUS" == "paused" ]]; then - echo "Sending stop command... (this may take up to 2 minutes)" - podman stop "$CONTAINER_NAME" &>/dev/null + echo "Stopping container..." + $ENGINE stop "$CONTAINER_NAME" &>/dev/null else - echo "Container is not running." + echo "Container is not running." fi - echo "Cleaning up all resources..." - # Finally, run 'down' to ensure the stopped container is fully removed. - if [[ -n "$COMPOSE_COMMAND" ]]; then - "$COMPOSE_COMMAND" --file "$COMPOSE_PATH" down --remove-orphans &>/dev/null - else - echo "Skipping podman-compose down: no podman-compose available." - fi + echo "Removing container..." + $ENGINE rm -f "$CONTAINER_NAME" &>/dev/null || echo "Error: Could not delete container." - # Finally, delete the container - if ! podman rm -f "$CONTAINER_NAME" &> /dev/null; then - echo "Error: Could not delete the Podman container." + echo "Removing associated data..." + if [[ "$ENGINE" == "podman" ]]; then + $ENGINE volume rm linoffice_data &>/dev/null || true else - echo "Deleted "$CONTAINER_NAME" container." + $ENGINE volume prune -f &>/dev/null + $ENGINE system prune -a -f &>/dev/null fi - # Remove the linoffice_data volume - if ! podman volume rm linoffice_data &> /dev/null; then - echo "Error: Could not delete the linoffice_data volume." - else - echo "Deleted linoffice_data volume." - fi + echo "Cleanup completed for $ENGINE." fi else echo "Windows container and data deletion aborted." fi + # Check if APPDATA_PATH exists and delete if it does if [[ -d "$APPDATA_PATH" ]]; then read -p "Do you want to delete the app data in $APPDATA_PATH? (y/n): " confirm