fix(tunnel): SSH key auth instead of sshpass; init.d boot persistence; copy fallback

aarch64-k3.10 Entware does not have sshpass or cronie packages. Switch to ed25519
SSH key auth and persist via /opt/etc/init.d/S99kdns_tunnel instead of cron.

- /api/routers/{rid}/tunnel-cmd: drop VPS_SSH_PASS dependency, generate one-time
  registration token, build install script that:
    1. opkg install autossh + openssh-keygen
    2. ssh-keygen ed25519 if not exists
    3. curl POST pubkey to new register-key endpoint with token
    4. autossh -i <key>  (no sshpass)
    5. /opt/etc/init.d/S99kdns_tunnel for boot autostart
- /api/routers/{rid}/tunnel-register-key: new endpoint, token-auth, appends pubkey
  to ~/.ssh/authorized_keys with kdns-tunnel-{rid} comment for de-dup
- copy button: fallback to document.execCommand('copy') for non-HTTPS contexts
  (navigator.clipboard requires secure context — UI runs on plain http)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Андрей Бобырев
2026-04-27 22:03:40 +03:00
parent cc31234e91
commit b140cdac43
2 changed files with 131 additions and 22 deletions

View File

@@ -435,9 +435,29 @@ function closeTunnelModal(){
}
function copyTunnelCmd(){
const txt=document.getElementById('tunnel-cmd-text').textContent;
navigator.clipboard.writeText(txt).then(()=>{
const btn=event.target;btn.textContent='✓ Скопировано';setTimeout(()=>{btn.textContent='📋 Скопировать команду';},2000);
}).catch(()=>alert('Не удалось скопировать'));
const btn=(typeof event!=='undefined'&&event&&event.target)?event.target:document.querySelector('button[onclick="copyTunnelCmd()"]');
const flash=()=>{if(btn){const o=btn.textContent;btn.textContent='✓ Скопировано';setTimeout(()=>{btn.textContent=o;},2000);}};
const fail=()=>{
const ta=document.getElementById('tunnel-cmd-text');
const r=document.createRange();r.selectNodeContents(ta);
const s=window.getSelection();s.removeAllRanges();s.addRange(r);
alert('Не получилось автоматически. Команда выделена — нажми Cmd/Ctrl+C, затем вставь в SSH роутера.');
};
// navigator.clipboard работает только в HTTPS / localhost. На http://IP:port — нет.
if(window.isSecureContext&&navigator.clipboard){
navigator.clipboard.writeText(txt).then(flash).catch(()=>execCopy(txt,flash,fail));
} else {
execCopy(txt,flash,fail);
}
}
function execCopy(txt,ok,fail){
const ta=document.createElement('textarea');
ta.value=txt;ta.style.position='fixed';ta.style.top='0';ta.style.left='0';ta.style.opacity='0';
document.body.appendChild(ta);ta.focus();ta.select();
let okv=false;
try{okv=document.execCommand('copy');}catch(e){okv=false;}
document.body.removeChild(ta);
okv?ok():fail();
}
async function applyTunnelUrl(){
if(!_tunnelRouterId)return;