+1
.github/workflows/provision.yml
+1
.github/workflows/provision.yml
+21
CHANGELOG.md
+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
+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
-6
spec/provisioning/spec/localhost/dependencies_step_spec.rb
+4
src/mstrap/bootstrappers/web_bootstrapper.cr
+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
+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
+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
+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
+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
+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
+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
+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
+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?)