Skip to content

Commit eb45f9e

Browse files
committed
feat(tor-support): add Tor support for terminal nodes
Extends existing Tor functionality to terminal lightning nodes.
1 parent d5e4f87 commit eb45f9e

File tree

7 files changed

+138
-9
lines changed

7 files changed

+138
-9
lines changed

docker/litd/Dockerfile

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ ARG LITD_VERSION
44
ENV PATH=/opt/litd:$PATH
55

66
RUN apt-get update -y \
7-
&& apt-get install -y curl gosu wait-for-it \
7+
&& apt-get install -y curl gosu wait-for-it tor\
88
&& apt-get clean \
99
&& rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
1010

@@ -21,7 +21,7 @@ RUN chmod a+x /entrypoint.sh
2121

2222
VOLUME ["/home/litd/.litd"]
2323

24-
EXPOSE 9735 8080 10000
24+
EXPOSE 9735 8080 10000 9050 9051
2525

2626
ENTRYPOINT ["/entrypoint.sh"]
2727

docker/litd/docker-entrypoint.sh

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,65 @@ if ! id litd > /dev/null 2>&1; then
1414
chown -R $USERID:$GROUPID /home/litd
1515
fi
1616

17+
usermod -a -G debian-tor litd
18+
mkdir -p /var/lib/tor/litd-service
19+
chown -R debian-tor:debian-tor /var/lib/tor
20+
chmod 755 /var/lib/tor
21+
chmod 700 /var/lib/tor/litd-service
22+
23+
if [ "${ENABLE_TOR}" = "true" ]; then
24+
echo "Starting Tor service..."
25+
26+
# Set default ports if not provided
27+
TOR_SOCKS_PORT=${TOR_SOCKS_PORT:-9050}
28+
TOR_CONTROL_PORT=${TOR_CONTROL_PORT:-9051}
29+
30+
# Generate torrc file with dynamic ports
31+
cat > /etc/tor/torrc <<EOF
32+
# Config
33+
SocksPort 127.0.0.1:${TOR_SOCKS_PORT}
34+
ControlPort 127.0.0.1:${TOR_CONTROL_PORT}
35+
CookieAuthentication 1
36+
DataDirectory /var/lib/tor
37+
Log notice stdout
38+
39+
# Hidden service settings (optional - for LND Tor address)
40+
HiddenServiceDir /var/lib/tor/litd-service
41+
HiddenServiceVersion 3
42+
HiddenServicePort 9735 127.0.0.1:9735
43+
HiddenServicePort 8080 127.0.0.1:8080
44+
EOF
45+
46+
gosu debian-tor tor &
47+
TOR_PID=$!
48+
49+
echo "Waiting for Tor to create auth cookie..."
50+
COOKIE_FILE="/var/lib/tor/control_auth_cookie"
51+
TIMEOUT=30
52+
ELAPSED=0
53+
54+
while [ ! -f "$COOKIE_FILE" ] && [ $ELAPSED -lt $TIMEOUT ]; do
55+
sleep 0.5
56+
ELAPSED=$((ELAPSED + 1))
57+
done
58+
59+
if [ -f "$COOKIE_FILE" ]; then
60+
# Get the litd group ID
61+
LITD_GID=$(getent group litd | cut -d: -f3)
62+
# Change the cookie file's group to litd
63+
chown debian-tor:$LITD_GID "$COOKIE_FILE"
64+
chmod 640 "$COOKIE_FILE"
65+
# Fix directory permissions - Tor sets this to 700, but we need 755
66+
# so litd user can traverse into the directory
67+
chmod 755 /var/lib/tor
68+
echo "Tor auth cookie created and permissions set for SAFECOOKIE auth"
69+
else
70+
echo "WARNING: Tor auth cookie not found after ${TIMEOUT} seconds"
71+
fi
72+
else
73+
echo "Tor service disabled (ENABLE_TOR != 'true')"
74+
fi
75+
1776
if [ $(echo "$1" | cut -c1) = "-" ]; then
1877
echo "$0: assuming arguments for litd"
1978

src/components/network/NetworkActions.spec.tsx

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -208,7 +208,7 @@ describe('NetworkActions Component', () => {
208208
expect(await findByText('tor-failed')).toBeInTheDocument();
209209
});
210210

211-
it('should display disabled switch when no nodes support Tor', () => {
211+
it('should display disabled switch when no nodes support Tor', async () => {
212212
const network = getNetwork(1, 'test network', Status.Stopped);
213213
network.nodes.bitcoin = [
214214
{ ...network.nodes.bitcoin[0], implementation: 'btcd' } as any,
@@ -223,7 +223,7 @@ describe('NetworkActions Component', () => {
223223
},
224224
};
225225

226-
const { getByRole, getByLabelText } = renderWithProviders(
226+
const { getByRole, getByLabelText, findByText } = renderWithProviders(
227227
<NetworkActions
228228
network={network}
229229
onClick={jest.fn()}
@@ -233,7 +233,8 @@ describe('NetworkActions Component', () => {
233233
/>,
234234
{ initialState },
235235
);
236-
236+
fireEvent.mouseOver(getByLabelText('more'));
237+
await findByText('Rename');
237238
const torSwitch = getByRole('switch');
238239
expect(torSwitch).toBeDisabled();
239240
expect(getByLabelText('unlock')).toBeInTheDocument();

src/lib/docker/composeFile.spec.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -263,4 +263,18 @@ describe('ComposeFile', () => {
263263
const service = composeFile.content.services[eclairNode.name];
264264
expect(service.environment?.ENABLE_TOR).toBe('false');
265265
});
266+
267+
it('should set ENABLE_TOR to true when tor is enabled on litd node', () => {
268+
litdNode.enableTor = true;
269+
composeFile.addLitd(litdNode, btcNode, litdNode);
270+
const service = composeFile.content.services[litdNode.name];
271+
expect(service.environment?.ENABLE_TOR).toBe('true');
272+
});
273+
274+
it('should set ENABLE_TOR to false when tor is disabled on litd node', () => {
275+
litdNode.enableTor = false;
276+
composeFile.addLitd(litdNode, btcNode, litdNode);
277+
const service = composeFile.content.services[litdNode.name];
278+
expect(service.environment?.ENABLE_TOR).toBe('false');
279+
});
266280
});

src/lib/docker/composeFile.ts

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -201,11 +201,18 @@ class ComposeFile {
201201
// use the node's custom image or the default for the implementation
202202
const image = node.docker.image || `${dockerConfigs.litd.imageName}:${version}`;
203203
// use the node's custom command or the default for the implementation
204-
const nodeCommand = node.docker.command || getDefaultCommand('litd', version);
204+
let nodeCommand = node.docker.command || getDefaultCommand('litd', version);
205205
// replace the variables in the command
206-
const command = this.mergeCommand(nodeCommand, variables);
206+
nodeCommand = this.mergeCommand(nodeCommand, variables);
207+
// Apply Tor flags if Tor is enabled
208+
nodeCommand = applyTorFlags(nodeCommand, !!node.enableTor, 'litd');
207209
// add the docker service
208-
const svc = litd(name, container, image, rest, grpc, p2p, web, command);
210+
const svc = litd(name, container, image, rest, grpc, p2p, web, nodeCommand);
211+
// add ENABLE_TOR variable
212+
svc.environment = {
213+
...svc.environment,
214+
ENABLE_TOR: node.enableTor ? 'true' : 'false',
215+
};
209216
this.addService(svc);
210217
}
211218

src/lib/lightning/clightning/clightningService.spec.ts

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -463,4 +463,33 @@ describe('CLightningService', () => {
463463
expect(clightningApiMock.httpPost).toHaveBeenCalledTimes(1);
464464
});
465465
});
466+
467+
it('should get node info using torv3 address for rpcUrl', async () => {
468+
const infoResponse: Partial<CLN.GetInfoResponse> = {
469+
id: 'asdf',
470+
alias: '',
471+
address: [
472+
{
473+
type: 'torv3',
474+
address: 'toraddress1234567890.onion',
475+
port: 9735,
476+
},
477+
],
478+
binding: [{ type: 'ipv4', address: '0.0.0.0', port: 9735 }],
479+
blockheight: 0,
480+
numActiveChannels: 0,
481+
numPendingChannels: 0,
482+
numInactiveChannels: 0,
483+
warningLightningdSync: 'blah',
484+
};
485+
486+
clightningApiMock.httpPost.mockResolvedValue(infoResponse);
487+
const expected = defaultStateInfo({
488+
pubkey: 'asdf',
489+
rpcUrl: 'asdf@toraddress1234567890.onion:9735',
490+
});
491+
492+
const actual = await clightningService.getInfo(node);
493+
expect(actual).toEqual(expected);
494+
});
466495
});

src/utils/network.ts

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -720,6 +720,14 @@ const getTorFlags = (implementation: NodeImplementation): string[] => {
720720
'--socks5.enabled=true',
721721
'--socks5.proxy=127.0.0.1:9050',
722722
];
723+
case 'litd':
724+
return [
725+
'--lnd.tor.active',
726+
'--lnd.tor.socks=127.0.0.1:9050',
727+
'--lnd.tor.control=127.0.0.1:9051',
728+
'--lnd.tor.v3',
729+
'--lnd.listen=localhost',
730+
];
723731
case 'bitcoind':
724732
return ['-proxy=127.0.0.1:9050', '-torcontrol=127.0.0.1:9051', '-bind=127.0.0.1'];
725733
default:
@@ -756,6 +764,16 @@ export const applyTorFlags = (
756764
});
757765
}
758766

767+
if (implementation === 'litd' && enableTor) {
768+
lines = lines.filter(line => {
769+
const trimmed = line.trim();
770+
return !(
771+
trimmed.startsWith('--lnd.listen=0.0.0.0') ||
772+
trimmed.startsWith('--lnd.externalip=')
773+
);
774+
});
775+
}
776+
759777
if (implementation === 'c-lightning' && enableTor) {
760778
lines = lines.filter(line => {
761779
const trimmed = line.trim();
@@ -815,7 +833,8 @@ export const supportsTor = (node: CommonNode): boolean => {
815833
return (
816834
lnNode.implementation === 'LND' ||
817835
lnNode.implementation === 'c-lightning' ||
818-
lnNode.implementation === 'eclair'
836+
lnNode.implementation === 'eclair' ||
837+
lnNode.implementation === 'litd'
819838
);
820839
}
821840
if (node.type === 'bitcoin') {

0 commit comments

Comments
 (0)