easy_caddy 0.1.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 +7 -0
- data/CHANGELOG.md +34 -0
- data/LICENSE +21 -0
- data/README.md +333 -0
- data/exe/ecaddy +6 -0
- data/lib/easy_caddy/caddy.rb +69 -0
- data/lib/easy_caddy/cli.rb +107 -0
- data/lib/easy_caddy/commands/audit.rb +576 -0
- data/lib/easy_caddy/commands/doctor.rb +34 -0
- data/lib/easy_caddy/commands/down.rb +42 -0
- data/lib/easy_caddy/commands/edit.rb +36 -0
- data/lib/easy_caddy/commands/ensure.rb +22 -0
- data/lib/easy_caddy/commands/list.rb +53 -0
- data/lib/easy_caddy/commands/logs.rb +63 -0
- data/lib/easy_caddy/commands/register_helpers.rb +116 -0
- data/lib/easy_caddy/commands/reload.rb +16 -0
- data/lib/easy_caddy/commands/remove.rb +41 -0
- data/lib/easy_caddy/commands/run.rb +35 -0
- data/lib/easy_caddy/commands/setup.rb +104 -0
- data/lib/easy_caddy/commands/status.rb +39 -0
- data/lib/easy_caddy/commands/up.rb +42 -0
- data/lib/easy_caddy/conflicts.rb +152 -0
- data/lib/easy_caddy/parser.rb +39 -0
- data/lib/easy_caddy/paths.rb +18 -0
- data/lib/easy_caddy/registry.rb +49 -0
- data/lib/easy_caddy/site.rb +13 -0
- data/lib/easy_caddy/version.rb +5 -0
- data/lib/easy_caddy.rb +13 -0
- metadata +173 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 15359e9854098ff355b15635597ff9979952d9224835eb31594676492fbd64d9
|
|
4
|
+
data.tar.gz: b3f4b62942373f0554e0d0ff92671bccb8fe7efd8ffef16038f4248c251687bb
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 2cccf4fca5dbe0eb0148998507d67666b294dcc0233c34fd83b2789e6c18c44191981dc2391b9f319067bc183729078b2985994641f9247838134c3361efbdf2
|
|
7
|
+
data.tar.gz: d8c66f1dba6f6d32210c2a2cbfa910a1560668a7ea5e5a84cfe9e6371142036fcd0543035b488f9b415f9e1c6ac9c58b6ac1e17cc06006e6f71a9e8c057e45bf
|
data/CHANGELOG.md
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [0.1.0] — 2026-06-09
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- Initial public release.
|
|
13
|
+
- Thor CLI `ecaddy` with commands: `setup`, `run`, `ensure`, `up`, `down`,
|
|
14
|
+
`list`, `edit`, `logs`, `remove`, `reload`, `status`, `doctor`, `audit`.
|
|
15
|
+
- Single-source-of-truth path management via `EasyCaddy::Paths` — every
|
|
16
|
+
filesystem path derives from the `ECADDY_HOME` env var (defaults to
|
|
17
|
+
`~/.config/caddy`), keeping the tool fully redirectable for tests and
|
|
18
|
+
multi-user setups.
|
|
19
|
+
- YAML registry (`ecaddy.yml`) tracking each site by name, enabled state,
|
|
20
|
+
and source Caddyfile path.
|
|
21
|
+
- Conflict detection: `*.localhost` domain collisions and
|
|
22
|
+
`reverse_proxy localhost:PORT` port collisions across registered
|
|
23
|
+
fragments, plus TCP probing of upstream ports via `ecaddy doctor`.
|
|
24
|
+
- Automatic rewrite of relative `output file` log paths to absolute paths
|
|
25
|
+
on registration, so Caddy (running as a background service detached from
|
|
26
|
+
the project directory) can write log files correctly.
|
|
27
|
+
- One-shot machine bootstrap (`ecaddy setup`): Homebrew install of Caddy,
|
|
28
|
+
global Caddyfile scaffold, `caddy trust` for local-CA HTTPS, and
|
|
29
|
+
`brew services` start. Idempotent — safe to re-run.
|
|
30
|
+
- Foreground `ecaddy run --site NAME` mode: registers the fragment, traps
|
|
31
|
+
`SIGTERM`/`SIGINT`, and unregisters on exit — designed to drop into a
|
|
32
|
+
Procfile alongside the Rails server.
|
|
33
|
+
|
|
34
|
+
[0.1.0]: https://github.com/pniemczyk/easy_caddy/releases/tag/v0.1.0
|
data/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Pawel Niemczyk
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,333 @@
|
|
|
1
|
+
# ecaddy
|
|
2
|
+
|
|
3
|
+
[](https://rubygems.org/gems/easy_caddy)
|
|
4
|
+
[](https://pniemczyk.github.io/easy_caddy/)
|
|
5
|
+
[](LICENSE)
|
|
6
|
+
[](CHANGELOG.md)
|
|
7
|
+
|
|
8
|
+
**[📖 Documentation](https://pniemczyk.github.io/easy_caddy/)** | **[GitHub](https://github.com/pniemczyk/easy_caddy)** | **[Changelog](CHANGELOG.md)**
|
|
9
|
+
|
|
10
|
+
One global [Caddy](https://caddyserver.com/) for all your local Rails projects.
|
|
11
|
+
|
|
12
|
+
Instead of fighting port conflicts from multiple Caddy processes, `ecaddy` manages a single shared Caddy instance. Each project keeps its own `Caddyfile` — `ecaddy` copies it in and out of the global config on demand.
|
|
13
|
+
|
|
14
|
+
## How it works
|
|
15
|
+
|
|
16
|
+
```
|
|
17
|
+
Browser
|
|
18
|
+
│
|
|
19
|
+
▼
|
|
20
|
+
Caddy (~/.config/caddy/Caddyfile)
|
|
21
|
+
│ imports sites/*.caddy
|
|
22
|
+
├── fishme.localhost → localhost:3104
|
|
23
|
+
├── letly.localhost → localhost:3100
|
|
24
|
+
└── traiderb.localhost → localhost:3106
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Each Rails project has its own `Caddyfile`. When you start the project, `ecaddy` copies it into `~/.config/caddy/sites/<name>.caddy` and reloads the global Caddy. When you stop, it removes the fragment and reloads again.
|
|
28
|
+
|
|
29
|
+
## Installation
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
gem install easy_caddy
|
|
33
|
+
ecaddy setup
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
`ecaddy setup` is a one-time bootstrap that:
|
|
37
|
+
|
|
38
|
+
1. Installs Caddy via Homebrew if not already present
|
|
39
|
+
2. Scaffolds `~/.config/caddy/{sites,disabled}/`
|
|
40
|
+
3. Writes the global `Caddyfile` (with `import sites/*.caddy`)
|
|
41
|
+
4. Symlinks it into `/opt/homebrew/etc/Caddyfile` so `brew services` picks it up
|
|
42
|
+
5. Runs `caddy trust` to install the local CA in your system keychain (makes `https://*.localhost` green in browsers)
|
|
43
|
+
6. Starts Caddy as a `brew services` background service
|
|
44
|
+
|
|
45
|
+
Run `ecaddy setup` again at any time — every step is idempotent.
|
|
46
|
+
|
|
47
|
+
### The `--site` option
|
|
48
|
+
|
|
49
|
+
Every `ecaddy` command that registers or references a project uses a **site name** — a short identifier you choose, e.g. `fishme`. This name:
|
|
50
|
+
|
|
51
|
+
- Determines the fragment filename: `~/.config/caddy/sites/fishme.caddy`
|
|
52
|
+
- Is used by `up`, `down`, `edit`, `remove` to target the right project
|
|
53
|
+
- Should be unique across all your local projects
|
|
54
|
+
|
|
55
|
+
The name is **not** read from the Caddyfile — you always supply it explicitly with `--site fishme` (short: `-s fishme`). This keeps `ecaddy` compatible with any Caddyfile content.
|
|
56
|
+
|
|
57
|
+
## Project setup
|
|
58
|
+
|
|
59
|
+
Each project needs two things: a `Caddyfile` and a Procfile line.
|
|
60
|
+
|
|
61
|
+
### 1. Write your project Caddyfile
|
|
62
|
+
|
|
63
|
+
Put a `Caddyfile` in your project root. Write it however you need — `ecaddy` treats it as read-only source. One automatic transform is applied on copy: relative `output file` log paths are rewritten to absolute paths so Caddy (running as a background service with no relation to your project directory) can actually write the log files.
|
|
64
|
+
|
|
65
|
+
```caddy
|
|
66
|
+
# Caddyfile (in your Rails project root)
|
|
67
|
+
|
|
68
|
+
fishme.localhost {
|
|
69
|
+
handle /vite-dev/* {
|
|
70
|
+
reverse_proxy localhost:3054
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
reverse_proxy localhost:3104
|
|
74
|
+
tls internal
|
|
75
|
+
|
|
76
|
+
log {
|
|
77
|
+
level INFO
|
|
78
|
+
output file log/caddy.log {
|
|
79
|
+
roll_size 2mb
|
|
80
|
+
roll_keep 5
|
|
81
|
+
roll_keep_for 48h
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
vite.fishme.localhost {
|
|
87
|
+
reverse_proxy localhost:3054
|
|
88
|
+
tls internal
|
|
89
|
+
}
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
Pick unique ports across your projects. Common pattern:
|
|
93
|
+
|
|
94
|
+
| Project | App port | Vite port |
|
|
95
|
+
|----------|----------|-----------|
|
|
96
|
+
| fishme | 3104 | 3054 |
|
|
97
|
+
| letly | 3100 | 3050 |
|
|
98
|
+
| traiderb | 3106 | 3056 |
|
|
99
|
+
|
|
100
|
+
### 2. Add a Procfile line
|
|
101
|
+
|
|
102
|
+
```procfile
|
|
103
|
+
# Procfile.dev
|
|
104
|
+
|
|
105
|
+
web: bin/rails server -p 3104
|
|
106
|
+
js: yarn dev
|
|
107
|
+
caddy: ecaddy run --config ./Caddyfile --site fishme
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
When foreman (or overmind) starts, `ecaddy run` copies your `Caddyfile` into the global config and reloads Caddy. When you press `Ctrl-C`, it removes the fragment and reloads again — the domain disappears cleanly.
|
|
111
|
+
|
|
112
|
+
### 3. Allow `.localhost` in Rails
|
|
113
|
+
|
|
114
|
+
```ruby
|
|
115
|
+
# config/environments/development.rb
|
|
116
|
+
|
|
117
|
+
config.hosts << /.*\.localhost/
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
### 4. Start your project
|
|
121
|
+
|
|
122
|
+
```bash
|
|
123
|
+
bin/dev
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
Visit `https://fishme.localhost` — done.
|
|
127
|
+
|
|
128
|
+
## Commands
|
|
129
|
+
|
|
130
|
+
### `ecaddy setup`
|
|
131
|
+
|
|
132
|
+
One-time machine bootstrap. Install Caddy, scaffold the global config, trust the local CA, start the brew service.
|
|
133
|
+
|
|
134
|
+
```bash
|
|
135
|
+
ecaddy setup
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
---
|
|
139
|
+
|
|
140
|
+
### `ecaddy run`
|
|
141
|
+
|
|
142
|
+
Register a project Caddyfile, block, and unregister on shutdown. Use in `Procfile.dev`.
|
|
143
|
+
|
|
144
|
+
```bash
|
|
145
|
+
ecaddy run --config ./Caddyfile --site fishme
|
|
146
|
+
ecaddy run -c ./Caddyfile -s fishme
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
On `SIGTERM` or `SIGINT`, the fragment is removed and Caddy is reloaded before the process exits.
|
|
150
|
+
|
|
151
|
+
Relative `output file` log paths in the Caddyfile are automatically rewritten to absolute paths (resolved from the directory of `--config`) before the fragment is installed.
|
|
152
|
+
|
|
153
|
+
---
|
|
154
|
+
|
|
155
|
+
### `ecaddy ensure`
|
|
156
|
+
|
|
157
|
+
One-shot variant of `run`. Copies the Caddyfile, reloads Caddy, exits immediately. The site stays registered until you run `ecaddy down` or `ecaddy remove`.
|
|
158
|
+
|
|
159
|
+
```bash
|
|
160
|
+
ecaddy ensure --config ./Caddyfile --site fishme
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
Useful in CI or shell scripts where you want Caddy configured but don't need a foreground process.
|
|
164
|
+
|
|
165
|
+
---
|
|
166
|
+
|
|
167
|
+
### `ecaddy list`
|
|
168
|
+
|
|
169
|
+
Show all registered sites.
|
|
170
|
+
|
|
171
|
+
```bash
|
|
172
|
+
ecaddy list
|
|
173
|
+
ecaddy list --format json
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
```
|
|
177
|
+
┌──────────┬────────┬──────────────────────────────────────────────┬────────────┬──────────────────────────┐
|
|
178
|
+
│ Name │ Status │ Domains │ Ports │ Source │
|
|
179
|
+
├──────────┼────────┼──────────────────────────────────────────────┼────────────┼──────────────────────────┤
|
|
180
|
+
│ fishme │ up │ fishme.localhost, vite.fishme.localhost │ 3054, 3104 │ /projects/fishme/Caddyfile │
|
|
181
|
+
│ letly │ down │ letly.localhost, vite.letly.localhost │ 3050, 3100 │ /projects/letly/Caddyfile │
|
|
182
|
+
└──────────┴────────┴──────────────────────────────────────────────┴────────────┴──────────────────────────┘
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
---
|
|
186
|
+
|
|
187
|
+
### `ecaddy up NAME` / `ecaddy down NAME`
|
|
188
|
+
|
|
189
|
+
Enable or disable a registered site without removing it.
|
|
190
|
+
|
|
191
|
+
```bash
|
|
192
|
+
ecaddy down fishme # moves sites/fishme.caddy → disabled/fishme.caddy, reloads
|
|
193
|
+
ecaddy up fishme # moves disabled/fishme.caddy → sites/fishme.caddy, reloads
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
---
|
|
197
|
+
|
|
198
|
+
### `ecaddy status`
|
|
199
|
+
|
|
200
|
+
Show global Caddy state and per-site health (whether the upstream app is actually running).
|
|
201
|
+
|
|
202
|
+
```bash
|
|
203
|
+
ecaddy status
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
```
|
|
207
|
+
Caddy service: running
|
|
208
|
+
Config: /Users/you/.config/caddy/Caddyfile
|
|
209
|
+
|
|
210
|
+
fishme up
|
|
211
|
+
fragment: /Users/you/.config/caddy/sites/fishme.caddy
|
|
212
|
+
source: /projects/fishme/Caddyfile
|
|
213
|
+
letly up (app not running)
|
|
214
|
+
fragment: /Users/you/.config/caddy/sites/letly.caddy
|
|
215
|
+
source: /projects/letly/Caddyfile
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
---
|
|
219
|
+
|
|
220
|
+
### `ecaddy doctor`
|
|
221
|
+
|
|
222
|
+
Scan all registered sites for port/domain conflicts and dead upstreams.
|
|
223
|
+
|
|
224
|
+
```bash
|
|
225
|
+
ecaddy doctor
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
Exits `0` if all clear or only INFO findings. Exits `1` on any BLOCK.
|
|
229
|
+
|
|
230
|
+
| Severity | Meaning |
|
|
231
|
+
|----------|---------|
|
|
232
|
+
| `BLOCK` | Two sites share a port or domain — one will fail |
|
|
233
|
+
| `WARN` | A port is bound by an unexpected process |
|
|
234
|
+
| `INFO` | Upstream not listening (app not started) |
|
|
235
|
+
|
|
236
|
+
---
|
|
237
|
+
|
|
238
|
+
### `ecaddy edit NAME`
|
|
239
|
+
|
|
240
|
+
Open a site's fragment in `$EDITOR`. Caddy is validated and reloaded after you save.
|
|
241
|
+
|
|
242
|
+
```bash
|
|
243
|
+
ecaddy edit fishme
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
This edits the copy in `~/.config/caddy/sites/fishme.caddy`, not your project source. Re-run `ecaddy run` (or `ecaddy ensure`) to sync from your project `Caddyfile` again.
|
|
247
|
+
|
|
248
|
+
---
|
|
249
|
+
|
|
250
|
+
### `ecaddy remove NAME`
|
|
251
|
+
|
|
252
|
+
Remove a site's fragment and registry entry entirely.
|
|
253
|
+
|
|
254
|
+
```bash
|
|
255
|
+
ecaddy remove fishme
|
|
256
|
+
ecaddy remove fishme --force # skip confirmation
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
---
|
|
260
|
+
|
|
261
|
+
### `ecaddy reload`
|
|
262
|
+
|
|
263
|
+
Validate the global config and reload Caddy.
|
|
264
|
+
|
|
265
|
+
```bash
|
|
266
|
+
ecaddy reload
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
---
|
|
270
|
+
|
|
271
|
+
### `ecaddy version`
|
|
272
|
+
|
|
273
|
+
```bash
|
|
274
|
+
ecaddy version
|
|
275
|
+
# ecaddy 0.1.0
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
## Global config layout
|
|
279
|
+
|
|
280
|
+
```
|
|
281
|
+
~/.config/caddy/
|
|
282
|
+
Caddyfile # global root: { admin ... } + import sites/*.caddy
|
|
283
|
+
ecaddy.yml # registry: name → { enabled, source_path }
|
|
284
|
+
sites/
|
|
285
|
+
fishme.caddy # enabled fragments — loaded by Caddy
|
|
286
|
+
letly.caddy
|
|
287
|
+
disabled/
|
|
288
|
+
traiderb.caddy # disabled fragments — preserved, not loaded
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
The global `Caddyfile` is also symlinked at `/opt/homebrew/etc/Caddyfile` so `brew services start caddy` picks it up automatically.
|
|
292
|
+
|
|
293
|
+
## Conflict detection
|
|
294
|
+
|
|
295
|
+
Before registering any Caddyfile, `ecaddy` parses it and checks:
|
|
296
|
+
|
|
297
|
+
- **Domain collision** — same `*.localhost` domain already registered by another enabled site → BLOCK
|
|
298
|
+
- **Port collision** — same `reverse_proxy localhost:PORT` already in use by another site → BLOCK
|
|
299
|
+
|
|
300
|
+
These checks run on `ecaddy run`, `ecaddy ensure`, and `ecaddy up`. Run `ecaddy doctor` at any time for a full cross-site audit.
|
|
301
|
+
|
|
302
|
+
## Environment variable
|
|
303
|
+
|
|
304
|
+
Set `ECADDY_HOME` to override the config root (defaults to `~/.config/caddy`). Useful for testing:
|
|
305
|
+
|
|
306
|
+
```bash
|
|
307
|
+
ECADDY_HOME=/tmp/ecaddy_test ecaddy list
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
## Development
|
|
311
|
+
|
|
312
|
+
```bash
|
|
313
|
+
bin/setup # bundle install
|
|
314
|
+
bundle exec rspec # run the full spec suite
|
|
315
|
+
bundle exec rubocop # lint
|
|
316
|
+
bin/console # IRB session with easy_caddy preloaded
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
To run the CLI against the local source without installing the gem:
|
|
320
|
+
|
|
321
|
+
```bash
|
|
322
|
+
bundle exec exe/ecaddy <command>
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
Cutting a release: bump `EasyCaddy::VERSION` in `lib/easy_caddy/version.rb`, add a `CHANGELOG.md` entry, commit, then `bundle exec rake release` — that tags the commit and pushes the gem to RubyGems (requires `gem signin` first).
|
|
326
|
+
|
|
327
|
+
## Contributing
|
|
328
|
+
|
|
329
|
+
Bug reports and pull requests are welcome at <https://github.com/pniemczyk/easy_caddy>. Please run the spec suite and `rubocop` before opening a PR.
|
|
330
|
+
|
|
331
|
+
## License
|
|
332
|
+
|
|
333
|
+
Released under the [MIT License](LICENSE).
|
data/exe/ecaddy
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'English'
|
|
4
|
+
require_relative 'paths'
|
|
5
|
+
|
|
6
|
+
module EasyCaddy
|
|
7
|
+
module Caddy
|
|
8
|
+
BINARY = 'caddy'
|
|
9
|
+
|
|
10
|
+
def self.installed?
|
|
11
|
+
system('which caddy > /dev/null 2>&1')
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def self.validate(caddyfile = Paths.caddyfile)
|
|
15
|
+
system("#{BINARY} validate --config #{caddyfile} 2>&1")
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def self.validate!(caddyfile = Paths.caddyfile)
|
|
19
|
+
return unless caddyfile.exist?
|
|
20
|
+
|
|
21
|
+
out = `#{BINARY} validate --config #{caddyfile} 2>&1`
|
|
22
|
+
raise "Caddy config invalid:\n#{out}" unless $CHILD_STATUS.success?
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def self.reload(caddyfile = Paths.caddyfile)
|
|
26
|
+
unless caddyfile.exist?
|
|
27
|
+
warn ' [ecaddy] Skipping reload — global Caddyfile not found. Run `ecaddy setup` first.'
|
|
28
|
+
return
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
out = `#{BINARY} reload --config #{caddyfile} 2>&1`
|
|
32
|
+
raise "Caddy reload failed:\n#{out}" unless $CHILD_STATUS.success?
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def self.trust
|
|
36
|
+
system("#{BINARY} trust")
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def self.running?
|
|
40
|
+
pid = brew_service_pid
|
|
41
|
+
pid && pid > 0
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def self.start_service
|
|
45
|
+
system('brew services start caddy')
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def self.restart_service
|
|
49
|
+
system('brew services restart caddy')
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def self.brew_service_pid
|
|
53
|
+
output = `brew services list 2>/dev/null | grep '^caddy '`
|
|
54
|
+
m = output.match(/(\d+)/)
|
|
55
|
+
m&.captures&.first&.to_i
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def self.process_pid
|
|
59
|
+
out = `pgrep -f 'caddy run' 2>/dev/null`.strip
|
|
60
|
+
return nil if out.empty?
|
|
61
|
+
|
|
62
|
+
out.lines.first.to_i
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def self.install_via_brew
|
|
66
|
+
system('brew install caddy')
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'thor'
|
|
4
|
+
require_relative 'version'
|
|
5
|
+
require_relative 'commands/setup'
|
|
6
|
+
require_relative 'commands/remove'
|
|
7
|
+
require_relative 'commands/edit'
|
|
8
|
+
require_relative 'commands/list'
|
|
9
|
+
require_relative 'commands/up'
|
|
10
|
+
require_relative 'commands/down'
|
|
11
|
+
require_relative 'commands/status'
|
|
12
|
+
require_relative 'commands/doctor'
|
|
13
|
+
require_relative 'commands/reload'
|
|
14
|
+
require_relative 'commands/ensure'
|
|
15
|
+
require_relative 'commands/run'
|
|
16
|
+
require_relative 'commands/logs'
|
|
17
|
+
require_relative 'commands/audit'
|
|
18
|
+
|
|
19
|
+
module EasyCaddy
|
|
20
|
+
class CLI < Thor
|
|
21
|
+
def self.exit_on_failure? = true
|
|
22
|
+
|
|
23
|
+
desc 'setup', 'One-time machine bootstrap: install Caddy, scaffold config, start service'
|
|
24
|
+
def setup
|
|
25
|
+
require 'tty-prompt'
|
|
26
|
+
Commands::Setup.new(prompt: TTY::Prompt.new).call
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
desc 'remove NAME', 'Remove a site from global Caddy and delete its fragment'
|
|
30
|
+
option :force, type: :boolean, default: false, aliases: '-f', desc: 'Skip confirmation'
|
|
31
|
+
def remove(name)
|
|
32
|
+
require 'tty-prompt'
|
|
33
|
+
Commands::Remove.new(name: name, force: options[:force], prompt: TTY::Prompt.new).call
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
desc 'edit NAME', 'Open a site fragment in $EDITOR and reload Caddy'
|
|
37
|
+
def edit(name)
|
|
38
|
+
Commands::Edit.new(name: name).call
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
desc 'list', 'List all registered sites'
|
|
42
|
+
option :format, type: :string, default: 'table', aliases: '-f', desc: 'Output format: table or json'
|
|
43
|
+
def list
|
|
44
|
+
Commands::List.new(format: options[:format]).call
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
desc 'up NAME', 'Enable a disabled site and reload Caddy'
|
|
48
|
+
def up(name)
|
|
49
|
+
Commands::Up.new(name: name).call
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
desc 'down NAME', 'Disable an enabled site and reload Caddy'
|
|
53
|
+
def down(name)
|
|
54
|
+
Commands::Down.new(name: name).call
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
desc 'status', 'Show global Caddy state and per-site health'
|
|
58
|
+
def status
|
|
59
|
+
Commands::Status.new.call
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
desc 'doctor', 'Scan for port/domain conflicts and dead upstreams'
|
|
63
|
+
def doctor
|
|
64
|
+
Commands::Doctor.new.call
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
desc 'reload', 'Validate and reload the global Caddy config'
|
|
68
|
+
def reload
|
|
69
|
+
Commands::Reload.new.call
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
desc 'ensure', 'One-shot: copy project Caddyfile into global config and exit (for scripts/CI)'
|
|
73
|
+
option :config, type: :string, required: true, aliases: '-c', desc: 'Path to project Caddyfile'
|
|
74
|
+
option :site, type: :string, required: true, aliases: '-s', desc: 'Site name (used as fragment filename)'
|
|
75
|
+
def ensure
|
|
76
|
+
Commands::Ensure.new(config_path: options[:config], site: options[:site]).call
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
desc 'run', 'Register Caddyfile; block and unregister on shutdown (for Procfile.dev)'
|
|
80
|
+
option :config, type: :string, required: true, aliases: '-c', desc: 'Path to project Caddyfile'
|
|
81
|
+
option :site, type: :string, required: true, aliases: '-s', desc: 'Site name (used as fragment filename)'
|
|
82
|
+
def caddy_run
|
|
83
|
+
Commands::Run.new(config_path: options[:config], site: options[:site]).call
|
|
84
|
+
end
|
|
85
|
+
map 'run' => :caddy_run
|
|
86
|
+
|
|
87
|
+
desc 'logs', 'Tail the Caddy log files for a site'
|
|
88
|
+
option :site, type: :string, required: true, aliases: '-s', desc: 'Site name'
|
|
89
|
+
option :lines, type: :numeric, default: 200, aliases: '-n', desc: 'Number of lines (with --no-follow)'
|
|
90
|
+
option :follow, type: :boolean, default: true, desc: 'Follow log (tail -F). Use --no-follow to print and exit.'
|
|
91
|
+
def logs
|
|
92
|
+
Commands::Logs.new(site: options[:site], lines: options[:lines], follow: options[:follow]).call
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
desc 'audit', 'Print a full system + TLS + site snapshot'
|
|
96
|
+
option :site, type: :string, aliases: '-s', desc: 'Limit to a single site'
|
|
97
|
+
option :fix, type: :boolean, default: false, desc: 'After report, prompt to apply a fix for each failing check'
|
|
98
|
+
def audit
|
|
99
|
+
Commands::Audit.new(site: options[:site], fix: options[:fix]).call
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
desc 'version', 'Print ecaddy version'
|
|
103
|
+
def version
|
|
104
|
+
puts "ecaddy #{EasyCaddy::VERSION}"
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
end
|