fbi-proxy 1.12.0 → 1.14.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.
- package/README.md +29 -21
- package/dist/cli.js +32 -0
- package/package.json +1 -1
- package/release/fbi-proxy-linux-arm64 +0 -0
- package/release/fbi-proxy-linux-x64 +0 -0
- package/release/fbi-proxy-macos-arm64 +0 -0
- package/release/fbi-proxy-macos-x64 +0 -0
- package/release/fbi-proxy-windows-arm64.exe +0 -0
- package/release/fbi-proxy-windows-x64.exe +0 -0
- package/rs/fbi-proxy.rs +135 -8
- package/ts/auth/setupWizard.ts +48 -0
package/README.md
CHANGED
|
@@ -11,42 +11,50 @@ FBI-Proxy provides easy HTTPS access to your local services with intelligent dom
|
|
|
11
11
|
|
|
12
12
|
### Current Features ✅
|
|
13
13
|
|
|
14
|
-
- **
|
|
14
|
+
- **One-command HTTPS gateway**: `bunx fbi-proxy --with-caddy --with-auth --provider snolab --domain fbi.com` brings up Caddy (auto-downloaded), fbi-auth (Firebase-backed Google sign-in), and the Rust proxy together — zero config needed on `.fbi.com`.
|
|
15
|
+
- **Rule-based Domain Routing** via `routes.yaml`:
|
|
15
16
|
- Port-based routing (e.g., `3000.fbi.com` → `localhost:3000`)
|
|
16
17
|
- Host--Port routing (e.g., `api--3001.fbi.com` → `api:3001`)
|
|
17
18
|
- Subdomain routing with Host headers (e.g., `admin.app.fbi.com` → `app:80`)
|
|
18
19
|
- Direct host forwarding (e.g., `myserver.fbi.com` → `myserver:80`)
|
|
19
|
-
-
|
|
20
|
-
- **
|
|
21
|
-
- **
|
|
22
|
-
- **
|
|
23
|
-
- **
|
|
24
|
-
- **
|
|
25
|
-
- **
|
|
20
|
+
- Placeholder syntax (`{name}`, `{name:int}`, `{name:slug}`, `{name:multi}`) for custom rules — see [docs/routing.md](docs/routing.md)
|
|
21
|
+
- **HTTPS Upstreams**: Targets with an `https://` prefix connect to upstream over TLS (Mozilla webpki roots).
|
|
22
|
+
- **WebSocket Support**: Full WebSocket forwarding (`ws://` and `wss://`) for all routing patterns.
|
|
23
|
+
- **Auth Gateway**: Google OAuth / Firebase Auth / zero-config snolab default IdP — JWT cookie scoped to `Domain=.your-domain` for cross-subdomain SSO. Audit log at `~/.config/fbi-proxy/audit.log`.
|
|
24
|
+
- **High Performance**: Built with Rust for optimal performance and low resource usage.
|
|
25
|
+
- **Easy Setup**: Simple one-command installation and startup.
|
|
26
|
+
- **Docker Support**: Available as a Docker image for containerized deployments.
|
|
27
|
+
- **Flexible Configuration**: Environment variables, CLI options, and `routes.yaml` overrides.
|
|
28
|
+
- **Cross-Platform**: Pre-built binaries for macOS, Linux, and Windows (x64 + arm64).
|
|
29
|
+
- **Integration Ready**: Compatible with reverse proxies like Caddy for HTTPS (and bundles its own `--with-caddy` automation).
|
|
26
30
|
|
|
27
31
|
## Roadmap
|
|
28
32
|
|
|
29
33
|
### Shipped ✅
|
|
30
34
|
|
|
31
|
-
- [x] **Auto Caddy Setup**
|
|
35
|
+
- [x] **Auto Caddy Setup** — One-command bootstrap that generates a Caddyfile for the chosen domain and supervises Caddy alongside fbi-proxy and fbi-auth (`bunx fbi-proxy --with-caddy --with-auth --domain example.dev`). Caddy binary is auto-downloaded from GitHub Releases on first run (SHA-512 verified against the release's `checksums.txt`), cached at `~/.fbi-proxy/bin/caddy`. Set `FBI_CADDY_AUTO_DOWNLOAD=false` to opt out.
|
|
36
|
+
- [x] **Auth Gateway** — Google OAuth, Firebase Auth, and a **zero-config snolab default IdP** (Firebase-based, live on `fbi.com`). Cookie-based SSO across `*.your-domain`. Sliding-window refresh, configurable threshold, JSONL audit log at `~/.config/fbi-proxy/audit.log`. See [lib/fbi-auth/docs/setup.md](lib/fbi-auth/docs/setup.md) and [lib/fbi-auth/docs/snolab.md](lib/fbi-auth/docs/snolab.md).
|
|
37
|
+
- [x] **Rule-based Routing** — `routes.yaml` with placeholder syntax (`{name}`, `{name:int}`, `{name:slug}`, `{name:multi}`). DNS-passthrough, k8s, Docker, and PR-preview recipes in [docs/routing.md](docs/routing.md). Override the bundled defaults with `--routes` or `FBI_PROXY_ROUTES`.
|
|
38
|
+
- [x] **HTTPS Upstream Support** — Route target with an `https://` prefix triggers TLS to upstream via `hyper-rustls` + Mozilla webpki roots. Backward compatible — plain `host:port` still uses HTTP. WebSocket upgrades flip to `wss://` automatically.
|
|
39
|
+
- [x] **Cross-platform Releases** — Every push builds six platforms in parallel (linux x64/arm64, macOS x64/arm64, windows x64/arm64). See [docs/cross-compile-tradeoffs.html](docs/cross-compile-tradeoffs.html).
|
|
32
40
|
|
|
33
41
|
### Next Up 🚧
|
|
34
42
|
|
|
35
|
-
- [
|
|
36
|
-
- [
|
|
37
|
-
- [ ] **
|
|
38
|
-
- [ ] **
|
|
39
|
-
- [ ] **
|
|
40
|
-
- [ ] **Health Checks** - Simple upstream service availability monitoring
|
|
43
|
+
- [x] **Custom Domain Wizard polish** — Print the DNS A-records to add (`*.example.dev → <ip>`) and a Caddyfile-with-DNS-01 sample for Cloudflare during `--reconfigure` on a non-fbi.com domain
|
|
44
|
+
- [x] **Hot Reload** — `routes.yaml` is watched; edits reload atomically without a restart (typos keep the previous rules live)
|
|
45
|
+
- [ ] **Metrics** — `/varz`-style counters: requests, 2xx/4xx/5xx, upstream-connect-failures, sessions-issued, sessions-refreshed (Prometheus format)
|
|
46
|
+
- [ ] **Health Checks** — Active upstream liveness probes, not just per-request failure detection
|
|
47
|
+
- [ ] **Cloudflare Tunnel / ngrok Integration** — Expose `*.your-domain` publicly without owning a static IP
|
|
41
48
|
|
|
42
49
|
### Future Improvements 🔮
|
|
43
50
|
|
|
44
|
-
- [ ] **Load Balancing**
|
|
45
|
-
- [ ] **
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
-
|
|
51
|
+
- [ ] **Load Balancing** — Round-robin between multiple upstream targets for one route
|
|
52
|
+
- [ ] **Custom Headers per route** — Beyond `Host:`, add response headers or rewrite request headers
|
|
53
|
+
|
|
54
|
+
### Won't do
|
|
55
|
+
|
|
56
|
+
- ~~**Built-in HTTPS via rustls + ACME**~~ — Caddy already does this very well, and the `--with-caddy` UX is one extra flag. Adding another ACME client to the Rust binary is more code, more attack surface, and another implementation of a solved problem. Caddy stays the canonical TLS path.
|
|
57
|
+
- ~~**SQLite session storage**~~ — JWT + `sessionSecret` rotation covers the threat model for fbi-proxy's intended scale (solo / small-team self-hosted). See [revoking sessions](lib/fbi-auth/docs/setup.md#revoking-sessions).
|
|
50
58
|
|
|
51
59
|
## Routing Examples
|
|
52
60
|
|
package/dist/cli.js
CHANGED
|
@@ -5529,8 +5529,40 @@ async function runWizard(prompter, opts) {
|
|
|
5529
5529
|
prompter.print("Config preview:");
|
|
5530
5530
|
prompter.print(JSON.stringify(redact(cfg), null, 2));
|
|
5531
5531
|
prompter.print("");
|
|
5532
|
+
if (cleanDomain !== "fbi.com") {
|
|
5533
|
+
printCustomDomainHints(prompter, cleanDomain);
|
|
5534
|
+
}
|
|
5532
5535
|
return cfg;
|
|
5533
5536
|
}
|
|
5537
|
+
function printCustomDomainHints(prompter, domain) {
|
|
5538
|
+
prompter.print("─── Custom-domain DNS + TLS hints ───");
|
|
5539
|
+
prompter.print("");
|
|
5540
|
+
prompter.print(`Your domain is '${domain}' (not the default fbi.com).`);
|
|
5541
|
+
prompter.print(`You'll need DNS A-records pointing the wildcard + sso host`);
|
|
5542
|
+
prompter.print(`at the public IP of the machine running fbi-proxy:`);
|
|
5543
|
+
prompter.print("");
|
|
5544
|
+
prompter.print(` *.${domain} A <your-public-ip>`);
|
|
5545
|
+
prompter.print(` sso.${domain} A <your-public-ip> (covered by the wildcard,`);
|
|
5546
|
+
prompter.print(` but call it out explicitly)`);
|
|
5547
|
+
prompter.print("");
|
|
5548
|
+
prompter.print(`For wildcard TLS via Let's Encrypt you need DNS-01 (HTTP-01`);
|
|
5549
|
+
prompter.print(`can't issue wildcards). With Cloudflare DNS:`);
|
|
5550
|
+
prompter.print("");
|
|
5551
|
+
prompter.print(` 1. Create a Cloudflare API token with Zone:DNS:Edit on '${domain}'.`);
|
|
5552
|
+
prompter.print(` 2. Export it: CLOUDFLARE_API_TOKEN=...`);
|
|
5553
|
+
prompter.print(` 3. Run with --with-caddy --tls-mode auto. fbi-proxy will generate`);
|
|
5554
|
+
prompter.print(` a Caddyfile that uses Caddy's cloudflare DNS plugin:`);
|
|
5555
|
+
prompter.print("");
|
|
5556
|
+
prompter.print(` *.${domain} {`);
|
|
5557
|
+
prompter.print(` tls { dns cloudflare {env.CLOUDFLARE_API_TOKEN} }`);
|
|
5558
|
+
prompter.print(` reverse_proxy 127.0.0.1:{$FBI_PROXY_PORT}`);
|
|
5559
|
+
prompter.print(` }`);
|
|
5560
|
+
prompter.print("");
|
|
5561
|
+
prompter.print(`If you're just testing locally without public DNS, point your`);
|
|
5562
|
+
prompter.print(`/etc/hosts at 127.0.0.1 and pass --tls-mode internal — Caddy will`);
|
|
5563
|
+
prompter.print(`use its local CA (the cert won't be trusted by other machines).`);
|
|
5564
|
+
prompter.print("");
|
|
5565
|
+
}
|
|
5534
5566
|
function redact(c) {
|
|
5535
5567
|
return {
|
|
5536
5568
|
...c,
|
package/package.json
CHANGED
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/rs/fbi-proxy.rs
CHANGED
|
@@ -11,7 +11,8 @@ use hyper_tungstenite::{HyperWebsocket, WebSocketStream};
|
|
|
11
11
|
use hyper_util::client::legacy::{Client, connect::HttpConnector};
|
|
12
12
|
use hyper_util::rt::TokioIo;
|
|
13
13
|
use hyper_rustls::HttpsConnector;
|
|
14
|
-
use
|
|
14
|
+
use arc_swap::ArcSwap;
|
|
15
|
+
use log::{error, info, warn};
|
|
15
16
|
use regex::Regex;
|
|
16
17
|
use std::convert::Infallible;
|
|
17
18
|
use std::net::SocketAddr;
|
|
@@ -33,7 +34,10 @@ pub struct FBIProxy {
|
|
|
33
34
|
client: Client<HttpsConnector<HttpConnector>, BoxBody>,
|
|
34
35
|
number_regex: Regex,
|
|
35
36
|
domain_filter: Option<String>,
|
|
36
|
-
|
|
37
|
+
/// Compiled routes wrapped in an ArcSwap so they can be replaced
|
|
38
|
+
/// atomically at runtime (hot reload from `--routes`). Reads use
|
|
39
|
+
/// `.load()` and never block; writes are atomic Arc swaps.
|
|
40
|
+
compiled_routes: Arc<ArcSwap<Vec<CompiledRoute>>>,
|
|
37
41
|
}
|
|
38
42
|
|
|
39
43
|
/*
|
|
@@ -85,10 +89,17 @@ impl FBIProxy {
|
|
|
85
89
|
client,
|
|
86
90
|
number_regex: Regex::new(r"^\d+$").unwrap(),
|
|
87
91
|
domain_filter,
|
|
88
|
-
compiled_routes,
|
|
92
|
+
compiled_routes: Arc::new(ArcSwap::from_pointee(compiled_routes)),
|
|
89
93
|
}
|
|
90
94
|
}
|
|
91
95
|
|
|
96
|
+
/// Return a handle to the live routes Arc so callers (e.g. the
|
|
97
|
+
/// file watcher) can swap them at runtime without re-creating the
|
|
98
|
+
/// proxy.
|
|
99
|
+
pub fn routes_handle(&self) -> Arc<ArcSwap<Vec<CompiledRoute>>> {
|
|
100
|
+
Arc::clone(&self.compiled_routes)
|
|
101
|
+
}
|
|
102
|
+
|
|
92
103
|
fn landing_page_html() -> String {
|
|
93
104
|
r#"<!DOCTYPE html>
|
|
94
105
|
<html lang="en">
|
|
@@ -216,8 +227,12 @@ npx fbi-proxy -d fbi.example.com # Only accept *.fbi.example.com</pre>
|
|
|
216
227
|
}
|
|
217
228
|
}
|
|
218
229
|
|
|
230
|
+
// Lock-free read of the live routes (may have been swapped by
|
|
231
|
+
// the file watcher mid-flight). `.load()` returns an Arc; we
|
|
232
|
+
// hold a reference for the duration of the match.
|
|
233
|
+
let routes_guard = self.compiled_routes.load();
|
|
219
234
|
let hit = routes::match_host_with_domain(
|
|
220
|
-
|
|
235
|
+
routes_guard.as_ref(),
|
|
221
236
|
host_header,
|
|
222
237
|
self.domain_filter.as_deref(),
|
|
223
238
|
)?;
|
|
@@ -616,16 +631,115 @@ fn load_routes(yaml_src: &str, source_label: &str) -> Vec<CompiledRoute> {
|
|
|
616
631
|
}
|
|
617
632
|
}
|
|
618
633
|
|
|
634
|
+
/// Parse + compile a routes file without panicking. Returns Err with a
|
|
635
|
+
/// human-readable message on any failure. Used by the hot-reload path
|
|
636
|
+
/// where we want to log + keep current rules rather than crash.
|
|
637
|
+
fn try_reload_routes(path: &str) -> Result<Vec<CompiledRoute>, String> {
|
|
638
|
+
let yaml = std::fs::read_to_string(path)
|
|
639
|
+
.map_err(|e| format!("read {}: {}", path, e))?;
|
|
640
|
+
let parsed = routes::parse_yaml(&yaml)
|
|
641
|
+
.map_err(|e| format!("parse {}: {}", path, e))?;
|
|
642
|
+
routes::compile(parsed.routes)
|
|
643
|
+
.map_err(|e| format!("compile {}: {}", path, e))
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
/// Watch a routes file and atomically swap in new rules on change.
|
|
647
|
+
/// Debounces flurries of FS events (some editors save by truncate+
|
|
648
|
+
/// rewrite which can fire multiple notifications in ~ms). On parse or
|
|
649
|
+
/// compile failure, log a warning and leave the existing rules in
|
|
650
|
+
/// place — the running proxy continues to work with whatever last
|
|
651
|
+
/// loaded successfully.
|
|
652
|
+
fn spawn_routes_watcher(
|
|
653
|
+
path: String,
|
|
654
|
+
handle: Arc<ArcSwap<Vec<CompiledRoute>>>,
|
|
655
|
+
) {
|
|
656
|
+
use notify::{RecursiveMode, Watcher};
|
|
657
|
+
use std::sync::mpsc;
|
|
658
|
+
|
|
659
|
+
std::thread::spawn(move || {
|
|
660
|
+
let (tx, rx) = mpsc::channel();
|
|
661
|
+
let mut watcher = match notify::recommended_watcher(move |res| {
|
|
662
|
+
// Best-effort forward; if the receiver is gone the watcher
|
|
663
|
+
// thread is shutting down anyway.
|
|
664
|
+
let _ = tx.send(res);
|
|
665
|
+
}) {
|
|
666
|
+
Ok(w) => w,
|
|
667
|
+
Err(e) => {
|
|
668
|
+
error!("[routes hot-reload] failed to create watcher: {}", e);
|
|
669
|
+
return;
|
|
670
|
+
}
|
|
671
|
+
};
|
|
672
|
+
|
|
673
|
+
if let Err(e) = watcher.watch(
|
|
674
|
+
std::path::Path::new(&path),
|
|
675
|
+
RecursiveMode::NonRecursive,
|
|
676
|
+
) {
|
|
677
|
+
error!("[routes hot-reload] failed to watch {}: {}", path, e);
|
|
678
|
+
return;
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
info!("[routes hot-reload] watching {}", path);
|
|
682
|
+
|
|
683
|
+
// Debounce window — wait this long for the burst to subside
|
|
684
|
+
// before reloading.
|
|
685
|
+
const DEBOUNCE: Duration = Duration::from_millis(150);
|
|
686
|
+
|
|
687
|
+
loop {
|
|
688
|
+
// Block for the next event.
|
|
689
|
+
match rx.recv() {
|
|
690
|
+
Ok(Ok(_event)) => {}
|
|
691
|
+
Ok(Err(e)) => {
|
|
692
|
+
warn!("[routes hot-reload] watcher error: {}", e);
|
|
693
|
+
continue;
|
|
694
|
+
}
|
|
695
|
+
Err(_) => break, // sender dropped — proxy shutting down
|
|
696
|
+
}
|
|
697
|
+
// Drain any additional events that arrive during the debounce
|
|
698
|
+
// window, so a single save that fires 3 events triggers
|
|
699
|
+
// exactly one reload.
|
|
700
|
+
loop {
|
|
701
|
+
match rx.recv_timeout(DEBOUNCE) {
|
|
702
|
+
Ok(_) => continue,
|
|
703
|
+
Err(_) => break,
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
match try_reload_routes(&path) {
|
|
708
|
+
Ok(new_routes) => {
|
|
709
|
+
let n = new_routes.len();
|
|
710
|
+
handle.store(Arc::new(new_routes));
|
|
711
|
+
info!("[routes hot-reload] reloaded {} rule(s) from {}", n, path);
|
|
712
|
+
}
|
|
713
|
+
Err(reason) => {
|
|
714
|
+
warn!(
|
|
715
|
+
"[routes hot-reload] reload failed, keeping previous rules: {}",
|
|
716
|
+
reason
|
|
717
|
+
);
|
|
718
|
+
}
|
|
719
|
+
}
|
|
720
|
+
}
|
|
721
|
+
});
|
|
722
|
+
}
|
|
723
|
+
|
|
619
724
|
pub async fn start_proxy_server(
|
|
620
725
|
host: Option<&str>,
|
|
621
726
|
port: u16,
|
|
622
727
|
domain_filter: Option<String>,
|
|
623
728
|
compiled_routes: Vec<CompiledRoute>,
|
|
729
|
+
watch_path: Option<String>,
|
|
624
730
|
) -> Result<(), BoxError> {
|
|
625
731
|
let host = host.unwrap_or("127.0.0.1");
|
|
626
732
|
let addr: SocketAddr = format!("{}:{}", host, port).parse()?;
|
|
627
733
|
let proxy = Arc::new(FBIProxy::new(domain_filter.clone(), compiled_routes));
|
|
628
734
|
|
|
735
|
+
// Hot-reload: when the user pointed us at a routes file with
|
|
736
|
+
// --routes, spawn a background watcher that re-parses + swaps in
|
|
737
|
+
// new rules on file change. Failures leave the current rules in
|
|
738
|
+
// place — never crash the running proxy because of a typo in YAML.
|
|
739
|
+
if let Some(path) = watch_path {
|
|
740
|
+
spawn_routes_watcher(path, proxy.routes_handle());
|
|
741
|
+
}
|
|
742
|
+
|
|
629
743
|
let listener = TcpListener::bind(addr).await?;
|
|
630
744
|
|
|
631
745
|
info!("FBI Proxy server running on http://{}", addr);
|
|
@@ -783,8 +897,10 @@ TRY RUN:
|
|
|
783
897
|
};
|
|
784
898
|
|
|
785
899
|
// Load routes: either from --routes <path> or the bundled default.
|
|
786
|
-
|
|
787
|
-
|
|
900
|
+
// The bundled YAML is baked into the binary and can't change at
|
|
901
|
+
// runtime, so hot reload only applies to the --routes path.
|
|
902
|
+
let (compiled_routes, watch_path) = if routes_path.is_empty() {
|
|
903
|
+
(load_routes(BUNDLED_ROUTES_YAML, "bundled routes.yaml"), None)
|
|
788
904
|
} else {
|
|
789
905
|
let src = match std::fs::read_to_string(routes_path) {
|
|
790
906
|
Ok(s) => s,
|
|
@@ -793,7 +909,10 @@ TRY RUN:
|
|
|
793
909
|
std::process::exit(2);
|
|
794
910
|
}
|
|
795
911
|
};
|
|
796
|
-
|
|
912
|
+
(
|
|
913
|
+
load_routes(&src, &format!("routes file '{}'", routes_path)),
|
|
914
|
+
Some(routes_path.clone()),
|
|
915
|
+
)
|
|
797
916
|
};
|
|
798
917
|
|
|
799
918
|
let rt = tokio::runtime::Runtime::new().unwrap();
|
|
@@ -802,7 +921,15 @@ TRY RUN:
|
|
|
802
921
|
"Starting FBI-Proxy on {}:{} with domain filter: {:?}",
|
|
803
922
|
host, port, domain_filter
|
|
804
923
|
);
|
|
805
|
-
if let Err(e) = start_proxy_server(
|
|
924
|
+
if let Err(e) = start_proxy_server(
|
|
925
|
+
Some(host),
|
|
926
|
+
port,
|
|
927
|
+
domain_filter,
|
|
928
|
+
compiled_routes,
|
|
929
|
+
watch_path,
|
|
930
|
+
)
|
|
931
|
+
.await
|
|
932
|
+
{
|
|
806
933
|
error!("Failed to start proxy server: {}", e);
|
|
807
934
|
}
|
|
808
935
|
});
|
package/ts/auth/setupWizard.ts
CHANGED
|
@@ -133,9 +133,57 @@ export async function runWizard(
|
|
|
133
133
|
prompter.print(JSON.stringify(redact(cfg), null, 2));
|
|
134
134
|
prompter.print("");
|
|
135
135
|
|
|
136
|
+
if (cleanDomain !== "fbi.com") {
|
|
137
|
+
printCustomDomainHints(prompter, cleanDomain);
|
|
138
|
+
}
|
|
139
|
+
|
|
136
140
|
return cfg;
|
|
137
141
|
}
|
|
138
142
|
|
|
143
|
+
function printCustomDomainHints(prompter: WizardPrompter, domain: string) {
|
|
144
|
+
prompter.print("─── Custom-domain DNS + TLS hints ───");
|
|
145
|
+
prompter.print("");
|
|
146
|
+
prompter.print(`Your domain is '${domain}' (not the default fbi.com).`);
|
|
147
|
+
prompter.print(`You'll need DNS A-records pointing the wildcard + sso host`);
|
|
148
|
+
prompter.print(`at the public IP of the machine running fbi-proxy:`);
|
|
149
|
+
prompter.print("");
|
|
150
|
+
prompter.print(` *.${domain} A <your-public-ip>`);
|
|
151
|
+
prompter.print(
|
|
152
|
+
` sso.${domain} A <your-public-ip> (covered by the wildcard,`,
|
|
153
|
+
);
|
|
154
|
+
prompter.print(
|
|
155
|
+
` but call it out explicitly)`,
|
|
156
|
+
);
|
|
157
|
+
prompter.print("");
|
|
158
|
+
prompter.print(`For wildcard TLS via Let's Encrypt you need DNS-01 (HTTP-01`);
|
|
159
|
+
prompter.print(`can't issue wildcards). With Cloudflare DNS:`);
|
|
160
|
+
prompter.print("");
|
|
161
|
+
prompter.print(
|
|
162
|
+
` 1. Create a Cloudflare API token with Zone:DNS:Edit on '${domain}'.`,
|
|
163
|
+
);
|
|
164
|
+
prompter.print(` 2. Export it: CLOUDFLARE_API_TOKEN=...`);
|
|
165
|
+
prompter.print(
|
|
166
|
+
` 3. Run with --with-caddy --tls-mode auto. fbi-proxy will generate`,
|
|
167
|
+
);
|
|
168
|
+
prompter.print(` a Caddyfile that uses Caddy's cloudflare DNS plugin:`);
|
|
169
|
+
prompter.print("");
|
|
170
|
+
prompter.print(` *.${domain} {`);
|
|
171
|
+
prompter.print(` tls { dns cloudflare {env.CLOUDFLARE_API_TOKEN} }`);
|
|
172
|
+
prompter.print(` reverse_proxy 127.0.0.1:{$FBI_PROXY_PORT}`);
|
|
173
|
+
prompter.print(` }`);
|
|
174
|
+
prompter.print("");
|
|
175
|
+
prompter.print(
|
|
176
|
+
`If you're just testing locally without public DNS, point your`,
|
|
177
|
+
);
|
|
178
|
+
prompter.print(
|
|
179
|
+
`/etc/hosts at 127.0.0.1 and pass --tls-mode internal — Caddy will`,
|
|
180
|
+
);
|
|
181
|
+
prompter.print(
|
|
182
|
+
`use its local CA (the cert won't be trusted by other machines).`,
|
|
183
|
+
);
|
|
184
|
+
prompter.print("");
|
|
185
|
+
}
|
|
186
|
+
|
|
139
187
|
function redact(c: AuthConfigShape): AuthConfigShape {
|
|
140
188
|
return {
|
|
141
189
|
...c,
|