11import shutil
22import platform
33import os
4- from typing import List , Optional , Union
4+ from typing import List , Optional , Union , Dict
55import subprocess
66
77from ..executor import Executor
1010from .apt_tools import apt_install , ensure_apt_repo
1111from .user_mgmt import add_user_to_group
1212
13+ def _get_os_release () -> Dict [str , str ]:
14+ """
15+ Parses /etc/os-release into a dictionary natively.
16+ Replaces brittle shell 'grep/cut/tr' with robust Python string matching.
17+ """
18+ info = {}
19+ try :
20+ if os .path .exists ("/etc/os-release" ):
21+ with open ("/etc/os-release" ) as f :
22+ for line in f :
23+ if "=" in line :
24+ key , value = line .rstrip ().split ("=" , 1 )
25+ # Remove surrounding quotes often found in these files
26+ info [key ] = value .strip ('"' )
27+ except Exception as e :
28+ log .error (f"Failed to read /etc/os-release: { e } " )
29+ return info
30+
1331def _remove_old_docker (exec_obj : Executor ) -> None :
1432 """
1533 Removes old, conflicting, or manually installed Docker packages
@@ -48,7 +66,12 @@ def _remove_old_docker(exec_obj: Executor) -> None:
4866def install_docker_and_add_users (exec_obj : Executor , * users_to_add : str ) -> None :
4967 """
5068 Installs Docker packages, starts the service, and adds users to the 'docker' group.
69+ Supports both Ubuntu and Debian automatically.
5170 """
71+ # Safeguard for macOS
72+ if platform .system ().lower () == "darwin" :
73+ log .warning ("Docker Engine installation is not supported on macOS via this orchestrator." )
74+ return
5275
5376 if shutil .which ("docker" ):
5477 log .success ("Docker binary detected, skipping installation steps." )
@@ -62,32 +85,33 @@ def install_docker_and_add_users(exec_obj: Executor, *users_to_add: str) -> None
6285 # DOCKER_DEPS includes ca-certificates and lsb-release
6386 apt_install (exec_obj , DOCKER_DEPS )
6487
88+ # 1. Detect OS details using Python dictionary matching
89+ os_info = _get_os_release ()
90+ os_id = os_info .get ("ID" ) # e.g., 'ubuntu' or 'debian'
91+ codename = os_info .get ("VERSION_CODENAME" ) # e.g., 'noble' or 'bookworm'
92+
93+ if not os_id or not codename :
94+ log .critical ("Could not detect OS ID or Codename from /etc/os-release. Aborting Docker setup." )
95+ raise RuntimeError ("Cannot proceed without distribution details." )
96+
97+ log .info (f"Detected OS: { os_id } , Codename: { codename } " )
98+
6599 keyrings_dir = "/etc/apt/keyrings"
66- docker_gpg_path = os .path .join (keyrings_dir , "docker.asc " ) # Official script uses docker.asc
100+ docker_gpg_path = os .path .join (keyrings_dir , "docker.gpg " ) # Modern standard uses .gpg binary
67101 list_file = "/etc/apt/sources.list.d/docker.list"
68102
69103 exec_obj .run (f"mkdir -p { keyrings_dir } " , force_sudo = True )
70104
71105 if not os .path .exists (docker_gpg_path ):
72- log .info ("Downloading and adding Docker GPG key." )
73- # Note: We use docker.asc file name for consistency with Docker's script output
74- curl_cmd = f"curl -fsSL https://download.docker.com/linux/ubuntu /gpg | gpg --dearmor -o { docker_gpg_path } "
106+ log .info (f "Downloading and adding Docker GPG key for { os_id } ." )
107+ # Note: We use gpg --dearmor to ensure a binary .gpg file for /etc/apt/keyrings compatibility
108+ curl_cmd = f"curl -fsSL https://download.docker.com/linux/{ os_id } /gpg | gpg --dearmor -o { docker_gpg_path } "
75109 exec_obj .run (curl_cmd , force_sudo = True )
76110 # Ensure proper read permissions for apt
77111 exec_obj .run (f"chmod a+r { docker_gpg_path } " , force_sudo = True )
78112 else :
79113 log .info ("Docker GPG key already exists." )
80114
81- # 1. Get Distribution Codename robustly
82- try :
83- result = exec_obj .run ("lsb_release -cs" , check = True , run_quiet = True )
84- codename = result .stdout .strip ()
85- log .info (f"Detected distribution codename: { codename } " )
86- except Exception as e :
87- log .critical ("Failed to determine Linux distribution codename (lsb_release -cs failed). Aborting Docker setup." )
88- log .debug (f"lsb_release error: { e } " )
89- raise RuntimeError ("Cannot proceed without distribution codename." )
90-
91115 arch = platform .machine ()
92116
93117 # --- FIX: Architecture Correction (aarch64 -> arm64) ---
@@ -96,11 +120,9 @@ def install_docker_and_add_users(exec_obj: Executor, *users_to_add: str) -> None
96120 display_arch = 'arm64'
97121 else :
98122 display_arch = arch
99-
100- effective_codename = codename # Trust the detected codename
101123
102- # 2. Interpolate the correct codename and arch into the repository line
103- repo_line = f"deb [arch={ display_arch } signed-by={ docker_gpg_path } ] https://download.docker.com/linux/ubuntu { effective_codename } stable"
124+ # 2. Interpolate the correct ID, codename and arch into the repository line
125+ repo_line = f"deb [arch={ display_arch } signed-by={ docker_gpg_path } ] https://download.docker.com/linux/{ os_id } { codename } stable"
104126
105127 log .info (f"Using APT repository line: { repo_line } " )
106128 ensure_apt_repo (exec_obj , list_file , repo_line )
0 commit comments