heighliner 0.9.0 → 0.10.0

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2652edf0b183383b89645cee4e2d22210c2099773c124fdfb44073dd9755be47
4
- data.tar.gz: ae906d0864fd3f2ceca23cb94c1675ffcf219a4ec4fd3de3082fbaf64a0cd4eb
3
+ metadata.gz: 07c572f9013340944a13b0fb7a784e97e4d9e589bddd9937d60df842346f9d80
4
+ data.tar.gz: 8d9c848d91867755298a95474fc53225f04d5bed4a7811f681b86b6930260300
5
5
  SHA512:
6
- metadata.gz: 4134df6ece248fc364167114265cef9bbe7b39bb4b7746a0d0cbb23f1e0dc22b816d27093295edcc441fe2f0a5fd1e08ccac77cfe03fb37e44e7035a136f8753
7
- data.tar.gz: 14d9595f84c5e5f545f942e6ce702bb0c8d97fc2e6603547a46c750caec41a32ebc7285059711b2e960a821d795da5f8ae36d814dd39fd289da071ef60fa3de6
6
+ metadata.gz: 26b7a442e80ee8249f08a854faa624f03b6af32845f151958f12fd35c162755f10642d9a098d0bbe76922f975feca0525fe9ba6a814abc9f74d1e6055eb0c028
7
+ data.tar.gz: 971f71b9a51aa4e27404289f2b8d61614168e0a59a83da1189d4ee2c6569500cfef412b968657e0338d3d185f7da74311ded0c074591078a0cbf59bb1bd1c9fc
data/README.md CHANGED
@@ -112,6 +112,12 @@ open http://myapp.lvh.me
112
112
 
113
113
  And enjoy previewing your app!
114
114
 
115
+ ## Examples
116
+
117
+ See the [examples](examples/) directory for starter projects:
118
+
119
+ - [simple](examples/simple/) — a minimal Sinatra + Puma app
120
+
115
121
  ## More documentation
116
122
 
117
123
  You can find even more documentation in https://davidsiaw.github.io/heighliner.
@@ -145,6 +151,9 @@ alias heighliner='docker run --rm -ti \
145
151
  -e _HEIGHLINER_USER_HOME=$HOME \
146
152
  -e _HEIGHLINER_POS=docker \
147
153
  -e CONTEXT_DIR="`pwd`" \
154
+ -e GIT_CONFIG_COUNT=1 \
155
+ -e GIT_CONFIG_KEY_0=safe.directory \
156
+ -e GIT_CONFIG_VALUE_0="$PWD" \
148
157
  davidsiaw/heighliner'
149
158
  ```
150
159
 
@@ -163,6 +172,9 @@ alias heighliner='docker run --rm -ti \
163
172
  -e _HEIGHLINER_USER_HOME=$HOME \
164
173
  -e _HEIGHLINER_POS=docker \
165
174
  -e CONTEXT_DIR="`pwd`" \
175
+ -e GIT_CONFIG_COUNT=1 \
176
+ -e GIT_CONFIG_KEY_0=safe.directory \
177
+ -e GIT_CONFIG_VALUE_0="$PWD" \
166
178
  -e OP_SERVICE_ACCOUNT_TOKEN=`op read op://Private/local-dev/credential` \
167
179
  davidsiaw/heighliner'
168
180
  ```
@@ -503,60 +503,76 @@ module Heighliner
503
503
 
504
504
  def copy_keyfile(file)
505
505
  Config.info_out.puts "Loading certificate file: #{file}"
506
- if Config.config[:cert_source][:folder]
507
- Config.info_out.puts " Source: folder (#{Config.config[:cert_source][:folder]})"
508
- CommandRunner.run! Config.out, "docker run --rm
509
- -v #{Config.config[:shared_names][:certs]}:/certs
510
- -v #{Config.config[:cert_source][:folder]}:/cert_source
511
- alpine cp /cert_source/#{file} /certs/#{file}"
512
-
513
- elsif Config.config[:cert_source][:url]
514
- Config.info_out.puts " Source: URL (#{Config.config[:cert_source][:url]}/#{file})"
515
- CommandRunner.run! Config.out, "docker run --rm
516
- -v #{Config.config[:shared_names][:certs]}:/certs
517
- alpine wget #{Config.config[:cert_source][:url]}/#{file}
518
- -O /certs/#{file}"
519
-
520
- elsif Config.config[:cert_source][:"1password"]
521
- item = Config.config[:cert_source][:"1password"]
522
- origfield = file.sub(/^#{http_suffix}\./, '')
523
- token = ENV['OP_SERVICE_ACCOUNT_TOKEN']
524
-
525
- field = origfield
526
- if Config.config[:cert_source]["1password-fields"]
527
- field = Config.config[:cert_source]["1password-fields"][origfield]
528
- end
506
+ cert_source = Config.config[:cert_source]
507
+
508
+ if cert_source[:folder]
509
+ copy_keyfile_from_folder(file)
510
+ elsif cert_source[:url]
511
+ copy_keyfile_from_url(file)
512
+ elsif cert_source[:"1password"]
513
+ copy_keyfile_from_1password(file)
514
+ end
515
+ end
516
+
517
+ def copy_keyfile_from_folder(file)
518
+ folder = Config.config[:cert_source][:folder]
519
+ Config.info_out.puts " Source: folder (#{folder})"
520
+ CommandRunner.run! Config.out, "docker run --rm
521
+ -v #{Config.config[:shared_names][:certs]}:/certs
522
+ -v #{folder}:/cert_source
523
+ alpine cp /cert_source/#{file} /certs/#{file}"
524
+ end
529
525
 
530
- raise Heighliner::Error, 'OP_SERVICE_ACCOUNT_TOKEN is not set' unless token
526
+ def copy_keyfile_from_url(file)
527
+ url = Config.config[:cert_source][:url]
528
+ Config.info_out.puts " Source: URL (#{url}/#{file})"
529
+ CommandRunner.run! Config.out, "docker run --rm
530
+ -v #{Config.config[:shared_names][:certs]}:/certs
531
+ alpine wget #{url}/#{file}
532
+ -O /certs/#{file}"
533
+ end
531
534
 
532
- Config.info_out.puts " Source: 1Password (item: #{item}, field: #{field} (#{origfield}) )"
535
+ def onepassword_field_for(file)
536
+ origfield = file.sub(/^#{http_suffix}\./, '')
537
+ fields = Config.config[:cert_source]['1password-fields']
538
+ field = fields ? fields[origfield] : origfield
539
+ [origfield, field]
540
+ end
533
541
 
534
- # take it out
535
- certstoredir = "#{ENV['CONTEXT_DIR']}/.tmp.certstore"
536
- tmpfile = "#{certstoredir}/#{file}"
537
- CommandRunner.run!(Config.out, "mkdir -p #{certstoredir}")
538
- CommandRunner.run!(Config.out, "op read \"op://#{item}/#{field}\" > #{tmpfile}")
539
- Config.info_out.puts("wrote into file #{tmpfile}")
540
- CommandRunner.run!(Config.out, "ls #{tmpfile}")
542
+ def copy_keyfile_from_1password(file)
543
+ item = Config.config[:cert_source][:"1password"]
544
+ origfield, field = onepassword_field_for(file)
541
545
 
542
- # put it in
543
- CommandRunner.run! Config.out, "docker run --rm
544
- -v #{Config.config[:shared_names][:certs]}:/certs
545
- -v #{certstoredir}:/tmpcert
546
- alpine
547
- cp /tmpcert/#{file} /certs/#{file}"
546
+ raise Heighliner::Error, 'OP_SERVICE_ACCOUNT_TOKEN is not set' unless ENV['OP_SERVICE_ACCOUNT_TOKEN']
548
547
 
549
- unless File.exist?(tmpfile) && File.size(tmpfile).positive?
550
- raise Heighliner::Error,
551
- "1Password field '#{field}' not found in item '#{item}'"
552
- end
548
+ Config.info_out.puts " Source: 1Password (item: #{item}, field: #{field} (#{origfield}) )"
549
+
550
+ certstoredir = "#{ENV['CONTEXT_DIR']}/.tmp.certstore"
551
+ tmpfile = "#{certstoredir}/#{file}"
553
552
 
554
- CommandRunner.run! Config.out, "docker run --rm
555
- -v #{Config.config[:shared_names][:certs]}:/certs
556
- -v #{tmpfile}:/cert_source
557
- alpine cp /cert_source /certs/#{file}"
558
- CommandRunner.run!(Config.out, "rm #{tmpfile}")
553
+ # take it out
554
+ CommandRunner.run!(Config.out, "mkdir -p #{certstoredir}")
555
+ CommandRunner.run!(Config.out, "op read \"op://#{item}/#{field}\" > #{tmpfile}")
556
+ Config.info_out.puts("wrote into file #{tmpfile}")
557
+ CommandRunner.run!(Config.out, "ls #{tmpfile}")
558
+
559
+ # put it in
560
+ CommandRunner.run! Config.out, "docker run --rm
561
+ -v #{Config.config[:shared_names][:certs]}:/certs
562
+ -v #{certstoredir}:/tmpcert
563
+ alpine
564
+ cp /tmpcert/#{file} /certs/#{file}"
565
+
566
+ unless File.exist?(tmpfile) && File.size(tmpfile).positive?
567
+ raise Heighliner::Error,
568
+ "1Password field '#{field}' not found in item '#{item}'"
559
569
  end
570
+
571
+ CommandRunner.run! Config.out, "docker run --rm
572
+ -v #{Config.config[:shared_names][:certs]}:/certs
573
+ -v #{tmpfile}:/cert_source
574
+ alpine cp /cert_source /certs/#{file}"
575
+ CommandRunner.run!(Config.out, "rm #{tmpfile}")
560
576
  end
561
577
 
562
578
  def prepare_cert_volume!
@@ -596,8 +612,6 @@ module Heighliner
596
612
  def ensure_setup
597
613
  ensure_env
598
614
 
599
- setup if network.nil?
600
-
601
615
  create_if_network_not_exist Config.config[:networkname]
602
616
  if_container_dead Config.config[:shared_names][:nginx] do
603
617
  prepare_cert_volume!
@@ -615,8 +629,11 @@ module Heighliner
615
629
  jwilder/nginx-proxy"
616
630
  )
617
631
 
618
- innerdnsconffile = "#{ENV['HOME']}/.heighliner/dnsconf"
619
- outerdnsconffile = "#{home_dir_loc}/.heighliner/dnsconf"
632
+ # The config dir is ~/.kaiser on hosts upgraded from Kaiser, so derive the
633
+ # directory name instead of hardcoding .heighliner.
634
+ config_dir_name = File.basename(Config.config_dir)
635
+ innerdnsconffile = "#{Config.config_dir}/dnsconf"
636
+ outerdnsconffile = "#{home_dir_loc}/#{config_dir_name}/dnsconf"
620
637
  File.write(innerdnsconffile, <<~HOSTS)
621
638
  log-queries
622
639
  no-resolv
@@ -656,8 +673,20 @@ module Heighliner
656
673
  `docker inspect -f '{{#{networkname}}}' #{containername}`.chomp
657
674
  end
658
675
 
659
- def network
660
- `docker network inspect #{Config.config[:networkname]} 2>/dev/null`
676
+ def network_inspect_output(net)
677
+ `docker network inspect #{net} 2>/dev/null`
678
+ end
679
+
680
+ def network_exists?(net)
681
+ out = network_inspect_output(net)
682
+ return false if out.strip.empty?
683
+
684
+ !JSON.parse(out).empty?
685
+ rescue JSON::ParserError
686
+ # Unparseable output is not evidence the network is absent, and guessing
687
+ # "absent" is the expensive guess: creating an existing network exits 1 and
688
+ # kills the command. Assume present and let docker report a real problem.
689
+ true
661
690
  end
662
691
 
663
692
  def container_dead?(container)
@@ -679,12 +708,14 @@ module Heighliner
679
708
  end
680
709
 
681
710
  def create_if_network_not_exist(net)
682
- out = `docker inspect #{net} 2>/dev/null`
683
- out = "[]" if out.strip.empty?
684
- x = JSON.parse(out)
685
- return unless x.empty?
711
+ return if network_exists?(net)
712
+
713
+ # Another heighliner process may create the network between the check and
714
+ # here, so a failure is not fatal by itself: re-check instead of raising.
715
+ CommandRunner.run Config.out, "docker network create #{net}"
716
+ return if network_exists?(net)
686
717
 
687
- CommandRunner.run! Config.out, "docker network create #{net}"
718
+ raise Heighliner::Error, "could not create docker network #{net}"
688
719
  end
689
720
 
690
721
  def run_if_dead(container, command)
@@ -14,7 +14,7 @@ module Heighliner
14
14
 
15
15
  def load(work_dir, use_steerfile: true)
16
16
  @work_dir = work_dir
17
- @config_dir = "#{ENV['HOME']}/.heighliner"
17
+ @config_dir = detect_config_dir
18
18
 
19
19
  migrate_dotted_config_files
20
20
 
@@ -38,9 +38,7 @@ module Heighliner
38
38
  load_config
39
39
 
40
40
  if use_steerfile
41
- POSSIBLE_STEERFILES.each do |x|
42
- @fname = x if File.exist?(x)
43
- end
41
+ @fname = detect_steerfile_name
44
42
 
45
43
  Optimist.die <<~ERROR if @fname.nil?
46
44
  No Steerfile in current directory.
@@ -53,16 +51,41 @@ module Heighliner
53
51
  @config
54
52
  end
55
53
 
54
+ # Later entries win over earlier ones, so the legacy Kaiser name comes
55
+ # first: a repo with both files uses the Steerfile.
56
56
  POSSIBLE_STEERFILES = %w[
57
+ Kaiserfile
57
58
  Steerfile
58
59
  Heighliner.config
59
60
  heighliner.config
60
61
  ].freeze
61
62
 
63
+ # Kaiserfile is the name Heighliner used when it was called Kaiser. It has
64
+ # the same contents as a Steerfile, so it is loaded the same way.
65
+ def detect_steerfile_name
66
+ POSSIBLE_STEERFILES.select { |x| File.exist?(x) }.last
67
+ end
68
+
62
69
  def always_verbose?
63
70
  @config[:always_verbose]
64
71
  end
65
72
 
73
+ # Heighliner used to be called Kaiser. If a user has an old ~/.kaiser
74
+ # directory and no ~/.heighliner directory yet, keep using the old one
75
+ # instead of starting from scratch.
76
+ def detect_config_dir
77
+ heighliner_dir = "#{home_dir}/.heighliner"
78
+ kaiser_dir = "#{home_dir}/.kaiser"
79
+
80
+ return kaiser_dir if !Dir.exist?(heighliner_dir) && Dir.exist?(kaiser_dir)
81
+
82
+ heighliner_dir
83
+ end
84
+
85
+ def home_dir
86
+ ENV['HOME']
87
+ end
88
+
66
89
  # Up until version 0.5.1, heighliner used dotfiles for all of it configuration.
67
90
  # It makes sense of hide the configuration directory itself but hiding the files
68
91
  # inside of it just causes confusion.
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Heighliner
4
- VERSION = '0.9.0'
4
+ VERSION = '0.10.0'
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: heighliner
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.9.0
4
+ version: 0.10.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - David Siaw
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2026-06-20 00:00:00.000000000 Z
11
+ date: 2026-08-12 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport