-
Notifications
You must be signed in to change notification settings - Fork 4
Expand file tree
/
Copy pathzbinstall
More file actions
executable file
·58 lines (52 loc) · 2.69 KB
/
Copy pathzbinstall
File metadata and controls
executable file
·58 lines (52 loc) · 2.69 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
#!/usr/bin/env -S java --source 25
import module java.net.http;
String name = MethodHandles.lookup().lookupClass().getName();
String version = "2026-06-10.1";
String baseUrl = "https://github.com/AdamBien/zb/releases/latest/download";
Path jarTarget = Path.of("zb.jar");
Path shellTarget = Path.of("zb.sh");
/// Both files land in the current working directory so the user can `mv` them
/// onto PATH (e.g., `~/bin/`) in one step. `zb.sh` is the recommended entry
/// point and needs the executable bit; the jar does not.
///
/// No flag handling — per the `/java-cli-script` convention, scripts with no
/// operands surface their version as the first line of normal output instead
/// of gating it behind `-version`.
void main(String... args) throws Exception {
IO.println("%s %s".formatted(name, version));
install(baseUrl + "/zb.jar", jarTarget, false);
install(baseUrl + "/zb.sh", shellTarget, true);
}
void install(String url, Path destination, boolean executable) throws Exception {
IO.println("%s: fetching %s".formatted(name, url));
download(url, destination);
if (executable) {
destination.toFile().setExecutable(true, false);
}
IO.println("%s: installed %s (%d bytes)".formatted(name, destination, Files.size(destination)));
}
/// Atomic-replace: `destination` either keeps its previous content or becomes
/// the fully-downloaded payload, never a half-written stream.
///
/// The `.part` staging file lives next to `destination`, not in the system
/// temp directory, so [Files#move] with [StandardCopyOption#ATOMIC_MOVE]
/// cannot fail with a cross-device link error on hosts where `/tmp` sits on
/// a different filesystem. [Path#toAbsolutePath] is what makes the lookup
/// work for `Path.of("zb.jar")` — a bare filename has a null relative parent.
///
/// Exit-on-failure on non-200 is deliberate — a single-file script has no
/// caller to unwind to.
void download(String url, Path destination) throws Exception {
var parent = destination.toAbsolutePath().getParent();
Files.createDirectories(parent);
var staging = Files.createTempFile(parent, destination.getFileName() + "-", ".part");
var client = HttpClient.newBuilder().followRedirects(HttpClient.Redirect.ALWAYS).build();
var request = HttpRequest.newBuilder(URI.create(url)).build();
var response = client.send(request, HttpResponse.BodyHandlers.ofFile(staging));
if (response.statusCode() != HttpURLConnection.HTTP_OK) {
Files.deleteIfExists(staging);
System.err.println("%s: download failed (HTTP %d)".formatted(name, response.statusCode()));
System.exit(1);
}
Files.move(staging, destination, StandardCopyOption.REPLACE_EXISTING, StandardCopyOption.ATOMIC_MOVE);
}