accli 2.0.0rc1__tar.gz → 2.0.0rc4__tar.gz
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.
- {accli-2.0.0rc1 → accli-2.0.0rc4}/PKG-INFO +4 -4
- {accli-2.0.0rc1 → accli-2.0.0rc4}/README.md +2 -2
- {accli-2.0.0rc1 → accli-2.0.0rc4}/accli/_version.py +1 -1
- {accli-2.0.0rc1 → accli-2.0.0rc4}/accli/cli.py +249 -98
- {accli-2.0.0rc1 → accli-2.0.0rc4}/accli/gui.py +10 -3
- {accli-2.0.0rc1 → accli-2.0.0rc4}/accli/mount_downloader.py +1 -1
- {accli-2.0.0rc1 → accli-2.0.0rc4}/accli/token.py +15 -1
- {accli-2.0.0rc1 → accli-2.0.0rc4}/accli.egg-info/PKG-INFO +4 -4
- {accli-2.0.0rc1 → accli-2.0.0rc4}/accli.egg-info/requires.txt +1 -1
- {accli-2.0.0rc1 → accli-2.0.0rc4}/pyproject.toml +1 -1
- {accli-2.0.0rc1 → accli-2.0.0rc4}/LICENSE +0 -0
- {accli-2.0.0rc1 → accli-2.0.0rc4}/accli/AcceleratorJobProjectService.py +0 -0
- {accli-2.0.0rc1 → accli-2.0.0rc4}/accli/AcceleratorTaskDispatcher.py +0 -0
- {accli-2.0.0rc1 → accli-2.0.0rc4}/accli/AcceleratorTerminalCliProjectService.py +0 -0
- {accli-2.0.0rc1 → accli-2.0.0rc4}/accli/CsvRegionalTimeseriesValidator.py +0 -0
- {accli-2.0.0rc1 → accli-2.0.0rc4}/accli/__init__.py +0 -0
- {accli-2.0.0rc1 → accli-2.0.0rc4}/accli/__main__.py +0 -0
- {accli-2.0.0rc1 → accli-2.0.0rc4}/accli/cleanup-nfs.ps1 +0 -0
- {accli-2.0.0rc1 → accli-2.0.0rc4}/accli/common.py +0 -0
- {accli-2.0.0rc1 → accli-2.0.0rc4}/accli/setup-nfs.ps1 +0 -0
- {accli-2.0.0rc1 → accli-2.0.0rc4}/accli.egg-info/SOURCES.txt +0 -0
- {accli-2.0.0rc1 → accli-2.0.0rc4}/accli.egg-info/dependency_links.txt +0 -0
- {accli-2.0.0rc1 → accli-2.0.0rc4}/accli.egg-info/entry_points.txt +0 -0
- {accli-2.0.0rc1 → accli-2.0.0rc4}/accli.egg-info/top_level.txt +0 -0
- {accli-2.0.0rc1 → accli-2.0.0rc4}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: accli
|
|
3
|
-
Version: 2.0.
|
|
3
|
+
Version: 2.0.0rc4
|
|
4
4
|
Summary: IIASA Accelerator Client
|
|
5
5
|
Author-email: Wrufesh S <wrufesh@gmail.com>
|
|
6
6
|
License: The MIT License (MIT)
|
|
@@ -39,7 +39,7 @@ Requires-Dist: requests>=2.25.1
|
|
|
39
39
|
Requires-Dist: jsonschema>=4.5.1
|
|
40
40
|
Requires-Dist: pydantic>=2.0
|
|
41
41
|
Requires-Dist: GitPython
|
|
42
|
-
Requires-Dist: hf-xet
|
|
42
|
+
Requires-Dist: hf-xet==1.5.2
|
|
43
43
|
Dynamic: license-file
|
|
44
44
|
|
|
45
45
|
# Accelerator Terminal Client and Python API
|
|
@@ -138,8 +138,8 @@ Usage: accli [OPTIONS] COMMAND [ARGS]...
|
|
|
138
138
|
|
|
139
139
|
### Handling self-signed certificates
|
|
140
140
|
|
|
141
|
-
1. Copy and paste certificates from `<control_services_backend>/certs/
|
|
142
|
-
`./certs/dev-backend.crt` and `<accelerator_service>/minio_certs/
|
|
141
|
+
1. Copy and paste certificates from `<control_services_backend>/certs/ca.crt` as
|
|
142
|
+
`./certs/dev-backend.crt` and `<accelerator_service>/minio_certs/ca.crt` as
|
|
143
143
|
`./certs/dev-minio-ca.crt`.
|
|
144
144
|
2. Run
|
|
145
145
|
```
|
|
@@ -94,8 +94,8 @@ Usage: accli [OPTIONS] COMMAND [ARGS]...
|
|
|
94
94
|
|
|
95
95
|
### Handling self-signed certificates
|
|
96
96
|
|
|
97
|
-
1. Copy and paste certificates from `<control_services_backend>/certs/
|
|
98
|
-
`./certs/dev-backend.crt` and `<accelerator_service>/minio_certs/
|
|
97
|
+
1. Copy and paste certificates from `<control_services_backend>/certs/ca.crt` as
|
|
98
|
+
`./certs/dev-backend.crt` and `<accelerator_service>/minio_certs/ca.crt` as
|
|
99
99
|
`./certs/dev-minio-ca.crt`.
|
|
100
100
|
2. Run
|
|
101
101
|
```
|
|
@@ -300,12 +300,6 @@ def copy(
|
|
|
300
300
|
verify_cert=(not ACCLI_DEBUG),
|
|
301
301
|
)
|
|
302
302
|
|
|
303
|
-
def make_token_refresher(slug: str):
|
|
304
|
-
def refresher() -> tuple[str, int]:
|
|
305
|
-
token_str, _, exp_time = exchange_refresh_token(slug)
|
|
306
|
-
return token_str, exp_time
|
|
307
|
-
return refresher
|
|
308
|
-
|
|
309
303
|
if is_src_remote:
|
|
310
304
|
# Download flow: acc://[project_slug]/[remote_prefix] -> local destination
|
|
311
305
|
source_parsed = source[len("acc://"):]
|
|
@@ -336,7 +330,7 @@ def copy(
|
|
|
336
330
|
else:
|
|
337
331
|
dest_path.parent.mkdir(parents=True, exist_ok=True)
|
|
338
332
|
|
|
339
|
-
|
|
333
|
+
files_to_download = []
|
|
340
334
|
for filename in filenames:
|
|
341
335
|
# Filename returned usually starts with project_slug/
|
|
342
336
|
if filename.startswith(f"{project_slug}/"):
|
|
@@ -378,32 +372,98 @@ def copy(
|
|
|
378
372
|
|
|
379
373
|
local_dest.parent.mkdir(parents=True, exist_ok=True)
|
|
380
374
|
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
destination_path=str(local_dest.resolve()),
|
|
384
|
-
hash=merkle_hash,
|
|
385
|
-
file_size=file_size
|
|
386
|
-
)
|
|
375
|
+
files_to_download.append(
|
|
376
|
+
(str(local_dest.resolve()), merkle_hash, file_size)
|
|
387
377
|
)
|
|
388
378
|
|
|
389
|
-
if not
|
|
379
|
+
if not files_to_download:
|
|
390
380
|
print("[bold red]ERROR: No valid files identified for download.[/bold red]")
|
|
391
381
|
raise typer.Exit(1)
|
|
392
382
|
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
383
|
+
MAX_FILES_PER_BATCH = 100
|
|
384
|
+
MAX_BYTES_PER_BATCH = 10 * 1024 * 1024 * 1024 # 10 GB
|
|
385
|
+
|
|
386
|
+
batches = []
|
|
387
|
+
current_batch = []
|
|
388
|
+
current_batch_size = 0
|
|
389
|
+
|
|
390
|
+
for item in files_to_download:
|
|
391
|
+
size = item[2]
|
|
392
|
+
if size >= MAX_BYTES_PER_BATCH and not current_batch:
|
|
393
|
+
batches.append([item])
|
|
394
|
+
continue
|
|
395
|
+
|
|
396
|
+
if len(current_batch) >= MAX_FILES_PER_BATCH or current_batch_size + size > MAX_BYTES_PER_BATCH:
|
|
397
|
+
batches.append(current_batch)
|
|
398
|
+
current_batch = []
|
|
399
|
+
current_batch_size = 0
|
|
400
|
+
|
|
401
|
+
current_batch.append(item)
|
|
402
|
+
current_batch_size += size
|
|
403
|
+
|
|
404
|
+
if current_batch:
|
|
405
|
+
batches.append(current_batch)
|
|
406
|
+
|
|
407
|
+
print(f"[bold cyan]Downloading {len(files_to_download)} files in {len(batches)} batches using hf-xet...[/bold cyan]")
|
|
408
|
+
|
|
409
|
+
refresh_url = f"{server_url.rstrip('/')}/api/v1/oauth/device/cas-token/"
|
|
410
|
+
refresh_headers = {"Authorization": f"Bearer {cas_token}"}
|
|
411
|
+
|
|
412
|
+
with Progress(
|
|
413
|
+
TextColumn("[progress.description]{task.description}"),
|
|
414
|
+
BarColumn(),
|
|
415
|
+
TaskProgressColumn(),
|
|
416
|
+
TimeElapsedColumn(),
|
|
417
|
+
) as progress:
|
|
418
|
+
total_size = sum(item[2] for item in files_to_download)
|
|
419
|
+
overall_task = progress.add_task("[green]Total Download Progress...", total=total_size)
|
|
420
|
+
|
|
421
|
+
completed_in_previous_batches = 0
|
|
422
|
+
|
|
423
|
+
session = hf_xet.XetSession()
|
|
424
|
+
for batch_index, batch_items in enumerate(batches, 1):
|
|
425
|
+
progress.print(f"[cyan]Processing batch {batch_index}/{len(batches)}...[/cyan]")
|
|
426
|
+
|
|
427
|
+
# capture current completed_in_previous_batches in a local scope to avoid late binding issues
|
|
428
|
+
file_tasks = {}
|
|
429
|
+
|
|
430
|
+
def make_progress_handler(base_completed):
|
|
431
|
+
def on_progress(group_report, item_reports):
|
|
432
|
+
progress.update(overall_task, completed=base_completed + group_report.total_bytes_completed)
|
|
433
|
+
for item_id, item in item_reports.items():
|
|
434
|
+
if item_id not in file_tasks:
|
|
435
|
+
file_tasks[item_id] = progress.add_task(f"[blue]{item.item_name}", total=item.total_bytes)
|
|
436
|
+
|
|
437
|
+
progress.update(file_tasks[item_id], completed=item.bytes_completed)
|
|
438
|
+
|
|
439
|
+
# Hide completed tasks to keep the UI clean when downloading thousands of files
|
|
440
|
+
if item.bytes_completed >= item.total_bytes:
|
|
441
|
+
progress.update(file_tasks[item_id], visible=False)
|
|
442
|
+
return on_progress
|
|
443
|
+
|
|
444
|
+
try:
|
|
445
|
+
with session.new_file_download_group(
|
|
446
|
+
endpoint=cas_endpoint,
|
|
447
|
+
token=cas_token,
|
|
448
|
+
token_expiry_unix_secs=expires_at,
|
|
449
|
+
token_refresh_url=refresh_url,
|
|
450
|
+
token_refresh_headers=refresh_headers,
|
|
451
|
+
progress_callback=make_progress_handler(completed_in_previous_batches),
|
|
452
|
+
progress_interval_ms=500,
|
|
453
|
+
) as group:
|
|
454
|
+
for dest_path, hash_val, size_val in batch_items:
|
|
455
|
+
info = hf_xet.XetFileInfo(hash_val, size_val)
|
|
456
|
+
group.start_download_file(info, dest_path)
|
|
457
|
+
|
|
458
|
+
batch_size = sum(item[2] for item in batch_items)
|
|
459
|
+
completed_in_previous_batches += batch_size
|
|
460
|
+
progress.update(overall_task, completed=completed_in_previous_batches)
|
|
461
|
+
progress.print(f"[bold green][OK] Batch {batch_index} completed successfully![/bold green]")
|
|
462
|
+
except Exception as e:
|
|
463
|
+
progress.print(f"[bold red]ERROR: Download failed for batch {batch_index}: {e}[/bold red]")
|
|
464
|
+
raise typer.Exit(1)
|
|
465
|
+
|
|
466
|
+
print("[bold green][OK] Download completed successfully![/bold green]")
|
|
407
467
|
|
|
408
468
|
else:
|
|
409
469
|
# Upload flow: local source -> acc://[project_slug]/[remote_path]
|
|
@@ -456,65 +516,137 @@ def copy(
|
|
|
456
516
|
print("[bold yellow]No files to upload.[/bold yellow]")
|
|
457
517
|
raise typer.Exit(0)
|
|
458
518
|
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
519
|
+
MAX_FILES_PER_BATCH = 100
|
|
520
|
+
MAX_BYTES_PER_BATCH = 10 * 1024 * 1024 * 1024 # 10 GB
|
|
521
|
+
|
|
522
|
+
batches = []
|
|
523
|
+
current_batch_paths = []
|
|
524
|
+
current_batch_remote = []
|
|
525
|
+
current_batch_size = 0
|
|
526
|
+
|
|
527
|
+
for local_p, remote_n in zip(local_paths, remote_filenames):
|
|
528
|
+
size = local_p.stat().st_size
|
|
529
|
+
if size >= MAX_BYTES_PER_BATCH and not current_batch_paths:
|
|
530
|
+
batches.append(([local_p], [remote_n]))
|
|
531
|
+
continue
|
|
532
|
+
|
|
533
|
+
if len(current_batch_paths) >= MAX_FILES_PER_BATCH or current_batch_size + size > MAX_BYTES_PER_BATCH:
|
|
534
|
+
batches.append((current_batch_paths, current_batch_remote))
|
|
535
|
+
current_batch_paths = []
|
|
536
|
+
current_batch_remote = []
|
|
537
|
+
current_batch_size = 0
|
|
538
|
+
|
|
539
|
+
current_batch_paths.append(local_p)
|
|
540
|
+
current_batch_remote.append(remote_n)
|
|
541
|
+
current_batch_size += size
|
|
542
|
+
|
|
543
|
+
if current_batch_paths:
|
|
544
|
+
batches.append((current_batch_paths, current_batch_remote))
|
|
545
|
+
|
|
546
|
+
print(f"[bold cyan]Uploading {len(local_paths)} files in {len(batches)} batches using hf-xet...[/bold cyan]")
|
|
547
|
+
|
|
548
|
+
refresh_url = f"{server_url.rstrip('/')}/api/v1/oauth/device/cas-token/"
|
|
549
|
+
refresh_headers = {"Authorization": f"Bearer {cas_token}"}
|
|
550
|
+
|
|
551
|
+
with Progress(
|
|
552
|
+
TextColumn("[progress.description]{task.description}"),
|
|
553
|
+
BarColumn(),
|
|
554
|
+
TaskProgressColumn(),
|
|
555
|
+
TimeElapsedColumn(),
|
|
556
|
+
) as progress:
|
|
557
|
+
total_size = sum(p.stat().st_size for p in local_paths)
|
|
558
|
+
overall_task = progress.add_task("[green]Total Upload Progress...", total=total_size)
|
|
559
|
+
|
|
560
|
+
completed_in_previous_batches = 0
|
|
561
|
+
|
|
562
|
+
session = hf_xet.XetSession()
|
|
563
|
+
for batch_index, (b_paths, b_remotes) in enumerate(batches, 1):
|
|
564
|
+
progress.print(f"[cyan]Processing batch {batch_index}/{len(batches)}...[/cyan]")
|
|
565
|
+
|
|
566
|
+
file_tasks = {}
|
|
567
|
+
|
|
568
|
+
def make_progress_handler(base_completed):
|
|
569
|
+
def on_progress(group_report, item_reports):
|
|
570
|
+
progress.update(overall_task, completed=base_completed + group_report.total_bytes_completed)
|
|
571
|
+
for item_id, item in item_reports.items():
|
|
572
|
+
if item_id not in file_tasks:
|
|
573
|
+
file_tasks[item_id] = progress.add_task(f"[blue]{item.item_name}", total=item.total_bytes)
|
|
574
|
+
|
|
575
|
+
progress.update(file_tasks[item_id], completed=item.bytes_completed)
|
|
576
|
+
|
|
577
|
+
# Hide completed tasks to keep the UI clean
|
|
578
|
+
if item.bytes_completed >= item.total_bytes:
|
|
579
|
+
progress.update(file_tasks[item_id], visible=False)
|
|
580
|
+
return on_progress
|
|
581
|
+
|
|
582
|
+
try:
|
|
583
|
+
with session.new_upload_commit(
|
|
584
|
+
endpoint=cas_endpoint,
|
|
585
|
+
token=cas_token,
|
|
586
|
+
token_expiry_unix_secs=expires_at,
|
|
587
|
+
token_refresh_url=refresh_url,
|
|
588
|
+
token_refresh_headers=refresh_headers,
|
|
589
|
+
progress_callback=make_progress_handler(completed_in_previous_batches),
|
|
590
|
+
progress_interval_ms=500,
|
|
591
|
+
) as commit:
|
|
592
|
+
handles = []
|
|
593
|
+
for p in b_paths:
|
|
594
|
+
sha = compute_sha256(str(p))
|
|
595
|
+
h = commit.start_upload_file(str(p), sha256=sha)
|
|
596
|
+
handles.append((p, sha, h))
|
|
597
|
+
|
|
598
|
+
upload_results = [(p, sha, h.result()) for p, sha, h in handles]
|
|
599
|
+
except Exception as e:
|
|
600
|
+
progress.print(f"[bold red]ERROR: Upload failed for batch {batch_index}: {e}[/bold red]")
|
|
601
|
+
raise typer.Exit(1)
|
|
475
602
|
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
"Authorization": f"Bearer {cas_token}"
|
|
493
|
-
}
|
|
603
|
+
progress.print("[cyan]Computing SHA-256 hashes and registering metadata for batch...[/cyan]")
|
|
604
|
+
registration_items = []
|
|
605
|
+
for local_path, remote_filename, (p, sha256_hash, upload_info) in zip(b_paths, b_remotes, upload_results):
|
|
606
|
+
registration_items.append({
|
|
607
|
+
"filename": f"{project_slug}/{remote_filename}",
|
|
608
|
+
"merkle_hash": upload_info.xet_info.hash,
|
|
609
|
+
"sha256": sha256_hash,
|
|
610
|
+
"file_size": upload_info.xet_info.file_size,
|
|
611
|
+
"content_type": "application/octet-stream"
|
|
612
|
+
})
|
|
613
|
+
|
|
614
|
+
headers = {
|
|
615
|
+
"Content-Type": "application/json",
|
|
616
|
+
"X-Project-Slug": project_slug,
|
|
617
|
+
"Authorization": f"Bearer {cas_token}"
|
|
618
|
+
}
|
|
494
619
|
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
620
|
+
try:
|
|
621
|
+
response = requests.post(
|
|
622
|
+
register_url,
|
|
623
|
+
json={"items": registration_items},
|
|
624
|
+
headers=headers,
|
|
625
|
+
verify=(not ACCLI_DEBUG)
|
|
626
|
+
)
|
|
627
|
+
response.raise_for_status()
|
|
628
|
+
|
|
629
|
+
batch_size = sum(p.stat().st_size for p in b_paths)
|
|
630
|
+
completed_in_previous_batches += batch_size
|
|
631
|
+
progress.update(overall_task, completed=completed_in_previous_batches)
|
|
632
|
+
|
|
633
|
+
progress.print(f"[bold green][OK] Batch {batch_index} registered successfully![/bold green]")
|
|
634
|
+
except requests.exceptions.HTTPError as e:
|
|
635
|
+
detail = None
|
|
636
|
+
try:
|
|
637
|
+
detail = e.response.json().get("detail")
|
|
638
|
+
except Exception:
|
|
639
|
+
pass
|
|
640
|
+
if detail:
|
|
641
|
+
progress.print(f"[bold red]ERROR: Bulk metadata registration failed for batch {batch_index}: {detail}[/bold red]")
|
|
642
|
+
else:
|
|
643
|
+
progress.print(f"[bold red]ERROR: Bulk metadata registration failed for batch {batch_index}: {e}[/bold red]")
|
|
644
|
+
raise typer.Exit(1)
|
|
645
|
+
except Exception as e:
|
|
646
|
+
progress.print(f"[bold red]ERROR: Bulk metadata registration failed for batch {batch_index}: {e}[/bold red]")
|
|
647
|
+
raise typer.Exit(1)
|
|
648
|
+
|
|
649
|
+
print("[bold green][OK] Upload completed successfully![/bold green]")
|
|
518
650
|
|
|
519
651
|
|
|
520
652
|
# --- Mount commands group ---
|
|
@@ -1314,21 +1446,40 @@ def mount_status(
|
|
|
1314
1446
|
args = [str(hf_mount_bin), "status"]
|
|
1315
1447
|
|
|
1316
1448
|
import subprocess
|
|
1449
|
+
active_mounts = []
|
|
1450
|
+
|
|
1451
|
+
# 1. Query hf-mount status directly
|
|
1317
1452
|
try:
|
|
1318
1453
|
result = subprocess.run(args, capture_output=True, text=True)
|
|
1319
1454
|
if result.returncode == 0:
|
|
1320
1455
|
stdout = result.stdout.strip()
|
|
1321
|
-
if
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1456
|
+
if stdout:
|
|
1457
|
+
for line in stdout.splitlines():
|
|
1458
|
+
if line.strip():
|
|
1459
|
+
active_mounts.append(line.strip())
|
|
1460
|
+
except Exception:
|
|
1461
|
+
pass
|
|
1462
|
+
|
|
1463
|
+
# 2. Fallback: parse system mount table on Linux to find localhost NFS or FUSE hf-mounts
|
|
1464
|
+
if not active_mounts and platform.system() == "Linux":
|
|
1465
|
+
try:
|
|
1466
|
+
if os.path.exists("/proc/mounts"):
|
|
1467
|
+
with open("/proc/mounts", "r") as f:
|
|
1468
|
+
for line in f:
|
|
1469
|
+
parts = line.strip().split()
|
|
1470
|
+
if len(parts) >= 3:
|
|
1471
|
+
dev, mnt_point, fstype = parts[0], parts[1], parts[2]
|
|
1472
|
+
is_nfs_localhost = (fstype == "nfs" and dev.startswith("127.0.0.1:"))
|
|
1473
|
+
is_fuse_hf = ("hf-mount" in dev or "hf-mount" in fstype)
|
|
1474
|
+
if is_nfs_localhost or is_fuse_hf:
|
|
1475
|
+
active_mounts.append(f"{mnt_point} ({fstype} via {dev})")
|
|
1476
|
+
except Exception:
|
|
1477
|
+
pass
|
|
1478
|
+
|
|
1479
|
+
if not active_mounts:
|
|
1480
|
+
print("[cyan]No running mounts found.[/cyan]")
|
|
1481
|
+
else:
|
|
1482
|
+
print("[bold cyan]Active Mounts:[/bold cyan]")
|
|
1483
|
+
for m in active_mounts:
|
|
1484
|
+
print(m)
|
|
1334
1485
|
|
|
@@ -113,7 +113,7 @@ class AccliGuiApp(tk.Tk):
|
|
|
113
113
|
self.build_auth_tab()
|
|
114
114
|
self.build_logs_tab()
|
|
115
115
|
|
|
116
|
-
def run_cli_async(self, args, on_done=None, log_to_viewer=True):
|
|
116
|
+
def run_cli_async(self, args, on_done=None, log_to_viewer=True, elevate=False):
|
|
117
117
|
"""Runs the CLI binary in a background thread to prevent UI freezing."""
|
|
118
118
|
def worker():
|
|
119
119
|
# Determine how to invoke accli
|
|
@@ -124,6 +124,13 @@ class AccliGuiApp(tk.Tk):
|
|
|
124
124
|
# Script mode
|
|
125
125
|
cmd = [sys.executable, "-m", "accli.cli"] + args
|
|
126
126
|
|
|
127
|
+
if elevate and platform.system() != "Windows":
|
|
128
|
+
import shutil
|
|
129
|
+
if shutil.which("pkexec"):
|
|
130
|
+
cmd = ["pkexec"] + cmd
|
|
131
|
+
else:
|
|
132
|
+
cmd = ["sudo"] + cmd
|
|
133
|
+
|
|
127
134
|
try:
|
|
128
135
|
if log_to_viewer:
|
|
129
136
|
self.append_log(f"Executing: {' '.join(cmd)}\n")
|
|
@@ -491,7 +498,7 @@ class AccliGuiApp(tk.Tk):
|
|
|
491
498
|
self.status_bar_val.set("Mount Failed")
|
|
492
499
|
messagebox.showerror("Error", f"Failed to mount project '{slug}'. See logs for details.")
|
|
493
500
|
|
|
494
|
-
self.run_cli_async(args, on_done=on_done)
|
|
501
|
+
self.run_cli_async(args, on_done=on_done, elevate=True)
|
|
495
502
|
|
|
496
503
|
def action_mount_stop(self):
|
|
497
504
|
selected = self.mounts_list.get(tk.ACTIVE)
|
|
@@ -521,7 +528,7 @@ class AccliGuiApp(tk.Tk):
|
|
|
521
528
|
self.status_bar_val.set("Stop Failed")
|
|
522
529
|
messagebox.showerror("Error", f"Failed to stop mount at {mount_point}.")
|
|
523
530
|
|
|
524
|
-
self.run_cli_async(args, on_done=on_done)
|
|
531
|
+
self.run_cli_async(args, on_done=on_done, elevate=True)
|
|
525
532
|
|
|
526
533
|
# ------------------ COPY TAB ------------------
|
|
527
534
|
def build_copy_tab(self):
|
|
@@ -9,7 +9,7 @@ import typer
|
|
|
9
9
|
from rich import print
|
|
10
10
|
from rich.progress import Progress, TextColumn, BarColumn, TaskProgressColumn, TimeElapsedColumn
|
|
11
11
|
|
|
12
|
-
DEFAULT_VERSION = "v0.6.5-acc-pr140-
|
|
12
|
+
DEFAULT_VERSION = "v0.6.5-acc-pr140-p5"
|
|
13
13
|
BINARY_DIR = Path.home() / ".accli" / "bin"
|
|
14
14
|
|
|
15
15
|
# Regex to safely validate version strings to prevent URL manipulation or directory traversal
|
|
@@ -4,7 +4,21 @@ from tinydb import TinyDB
|
|
|
4
4
|
|
|
5
5
|
|
|
6
6
|
def get_db_path():
|
|
7
|
-
|
|
7
|
+
sudo_user = os.environ.get("SUDO_USER")
|
|
8
|
+
pkexec_uid = os.environ.get("PKEXEC_UID")
|
|
9
|
+
|
|
10
|
+
if (sudo_user or pkexec_uid) and os.name != "nt":
|
|
11
|
+
try:
|
|
12
|
+
import pwd
|
|
13
|
+
if pkexec_uid:
|
|
14
|
+
home = pwd.getpwuid(int(pkexec_uid)).pw_dir
|
|
15
|
+
else:
|
|
16
|
+
home = pwd.getpwnam(sudo_user).pw_dir
|
|
17
|
+
except Exception:
|
|
18
|
+
home = os.path.expanduser("~")
|
|
19
|
+
else:
|
|
20
|
+
home = os.path.expanduser("~")
|
|
21
|
+
|
|
8
22
|
token_directory = f"{home}/.accli"
|
|
9
23
|
|
|
10
24
|
if not os.path.exists(token_directory):
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: accli
|
|
3
|
-
Version: 2.0.
|
|
3
|
+
Version: 2.0.0rc4
|
|
4
4
|
Summary: IIASA Accelerator Client
|
|
5
5
|
Author-email: Wrufesh S <wrufesh@gmail.com>
|
|
6
6
|
License: The MIT License (MIT)
|
|
@@ -39,7 +39,7 @@ Requires-Dist: requests>=2.25.1
|
|
|
39
39
|
Requires-Dist: jsonschema>=4.5.1
|
|
40
40
|
Requires-Dist: pydantic>=2.0
|
|
41
41
|
Requires-Dist: GitPython
|
|
42
|
-
Requires-Dist: hf-xet
|
|
42
|
+
Requires-Dist: hf-xet==1.5.2
|
|
43
43
|
Dynamic: license-file
|
|
44
44
|
|
|
45
45
|
# Accelerator Terminal Client and Python API
|
|
@@ -138,8 +138,8 @@ Usage: accli [OPTIONS] COMMAND [ARGS]...
|
|
|
138
138
|
|
|
139
139
|
### Handling self-signed certificates
|
|
140
140
|
|
|
141
|
-
1. Copy and paste certificates from `<control_services_backend>/certs/
|
|
142
|
-
`./certs/dev-backend.crt` and `<accelerator_service>/minio_certs/
|
|
141
|
+
1. Copy and paste certificates from `<control_services_backend>/certs/ca.crt` as
|
|
142
|
+
`./certs/dev-backend.crt` and `<accelerator_service>/minio_certs/ca.crt` as
|
|
143
143
|
`./certs/dev-minio-ca.crt`.
|
|
144
144
|
2. Run
|
|
145
145
|
```
|
|
@@ -20,7 +20,7 @@ dependencies = [
|
|
|
20
20
|
"jsonschema>=4.5.1", # 4.5.1+ is safe across libraries
|
|
21
21
|
"pydantic>=2.0", # Major version 2 required if you're using v2 features
|
|
22
22
|
"GitPython",
|
|
23
|
-
"hf-xet
|
|
23
|
+
"hf-xet==1.5.2",
|
|
24
24
|
]
|
|
25
25
|
classifiers = [
|
|
26
26
|
"Programming Language :: Python :: 3",
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|