hostctl 0.1.36 → 0.1.39

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.
package/README.md CHANGED
@@ -202,7 +202,7 @@ johndoe: - age1jx3ec0y5hctkfwfut4p8cj08ulcezqex8r8slwlwgwy8k2plx5ksdzy8ll
202
202
  janedoe: - age1qdqv054hmfmnxk56dkjm3cq8qz4lxfl7tvqfp0rqdl0nyygjlccqw0ly40
203
203
 
204
204
  hostctl on  main [!] is 📦 v0.1.31 via 🥟 v1.2.10 via  v23.11.0
205
- ❯ AGE_IDS="example/sample-age-ids/*.priv" hostctl inventory -c example/hostctl.yaml decrypt -v
205
+ ❯ AGE_IDS="example/sample-age-ids/\*.priv" hostctl inventory -c example/hostctl.yaml decrypt -v
206
206
  Decrypting inventory file: example/hostctl.yaml
207
207
  Decrypted with one or more of the following private keys:
208
208
  example/sample-age-ids/johndoe.priv
@@ -227,7 +227,7 @@ ids:
227
227
  johndoe: - age1jx3ec0y5hctkfwfut4p8cj08ulcezqex8r8slwlwgwy8k2plx5ksdzy8ll
228
228
  janedoe: - age1qdqv054hmfmnxk56dkjm3cq8qz4lxfl7tvqfp0rqdl0nyygjlccqw0ly40
229
229
 
230
- ```
230
+ ````
231
231
 
232
232
  ## Vision
233
233
 
@@ -294,6 +294,24 @@ The vision for `hostctl` is to be an Ansible replacement that is significantly e
294
294
  - **`core.pkg.remove`**: Removes a package.
295
295
  - **`core.pkg.update`**: Updates a package.
296
296
 
297
+ #### Abstract Package Management (Cross-Platform, Non-Interactive)
298
+ - **`core.pkg.abstract-install`**: Install packages using auto-detected or specified package manager (non-interactive, no progress bars).
299
+ - **`core.pkg.abstract-uninstall`**: Uninstall packages using auto-detected or specified package manager (non-interactive, no progress bars).
300
+ - **`core.pkg.abstract-update`**: Update package sources using auto-detected or specified package manager (non-interactive, no progress bars).
301
+ - **`core.pkg.abstract-upgrade`**: Upgrade packages using auto-detected or specified package manager (non-interactive, no progress bars).
302
+ - **`core.pkg.abstract-search`**: Search for packages using auto-detected or specified package manager (non-interactive, no progress bars).
303
+ - **`core.pkg.abstract-list`**: List installed packages using auto-detected or specified package manager (non-interactive, no progress bars).
304
+ - **`core.pkg.abstract-clean`**: Clean package cache using auto-detected or specified package manager (non-interactive, no progress bars).
305
+
306
+ **Supported Package Managers**: Apt, Apk, Yum, Dnf, Pacman, Zypper, Eopkg, Winget, Choco, Brew
307
+
308
+ **Non-Interactive Features**: All abstract package management tasks run completely non-interactively with:
309
+ - No user prompts or confirmations
310
+ - No progress bars or interactive indicators
311
+ - No color output or terminal formatting
312
+ - Automatic confirmations via environment variables and command flags
313
+ - Clean, machine-readable output suitable for CI/CD pipelines
314
+
297
315
  ### Service and Process Management
298
316
  - **`core.systemd.disable`**: Disables a systemd service.
299
317
  - **`core.systemd.enable`**: Enables a systemd service.
@@ -437,6 +455,186 @@ hostctl bundle
437
455
  hostctl bundle ./my-hostctl-project
438
456
  ```
439
457
 
458
+ #### `pkg`
459
+
460
+ Manages `hostctl` packages with a comprehensive package management system that maintains a manifest of installed packages and their tasks.
461
+
462
+ **Usage:**
463
+
464
+ ```bash
465
+ hostctl pkg [subcommand]
466
+ ```
467
+
468
+ **Subcommands:**
469
+
470
+ - **`create <package-name>`**
471
+ - Scaffolds a new `hostctl` package in the current directory.
472
+ - **Usage:** `hostctl pkg create my-new-task`
473
+ - **Options:**
474
+ - `--lang <language>`: Specify the language for the package (`typescript` or `javascript`). Defaults to `typescript`.
475
+
476
+ - **`install <package-source>`**
477
+ - Installs a `hostctl` package and maintains it in the package manifest.
478
+ - The `<package-source>` can be a local path, a Git URL, or an npm package name.
479
+ - **Usage:**
480
+
481
+ ```bash
482
+ # Install from a local directory
483
+ hostctl pkg install ./my-local-task
484
+
485
+ # Install from a Git repository
486
+ hostctl pkg install https://github.com/user/my-hostctl-task.git
487
+
488
+ # Install from npm
489
+ hostctl pkg install my-npm-hostctl-task
490
+ ```
491
+
492
+ - **`list`** (alias: `ls`)
493
+ - Lists all installed `hostctl` packages with their discovered tasks.
494
+ - **Usage:** `hostctl pkg list` or `hostctl pkg ls`
495
+ - **Output Example:**
496
+
497
+ ```
498
+ Found 1 package(s):
499
+
500
+ • hostsfile (v1.0.0) - hostsfile package [https_github.com_davidkellis_hostsfile]
501
+ └─ add_entry
502
+ ```
503
+
504
+ - **`remove <package-identifier>`** (alias: `rm`)
505
+ - Removes an installed `hostctl` package and updates the manifest.
506
+ - The `<package-identifier>` can be either the package name or the directory name.
507
+ - **Usage:** `hostctl pkg remove my-package` or `hostctl pkg rm my-package`
508
+
509
+ ## Package Management System
510
+
511
+ `hostctl` includes a comprehensive package management system that allows you to install, manage, and run tasks from external packages. This system maintains a manifest of installed packages and provides intelligent task discovery and resolution.
512
+
513
+ ### Package Manifest
514
+
515
+ The package management system maintains a `manifest.json` file in `~/.hostctl/packages/` that tracks all installed packages. This manifest contains:
516
+
517
+ - **Package Source Mapping**: Each package source (URL, Git repo, local path) is mapped to package information
518
+ - **Package Metadata**: Name, version, description, and installation directory
519
+ - **Task Discovery**: Automatically discovered tasks within each package
520
+ - **Persistent Storage**: Package information persists across sessions
521
+
522
+ ### Task Discovery
523
+
524
+ When a package is installed, `hostctl` automatically discovers tasks within the package by scanning for:
525
+
526
+ - **TypeScript/JavaScript Files**: `.ts` and `.js` files in the package root
527
+ - **Subdirectories**: Tasks in `tasks/` and `src/` subdirectories
528
+ - **Default Tasks**: Common default task files like `index.ts`, `main.ts`, etc.
529
+
530
+ ### Task Resolution
531
+
532
+ The `hostctl run` command now supports multiple resolution strategies:
533
+
534
+ 1. **Built-in Core Tasks**: `hostctl run core.echo hello`
535
+ 2. **Installed Package Tasks**:
536
+ - Default task: `hostctl run https://github.com/user/repo`
537
+ - Specific task: `hostctl run https://github.com/user/repo:taskname`
538
+ 3. **Relative Paths**: `hostctl run ./script.ts`
539
+
540
+ ### Package Resolution Examples
541
+
542
+ ```bash
543
+ # Install a package
544
+ hostctl pkg install https://github.com/davidkellis/hostsfile
545
+
546
+ # List installed packages and their tasks
547
+ hostctl pkg list
548
+
549
+ # Run the default task from an installed package
550
+ hostctl run https://github.com/davidkellis/hostsfile names:foo,bar
551
+
552
+ # Run a specific task from an installed package
553
+ hostctl run https://github.com/davidkellis/hostsfile:add_entry names:foo,bar
554
+
555
+ # Run core tasks
556
+ hostctl run core.echo hello
557
+
558
+ # Run relative paths (still works)
559
+ hostctl run example/echo.ts hello
560
+ ```
561
+
562
+ ### Abstract Package Management Examples
563
+
564
+ The abstract package management system automatically detects your platform's package manager and provides a unified interface:
565
+
566
+ ```bash
567
+ # Auto-detect and update package sources
568
+ hostctl run core.pkg.abstractUpdate
569
+
570
+ # Search for packages (auto-detects package manager)
571
+ hostctl run core.pkg.abstractSearch query:nginx
572
+
573
+ # Install packages (auto-detects package manager)
574
+ hostctl run core.pkg.abstractInstall package:curl
575
+
576
+ # Install multiple packages
577
+ hostctl run core.pkg.abstractInstall package:curl,wget,git
578
+
579
+ # Force use of specific package manager
580
+ hostctl run core.pkg.abstractInstall package:nginx packageManager:apt
581
+
582
+ # List installed packages
583
+ hostctl run core.pkg.abstractList
584
+
585
+ # Upgrade all packages
586
+ hostctl run core.pkg.abstractUpgrade
587
+
588
+ # Clean package cache
589
+ hostctl run core.pkg.abstractClean
590
+ ```
591
+
592
+ **Supported Platforms**: Ubuntu/Debian (apt), Alpine (apk), CentOS/RHEL (yum/dnf), Arch Linux (pacman), openSUSE (zypper), Solus (eopkg), Windows (winget/choco), macOS (brew)
593
+
594
+ ### Package Structure
595
+
596
+ A `hostctl` package should follow this structure:
597
+
598
+ ```
599
+ my-hostctl-package/
600
+ ├── package.json # Package metadata
601
+ ├── index.ts # Default task (optional)
602
+ ├── task1.ts # Individual task
603
+ ├── task2.ts # Individual task
604
+ ├── tasks/ # Task subdirectory (optional)
605
+ │ ├── task3.ts
606
+ │ └── task4.ts
607
+ └── src/ # Source subdirectory (optional)
608
+ ├── task5.ts
609
+ └── task6.ts
610
+ ```
611
+
612
+ ### Package Dependencies
613
+
614
+ Each installed package maintains its own `node_modules` directory, ensuring that:
615
+
616
+ - Dependencies are isolated per package
617
+ - The `hostctl` runtime is available to each package
618
+ - Packages can have their own version requirements
619
+
620
+ ### Package Sources
621
+
622
+ The package management system supports multiple source types:
623
+
624
+ - **Local Paths**: `./my-package` or `/path/to/package`
625
+ - **Git Repositories**: `https://github.com/user/repo.git` or `git@github.com:user/repo.git`
626
+ - **GitHub Shorthand**: `github.com/user/repo`
627
+ - **NPM Packages**: `@scope/package-name` or `package-name`
628
+
629
+ ### Task Execution Context
630
+
631
+ When running tasks from installed packages:
632
+
633
+ - Tasks have access to the full `hostctl` runtime API
634
+ - Package dependencies are available in the task's `node_modules`
635
+ - Tasks can use all core tasks and utilities
636
+ - Remote execution works seamlessly with installed package tasks
637
+
440
638
  #### `inventory` (alias: `i`)
441
639
 
442
640
  Manages and displays information about the host inventory.
@@ -565,7 +763,6 @@ When working with the inventory decryption functionality, it's important to unde
565
763
  1. **Hostname Storage**: In the YAML configuration, hostnames are stored as the keys in the `hosts` map, not as properties inside each host object. When a host is serialized to YAML via `Host.toYAML()`, the `hostname` field is not included because it's represented by the map key.
566
764
 
567
765
  2. **Verbosity and Logging**: The inventory decryption process follows the general verbosity rules of hostctl:
568
-
569
766
  - By default, only ERROR level messages are shown
570
767
  - INFO level messages (like "No encrypted secrets found to decrypt. Inventory is already decrypted.") require the `-v` flag to be visible
571
768
  - For idempotent operations like decrypting an already-decrypted inventory, you need to use the `-v` flag to see confirmation messages
@@ -705,7 +902,7 @@ The `TaskContext` is the heart of the scripting API. It's passed to your `run` f
705
902
  - `ssh(hostSelector, taskDefinition, params?): Promise<TRemoteReturn[] | Error>`: Executes another `hostctl` task on one or more remote hosts.
706
903
  - **`hostSelector`**: A string, an array of strings, a `Host` object, or an array of `Host` objects to specify the target hosts from your inventory.
707
904
  - **`taskDefinition`**: The task to execute on the remote host(s).
708
- - **`params`**: The parameters to pass to the remote task.
905
+ - `params`: The parameters to pass to the remote task.
709
906
  - `file: FileOperations`: An object for performing file operations on the target host.
710
907
  - `read(path)`: Reads the content of a file.
711
908
  - `write(path, content)`: Writes content to a file.
@@ -835,191 +1032,6 @@ async function run(context: TaskContext<InstallNginxParams>): Promise<void> {
835
1032
  export default task(run, 'Install Nginx on a Debian-based system');
836
1033
  ```
837
1034
 
838
- ## Roadmap: Expanding Capabilities
839
-
840
- To further enhance `hostctl` and position it as a comprehensive Ansible alternative, the following task primitives are planned for future development. These aim to provide built-in support for common system administration and automation workflows:
841
-
842
- 1. **File Management:**
843
- - `file.replace` (regex substitution)
844
- - `file.fetch` (remote → local)
845
-
846
- 2. **Package Management:**
847
- - `pkg.purge`
848
- - `pkg.autoremove`
849
-
850
- 3. **User & Group Management:**
851
- - `user.password` (change password)
852
-
853
- 4. **System Operations:**
854
- - `system.timezone`
855
- - `system.mount`
856
-
857
- 5. **Archive Management:**
858
- - `archive.compress`
859
- - `archive.extract`
860
- - `archive.unarchive` (auto-detect format)
861
-
862
- 6. **Networking & Firewall:**
863
- - `firewall.status`
864
- - `firewall.add_rule`
865
- - `firewall.remove_rule`
866
- - `network.interfaces`
867
- - `network.get_facts`
868
-
869
- 7. **Templating & Control Flow:**
870
- - Enhanced templating (diff mode, variables, idempotent)
871
- - `retry`
872
- - `until` (wait for condition)
873
- - `include_tasks` (run another script)
874
- - `wait_for`
875
- - `assert`
876
- - Improved looping & retry helpers
877
-
878
- This roadmap will evolve, and contributions are welcome!
879
-
880
- 3. **Define ResultTypes** (optional but recommended): Specify the structure of the result your task will return (e.g., `MyTaskResult`).
881
- 4. **Implement the `run` function**: This is the core logic of your task.
882
- 5. **Export the task**: Use `export default task(runFunction, {description: "Optional description"});`.
883
-
884
- Here's a template:
885
-
886
- ```typescript
887
- // my-task.ts
888
- import { task, type TaskContext, type IInvocation, type LogLevel } from 'hostctl'; // Adjust path if developing hostctl itself
889
- // For users of the hostctl package, it would typically be:
890
- // import { task, type TaskContext, type IInvocation, type LogLevel, Verbosity } from "hostctl";
891
-
892
- // (If developing hostctl, Verbosity might be imported from a relative path like "../app")
893
- import { Verbosity } from '../app'; // Example for internal development path
894
-
895
- // 1. Define the structure of parameters your task expects
896
- export type MyTaskParams = {
897
- targetSystem: string;
898
- enableFeature?: boolean;
899
- };
900
-
901
- // 2. Define the structure of the result your task will return
902
- export interface MyTaskResult {
903
- success: boolean;
904
- details?: string;
905
- }
906
-
907
- // 3. Implement the core task logic in a 'run' function
908
- async function run(
909
- context: TaskContext<MyTaskParams>, // First argument is the TaskContext
910
- ): Promise<MyTaskResult> {
911
- // Access parameters passed to the script via context.params
912
- const { targetSystem, enableFeature = false } = context.params;
913
-
914
- // Use 'this' for invocation-specific operations
915
- this.log(Verbosity.INFO as LogLevel, `Configuring ${targetSystem}...`);
916
-
917
- try {
918
- // Use context for runtime utilities like 'exec' or 'file' operations
919
- const { stdout } = await context.exec(
920
- `configure-system --target ${targetSystem} ${enableFeature ? '--enable' : ''}`,
921
- );
922
- this.log(Verbosity.DEBUG as LogLevel, `Configuration output: ${stdout}`);
923
-
924
- // Example of using this.sh (often a shorthand for context.exec for simple commands)
925
- // const { stdout: diskSpace } = await this.sh("df -h /");
926
- // this.log(Verbosity.INFO as LogLevel, `Disk space: ${diskSpace}`);
927
-
928
- return { success: true, details: 'System configured successfully.' };
929
- } catch (error: any) {
930
- this.log(Verbosity.ERROR as LogLevel, `Error during configuration: ${error.message}`);
931
- return { success: false, details: error.message };
932
- }
933
- }
934
-
935
- // 4. Export the task, optionally with a dynamic description
936
- // The '{{ args?.join(" ") }}' uses Handlebars templating.
937
- // 'args' needs to be available in the template context for this to work.
938
- export default task(run, 'Configures {{ targetSystem }} with feature: {{ enableFeature }}');
939
- ```
940
-
941
- ### The `run` Function Signature
942
-
943
- The `run` function is where your task's main logic resides. It must conform to the following signature:
944
-
945
- `async function run(context: TaskContext<TParams>): Promise<TResult>`
946
-
947
- - **`context: TaskContext<TParams>`**: This is the **first and only argument** passed to your `run` function. It's a crucial object providing access to:
948
-
949
- - `context.params: TParams`: An object containing the parameters passed to your script from the command line (e.g., `args:key,value` or via `--params <json_string>`). `TParams` is a type interface you define to structure these expected parameters.
950
- - `context.log`, `context.exec`: The same logging and command execution functions available via `this`.
951
- - `context.file: FileSystemOperations`: An object with methods for file system operations (`read`, `write`, `exists`, `mkdir`, `rm`) that work on the target host (local or remote).
952
- - `context.host?: Host`: If the task is executing on a remote host, this object provides details about that host.
953
- - `context.cwd: string`: The current working directory of the task.
954
-
955
- - **`Promise<TResult>`**: Your `run` function must be `async` and should return a `Promise`. The value this promise resolves to is considered the result of your task. `TResult` is a type interface you define for structuring this result.
956
-
957
- ### Defining Parameters (`TParams`) and Results (`TResult`)
958
-
959
- It's highly recommended to define TypeScript interfaces or types for your task's parameters (`TParams`) and its return value (`TResult`). This greatly improves code clarity, maintainability, and enables type checking.
960
-
961
- ```typescript
962
- // For parameters
963
- export type MyScriptParams = {
964
- inputFile: string;
965
- outputDir?: string;
966
- forceOverwrite: boolean;
967
- };
968
-
969
- // For results
970
- export interface MyScriptResult {
971
- filesProcessed: number;
972
- errorsEncountered: number;
973
- outputLocation?: string;
974
- }
975
- ```
976
-
977
- ### Example: The `echo` Task (from `src/core/echo.ts`)
978
-
979
- This task demonstrates the core concepts:
980
-
981
- ```typescript
982
- import { task, type TaskContext, type IInvocation, type LogLevel } from 'hostctl';
983
- import { Verbosity } from 'hostctl/app'; // Path for user-facing package
984
-
985
- // 1. Define Parameters
986
- export type EchoParams = {
987
- args: string[]; // Expects an array of strings named 'args'
988
- };
989
-
990
- // 2. Define Result
991
- export interface EchoResult {
992
- stdout: string; // The echoed output
993
- }
994
-
995
- // 3. Implement the 'run' function
996
- async function run(context: TaskContext<EchoParams>): Promise<EchoResult> {
997
- const { params, exec, log } = context;
998
- // Provide a default for args in case it's not passed from the CLI
999
- const { args = [] } = params;
1000
-
1001
- log(Verbosity.DEBUG as LogLevel, `Echoing arguments: ${args.join(' ')}`);
1002
-
1003
- // Use context.exec with an array of command and arguments
1004
- // This is generally safer than manually joining strings for shell execution.
1005
- const commandArray = ['echo', ...args];
1006
- const { stdout } = await exec(commandArray);
1007
-
1008
- return {
1009
- stdout, // Return the standard output of the echo command
1010
- };
1011
- }
1012
-
1013
- // 4. Export the task with a dynamic description
1014
- // The '{{ args?.join(" ") }}' uses Handlebars templating.
1015
- // 'args' needs to be available in the template context for this to work.
1016
- export default task(run, "Echoes the provided arguments: {{ args?.join(' ') }}");
1017
- ```
1018
-
1019
- To run this echo task:
1020
- `hostctl run path/to/echo.ts args:hello,world`
1021
- Inside the `run` function, `context.params` would be `{ args: ["hello", "world"] }`.
1022
-
1023
1035
  ## TODO
1024
1036
 
1025
1037
  - [ ] Further refine the script execution interface.