atomicshop 3.3.8__py3-none-any.whl → 3.10.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of atomicshop might be problematic. Click here for more details.

Files changed (120) hide show
  1. atomicshop/__init__.py +1 -1
  2. atomicshop/a_mains/get_local_tcp_ports.py +85 -0
  3. atomicshop/a_mains/install_ca_certificate.py +172 -0
  4. atomicshop/a_mains/process_from_port.py +119 -0
  5. atomicshop/a_mains/set_default_dns_gateway.py +90 -0
  6. atomicshop/basics/strings.py +1 -1
  7. atomicshop/certificates.py +2 -2
  8. atomicshop/dns.py +26 -28
  9. atomicshop/etws/traces/trace_tcp.py +1 -2
  10. atomicshop/mitm/centered_settings.py +133 -0
  11. atomicshop/mitm/config_static.py +22 -44
  12. atomicshop/mitm/connection_thread_worker.py +383 -165
  13. atomicshop/mitm/engines/__parent/recorder___parent.py +1 -1
  14. atomicshop/mitm/engines/__parent/requester___parent.py +1 -1
  15. atomicshop/mitm/engines/__parent/responder___parent.py +15 -2
  16. atomicshop/mitm/engines/create_module_template.py +1 -2
  17. atomicshop/mitm/import_config.py +91 -89
  18. atomicshop/mitm/initialize_engines.py +1 -2
  19. atomicshop/mitm/message.py +5 -4
  20. atomicshop/mitm/mitm_main.py +238 -122
  21. atomicshop/mitm/recs_files.py +61 -5
  22. atomicshop/mitm/ssh_tester.py +82 -0
  23. atomicshop/mitm/statistic_analyzer.py +33 -12
  24. atomicshop/mitm/statistic_analyzer_helper/moving_average_helper.py +104 -31
  25. atomicshop/networks.py +160 -92
  26. atomicshop/package_mains_processor.py +84 -0
  27. atomicshop/permissions/ubuntu_permissions.py +47 -0
  28. atomicshop/print_api.py +3 -5
  29. atomicshop/process.py +11 -4
  30. atomicshop/python_functions.py +23 -108
  31. atomicshop/speech_recognize.py +8 -0
  32. atomicshop/ssh_remote.py +140 -164
  33. atomicshop/web.py +63 -22
  34. atomicshop/web_apis/google_llm.py +22 -14
  35. atomicshop/wrappers/ctyping/msi_windows_installer/cabs.py +2 -1
  36. atomicshop/wrappers/ctyping/msi_windows_installer/extract_msi_main.py +2 -1
  37. atomicshop/wrappers/dockerw/dockerw.py +2 -2
  38. atomicshop/wrappers/elasticsearchw/config_basic.py +0 -12
  39. atomicshop/wrappers/elasticsearchw/elastic_infra.py +0 -190
  40. atomicshop/wrappers/factw/install/pre_install_and_install_before_restart.py +5 -5
  41. atomicshop/wrappers/githubw.py +180 -68
  42. atomicshop/wrappers/loggingw/consts.py +1 -1
  43. atomicshop/wrappers/loggingw/handlers.py +1 -1
  44. atomicshop/wrappers/loggingw/loggingw.py +20 -4
  45. atomicshop/wrappers/loggingw/reading.py +18 -0
  46. atomicshop/wrappers/mongodbw/mongo_infra.py +0 -38
  47. atomicshop/wrappers/netshw.py +124 -3
  48. atomicshop/wrappers/playwrightw/scenarios.py +1 -1
  49. atomicshop/wrappers/powershell_networking.py +80 -0
  50. atomicshop/wrappers/psutilw/psutil_networks.py +9 -0
  51. atomicshop/wrappers/pywin32w/win_event_log/fetch.py +174 -0
  52. atomicshop/wrappers/pywin32w/win_event_log/subscribes/process_create.py +3 -105
  53. atomicshop/wrappers/pywin32w/win_event_log/subscribes/process_terminate.py +3 -57
  54. atomicshop/wrappers/pywin32w/wmis/win32_networkadapterconfiguration.py +12 -27
  55. atomicshop/wrappers/pywin32w/wmis/win32networkadapter.py +15 -9
  56. atomicshop/wrappers/socketw/certificator.py +19 -9
  57. atomicshop/wrappers/socketw/creator.py +101 -14
  58. atomicshop/wrappers/socketw/dns_server.py +17 -5
  59. atomicshop/wrappers/socketw/exception_wrapper.py +21 -16
  60. atomicshop/wrappers/socketw/process_getter.py +86 -0
  61. atomicshop/wrappers/socketw/receiver.py +29 -9
  62. atomicshop/wrappers/socketw/sender.py +10 -9
  63. atomicshop/wrappers/socketw/sni.py +31 -10
  64. atomicshop/wrappers/socketw/{base.py → socket_base.py} +33 -1
  65. atomicshop/wrappers/socketw/socket_client.py +11 -10
  66. atomicshop/wrappers/socketw/socket_wrapper.py +125 -32
  67. atomicshop/wrappers/socketw/ssl_base.py +6 -2
  68. atomicshop/wrappers/ubuntu_terminal.py +21 -18
  69. atomicshop/wrappers/win_auditw.py +189 -0
  70. {atomicshop-3.3.8.dist-info → atomicshop-3.10.0.dist-info}/METADATA +25 -30
  71. {atomicshop-3.3.8.dist-info → atomicshop-3.10.0.dist-info}/RECORD +83 -109
  72. atomicshop/_basics_temp.py +0 -101
  73. atomicshop/a_installs/ubuntu/docker_rootless.py +0 -11
  74. atomicshop/a_installs/ubuntu/docker_sudo.py +0 -11
  75. atomicshop/a_installs/ubuntu/elastic_search_and_kibana.py +0 -10
  76. atomicshop/a_installs/ubuntu/mongodb.py +0 -12
  77. atomicshop/a_installs/win/fibratus.py +0 -9
  78. atomicshop/a_installs/win/mongodb.py +0 -9
  79. atomicshop/a_installs/win/wsl_ubuntu_lts.py +0 -10
  80. atomicshop/addons/a_setup_scripts/install_psycopg2_ubuntu.sh +0 -3
  81. atomicshop/addons/package_setup/CreateWheel.cmd +0 -7
  82. atomicshop/addons/package_setup/Setup in Edit mode.cmd +0 -6
  83. atomicshop/addons/package_setup/Setup.cmd +0 -7
  84. atomicshop/archiver/__init__.py +0 -0
  85. atomicshop/archiver/_search_in_zip.py +0 -189
  86. atomicshop/archiver/search_in_archive.py +0 -284
  87. atomicshop/archiver/sevenz_app_w.py +0 -86
  88. atomicshop/archiver/sevenzs.py +0 -73
  89. atomicshop/archiver/shutils.py +0 -34
  90. atomicshop/archiver/zips.py +0 -353
  91. atomicshop/file_types.py +0 -24
  92. atomicshop/pbtkmultifile_argparse.py +0 -88
  93. atomicshop/script_as_string_processor.py +0 -42
  94. atomicshop/ssh_scripts/process_from_ipv4.py +0 -37
  95. atomicshop/ssh_scripts/process_from_port.py +0 -27
  96. atomicshop/wrappers/_process_wrapper_curl.py +0 -27
  97. atomicshop/wrappers/_process_wrapper_tar.py +0 -21
  98. atomicshop/wrappers/dockerw/install_docker.py +0 -449
  99. atomicshop/wrappers/elasticsearchw/install_elastic.py +0 -233
  100. atomicshop/wrappers/ffmpegw.py +0 -125
  101. atomicshop/wrappers/fibratusw/__init__.py +0 -0
  102. atomicshop/wrappers/fibratusw/install.py +0 -80
  103. atomicshop/wrappers/mongodbw/install_mongodb_ubuntu.py +0 -100
  104. atomicshop/wrappers/mongodbw/install_mongodb_win.py +0 -244
  105. atomicshop/wrappers/process_wrapper_pbtk.py +0 -16
  106. atomicshop/wrappers/socketw/get_process.py +0 -123
  107. atomicshop/wrappers/wslw.py +0 -192
  108. atomicshop-3.3.8.dist-info/entry_points.txt +0 -2
  109. /atomicshop/{addons → a_mains/addons}/PlayWrightCodegen.cmd +0 -0
  110. /atomicshop/{addons → a_mains/addons}/ScriptExecution.cmd +0 -0
  111. /atomicshop/{addons → a_mains/addons}/inits/init_to_import_all_modules.py +0 -0
  112. /atomicshop/{addons → a_mains/addons}/process_list/ReadMe.txt +0 -0
  113. /atomicshop/{addons → a_mains/addons}/process_list/compile.cmd +0 -0
  114. /atomicshop/{addons → a_mains/addons}/process_list/compiled/Win10x64/process_list.dll +0 -0
  115. /atomicshop/{addons → a_mains/addons}/process_list/compiled/Win10x64/process_list.exp +0 -0
  116. /atomicshop/{addons → a_mains/addons}/process_list/compiled/Win10x64/process_list.lib +0 -0
  117. /atomicshop/{addons → a_mains/addons}/process_list/process_list.cpp +0 -0
  118. {atomicshop-3.3.8.dist-info → atomicshop-3.10.0.dist-info}/WHEEL +0 -0
  119. {atomicshop-3.3.8.dist-info → atomicshop-3.10.0.dist-info}/licenses/LICENSE.txt +0 -0
  120. {atomicshop-3.3.8.dist-info → atomicshop-3.10.0.dist-info}/top_level.txt +0 -0
atomicshop/networks.py CHANGED
@@ -1,18 +1,23 @@
1
1
  import socket
2
2
  import time
3
- from typing import Union
3
+ from typing import Union, Literal
4
4
  import os
5
5
  import psutil
6
6
  import ctypes
7
+ from logging import Logger
8
+ import subprocess
7
9
 
8
10
  from icmplib import ping
9
11
  from icmplib.models import Host
10
12
  from win32com.client import CDispatch
11
13
 
12
- from .wrappers.pywin32w.wmis import win32networkadapter, win32_networkadapterconfiguration, wmi_helpers, msft_netipaddress
14
+ from .print_api import print_api
15
+ from .wrappers.pywin32w.wmis import win32networkadapter, win32_networkadapterconfiguration, wmi_helpers
13
16
  from .wrappers.ctyping import setup_device
14
17
  from .wrappers.winregw import winreg_network
15
18
  from .wrappers.psutilw import psutil_networks
19
+ from .wrappers import powershell_networking, netshw
20
+ from .wrappers.socketw import socket_base
16
21
 
17
22
 
18
23
  MICROSOFT_LOOPBACK_DEVICE_NAME: str = 'Microsoft KM-TEST Loopback Adapter'
@@ -61,6 +66,7 @@ def is_ip_in_use_arp(
61
66
  def _send_arp(ip: str) -> str | None:
62
67
  """Return MAC string like 'aa:bb:cc:dd:ee:ff' if IP is claimed on the LAN, else None."""
63
68
  # inet_addr returns DWORD in network byte order
69
+ # noinspection PyUnresolvedReferences
64
70
  dest_ip = ws2_32.inet_addr(ip.encode('ascii'))
65
71
  if dest_ip == 0xFFFFFFFF: # INVALID
66
72
  raise ValueError(f"Bad IPv4 address: {ip}")
@@ -68,6 +74,7 @@ def is_ip_in_use_arp(
68
74
  mac_buf = ctypes.c_uint64(0) # storage for up to 8 bytes
69
75
  mac_len = ctypes.c_ulong(ctypes.sizeof(mac_buf)) # in/out len
70
76
  # SrcIP=0 lets Windows pick the right interface
77
+ # noinspection PyUnresolvedReferences
71
78
  rc = iphlpapi.SendARP(dest_ip, 0, ctypes.byref(mac_buf), ctypes.byref(mac_len))
72
79
  if rc != 0: # Non-zero means no ARP reply / not on-link / other error
73
80
  return None
@@ -105,32 +112,34 @@ def get_default_internet_ipv4(target: str = "8.8.8.8") -> str:
105
112
  return s.getsockname()[0] # local address of that route
106
113
 
107
114
 
108
- def get_hostname() -> str:
115
+ def get_default_interface_name() -> str:
116
+ default_connection_name_dict: dict = psutil_networks.get_default_connection_name()
117
+ if not default_connection_name_dict:
118
+ return ""
119
+ # Get the first key from the dictionary.
120
+ connection_name: str = list(default_connection_name_dict.keys())[0]
121
+ return connection_name
122
+
123
+
124
+ def list_network_interfaces() -> list[str]:
109
125
  """
110
- Get the default network interface name that is being used for internet.
111
- :return: string, default network interface name.
126
+ List all network interfaces on the system.
127
+ :return: list of strings, network interface names.
112
128
  """
113
129
 
114
- return socket.gethostname()
130
+ return psutil_networks.list_network_interfaces()
115
131
 
116
132
 
117
- def get_default_internet_interface_name() -> str | None:
133
+ def get_hostname() -> str:
118
134
  """
119
135
  Get the default network interface name that is being used for internet.
120
136
  :return: string, default network interface name.
121
137
  """
122
138
 
123
- interface_dict: dict = psutil_networks.get_default_connection_name()
124
- if not interface_dict:
125
- result = None
126
- else:
127
- # Get the first interface name from the dictionary.
128
- result = next(iter(interface_dict.keys()), None)
129
-
130
- return result
139
+ return socket.gethostname()
131
140
 
132
141
 
133
- def get_interface_ips(
142
+ def get_interface_ips_psutil(
134
143
  interface_name: str = None,
135
144
  ipv4: bool = True,
136
145
  ipv6: bool = True,
@@ -144,7 +153,7 @@ def get_interface_ips(
144
153
 
145
154
  if default_interface:
146
155
  # Get the default interface name.
147
- interface_name = get_default_internet_interface_name()
156
+ interface_name = get_default_interface_name()
148
157
 
149
158
  physical_ip_types: list[str] = []
150
159
  if ipv4:
@@ -169,6 +178,75 @@ def get_interface_ips(
169
178
  return ips
170
179
 
171
180
 
181
+ def get_host_ips_psutil(
182
+ localhost: bool = True,
183
+ ipv4: bool = True,
184
+ ipv6: bool = True
185
+ ) -> list[str]:
186
+ """
187
+ Yield (ifname, family, ip) for all UP interfaces that have bindable addresses.
188
+
189
+ Args:
190
+ localhost: include 127.0.0.0/8 and ::1 if True.
191
+ ipv4: include IPv4 addresses if True.
192
+ ipv6: include IPv6 addresses if True.
193
+ """
194
+ stats = psutil.net_if_stats()
195
+
196
+ ip_list: list[str] = []
197
+ for ifname, addrs in psutil.net_if_addrs().items():
198
+ st = stats.get(ifname)
199
+ if not st or not st.isup:
200
+ continue # interface is down or unknown
201
+
202
+ for a in addrs:
203
+ fam = a.family
204
+ if fam not in (socket.AF_INET, socket.AF_INET6):
205
+ continue
206
+
207
+ # Family filters
208
+ if fam == socket.AF_INET and not ipv4:
209
+ continue
210
+ if fam == socket.AF_INET6 and not ipv6:
211
+ continue
212
+
213
+ ip = a.address
214
+
215
+ # Skip placeholders/wildcards
216
+ if fam == socket.AF_INET and ip == "0.0.0.0":
217
+ continue
218
+ if fam == socket.AF_INET6 and ip in ("::",):
219
+ continue
220
+
221
+ # Optionally skip loopback
222
+ if not localhost:
223
+ if fam == socket.AF_INET and ip.startswith("127."):
224
+ continue
225
+ if fam == socket.AF_INET6 and (ip == "::1" or ip.startswith("::1%")):
226
+ continue
227
+
228
+ # yield ifname, fam, ip
229
+ ip_list.append(ip)
230
+
231
+ return ip_list
232
+
233
+
234
+ def get_interface_ips_powershell(
235
+ interface_name: str = None,
236
+ ip_type: Literal["virtual", "dynamic", "all"] = "virtual"
237
+ ) -> list[str]:
238
+ """
239
+ Get the IP addresses of a network interface using PowerShell.
240
+
241
+ :param interface_name: string, name of the network interface.
242
+ If None, all interfaces will be queried.
243
+ :param ip_type: string, type of IP addresses to retrieve.
244
+ :return: list of strings, IP addresses of the network interface.
245
+ """
246
+
247
+ return powershell_networking.get_interface_ips(interface_name=interface_name, ip_type=ip_type)
248
+
249
+
172
250
  def get_microsoft_loopback_device_network_configuration(
173
251
  wmi_instance: CDispatch = None,
174
252
  timeout: int = 1,
@@ -303,17 +381,14 @@ def change_interface_metric_restart_device(
303
381
 
304
382
 
305
383
  def get_wmi_network_adapter_configuration(
306
- use_default_interface: bool = False,
307
- connection_name: str = None,
384
+ interface_name: str = None,
308
385
  mac_address: str = None,
309
386
  wmi_instance: CDispatch = None,
310
387
  get_info_from_network_config: bool = True
311
388
  ) -> tuple:
312
389
  """
313
390
  Get the WMI network configuration for a network adapter.
314
- :param use_default_interface: bool, if True, the default network interface will be used.
315
- This is the adapter that your internet is being used from.
316
- :param connection_name: string, adapter name as shown in the network settings.
391
+ :param interface_name: string, adapter name as shown in the network settings.
317
392
  :param mac_address: string, MAC address of the adapter. Format: '00:00:00:00:00:00'.
318
393
  :param wmi_instance: WMI instance. You can get it from:
319
394
  wrappers.pywin32s.wmis.wmi_helpers.get_wmi_instance()
@@ -324,14 +399,14 @@ def get_wmi_network_adapter_configuration(
324
399
  """
325
400
 
326
401
  wmi_network_config, wmi_network_adapter = win32_networkadapterconfiguration.get_adapter_network_configuration(
327
- use_default_interface=use_default_interface,
328
- connection_name=connection_name,
402
+ interface_name=interface_name,
329
403
  mac_address=mac_address,
330
404
  wmi_instance=wmi_instance
331
405
  )
332
406
 
333
407
  if get_info_from_network_config:
334
408
  adapter_info: dict = win32_networkadapterconfiguration.get_info_from_network_config(wmi_network_config)
409
+ adapter_info['name'] = wmi_network_adapter.NetConnectionID
335
410
  else:
336
411
  adapter_info: dict = {}
337
412
 
@@ -415,7 +490,7 @@ def generate_unused_ipv4_addresses_from_ip(
415
490
  return generated_ips, masks_for_ips
416
491
 
417
492
 
418
- def set_dynamic_ip_for_adapter(
493
+ def set_dynamic_ip_for_adapter_wmi(
419
494
  network_config: CDispatch,
420
495
  reset_dns: bool = True,
421
496
  reset_wins: bool = True
@@ -431,7 +506,7 @@ def set_dynamic_ip_for_adapter(
431
506
  nic_cfg=network_config, reset_dns=reset_dns, reset_wins=reset_wins)
432
507
 
433
508
 
434
- def set_static_ip_for_adapter(
509
+ def set_static_ip_for_adapter_wmi(
435
510
  network_config: CDispatch,
436
511
  ips: list[str],
437
512
  masks: list[str],
@@ -459,19 +534,19 @@ def set_static_ip_for_adapter(
459
534
  )
460
535
 
461
536
 
462
- def add_virtual_ips_to_default_adapter_by_current_setting(
537
+ def add_virtual_ips_to_network_interface(
538
+ interface_name: str,
463
539
  number_of_ips: int = 0,
464
540
  virtual_ipv4s_to_add: list[str] = None,
465
541
  virtual_ipv4_masks_to_add: list[str] = None,
466
542
  set_virtual_ips_skip_as_source: bool = True,
467
- gateways: list[str] | None = None,
468
- dns_gateways: list[str] | None = None,
469
- availability_wait_seconds: int = 15,
470
543
  simulate_only: bool = False,
471
544
  locator: CDispatch = None,
472
545
  wait_until_applied: bool = True,
473
- wait_until_applied_seconds: int = 15
474
- ) -> tuple[list[str], list[str], list[str], list[str]]:
546
+ wait_until_applied_seconds: int = 15,
547
+ verbose: bool = False,
548
+ logger: Logger = None,
549
+ ) -> tuple[list[str], list[str]] | None:
475
550
  """
476
551
  Add virtual IP addresses to the default network adapter.
477
552
  The adapter will set to static IP and DNS gateway, instead of dynamic DHCP.
@@ -482,6 +557,8 @@ def add_virtual_ips_to_default_adapter_by_current_setting(
482
557
  While generating the IPs, the function will skip the already existing IPs in the adapter, like default gateway
483
558
  and DNS servers.
484
559
 
560
+ :param interface_name: string, adapter name as shown in the network settings.
561
+
485
562
  :param number_of_ips: int, number of IPs to generate in addition to the IPv4s that already exist in the adapter.
486
563
  Or you add the IPs and masks to the adapter with the parameters virtual_ipv4s_to_add and virtual_ipv4_masks_to_add.
487
564
 
@@ -491,15 +568,11 @@ def add_virtual_ips_to_default_adapter_by_current_setting(
491
568
 
492
569
  :param set_virtual_ips_skip_as_source: bool, if True, the SkipAsSource flag will be set for the virtual IPs.
493
570
  This is needed to avoid the endless accept() loop.
494
- :param gateways: list of strings, default IPv4 gateways to assign.
495
- None: The already existing gateways in the adapter will be used.
496
- []: No gateways will be assigned.
497
- :param dns_gateways: list of strings, IPv4 DNS servers to assign.
498
- None: The already existing DNS servers in the adapter will be used.
499
- []: No DNS servers will be assigned.
500
- :param availability_wait_seconds: int, seconds to wait for the adapter to be available after setting the IP address.
501
- :param simulate_only: bool, if True, the function will only prepare the ip addresses and return them without changing anything.
502
- :param locator: CDispatch, WMI locator object. If not specified, it will be created.
571
+
572
+ :param simulate_only: bool, if True, the function will only simulate the addition of the IP addresses.
573
+ No changes will be made to the system.
574
+ :param locator: CDispatch, WMI locator object. You can get it from:
575
+ wrappers.pywin32s.wmis.wmi_helpers.get_wmi_instance()
503
576
 
504
577
  :param wait_until_applied: bool, if True, the function will wait until the IP addresses are applied.
505
578
  By default, while WMI command is executed, there is no indication if the addresses were finished applying or not.
@@ -509,7 +582,10 @@ def add_virtual_ips_to_default_adapter_by_current_setting(
509
582
  after setting the IP addresses. This is the time to wait for the IP addresses to be
510
583
  applied after setting them. If the IP addresses are not applied in this time, a TimeoutError will be raised.
511
584
 
512
- :return: tuple of lists, (current_ipv4s, current_ipv4_masks, ips_to_assign, masks_to_assign)
585
+ :param verbose: bool, if True, the function will print verbose output.
586
+ :param logger: Logger, if provided, the function will log messages to this logger.
587
+
588
+ :return: tuple of lists, (ips_to_assign, masks_to_assign)
513
589
  """
514
590
 
515
591
  if virtual_ipv4s_to_add and not virtual_ipv4_masks_to_add:
@@ -526,24 +602,19 @@ def add_virtual_ips_to_default_adapter_by_current_setting(
526
602
  # Connect to WMi.
527
603
  wmi_civ2_instance, locator = wmi_helpers.get_wmi_instance(locator=locator)
528
604
 
529
- # initial_default_ipv4: str = socket.gethostbyname(socket.gethostname())
530
-
531
- # Get the default network adapter configuration.
532
- default_network_adapter_config, default_network_adapter, default_adapter_info = get_wmi_network_adapter_configuration(
533
- use_default_interface=True, wmi_instance=wmi_civ2_instance, get_info_from_network_config=True)
534
-
535
- current_ipv4s: list[str] = default_adapter_info['ipv4s']
536
- current_ipv4_masks: list[str] = default_adapter_info['ipv4_subnet_masks']
605
+ # Get the network adapter configuration.
606
+ network_adapter_config, network_adapter, adapter_info = get_wmi_network_adapter_configuration(
607
+ interface_name=interface_name, wmi_instance=wmi_civ2_instance, get_info_from_network_config=True)
537
608
 
538
- # print(f"Current IPs: {current_ipv4s}")
539
- # current_ips_count: int = len(current_ipv4s)
609
+ current_ipv4s: list[str] = adapter_info['ipv4s']
610
+ current_ipv4_masks: list[str] = adapter_info['ipv4_subnet_masks']
540
611
 
541
612
  if number_of_ips > 0:
542
613
  ips_to_assign, masks_to_assign = generate_unused_ipv4_addresses_from_ip(
543
614
  ip_address=current_ipv4s[0],
544
615
  mask=current_ipv4_masks[0],
545
616
  number_of_ips=number_of_ips,
546
- skip_ips=current_ipv4s + default_adapter_info['default_gateways'] + default_adapter_info['dns_gateways']
617
+ skip_ips=current_ipv4s + adapter_info['default_gateways'] + adapter_info['dns_gateways']
547
618
  )
548
619
  elif virtual_ipv4s_to_add and virtual_ipv4_masks_to_add:
549
620
  ips_to_assign = virtual_ipv4s_to_add
@@ -553,51 +624,48 @@ def add_virtual_ips_to_default_adapter_by_current_setting(
553
624
  masks_to_assign = []
554
625
 
555
626
  if not simulate_only:
556
- # Final list of IPs to assign.
557
- ips: list[str] = current_ipv4s + ips_to_assign
558
- masks: list[str] = current_ipv4_masks + masks_to_assign
559
-
560
- # ---------- IP assignment --------------------------------------------
561
- if ips != current_ipv4s:
562
- # print("[+] Setting static IPv4 addresses …")
563
- if gateways is None:
564
- gateways = default_adapter_info['default_gateways']
565
-
566
- if dns_gateways is None:
567
- dns_gateways = default_adapter_info['dns_gateways']
568
-
569
- # We will get the default IP address of the machine.
570
- default_ip_address: str = get_default_internet_ipv4()
571
- # So we can make it the first IP in the list, but first remove it from the list.
572
- _ = ips.pop(ips.index(default_ip_address))
573
- # At this point we will copy the list of IPs that we will set the SkipAsSource flag for.
574
- ips_for_skip_as_source = ips.copy()
575
- # Add it back to the beginning of the list.
576
- ips.insert(0, default_ip_address)
577
-
578
- win32_networkadapterconfiguration.set_static_ips(
579
- default_network_adapter_config, ips=ips, masks=masks,
580
- gateways=gateways, dns_gateways=dns_gateways,
581
- availability_wait_seconds=availability_wait_seconds)
582
-
583
- # If there were already virtual IPs assigned to the adapter and already were set SkipAsSource,
584
- # we need to set SkipAsSource for them once again as well as for the new IPs.
585
- if set_virtual_ips_skip_as_source:
586
- wmi_standard_cimv2_instance, _ = wmi_helpers.get_wmi_instance(
587
- namespace='root\\StandardCimv2', wmi_instance=wmi_civ2_instance, locator=locator)
588
- msft_netipaddress.set_skip_as_source(ips_for_skip_as_source, enable=True, wmi_instance=wmi_standard_cimv2_instance)
589
- else:
590
- # print("[!] No new IPs to assign.")
591
- pass
627
+ # Enable DHCP + static IP coexistence on the interface.
628
+ process_complete: subprocess.CompletedProcess = netshw.enable_dhcp_static_coexistence(interface_name=interface_name)
629
+ if process_complete.returncode != 0:
630
+ print_api(f"[!] Failed to enable DHCP + static IP coexistence on interface {interface_name}.\n"
631
+ f" stdout: {process_complete.stdout}\n"
632
+ f" stderr: {process_complete.stderr}", color="red", logger=logger)
633
+ return None
634
+
635
+ for ip, mask in zip(ips_to_assign, masks_to_assign):
636
+ if verbose:
637
+ print_api(f"[+] Adding virtual IP {ip} with mask {mask} to interface {interface_name}.", logger=logger)
638
+
639
+ netshw.add_virtual_ip(
640
+ interface_name=interface_name,
641
+ ip=ip,
642
+ mask=mask,
643
+ skip_as_source=set_virtual_ips_skip_as_source
644
+ )
592
645
 
593
646
  if wait_until_applied:
594
647
  # Wait until the IP addresses are applied.
595
648
  for _ in range(wait_until_applied_seconds):
596
- current_ips = get_interface_ips(ipv4=True, ipv6=False, localhost=False, default_interface=True)
597
- if set(current_ips) == set(ips):
649
+ current_virtual_ips = get_interface_ips_powershell(interface_name=interface_name, ip_type="virtual")
650
+ if set(current_virtual_ips) == set(ips_to_assign):
598
651
  break
599
652
  time.sleep(1)
600
653
  else:
601
654
  raise TimeoutError("Timeout while waiting for the IP addresses to be applied.")
602
655
 
603
- return current_ipv4s, current_ipv4_masks, ips_to_assign, masks_to_assign
656
+ return ips_to_assign, masks_to_assign
657
+
658
+
659
+ def wait_for_ip_bindable_socket(
660
+ ip: str,
661
+ port: int = 0,
662
+ timeout: float = 15.0,
663
+ interval: float = 0.5,
664
+ ) -> None:
665
+ """
666
+ Wait until a single IP is bindable (or timeout).
667
+
668
+ Raises TimeoutError if the IP cannot be bound within 'timeout' seconds.
669
+ """
670
+
671
+ socket_base.wait_for_ip_bindable(ip=ip, port=port, timeout=timeout, interval=interval)
@@ -0,0 +1,84 @@
1
+ """Loading resources using stdlib importlib.resources APIs (Python 3.7+)
2
+ https://docs.python.org/3/library/importlib.html#module-importlib.resources"""
3
+ import importlib.resources
4
+ from contextlib import redirect_stdout
5
+ import io
6
+ import subprocess
7
+ import sys
8
+ from typing import Callable
9
+
10
+
11
+ class PackageMainsProcessor:
12
+ def __init__(
13
+ self,
14
+ script_file_stem: str = None
15
+ ):
16
+ self.script_file_stem: str = script_file_stem
17
+ self.resources_directory_name: str = 'a_mains'
18
+
19
+ def get_resource_path(self) -> str:
20
+ return f'{__package__}.{self.resources_directory_name}'
21
+
22
+ def read_script_file_to_string(self) -> str:
23
+ script_string = importlib.resources.read_text(self.get_resource_path(), f'{self.script_file_stem}.py')
24
+
25
+ return script_string
26
+
27
+ def execute_script_file(
28
+ self,
29
+ function_name: str = 'main',
30
+ args: tuple = None,
31
+ kwargs: dict = None,
32
+ get_printed_output: bool = False
33
+ ) -> str:
34
+ """
35
+ Execute a script file from the package resources and get result as string.
36
+
37
+ :param function_name: Name of the function to call within the script.
38
+ :param args: Tuple of positional arguments to pass to the function.
39
+ :param kwargs: Dictionary of keyword arguments to pass to the function.
40
+ :param get_printed_output: If True, captures and returns printed output instead of return value.
41
+
42
+ :return: Output of the script execution as a string.
43
+ """
44
+
45
+ if not args:
46
+ args = ()
47
+ if not kwargs:
48
+ kwargs = {}
49
+
50
+ module_name = f"{self.get_resource_path()}.{self.script_file_stem}" # script_file_name WITHOUT ".py"
51
+
52
+ module = importlib.import_module(module_name)
53
+ callable_function: Callable = getattr(module, function_name)
54
+
55
+ if get_printed_output:
56
+ with io.StringIO() as buffer, redirect_stdout(buffer):
57
+ callable_function(*args, **kwargs)
58
+ result = buffer.getvalue()
59
+ else:
60
+ result = callable_function(*args, **kwargs)
61
+
62
+ return result
63
+
64
+ def execute_script_with_subprocess(
65
+ self,
66
+ arguments: list = None
67
+ ) -> tuple[str, str, int]:
68
+ """
69
+ Execute a script file from the package resources using subprocess and get result as string.
70
+ :param arguments: Dictionary of arguments to pass to the script.
71
+ Example: ['--port', '8080', '-v']
72
+ :return: Tuple containing (stdout, stderr, returncode).
73
+ """
74
+
75
+ # script_file_name WITHOUT ".py"
76
+ module_name = f"{self.get_resource_path()}.{self.script_file_stem}"
77
+
78
+ command = [sys.executable, "-m", module_name]
79
+ if arguments:
80
+ command.extend(arguments)
81
+
82
+ result = subprocess.run(command, capture_output=True, text=True)
83
+
84
+ return result.stdout, result.stderr, result.returncode
@@ -2,6 +2,7 @@ import os
2
2
  import stat
3
3
  import contextlib
4
4
  import subprocess
5
+ import getpass
5
6
 
6
7
  # Import pwd only on linux.
7
8
  if os.name == 'posix':
@@ -20,6 +21,52 @@ def get_sudo_executer_username() -> str:
20
21
  return ''
21
22
 
22
23
 
24
+ def detect_current_user(
25
+ optional_env_user_var: str = 'CUSTOM_SCRIPTED_USER'
26
+ ) -> str:
27
+ """
28
+ Try to robustly determine the 'real' installing user.
29
+
30
+ Priority:
31
+ 1. FDB_INSTALL_USER env var (explicit override).
32
+ 2. If running as root with sudo: use SUDO_USER.
33
+ 3. Otherwise: use effective uid.
34
+ 4. Fallbacks: getpass.getuser() / $USER.
35
+
36
+ :param optional_env_user_var: str, name of the environment variable that can override the user detection.
37
+ :return: str, username.
38
+ """
39
+
40
+ # 1. Explicit override for weird environments (CI, containers, etc.)
41
+ env_user = os.getenv(optional_env_user_var)
42
+ if env_user:
43
+ return env_user
44
+
45
+ # 2. If we are root, prefer the sudo caller if any
46
+ try:
47
+ euid = os.geteuid()
48
+ except AttributeError: # non-POSIX, very unlikely here
49
+ euid = None
50
+
51
+ if euid == 0:
52
+ sudo_user = os.environ.get("SUDO_USER")
53
+ if sudo_user:
54
+ return sudo_user
55
+
56
+ # 3. Normal case: effective uid -> username
57
+ if euid is not None:
58
+ try:
59
+ return pwd.getpwuid(euid).pw_name
60
+ except Exception:
61
+ pass
62
+
63
+ # 4. Fallbacks that don’t depend on utmp/tty
64
+ try:
65
+ return getpass.getuser()
66
+ except Exception:
67
+ return os.environ.get("USER", "unknown")
68
+
69
+
23
70
  def set_executable(file_path: str):
24
71
  """
25
72
  Function sets the executable permission on a file.
atomicshop/print_api.py CHANGED
@@ -1,13 +1,14 @@
1
1
  import sys
2
2
  import logging
3
+ from typing import Any
3
4
 
4
5
  from .basics import ansi_escape_codes
5
6
  from .basics import tracebacks
6
7
 
7
8
 
8
9
  def print_api(
9
- message: any,
10
- color: any = None,
10
+ message: Any,
11
+ color: Any = None,
11
12
  print_end: str = '\n',
12
13
  rtl: bool = False,
13
14
  error_type: bool = False,
@@ -73,7 +74,6 @@ def print_api(
73
74
 
74
75
  # Inner functions already get all the local variables of the main function.
75
76
  def print_or_logger():
76
- from .wrappers.loggingw import loggingw
77
77
  nonlocal message
78
78
  nonlocal color
79
79
  nonlocal traceback_string
@@ -83,8 +83,6 @@ def print_api(
83
83
 
84
84
  # If 'rtl' is set to 'True', we'll add Right-To-Left text conversion to 'message'.
85
85
  if rtl:
86
- # Lazy importing of 'bidi' library. It's not a problem since python caches the library after first import.
87
- # Off-course, it will be imported from the cache each time this section is triggered.
88
86
  # pip install python-bidi
89
87
  from bidi.algorithm import get_display
90
88
  message = get_display(message)
atomicshop/process.py CHANGED
@@ -316,14 +316,21 @@ def kill_process_by_filename_pattern(pattern: str):
316
316
  processes.kill_process_by_pid(running_processes[0]['pid'])
317
317
 
318
318
 
319
- def run_powershell_command(command):
319
+ def run_powershell_command(
320
+ command: str
321
+ ):
320
322
  try:
321
323
  result = subprocess.run(["powershell", "-Command", command], capture_output=True, text=True, check=True)
322
- print_api(result.stdout)
324
+ if result.stdout:
325
+ print_api(result.stdout)
326
+ if result.stderr: # PS can write warnings to stderr even on success
327
+ print_api(result.stderr, color='yellow')
323
328
  return result.stdout
324
329
  except subprocess.CalledProcessError as e:
325
- print_api(f"An error occurred: {e}", color='red', error_type=True)
326
- return e
330
+ # e.stderr and e.stdout are populated because capture_output=True
331
+ msg = (e.stderr or e.stdout or f"PowerShell exited with code {e.returncode}")
332
+ print_api(msg, color='red', error_type=True)
333
+ return msg
327
334
 
328
335
 
329
336
  """