pwn 0.5.783 → 0.5.785
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.
- checksums.yaml +4 -4
- data/.ruby-version +1 -1
- data/Gemfile +8 -8
- data/README.md +37 -0
- data/Rakefile +19 -1
- data/bin/pwn +2 -1
- data/bin/pwn_setup +1 -1
- data/bin/pwn_web_cache_deception +35 -7
- data/build_gem.sh +1 -1
- data/documentation/AI-Integration.md +7 -5
- data/documentation/Agent-Tool-Registry.md +2 -2
- data/documentation/Backup-Retention.md +82 -0
- data/documentation/Configuration.md +10 -6
- data/documentation/Contributing.md +1 -1
- data/documentation/Cron.md +64 -35
- data/documentation/Diagrams.md +2 -2
- data/documentation/General-PWN-Usage.md +4 -3
- data/documentation/Home.md +6 -5
- data/documentation/How-PWN-Works.md +7 -6
- data/documentation/Installation.md +254 -10
- data/documentation/Mission-Cancellation.md +134 -0
- data/documentation/Operability.md +17 -0
- data/documentation/Persistence.md +8 -6
- data/documentation/Plugin-Degradation.md +12 -0
- data/documentation/Plugins.md +3 -2
- data/documentation/Reinforcement-Learning.md +5 -6
- data/documentation/Runtime-Source-Protection.md +162 -0
- data/documentation/Transparent-Browser.md +11 -0
- data/documentation/Troubleshooting.md +3 -2
- data/documentation/What-is-PWN.md +2 -2
- data/documentation/YubiKey-Vaults.md +183 -0
- data/documentation/YubiKey.md +339 -0
- data/documentation/diagrams/cron-scheduling.svg +130 -128
- data/documentation/diagrams/dot/cron-scheduling.dot +5 -5
- data/documentation/diagrams/dot/persistence-filesystem.dot +5 -3
- data/documentation/diagrams/dot/plugin-ecosystem.dot +2 -2
- data/documentation/diagrams/persistence-filesystem.svg +172 -142
- data/documentation/diagrams/plugin-ecosystem.svg +64 -64
- data/documentation/pwn-ai-Agent.md +10 -4
- data/etc/default_cron/session_cleanup.rb +1245 -22
- data/etc/default_skills/pwn/ai/agent/mission_namespace/SKILL.md +21 -0
- data/etc/default_skills/pwn/ai/agent/tools/browser_act/SKILL.md +45 -0
- data/etc/default_skills/pwn/ai/agent/tools/loot_query/SKILL.md +27 -26
- data/etc/default_skills/pwn/ai/agent/tools/recon_run/SKILL.md +25 -15
- data/etc/default_skills/pwn/ai/agent/tools/workspace_tool/SKILL.md +24 -0
- data/etc/default_skills/pwn/ai/server_tools/SKILL.md +25 -0
- data/etc/default_skills/pwn/backups/SKILL.md +54 -0
- data/etc/default_skills/pwn/plugins/recon/SKILL.md +12 -1
- data/etc/default_skills/pwn/plugins/yubikey/SKILL.md +157 -0
- data/etc/default_skills/pwn/plugins/yubikey/fido/SKILL.md +189 -0
- data/etc/default_skills/pwn/plugins/yubikey/hsmauth/SKILL.md +83 -0
- data/etc/default_skills/pwn/plugins/yubikey/management/SKILL.md +80 -0
- data/etc/default_skills/pwn/plugins/yubikey/oath/SKILL.md +115 -0
- data/etc/default_skills/pwn/plugins/yubikey/openpgp/SKILL.md +144 -0
- data/etc/default_skills/pwn/plugins/yubikey/otp/SKILL.md +97 -0
- data/etc/default_skills/pwn/plugins/yubikey/piv/SKILL.md +110 -0
- data/etc/default_skills/pwn/plugins/yubikey/protocol/SKILL.md +21 -0
- data/etc/default_skills/pwn/plugins/yubikey/security_domain/SKILL.md +75 -0
- data/etc/default_skills/pwn/plugins/yubikey/security_domain/scp/SKILL.md +46 -0
- data/etc/default_skills/pwn/plugins/yubikey/transport/SKILL.md +98 -0
- data/etc/default_skills/pwn/redaction/SKILL.md +13 -1
- data/lib/pwn/ai/agent/dispatch.rb +26 -15
- data/lib/pwn/ai/agent/extrospection.rb +6 -7
- data/lib/pwn/ai/agent/learning.rb +62 -25
- data/lib/pwn/ai/agent/loop.rb +17 -1
- data/lib/pwn/ai/agent/mission_namespace.rb +331 -0
- data/lib/pwn/ai/agent/policy.rb +5 -0
- data/lib/pwn/ai/agent/policy_evaluation.rb +8 -8
- data/lib/pwn/ai/agent/prompt_builder.rb +13 -2
- data/lib/pwn/ai/agent/request_runtime.rb +122 -18
- data/lib/pwn/ai/agent/reward.rb +5 -3
- data/lib/pwn/ai/agent/skill_consolidation.rb +3 -2
- data/lib/pwn/ai/agent/skill_review.rb +9 -5
- data/lib/pwn/ai/agent/swarm.rb +35 -16
- data/lib/pwn/ai/agent/tools/browser_act.rb +32 -0
- data/lib/pwn/ai/agent/tools/loot_query.rb +11 -1
- data/lib/pwn/ai/agent/tools/recon_run.rb +11 -1
- data/lib/pwn/ai/agent/tools/workspace_tool.rb +29 -0
- data/lib/pwn/ai/agent/turn_finalizer.rb +6 -2
- data/lib/pwn/ai/agent.rb +1 -0
- data/lib/pwn/ai/anthropic.rb +8 -33
- data/lib/pwn/ai/cli.rb +34 -5
- data/lib/pwn/ai/gemini.rb +1 -0
- data/lib/pwn/ai/grok.rb +39 -28
- data/lib/pwn/ai/ollama.rb +4 -2
- data/lib/pwn/ai/open_ai.rb +11 -32
- data/lib/pwn/ai/open_web_ui.rb +62 -4
- data/lib/pwn/ai/router.rb +53 -3
- data/lib/pwn/ai/server_tools.rb +231 -0
- data/lib/pwn/ai.rb +1 -0
- data/lib/pwn/backups.rb +347 -0
- data/lib/pwn/config.rb +70 -39
- data/lib/pwn/cron.rb +120 -49
- data/lib/pwn/migrate.rb +150 -68
- data/lib/pwn/module_skills.rb +7 -0
- data/lib/pwn/plugins/ai_sandbox.rb +538 -1
- data/lib/pwn/plugins/authentication_helper.rb +74 -4
- data/lib/pwn/plugins/basic_auth.rb +2 -1
- data/lib/pwn/plugins/binary_parser.rb +5 -4
- data/lib/pwn/plugins/ghidra.rb +4 -3
- data/lib/pwn/plugins/google_workspace.rb +4 -21
- data/lib/pwn/plugins/jobs.rb +1 -1
- data/lib/pwn/plugins/log.rb +21 -3
- data/lib/pwn/plugins/preflight_checker.rb +34 -9
- data/lib/pwn/plugins/recon.rb +4 -1
- data/lib/pwn/plugins/repl/ai.rb +305 -27
- data/lib/pwn/plugins/repl/ai_console.rb +273 -89
- data/lib/pwn/plugins/repl/ai_swarm.rb +181 -62
- data/lib/pwn/plugins/repl/mesh.rb +3 -16
- data/lib/pwn/plugins/repl/vault.rb +4 -15
- data/lib/pwn/plugins/repl.rb +1 -216
- data/lib/pwn/plugins/sandbox/driver.rb +4 -0
- data/lib/pwn/plugins/transparent_browser.rb +215 -0
- data/lib/pwn/plugins/vault.rb +412 -135
- data/lib/pwn/plugins/yubikey/fido.rb +1288 -0
- data/lib/pwn/plugins/yubikey/hsmauth.rb +439 -0
- data/lib/pwn/plugins/yubikey/management.rb +686 -0
- data/lib/pwn/plugins/yubikey/oath.rb +766 -0
- data/lib/pwn/plugins/yubikey/openpgp.rb +1063 -0
- data/lib/pwn/plugins/yubikey/otp.rb +897 -0
- data/lib/pwn/plugins/yubikey/piv.rb +1315 -0
- data/lib/pwn/plugins/yubikey/protocol.rb +341 -0
- data/lib/pwn/plugins/yubikey/security_domain/scp.rb +482 -0
- data/lib/pwn/plugins/yubikey/security_domain.rb +464 -0
- data/lib/pwn/plugins/yubikey/transport.rb +1017 -0
- data/lib/pwn/plugins/yubikey.rb +844 -0
- data/lib/pwn/plugins.rb +1 -0
- data/lib/pwn/redaction.rb +69 -12
- data/lib/pwn/session_trace.rb +4 -0
- data/lib/pwn/sessions.rb +58 -16
- data/lib/pwn/setup.rb +123 -41
- data/lib/pwn/version.rb +1 -1
- data/lib/pwn/www/app_cobalt_io.rb +16 -5
- data/lib/pwn/www/bug_crowd.rb +16 -5
- data/lib/pwn/www/coinbase_pro.rb +16 -5
- data/lib/pwn/www/github.rb +30 -14
- data/lib/pwn/www/paypal.rb +15 -4
- data/lib/pwn/www/synack.rb +16 -5
- data/lib/pwn/www/twitter.rb +17 -6
- data/lib/pwn.rb +1 -0
- data/scripts/build_gate.rb +58 -0
- data/spec/documentation/installation_md_spec.rb +1 -1
- data/spec/fixtures/yubikey/apdu_frames.json +162 -0
- data/spec/integration/authentication_helper_oath_spec.rb +117 -0
- data/spec/integration/build_gate_spec.rb +73 -0
- data/spec/integration/controller_spill_spec.rb +48 -0
- data/spec/integration/harness_process_spec.rb +97 -0
- data/spec/integration/mission_cancel_spec.rb +79 -0
- data/spec/integration/mission_curses_pty_spec.rb +44 -0
- data/spec/integration/persistence_roundtrip_spec.rb +1 -7
- data/spec/integration/readme_security_spec.rb +47 -0
- data/spec/integration/web_cache_deception_yubikey_spec.rb +159 -0
- data/spec/lib/pwn/ai/agent/dispatch_spec.rb +6 -3
- data/spec/lib/pwn/ai/agent/dispatch_vault_safety_spec.rb +86 -0
- data/spec/lib/pwn/ai/agent/injection_guard_spec.rb +2 -0
- data/spec/lib/pwn/ai/agent/learning_spec.rb +4 -4
- data/spec/lib/pwn/ai/agent/loop_artifacts_spec.rb +5 -0
- data/spec/lib/pwn/ai/agent/loop_spec.rb +104 -29
- data/spec/lib/pwn/ai/agent/mission_namespace_spec.rb +138 -0
- data/spec/lib/pwn/ai/agent/policy_spec.rb +27 -0
- data/spec/lib/pwn/ai/agent/request_runtime_spec.rb +48 -0
- data/spec/lib/pwn/ai/agent/shell_search_effect_spec.rb +44 -0
- data/spec/lib/pwn/ai/agent/solve_spec.rb +6 -4
- data/spec/lib/pwn/ai/agent/tools/browser_act_spec.rb +44 -0
- data/spec/lib/pwn/ai/agent/tools/loot_query_yubikey_spec.rb +55 -0
- data/spec/lib/pwn/ai/agent/tools/recon_run_yubikey_spec.rb +43 -0
- data/spec/lib/pwn/ai/agent/tools/workspace_tool_spec.rb +20 -0
- data/spec/lib/pwn/ai/agent/turn_finalizer_spec.rb +7 -0
- data/spec/lib/pwn/ai/anthropic_vault_enrollment_spec.rb +37 -0
- data/spec/lib/pwn/ai/cli_spec.rb +6 -9
- data/spec/lib/pwn/ai/oauth_vault_persistence_spec.rb +195 -0
- data/spec/lib/pwn/ai/open_ai_spec.rb +2 -2
- data/spec/lib/pwn/ai/open_web_ui_workspace_spec.rb +36 -0
- data/spec/lib/pwn/ai/router_spec.rb +31 -3
- data/spec/lib/pwn/ai/server_tools_spec.rb +39 -0
- data/spec/lib/pwn/backups_spec.rb +114 -0
- data/spec/lib/pwn/config_redaction_yubikey_spec.rb +28 -0
- data/spec/lib/pwn/config_yubikey_spec.rb +92 -0
- data/spec/lib/pwn/cron_spec.rb +113 -6
- data/spec/lib/pwn/migrate_spec.rb +108 -5
- data/spec/lib/pwn/migrate_yubikey_spec.rb +128 -0
- data/spec/lib/pwn/module_skills_spec.rb +8 -0
- data/spec/lib/pwn/plugins/ai_sandbox_spec.rb +230 -0
- data/spec/lib/pwn/plugins/authentication_helper_native_yubikey_spec.rb +40 -0
- data/spec/lib/pwn/plugins/authentication_helper_yubikey_spec.rb +90 -0
- data/spec/lib/pwn/plugins/capability_broker/daemon_spec.rb +1 -1
- data/spec/lib/pwn/plugins/google_workspace_spec.rb +17 -35
- data/spec/lib/pwn/plugins/log_spec.rb +7 -0
- data/spec/lib/pwn/plugins/log_yubikey_spec.rb +47 -0
- data/spec/lib/pwn/plugins/preflight_checker_native_yubikey_spec.rb +19 -0
- data/spec/lib/pwn/plugins/preflight_checker_yubikey_spec.rb +55 -0
- data/spec/lib/pwn/plugins/recon_yubikey_spec.rb +43 -0
- data/spec/lib/pwn/plugins/repl/ai_console_spec.rb +125 -78
- data/spec/lib/pwn/plugins/repl/ai_legacy_containment_spec.rb +200 -0
- data/spec/lib/pwn/plugins/repl/ai_swarm_spec.rb +30 -16
- data/spec/lib/pwn/plugins/repl/ai_vault_edit_yubikey_spec.rb +103 -0
- data/spec/lib/pwn/plugins/repl/ai_yubikey_spec.rb +48 -0
- data/spec/lib/pwn/plugins/repl/mesh_vault_persistence_spec.rb +149 -0
- data/spec/lib/pwn/plugins/repl/vault_yaml_serialization_spec.rb +11 -0
- data/spec/lib/pwn/plugins/repl/vault_yubikey_spec.rb +39 -0
- data/spec/lib/pwn/plugins/repl_spec.rb +56 -5
- data/spec/lib/pwn/plugins/transparent_browser_jev_spec.rb +46 -0
- data/spec/lib/pwn/plugins/vault_native_yubikey_spec.rb +29 -0
- data/spec/lib/pwn/plugins/vault_publication_safety_spec.rb +258 -0
- data/spec/lib/pwn/plugins/vault_store_safety_spec.rb +241 -0
- data/spec/lib/pwn/plugins/vault_yubikey_safety_spec.rb +168 -0
- data/spec/lib/pwn/plugins/vault_yubikey_spec.rb +128 -0
- data/spec/lib/pwn/plugins/vault_yubikey_vector_spec.rb +45 -0
- data/spec/lib/pwn/plugins/yubikey/fido_spec.rb +854 -0
- data/spec/lib/pwn/plugins/yubikey/hsmauth_spec.rb +235 -0
- data/spec/lib/pwn/plugins/yubikey/management_spec.rb +277 -0
- data/spec/lib/pwn/plugins/yubikey/oath_spec.rb +349 -0
- data/spec/lib/pwn/plugins/yubikey/openpgp_spec.rb +810 -0
- data/spec/lib/pwn/plugins/yubikey/otp_spec.rb +337 -0
- data/spec/lib/pwn/plugins/yubikey/piv_spec.rb +646 -0
- data/spec/lib/pwn/plugins/yubikey/protocol_secure_spec.rb +28 -0
- data/spec/lib/pwn/plugins/yubikey/protocol_spec.rb +149 -0
- data/spec/lib/pwn/plugins/yubikey/protocol_vectors_spec.rb +18 -0
- data/spec/lib/pwn/plugins/yubikey/security_domain/scp_spec.rb +256 -0
- data/spec/lib/pwn/plugins/yubikey/security_domain/support.rb +153 -0
- data/spec/lib/pwn/plugins/yubikey/security_domain_spec.rb +296 -0
- data/spec/lib/pwn/plugins/yubikey/transport_spec.rb +760 -0
- data/spec/lib/pwn/plugins/yubikey_security_spec.rb +260 -0
- data/spec/lib/pwn/plugins/yubikey_spec.rb +209 -0
- data/spec/lib/pwn/plugins/yubikey_terminal_spec.rb +147 -0
- data/spec/lib/pwn/redaction_spec.rb +13 -10
- data/spec/lib/pwn/redaction_yubikey_spec.rb +61 -0
- data/spec/lib/pwn/session_cleanup_concurrency_spec.rb +201 -0
- data/spec/lib/pwn/session_cleanup_default_spec.rb +167 -2
- data/spec/lib/pwn/session_cleanup_legacy_spec.rb +101 -0
- data/spec/lib/pwn/session_cleanup_payload_spec.rb +335 -2
- data/spec/lib/pwn/session_cleanup_recovery_spec.rb +403 -0
- data/spec/lib/pwn/sessions_spec.rb +112 -0
- data/spec/lib/pwn/setup_native_yubikey_spec.rb +66 -0
- data/spec/lib/pwn/setup_spec.rb +3 -1
- data/spec/lib/pwn/setup_vault_yubikey_spec.rb +61 -0
- data/spec/lib/pwn/setup_yubikey_spec.rb +40 -0
- data/spec/lib/pwn/www/login_yubikey_spec.rb +273 -0
- data/spec/spec_helper.rb +15 -0
- data/spec/support/gate_failure_formatter.rb +14 -0
- data/spec/support/harness/cli_faults.rb +13 -9
- data/spec/support/harness/controller_spill.rb +26 -0
- data/spec/support/harness/controller_state.rb +54 -0
- data/spec/support/harness/evaluate.rb +12 -1
- data/spec/support/harness/experiments.rb +1 -1
- data/spec/support/harness/fake_provider.rb +22 -6
- data/spec/support/harness/fault_preload.rb +3 -1
- data/spec/support/harness/mission_console.rb +54 -0
- data/spec/support/harness/mission_curses_pty.rb +41 -0
- data/spec/support/harness/mission_dispatch.rb +30 -0
- data/spec/support/harness/mission_fixture_preload.rb +24 -0
- data/spec/support/harness/mission_job_lifetime.rb +25 -0
- data/spec/support/harness/mission_legacy.rb +25 -0
- data/spec/support/harness/mission_normal_exit.rb +34 -0
- data/spec/support/harness/mission_swarm.rb +23 -0
- data/spec/support/harness/mission_unsupported.rb +29 -0
- data/spec/support/harness/package_acceptance.rb +33 -5
- data/spec/support/harness/session_acceptance.rb +4 -4
- data/spec/support/harness/swarm_acceptance.rb +1 -1
- data/spec/support/harness/swarm_control.rb +44 -0
- data/spec/support/mission_namespace.rb +14 -0
- data/spec/support/mission_pipe_queue.rb +33 -0
- data/third_party/pwn_rdoc.jsonl +599 -15
- metadata +140 -17
data/lib/pwn/config.rb
CHANGED
|
@@ -364,13 +364,20 @@ module PWN
|
|
|
364
364
|
|
|
365
365
|
# Supported Method Parameters::
|
|
366
366
|
# env = PWN::Config.default_env(
|
|
367
|
-
# pwn_env_path: 'optional - Path to pwn.yaml file.
|
|
367
|
+
# pwn_env_path: 'optional - Path to pwn.yaml file. Defaults to ~/.pwn/pwn.yaml',
|
|
368
|
+
# pwn_dec_path: 'optional - Legacy decryptor path; defaults to the vault path plus .decryptor',
|
|
369
|
+
# yubikey: 'optional - Explicit hardware opt-in with slot, serial, reader, device_path, connection, timeout selector'
|
|
368
370
|
# )
|
|
369
371
|
public_class_method def self.default_env(opts = {})
|
|
370
|
-
pwn_env_path = opts[:pwn_env_path]
|
|
371
|
-
pwn_dec_path = "#{pwn_env_path}.decryptor"
|
|
372
|
-
|
|
373
|
-
|
|
372
|
+
pwn_env_path = opts[:pwn_env_path] || File.join(Dir.home, '.pwn', 'pwn.yaml')
|
|
373
|
+
pwn_dec_path = opts[:pwn_dec_path] || "#{pwn_env_path}.decryptor"
|
|
374
|
+
yubikey = opts[:yubikey]
|
|
375
|
+
raise ArgumentError, 'yubikey must be a selector Hash' unless yubikey.nil? || yubikey.is_a?(Hash)
|
|
376
|
+
|
|
377
|
+
if yubikey
|
|
378
|
+
puts "[*] Writing YubiKey-protected PWN::Env to #{pwn_env_path}; no software decryptor is created."
|
|
379
|
+
else
|
|
380
|
+
puts "
|
|
374
381
|
[*] NOTICE:
|
|
375
382
|
1. Writing minimal PWN::Env to:
|
|
376
383
|
#{pwn_env_path}
|
|
@@ -381,15 +388,22 @@ module PWN
|
|
|
381
388
|
4. For optimal security, it's recommended to move:
|
|
382
389
|
#{pwn_dec_path}
|
|
383
390
|
to a secure location and use the --pwn-dec parameter for PWN drivers.
|
|
384
|
-
|
|
391
|
+
"
|
|
392
|
+
end
|
|
385
393
|
env = env_template
|
|
386
394
|
|
|
387
|
-
#
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
395
|
+
# YAML tags preserve symbol keys without rewriting multiline scalar bytes.
|
|
396
|
+
yaml_env = YAML.dump(env)
|
|
397
|
+
FileUtils.mkdir_p(File.dirname(pwn_env_path), mode: 0o700)
|
|
398
|
+
if yubikey
|
|
399
|
+
encrypted = PWN::Plugins::Vault.encrypt_data(data: yaml_env, yubikey: yubikey)
|
|
400
|
+
File.open(pwn_env_path, File::WRONLY | File::CREAT | File::EXCL, 0o600) { |file| file.write(encrypted) }
|
|
401
|
+
else
|
|
402
|
+
File.write(pwn_env_path, yaml_env)
|
|
403
|
+
# Change file permission to 600
|
|
404
|
+
File.chmod(0o600, pwn_env_path)
|
|
405
|
+
PWN::Plugins::Vault.create(file: pwn_env_path, decryptor_file: pwn_dec_path)
|
|
406
|
+
end
|
|
393
407
|
|
|
394
408
|
# Ensure skills dir for pwn-ai agent (in parent of pwn_env_path)
|
|
395
409
|
pwn_env_root = File.dirname(pwn_env_path)
|
|
@@ -401,11 +415,6 @@ module PWN
|
|
|
401
415
|
pwn_dec_path: pwn_dec_path
|
|
402
416
|
}
|
|
403
417
|
|
|
404
|
-
PWN::Plugins::Vault.create(
|
|
405
|
-
file: pwn_env_path,
|
|
406
|
-
decryptor_file: pwn_dec_path
|
|
407
|
-
)
|
|
408
|
-
|
|
409
418
|
Pry.config.refresh_pwn_env = false if defined?(Pry)
|
|
410
419
|
env[:pwn_skills_path] = pwn_skills_path
|
|
411
420
|
PWN::Config.install_default_skills(pwn_skills_path: pwn_skills_path)
|
|
@@ -430,6 +439,7 @@ module PWN
|
|
|
430
439
|
# )
|
|
431
440
|
public_class_method def self.redact_sensitive_artifacts(opts = {})
|
|
432
441
|
config = opts[:config] ||= PWN::Env
|
|
442
|
+
return config.map { |value| value.is_a?(Hash) || value.is_a?(Array) ? redact_sensitive_artifacts(config: value) : value } if config.is_a?(Array)
|
|
433
443
|
|
|
434
444
|
sensitive_keys = %i[
|
|
435
445
|
admin_key
|
|
@@ -445,14 +455,38 @@ module PWN
|
|
|
445
455
|
refresh_token
|
|
446
456
|
secret_key
|
|
447
457
|
token
|
|
458
|
+
pin
|
|
459
|
+
new_pin
|
|
460
|
+
admin_pin
|
|
461
|
+
puk
|
|
462
|
+
new_puk
|
|
463
|
+
management_key
|
|
464
|
+
new_management_key
|
|
465
|
+
management_password
|
|
466
|
+
access_code
|
|
467
|
+
new_access_code
|
|
468
|
+
oath_secret
|
|
469
|
+
oath_password
|
|
470
|
+
unlock_code
|
|
471
|
+
unlock_password
|
|
472
|
+
lock_code
|
|
473
|
+
new_lock_code
|
|
474
|
+
reset_code
|
|
475
|
+
scp
|
|
476
|
+
scp_key
|
|
477
|
+
scp_keys
|
|
478
|
+
scp03_key
|
|
479
|
+
scp11_key
|
|
480
|
+
static_password
|
|
481
|
+
hmac_secret
|
|
448
482
|
]
|
|
449
483
|
|
|
450
484
|
# Transform values at the current level: redact sensitive keys
|
|
451
485
|
config.transform_values.with_index do |v, k|
|
|
452
|
-
if sensitive_keys.include?(config.keys[k])
|
|
486
|
+
if sensitive_keys.include?(config.keys[k].to_s.downcase.tr('-', '_').to_sym)
|
|
453
487
|
'>>> REDACTED >>> USE `pwn-vault` FOR ADMINISTRATION <<< REDACTED <<<'
|
|
454
488
|
else
|
|
455
|
-
v.is_a?(Hash) ? redact_sensitive_artifacts(config: v) : v
|
|
489
|
+
v.is_a?(Hash) || v.is_a?(Array) ? redact_sensitive_artifacts(config: v) : v
|
|
456
490
|
end
|
|
457
491
|
end
|
|
458
492
|
rescue StandardError => e
|
|
@@ -477,7 +511,8 @@ module PWN
|
|
|
477
511
|
# Supported Method Parameters::
|
|
478
512
|
# PWN::Config.refresh_env(
|
|
479
513
|
# pwn_env_path: 'optional - Path to pwn.yaml file. Defaults to ~/.pwn/pwn.yaml',
|
|
480
|
-
# pwn_dec_path: 'optional -
|
|
514
|
+
# pwn_dec_path: 'optional - Legacy decryptor path; not needed for hardware vaults',
|
|
515
|
+
# yubikey: 'optional - Hardware selector override or explicit opt-in for a new vault'
|
|
481
516
|
# )
|
|
482
517
|
|
|
483
518
|
# Deep-fill missing Hash keys from defaults into target (mutates target).
|
|
@@ -541,27 +576,20 @@ module PWN
|
|
|
541
576
|
pwn_skills_path = File.join(pwn_env_root, 'skills')
|
|
542
577
|
FileUtils.mkdir_p(pwn_skills_path)
|
|
543
578
|
|
|
544
|
-
return default_env(pwn_env_path: pwn_env_path) unless File.exist?(pwn_env_path)
|
|
579
|
+
return default_env(pwn_env_path: pwn_env_path, pwn_dec_path: opts[:pwn_dec_path], yubikey: opts[:yubikey]) unless File.exist?(pwn_env_path)
|
|
545
580
|
|
|
546
581
|
is_encrypted = PWN::Plugins::Vault.file_encrypted?(file: pwn_env_path)
|
|
547
582
|
raise "PWN Environment (#{pwn_env_path}) is not encrypted! Use PWN::Vault.create(file: '#{pwn_env_path}', decryptor_file: '#{pwn_env_path}.decryptor') to encrypt it." unless is_encrypted
|
|
548
583
|
|
|
549
584
|
pwn_dec_path = opts[:pwn_dec_path] ||= "#{pwn_env_path}.decryptor"
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
pwn_decryptor = YAML.load_file(pwn_dec_path, symbolize_names: true)
|
|
553
|
-
|
|
554
|
-
key = opts[:key] ||= pwn_decryptor[:key] ||= ENV.fetch('PWN_DECRYPTOR_KEY')
|
|
555
|
-
key = PWN::Plugins::AuthenticationHelper.mask_password(prompt: 'Decryption Key') if key.nil?
|
|
556
|
-
|
|
557
|
-
iv = opts[:iv] ||= pwn_decryptor[:iv] ||= ENV.fetch('PWN_DECRYPTOR_IV')
|
|
558
|
-
iv = PWN::Plugins::AuthenticationHelper.mask_password(prompt: 'Decryption IV') if iv.nil?
|
|
559
|
-
|
|
560
|
-
env = PWN::Plugins::Vault.dump(
|
|
585
|
+
access = PWN::Plugins::Vault.access_options(
|
|
561
586
|
file: pwn_env_path,
|
|
562
|
-
|
|
563
|
-
|
|
587
|
+
decryptor_file: pwn_dec_path,
|
|
588
|
+
key: opts[:key],
|
|
589
|
+
iv: opts[:iv],
|
|
590
|
+
yubikey: opts[:yubikey]
|
|
564
591
|
)
|
|
592
|
+
env = PWN::Plugins::Vault.dump(access.merge(file: pwn_env_path))
|
|
565
593
|
|
|
566
594
|
valid_ai_engines = PWN::AI.help.reject { |e| e.downcase == :agent }.map(&:downcase)
|
|
567
595
|
|
|
@@ -1142,7 +1170,9 @@ module PWN
|
|
|
1142
1170
|
|
|
1143
1171
|
# Run default env and return its result
|
|
1144
1172
|
#{self}.default_env(
|
|
1145
|
-
pwn_env_path: 'optional - Path to pwn.yaml file.
|
|
1173
|
+
pwn_env_path: 'optional - Path to pwn.yaml file. Defaults to ~/.pwn/pwn.yaml',
|
|
1174
|
+
pwn_dec_path: 'optional - Legacy decryptor path; defaults to the vault path plus .decryptor',
|
|
1175
|
+
yubikey: 'optional - Explicit hardware opt-in with slot, serial, reader, device_path, connection, timeout selector; no sidecar'
|
|
1146
1176
|
)
|
|
1147
1177
|
|
|
1148
1178
|
# Run redact sensitive artifacts and return its result
|
|
@@ -1155,10 +1185,11 @@ module PWN
|
|
|
1155
1185
|
|
|
1156
1186
|
# Run refresh env and return its result
|
|
1157
1187
|
#{self}.refresh_env(
|
|
1158
|
-
pwn_env_path: '
|
|
1159
|
-
pwn_dec_path: '
|
|
1160
|
-
key: 'optional -
|
|
1161
|
-
iv: 'optional -
|
|
1188
|
+
pwn_env_path: 'optional - Configuration vault path; defaults to ~/.pwn/pwn.yaml',
|
|
1189
|
+
pwn_dec_path: 'optional - Legacy decryptor path; not needed for hardware vaults',
|
|
1190
|
+
key: 'optional - Legacy vault key override',
|
|
1191
|
+
iv: 'optional - Legacy vault IV override',
|
|
1192
|
+
yubikey: 'optional - Hardware selector with slot, serial, reader, device_path, connection, timeout; opts in for new vaults'
|
|
1162
1193
|
)
|
|
1163
1194
|
|
|
1164
1195
|
# Run pwn skills path and return its result
|
data/lib/pwn/cron.rb
CHANGED
|
@@ -48,6 +48,8 @@ module PWN
|
|
|
48
48
|
# enabled: true
|
|
49
49
|
# )
|
|
50
50
|
public_class_method def self.create(opts = {})
|
|
51
|
+
return with_registry_lock { create(opts) } unless registry_owned?
|
|
52
|
+
|
|
51
53
|
jobs = load_jobs
|
|
52
54
|
id = SecureRandom.hex(6)
|
|
53
55
|
name = opts[:name] || "job-#{id}"
|
|
@@ -69,7 +71,7 @@ module PWN
|
|
|
69
71
|
|
|
70
72
|
# Optionally install a crontab entry (user must have permission)
|
|
71
73
|
install_crontab_entry(job: job) if opts[:install_crontab]
|
|
72
|
-
sync_one_job(job: job) if cron_enabled? && !opts[:install_crontab]
|
|
74
|
+
sync_one_job(job: job) if cron_enabled? && !opts[:install_crontab] && opts.fetch(:sync_native, true)
|
|
73
75
|
|
|
74
76
|
job
|
|
75
77
|
end
|
|
@@ -83,6 +85,7 @@ module PWN
|
|
|
83
85
|
jobs = load_jobs
|
|
84
86
|
job = jobs[id] || jobs.values.find { |j| j[:name] == id || j[:id] == id }
|
|
85
87
|
raise "Job #{id} not found" unless job
|
|
88
|
+
return { skipped: true, reason: :housekeeping_internal_only, status: 'skipped' } if native_execution? && housekeeping_job?(job: job)
|
|
86
89
|
|
|
87
90
|
start = Time.now
|
|
88
91
|
result = nil
|
|
@@ -133,20 +136,28 @@ module PWN
|
|
|
133
136
|
|
|
134
137
|
job[:last_run] = Time.now.utc.iso8601
|
|
135
138
|
job[:last_status] = status
|
|
136
|
-
|
|
137
|
-
|
|
139
|
+
with_registry_lock do
|
|
140
|
+
current = load_jobs
|
|
141
|
+
if current.key?(job[:id])
|
|
142
|
+
current[job[:id]][:last_run] = job[:last_run]
|
|
143
|
+
current[job[:id]][:last_status] = status
|
|
144
|
+
save_jobs(jobs: current)
|
|
145
|
+
end
|
|
146
|
+
end
|
|
138
147
|
|
|
139
148
|
{ job: job, result: result, duration: Time.now - start, status: status }
|
|
140
149
|
end
|
|
141
150
|
|
|
142
151
|
# Supported Method Parameters::
|
|
143
|
-
# PWN::Cron.remove(id:)
|
|
144
|
-
public_class_method def self.remove(opts = {})
|
|
152
|
+
# PWN::Cron.remove(id:, sync_native: false)
|
|
153
|
+
public_class_method def self.remove(opts = {})
|
|
154
|
+
return with_registry_lock { remove(opts) } unless registry_owned?
|
|
155
|
+
|
|
145
156
|
id = opts[:id].to_s
|
|
146
157
|
jobs = load_jobs
|
|
147
158
|
gone = jobs.delete(id)
|
|
148
159
|
save_jobs(jobs: jobs)
|
|
149
|
-
remove_native_job(job: gone) if gone
|
|
160
|
+
remove_native_job(job: gone) if gone && opts.fetch(:sync_native, true)
|
|
150
161
|
true
|
|
151
162
|
end
|
|
152
163
|
|
|
@@ -181,16 +192,48 @@ module PWN
|
|
|
181
192
|
# and break jobs[id] / jobs.delete(id) / toggle lookups).
|
|
182
193
|
raw.each_with_object({}) { |(k, v), h| h[k.to_s] = v }
|
|
183
194
|
rescue StandardError => e
|
|
184
|
-
|
|
185
|
-
|
|
195
|
+
raise IOError, "Cron registry unreadable (#{e.class}); refusing an empty replacement"
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
# A permanent inode coordinates registry writers, never job execution.
|
|
199
|
+
private_class_method def self.with_registry_lock
|
|
200
|
+
return yield if registry_owned?
|
|
201
|
+
|
|
202
|
+
owner = Thread.current[:pwn_cron_registry_owner]
|
|
203
|
+
File.open(File.join(cron_dir, '.jobs.write.lock'), File::RDWR | File::CREAT | File::NOFOLLOW, 0o600) do |file|
|
|
204
|
+
file.flock(File::LOCK_EX)
|
|
205
|
+
begin
|
|
206
|
+
Thread.current[:pwn_cron_registry_owner] = [Process.pid, Thread.current, jobs_file]
|
|
207
|
+
yield
|
|
208
|
+
ensure
|
|
209
|
+
Thread.current[:pwn_cron_registry_owner] = owner
|
|
210
|
+
end
|
|
211
|
+
end
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
private_class_method def self.registry_owned?
|
|
215
|
+
Thread.current[:pwn_cron_registry_owner] == [Process.pid, Thread.current, jobs_file]
|
|
186
216
|
end
|
|
187
217
|
|
|
188
218
|
private_class_method def self.save_jobs(opts = {})
|
|
189
219
|
jobs = opts[:jobs] ||= {}
|
|
190
|
-
|
|
220
|
+
temporary = "#{jobs_file}.#{SecureRandom.hex(8)}.tmp"
|
|
221
|
+
begin
|
|
222
|
+
File.open(temporary, File::WRONLY | File::CREAT | File::EXCL, 0o600) do |file|
|
|
223
|
+
file.write(YAML.dump(jobs))
|
|
224
|
+
file.flush
|
|
225
|
+
file.fsync
|
|
226
|
+
end
|
|
227
|
+
File.rename(temporary, jobs_file)
|
|
228
|
+
File.open(cron_dir, File::RDONLY, &:fsync)
|
|
229
|
+
ensure
|
|
230
|
+
FileUtils.rm_f(temporary)
|
|
231
|
+
end
|
|
191
232
|
end
|
|
192
233
|
|
|
193
234
|
private_class_method def self.toggle(opts = {})
|
|
235
|
+
return with_registry_lock { toggle(opts) } unless registry_owned?
|
|
236
|
+
|
|
194
237
|
id = opts[:id].to_s
|
|
195
238
|
enabled = opts[:enabled]
|
|
196
239
|
jobs = load_jobs
|
|
@@ -202,9 +245,29 @@ module PWN
|
|
|
202
245
|
jobs[id]
|
|
203
246
|
end
|
|
204
247
|
|
|
248
|
+
private_class_method def self.native_execution?
|
|
249
|
+
return true if ENV['PWN_CRON_NATIVE'] == '1'
|
|
250
|
+
|
|
251
|
+
# An interactive terminal may inherit INVOCATION_ID; only PWN scheduler
|
|
252
|
+
# cgroups identify legacy units that predate the explicit marker.
|
|
253
|
+
File.read('/proc/self/cgroup').match?(%r{/pwn-cron-(?:worker|j[^/]+)\.service(?:/|$)})
|
|
254
|
+
rescue Errno::ENOENT, Errno::EACCES
|
|
255
|
+
false
|
|
256
|
+
end
|
|
257
|
+
|
|
258
|
+
# Housekeeping is owned exclusively by the portable worker/manual commands.
|
|
259
|
+
private_class_method def self.housekeeping_job?(opts = {})
|
|
260
|
+
job = opts[:job]
|
|
261
|
+
return false unless job.is_a?(Hash)
|
|
262
|
+
|
|
263
|
+
%w[session_cleanup learning_consolidate_nightly pwn_stores_lean_nightly].include?(job[:name].to_s) ||
|
|
264
|
+
[job[:ruby], job[:script]].any? { |payload| payload.to_s.match?(/SessionCleanup|session_cleanup\.rb/) }
|
|
265
|
+
end
|
|
266
|
+
|
|
205
267
|
private_class_method def self.sync_one_job(opts = {})
|
|
206
268
|
job = opts[:job]
|
|
207
269
|
return unless job.is_a?(Hash)
|
|
270
|
+
return if housekeeping_job?(job: job)
|
|
208
271
|
|
|
209
272
|
if job[:enabled] && cron_enabled?
|
|
210
273
|
install_native_job(job: job)
|
|
@@ -218,15 +281,11 @@ module PWN
|
|
|
218
281
|
|
|
219
282
|
# Supported Method Parameters::
|
|
220
283
|
# PWN::Cron.install_defaults
|
|
221
|
-
#
|
|
222
|
-
#
|
|
223
|
-
#
|
|
224
|
-
#
|
|
225
|
-
|
|
226
|
-
# Hygiene jobs are enabled. Curriculum self-play / judge / LoRA are
|
|
227
|
-
# seeded disabled: practice runs Loop.run (real tools) overnight;
|
|
228
|
-
# offline_judge spends API tokens; train needs a GPU trainer.
|
|
229
|
-
DEFAULT_JOBS = [
|
|
284
|
+
# Fresh installs seed one internal housekeeping coordinator. Existing
|
|
285
|
+
# schedules are preserved, not automatically merged or re-enabled.
|
|
286
|
+
# Curriculum templates below require explicit create; installation and
|
|
287
|
+
# migration never seed them, including after operator deletion.
|
|
288
|
+
CURRICULUM_JOBS = [
|
|
230
289
|
{
|
|
231
290
|
name: 'curriculum_practice_nightly',
|
|
232
291
|
schedule: '0 3 * * *',
|
|
@@ -245,29 +304,24 @@ module PWN
|
|
|
245
304
|
ruby: 'PWN::AI::Agent::Curriculum.offline_judge(since_hours: 24) if defined?(PWN::AI::Agent::Curriculum)',
|
|
246
305
|
aliases: %w[offline_judge_nightly],
|
|
247
306
|
enabled: false
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
ruby: 'PWN::AI::Agent::Learning.consolidate if defined?(PWN::AI::Agent::Learning)',
|
|
253
|
-
enabled: true
|
|
254
|
-
},
|
|
307
|
+
}
|
|
308
|
+
].freeze
|
|
309
|
+
|
|
310
|
+
DEFAULT_JOBS = [
|
|
255
311
|
{
|
|
256
312
|
name: 'session_cleanup',
|
|
257
313
|
schedule: '45 3 * * *',
|
|
258
|
-
ruby: "require File.join(PWN::Cron.cron_dir, 'session_cleanup.rb'); PWN::SessionCleanup.
|
|
259
|
-
enabled: true
|
|
260
|
-
},
|
|
261
|
-
{
|
|
262
|
-
name: 'pwn_stores_lean_nightly',
|
|
263
|
-
schedule: '15 5 * * *',
|
|
264
|
-
ruby: 'PWN::AI::Agent::Learning.gc_stores! if defined?(PWN::AI::Agent::Learning)',
|
|
314
|
+
ruby: "require File.join(PWN::Cron.cron_dir, 'session_cleanup.rb'); PWN::SessionCleanup.coordinate(dry_run: false)",
|
|
265
315
|
enabled: true
|
|
266
316
|
}
|
|
267
317
|
].freeze
|
|
268
318
|
|
|
269
319
|
public_class_method def self.install_defaults
|
|
320
|
+
return with_registry_lock { install_defaults } unless registry_owned?
|
|
321
|
+
|
|
270
322
|
install_default_payloads
|
|
323
|
+
require File.expand_path('../../etc/default_cron/session_cleanup', __dir__)
|
|
324
|
+
PWN::SessionCleanup.install(root: File.dirname(cron_dir))
|
|
271
325
|
jobs = load_jobs
|
|
272
326
|
names = jobs.values.map { |j| j[:name].to_s }
|
|
273
327
|
seeded = []
|
|
@@ -282,21 +336,13 @@ module PWN
|
|
|
282
336
|
schedule: spec[:schedule],
|
|
283
337
|
ruby: default_payload(spec: spec),
|
|
284
338
|
delivery: 'log',
|
|
339
|
+
sync_native: false,
|
|
285
340
|
enabled: spec.fetch(:enabled, true)
|
|
286
341
|
)
|
|
287
342
|
names << spec[:name].to_s
|
|
288
343
|
end
|
|
289
344
|
|
|
290
|
-
|
|
291
|
-
if names.include?('curriculum_offline_judge') && names.include?('offline_judge_nightly')
|
|
292
|
-
begin
|
|
293
|
-
dup = jobs.values.find { |j| j[:name].to_s == 'offline_judge_nightly' }
|
|
294
|
-
disable(id: dup[:id]) if dup && dup[:enabled]
|
|
295
|
-
rescue StandardError
|
|
296
|
-
nil
|
|
297
|
-
end
|
|
298
|
-
end
|
|
299
|
-
|
|
345
|
+
PWN::SessionCleanup.request_background(root: File.dirname(cron_dir))
|
|
300
346
|
seeded
|
|
301
347
|
rescue StandardError => e
|
|
302
348
|
warn("[PWN::Cron] install_defaults failed: #{e.class}: #{e.message}")
|
|
@@ -327,7 +373,9 @@ module PWN
|
|
|
327
373
|
|
|
328
374
|
slot = Time.at(((Time.now.to_i / 60) + 1) * 60)
|
|
329
375
|
slot += 60 until slot.hour == 3 && slot.min == 45
|
|
330
|
-
|
|
376
|
+
payload = spec[:ruby]
|
|
377
|
+
payload = payload.sub('SessionCleanup.coordinate(', 'SessionCleanup.maintain(') if load_jobs.values.any? { |job| %w[learning_consolidate_nightly pwn_stores_lean_nightly].include?(job[:name]) }
|
|
378
|
+
"if Time.now.to_i < #{slot.to_i}; { deferred: true, deleted: 0 }; else; #{payload}; end"
|
|
331
379
|
end
|
|
332
380
|
|
|
333
381
|
# ------------------------------------------------------------------
|
|
@@ -410,6 +458,7 @@ module PWN
|
|
|
410
458
|
load_jobs.each_with_object({}) do |(id, job), acc|
|
|
411
459
|
next unless job.is_a?(Hash)
|
|
412
460
|
next unless job[:enabled]
|
|
461
|
+
next if native_execution? && housekeeping_job?(job: job)
|
|
413
462
|
next unless due?(schedule: job[:schedule], last_run: job[:last_run], now: now)
|
|
414
463
|
|
|
415
464
|
acc[id] = job
|
|
@@ -533,7 +582,16 @@ module PWN
|
|
|
533
582
|
|
|
534
583
|
# Single tick used by the loop AND by tests (no sleep).
|
|
535
584
|
public_class_method def self.tick(opts = {})
|
|
536
|
-
run_due(now: opts[:now] || Time.now)
|
|
585
|
+
results = run_due(now: opts[:now] || Time.now)
|
|
586
|
+
return results if native_execution?
|
|
587
|
+
|
|
588
|
+
require File.expand_path('../../etc/default_cron/session_cleanup', __dir__)
|
|
589
|
+
background = PWN::SessionCleanup.background_request(root: File.dirname(cron_dir)) do
|
|
590
|
+
job = load_jobs.values.find { |entry| entry[:name] == 'session_cleanup' && entry[:enabled] }
|
|
591
|
+
results.find { |entry| entry[:id] == job[:id] } || run(id: job[:id]).merge(id: job[:id]) if job
|
|
592
|
+
end
|
|
593
|
+
results << background if background && results.none? { |entry| entry[:id] == background[:id] }
|
|
594
|
+
results
|
|
537
595
|
end
|
|
538
596
|
|
|
539
597
|
# Blocking poll loop. `once: true` runs a single tick then returns
|
|
@@ -1064,6 +1122,7 @@ module PWN
|
|
|
1064
1122
|
|
|
1065
1123
|
public_class_method def self.install_native_job(opts = {})
|
|
1066
1124
|
job = opts[:job]
|
|
1125
|
+
return { skipped: true, reason: :housekeeping_internal_only } if housekeeping_job?(job: job)
|
|
1067
1126
|
return nil unless job.is_a?(Hash) && job[:id]
|
|
1068
1127
|
|
|
1069
1128
|
backend = (opts[:backend] || scheduler_backend).to_sym
|
|
@@ -1080,6 +1139,8 @@ module PWN
|
|
|
1080
1139
|
|
|
1081
1140
|
public_class_method def self.remove_native_job(opts = {})
|
|
1082
1141
|
job = opts[:job] || { id: opts[:id], name: opts[:name] }
|
|
1142
|
+
return { skipped: true, reason: :housekeeping_internal_only } if housekeeping_job?(job: job)
|
|
1143
|
+
|
|
1083
1144
|
backend = (opts[:backend] || scheduler_backend).to_sym
|
|
1084
1145
|
apply = apply_native?(opts)
|
|
1085
1146
|
case backend
|
|
@@ -1110,7 +1171,7 @@ module PWN
|
|
|
1110
1171
|
public_class_method def self.install_crontab_worker(opts = {})
|
|
1111
1172
|
apply = apply_native?(opts)
|
|
1112
1173
|
marker = 'PWN::Cron.ensure_worker'
|
|
1113
|
-
line = "@reboot #{ruby_eval_shell(snippet: 'require "pwn"; PWN::Cron.ensure_worker(restart: false)', log: worker_log)}"
|
|
1174
|
+
line = "@reboot #{ruby_eval_shell(snippet: 'ENV["PWN_CRON_NATIVE"] = "1"; require "pwn"; PWN::Cron.ensure_worker(restart: false)', log: worker_log)}"
|
|
1114
1175
|
preview = "# pwn-cron worker\n#{line}\n"
|
|
1115
1176
|
write_native_preview(name: 'worker.crontab', body: preview)
|
|
1116
1177
|
return { backend: :crontab, apply: false, line: line } unless apply && crontab_available?
|
|
@@ -1124,6 +1185,8 @@ module PWN
|
|
|
1124
1185
|
|
|
1125
1186
|
public_class_method def self.install_crontab_job(opts = {})
|
|
1126
1187
|
job = opts[:job]
|
|
1188
|
+
return { skipped: true, reason: :housekeeping_internal_only } if housekeeping_job?(job: job)
|
|
1189
|
+
|
|
1127
1190
|
apply = apply_native?(opts)
|
|
1128
1191
|
snippet = "require \"pwn\"; PWN::Cron.run(id: #{job[:id].to_s.inspect})"
|
|
1129
1192
|
line = "#{job[:schedule]} #{ruby_eval_shell(snippet: snippet, log: File.join(cron_dir, 'cron.log'))}"
|
|
@@ -1165,7 +1228,7 @@ module PWN
|
|
|
1165
1228
|
public_class_method def self.install_systemd_user_worker(opts = {})
|
|
1166
1229
|
apply = apply_native?(opts)
|
|
1167
1230
|
exec_start = ruby_eval_shell(
|
|
1168
|
-
snippet: 'require "pwn"; PWN::Cron.start_worker(restart: false, foreground: true)'
|
|
1231
|
+
snippet: 'ENV["PWN_CRON_NATIVE"] = "1"; require "pwn"; PWN::Cron.start_worker(restart: false, foreground: true)'
|
|
1169
1232
|
)
|
|
1170
1233
|
body = <<~UNIT
|
|
1171
1234
|
[Unit]
|
|
@@ -1194,6 +1257,8 @@ module PWN
|
|
|
1194
1257
|
|
|
1195
1258
|
public_class_method def self.install_systemd_user_job(opts = {})
|
|
1196
1259
|
job = opts[:job]
|
|
1260
|
+
return { skipped: true, reason: :housekeeping_internal_only } if housekeeping_job?(job: job)
|
|
1261
|
+
|
|
1197
1262
|
apply = apply_native?(opts)
|
|
1198
1263
|
cal = cron_to_calendar(schedule: job[:schedule])
|
|
1199
1264
|
oncal = systemd_on_calendar(calendar: cal)
|
|
@@ -1272,7 +1337,7 @@ module PWN
|
|
|
1272
1337
|
label = LAUNCHD_WORKER_LABEL
|
|
1273
1338
|
plist = launchd_plist(
|
|
1274
1339
|
label: label,
|
|
1275
|
-
argv: ruby_eval_argv(snippet: 'require "pwn"; PWN::Cron.start_worker(restart: false, foreground: true)'),
|
|
1340
|
+
argv: ruby_eval_argv(snippet: 'ENV["PWN_CRON_NATIVE"] = "1"; require "pwn"; PWN::Cron.start_worker(restart: false, foreground: true)'),
|
|
1276
1341
|
keepalive: true,
|
|
1277
1342
|
run_at_load: true
|
|
1278
1343
|
)
|
|
@@ -1283,6 +1348,8 @@ module PWN
|
|
|
1283
1348
|
|
|
1284
1349
|
public_class_method def self.install_launchd_job(opts = {})
|
|
1285
1350
|
job = opts[:job]
|
|
1351
|
+
return { skipped: true, reason: :housekeeping_internal_only } if housekeeping_job?(job: job)
|
|
1352
|
+
|
|
1286
1353
|
apply = apply_native?(opts)
|
|
1287
1354
|
ints = launchd_calendar_interval(schedule: job[:schedule])
|
|
1288
1355
|
return { backend: :launchd, id: job[:id], skipped: true, reason: :no_calendar } unless ints
|
|
@@ -1325,7 +1392,7 @@ module PWN
|
|
|
1325
1392
|
|
|
1326
1393
|
public_class_method def self.install_schtasks_worker(opts = {})
|
|
1327
1394
|
apply = apply_native?(opts)
|
|
1328
|
-
tr = ruby_eval_shell(snippet: 'require "pwn"; PWN::Cron.ensure_worker(restart: false)')
|
|
1395
|
+
tr = ruby_eval_shell(snippet: 'ENV["PWN_CRON_NATIVE"] = "1"; require "pwn"; PWN::Cron.ensure_worker(restart: false)')
|
|
1329
1396
|
argv = ['schtasks', '/Create', '/F', '/TN', SCHTASKS_WORKER_NAME, '/SC', 'ONLOGON', '/RL', 'LIMITED', '/TR', tr]
|
|
1330
1397
|
write_native_preview(name: 'worker.schtasks.txt', body: argv.inspect)
|
|
1331
1398
|
run_argv(argv: argv) if apply && schtasks_available?
|
|
@@ -1334,6 +1401,8 @@ module PWN
|
|
|
1334
1401
|
|
|
1335
1402
|
public_class_method def self.install_schtasks_job(opts = {})
|
|
1336
1403
|
job = opts[:job]
|
|
1404
|
+
return { skipped: true, reason: :housekeeping_internal_only } if housekeeping_job?(job: job)
|
|
1405
|
+
|
|
1337
1406
|
apply = apply_native?(opts)
|
|
1338
1407
|
spec = schtasks_spec(schedule: job[:schedule])
|
|
1339
1408
|
return { backend: :schtasks, id: job[:id], skipped: true, reason: :no_calendar } unless spec
|
|
@@ -1568,6 +1637,7 @@ module PWN
|
|
|
1568
1637
|
script: 'optional - path to external script',
|
|
1569
1638
|
delivery: 'optional - log|stdout (default log)',
|
|
1570
1639
|
enabled: 'optional - enabled value consumed by #create',
|
|
1640
|
+
sync_native: 'optional - synchronize generic native jobs (default true); defaults are internal only',
|
|
1571
1641
|
install_crontab: 'optional - install crontab value consumed by #create'
|
|
1572
1642
|
)
|
|
1573
1643
|
|
|
@@ -1578,7 +1648,8 @@ module PWN
|
|
|
1578
1648
|
|
|
1579
1649
|
# Run remove and return its result
|
|
1580
1650
|
#{self}.remove(
|
|
1581
|
-
id: 'optional - id value consumed by #remove'
|
|
1651
|
+
id: 'optional - id value consumed by #remove',
|
|
1652
|
+
sync_native: 'optional - false deletes only the schedule, preserving native schedulers and evidence'
|
|
1582
1653
|
)
|
|
1583
1654
|
|
|
1584
1655
|
# Run enable and return its result
|