diff --git a/crud.py b/crud.py index b942190..a2b1ddd 100644 --- a/crud.py +++ b/crud.py @@ -308,8 +308,10 @@ async def create_lamassu_config(data: CreateLamassuConfigData) -> LamassuConfig: await db.execute( """ INSERT INTO myextension.lamassu_config - (id, host, port, database_name, username, password, is_active, created_at, updated_at) - VALUES (:id, :host, :port, :database_name, :username, :password, :is_active, :created_at, :updated_at) + (id, host, port, database_name, username, password, is_active, created_at, updated_at, + use_ssh_tunnel, ssh_host, ssh_port, ssh_username, ssh_password, ssh_private_key) + VALUES (:id, :host, :port, :database_name, :username, :password, :is_active, :created_at, :updated_at, + :use_ssh_tunnel, :ssh_host, :ssh_port, :ssh_username, :ssh_password, :ssh_private_key) """, { "id": config_id, @@ -320,7 +322,13 @@ async def create_lamassu_config(data: CreateLamassuConfigData) -> LamassuConfig: "password": data.password, "is_active": True, "created_at": datetime.now(), - "updated_at": datetime.now() + "updated_at": datetime.now(), + "use_ssh_tunnel": data.use_ssh_tunnel, + "ssh_host": data.ssh_host, + "ssh_port": data.ssh_port, + "ssh_username": data.ssh_username, + "ssh_password": data.ssh_password, + "ssh_private_key": data.ssh_private_key } ) return await get_lamassu_config(config_id) diff --git a/migrations.py b/migrations.py index 1048e94..7556f97 100644 --- a/migrations.py +++ b/migrations.py @@ -116,3 +116,45 @@ async def m006_create_lamassu_config(db): ); """ ) + + +async def m007_add_ssh_tunnel_support(db): + """ + Add SSH tunnel support to Lamassu configuration table. + """ + await db.execute( + """ + ALTER TABLE myextension.lamassu_config + ADD COLUMN use_ssh_tunnel BOOLEAN NOT NULL DEFAULT false; + """ + ) + await db.execute( + """ + ALTER TABLE myextension.lamassu_config + ADD COLUMN ssh_host TEXT; + """ + ) + await db.execute( + """ + ALTER TABLE myextension.lamassu_config + ADD COLUMN ssh_port INTEGER NOT NULL DEFAULT 22; + """ + ) + await db.execute( + """ + ALTER TABLE myextension.lamassu_config + ADD COLUMN ssh_username TEXT; + """ + ) + await db.execute( + """ + ALTER TABLE myextension.lamassu_config + ADD COLUMN ssh_password TEXT; + """ + ) + await db.execute( + """ + ALTER TABLE myextension.lamassu_config + ADD COLUMN ssh_private_key TEXT; + """ + ) diff --git a/models.py b/models.py index 5d60566..c916953 100644 --- a/models.py +++ b/models.py @@ -106,6 +106,13 @@ class CreateLamassuConfigData(BaseModel): database_name: str username: str password: str + # SSH Tunnel settings + use_ssh_tunnel: bool = False + ssh_host: Optional[str] = None + ssh_port: int = 22 + ssh_username: Optional[str] = None + ssh_password: Optional[str] = None + ssh_private_key: Optional[str] = None # Path to private key file or key content class LamassuConfig(BaseModel): @@ -120,6 +127,13 @@ class LamassuConfig(BaseModel): test_connection_success: Optional[bool] created_at: datetime updated_at: datetime + # SSH Tunnel settings + use_ssh_tunnel: bool = False + ssh_host: Optional[str] = None + ssh_port: int = 22 + ssh_username: Optional[str] = None + ssh_password: Optional[str] = None + ssh_private_key: Optional[str] = None class UpdateLamassuConfigData(BaseModel): @@ -129,6 +143,13 @@ class UpdateLamassuConfigData(BaseModel): username: Optional[str] = None password: Optional[str] = None is_active: Optional[bool] = None + # SSH Tunnel settings + use_ssh_tunnel: Optional[bool] = None + ssh_host: Optional[str] = None + ssh_port: Optional[int] = None + ssh_username: Optional[str] = None + ssh_password: Optional[str] = None + ssh_private_key: Optional[str] = None # Legacy models (keep for backward compatibility during transition) diff --git a/static/js/index.js b/static/js/index.js index d4d144a..0e02e66 100644 --- a/static/js/index.js +++ b/static/js/index.js @@ -69,7 +69,14 @@ window.app = Vue.createApp({ port: 5432, database_name: '', username: '', - password: '' + password: '', + // SSH Tunnel settings + use_ssh_tunnel: false, + ssh_host: '', + ssh_port: 22, + ssh_username: '', + ssh_password: '', + ssh_private_key: '' } }, @@ -148,7 +155,14 @@ window.app = Vue.createApp({ port: this.configDialog.data.port, database_name: this.configDialog.data.database_name, username: this.configDialog.data.username, - password: this.configDialog.data.password + password: this.configDialog.data.password, + // SSH Tunnel settings + use_ssh_tunnel: this.configDialog.data.use_ssh_tunnel, + ssh_host: this.configDialog.data.ssh_host, + ssh_port: this.configDialog.data.ssh_port, + ssh_username: this.configDialog.data.ssh_username, + ssh_password: this.configDialog.data.ssh_password, + ssh_private_key: this.configDialog.data.ssh_private_key } const {data: config} = await LNbits.api.request( @@ -178,7 +192,14 @@ window.app = Vue.createApp({ port: 5432, database_name: '', username: '', - password: '' + password: '', + // SSH Tunnel settings + use_ssh_tunnel: false, + ssh_host: '', + ssh_port: 22, + ssh_username: '', + ssh_password: '', + ssh_private_key: '' } }, @@ -640,6 +661,22 @@ window.app = Vue.createApp({ }, computed: { + isConfigFormValid() { + const data = this.configDialog.data + + // Basic database fields are required + const basicValid = data.host && data.database_name && data.username + + // If SSH tunnel is enabled, validate SSH fields + if (data.use_ssh_tunnel) { + const sshValid = data.ssh_host && data.ssh_username && + (data.ssh_password || data.ssh_private_key) + return basicValid && sshValid + } + + return basicValid + }, + clientOptions() { return this.dcaClients.map(client => ({ label: `${client.user_id.substring(0, 8)}... (${client.dca_mode})`, diff --git a/templates/myextension/index.html b/templates/myextension/index.html index f7b2577..503dc93 100644 --- a/templates/myextension/index.html +++ b/templates/myextension/index.html @@ -558,6 +558,83 @@ hint="Database password" > + + +
SSH Tunnel (Recommended)
+ +
+ + Use SSH Tunnel +
+ +
+ + + + + + + + + + + + + SSH tunneling keeps your database secure by avoiding direct internet exposure. + The database connection will be routed through the SSH server. + +
+