lux-hammer 0.3.10 → 0.3.13
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/.version +1 -1
- data/AGENTS.md +57 -41
- data/README.md +47 -44
- data/lib/hammer/builtins.rb +40 -33
- data/lib/hammer/dotenv.rb +3 -2
- data/lib/hammer/loader.rb +13 -7
- data/lib/hammer/option.rb +2 -0
- data/lib/hammer/parser.rb +40 -10
- data/lib/hammer/recipe.rb +2 -2
- data/lib/hammer/shell.rb +5 -2
- data/lib/lux-hammer.rb +113 -58
- data/recipes/deploy.rb +32 -0
- data/recipes/lib/deploy/boot.rb +52 -0
- data/recipes/lib/deploy/commands.rb +555 -0
- data/recipes/lib/deploy/config.rb +62 -0
- data/recipes/lib/deploy/context.rb +149 -0
- data/recipes/lib/deploy/doctor.rb +238 -0
- data/recipes/lib/deploy/hammer.rb +168 -0
- data/recipes/lib/deploy/manifest.rb +169 -0
- data/recipes/lib/deploy/ssh.rb +129 -0
- data/recipes/lib/deploy/template.rb +39 -0
- metadata +12 -2
data/lib/lux-hammer.rb
CHANGED
|
@@ -94,8 +94,8 @@ class Hammer
|
|
|
94
94
|
# If the method takes no args, call it without opts. Otherwise pass
|
|
95
95
|
# opts. So both `def build` and `def build(opts)` work.
|
|
96
96
|
m = method_name
|
|
97
|
-
|
|
98
|
-
cmd.handler =
|
|
97
|
+
takes_arg = instance_method(method_name).parameters.any? { |type, _| %i[req opt rest].include?(type) }
|
|
98
|
+
cmd.handler = takes_arg ? proc { |opts| send(m, opts) } : proc { send(m) }
|
|
99
99
|
cmd.finalize!
|
|
100
100
|
commands[cmd.name] = cmd
|
|
101
101
|
|
|
@@ -164,7 +164,11 @@ class Hammer
|
|
|
164
164
|
end
|
|
165
165
|
cmd.handler = handler
|
|
166
166
|
|
|
167
|
-
|
|
167
|
+
# Only warn when overriding a task that was also defined inside the
|
|
168
|
+
# main app. Overriding one that came from outside - a framework
|
|
169
|
+
# default, plugin, or gem - is an intentional override, so stay quiet
|
|
170
|
+
# and don't tag it `(redefined)` in help.
|
|
171
|
+
if (prev = commands[cmd.name]) && app_local_location?(prev.location)
|
|
168
172
|
cmd.prev_location = prev.location
|
|
169
173
|
warn_redefinition('task', cmd.name, prev.location, cmd.location)
|
|
170
174
|
end
|
|
@@ -190,28 +194,29 @@ class Hammer
|
|
|
190
194
|
# namespace :users do ... end
|
|
191
195
|
# end
|
|
192
196
|
#
|
|
197
|
+
# Reopening a namespace merges: the same `namespace :db do ... end` can
|
|
198
|
+
# be split across files (Rake-style) and the blocks accumulate onto one
|
|
199
|
+
# subclass. Only a duplicate *task* name inside warns - that's handled
|
|
200
|
+
# by `task`. The namespace subclass is created lazily on first mention.
|
|
193
201
|
def namespace(name, &block)
|
|
194
|
-
sub =
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
sub.instance_variable_set(:@prev_location, prev.instance_variable_get(:@location))
|
|
211
|
-
warn_redefinition('namespace', name.to_s, prev.instance_variable_get(:@location), sub.instance_variable_get(:@location))
|
|
212
|
-
end
|
|
202
|
+
sub = (@namespaces[name.to_s] ||= begin
|
|
203
|
+
ns = Class.new(Hammer)
|
|
204
|
+
# Track the top-level CLI class so cross-invocation
|
|
205
|
+
# (`hammer 'ns:cmd'`) from inside a namespaced command dispatches
|
|
206
|
+
# against the full tree, not just the current namespace.
|
|
207
|
+
ns.instance_variable_set(:@root, root)
|
|
208
|
+
# Parent link, so `before` hooks defined further up the namespace
|
|
209
|
+
# tree can be collected and run outer -> inner before a command.
|
|
210
|
+
ns.instance_variable_set(:@parent, self)
|
|
211
|
+
# Share the parent's resolved program_name so help banners show
|
|
212
|
+
# "myapp ns:cmd" with the same prefix everywhere - and so the value
|
|
213
|
+
# captured pre-chdir (see `Hammer.cli`) survives into nested classes.
|
|
214
|
+
ns.instance_variable_set(:@program_name, program_name)
|
|
215
|
+
ns.instance_variable_set(:@location, source_location_of(block))
|
|
216
|
+
ns
|
|
217
|
+
end)
|
|
213
218
|
|
|
214
|
-
|
|
219
|
+
Hammer.with_target(sub) { sub.class_eval(&block) } if block
|
|
215
220
|
end
|
|
216
221
|
|
|
217
222
|
# Register a hook to run before every command in this class (root or
|
|
@@ -252,7 +257,23 @@ class Hammer
|
|
|
252
257
|
# built-in C-defined procs, eval'd blocks).
|
|
253
258
|
def source_location_of(block)
|
|
254
259
|
loc = block&.source_location
|
|
255
|
-
loc ? "#{loc[0]}:#{loc[1]}" : '(unknown)'
|
|
260
|
+
loc ? "#{relativize_path(loc[0])}:#{loc[1]}" : '(unknown)'
|
|
261
|
+
end
|
|
262
|
+
|
|
263
|
+
# Trim the cwd prefix off an absolute path so redefinition warnings
|
|
264
|
+
# read as `./lib/tasks/foo.rb` instead of a long absolute path. Paths
|
|
265
|
+
# outside cwd (framework / gem files) are left absolute.
|
|
266
|
+
def relativize_path(path)
|
|
267
|
+
prefix = "#{Dir.pwd}/"
|
|
268
|
+
path.start_with?(prefix) ? ".#{path[Dir.pwd.length..]}" : path
|
|
269
|
+
end
|
|
270
|
+
|
|
271
|
+
# True when a captured location lives inside the main app. relativize_path
|
|
272
|
+
# rewrites in-app absolute paths to a `.`-relative form, so anything still
|
|
273
|
+
# starting with "/" is an absolute path outside cwd - a framework, plugin,
|
|
274
|
+
# or gem file. Relative locations are already cwd-anchored, hence local.
|
|
275
|
+
def app_local_location?(loc)
|
|
276
|
+
!loc.to_s.start_with?('/')
|
|
256
277
|
end
|
|
257
278
|
|
|
258
279
|
# Emit a yellow [hammer] warning on stderr when a task/namespace is
|
|
@@ -392,6 +413,13 @@ class Hammer
|
|
|
392
413
|
return print_help
|
|
393
414
|
end
|
|
394
415
|
|
|
416
|
+
# An exact namespace beats a fuzzy command match, so `hammer h` lists
|
|
417
|
+
# the `h:` namespace instead of prefix-matching some `h...` command.
|
|
418
|
+
if !commands.key?(name) && namespaces.key?(name)
|
|
419
|
+
ns, canonical = resolve_namespace(name)
|
|
420
|
+
return print_namespace_help(canonical, ns)
|
|
421
|
+
end
|
|
422
|
+
|
|
395
423
|
cmd, owner, canonical = resolve(name)
|
|
396
424
|
return owner.run_command(cmd, argv, full: canonical) if cmd
|
|
397
425
|
|
|
@@ -497,6 +525,7 @@ class Hammer
|
|
|
497
525
|
# Tries prefix match first, then substring; raises AmbiguousMatch
|
|
498
526
|
# when either pass hits more than one item.
|
|
499
527
|
def fuzzy_pick(name, items, kind, &keys_for)
|
|
528
|
+
return nil if name.empty?
|
|
500
529
|
[:start_with?, :include?].each do |op|
|
|
501
530
|
matches = items.select { |item| keys_for.call(item).any? { |k| k.send(op, name) } }
|
|
502
531
|
next if matches.empty?
|
|
@@ -527,21 +556,30 @@ class Hammer
|
|
|
527
556
|
opts.each do |k, v|
|
|
528
557
|
next if v == false
|
|
529
558
|
flag = "--#{k.to_s.tr('_', '-')}"
|
|
530
|
-
|
|
559
|
+
if v == true
|
|
560
|
+
argv << flag
|
|
561
|
+
else
|
|
562
|
+
argv << "#{flag}=#{v.is_a?(Array) ? v.join(',') : v}"
|
|
563
|
+
end
|
|
531
564
|
end
|
|
532
565
|
start(argv)
|
|
533
566
|
end
|
|
534
567
|
|
|
535
568
|
# Yield [full_colon_path, Command] for every command in this class
|
|
536
|
-
# and all nested namespaces.
|
|
537
|
-
|
|
569
|
+
# and all nested namespaces. `include_builtins: false` prunes
|
|
570
|
+
# namespaces flagged `@builtin_namespace` (the reserved `h:` tree) -
|
|
571
|
+
# used so the compact listing hides built-ins outside `--help`. Only
|
|
572
|
+
# affects descent from a parent; iterating a flagged namespace
|
|
573
|
+
# directly (e.g. `hammer h:`) still lists its own commands.
|
|
574
|
+
def each_command(prefix = nil, include_builtins: true, &block)
|
|
538
575
|
commands.each_value do |c|
|
|
539
576
|
full = prefix ? "#{prefix}:#{c.name}" : c.name
|
|
540
577
|
yield full, c
|
|
541
578
|
end
|
|
542
579
|
namespaces.each do |ns_name, sub|
|
|
580
|
+
next if !include_builtins && sub.instance_variable_get(:@builtin_namespace)
|
|
543
581
|
sub_prefix = prefix ? "#{prefix}:#{ns_name}" : ns_name
|
|
544
|
-
sub.each_command(sub_prefix, &block)
|
|
582
|
+
sub.each_command(sub_prefix, include_builtins: include_builtins, &block)
|
|
545
583
|
end
|
|
546
584
|
end
|
|
547
585
|
|
|
@@ -580,7 +618,7 @@ class Hammer
|
|
|
580
618
|
if o.boolean?
|
|
581
619
|
parts << (val ? "--#{o.name}" : "--no-#{o.name}")
|
|
582
620
|
else
|
|
583
|
-
parts << "--#{o.name}=#{val}"
|
|
621
|
+
parts << "--#{o.name}=#{val.is_a?(Array) ? val.join(',') : val}"
|
|
584
622
|
end
|
|
585
623
|
end
|
|
586
624
|
parts.concat(positional)
|
|
@@ -591,6 +629,9 @@ class Hammer
|
|
|
591
629
|
# Each class's hooks fire at most once per top-level `start`, so
|
|
592
630
|
# prereqs dispatched via `needs` won't re-trigger them.
|
|
593
631
|
def run_before_hooks(instance, opts)
|
|
632
|
+
# Built-in `h:` meta-commands parent to the project root but must not
|
|
633
|
+
# trigger the project's own `before` hooks (dotenv, env checks, ...).
|
|
634
|
+
return if instance_variable_get(:@builtin_namespace)
|
|
594
635
|
ran = Thread.current[:hammer_before_ran] ||= {}
|
|
595
636
|
ancestor_chain.each do |klass|
|
|
596
637
|
next if ran[klass.object_id]
|
|
@@ -640,15 +681,29 @@ class Hammer
|
|
|
640
681
|
|
|
641
682
|
print_top_banner
|
|
642
683
|
Shell.say "Usage: #{program_name} COMMAND [ARGS]", :cyan
|
|
684
|
+
# Compact (bare-invocation) view only - the extended `--help` view
|
|
685
|
+
# already IS the full usage, so don't nag about it there.
|
|
686
|
+
unless extended
|
|
687
|
+
Shell.say "add `--help` to show usage help", :gray
|
|
688
|
+
# No project Hammerfile + no custom tasks loaded: point the user at
|
|
689
|
+
# `h:init`. The flag is set by `Hammer.cli` when the lookup misses.
|
|
690
|
+
if instance_variable_get(:@no_hammerfile)
|
|
691
|
+
Shell.say "no Hammerfile found in #{Dir.pwd} - run `#{program_name} h:init` to create one", :gray
|
|
692
|
+
end
|
|
693
|
+
end
|
|
643
694
|
if @app_desc && !@app_desc.empty?
|
|
644
695
|
Shell.say ''
|
|
645
696
|
@app_desc.each_line { |l| Shell.say " #{l.chomp}" }
|
|
646
697
|
end
|
|
698
|
+
# Built-in `h:` commands only surface in the extended view
|
|
699
|
+
# (`--help` / `-h` / `help`); the bare-invocation listing stays
|
|
700
|
+
# focused on the project's own tasks. They remain dispatchable
|
|
701
|
+
# regardless - this only governs what the listing shows.
|
|
647
702
|
if expanded
|
|
648
|
-
each_command { |path, c| print_full_block(path, c) unless c.desc.empty? }
|
|
703
|
+
each_command(include_builtins: extended) { |path, c| print_full_block(path, c) unless c.desc.empty? }
|
|
649
704
|
else
|
|
650
705
|
Shell.say ''
|
|
651
|
-
print_command_list(self)
|
|
706
|
+
print_command_list(self, include_builtins: extended)
|
|
652
707
|
end
|
|
653
708
|
print_recipes_section if extended && root.instance_variable_get(:@hammer_binary)
|
|
654
709
|
print_extras if extended
|
|
@@ -667,7 +722,7 @@ class Hammer
|
|
|
667
722
|
entries.each do |name, file|
|
|
668
723
|
desc = Hammer::Recipe.desc(file)
|
|
669
724
|
installed = Hammer::Recipe.installed_path(name)
|
|
670
|
-
suffix = installed ? "(installed: #{installed})" : "[install: #{program_name} recipes --install #{name}]"
|
|
725
|
+
suffix = installed ? "(installed: #{installed})" : "[install: #{program_name} h:recipes --install #{name}]"
|
|
671
726
|
Shell.say " #{name.ljust(width)} # #{desc}"
|
|
672
727
|
Shell.say " #{' ' * width} #{suffix}", :gray
|
|
673
728
|
end
|
|
@@ -735,24 +790,24 @@ class Hammer
|
|
|
735
790
|
|
|
736
791
|
def print_footer
|
|
737
792
|
Shell.say ''
|
|
738
|
-
Shell.say "powered by hammer - #{HOMEPAGE}", :gray
|
|
793
|
+
Shell.say "powered by hammer (v#{VERSION}) - #{HOMEPAGE}", :gray
|
|
739
794
|
end
|
|
740
795
|
|
|
741
796
|
# Hammerfile cheat-sheet shown under `hammer --help`. Same content
|
|
742
797
|
# as `hammer --init` writes - single source of truth via
|
|
743
|
-
# `Hammer::STARTER_HAMMERFILE`. For exhaustive docs see `hammer agents`.
|
|
798
|
+
# `Hammer::STARTER_HAMMERFILE`. For exhaustive docs see `hammer h:agents`.
|
|
744
799
|
def print_hammerfile_example
|
|
745
800
|
Shell.say ''
|
|
746
801
|
Shell.say 'Hammerfile example:', :yellow
|
|
747
802
|
Shell.say Hammer::STARTER_HAMMERFILE
|
|
748
803
|
end
|
|
749
804
|
|
|
750
|
-
def print_command_list(klass, prefix = nil)
|
|
805
|
+
def print_command_list(klass, prefix = nil, include_builtins: true)
|
|
751
806
|
rows = []
|
|
752
807
|
# Commands without a `desc` are hidden from listings but still
|
|
753
808
|
# dispatchable + `hammer`-callable - useful for private helpers
|
|
754
809
|
# invoked from `before` hooks or other commands (e.g. `:env`, `:app`).
|
|
755
|
-
klass.each_command(prefix) { |full, c| rows << [full, c] unless c.desc.empty? }
|
|
810
|
+
klass.each_command(prefix, include_builtins: include_builtins) { |full, c| rows << [full, c] unless c.desc.empty? }
|
|
756
811
|
return if rows.empty?
|
|
757
812
|
|
|
758
813
|
# group by "section" = everything between the view prefix and the
|
|
@@ -890,7 +945,7 @@ class Hammer
|
|
|
890
945
|
Shell.print_error "unknown recipe: #{name}"
|
|
891
946
|
Shell.say 'available recipes:', :yellow
|
|
892
947
|
Recipe.all.keys.sort.each { |n| Shell.say " #{n}" }
|
|
893
|
-
Shell.say 'try `hammer recipes` to list with descriptions', :gray
|
|
948
|
+
Shell.say 'try `hammer h:recipes` to list with descriptions', :gray
|
|
894
949
|
exit 1
|
|
895
950
|
end
|
|
896
951
|
|
|
@@ -946,12 +1001,12 @@ class Hammer
|
|
|
946
1001
|
end
|
|
947
1002
|
RUBY
|
|
948
1003
|
|
|
949
|
-
# Default install dir used by install.sh and `hammer update`.
|
|
1004
|
+
# Default install dir used by install.sh and `hammer h:update`.
|
|
950
1005
|
SELF_UPDATE_DIR ||= File.expand_path('~/.local/share/lux-hammer')
|
|
951
1006
|
SELF_UPDATE_REPO ||= 'https://github.com/dux/hammer.git'
|
|
952
1007
|
SELF_INSTALL_URL ||= 'https://raw.githubusercontent.com/dux/hammer/main/install.sh'
|
|
953
1008
|
|
|
954
|
-
# `hammer update`: pull main in the install-script checkout and
|
|
1009
|
+
# `hammer h:update`: pull main in the install-script checkout and
|
|
955
1010
|
# reinstall the gem. Assumes the install.sh layout - if the dir is
|
|
956
1011
|
# missing, point the user at the curl-pipe installer.
|
|
957
1012
|
def self.self_update
|
|
@@ -1007,15 +1062,17 @@ class Hammer
|
|
|
1007
1062
|
path = force_system ? nil : find_hammerfile(Dir.pwd)
|
|
1008
1063
|
unless path
|
|
1009
1064
|
# No Hammerfile (or --system) - all built-ins are reachable. Bare
|
|
1010
|
-
# `hammer`, `hammer recipes`, `hammer update`, `hammer agents`,
|
|
1011
|
-
# `hammer version`, `hammer init` all work.
|
|
1065
|
+
# `hammer`, `hammer h:recipes`, `hammer h:update`, `hammer h:agents`,
|
|
1066
|
+
# `hammer h:version`, `hammer h:init` all work.
|
|
1012
1067
|
if force_system || dispatches_to_builtin?(argv) || looks_like_builtin?(argv)
|
|
1013
1068
|
klass = Class.new(Hammer)
|
|
1014
1069
|
klass.instance_variable_set(:@hammer_binary, true)
|
|
1070
|
+
# No project Hammerfile was found - only built-ins are loaded. The
|
|
1071
|
+
# bare-invocation help uses this to note that no Hammerfile exists.
|
|
1072
|
+
klass.instance_variable_set(:@no_hammerfile, true)
|
|
1015
1073
|
klass.program_name
|
|
1016
1074
|
require_relative 'hammer/builtins'
|
|
1017
|
-
Hammer::Builtins.
|
|
1018
|
-
Hammer::Builtins.register_no_project(klass)
|
|
1075
|
+
Hammer::Builtins.register(klass)
|
|
1019
1076
|
klass.start(argv)
|
|
1020
1077
|
return
|
|
1021
1078
|
end
|
|
@@ -1039,8 +1096,8 @@ class Hammer
|
|
|
1039
1096
|
Shell.say STARTER_HAMMERFILE
|
|
1040
1097
|
Shell.say ''
|
|
1041
1098
|
bin = File.basename($PROGRAM_NAME)
|
|
1042
|
-
Shell.say "tip: run `#{bin} init` to drop the example above into ./Hammerfile", :gray
|
|
1043
|
-
Shell.say "tip: run `#{bin} agents` for AI-friendly Hammerfile authoring docs", :gray
|
|
1099
|
+
Shell.say "tip: run `#{bin} h:init` to drop the example above into ./Hammerfile", :gray
|
|
1100
|
+
Shell.say "tip: run `#{bin} h:agents` for AI-friendly Hammerfile authoring docs", :gray
|
|
1044
1101
|
exit 1
|
|
1045
1102
|
end
|
|
1046
1103
|
|
|
@@ -1061,14 +1118,13 @@ class Hammer
|
|
|
1061
1118
|
# are NOT visible during Hammerfile evaluation, only inside handlers.
|
|
1062
1119
|
Hammer::Dotenv.load(Dir.pwd) if klass.dotenv_enabled?
|
|
1063
1120
|
|
|
1064
|
-
#
|
|
1065
|
-
#
|
|
1066
|
-
#
|
|
1067
|
-
# `
|
|
1068
|
-
#
|
|
1069
|
-
# `hammer --system recipes` to reach them from inside a project.
|
|
1121
|
+
# Built-ins register AFTER Hammerfile eval so user-defined tasks win
|
|
1122
|
+
# (the `unless commands.key?(...)` guards skip a built-in when the
|
|
1123
|
+
# Hammerfile already owns the name - no redefinition warning). All
|
|
1124
|
+
# built-ins live under `h:`, so they can't collide with project root
|
|
1125
|
+
# tasks and the full set registers in every context.
|
|
1070
1126
|
require_relative 'hammer/builtins'
|
|
1071
|
-
Hammer::Builtins.
|
|
1127
|
+
Hammer::Builtins.register(klass)
|
|
1072
1128
|
|
|
1073
1129
|
klass.start(argv)
|
|
1074
1130
|
end
|
|
@@ -1088,7 +1144,7 @@ class Hammer
|
|
|
1088
1144
|
end
|
|
1089
1145
|
|
|
1090
1146
|
# Evaluate a shebang script as a self-contained CLI. Mirrors `recipe`
|
|
1091
|
-
# semantics: no chdir, no `@hammer_binary` flag, no `
|
|
1147
|
+
# semantics: no chdir, no `@hammer_binary` flag, no `Builtins.register`
|
|
1092
1148
|
# built-ins (so the script's `--help` shows only what it defines).
|
|
1093
1149
|
# `program_name` is the script's basename so help reads "myscript foo"
|
|
1094
1150
|
# rather than "hammer foo" - works even when invoked via a symlink in
|
|
@@ -1110,15 +1166,14 @@ class Hammer
|
|
|
1110
1166
|
first == 'help' || first == '-h' || first == '--help' || first.start_with?('-')
|
|
1111
1167
|
end
|
|
1112
1168
|
|
|
1113
|
-
# True if argv
|
|
1114
|
-
# `
|
|
1115
|
-
# built-ins for invocations like `hammer recipes` that aren't a flag
|
|
1169
|
+
# True if argv targets the reserved `h:` built-in namespace (`h`, `h:`,
|
|
1170
|
+
# `h:update`, ...). Used in the no-Hammerfile branch to wake up the
|
|
1171
|
+
# built-ins for invocations like `hammer h:recipes` that aren't a flag
|
|
1116
1172
|
# or help request.
|
|
1117
|
-
BUILTIN_TASKS ||= %w[recipes update agents version init].freeze
|
|
1118
1173
|
def self.looks_like_builtin?(argv)
|
|
1119
1174
|
first = argv.first
|
|
1120
1175
|
return false unless first
|
|
1121
|
-
|
|
1176
|
+
first == 'h' || first.start_with?('h:')
|
|
1122
1177
|
end
|
|
1123
1178
|
|
|
1124
1179
|
# Walk up the directory tree looking for a Hammerfile.
|
data/recipes/deploy.rb
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
#!/usr/bin/env hammer
|
|
2
|
+
# desc: SSH/rsync deploy - Caddy + systemd + atomic releases
|
|
3
|
+
|
|
4
|
+
desc <<~TXT
|
|
5
|
+
Stupid-simple SSH/rsync deploy (Caddy + systemd + atomic releases).
|
|
6
|
+
|
|
7
|
+
Quickstart:
|
|
8
|
+
deploy app:init # copy templates into ./config/deploy/
|
|
9
|
+
deploy doctor # check & prep the host
|
|
10
|
+
deploy up # deploy current branch
|
|
11
|
+
|
|
12
|
+
Server:
|
|
13
|
+
deploy server:log # tail the systemd journal
|
|
14
|
+
deploy server:ssh # shell into the current release
|
|
15
|
+
deploy server:restart # restart the web service
|
|
16
|
+
deploy log --log errors # dump a remote log
|
|
17
|
+
TXT
|
|
18
|
+
|
|
19
|
+
# Loads the bundled lib (config/ssh/template/doctor/context/manifest/
|
|
20
|
+
# commands/hammer) - same require chain the gem's lib/lux_deploy.rb had.
|
|
21
|
+
require_relative 'lib/deploy/boot'
|
|
22
|
+
|
|
23
|
+
# Auto-load the app's deploy bootstrap, if present, before tasks fire.
|
|
24
|
+
# A consumer can inject Ruby (e.g. a pre-deploy hook) without writing a
|
|
25
|
+
# custom Hammerfile.
|
|
26
|
+
init = File.join(Dir.pwd, 'config', 'deploy', 'init.rb')
|
|
27
|
+
load init if File.file?(init)
|
|
28
|
+
|
|
29
|
+
# `self` here is the recipe's Builder context - same surface a Hammerfile
|
|
30
|
+
# gets. Pass templates_dir explicitly since there's no gem ROOT to fall
|
|
31
|
+
# back to; it resolves to recipes/lib/deploy/templates.
|
|
32
|
+
LuxDeploy::Hammer.register(self, templates_dir: File.join(__dir__, 'lib/deploy/templates'))
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
require 'fileutils'
|
|
2
|
+
require 'pathname'
|
|
3
|
+
require 'open3'
|
|
4
|
+
require 'set'
|
|
5
|
+
require 'shellwords'
|
|
6
|
+
require 'yaml'
|
|
7
|
+
|
|
8
|
+
module LuxDeploy
|
|
9
|
+
# Recipe layout: this file and its siblings live under
|
|
10
|
+
# recipes/lib/deploy/, so ROOT points here and `templates/` sits
|
|
11
|
+
# right next to us (no gem root anymore).
|
|
12
|
+
ROOT ||= Pathname.new(__dir__)
|
|
13
|
+
VERSION ||= '0.2.0'
|
|
14
|
+
|
|
15
|
+
# Branches that select `.env` instead of `.env.staging`.
|
|
16
|
+
MAIN_BRANCHES ||= %w[master main]
|
|
17
|
+
|
|
18
|
+
# Server-side conventions. Not config-tunable because doctor and the
|
|
19
|
+
# deploy flow both hardcode these paths in the host setup. A different
|
|
20
|
+
# caddy/systemd layout means a different recipe.
|
|
21
|
+
PORT_RANGE ||= (3010..3990).step(10).to_a
|
|
22
|
+
CADDY_SITES ||= '/etc/caddy/sites'
|
|
23
|
+
SYSTEMD_DIR ||= '/etc/systemd/system'
|
|
24
|
+
|
|
25
|
+
class Error < StandardError
|
|
26
|
+
def to_s
|
|
27
|
+
"ERROR: #{super}"
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# Host-supplied defaults that sit under the user's .yaml. Set once by a
|
|
32
|
+
# wrapping plugin/Hammerfile (e.g. lux-fw seeds 'lux-web' / 'lux-apps'),
|
|
33
|
+
# consumed by Config.new. Empty by default so the recipe stays "generic".
|
|
34
|
+
@defaults = {}
|
|
35
|
+
|
|
36
|
+
class << self
|
|
37
|
+
attr_reader :defaults
|
|
38
|
+
|
|
39
|
+
def set_defaults(hash)
|
|
40
|
+
@defaults = (hash || {}).each_with_object({}) { |(k, v), h| h[k.to_s] = v }
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
require_relative 'config'
|
|
46
|
+
require_relative 'ssh'
|
|
47
|
+
require_relative 'template'
|
|
48
|
+
require_relative 'doctor'
|
|
49
|
+
require_relative 'context'
|
|
50
|
+
require_relative 'manifest'
|
|
51
|
+
require_relative 'commands'
|
|
52
|
+
require_relative 'hammer'
|