Derive install dir from jar location instead of nonexistent property

The signal.cli.install.dir system property was never set by the Gradle
start script or anywhere else. Replace it with code source detection:
resolve the jar's parent directory to find the install root, then look
for bin/signal-call-tunnel relative to that.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Shaheen Gandhi 2026-03-17 16:02:50 -07:00
parent 510edd625f
commit 2df034c01b
3 changed files with 56 additions and 7 deletions

View File

@ -42,7 +42,7 @@ For each call, signal-cli:
The `signal-call-tunnel` binary is located by searching (in order):
1. `SIGNAL_CALL_TUNNEL_BIN` environment variable
2. `<signal-cli install dir>/bin/signal-call-tunnel`
2. `<signal-cli install dir>/bin/signal-call-tunnel` (detected from jar location)
3. `signal-call-tunnel` on `PATH`
### Config JSON

View File

@ -421,19 +421,35 @@ public class CallManager implements AutoCloseable {
return envPath;
}
// Check relative to the signal-cli installation
var installDir = System.getProperty("signal.cli.install.dir");
if (installDir != null) {
var binPath = Path.of(installDir, "bin", "signal-call-tunnel");
if (Files.isExecutable(binPath)) {
return binPath.toString();
// Check relative to the signal-cli installation directory
try {
var codeSource = CallManager.class.getProtectionDomain().getCodeSource();
if (codeSource != null) {
var jarPath = Path.of(codeSource.getLocation().toURI());
var binPath = tunnelBinaryFromCodeSourcePath(jarPath);
if (Files.isExecutable(binPath)) {
return binPath.toString();
}
}
} catch (Exception e) {
logger.debug("Failed to determine install dir from code source", e);
}
// Fall back to PATH
return "signal-call-tunnel";
}
/**
* Resolves the expected tunnel binary path from a code source path.
* The code source (jar or class dir) is expected to be in {@code <install>/lib/},
* so we go up two levels to reach the install root, then look for
* {@code bin/signal-call-tunnel}.
*/
static Path tunnelBinaryFromCodeSourcePath(Path codeSourcePath) {
var installDir = codeSourcePath.getParent().getParent();
return installDir.resolve("bin").resolve("signal-call-tunnel");
}
private String buildConfig(CallState state) {
// Generate control channel authentication token
var tokenBytes = new byte[32];

View File

@ -11,6 +11,7 @@ import org.junit.jupiter.params.provider.ValueSource;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.math.BigInteger;
import java.nio.file.Path;
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
@ -412,6 +413,38 @@ class CallManagerTest {
assertEquals(null, info.outputDeviceName());
}
// ========================================================================
// tunnelBinaryFromCodeSourcePath tests
//
// The install dir is derived from the code source location (jar or class
// directory): go up two levels (out of lib/) to reach the install root,
// then resolve bin/signal-call-tunnel.
// ========================================================================
@Test
void tunnelBinaryFromCodeSourcePath_resolvesFromJarInLib() {
// Simulate: /opt/signal-cli/lib/signal-cli.jar
var jarPath = Path.of("/opt/signal-cli/lib/signal-cli.jar");
var result = CallManager.tunnelBinaryFromCodeSourcePath(jarPath);
assertEquals(Path.of("/opt/signal-cli/bin/signal-call-tunnel"), result);
}
@Test
void tunnelBinaryFromCodeSourcePath_resolvesFromClassDir() {
// In dev/test, code source is a directory like build/classes/java/main
var classDir = Path.of("/project/lib/build/classes/java/main");
var result = CallManager.tunnelBinaryFromCodeSourcePath(classDir);
// Goes up two levels from main -> classes, then looks for bin/signal-call-tunnel
assertEquals(Path.of("/project/lib/build/classes/bin/signal-call-tunnel"), result);
}
@Test
void tunnelBinaryFromCodeSourcePath_deeplyNestedPath() {
var jarPath = Path.of("/home/user/.local/share/signal-cli/lib/signal-cli.jar");
var result = CallManager.tunnelBinaryFromCodeSourcePath(jarPath);
assertEquals(Path.of("/home/user/.local/share/signal-cli/bin/signal-call-tunnel"), result);
}
// ========================================================================
// Helpers that reproduce the documented logic from handleStateChange and
// endCall, allowing us to verify the state machine rules without needing