Skip to content

Commit f37bdfc

Browse files
Merge pull request #281 from P6g9YHK6/main
updates + new pwsh snippet + rustdesk exemples
2 parents ffe1f8d + b221d1e commit f37bdfc

File tree

6 files changed

+343
-92
lines changed

6 files changed

+343
-92
lines changed

scripts_staging/Backend/Sync TRMM with GIT.py

Lines changed: 132 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -93,16 +93,19 @@
9393
v9.0.3.2 24/04/25 SAN couple of pre-flight fixes
9494
v9.0.3.3 24/04/25 SAN fix commit errors
9595
v9.0.4.0 24/04/25 SAN New commit design, decreased importance of uncommited at git check, added emojis ✅, bugfix on git stdout
96-
96+
v9.0.4.1 28/04/25 SAN Paranoid check added to avoid random deletion, more verbose output on file deletion, moved folder check after git as git require an empty folder when first cloning, couple of pre-flight fixes
97+
v9.0.4.2 29/04/25 SAN more explicit part 2 & 3 outputs, added RW check
98+
v9.0.4.3 29/04/25 SAN more explicit outputs for git pull & fix other output
99+
v9.0.4.4 30/04/25 SAN fix pull
97100
98101
.TODO
99-
Handle rights issues when executing git commands
102+
move env setup to pre-flight
100103
Review flow of step 3 for optimisations
101-
104+
Review the counters for step 3
102105
Revamp folder structure:
103-
Move raws from "scriptsraw" to Category/folder/raws/
106+
Move raws from "scriptsraw" to Category_name/raws/
104107
add "uncategorised" folder
105-
remove "scripts" top level folder while keeping snippets
108+
remove "scripts" top level folder while keeping snippets and move snippets raws to snippets/raws/
106109
107110
Move ID from json to an array like this and make sure that this array is never overwriten to keep tracks of IDs across instances only add current instance in step 2 if missing:
108111
"ids": [
@@ -115,6 +118,7 @@
115118
Delete script support from git ? (dedicated function required at the end of step 2, if json exist but no script matches mark for delete json and use the id of the json to tell the api to delete in trmm)
116119
Squash commit from minor update json with previous commit
117120
Add reporting support
121+
118122
119123
"""
120124

@@ -133,7 +137,6 @@
133137

134138
# Retrieve the git pull branch or default to 'master'
135139
git_pull_branch = os.getenv('GIT_PULL_BRANCH', 'master')
136-
if git_pull_branch != 'master': print(f"Git Pull Branch: {git_pull_branch}")
137140

138141
# Retrieve flags from environment variables (default to True unless set to 'false')
139142
ENABLE_GIT_PULL = os.getenv('ENABLE_GIT_PULL', 'True').lower() != 'false'
@@ -147,35 +150,49 @@
147150

148151

149152
def delete_obsolete_files(folder, current_scripts):
150-
print(f"Cleaning {folder}...")
151-
obsolete = {f for f in folder.rglob('*') if f.is_file() and f.relative_to(folder) not in current_scripts}
152-
153+
if not current_scripts:
154+
print("❌ ERROR: No valid scripts provided by api. Aborting.")
155+
sys.exit(1)
156+
if not isinstance(current_scripts, set):
157+
print("❌ ERROR: 'current_scripts' must be a set. Aborting.")
158+
sys.exit(1)
159+
160+
print(f"🧹 Cleaning {folder}...")
161+
162+
all_paths = list(folder.rglob('*'))
163+
obsolete = {f for f in all_paths if f.is_file() and f.relative_to(folder) not in current_scripts}
164+
153165
if not obsolete:
154-
print("No files missing from the API but still present in the repo.")
166+
print("✅ No files missing from the API but still present in the repo.")
167+
else:
168+
with open("deletion.log", "a") as log:
169+
for f in obsolete:
170+
action = "🗑️📄 Deleted" if ENABLE_WRITETOFILE else "🗑️📄 Simulated deletion of"
171+
try:
172+
if ENABLE_WRITETOFILE:
173+
f.unlink()
174+
print(f"{action} file no longer in the API: {f}")
175+
log.write(f"{action}: {f}\n")
176+
except Exception as e:
177+
print(f"⚠️ Error deleting file {f}: {e}")
178+
log.write(f"⚠️ Error deleting {f}: {e}\n")
155179

156-
for f in obsolete:
157-
if ENABLE_WRITETOFILE:
158-
try:
159-
f.unlink()
160-
print(f"Deleted file no longer in the API: {f}")
161-
except Exception as e:
162-
print(f"Error deleting file {f}: {e}")
163-
else:
164-
print(f"Simulated deletion of file no longer in the API: {f}")
180+
empty_dirs = [d for d in sorted(all_paths, key=lambda p: -len(p.parts)) if d.is_dir() and not any(d.iterdir())]
165181

166-
empty_dirs = [d for d in sorted(folder.rglob('*'), key=lambda p: -len(p.parts)) if d.is_dir() and not any(d.iterdir())]
167182
if not empty_dirs:
168-
print("No empty directories to remove.")
169-
170-
for d in empty_dirs:
171-
if ENABLE_WRITETOFILE:
172-
try:
173-
d.rmdir()
174-
print(f"Removed empty directory: {d}")
175-
except Exception as e:
176-
print(f"Could not delete dir {d}: {e}")
177-
else:
178-
print(f"Simulated removal of empty directory: {d}")
183+
print("✅ No empty directories to remove.")
184+
else:
185+
with open("deletion.log", "a") as log:
186+
for d in empty_dirs:
187+
action = "🗑️📁 Removed" if ENABLE_WRITETOFILE else "🗑️📁 Simulated removal of"
188+
try:
189+
if ENABLE_WRITETOFILE:
190+
d.rmdir()
191+
print(f"{action} empty directory: {d}")
192+
log.write(f"{action}: {d}\n")
193+
except Exception as e:
194+
print(f"⚠️ Could not delete dir {d}: {e}")
195+
log.write(f"⚠️ Could not delete dir {d}: {e}\n")
179196

180197
def sanitize_filename(name: str) -> str:
181198
removed_chars = []
@@ -223,7 +240,7 @@ def process_scripts(scripts, script_folder, script_raw_folder, shell_summary, is
223240
current.add((folder / fname).relative_to(script_folder))
224241
current.add((raw_folder / raw_name).relative_to(script_raw_folder))
225242

226-
print(f"Processed {len(current)} {'snippets' if is_snippet else 'scripts'}.")
243+
print(f"Processed {len(current)} {'snippets' if is_snippet else 'scripts'}.\n")
227244
return current
228245

229246
def compute_hash(file_path):
@@ -237,9 +254,9 @@ def save_file(path, content, is_json=False):
237254
data = json.dumps(content, indent=4, ensure_ascii=False) if is_json else content
238255
if ENABLE_WRITETOFILE:
239256
path.write_text(data, encoding="utf-8")
240-
print(f"File saved: {path}")
257+
print(f"File saved: {path.relative_to(base_dir) if base_dir else path}")
241258
else:
242-
print(f"File would be saved (simulation): {path}")
259+
print(f"File would be saved (simulation): {path.relative_to(base_dir) if base_dir else path}")
243260

244261

245262
def pull_from_api(url):
@@ -310,6 +327,7 @@ def update_api_if_needed(match, raw_data, is_snippet):
310327

311328
return False
312329

330+
313331
def write_modifications_to_api(base_dir, folders):
314332
print("Comparing script files with JSON files...")
315333
mismatches = []
@@ -318,7 +336,7 @@ def write_modifications_to_api(base_dir, folders):
318336
total_matches = 0
319337
total_mismatches = 0
320338
total_updated = 0
321-
total_skipped = 0
339+
total_not_updated = 0
322340

323341
for folder_key, folder in folders.items():
324342
is_snippet = folder_key == 'snippetsraw'
@@ -332,15 +350,15 @@ def write_modifications_to_api(base_dir, folders):
332350
if p.is_file() and p.stem.lower() == raw_name), None)
333351
except Exception as e:
334352
print(f"Error matching file for {raw_path}: {e}")
335-
total_skipped += 1
353+
total_not_updated += 1
336354
continue
337355

338356
if not match:
339-
print(f"No match for {'snippet' if is_snippet else 'script'}: {raw_path}")
340-
total_skipped += 1
357+
print(f"No match for {'snippet' if is_snippet else 'script'}: {raw_path.relative_to(base_dir)}")
358+
total_not_updated += 1
341359
continue
342360

343-
print(f"Matched {'snippet' if is_snippet else 'script'}: {match} <-> {raw_path}")
361+
print(f"Matched {'snippet' if is_snippet else 'script'}: {match.relative_to(base_dir)} <-> {raw_path.relative_to(base_dir)}")
344362
total_matches += 1
345363

346364
file_hash, code_hash, raw_data = compare_files_and_hashes(match, raw_path)
@@ -364,13 +382,24 @@ def write_modifications_to_api(base_dir, folders):
364382

365383
if updated:
366384
total_updated += 1
385+
else:
386+
total_not_updated += 1
387+
388+
print("\n🔍 Comparison Complete:")
389+
390+
print(f"🧾 Total files checked: {total_files_checked}")
391+
if total_matches > 0:
392+
print(f"↔️ Total matches: {total_matches}")
393+
if total_mismatches > 0:
394+
print(f"🧩 Total mismatches to update: {total_mismatches}")
395+
if total_updated > 0:
396+
print(f"✅ Total updated: {total_updated}")
397+
if total_not_updated > 0:
398+
print(f"❌ Total errors: {total_not_updated}")
399+
400+
if total_matches == total_files_checked:
401+
print("✅ Everything is up to date in the api")
367402

368-
print("\nComparison Complete:")
369-
print(f"Total files checked: {total_files_checked}")
370-
print(f"Total matches: {total_matches}")
371-
print(f"Total mismatches: {total_mismatches}")
372-
print(f"Total updates: {total_updated}")
373-
print(f"Total skipped: {total_skipped}")
374403

375404
def update_to_api(item_id, payload, is_snippet=False):
376405
"""Update the API with the provided item ID and payload."""
@@ -395,13 +424,27 @@ def update_to_api(item_id, payload, is_snippet=False):
395424
print(f"Request error for {'snippet' if is_snippet else 'script'} {item_id}: {e}")
396425

397426
def git_pull(base_dir):
398-
"""Force pull the latest changes from the git repository, discarding local changes."""
399-
427+
"""Force pull latest changes from the git repository if there are changes, discarding local changes."""
428+
400429
print("Starting pull process...", flush=True)
401430
try:
431+
print(f"Branch to pull: '{git_pull_branch}'")
402432
print("Fetching latest changes from remote...", flush=True)
403433
subprocess.check_call(['git', '-C', base_dir, 'fetch', 'origin'], stdout=sys.stdout, stderr=sys.stderr)
404434

435+
print("Checking for incoming changes...", flush=True)
436+
result = subprocess.run(
437+
['git', '-C', base_dir, 'log', f'HEAD..origin/{git_pull_branch}', '--oneline'],
438+
capture_output=True, text=True
439+
)
440+
441+
if not result.stdout.strip():
442+
print("No changes to pull. Repository is up to date.", flush=True)
443+
return
444+
445+
print("Incoming commits:")
446+
print(result.stdout, flush=True)
447+
405448
print(f"Resetting local branch to match 'origin/{git_pull_branch}'...", flush=True)
406449
subprocess.check_call(['git', '-C', base_dir, 'reset', '--hard', f'origin/{git_pull_branch}'], stdout=sys.stdout, stderr=sys.stderr)
407450

@@ -498,13 +541,22 @@ def git_push(base_dir):
498541
print(f"Changes pushed to branch '{git_pull_branch}'")
499542

500543
else:
501-
print("No changes to commit.")
544+
print("No changes to commit.")
502545
except subprocess.CalledProcessError as e:
503546
print(f"Git operation failed: {e}")
504547

505548
def check_git_health(base_dir):
506549
"""Check the health of the Git repository."""
507550

551+
# Check the rights to read/write in the directory
552+
try:
553+
if not os.access(base_dir, os.R_OK | os.W_OK):
554+
print(f"❌ Error: No read/write permissions for the directory '{base_dir}'.")
555+
return False
556+
except Exception as e:
557+
print(f"❌ Error: Failed to check permissions for the directory '{base_dir}'. {e}")
558+
return False
559+
508560
# Check if 'git' command is available
509561
try:
510562
subprocess.check_call(['git', '--version'], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
@@ -601,11 +653,12 @@ def check_git_health(base_dir):
601653
print("❌ Error: Failed to check commit history.")
602654
return False
603655

604-
print("✅ Git repository health check passed.")
605656
return True
606657

658+
607659
def pre_flight():
608-
global domain, scriptpath, headers
660+
global domain, headers, base_dir
661+
609662
domain = os.getenv('DOMAIN')
610663
api_token = os.getenv('API_TOKEN')
611664
scriptpath = os.getenv('SCRIPTPATH')
@@ -619,8 +672,13 @@ def pre_flight():
619672
if var == 'SCRIPTPATH': print(f" - SCRIPTPATH: The local folder path for Git commands.")
620673
sys.exit(1)
621674

675+
#Build headers
622676
headers = {"X-API-KEY": api_token}
623-
domain_for_connection = domain.replace("https://", "").replace("http://", "")
677+
#Build base_dir path
678+
base_dir = Path(scriptpath).resolve()
679+
680+
# no http for tcp test or any trailing slash
681+
domain_for_connection = domain.replace("https://", "").replace("http://", "").rstrip("/")
624682

625683
try:
626684
socket.create_connection((domain_for_connection, 443), timeout=5)
@@ -630,11 +688,13 @@ def pre_flight():
630688
print(f"❌ Error: Unable to connect to {domain} on port 443 - {e} (Obfuscated API Token: {obfuscated})")
631689
sys.exit(1)
632690

691+
# Make sure domain starts with https:// and remove any trailing slash
633692
if not domain.startswith("http://") and not domain.startswith("https://"):
634693
domain = "https://" + domain
694+
domain = domain.rstrip("/")
635695

696+
#Test api token for read, it is currently not possible to test for write as any request to the api would write empty file.
636697
obfuscated = api_token[:3] + '*' * (len(api_token) - 6) + api_token[-3:]
637-
638698
try:
639699
response = requests.get(f"{domain}/scripts/", headers=headers, timeout=5)
640700
if response.status_code == 200:
@@ -648,6 +708,7 @@ def pre_flight():
648708

649709
return
650710

711+
651712
def check_and_create_folders(base_path, subfolders):
652713
try:
653714
if not base_path.exists():
@@ -671,21 +732,11 @@ def main():
671732

672733
# 0. Prep: Verify Dependencies, Set Up Environment, and Git Health Check
673734
print("\n===== Step 0: General Prep =====")
674-
675-
# ENV vars & network checks
735+
736+
737+
# ENV vars & network checks & prep vars
676738
pre_flight()
677739

678-
# Folder structure check
679-
base_dir = Path(scriptpath).resolve()
680-
folders = {
681-
"scripts": base_dir / "scripts",
682-
"scriptsraw": base_dir / "scriptsraw",
683-
"snippets": base_dir / "snippets",
684-
"snippetsraw": base_dir / "snippetsraw"
685-
}
686-
check_and_create_folders(base_dir, folders)
687-
print("✅ All folders created and verified.")
688-
689740
# Check the health of the Git repo
690741
if ENABLE_GIT_PULL or ENABLE_GIT_PUSH:
691742
if check_git_health(base_dir):
@@ -695,12 +746,21 @@ def main():
695746
sys.exit(1)
696747
else:
697748
print("Skipping Git health check because both pull and push are disabled.")
698-
749+
750+
# Folder structure check
751+
folders = {
752+
"scripts": base_dir / "scripts",
753+
"scriptsraw": base_dir / "scriptsraw",
754+
"snippets": base_dir / "snippets",
755+
"snippetsraw": base_dir / "snippetsraw"
756+
}
757+
check_and_create_folders(base_dir, folders)
758+
print("✅ All folders created and verified.")
759+
699760
print("===== End of Step 0: General Prep =====\n")
700761

701762
# 1. Git Pull
702763
print("\n===== Step 1: Git Pull =====")
703-
print(f"Branch to pull: '{git_pull_branch}'")
704764
if ENABLE_GIT_PULL:
705765
git_pull(base_dir)
706766
else:
@@ -716,19 +776,21 @@ def main():
716776
print("\n===== Step 3: Fetch and Process Scripts and Snippets =====")
717777
# Initialize counters and sets
718778
shell_summary, current_scripts = defaultdict(int), set()
719-
print("Fetching scripts...")
779+
print("Fetching script list...")
720780
user_defined_scripts = pull_from_api(f"{domain}/scripts/?showHiddenScripts=true")
721781
user_defined_scripts = [item for item in user_defined_scripts if item.get('script_type') == 'userdefined']
722782
current_scripts.update(process_scripts(user_defined_scripts, folders["scripts"], folders["scriptsraw"], shell_summary))
723783

724784
# Fetch and process snippets
725-
print("Fetching snippets...")
785+
print("Fetching snippets list...")
726786
snippets = pull_from_api(f"{domain}/scripts/snippets/")
727787
current_scripts.update(process_scripts(snippets, folders["snippets"], folders["snippetsraw"], shell_summary, is_snippet=True))
728788

729789
# Output the total number of scripts exported and provide a summary of the shell counts
730790
print(f"Total number of scripts exported: {len(current_scripts)}")
731-
print("Shell summary:", "\n".join(f"{shell}: {count}" for shell, count in shell_summary.items()))
791+
print("Shell summary:")
792+
for shell, count in shell_summary.items():
793+
print(f"{shell.strip()}: {count}")
732794

733795
# Remove any obsolete files that are no longer existing in the api
734796
print("\nRemove any obsolete files")
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
#public
2+
#grab public id of restdesk to set a custom field
3+
$ErrorActionPreference= 'silentlycontinue'
4+
5+
cd $env:ProgramFiles\RustDesk\
6+
.\RustDesk.exe --get-id | out-host

0 commit comments

Comments
 (0)