Skip to content

Commit f9ef810

Browse files
committed
deleted keys fix
1 parent f2670d7 commit f9ef810

6 files changed

Lines changed: 97 additions & 5 deletions

File tree

‎__init__.py‎

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
from loguru import logger
66

77
from .crud import db
8-
from .tasks import wait_for_paid_invoices
8+
from .tasks import cleanup_tunnels_hourly, wait_for_paid_invoices
99
from .views import reverse_proxy_generic_router
1010
from .views_api import reverse_proxy_api_router
1111

@@ -37,6 +37,10 @@ def reverse_proxy_stop():
3737
def reverse_proxy_start():
3838
task = create_permanent_unique_task("ext_reverse_proxy", wait_for_paid_invoices)
3939
scheduled_tasks.append(task)
40+
hourly_cleanup = create_permanent_unique_task(
41+
"ext_reverse_proxy_cleanup", cleanup_tunnels_hourly
42+
)
43+
scheduled_tasks.append(hourly_cleanup)
4044

4145

4246
__all__ = [
@@ -45,4 +49,4 @@ def reverse_proxy_start():
4549
"reverse_proxy_start",
4650
"reverse_proxy_static_files",
4751
"reverse_proxy_stop",
48-
]
52+
]

‎crud.py‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,13 @@ async def update_extension_settings(user_id: str, data: ExtensionSettings) -> Ex
5656
return settings
5757

5858

59+
async def get_all_extension_settings() -> list[UserExtensionSettings]:
60+
return await db.fetchall(
61+
"SELECT * FROM reverse_proxy.extension_settings",
62+
model=UserExtensionSettings,
63+
)
64+
65+
5966
########################### Proxies ############################
6067
async def create_proxies(
6168
user_id: str,

‎migrations.py‎

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,4 +67,17 @@ async def m004_extension_settings(db):
6767
updated_at TIMESTAMP NOT NULL DEFAULT {db.timestamp_now}
6868
);
6969
"""
70-
)
70+
)
71+
72+
73+
async def m005_add_ssh_public_key(db):
74+
"""
75+
Store the client's SSH public key so it can be re-authorized after grace period.
76+
"""
77+
78+
await db.execute(
79+
"""
80+
ALTER TABLE reverse_proxy.proxies
81+
ADD COLUMN ssh_public_key TEXT;
82+
"""
83+
)

‎models.py‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ class CreateProxies(BaseModel):
3131
subdomain: str
3232
remote_port: int
3333
ssh_user: str
34+
ssh_public_key: str | None = None
3435
expires_at: datetime
3536
status: str
3637
public_id: str
@@ -44,6 +45,7 @@ class Proxies(BaseModel):
4445
subdomain: str
4546
remote_port: int
4647
ssh_user: str
48+
ssh_public_key: str | None = None
4749
expires_at: datetime
4850
status: str
4951
public_id: str

‎services.py‎

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -278,6 +278,10 @@ async def cleanup_expired_proxies_and_sync_caddy(
278278
model=Proxies,
279279
)
280280
for proxy in expired:
281+
if not proxy.ssh_public_key:
282+
captured_key = _extract_authorized_key(settings, proxy)
283+
if captured_key:
284+
proxy.ssh_public_key = captured_key
281285
proxy.status = "expired"
282286
await update_proxies(proxy)
283287
_remove_authorized_key(settings, proxy)
@@ -309,6 +313,15 @@ async def cleanup_expired_proxies_and_sync_caddy(
309313
model=Proxies,
310314
)
311315

316+
# 2b) Ensure authorized_keys contains entries for active tunnels
317+
for proxy in active:
318+
if not proxy.ssh_public_key:
319+
proxy.ssh_public_key = _extract_authorized_key(settings, proxy)
320+
if proxy.ssh_public_key:
321+
await update_proxies(proxy)
322+
if proxy.ssh_public_key:
323+
_add_authorized_key(settings, proxy, proxy.ssh_public_key)
324+
312325
# 3) Rewrite Caddy tunnels file
313326
try:
314327
os.makedirs(os.path.dirname(settings.caddy_tunnels_file), exist_ok=True)
@@ -437,6 +450,9 @@ async def payment_received_for_payments(payment: Payment) -> bool:
437450
logger.warning("reverse_proxy: invalid price_per_day_sats in settings")
438451
return False
439452

453+
if not proxy.ssh_public_key:
454+
proxy.ssh_public_key = _extract_authorized_key(settings, proxy)
455+
440456
days_to_extend = (payment.amount / 1000) / price_per_day
441457

442458
now = datetime.now(timezone.utc)
@@ -447,6 +463,9 @@ async def payment_received_for_payments(payment: Payment) -> bool:
447463
proxy.status = "active"
448464
await update_proxies(proxy)
449465

466+
if proxy.ssh_public_key:
467+
_add_authorized_key(settings, proxy, proxy.ssh_public_key)
468+
450469
payments.paid = True
451470
await update_payments(payments)
452471

@@ -495,13 +514,16 @@ async def _process_payment_with_setup(payment: Payment, setup: dict) -> bool:
495514
subdomain=setup.get("subdomain", random_subdomain()),
496515
remote_port=remote_port,
497516
ssh_user=setup.get("ssh_user", settings.ssh_user),
517+
ssh_public_key=pubkey,
498518
expires_at=expires_at,
499519
status="pending",
500520
public_id=public_id,
501521
client_note=setup.get("client_note", ""),
502522
),
503523
proxies_id=proxy_id,
504524
)
525+
if pubkey:
526+
proxy.ssh_public_key = pubkey
505527
if getattr(proxy, "status", "") != "active":
506528
proxy.status = "active"
507529

@@ -544,6 +566,31 @@ def _authorized_keys_path(settings: ExtensionSettings) -> str:
544566
return os.path.join("/home", settings.ssh_user, ".ssh", "authorized_keys")
545567

546568

569+
def _extract_authorized_key(settings: ExtensionSettings, proxy: Proxies) -> str | None:
570+
"""
571+
Try to read the stored public key for this proxy from authorized_keys.
572+
"""
573+
ak_path = _authorized_keys_path(settings)
574+
marker = f"reverse_proxy:{proxy.id}"
575+
if not os.path.exists(ak_path):
576+
return None
577+
578+
try:
579+
with open(ak_path, "r", encoding="utf-8") as f:
580+
for line in f:
581+
if marker not in line:
582+
continue
583+
parts = line.strip().split()
584+
if len(parts) < 3 or parts[-1] != marker:
585+
continue
586+
return " ".join(parts[1:-1])
587+
except Exception as exc:
588+
logger.error(
589+
f"reverse_proxy: failed to read authorized_key for {proxy.id}: {exc}"
590+
)
591+
return None
592+
593+
547594
def _add_authorized_key(settings: ExtensionSettings, proxy: Proxies, pubkey: str) -> None:
548595
"""
549596
Append a restricted SSH key for this proxy.

‎tasks.py‎

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,14 @@
11
import asyncio
22

33
from lnbits.core.models import Payment
4-
from lnbits.tasks import register_invoice_listener
4+
from lnbits.tasks import register_invoice_listener, run_interval
55
from loguru import logger
66

7-
from .services import payment_received_for_payments
7+
from .crud import get_all_extension_settings
8+
from .services import (
9+
cleanup_expired_proxies_and_sync_caddy,
10+
payment_received_for_payments,
11+
)
812

913

1014
async def wait_for_paid_invoices():
@@ -26,3 +30,18 @@ async def on_invoice_paid(payment: Payment) -> None:
2630
await payment_received_for_payments(payment)
2731
except Exception as e:
2832
logger.error(f"Error processing payment for reverse_proxy: {e}")
33+
34+
35+
async def _cleanup_all_tunnels_once():
36+
settings_list = await get_all_extension_settings()
37+
for settings in settings_list:
38+
try:
39+
await cleanup_expired_proxies_and_sync_caddy(settings.id, settings)
40+
except Exception as exc:
41+
logger.error(
42+
f"reverse_proxy: cleanup failed for user {settings.id}: {exc}"
43+
)
44+
45+
46+
# Runs every hour while the app is running
47+
cleanup_tunnels_hourly = run_interval(3600, _cleanup_all_tunnels_once)

0 commit comments

Comments
 (0)