1313
1414class Config :
1515 """Configuration manager for miniupdate."""
16-
16+
1717 def __init__ (self , config_path : Optional [str ] = None ):
1818 """
1919 Initialize configuration manager.
20-
20+
2121 Args:
2222 config_path: Path to configuration file. If None, looks for config.toml
2323 in current directory or ~/.miniupdate/config.toml
2424 """
2525 self .config_path = self ._find_config_path (config_path )
2626 self .config = self ._load_config ()
27-
27+
2828 def _find_config_path (self , config_path : Optional [str ]) -> Path :
2929 """Find configuration file path."""
3030 if config_path :
3131 return Path (config_path )
32-
32+
3333 # Check current directory
3434 current_config = Path ("config.toml" )
3535 if current_config .exists ():
3636 return current_config
37-
37+
3838 # Check home directory
3939 home_config = Path .home () / ".miniupdate" / "config.toml"
4040 if home_config .exists ():
4141 return home_config
42-
42+
4343 # Default to current directory config.toml (may not exist yet)
4444 return current_config
45-
45+
4646 def _load_config (self ) -> Dict [str , Any ]:
4747 """Load configuration from TOML file."""
4848 if not self .config_path .exists ():
4949 raise FileNotFoundError (
5050 f"Configuration file not found at { self .config_path } . "
5151 f"Please create a config.toml file or see config.toml.example"
5252 )
53-
53+
5454 try :
55- with open (self .config_path , 'r' , encoding = ' utf-8' ) as f :
55+ with open (self .config_path , "r" , encoding = " utf-8" ) as f :
5656 return toml .load (f )
5757 except Exception as e :
5858 raise ValueError (f"Error parsing configuration file: { e } " )
59-
59+
6060 @property
6161 def smtp_config (self ) -> Dict [str , Any ]:
6262 """Get SMTP configuration."""
63- if ' email' not in self .config :
63+ if " email" not in self .config :
6464 raise ValueError ("No [email] section found in configuration" )
65-
66- email_config = self .config [' email' ]
67- required_fields = [' smtp_server' , ' smtp_port' , ' from_email' , ' to_email' ]
68-
65+
66+ email_config = self .config [" email" ]
67+ required_fields = [" smtp_server" , " smtp_port" , " from_email" , " to_email" ]
68+
6969 for field in required_fields :
7070 if field not in email_config :
7171 raise ValueError (f"Missing required email configuration: { field } " )
72-
72+
7373 return email_config
74-
74+
7575 @property
7676 def inventory_path (self ) -> str :
7777 """Get Ansible inventory path with environment variable expansion."""
78- if ' inventory' not in self .config :
78+ if " inventory" not in self .config :
7979 raise ValueError ("No [inventory] section found in configuration" )
80-
81- inventory_config = self .config [' inventory' ]
82- if ' path' not in inventory_config :
80+
81+ inventory_config = self .config [" inventory" ]
82+ if " path" not in inventory_config :
8383 raise ValueError ("Missing required inventory path" )
84-
85- raw_path = inventory_config [' path' ]
86-
84+
85+ raw_path = inventory_config [" path" ]
86+
8787 # Expand environment variables
8888 expanded_path = os .path .expandvars (raw_path )
89-
89+
9090 # Expand user home directory (~)
9191 expanded_path = os .path .expanduser (expanded_path )
92-
92+
9393 # Convert to absolute path if it's relative (unless it's already absolute)
9494 path_obj = Path (expanded_path )
9595 if not path_obj .is_absolute ():
9696 # Make relative to the config file directory if config is not in current dir
9797 if self .config_path .parent != Path .cwd ():
9898 path_obj = self .config_path .parent / path_obj
99-
99+
100100 return str (path_obj )
101-
101+
102102 @property
103103 def ssh_config (self ) -> Dict [str , Any ]:
104104 """Get SSH configuration."""
105- return self .config .get (' ssh' , {})
106-
105+ return self .config .get (" ssh" , {})
106+
107107 @property
108108 def proxmox_config (self ) -> Dict [str , Any ]:
109109 """Get Proxmox configuration."""
110- if ' proxmox' not in self .config :
110+ if " proxmox" not in self .config :
111111 return {}
112- return self .config [' proxmox' ]
113-
112+ return self .config [" proxmox" ]
113+
114114 @property
115115 def update_config (self ) -> Dict [str , Any ]:
116116 """Get update automation configuration."""
117- if ' updates' not in self .config :
117+ if " updates" not in self .config :
118118 return {}
119- return self .config [' updates' ]
120-
119+ return self .config [" updates" ]
120+
121121 @property
122122 def update_opt_out_hosts (self ) -> List [str ]:
123123 """Get list of hosts that should not receive automatic updates."""
124124 update_config = self .update_config
125- return update_config .get (' opt_out_hosts' , [])
126-
125+ return update_config .get (" opt_out_hosts" , [])
126+
127127 def get (self , key : str , default : Any = None ) -> Any :
128128 """Get configuration value by key."""
129129 return self .config .get (key , default )
@@ -139,58 +139,48 @@ def create_example_config(path: str = "config.toml.example") -> None:
139139140140 "password" : "your-app-password" ,
141141 "from_email" :
"[email protected] " ,
142- 142+ 143143 },
144144 "inventory" : {
145145 # Local inventory file (relative to config file)
146146 "path" : "inventory.yml" ,
147-
148147 # Alternative examples (uncomment one):
149148 # Absolute path
150149 # "path": "/etc/ansible/inventory.yml",
151-
152150 # Path using environment variable
153151 # "path": "$ANSIBLE_INVENTORY_PATH/inventory.yml",
154-
155152 # Path to external git repository
156153 # "path": "~/git/infrastructure/ansible/inventory.yml",
157-
158154 # Corporate shared inventory
159155 # "path": "/shared/ansible-configs/production/inventory.yml",
160-
161- "format" : "ansible"
162- },
163- "ssh" : {
164- "timeout" : 30 ,
165- "key_file" : None ,
166- "username" : None ,
167- "port" : 22
156+ "format" : "ansible" ,
168157 },
158+ "ssh" : {"timeout" : 30 , "key_file" : None , "username" : None , "port" : 22 },
169159 "settings" : {
170160 "parallel_connections" : 5 ,
171161 "log_level" : "INFO" ,
172- "check_timeout" : 120
162+ "check_timeout" : 120 ,
173163 },
174164 "proxmox" : {
175165 "endpoint" : "https://pve.example.com:8006" ,
176166 "username" : "root@pam" ,
177167 "password" : "your-proxmox-password" ,
178168 "verify_ssl" : True ,
179169 "timeout" : 30 ,
180- "vm_mapping_file" : "vm_mapping.toml"
170+ "vm_mapping_file" : "vm_mapping.toml" ,
181171 },
182172 "updates" : {
183173 "apply_updates" : True ,
184174 "reboot_after_updates" : True ,
185175 "reboot_timeout" : 300 , # 5 minutes
186- "ping_timeout" : 120 , # 2 minutes for ping check after reboot
187- "ping_interval" : 5 , # Check every 5 seconds
176+ "ping_timeout" : 120 , # 2 minutes for ping check after reboot
177+ "ping_interval" : 5 , # Check every 5 seconds
188178 "snapshot_name_prefix" : "pre-update" ,
189179 "cleanup_snapshots" : True ,
190180 "snapshot_retention_days" : 7 ,
191- "opt_out_hosts" : [] # List of hosts to exclude from automatic updates (check-only mode)
192- }
181+ "opt_out_hosts" : [], # List of hosts to exclude from automatic updates (check-only mode)
182+ },
193183 }
194-
195- with open (path , 'w' , encoding = ' utf-8' ) as f :
196- toml .dump (example_config , f )
184+
185+ with open (path , "w" , encoding = " utf-8" ) as f :
186+ toml .dump (example_config , f )
0 commit comments