Machine bootstrapping tool with a focus on sensible defaults, conventions, and avoidance of vendoring - This is a mirror

Update for Homebrew 4.6.0 (#67)

Removes the last use of the `brew cask` subcommand, which no longer
works.

Also replaces `launchdns` with `dnsmasq`, which has been disabled in
`homebrew-core` due to the upstream project being archived.

authored by mx4ke.xyz and committed by GitHub eecdd0c7 0d9c5717

Changed files
+171 -13
.github
workflows
spec
provisioning
src
mstrap
+1
.github/workflows/provision.yml
··· 75 75 - fedora-38-test 76 76 - fedora-39-test 77 77 - ubuntu-2204-test 78 + - ubuntu-2404-test 78 79 runs-on: ubuntu-latest 79 80 80 81 steps:
+21
CHANGELOG.md
··· 14 14 15 15 ### Removed 16 16 17 + ## [0.7.2] - 2025-08-13 18 + 19 + ### Added 20 + 21 + * ci: Add Ubuntu 24.04 LTS to CI 22 + 23 + ### Changed 24 + 25 + * macOS: replace `launchdns` with `dnsmasq` for `*.localhost` TLD resolution for 26 + bootstrapped web projects 27 + 28 + `launchdns` has been removed from Homebrew after the upstream project was 29 + archived, so it is no longer installable. `dnsmasq` is a mostly drop-in 30 + replacement 31 + 32 + ### Bugfixes 33 + 34 + * Fixed install of Docker Desktop on macOS via Homebrew 35 + 36 + ### Removed 37 + 17 38 ## [0.7.1] - 2025-02-06 18 39 19 40 ### Added
+16
spec/provisioning/docker/ubuntu-2404-test.Dockerfile
··· 1 + FROM ubuntu:24.04 2 + 3 + RUN apt-get update && \ 4 + apt-get -y install \ 5 + curl expat libexpat1-dev lsb-release ruby ruby-bundler \ 6 + openssh-client sudo zsh && \ 7 + apt-get clean all 8 + 9 + RUN useradd -m -s /bin/bash -G sudo mstrap 10 + RUN echo 'mstrap ALL=(ALL) NOPASSWD:ALL' >> /etc/sudoers 11 + 12 + USER mstrap 13 + RUN mkdir -p $HOME/.mstrap 14 + ADD --chown=mstrap --chmod=600 config.hcl $HOME/.mstrap/ 15 + ADD test.sh test.sh 16 + CMD [ "./test.sh" ]
-6
spec/provisioning/spec/localhost/dependencies_step_spec.rb
··· 59 59 end 60 60 end 61 61 end 62 - 63 - describe "launchdns", :if => os[:family] == 'darwin' do 64 - describe service('homebrew.mxcl.launchdns') do 65 - it { is_expected.to be_enabled } 66 - end 67 - end 68 62 end
+4
src/mstrap/bootstrappers/web_bootstrapper.cr
··· 7 7 8 8 def initialize(@config : Configuration) 9 9 super 10 + @dnsmasq = DNSMasq.new 10 11 @mkcert = Mkcert.new 11 12 end 12 13 13 14 # Executes the bootstrapper 14 15 def bootstrap(project : Project) : Bool 15 16 logd "'#{project.name}' is a web project. Running web bootstrapper." 17 + 18 + dnsmasq.install! unless dnsmasq.installed? 16 19 17 20 if mkcert.installed? 18 21 Dir.cd(Paths::PROJECT_CERTS) do ··· 27 30 true 28 31 end 29 32 33 + private getter :dnsmasq 30 34 private getter :mkcert 31 35 end 32 36 end
+10
src/mstrap/platform.cr
··· 117 117 platform.install_packages!([package_name]) 118 118 end 119 119 120 + # Uninstall a list of packages using the platform's package manager 121 + def self.uninstall_packages!(packages : Array(String)) 122 + platform.uninstall_packages!(packages) 123 + end 124 + 125 + # Uninstall a single package using the platform's package manager 126 + def self.uninstall_package!(package_name : String) 127 + platform.uninstall_packages!([package_name]) 128 + end 129 + 120 130 # Install a single package using the platform's package manager 121 131 def self.package_installed?(package_name : String) 122 132 platform.package_installed?(package_name)
+4
src/mstrap/platform/darwin/macos.cr
··· 33 33 cmd("brew", ["install"] + packages) 34 34 end 35 35 36 + def self.uninstall_packages!(packages : Array(String)) 37 + cmd("brew", ["uninstall"] + packages) 38 + end 39 + 36 40 def self.package_installed?(package_name : String) 37 41 cmd("brew list | grep -q '^#{package_name}$'", quiet: true) 38 42 end
+4
src/mstrap/platform/linux/archlinux.cr
··· 7 7 cmd("pacman", ["-Sy", "--noconfirm", "--needed"] + packages, sudo: true) 8 8 end 9 9 10 + def self.uninstall_packages!(packages : Array(String)) 11 + cmd("pacman", ["-R", "--noconfirm"] + packages, sudo: true) 12 + end 13 + 10 14 def self.package_installed?(package_name : String) 11 15 cmd("pacman", ["-Qi", package_name], quiet: true) 12 16 end
+4
src/mstrap/platform/linux/debian.cr
··· 7 7 cmd("apt-get", ["-y", "install"] + packages, sudo: true) 8 8 end 9 9 10 + def self.uninstall_packages!(packages : Array(String)) 11 + cmd("apt-get", ["-y", "remove"] + packages, sudo: true) 12 + end 13 + 10 14 def self.package_installed?(package_name : String) 11 15 cmd("dpkg-query -W -f='${Status}' #{package_name} | grep -q 'ok installed'", quiet: true) 12 16 end
+4
src/mstrap/platform/linux/fedora.cr
··· 8 8 cmd("dnf", ["-y", "install"] + packages, sudo: true) 9 9 end 10 10 11 + def self.uninstall_packages!(packages : Array(String)) 12 + cmd("dnf", ["-y", "remove"] + packages, sudo: true) 13 + end 14 + 11 15 def self.package_installed?(package_name : String) 12 16 cmd("dnf", ["info", "--installed", package_name], quiet: true) 13 17 end
+4
src/mstrap/platform/linux/rhel.cr
··· 7 7 cmd("yum", ["-y", "install"] + packages, sudo: true) 8 8 end 9 9 10 + def self.uninstall_packages!(packages : Array(String)) 11 + cmd("yum", ["-y", "remove"] + packages, sudo: true) 12 + end 13 + 10 14 def self.package_installed?(package_name : String) 11 15 cmd("yum", ["info", "--installed", package_name], quiet: true) 12 16 end
+97
src/mstrap/supports/dnsmasq.cr
··· 1 + module MStrap 2 + class DNSMasq 3 + include DSL 4 + 5 + # :nodoc: 6 + CONFIG_BLOCK = <<-CFG 7 + # BEGIN MSTRAP 8 + address=/localhost/127.0.0.1 9 + # END MSTRAP 10 + CFG 11 + 12 + # :nodoc: 13 + CONFIG_PATH = File.join(MStrap::Paths::HOMEBREW_PREFIX, "etc", "dnsmasq.conf") 14 + 15 + # :nodoc: 16 + RESOLVER_CONFIG_PATH = "/etc/resolver/localhost" 17 + 18 + # Returns whether dnsmasq is installed 19 + def installed? 20 + has_command?("dnsmasq") && config_installed? && resolver_installed? 21 + end 22 + 23 + # Runs dnsmasq install process and adds resolver for .localhost 24 + def install! 25 + remove_launchdns! 26 + install_dnsmasq! 27 + install_dnsmasq_config! 28 + install_resolver! 29 + end 30 + 31 + private def config_installed? : Bool 32 + File.exists?(CONFIG_PATH) && File.read(CONFIG_PATH).includes?(CONFIG_BLOCK) 33 + end 34 + 35 + private def resolver_installed? : Bool 36 + File.exists?(RESOLVER_CONFIG_PATH) 37 + end 38 + 39 + private def remove_launchdns! : Nil 40 + return unless has_command?("launchdns") 41 + 42 + logn "==> Found launchdns. Removing..." 43 + 44 + log "===> Remove .localhost resolver config:" 45 + if File.exists?(RESOLVER_CONFIG_PATH) 46 + unless cmd("rm", "-f", RESOLVER_CONFIG_PATH, sudo: true) 47 + logc "An error occurred while removing #{RESOLVER_CONFIG_PATH}" 48 + end 49 + end 50 + success "OK" 51 + 52 + log "===> Uninstalling launchdns:" 53 + MStrap::Platform.uninstall_packages!(["launchdns"]) 54 + success "OK" 55 + end 56 + 57 + private def install_dnsmasq! : Nil 58 + return if has_command?("dnsmasq") 59 + log "==> Installing dnsmasq:" 60 + MStrap::Platform.install_packages!(["dnsmasq"]) 61 + success "OK" 62 + end 63 + 64 + private def install_dnsmasq_config! : Nil 65 + return if config_installed? 66 + 67 + unless File.exists?(CONFIG_PATH) 68 + logc "dnsmasq config doesn't exist at #{CONFIG_PATH}" 69 + end 70 + 71 + log "==> Updating dnsmasq config to resolve *.localhost: " 72 + File.open(CONFIG_PATH, "a") do |config_file| 73 + config_file.puts CONFIG_BLOCK 74 + end 75 + success "OK" 76 + log "==> Restarting dnsmasq: " 77 + unless cmd("brew services restart dnsmasq", quiet: true, sudo: true) 78 + logc "An error occurred while restarting dnsmasq. Did you have a prior configuration that conflicts?" 79 + end 80 + success "OK" 81 + end 82 + 83 + private def install_resolver! : Nil 84 + unless cmd("mkdir", "-p", "/etc/resolver", sudo: true) 85 + logc "An error occurred while creating /etc/resolver" 86 + end 87 + 88 + unless cmd("touch #{RESOLVER_CONFIG_PATH}", sudo: true) 89 + logc "An error occurred while creating #{RESOLVER_CONFIG_PATH}" 90 + end 91 + 92 + unless cmd("echo nameserver 127.0.0.1 | sudo tee -a #{RESOLVER_CONFIG_PATH}") 93 + logc "An error occurred while writing #{RESOLVER_CONFIG_PATH}" 94 + end 95 + end 96 + end 97 + end
+2 -2
src/mstrap/supports/docker.cr
··· 104 104 # requiring a reboot. 105 105 def install! 106 106 {% if flag?(:darwin) %} 107 - unless app_path || cmd "brew cask install docker" 108 - logc "Could not install docker via Homebrew cask" 107 + unless app_path || cmd "brew install --cask docker-desktop" 108 + logc "Could not install docker-desktop via Homebrew cask" 109 109 end 110 110 {% elsif flag?(:linux) %} 111 111 if !cmd("docker version", quiet: true, sudo: requires_sudo?) || !cmd("docker compose version", quiet: true, sudo: requires_sudo?)
-5
src/mstrap/templates/Brewfile.ecr
··· 23 23 brew 'gnu-sed' 24 24 brew 'gnu-tar' 25 25 brew 'libiconv' 26 - 27 - # Resolve *.localhost 28 - brew 'launchdns', restart_service: true 29 - else 30 - # TODO: dnsmasq or something 31 26 end