Main #1

Open
ariska wants to merge 3 commits from ariska/BackOne:main into main
pull from: ariska/BackOne:main
145 changed files with 13608 additions and 2099 deletions
Showing only changes of commit d604c01114 - Show all commits

No files matched your search

@@ -0,0 +1,10 @@
{
"id": "comms-core",
"name": "Comms Core",
"version": "1.17.1",
"requires": [],
"backend": {
"routes": ["/controller/comms"],
"websocketEvents": ["presence", "incoming_call", "call_state", "chat_message"]
}
}
+4
View File
@@ -27,6 +27,10 @@ ifeq ($(OSTYPE),NetBSD)
include make-netbsd.mk
endif
ifeq ($(OS),Windows_NT)
include make-windows.mk
endif
drone:
@echo "rendering .drone.yaml from .drone.jsonnet"
drone jsonnet --format --stream
+57
View File
@@ -0,0 +1,57 @@
window.BackOneRingingPopup = {
_audio: null,
showIncomingCall({ callerName, callType, callId, onAccept, onReject }) {
const el = document.getElementById('ringing-popup');
if (!el) return;
el.classList.remove('ringing-hidden');
el.innerHTML = `
<div class="ringing-card">
<h2>Incoming ${callType} call</h2>
<p>${callerName || 'Unknown peer'}</p>
<button class="ringing-accept">Accept</button>
<button class="ringing-reject">Reject</button>
</div>`;
el.querySelector('.ringing-accept').onclick = () => { this.dismiss(); onAccept && onAccept(callId); };
el.querySelector('.ringing-reject').onclick = () => { this.dismiss(); onReject && onReject(callId); };
this.playRingtone();
if (Notification.permission === 'default') Notification.requestPermission();
if (Notification.permission === 'granted') {
new Notification(`Incoming ${callType} call`, { body: callerName || 'BackOne peer' });
}
},
dismiss() {
const el = document.getElementById('ringing-popup');
if (el) { el.classList.add('ringing-hidden'); el.innerHTML = ''; }
this.stopRingtone();
},
playRingtone() {
this.stopRingtone();
try {
this._audio = new Audio('data:audio/wav;base64,UklGRnoGAABXQVZFZm10IBAAAAABAAEAQB8AAEAfAAABAAgAZGF0YQoGAACBhYqFbF1fdJivrJBhNjVgodDbq2EcBj+a2/LDciUFLIHO8tiJNwgZaLvt559NEAxQp+PwtmMcBjiR1/LMeSwFJHfH8N2QQAoUXrTp66hVFApGn+DyvmwhBSuBzvLZiTYIG2m98OScTgwOUarm7blmHgU7k9n1unEiBC13y+/ek0EKElyx5+ytWBUIQ5zd8sFuJAUwgM/z2Ik6CBxpvfDknE4MDlGq5u25Zh4FO5PZ9br');
this._audio.loop = true;
this._audio.play().catch(() => {});
} catch (_) {}
},
stopRingtone() {
if (this._audio) { this._audio.pause(); this._audio = null; }
}
};
async function loadAddonManifest() {
const res = await fetch('/app/addons/manifest.json', { cache: 'no-store' });
if (!res.ok) {
console.warn('manifest.json fetch failed:', res.status);
return [];
}
const data = await res.json();
return Array.isArray(data) ? data : [];
}
async function loadAddonModule(entry) {
const entryPath = entry.frontend && entry.frontend.entry ? entry.frontend.entry : `/app/addons/${entry.id}/index.js`;
await import(entryPath);
const factory = window.BackOneAddons && window.BackOneAddons[entry.id];
return factory ? factory() : null;
}
window.BackOneAddonLoader = { loadAddonManifest, loadAddonModule };
+195
View File
@@ -0,0 +1,195 @@
/**
* BackOne API auth — same token as backone-cli (~/.backOneOneAuthToken / X-ZT1-Auth).
*/
window.BackOneAuth = (function () {
const LS_KEY = 'backone.authToken';
let cache = null;
function trimToken(s) {
return String(s || '').replace(/\s+/g, '');
}
function tokenFromUrl() {
try {
const params = new URLSearchParams(location.search);
const fromQuery = trimToken(params.get('auth') || params.get('token'));
if (fromQuery) return fromQuery;
const hash = (location.hash || '').replace(/^#\/?/, '');
const hashParams = new URLSearchParams(hash.includes('?') ? hash.split('?')[1] : '');
return trimToken(hashParams.get('auth') || hashParams.get('token'));
} catch (_) {
return '';
}
}
async function probeToken(token) {
if (!token) return false;
try {
const res = await fetch('/network', {
cache: 'no-store',
headers: { 'X-ZT1-Auth': token }
});
return res.ok;
} catch (_) {
return false;
}
}
async function fetchAuthtoken() {
for (const path of ['/api/backone/authtoken', '/app/api/authtoken']) {
try {
const res = await fetch(path, { cache: 'no-store' });
if (res.ok) {
const data = await res.json();
const token = trimToken(data && data.token);
if (token) return token;
}
} catch (_) {
/* try next */
}
}
return '';
}
async function resolveToken() {
const urlToken = tokenFromUrl();
if (urlToken) {
if (await probeToken(urlToken)) {
return urlToken;
}
}
const stored = trimToken(localStorage.getItem(LS_KEY));
if (stored && await probeToken(stored)) {
return stored;
}
const apiToken = await fetchAuthtoken();
if (apiToken && await probeToken(apiToken)) {
return apiToken;
}
if (stored) return stored;
if (apiToken) return apiToken;
if (urlToken) return urlToken;
return '';
}
async function getToken() {
if (cache) return cache;
cache = await resolveToken();
return cache || '';
}
async function authHeaders(extra) {
const token = await getToken();
const headers = Object.assign({}, extra || {});
if (token) {
headers['X-ZT1-Auth'] = token;
}
return headers;
}
async function fetchWithAuth(path, options) {
options = options || {};
const headers = await authHeaders(options.headers || {});
return fetch(path, Object.assign({}, options, { headers }));
}
async function apiGet(path) {
const res = await fetchWithAuth(path, { cache: 'no-store' });
if (!res.ok) throw new Error(`${path} returned ${res.status}`);
return res.json();
}
async function apiGetFirst(paths) {
let lastErr = null;
for (const path of paths) {
try {
return await apiGet(path);
} catch (err) {
lastErr = err;
}
}
throw lastErr || new Error('API request failed');
}
async function apiSend(path, method, body) {
const headers = await authHeaders(
body != null ? { 'Content-Type': 'application/json' } : {}
);
const res = await fetch(path, {
method,
headers,
body: body != null ? JSON.stringify(body) : undefined
});
if (!res.ok) {
const text = await res.text().catch(() => '');
throw new Error(text || `${method} ${path} returned ${res.status}`);
}
if (res.status === 204) {
return { result: true };
}
const text = await res.text();
if (!text) {
return { result: true };
}
try {
return JSON.parse(text);
} catch (_) {
return { result: true };
}
}
async function apiSendFirst(paths, method, body) {
let lastErr = null;
for (const path of paths) {
try {
return await apiSend(path, method, body);
} catch (err) {
lastErr = err;
}
}
throw lastErr || new Error(`${method} failed`);
}
/** Store token from ~/.backOneOneAuthToken (same as backone-cli). */
async function setUserToken(token) {
cache = trimToken(token);
if (cache) {
localStorage.setItem(LS_KEY, cache);
} else {
localStorage.removeItem(LS_KEY);
}
return cache;
}
async function validateCurrentToken() {
cache = null;
const token = await getToken();
if (!token) return false;
return probeToken(token);
}
function invalidate() {
cache = null;
}
async function init() {
return getToken();
}
return {
getToken,
authHeaders,
fetchWithAuth,
apiGet,
apiGetFirst,
apiSend,
apiSendFirst,
setUserToken,
validateCurrentToken,
invalidate,
init
};
})();
+57
View File
@@ -0,0 +1,57 @@
window.BackOneContacts = (function () {
let cache = null;
async function load() {
if (cache) return cache;
try {
const res = await BackOneAuth.fetchWithAuth('/api/backone/contacts', { cache: 'no-store' })
.catch(() => BackOneAuth.fetchWithAuth('/app/api/contacts', { cache: 'no-store' }));
if (!res.ok) {
cache = {};
return cache;
}
cache = await res.json();
if (!cache || typeof cache !== 'object') cache = {};
} catch (_) {
cache = {};
}
return cache;
}
function getDisplayName(peerId, controllerName) {
const c = cache || {};
const entry = c[peerId];
if (entry && entry.nickname) return entry.nickname;
if (controllerName) return controllerName;
return peerId;
}
function getNickname(peerId) {
const entry = (cache || {})[peerId];
return entry && entry.nickname ? entry.nickname : '';
}
async function setNickname(peerId, nickname) {
await load();
const patch = {};
patch[peerId] = { nickname: nickname || '' };
const res = await BackOneAuth.fetchWithAuth('/api/backone/contacts', {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(patch)
}).catch(() => BackOneAuth.fetchWithAuth('/app/api/contacts', {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(patch)
}));
if (!res.ok) throw new Error('Failed to save nickname');
cache = await res.json();
return cache;
}
function invalidate() {
cache = null;
}
return { load, getDisplayName, getNickname, setNickname, invalidate };
})();
+42
View File
@@ -0,0 +1,42 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>BackOne</title>
<link rel="stylesheet" href="/app/styles.css">
</head>
<body>
<header>
<h1>BackOne</h1>
<div id="network-bar" class="network-bar">
<label class="network-bar-label">Network
<select id="global-network"></select>
</label>
<input id="global-join-id" type="text" maxlength="16" placeholder="Join network ID" autocomplete="off" />
<button type="button" id="global-join-btn">Join</button>
<button type="button" id="global-leave-btn">Leave</button>
<span id="global-network-status" class="network-bar-status"></span>
<div id="auth-token-bar" class="auth-token-bar auth-token-hidden">
<label class="auth-token-label">Auth token
<input id="auth-token-input" type="password" autocomplete="off"
placeholder="Paste from ~/.backOneOneAuthToken" />
</label>
<button type="button" id="auth-token-save">Save</button>
</div>
</div>
<nav id="addon-nav"></nav>
</header>
<main id="addon-panel">
<p>Select an addon from the navigation bar.</p>
</main>
<div id="ringing-popup" class="ringing-hidden"></div>
<script src="/app/auth-store.js"></script>
<script src="/app/contacts-store.js"></script>
<script src="/app/peer-directory.js"></script>
<script src="/app/network-session.js"></script>
<script src="/app/peer-picker.js"></script>
<script src="/app/addon-loader.js"></script>
<script src="/app/main.js"></script>
</body>
</html>
+228
View File
@@ -0,0 +1,228 @@
window.BackOneAddons = window.BackOneAddons || {};
const addonButtons = {};
let addonModules = {};
let coreRefreshTimer = null;
function hashAddonId() {
const h = (location.hash || '').replace(/^#\/?/, '').toLowerCase();
if (h === 'voice' || h === 'video' || h === 'chat') return h;
return '';
}
function activateAddon(id) {
const btn = addonButtons[id];
if (!btn) return;
btn.click();
}
function escapeHtml(s) {
return String(s).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
}
function networkLabel(n) {
if (!n) return '';
return n.name ? `${n.name} (${n.id})` : n.id;
}
function renderCoreStatus(statusEl) {
const core = BackOneNetworkSession.getCoreStatus();
const net = BackOneNetworkSession.getActiveNetwork();
if (!core.online) {
statusEl.innerHTML = '<span class="core-dot offline"></span> BackOne offline — start <code>backone</code> service';
return;
}
const netPart = net ? networkLabel(net) : 'no network joined';
const addr = core.address ? ` · ${escapeHtml(core.address)}` : '';
const authNote = core.authenticated
? ' · auth token OK'
: ' · <span class="peer-network-warn">paste <code>~/.backOneOneAuthToken</code> below</span>';
statusEl.innerHTML =
`<span class="core-dot online"></span> BackOne online${addr} · ` +
`<strong>${escapeHtml(netPart)}</strong> · ${core.leafPeerCount} peer(s)${authNote}`;
}
async function initAuthTokenBar(statusEl, onSaved) {
const bar = document.getElementById('auth-token-bar');
const input = document.getElementById('auth-token-input');
const saveBtn = document.getElementById('auth-token-save');
if (!bar || !input || !saveBtn) return;
async function refreshAuthUi() {
const ok = await BackOneAuth.validateCurrentToken();
bar.classList.toggle('auth-token-hidden', ok);
if (!ok && !input.value) {
input.placeholder = 'Paste from ~/.backOneOneAuthToken';
}
return ok;
}
saveBtn.addEventListener('click', async () => {
const token = input.value.trim();
if (!token) {
if (statusEl) statusEl.textContent = 'Paste the token from ~/.backOneOneAuthToken';
return;
}
saveBtn.disabled = true;
await BackOneAuth.setUserToken(token);
BackOneAuth.invalidate();
const ok = await BackOneAuth.validateCurrentToken();
if (!ok) {
if (statusEl) statusEl.textContent = 'Token rejected — check ~/.backOneOneAuthToken matches the running backone service';
saveBtn.disabled = false;
return;
}
input.value = '';
bar.classList.add('auth-token-hidden');
if (typeof onSaved === 'function') await onSaved();
saveBtn.disabled = false;
});
await refreshAuthUi();
return refreshAuthUi;
}
async function initNetworkBar() {
const bar = document.getElementById('network-bar');
if (!bar) return;
const netSel = bar.querySelector('#global-network');
const joinInput = bar.querySelector('#global-join-id');
const joinBtn = bar.querySelector('#global-join-btn');
const leaveBtn = bar.querySelector('#global-leave-btn');
const statusEl = bar.querySelector('#global-network-status');
async function reloadNetworkList(selectId) {
await BackOneNetworkSession.syncFromCore();
const networks = await BackOneNetworkSession.loadNetworks();
netSel.innerHTML = networks.map(n =>
`<option value="${n.id}">${escapeHtml(networkLabel(n))} · ${escapeHtml(n.status || '')}</option>`
).join('') || '<option value="">— no networks joined —</option>';
const want = (selectId || BackOneNetworkSession.getNetworkId() || '').toLowerCase();
if (want && networks.some(n => (n.id || '').toLowerCase() === want)) {
netSel.value = networks.find(n => (n.id || '').toLowerCase() === want).id;
await BackOneNetworkSession.selectNetwork(netSel.value);
} else if (networks.length === 1) {
netSel.value = networks[0].id;
await BackOneNetworkSession.selectNetwork(networks[0].id);
} else if (networks.length > 0 && BackOneNetworkSession.getNetworkId()) {
netSel.value = BackOneNetworkSession.getNetworkId();
}
renderCoreStatus(statusEl);
}
netSel.addEventListener('change', async () => {
if (!netSel.value) return;
statusEl.textContent = 'Switching network…';
await BackOneNetworkSession.selectNetwork(netSel.value);
await BackOneNetworkSession.syncFromCore();
renderCoreStatus(statusEl);
});
joinBtn.addEventListener('click', async () => {
const id = BackOnePeerDirectory.normalizeNetworkId(joinInput.value);
if (!id) {
statusEl.textContent = 'Enter a 16-digit hexadecimal network ID';
return;
}
joinBtn.disabled = true;
leaveBtn.disabled = true;
statusEl.textContent = `Joining ${id}…`;
try {
await BackOneNetworkSession.joinNetwork(id);
joinInput.value = '';
await reloadNetworkList(id);
} catch (err) {
statusEl.textContent = err.message || 'Join failed';
} finally {
joinBtn.disabled = false;
leaveBtn.disabled = false;
}
});
leaveBtn.addEventListener('click', async () => {
const id = netSel.value || BackOneNetworkSession.getNetworkId();
if (!id) {
statusEl.textContent = 'Select a network to leave';
return;
}
if (!confirm(`Leave network ${networkLabel(BackOneNetworkSession.getActiveNetwork() || { id })}?`)) return;
joinBtn.disabled = true;
leaveBtn.disabled = true;
statusEl.textContent = `Leaving ${id}…`;
try {
await BackOneNetworkSession.leaveNetwork(id);
await reloadNetworkList();
} catch (err) {
statusEl.textContent = err.message || 'Leave failed';
} finally {
joinBtn.disabled = false;
leaveBtn.disabled = false;
}
});
BackOneNetworkSession.subscribe(() => renderCoreStatus(statusEl));
const refreshAuthUi = await initAuthTokenBar(statusEl, () => reloadNetworkList());
await reloadNetworkList();
if (refreshAuthUi) await refreshAuthUi();
coreRefreshTimer = setInterval(() => {
BackOneNetworkSession.syncFromCore().then(() => renderCoreStatus(statusEl));
}, 45000);
}
async function initAppShell() {
await BackOneAuth.init();
await initNetworkBar();
const nav = document.getElementById('addon-nav');
const panel = document.getElementById('addon-panel');
const addons = await BackOneAddonLoader.loadAddonManifest();
if (!addons.length) {
panel.innerHTML = '<p>No UI addons found. Install backone-addon-voice, backone-addon-video, or backone-addon-chat, then run: <code>sudo backone-updater rebuild-manifest</code> and restart backone.</p>';
return;
}
for (const entry of addons) {
const mod = await BackOneAddonLoader.loadAddonModule(entry);
if (mod) addonModules[entry.id] = mod;
const btn = document.createElement('button');
btn.dataset.addonId = entry.id;
btn.textContent = (entry.frontend && entry.frontend.navLabel) || entry.name || entry.id;
btn.onclick = () => {
nav.querySelectorAll('button').forEach(b => b.classList.remove('active'));
btn.classList.add('active');
panel.innerHTML = '';
if (addonModules[entry.id] && addonModules[entry.id].mount) {
addonModules[entry.id].mount(panel);
} else {
panel.innerHTML = `<p>Addon "${entry.id}" loaded.</p>`;
}
};
addonButtons[entry.id] = btn;
nav.appendChild(btn);
}
const fromHash = hashAddonId();
if (fromHash && addonButtons[fromHash]) {
activateAddon(fromHash);
} else {
nav.querySelector('button')?.click();
}
}
window.addEventListener('hashchange', () => {
const id = hashAddonId();
if (id) activateAddon(id);
});
document.addEventListener('visibilitychange', () => {
if (document.visibilityState === 'visible') {
BackOneNetworkSession.syncFromCore();
}
});
document.addEventListener('DOMContentLoaded', initAppShell);
+178
View File
@@ -0,0 +1,178 @@
/**
* Single BackOne core network for all addons (same as backone-cli listnetworks/join).
*/
window.BackOneNetworkSession = (function () {
const NETWORK_KEY = 'backone.lastNetworkId';
const PEER_KEY = 'backone.lastPeerId';
let activeNetwork = null;
let selectedPeer = null;
let coreStatus = { online: false, address: '', networkCount: 0, leafPeerCount: 0, authenticated: false };
const listeners = new Set();
function emit() {
const snap = {
network: activeNetwork,
networkId: getNetworkId(),
peer: selectedPeer,
core: { ...coreStatus }
};
listeners.forEach(fn => {
try { fn(snap); } catch (_) { /* ignore */ }
});
}
function getNetworkId() {
return (localStorage.getItem(NETWORK_KEY) || '').toLowerCase();
}
function setNetworkId(id) {
const nwid = BackOnePeerDirectory.normalizeNetworkId(id);
if (nwid) {
localStorage.setItem(NETWORK_KEY, nwid);
} else {
localStorage.removeItem(NETWORK_KEY);
}
BackOnePeerDirectory.setLastNetworkId(nwid);
return nwid;
}
function getSelectedPeerId() {
return (localStorage.getItem(PEER_KEY) || '').toLowerCase();
}
function setSelectedPeer(peer) {
selectedPeer = peer || null;
if (peer && peer.peerId) {
localStorage.setItem(PEER_KEY, peer.peerId);
} else {
localStorage.removeItem(PEER_KEY);
}
emit();
}
function pickDefaultNetworkId(networks) {
if (!networks.length) return '';
const stored = getNetworkId();
if (stored && networks.some(n => (n.id || '').toLowerCase() === stored)) {
return stored;
}
const preferred = networks.find(n => n.status === 'OK')
|| networks.find(n => n.status === 'REQUESTING_CONFIGURATION')
|| networks[0];
return preferred ? (preferred.id || '').toLowerCase() : '';
}
/**
* On app start: read BackOne core state (same as backone-cli info / listnetworks / peers).
*/
async function syncFromCore() {
const token = await BackOneAuth.getToken();
const [status, networks, cliPeers] = await Promise.all([
BackOnePeerDirectory.loadCoreStatus(),
BackOnePeerDirectory.loadNetworks(),
BackOnePeerDirectory.loadCliPeers()
]);
coreStatus = {
online: !!(status && status.online),
address: (status && status.address) || '',
version: (status && status.version) || '',
networkCount: networks.length,
leafPeerCount: BackOnePeerDirectory.countLeafPeers(cliPeers),
authenticated: !!token
};
const pick = pickDefaultNetworkId(networks);
if (pick) {
setNetworkId(pick);
activeNetwork = networks.find(n => (n.id || '').toLowerCase() === pick)
|| await BackOnePeerDirectory.loadNetwork(pick);
} else {
setNetworkId('');
activeNetwork = null;
}
emit();
return { coreStatus, activeNetwork, networks };
}
async function refresh() {
const id = getNetworkId();
if (!id) {
activeNetwork = null;
emit();
return null;
}
activeNetwork = await BackOnePeerDirectory.loadNetwork(id);
if (!activeNetwork) {
activeNetwork = { id, name: id, status: 'UNKNOWN' };
}
emit();
return activeNetwork;
}
async function loadNetworks() {
return BackOnePeerDirectory.loadNetworks();
}
async function selectNetwork(nwid) {
const id = setNetworkId(nwid);
if (!id) {
await refresh();
return null;
}
return refresh();
}
async function joinNetwork(nwid) {
const id = BackOnePeerDirectory.normalizeNetworkId(nwid);
if (!id) throw new Error('Network ID must be 16 hexadecimal characters');
await BackOnePeerDirectory.joinNetwork(id);
return syncFromCore();
}
async function leaveNetwork(nwid) {
const id = BackOnePeerDirectory.normalizeNetworkId(nwid || getNetworkId());
if (!id) throw new Error('No network selected');
await BackOnePeerDirectory.leaveNetwork(id);
if (getNetworkId() === id) {
setNetworkId('');
selectedPeer = null;
localStorage.removeItem(PEER_KEY);
}
return syncFromCore();
}
function subscribe(fn) {
listeners.add(fn);
return () => listeners.delete(fn);
}
function requireNetworkId() {
const id = getNetworkId();
if (!id) throw new Error('Join a BackOne network first (same as backone-cli join)');
return id;
}
function getCoreStatus() {
return { ...coreStatus };
}
return {
getNetworkId,
getActiveNetwork: () => activeNetwork,
getCoreStatus,
getSelectedPeer: () => selectedPeer,
getSelectedPeerId,
setSelectedPeer,
selectNetwork,
joinNetwork,
leaveNetwork,
loadNetworks,
syncFromCore,
refresh,
subscribe,
requireNetworkId
};
})();
+266
View File
@@ -0,0 +1,266 @@
window.BackOnePeerDirectory = (function () {
const NETWORK_KEY = 'backone.lastNetworkId';
const API = {
peers: ['/api/backone/peers', '/app/api/peers', '/peer'],
networks: ['/api/backone/networks', '/app/api/networks', '/network'],
status: ['/api/backone/status', '/app/api/status', '/status'],
network: (id) => [
`/api/backone/network/${id}`,
`/app/api/network/${id}`,
`/network/${id}`
],
members: (id) => [
`/api/backone/network/${id}/members`,
`/app/api/network/${id}/members`,
`/unstable/controller/network/${id}/member`
],
join: (id) => [
`/api/backone/network/${id}`,
`/app/api/network/${id}`,
`/network/${id}`
],
leave: (id) => [
`/api/backone/network/${id}`,
`/app/api/network/${id}`,
`/network/${id}`
]
};
function apiGet(path) {
return BackOneAuth.apiGet(path);
}
/** Same data as `backone-cli -j peers` (GET /peer). */
async function loadCliPeers() {
try {
const peers = await BackOneAuth.apiGetFirst(API.peers);
return Array.isArray(peers) ? peers : [];
} catch (_) {
return [];
}
}
/** Same data as `backone-cli -j listnetworks` (GET /network). */
async function loadNetworks() {
try {
const nets = await BackOneAuth.apiGetFirst(API.networks);
return Array.isArray(nets) ? nets : [];
} catch (_) {
return [];
}
}
/** Same data as `backone-cli -j info` (GET /status). */
async function loadCoreStatus() {
try {
return await BackOneAuth.apiGetFirst(API.status);
} catch (_) {
try {
const res = await fetch('/health', { cache: 'no-store' });
return { online: res.ok };
} catch (_2) {
return { online: false };
}
}
}
function countLeafPeers(cliPeers) {
if (!Array.isArray(cliPeers)) return 0;
return cliPeers.filter(p => !p.role || p.role === 'LEAF').length;
}
async function loadNetwork(nwid) {
if (!nwid) return null;
const networks = await loadNetworks();
const found = networks.find(n => n.id === nwid);
if (found) return found;
try {
return await BackOneAuth.apiGetFirst(API.network(nwid));
} catch (_) {
return null;
}
}
function getLastNetworkId() {
return localStorage.getItem(NETWORK_KEY) || '';
}
function setLastNetworkId(nwid) {
if (nwid) localStorage.setItem(NETWORK_KEY, nwid);
}
function peerIsOnline(p) {
if (!p) return false;
const paths = p.paths || [];
return paths.some(x => x.active && !x.expired);
}
function peerLinkLabel(p) {
if (!p) return '';
if (p.tunneled) return 'RELAY';
const paths = p.paths || [];
for (const path of paths) {
if (path.preferred && path.active && !path.expired) {
return 'DIRECT';
}
}
for (const path of paths) {
if (path.active && !path.expired) {
return 'DIRECT';
}
}
return 'RELAY';
}
function peerPreferredPath(p) {
if (!p) return '';
const paths = p.paths || [];
for (const path of paths) {
if (path.preferred && path.address) return path.address;
}
for (const path of paths) {
if (path.active && path.address) return path.address;
}
return '';
}
function cliPeerMap(cliPeers) {
const map = {};
for (const p of cliPeers) {
const id = (p.address || '').toLowerCase();
if (id) map[id] = p;
}
return map;
}
async function loadMemberMap(nwid) {
if (!nwid) return {};
try {
const data = await BackOneAuth.apiGetFirst(API.members(nwid));
const map = {};
if (data && Array.isArray(data.data)) {
for (const m of data.data) {
const id = (m.id || '').toLowerCase();
if (id) map[id] = m;
}
}
return map;
} catch (_) {
return {};
}
}
function buildPeerEntry(peerId, member, cliPeer, networkId, networkName) {
const controllerName = member ? (member.name || '') : '';
const ips = member && member.ipAssignments ? member.ipAssignments : [];
return {
peerId,
networkId: networkId || '',
networkName: networkName || '',
displayName: BackOneContacts.getDisplayName(peerId, controllerName),
controllerName,
nickname: BackOneContacts.getNickname(peerId),
online: peerIsOnline(cliPeer),
latency: cliPeer && typeof cliPeer.latency === 'number' ? cliPeer.latency : null,
role: (cliPeer && cliPeer.role) || (member ? 'LEAF' : ''),
version: (cliPeer && cliPeer.version) || '',
link: cliPeer ? peerLinkLabel(cliPeer) : '',
path: cliPeer ? peerPreferredPath(cliPeer) : '',
ip: ips.length ? ips[0] : '',
onNetwork: !!member
};
}
async function loadPeersForNetwork(nwid) {
if (!nwid) return [];
const [cliPeers, members, selfStatus, network] = await Promise.all([
loadCliPeers(),
loadMemberMap(nwid),
loadCoreStatus().catch(() => ({})),
loadNetwork(nwid)
]);
await BackOneContacts.load();
const selfId = ((selfStatus && selfStatus.address) || '').toLowerCase();
const networkId = nwid;
const networkName = network ? (network.name || network.id || nwid) : nwid;
const cliById = cliPeerMap(cliPeers);
const out = [];
if (Object.keys(members).length > 0) {
for (const peerId of Object.keys(members)) {
const member = members[peerId];
if (!member || member.authorized === false) continue;
if (peerId === selfId) continue;
out.push(buildPeerEntry(peerId, member, cliById[peerId], networkId, networkName));
}
} else {
for (const peerId of Object.keys(cliById)) {
const p = cliById[peerId];
if (!peerId || peerId.length !== 10 || peerId === selfId) continue;
if (p.role && p.role !== 'LEAF') continue;
out.push(buildPeerEntry(peerId, null, p, networkId, networkName));
}
}
out.sort((a, b) => {
if (a.online !== b.online) return a.online ? -1 : 1;
return a.displayName.localeCompare(b.displayName);
});
return out;
}
function searchPeers(query, list) {
const q = (query || '').trim().toLowerCase();
if (!q) return list;
return list.filter(p =>
p.displayName.toLowerCase().includes(q) ||
p.peerId.includes(q) ||
(p.networkName || '').toLowerCase().includes(q) ||
(p.networkId || '').includes(q) ||
(p.controllerName || '').toLowerCase().includes(q) ||
(p.role || '').toLowerCase().includes(q) ||
(p.path || '').toLowerCase().includes(q) ||
(p.ip || '').toLowerCase().includes(q)
);
}
function normalizeNetworkId(input) {
const id = String(input || '').replace(/[^0-9a-fA-F]/g, '').toLowerCase();
return id.length === 16 ? id : '';
}
async function joinNetwork(nwid) {
const id = normalizeNetworkId(nwid);
if (!id) {
throw new Error('Network ID must be 16 hexadecimal characters');
}
return BackOneAuth.apiSendFirst(API.join(id), 'POST', {});
}
async function leaveNetwork(nwid) {
const id = normalizeNetworkId(nwid);
if (!id) {
throw new Error('Network ID must be 16 hexadecimal characters');
}
return BackOneAuth.apiSendFirst(API.leave(id), 'DELETE');
}
return {
loadCliPeers,
loadNetworks,
loadNetwork,
loadCoreStatus,
countLeafPeers,
loadPeersForNetwork,
getLastNetworkId,
setLastNetworkId,
joinNetwork,
leaveNetwork,
normalizeNetworkId,
searchPeers,
peerIsOnline,
peerLinkLabel
};
})();
+151
View File
@@ -0,0 +1,151 @@
window.BackOnePeerPicker = function PeerPicker(container, options) {
options = options || {};
const onSelect = options.onSelect || (() => {});
const onNetworkChange = options.onNetworkChange || (() => {});
const onAction = options.onAction || null;
const actionLabel = options.actionLabel || 'Select';
const showActionButton = options.showActionButton !== false;
const useSharedNetwork = options.useSharedNetwork !== false;
let selectedPeer = null;
let peers = [];
let currentNetwork = null;
let unsub = null;
container.innerHTML = `
<div class="peer-picker">
<div class="peer-picker-core">BackOne core · same network as <code>backone-cli</code></div>
<div id="pp-network-banner" class="peer-network-banner"></div>
<label class="peer-picker-row">Search
<input id="pp-search" type="search" placeholder="Name or peer ID" />
</label>
<ul id="pp-list" class="peer-list"></ul>
<div id="pp-selected" class="peer-selected"></div>
${showActionButton ? `<button id="pp-action" type="button" disabled>${actionLabel}</button>` : ''}
</div>`;
const netBanner = container.querySelector('#pp-network-banner');
const searchInput = container.querySelector('#pp-search');
const listEl = container.querySelector('#pp-list');
const selectedEl = container.querySelector('#pp-selected');
const actionBtn = container.querySelector('#pp-action');
function networkLabel(n) {
if (!n) return '';
return n.name ? `${n.name} (${n.id})` : n.id;
}
function renderNetworkBanner() {
if (!useSharedNetwork) return;
const id = BackOneNetworkSession.getNetworkId();
const core = BackOneNetworkSession.getCoreStatus();
if (!core.online) {
netBanner.innerHTML = '<span class="peer-network-warn">BackOne offline — peers from <code>backone-cli</code> unavailable</span>';
return;
}
if (!currentNetwork || !id) {
netBanner.innerHTML = '<span class="peer-network-warn">No network — core reports ' + core.networkCount + ' joined (use header bar or <code>backone-cli listnetworks</code>)</span>';
return;
}
netBanner.textContent = `Network: ${networkLabel(currentNetwork)} · ${peers.length} contact(s) · ${core.leafPeerCount} VL1 peer(s) from core`;
}
function renderList() {
const q = searchInput.value;
const filtered = BackOnePeerDirectory.searchPeers(q, peers);
const netTag = currentNetwork ? escapeHtml(currentNetwork.name || currentNetwork.id) : '';
listEl.innerHTML = filtered.map(p => {
const lat = p.latency != null ? `${p.latency}ms` : '—';
const meta = [p.role || 'LEAF', lat, p.link || ''].filter(Boolean).join(' · ');
const ip = p.ip ? ` · ${escapeHtml(p.ip)}` : '';
return `
<li class="peer-item ${p.online ? 'online' : 'offline'} ${selectedPeer && selectedPeer.peerId === p.peerId ? 'active' : ''}" data-id="${p.peerId}">
<span class="peer-dot" title="${p.online ? 'Online' : 'Offline'}"></span>
<span class="peer-main">
<strong class="peer-name">${escapeHtml(p.displayName)}</strong>
<small class="peer-sub">${escapeHtml(p.peerId)} · ${escapeHtml(meta)}${netTag ? ' · ' + netTag : ''}${ip}</small>
</span>
<button type="button" class="peer-edit" title="Edit nickname">✎</button>
</li>`;
}).join('') || '<li class="peer-empty">No peers on the active network — check <code>backone-cli peers</code> and controller members</li>';
listEl.querySelectorAll('.peer-item').forEach(li => {
li.addEventListener('click', (ev) => {
if (ev.target.classList.contains('peer-edit')) return;
const id = li.dataset.id;
selectedPeer = peers.find(x => x.peerId === id) || null;
if (selectedPeer) BackOneNetworkSession.setSelectedPeer(selectedPeer);
renderList();
renderSelected();
if (selectedPeer) onSelect(selectedPeer, currentNetwork);
});
const editBtn = li.querySelector('.peer-edit');
if (editBtn) {
editBtn.addEventListener('click', async (ev) => {
ev.stopPropagation();
const id = li.dataset.id;
const peer = peers.find(x => x.peerId === id);
if (!peer) return;
const nick = prompt('Nickname for ' + peer.peerId, peer.nickname || peer.controllerName || '');
if (nick === null) return;
await BackOneContacts.setNickname(peer.peerId, nick);
BackOneContacts.invalidate();
await refreshPeers();
});
}
});
}
function renderSelected() {
if (!selectedPeer) {
selectedEl.textContent = '';
if (actionBtn) actionBtn.disabled = true;
return;
}
const net = currentNetwork ? networkLabel(currentNetwork) : BackOneNetworkSession.getNetworkId();
selectedEl.innerHTML = `Selected: <strong>${escapeHtml(selectedPeer.displayName)}</strong> (${escapeHtml(selectedPeer.peerId)})${net ? ` on <em>${escapeHtml(net)}</em>` : ''}`;
if (actionBtn) actionBtn.disabled = !BackOneNetworkSession.getNetworkId();
}
function escapeHtml(s) {
return String(s).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
}
async function refreshPeers() {
currentNetwork = BackOneNetworkSession.getActiveNetwork();
if (useSharedNetwork && BackOneNetworkSession.getNetworkId() && !currentNetwork) {
currentNetwork = await BackOneNetworkSession.refresh();
}
renderNetworkBanner();
const nwid = BackOneNetworkSession.getNetworkId();
await BackOneContacts.load();
peers = nwid ? await BackOnePeerDirectory.loadPeersForNetwork(nwid) : [];
const lastPeerId = BackOneNetworkSession.getSelectedPeerId();
if (lastPeerId) {
selectedPeer = peers.find(p => p.peerId === lastPeerId) || BackOneNetworkSession.getSelectedPeer();
}
renderList();
renderSelected();
onNetworkChange(currentNetwork, peers);
}
searchInput.addEventListener('input', renderList);
if (actionBtn) {
actionBtn.addEventListener('click', () => {
if (selectedPeer && onAction) onAction(selectedPeer, currentNetwork);
});
}
if (useSharedNetwork) {
unsub = BackOneNetworkSession.subscribe(() => refreshPeers());
}
refreshPeers();
return {
getSelectedPeer: () => selectedPeer,
getSelectedNetwork: () => currentNetwork,
refresh: refreshPeers,
destroy: () => { if (unsub) unsub(); }
};
};
+53
View File
@@ -0,0 +1,53 @@
body { font-family: system-ui, sans-serif; margin: 0; background: #0f1419; color: #e7ecf1; }
header { display: flex; align-items: center; gap: 1rem; padding: 1rem; background: #1a2332; border-bottom: 1px solid #2d3a4d; flex-wrap: wrap; }
header h1 { margin: 0; font-size: 1.25rem; }
.network-bar { display: flex; align-items: center; gap: 0.5rem; flex-wrap: wrap; flex: 1; min-width: 280px; }
.network-bar-label { font-size: 0.85rem; color: #8b9bb4; }
.network-bar select, .network-bar input { background: #0f1419; color: #e7ecf1; border: 1px solid #2d3a4d; border-radius: 4px; padding: 0.35rem 0.5rem; }
.network-bar input { width: 10rem; }
.network-bar button { background: #2d5a8a; color: #fff; border: none; padding: 0.35rem 0.65rem; border-radius: 4px; cursor: pointer; }
.network-bar-status { font-size: 0.8rem; color: #6b8cae; flex: 1 1 100%; }
.auth-token-bar { display: flex; align-items: center; gap: 0.5rem; flex: 1 1 100%; }
.auth-token-hidden { display: none; }
.auth-token-label { font-size: 0.85rem; color: #8b9bb4; }
.auth-token-bar input { background: #0f1419; color: #e7ecf1; border: 1px solid #2d3a4d; border-radius: 4px; padding: 0.35rem 0.5rem; width: 16rem; }
.auth-token-bar button { background: #2d5a8a; color: #fff; border: none; padding: 0.35rem 0.65rem; border-radius: 4px; cursor: pointer; }
.core-dot { display: inline-block; width: 8px; height: 8px; border-radius: 50%; margin-right: 0.35rem; vertical-align: middle; }
.core-dot.online { background: #2ecc71; }
.core-dot.offline { background: #e74c3c; }
nav { display: flex; gap: 0.5rem; flex-wrap: wrap; }
nav button { background: #2d5a8a; color: #fff; border: none; padding: 0.5rem 1rem; border-radius: 6px; cursor: pointer; }
nav button.active { background: #4a90d9; }
main { padding: 1.5rem; }
.ringing-hidden { display: none; }
#ringing-popup { position: fixed; inset: 0; background: rgba(0,0,0,0.6); display: flex; align-items: center; justify-content: center; z-index: 1000; }
#ringing-popup.ringing-hidden { display: none; }
.ringing-card { background: #1a2332; padding: 2rem; border-radius: 12px; text-align: center; min-width: 280px; }
.ringing-card button { margin: 0.5rem; padding: 0.6rem 1.2rem; border: none; border-radius: 6px; cursor: pointer; }
.ringing-accept { background: #2ecc71; color: #000; }
.ringing-reject { background: #e74c3c; color: #fff; }
.peer-picker { max-width: 480px; }
.peer-picker-core { font-size: 0.75rem; color: #6b8cae; margin-bottom: 0.5rem; text-transform: uppercase; letter-spacing: 0.04em; }
.peer-network-banner { font-size: 0.85rem; color: #8b9bb4; margin-bottom: 0.75rem; padding: 0.4rem 0.5rem; background: #1a2332; border-radius: 4px; }
.peer-network-warn { color: #e6a23c; }
.peer-picker-row { display: block; margin-bottom: 0.75rem; }
.peer-picker-row select, .peer-picker-row input { width: 100%; margin-top: 0.25rem; padding: 0.4rem; background: #1a2332; color: #e7ecf1; border: 1px solid #2d3a4d; border-radius: 4px; }
.peer-list { list-style: none; padding: 0; margin: 0.5rem 0; max-height: 280px; overflow: auto; border: 1px solid #2d3a4d; border-radius: 6px; }
.peer-item { display: flex; align-items: center; gap: 0.5rem; padding: 0.6rem 0.75rem; cursor: pointer; border-bottom: 1px solid #2d3a4d; }
.peer-item:last-child { border-bottom: none; }
.peer-item:hover, .peer-item.active { background: #243044; }
.peer-dot { width: 8px; height: 8px; border-radius: 50%; background: #666; flex-shrink: 0; }
.peer-item.online .peer-dot { background: #2ecc71; }
.peer-main { flex: 1; min-width: 0; }
.peer-name { display: block; }
.peer-sub { color: #8b9bb4; font-size: 0.8rem; }
.peer-edit { background: transparent; border: none; color: #8b9bb4; cursor: pointer; padding: 0.25rem; }
.peer-empty { padding: 1rem; color: #8b9bb4; }
.peer-selected { margin: 0.5rem 0; min-height: 1.25rem; }
.chat-layout { display: flex; gap: 1rem; min-height: 400px; }
.chat-peers { flex: 0 0 280px; }
.chat-thread-panel { flex: 1; display: flex; flex-direction: column; }
.chat-thread-header { padding-bottom: 0.5rem; border-bottom: 1px solid #2d3a4d; margin-bottom: 0.5rem; }
#chat-thread { flex: 1; overflow: auto; border: 1px solid #2d3a4d; padding: 0.5rem; border-radius: 6px; margin-bottom: 0.5rem; }
.video-remote-label { position: absolute; bottom: 4px; left: 4px; background: rgba(0,0,0,0.6); padding: 2px 6px; font-size: 0.75rem; border-radius: 4px; }
.video-wrap { position: relative; display: inline-block; }
+30
View File
@@ -0,0 +1,30 @@
{
"id": "chat",
"name": "Chat P2P",
"version": "1.17.1",
"requires": ["comms-core"],
"backoneCore": {
"apis": [
"/app/api/peers",
"/app/api/networks",
"POST /app/api/network/{nwid}",
"DELETE /app/api/network/{nwid}",
"/app/api/network/{nwid}",
"/app/api/network/{nwid}/members",
"/app/api/authtoken",
"/app/api/contacts"
]
},
"backend": {
"routes": ["/controller/comms/network"],
"websocketEvents": ["chat_message", "typing"]
},
"frontend": {
"entry": "/app/addons/chat/index.js",
"navLabel": "Chat",
"icon": "chat"
},
"settings": {
"attachmentMaxBytes": 10485760
}
}
+160
View File
@@ -0,0 +1,160 @@
window.BackOneAddons = window.BackOneAddons || {};
window.BackOneAddons.chat = function chatAddon() {
const messagesByPeer = {};
let selectedPeer = null;
let root = null;
function getThread(peerId) {
if (!messagesByPeer[peerId]) messagesByPeer[peerId] = [];
return messagesByPeer[peerId];
}
function networkLabel() {
const n = BackOneNetworkSession.getActiveNetwork();
return n ? (n.name || n.id) : BackOneNetworkSession.getNetworkId();
}
function updateConnectionBar() {
const bar = root?.querySelector('#chat-conn-bar');
if (!bar || !window.BackOneCommLayout) return;
bar.innerHTML = BackOneCommLayout.connectionBarHtml(
selectedPeer,
BackOneNetworkSession.getActiveNetwork()
);
}
function renderHeader() {
const header = root?.querySelector('#chat-header');
if (!header) return;
const net = networkLabel();
if (!selectedPeer) {
header.textContent = net ? `Select a contact on ${net}` : 'Join a network in the header bar';
return;
}
header.innerHTML = `<strong>${selectedPeer.displayName || selectedPeer.peerId}</strong> <small>${selectedPeer.peerId}${net ? ' · ' + net : ''}</small>`;
updateConnectionBar();
}
return {
mount(container) {
root = container;
container.innerHTML = `
<div class="comm-layout">
<aside class="comm-sidebar">
<div class="comm-sidebar-head">
<h2>Chat P2P</h2>
<p class="comm-sidebar-sub">BackOne core · same network as backone-cli</p>
</div>
<div class="comm-sidebar-body">
<div id="chat-peer-picker"></div>
</div>
</aside>
<div class="comm-main">
<div class="comm-main-head">
<div class="comm-breadcrumb">Chat P2P &gt; <strong id="chat-breadcrumb-peer">Select contact</strong></div>
</div>
<div class="chat-thread-panel">
<div class="chat-thread-header" id="chat-header">Select a contact</div>
<div id="chat-thread"></div>
<div class="chat-compose">
<textarea id="chat-input" rows="2" placeholder="Type a message" disabled></textarea>
<div class="chat-compose-row">
<input type="file" id="chat-file" accept="image/*,.pdf,.doc,.docx,.txt" disabled />
<button type="button" id="chat-send" class="btn-primary" disabled>Send</button>
</div>
</div>
</div>
</div>
</div>
<div id="chat-conn-bar" class="comm-connection-bar"></div>`;
const thread = container.querySelector('#chat-thread');
const input = container.querySelector('#chat-input');
const fileInput = container.querySelector('#chat-file');
const sendBtn = container.querySelector('#chat-send');
function updateInputs() {
const ok = !!BackOneNetworkSession.getNetworkId() && !!selectedPeer;
input.disabled = !ok;
fileInput.disabled = !ok;
sendBtn.disabled = !ok;
renderHeader();
const bc = root.querySelector('#chat-breadcrumb-peer');
if (bc) bc.textContent = selectedPeer ? (selectedPeer.displayName || selectedPeer.peerId) : 'Select contact';
}
const render = () => {
if (!selectedPeer) {
thread.innerHTML = '<p class="peer-empty">Choose a peer from the list</p>';
return;
}
const messages = getThread(selectedPeer.peerId);
thread.innerHTML = messages.map(m =>
`<div><strong>${m.senderName || m.sender || 'me'}:</strong> ${m.body || ''}${m.file ? ` [${m.file}]` : ''}</div>`
).join('');
thread.scrollTop = thread.scrollHeight;
};
BackOnePeerPicker(container.querySelector('#chat-peer-picker'), {
layout: 'sidebar',
showActionButton: false,
onSelect(peer) {
selectedPeer = peer;
updateInputs();
render();
},
onNetworkChange() {
selectedPeer = null;
updateInputs();
render();
}
});
BackOneNetworkSession.subscribe(() => {
updateInputs();
render();
});
sendBtn.onclick = async () => {
const networkId = BackOneNetworkSession.getNetworkId();
if (!selectedPeer || !networkId) return;
const body = input.value.trim();
const selfName = 'me';
if (fileInput.files.length) {
const fd = new FormData();
fd.append('file', fileInput.files[0]);
fd.append('networkId', networkId);
await BackOneAuth.fetchWithAuth(`/controller/comms/network/${networkId}/chat/attachment`, {
method: 'POST',
body: fd
}).catch(() => {});
getThread(selectedPeer.peerId).push({
sender: selfName,
senderName: selfName,
body: '',
file: fileInput.files[0].name
});
fileInput.value = '';
}
if (body) {
await BackOneAuth.fetchWithAuth(`/controller/comms/network/${networkId}/chat`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ body, targetId: selectedPeer.peerId, networkId })
}).catch(() => {});
getThread(selectedPeer.peerId).push({
sender: selfName,
senderName: selfName,
body
});
input.value = '';
}
render();
};
updateInputs();
render();
}
};
};
+10
View File
@@ -0,0 +1,10 @@
{
"id": "comms-core",
"name": "Comms Core",
"version": "1.17.1",
"requires": [],
"backend": {
"routes": ["/controller/comms"],
"websocketEvents": ["presence", "incoming_call", "call_state", "chat_message"]
}
}
+32
View File
@@ -0,0 +1,32 @@
-- BackOne comms-core addon schema (PostgreSQL)
CREATE TABLE IF NOT EXISTS backone_calls (
id TEXT PRIMARY KEY,
network_id TEXT NOT NULL,
caller_id TEXT NOT NULL,
callee_id TEXT NOT NULL,
call_type TEXT NOT NULL CHECK (call_type IN ('voice', 'video')),
state TEXT NOT NULL,
sdp_offer TEXT,
sdp_answer TEXT,
created_at BIGINT NOT NULL
);
CREATE TABLE IF NOT EXISTS backone_chat_messages (
id TEXT PRIMARY KEY,
network_id TEXT NOT NULL,
sender_id TEXT NOT NULL,
body TEXT,
attachment_id TEXT,
created_at BIGINT NOT NULL
);
CREATE TABLE IF NOT EXISTS backone_attachments (
id TEXT PRIMARY KEY,
network_id TEXT NOT NULL,
sender_id TEXT NOT NULL,
filename TEXT NOT NULL,
mime_type TEXT NOT NULL,
size_bytes BIGINT NOT NULL,
storage_path TEXT NOT NULL,
created_at BIGINT NOT NULL
);
+10
View File
@@ -0,0 +1,10 @@
{
"id": "updater",
"name": "BackOne Updater",
"version": "1.17.1",
"requires": [],
"backend": {
"routes": [],
"websocketEvents": []
}
}
+129
View File
@@ -0,0 +1,129 @@
#!/bin/bash
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
if [ -f "${SCRIPT_DIR}/platform-paths.sh" ]; then
# shellcheck source=platform-paths.sh
. "${SCRIPT_DIR}/platform-paths.sh"
else
# shellcheck source=../../pkg/common/platform-paths.sh
. "${SCRIPT_DIR}/../../pkg/common/platform-paths.sh"
fi
backone_resolve_paths
HOME_DIR="${BACKONE_HOME}"
STAMP="${HOME_DIR}/.installed-version"
LOG_DIR="${BACKONE_LOG_DIR:-/var/log/backone}"
LOG_FILE="${LOG_DIR}/updater.log"
MIGRATIONS="${BACKONE_ADDONS_DIR}/updater/migrations"
LEGACY_HOME="/var/lib/zerotier-one"
log() {
mkdir -p "$LOG_DIR"
echo "$(date -Iseconds) $*" | tee -a "$LOG_FILE"
}
read_version() {
if [ -f "$STAMP" ]; then
cat "$STAMP"
else
echo '{"package":"backone","version":"0.0.0","schemaVersion":0}'
fi
}
write_version() {
local version="${1:-$(cat debian/version 2>/dev/null || echo 1.15.0)}"
mkdir -p "$HOME_DIR"
cat > "$STAMP" <<EOF
{"package":"backone","version":"${version}","schemaVersion":2,"updatedAt":"$(date -Iseconds)"}
EOF
chown backone:backone "$STAMP" 2>/dev/null || true
}
run_migrations() {
local scope="${1:-all}"
[ -d "$MIGRATIONS" ] || return 0
for script in "$MIGRATIONS"/*; do
[ -f "$script" ] || continue
case "$(basename "$script")" in
001_*)
[ "$scope" = "all" ] || [ "$scope" = "core" ] || continue
;;
002_*)
[ "$scope" = "all" ] || [ "$scope" = "core" ] || continue
;;
003_*)
[ "$scope" = "all" ] || [ "$scope" = "core" ] || continue
;;
*)
if [ "$scope" != "all" ] && [ "$scope" != "$ADDON_SCOPE" ]; then
continue
fi
;;
esac
log "Running migration: $(basename "$script")"
BACKONE_HOME="$HOME_DIR" bash "$script" || log "Migration warning: $(basename "$script")"
done
}
migrate_zerotier() {
if [ -d "$LEGACY_HOME" ] && [ ! -f "$HOME_DIR/identity.public" ]; then
log "Migrating $LEGACY_HOME -> $HOME_DIR"
mkdir -p "$HOME_DIR"
cp -a "$LEGACY_HOME/." "$HOME_DIR/" 2>/dev/null || true
ln -sfn "$HOME_DIR" "$LEGACY_HOME" 2>/dev/null || true
fi
}
rebuild_manifest() {
local registry="${HOME_DIR}/addons.d/enabled.json"
mkdir -p "${HOME_DIR}/addons.d"
if [ ! -f "$registry" ]; then
echo '[]' > "$registry"
fi
local script_dir manifest_script
script_dir="$(cd "$(dirname "$(readlink -f "${BASH_SOURCE[0]}")")" && pwd)"
manifest_script="${script_dir}/rebuild-addon-manifest.sh"
if [ ! -x "$manifest_script" ]; then
echo "backone-updater: ${manifest_script} not found" >&2
exit 1
fi
local count
count="$(BACKONE_HOME="$HOME_DIR" BACKONE_ADDONS_DIR="${BACKONE_ADDONS_DIR}" bash "$manifest_script")"
log "Wrote ${HOME_DIR}/app/addons/manifest.json (${count} UI addons)"
}
cmd_migrate() {
local scope="all"
ADDON_SCOPE=""
while [ $# -gt 0 ]; do
case "$1" in
--core) scope="core"; shift ;;
--addon) ADDON_SCOPE="$2"; scope="addon"; shift 2 ;;
--dry-run)
log "Dry run: would migrate scope=$scope addon=$ADDON_SCOPE"
ls -1 "$MIGRATIONS" 2>/dev/null || true
exit 0
;;
*) shift ;;
esac
done
migrate_zerotier
run_migrations "$scope"
write_version
rebuild_manifest
log "Migration complete"
}
cmd_status() {
echo "Installed: $(read_version)"
echo "Package version: $(cat "${BACKONE_ADDONS_DIR}/comms-core/addon.json" 2>/dev/null | grep '"version"' | head -1 || cat debian/version 2>/dev/null || echo unknown)"
echo "Addons registry: ${HOME_DIR}/addons.d/enabled.json"
}
case "${1:-}" in
migrate) shift; cmd_migrate "$@" ;;
status) cmd_status ;;
rebuild-manifest) rebuild_manifest ;;
*) echo "Usage: backone-updater migrate [--core|--addon ID] [--dry-run] | status | rebuild-manifest" >&2; exit 1 ;;
esac
+55
View File
@@ -0,0 +1,55 @@
#!/bin/bash
# Deep-merge JSON into /var/lib/backone/local.conf (idempotent).
set -euo pipefail
HOME_DIR="${BACKONE_HOME:-/var/lib/backone}"
CONF="${HOME_DIR}/local.conf"
PATCH="${1:-}"
if [ -z "$PATCH" ] && [ ! -t 0 ]; then
PATCH="$(cat)"
fi
if [ -z "$PATCH" ]; then
echo "Usage: merge-local-conf.sh '<json>' or pipe JSON on stdin" >&2
exit 1
fi
python3 - "$CONF" "$PATCH" <<'PY'
import json
import os
import sys
conf_path, patch_raw = sys.argv[1], sys.argv[2]
def deep_merge(base, patch):
if not isinstance(base, dict) or not isinstance(patch, dict):
return patch
out = dict(base)
for k, v in patch.items():
if k in out and isinstance(out[k], dict) and isinstance(v, dict):
out[k] = deep_merge(out[k], v)
else:
out[k] = v
return out
patch = json.loads(patch_raw)
current = {}
if os.path.isfile(conf_path):
with open(conf_path, encoding="utf-8") as f:
try:
current = json.load(f)
except json.JSONDecodeError:
current = {}
if not isinstance(current, dict):
current = {}
merged = deep_merge(current, patch)
os.makedirs(os.path.dirname(conf_path), exist_ok=True)
with open(conf_path, "w", encoding="utf-8") as f:
json.dump(merged, f, indent=2)
f.write("\n")
PY
chown backone:backone "$CONF" 2>/dev/null || true
chmod 644 "$CONF" 2>/dev/null || true
+12
View File
@@ -0,0 +1,12 @@
#!/bin/bash
set -euo pipefail
BACKONE_HOME="${BACKONE_HOME:-/var/lib/backone}"
LEGACY="/var/lib/zerotier-one"
if [ -d "$LEGACY" ] && [ ! -f "$BACKONE_HOME/identity.public" ]; then
mkdir -p "$BACKONE_HOME"
cp -an "$LEGACY/." "$BACKONE_HOME/" 2>/dev/null || cp -a "$LEGACY/." "$BACKONE_HOME/"
fi
if ! getent passwd backone >/dev/null 2>&1 && getent passwd zerotier-one >/dev/null 2>&1; then
usermod -l backone -d "$BACKONE_HOME" zerotier-one 2>/dev/null || true
fi
chown -R backone:backone "$BACKONE_HOME" 2>/dev/null || true
+8
View File
@@ -0,0 +1,8 @@
#!/bin/bash
set -euo pipefail
BACKONE_HOME="${BACKONE_HOME:-/var/lib/backone}"
mkdir -p "$BACKONE_HOME/addons.d" "$BACKONE_HOME/app/addons"
if [ ! -f "$BACKONE_HOME/addons.d/enabled.json" ]; then
echo '[]' > "$BACKONE_HOME/addons.d/enabled.json"
fi
chown -R backone:backone "$BACKONE_HOME/addons.d" "$BACKONE_HOME/app" 2>/dev/null || true
@@ -0,0 +1,7 @@
#!/bin/bash
set -euo pipefail
BACKONE_HOME="${BACKONE_HOME:-/var/lib/backone}"
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
if [ -x "${SCRIPT_DIR}/merge-local-conf.sh" ]; then
BACKONE_HOME="$BACKONE_HOME" bash "${SCRIPT_DIR}/merge-local-conf.sh" '{"settings":{"enableWebServer":true}}'
fi
+60
View File
@@ -0,0 +1,60 @@
#!/bin/bash
# Writes $BACKONE_HOME/app/addons/manifest.json from installed addon.json files.
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
if [ -f "${SCRIPT_DIR}/platform-paths.sh" ]; then
# shellcheck source=platform-paths.sh
. "${SCRIPT_DIR}/platform-paths.sh"
else
# shellcheck source=../../pkg/common/platform-paths.sh
. "${SCRIPT_DIR}/../../pkg/common/platform-paths.sh"
fi
backone_resolve_paths
HOME_DIR="${BACKONE_HOME}"
ADDONS_DIR="${BACKONE_ADDONS_DIR}"
MANIFEST="${HOME_DIR}/app/addons/manifest.json"
mkdir -p "${HOME_DIR}/app/addons" "${HOME_DIR}/addons.d"
if ! command -v python3 >/dev/null 2>&1; then
echo "rebuild-addon-manifest: python3 required" >&2
exit 1
fi
python3 - "$ADDONS_DIR" "$MANIFEST" <<'PY'
import json
import os
import sys
addons_dir, manifest_path = sys.argv[1], sys.argv[2]
out = []
if os.path.isdir(addons_dir):
for name in sorted(os.listdir(addons_dir)):
path = os.path.join(addons_dir, name, "addon.json")
if not os.path.isfile(path):
continue
with open(path, encoding="utf-8") as f:
m = json.load(f)
if "frontend" not in m:
continue
item = {
"id": m["id"],
"name": m.get("name", m["id"]),
"version": m.get("version", "0.0.0"),
"frontend": m["frontend"],
}
if "requires" in m:
item["requires"] = m["requires"]
out.append(item)
os.makedirs(os.path.dirname(manifest_path), exist_ok=True)
with open(manifest_path, "w", encoding="utf-8") as f:
json.dump(out, f, indent=2)
f.write("\n")
print(len(out))
PY
chown backone:backone "$MANIFEST" 2>/dev/null || true
chmod 644 "$MANIFEST" 2>/dev/null || true
+53
View File
@@ -0,0 +1,53 @@
#!/bin/sh
# Keep ~/.backOneOneAuthToken and /var/lib/backone/authtoken.secret in sync (same as backone-cli).
set -e
BACKONE_HOME="${BACKONE_HOME:-/var/lib/backone}"
SECRET="${BACKONE_HOME}/authtoken.secret"
trim_token() {
tr -d '\n\r \t' < "$1"
}
sync_for_user() {
user="$1"
home="$2"
[ -n "$user" ] && [ -n "$home" ] && [ -d "$home" ] || return 0
user_token="${home}/.backOneOneAuthToken"
if [ -f "$user_token" ] && [ -s "$user_token" ]; then
ut="$(trim_token "$user_token")"
[ -n "$ut" ] || return 0
st=""
if [ -f "$SECRET" ]; then
st="$(trim_token "$SECRET" 2>/dev/null || true)"
fi
if [ "$ut" != "$st" ]; then
printf '%s' "$ut" > "$SECRET"
chown backone:backone "$SECRET" 2>/dev/null || true
chmod 0600 "$SECRET" 2>/dev/null || true
echo "Synced ${SECRET} from ${user_token}"
fi
return 0
fi
if [ -f "$SECRET" ]; then
install -o "$user" -g "$(id -gn "$user" 2>/dev/null || echo "$user")" -m 0600 "$SECRET" "$user_token" 2>/dev/null \
|| cp "$SECRET" "$user_token"
chown "$user":"$(id -gn "$user" 2>/dev/null || echo "$user")" "$user_token" 2>/dev/null || true
chmod 0600 "$user_token" 2>/dev/null || true
echo "Installed ${user_token} from ${SECRET}"
fi
}
if [ -n "${SUDO_USER:-}" ] && [ "$SUDO_USER" != "root" ]; then
home="$(getent passwd "$SUDO_USER" | cut -d: -f6)"
sync_for_user "$SUDO_USER" "$home"
fi
if [ -n "${USER:-}" ] && [ "$USER" != "root" ] && [ "$USER" != "${SUDO_USER:-}" ]; then
home="$(getent passwd "$USER" | cut -d: -f6)"
sync_for_user "$USER" "$home"
fi
exit 0
+30
View File
@@ -0,0 +1,30 @@
{
"id": "video",
"name": "Video Call P2P",
"version": "1.17.1",
"requires": ["comms-core", "voice"],
"backoneCore": {
"apis": [
"/app/api/peers",
"/app/api/networks",
"POST /app/api/network/{nwid}",
"DELETE /app/api/network/{nwid}",
"/app/api/network/{nwid}",
"/app/api/network/{nwid}/members",
"/app/api/authtoken",
"/app/api/contacts"
]
},
"backend": {
"routes": ["/controller/comms/call"],
"websocketEvents": ["incoming_call", "call_state"]
},
"frontend": {
"entry": "/app/addons/video/index.js",
"navLabel": "Video",
"icon": "video"
},
"settings": {
"enableVideoCall": true
}
}
+206
View File
@@ -0,0 +1,206 @@
window.BackOneAddons = window.BackOneAddons || {};
window.BackOneAddons.video = function videoAddon() {
let pc = null;
let picker = null;
let selectedPeer = null;
let inCall = false;
let localStream = null;
let micEnabled = true;
let camEnabled = true;
let root = null;
function networkLabel() {
const n = BackOneNetworkSession.getActiveNetwork();
return n ? (n.name || n.id) : BackOneNetworkSession.getNetworkId();
}
function updateConnectionBar() {
const bar = root?.querySelector('#video-conn-bar');
if (!bar || !window.BackOneCommLayout) return;
bar.innerHTML = BackOneCommLayout.connectionBarHtml(
selectedPeer,
BackOneNetworkSession.getActiveNetwork()
);
}
function updateHeadStatus() {
const statusEl = root?.querySelector('#video-head-status');
const breadcrumb = root?.querySelector('#video-breadcrumb-peer');
if (!statusEl) return;
if (inCall && selectedPeer) {
statusEl.innerHTML = `<span class="connected">Connected</span> · ${selectedPeer.latency != null ? selectedPeer.latency + 'ms' : '—'}`;
if (breadcrumb) breadcrumb.textContent = selectedPeer.peerId;
} else if (selectedPeer) {
statusEl.textContent = 'Ready';
if (breadcrumb) breadcrumb.textContent = selectedPeer.peerId;
} else {
statusEl.textContent = '';
if (breadcrumb) breadcrumb.textContent = '—';
}
}
function updateCallButtons() {
const callBtn = root?.querySelector('#video-call');
if (callBtn) callBtn.disabled = !selectedPeer || !BackOneNetworkSession.getNetworkId() || inCall;
const selectedLabel = root?.querySelector('#video-selected-label');
if (selectedLabel) {
selectedLabel.innerHTML = selectedPeer
? `Selected: <strong>${selectedPeer.peerId.slice(0, 10)}…</strong>`
: 'Selected: <strong>—</strong>';
}
updateHeadStatus();
updateConnectionBar();
}
function showPlaceholder(show) {
const ph = root?.querySelector('#video-placeholder');
const remote = root?.querySelector('#video-remote');
if (ph) ph.style.display = show ? 'block' : 'none';
if (remote) remote.style.display = show ? 'none' : 'block';
}
function hangUp() {
if (pc) { pc.close(); pc = null; }
if (localStream) {
localStream.getTracks().forEach(t => t.stop());
localStream = null;
}
const local = root?.querySelector('#video-local');
const remote = root?.querySelector('#video-remote');
if (local) local.srcObject = null;
if (remote) remote.srcObject = null;
inCall = false;
micEnabled = true;
camEnabled = true;
showPlaceholder(true);
updateCallButtons();
window.BackOneRingingPopup?.dismiss();
}
return {
mount(container) {
root = container;
container.innerHTML = `
<div class="comm-layout">
<aside class="comm-sidebar">
<div class="comm-sidebar-head">
<h2>Video Call P2P</h2>
<p class="comm-sidebar-sub">BackOne core · same network as backone-cli</p>
</div>
<div class="comm-sidebar-body">
<div id="video-picker"></div>
</div>
<div class="comm-sidebar-actions">
<div id="video-selected-label" class="comm-selected-label">Selected: <strong>—</strong></div>
<button type="button" id="video-call" class="btn-primary" disabled>Video Call</button>
<button type="button" id="video-hangup" class="btn-secondary">Hang Up</button>
</div>
</aside>
<div class="comm-main">
<div class="comm-main-head">
<div class="comm-breadcrumb">Video Call P2P &gt; <span id="video-breadcrumb-state">${inCall ? 'Connected to' : 'Select peer'}</span> <strong id="video-breadcrumb-peer">—</strong></div>
<div id="video-head-status" class="comm-head-status"></div>
</div>
<div class="comm-stage">
<div class="comm-remote-tag"><span class="dot"></span> Remote</div>
<div id="video-placeholder" class="comm-remote-placeholder">
<div class="avatar">👤</div>
<div class="peer-id" id="video-ph-peer">—</div>
<div>Remote Peer</div>
</div>
<video id="video-remote" autoplay playsinline style="display:none"></video>
<div class="video-local-pip">
<video id="video-local" autoplay muted playsinline></video>
</div>
<div class="comm-call-controls">
<button type="button" id="video-mic">Mic</button>
<button type="button" id="video-cam">Camera</button>
<button type="button" id="video-fs">Fullscreen</button>
<button type="button" id="video-hangup-stage" class="danger">Hang Up</button>
</div>
</div>
</div>
</div>
<div id="video-conn-bar" class="comm-connection-bar"></div>`;
const callBtn = container.querySelector('#video-call');
picker = BackOnePeerPicker(container.querySelector('#video-picker'), {
layout: 'sidebar',
showActionButton: false,
onSelect(peer) {
selectedPeer = peer;
const phPeer = root.querySelector('#video-ph-peer');
if (phPeer) phPeer.textContent = peer.peerId;
updateCallButtons();
},
onNetworkChange() {
if (!inCall) selectedPeer = null;
updateCallButtons();
}
});
BackOneNetworkSession.subscribe(() => updateCallButtons());
callBtn.onclick = async () => {
const peer = selectedPeer || picker.getSelectedPeer();
const networkId = BackOneNetworkSession.getNetworkId();
if (!peer || !networkId) return;
try {
pc = new RTCPeerConnection();
pc.ontrack = (ev) => {
root.querySelector('#video-remote').srcObject = ev.streams[0];
showPlaceholder(false);
};
localStream = await navigator.mediaDevices.getUserMedia({ audio: true, video: true });
root.querySelector('#video-local').srcObject = localStream;
localStream.getTracks().forEach(t => pc.addTrack(t, localStream));
const offer = await pc.createOffer();
await pc.setLocalDescription(offer);
await BackOneAuth.fetchWithAuth('/controller/comms/call', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
type: 'video',
networkId,
targetId: peer.peerId,
sdp: offer.sdp
})
});
inCall = true;
root.querySelector('#video-breadcrumb-state').textContent = 'Connected to';
updateCallButtons();
} catch (err) {
hangUp();
alert(err.message || 'Call failed');
}
};
container.querySelector('#video-hangup').onclick = hangUp;
container.querySelector('#video-hangup-stage').onclick = hangUp;
container.querySelector('#video-mic').onclick = () => {
if (!localStream) return;
micEnabled = !micEnabled;
localStream.getAudioTracks().forEach(t => { t.enabled = micEnabled; });
container.querySelector('#video-mic').textContent = micEnabled ? 'Mic' : 'Mic off';
};
container.querySelector('#video-cam').onclick = () => {
if (!localStream) return;
camEnabled = !camEnabled;
localStream.getVideoTracks().forEach(t => { t.enabled = camEnabled; });
container.querySelector('#video-cam').textContent = camEnabled ? 'Camera' : 'Camera off';
};
container.querySelector('#video-fs').onclick = () => {
const stage = container.querySelector('.comm-stage');
if (stage.requestFullscreen) stage.requestFullscreen();
};
showPlaceholder(true);
updateCallButtons();
}
};
};
+31
View File
@@ -0,0 +1,31 @@
{
"id": "voice",
"name": "Voice Call P2P",
"version": "1.17.1",
"requires": ["comms-core"],
"backoneCore": {
"apis": [
"/app/api/peers",
"/app/api/networks",
"POST /app/api/network/{nwid}",
"DELETE /app/api/network/{nwid}",
"/app/api/network/{nwid}",
"/app/api/network/{nwid}/members",
"/app/api/authtoken",
"/app/api/contacts"
]
},
"backend": {
"routes": ["/controller/comms/call"],
"websocketEvents": ["incoming_call", "call_state"]
},
"frontend": {
"entry": "/app/addons/voice/index.js",
"navLabel": "Voice",
"icon": "phone"
},
"settings": {
"enableVoiceCall": true,
"ringTimeoutSec": 30
}
}
+164
View File
@@ -0,0 +1,164 @@
window.BackOneAddons = window.BackOneAddons || {};
window.BackOneAddons.voice = function voiceAddon() {
let pc = null;
let picker = null;
let selectedPeer = null;
let inCall = false;
let localStream = null;
let micEnabled = true;
let root = null;
function updateConnectionBar() {
const bar = root?.querySelector('#voice-conn-bar');
if (!bar || !window.BackOneCommLayout) return;
bar.innerHTML = BackOneCommLayout.connectionBarHtml(
selectedPeer,
BackOneNetworkSession.getActiveNetwork()
);
}
function updateHeadStatus() {
const statusEl = root?.querySelector('#voice-head-status');
const breadcrumb = root?.querySelector('#voice-breadcrumb-peer');
if (!statusEl) return;
if (inCall && selectedPeer) {
statusEl.innerHTML = `<span class="connected">Connected</span> · ${selectedPeer.latency != null ? selectedPeer.latency + 'ms' : '—'}`;
if (breadcrumb) breadcrumb.textContent = selectedPeer.peerId;
} else if (selectedPeer) {
statusEl.textContent = 'Ready';
if (breadcrumb) breadcrumb.textContent = selectedPeer.peerId;
} else {
statusEl.textContent = '';
if (breadcrumb) breadcrumb.textContent = '—';
}
}
function updateCallButtons() {
const callBtn = root?.querySelector('#voice-call');
if (callBtn) callBtn.disabled = !selectedPeer || !BackOneNetworkSession.getNetworkId() || inCall;
const selectedLabel = root?.querySelector('#voice-selected-label');
if (selectedLabel) {
selectedLabel.innerHTML = selectedPeer
? `Selected: <strong>${selectedPeer.peerId.slice(0, 10)}…</strong>`
: 'Selected: <strong>—</strong>';
}
updateHeadStatus();
updateConnectionBar();
}
function hangUp() {
if (pc) { pc.close(); pc = null; }
if (localStream) {
localStream.getTracks().forEach(t => t.stop());
localStream = null;
}
inCall = false;
micEnabled = true;
updateCallButtons();
window.BackOneRingingPopup?.dismiss();
}
return {
mount(container) {
root = container;
container.innerHTML = `
<div class="comm-layout">
<aside class="comm-sidebar">
<div class="comm-sidebar-head">
<h2>Voice Call P2P</h2>
<p class="comm-sidebar-sub">BackOne core · same network as backone-cli</p>
</div>
<div class="comm-sidebar-body">
<div id="voice-picker"></div>
</div>
<div class="comm-sidebar-actions">
<div id="voice-selected-label" class="comm-selected-label">Selected: <strong>—</strong></div>
<button type="button" id="voice-call" class="btn-primary" disabled>Voice Call</button>
<button type="button" id="voice-hangup" class="btn-secondary">Hang Up</button>
</div>
</aside>
<div class="comm-main">
<div class="comm-main-head">
<div class="comm-breadcrumb">Voice Call P2P &gt; <span id="voice-breadcrumb-state">Select peer</span> <strong id="voice-breadcrumb-peer">—</strong></div>
<div id="voice-head-status" class="comm-head-status"></div>
</div>
<div class="comm-stage">
<div class="voice-stage-inner">
<div class="avatar">🎙</div>
<div class="peer-id" id="voice-ph-peer">—</div>
<div id="voice-stage-label">Select a peer to call</div>
</div>
<div class="comm-call-controls">
<button type="button" id="voice-mic">Mic</button>
<button type="button" id="voice-hangup-stage" class="danger">Hang Up</button>
</div>
</div>
</div>
</div>
<div id="voice-conn-bar" class="comm-connection-bar"></div>`;
const callBtn = container.querySelector('#voice-call');
const stageLabel = container.querySelector('#voice-stage-label');
picker = BackOnePeerPicker(container.querySelector('#voice-picker'), {
layout: 'sidebar',
showActionButton: false,
onSelect(peer) {
selectedPeer = peer;
root.querySelector('#voice-ph-peer').textContent = peer.peerId;
if (!inCall) stageLabel.textContent = `Ready to call ${peer.displayName || peer.peerId}`;
updateCallButtons();
},
onNetworkChange() {
if (!inCall) selectedPeer = null;
updateCallButtons();
}
});
BackOneNetworkSession.subscribe(() => updateCallButtons());
callBtn.onclick = async () => {
const peer = selectedPeer || picker.getSelectedPeer();
const networkId = BackOneNetworkSession.getNetworkId();
if (!peer || !networkId) return;
try {
pc = new RTCPeerConnection();
localStream = await navigator.mediaDevices.getUserMedia({ audio: true, video: false });
localStream.getTracks().forEach(t => pc.addTrack(t, localStream));
const offer = await pc.createOffer();
await pc.setLocalDescription(offer);
await BackOneAuth.fetchWithAuth('/controller/comms/call', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
type: 'voice',
networkId,
targetId: peer.peerId,
sdp: offer.sdp
})
});
inCall = true;
root.querySelector('#voice-breadcrumb-state').textContent = 'Connected to';
stageLabel.textContent = `In call with ${peer.displayName || peer.peerId}`;
updateCallButtons();
} catch (err) {
hangUp();
alert(err.message || 'Call failed');
}
};
container.querySelector('#voice-hangup').onclick = hangUp;
container.querySelector('#voice-hangup-stage').onclick = hangUp;
container.querySelector('#voice-mic').onclick = () => {
if (!localStream) return;
micEnabled = !micEnabled;
localStream.getAudioTracks().forEach(t => { t.enabled = micEnabled; });
container.querySelector('#voice-mic').textContent = micEnabled ? 'Mic' : 'Mic off';
};
updateCallButtons();
}
};
};
+57
View File
@@ -0,0 +1,57 @@
window.BackOneRingingPopup = {
_audio: null,
showIncomingCall({ callerName, callType, callId, onAccept, onReject }) {
const el = document.getElementById('ringing-popup');
if (!el) return;
el.classList.remove('ringing-hidden');
el.innerHTML = `
<div class="ringing-card">
<h2>Incoming ${callType} call</h2>
<p>${callerName || 'Unknown peer'}</p>
<button class="ringing-accept">Accept</button>
<button class="ringing-reject">Reject</button>
</div>`;
el.querySelector('.ringing-accept').onclick = () => { this.dismiss(); onAccept && onAccept(callId); };
el.querySelector('.ringing-reject').onclick = () => { this.dismiss(); onReject && onReject(callId); };
this.playRingtone();
if (Notification.permission === 'default') Notification.requestPermission();
if (Notification.permission === 'granted') {
new Notification(`Incoming ${callType} call`, { body: callerName || 'BackOne peer' });
}
},
dismiss() {
const el = document.getElementById('ringing-popup');
if (el) { el.classList.add('ringing-hidden'); el.innerHTML = ''; }
this.stopRingtone();
},
playRingtone() {
this.stopRingtone();
try {
this._audio = new Audio('data:audio/wav;base64,UklGRnoGAABXQVZFZm10IBAAAAABAAEAQB8AAEAfAAABAAgAZGF0YQoGAACBhYqFbF1fdJivrJBhNjVgodDbq2EcBj+a2/LDciUFLIHO8tiJNwgZaLvt559NEAxQp+PwtmMcBjiR1/LMeSwFJHfH8N2QQAoUXrTp66hVFApGn+DyvmwhBSuBzvLZiTYIG2m98OScTgwOUarm7blmHgU7k9n1unEiBC13y+/ek0EKElyx5+ytWBUIQ5zd8sFuJAUwgM/z2Ik6CBxpvfDknE4MDlGq5u25Zh4FO5PZ9br');
this._audio.loop = true;
this._audio.play().catch(() => {});
} catch (_) {}
},
stopRingtone() {
if (this._audio) { this._audio.pause(); this._audio = null; }
}
};
async function loadAddonManifest() {
const res = await fetch('/app/addons/manifest.json', { cache: 'no-store' });
if (!res.ok) {
console.warn('manifest.json fetch failed:', res.status);
return [];
}
const data = await res.json();
return Array.isArray(data) ? data : [];
}
async function loadAddonModule(entry) {
const entryPath = entry.frontend && entry.frontend.entry ? entry.frontend.entry : `/app/addons/${entry.id}/index.js`;
await import(entryPath);
const factory = window.BackOneAddons && window.BackOneAddons[entry.id];
return factory ? factory() : null;
}
window.BackOneAddonLoader = { loadAddonManifest, loadAddonModule };
+195
View File
@@ -0,0 +1,195 @@
/**
* BackOne API auth — same token as backone-cli (~/.backOneOneAuthToken / X-ZT1-Auth).
*/
window.BackOneAuth = (function () {
const LS_KEY = 'backone.authToken';
let cache = null;
function trimToken(s) {
return String(s || '').replace(/\s+/g, '');
}
function tokenFromUrl() {
try {
const params = new URLSearchParams(location.search);
const fromQuery = trimToken(params.get('auth') || params.get('token'));
if (fromQuery) return fromQuery;
const hash = (location.hash || '').replace(/^#\/?/, '');
const hashParams = new URLSearchParams(hash.includes('?') ? hash.split('?')[1] : '');
return trimToken(hashParams.get('auth') || hashParams.get('token'));
} catch (_) {
return '';
}
}
async function probeToken(token) {
if (!token) return false;
try {
const res = await fetch('/network', {
cache: 'no-store',
headers: { 'X-ZT1-Auth': token }
});
return res.ok;
} catch (_) {
return false;
}
}
async function fetchAuthtoken() {
for (const path of ['/api/backone/authtoken', '/app/api/authtoken']) {
try {
const res = await fetch(path, { cache: 'no-store' });
if (res.ok) {
const data = await res.json();
const token = trimToken(data && data.token);
if (token) return token;
}
} catch (_) {
/* try next */
}
}
return '';
}
async function resolveToken() {
const urlToken = tokenFromUrl();
if (urlToken) {
if (await probeToken(urlToken)) {
return urlToken;
}
}
const stored = trimToken(localStorage.getItem(LS_KEY));
if (stored && await probeToken(stored)) {
return stored;
}
const apiToken = await fetchAuthtoken();
if (apiToken && await probeToken(apiToken)) {
return apiToken;
}
if (stored) return stored;
if (apiToken) return apiToken;
if (urlToken) return urlToken;
return '';
}
async function getToken() {
if (cache) return cache;
cache = await resolveToken();
return cache || '';
}
async function authHeaders(extra) {
const token = await getToken();
const headers = Object.assign({}, extra || {});
if (token) {
headers['X-ZT1-Auth'] = token;
}
return headers;
}
async function fetchWithAuth(path, options) {
options = options || {};
const headers = await authHeaders(options.headers || {});
return fetch(path, Object.assign({}, options, { headers }));
}
async function apiGet(path) {
const res = await fetchWithAuth(path, { cache: 'no-store' });
if (!res.ok) throw new Error(`${path} returned ${res.status}`);
return res.json();
}
async function apiGetFirst(paths) {
let lastErr = null;
for (const path of paths) {
try {
return await apiGet(path);
} catch (err) {
lastErr = err;
}
}
throw lastErr || new Error('API request failed');
}
async function apiSend(path, method, body) {
const headers = await authHeaders(
body != null ? { 'Content-Type': 'application/json' } : {}
);
const res = await fetch(path, {
method,
headers,
body: body != null ? JSON.stringify(body) : undefined
});
if (!res.ok) {
const text = await res.text().catch(() => '');
throw new Error(text || `${method} ${path} returned ${res.status}`);
}
if (res.status === 204) {
return { result: true };
}
const text = await res.text();
if (!text) {
return { result: true };
}
try {
return JSON.parse(text);
} catch (_) {
return { result: true };
}
}
async function apiSendFirst(paths, method, body) {
let lastErr = null;
for (const path of paths) {
try {
return await apiSend(path, method, body);
} catch (err) {
lastErr = err;
}
}
throw lastErr || new Error(`${method} failed`);
}
/** Store token from ~/.backOneOneAuthToken (same as backone-cli). */
async function setUserToken(token) {
cache = trimToken(token);
if (cache) {
localStorage.setItem(LS_KEY, cache);
} else {
localStorage.removeItem(LS_KEY);
}
return cache;
}
async function validateCurrentToken() {
cache = null;
const token = await getToken();
if (!token) return false;
return probeToken(token);
}
function invalidate() {
cache = null;
}
async function init() {
return getToken();
}
return {
getToken,
authHeaders,
fetchWithAuth,
apiGet,
apiGetFirst,
apiSend,
apiSendFirst,
setUserToken,
validateCurrentToken,
invalidate,
init
};
})();
+57
View File
@@ -0,0 +1,57 @@
window.BackOneContacts = (function () {
let cache = null;
async function load() {
if (cache) return cache;
try {
const res = await BackOneAuth.fetchWithAuth('/api/backone/contacts', { cache: 'no-store' })
.catch(() => BackOneAuth.fetchWithAuth('/app/api/contacts', { cache: 'no-store' }));
if (!res.ok) {
cache = {};
return cache;
}
cache = await res.json();
if (!cache || typeof cache !== 'object') cache = {};
} catch (_) {
cache = {};
}
return cache;
}
function getDisplayName(peerId, controllerName) {
const c = cache || {};
const entry = c[peerId];
if (entry && entry.nickname) return entry.nickname;
if (controllerName) return controllerName;
return peerId;
}
function getNickname(peerId) {
const entry = (cache || {})[peerId];
return entry && entry.nickname ? entry.nickname : '';
}
async function setNickname(peerId, nickname) {
await load();
const patch = {};
patch[peerId] = { nickname: nickname || '' };
const res = await BackOneAuth.fetchWithAuth('/api/backone/contacts', {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(patch)
}).catch(() => BackOneAuth.fetchWithAuth('/app/api/contacts', {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(patch)
}));
if (!res.ok) throw new Error('Failed to save nickname');
cache = await res.json();
return cache;
}
function invalidate() {
cache = null;
}
return { load, getDisplayName, getNickname, setNickname, invalidate };
})();
+43
View File
@@ -0,0 +1,43 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>BackOne</title>
<link rel="stylesheet" href="/app/styles.css">
</head>
<body>
<header class="app-header">
<div class="app-brand">
<h1>BackOne</h1>
<span id="core-status-badge" class="core-badge offline">Offline</span>
</div>
<div id="network-bar" class="network-bar">
<label class="network-bar-label">Network
<select id="global-network"></select>
</label>
<input id="global-join-id" type="text" maxlength="16" placeholder="Join network ID" autocomplete="off" />
<button type="button" id="global-join-btn">Join</button>
<button type="button" id="global-leave-btn">Leave</button>
<span id="global-network-status" class="network-bar-status" aria-live="polite"></span>
<div id="auth-token-bar" class="auth-token-bar auth-token-hidden">
<label class="auth-token-label">Auth token
<input id="auth-token-input" type="password" autocomplete="off"
placeholder="Paste from ~/.backOneOneAuthToken" />
</label>
<button type="button" id="auth-token-save">Save</button>
</div>
</div>
<nav id="addon-nav" class="addon-nav"></nav>
</header>
<main id="addon-panel" class="addon-panel"></main>
<div id="ringing-popup" class="ringing-hidden"></div>
<script src="/app/auth-store.js"></script>
<script src="/app/contacts-store.js"></script>
<script src="/app/peer-directory.js"></script>
<script src="/app/network-session.js"></script>
<script src="/app/peer-picker.js"></script>
<script src="/app/addon-loader.js"></script>
<script src="/app/main.js"></script>
</body>
</html>
+237
View File
@@ -0,0 +1,237 @@
window.BackOneAddons = window.BackOneAddons || {};
const addonButtons = {};
let addonModules = {};
let coreRefreshTimer = null;
function hashAddonId() {
const h = (location.hash || '').replace(/^#\/?/, '').toLowerCase();
if (h === 'voice' || h === 'video' || h === 'chat') return h;
return '';
}
function activateAddon(id) {
const btn = addonButtons[id];
if (!btn) return;
btn.click();
}
function escapeHtml(s) {
return String(s).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
}
function networkLabel(n) {
if (!n) return '';
return n.name ? `${n.name} (${n.id})` : n.id;
}
function updateCoreBadge(statusEl) {
const badge = document.getElementById('core-status-badge');
const core = BackOneNetworkSession.getCoreStatus();
if (badge) {
badge.textContent = core.online ? 'Online' : 'Offline';
badge.classList.toggle('online', core.online);
badge.classList.toggle('offline', !core.online);
}
if (!statusEl) return;
if (!core.online) {
statusEl.textContent = 'Start the backone service to use networks and peers';
return;
}
const net = BackOneNetworkSession.getActiveNetwork();
const netPart = net ? networkLabel(net) : 'no network joined';
statusEl.textContent = `${netPart} · ${core.leafPeerCount} peer(s)`;
}
async function initAuthTokenBar(statusEl, onSaved) {
const bar = document.getElementById('auth-token-bar');
const input = document.getElementById('auth-token-input');
const saveBtn = document.getElementById('auth-token-save');
if (!bar || !input || !saveBtn) return;
async function refreshAuthUi() {
const ok = await BackOneAuth.validateCurrentToken();
bar.classList.toggle('auth-token-hidden', ok);
if (!ok && !input.value) {
input.placeholder = 'Paste from ~/.backOneOneAuthToken';
}
return ok;
}
saveBtn.addEventListener('click', async () => {
const token = input.value.trim();
if (!token) {
if (statusEl) statusEl.textContent = 'Paste the token from ~/.backOneOneAuthToken';
return;
}
saveBtn.disabled = true;
await BackOneAuth.setUserToken(token);
BackOneAuth.invalidate();
const ok = await BackOneAuth.validateCurrentToken();
if (!ok) {
if (statusEl) statusEl.textContent = 'Token rejected — check ~/.backOneOneAuthToken';
saveBtn.disabled = false;
return;
}
input.value = '';
bar.classList.add('auth-token-hidden');
if (typeof onSaved === 'function') await onSaved();
saveBtn.disabled = false;
});
await refreshAuthUi();
return refreshAuthUi;
}
async function initNetworkBar() {
const bar = document.getElementById('network-bar');
if (!bar) return;
const netSel = bar.querySelector('#global-network');
const joinInput = bar.querySelector('#global-join-id');
const joinBtn = bar.querySelector('#global-join-btn');
const leaveBtn = bar.querySelector('#global-leave-btn');
const statusEl = bar.querySelector('#global-network-status');
async function reloadNetworkList(selectId) {
await BackOneNetworkSession.syncFromCore();
const networks = await BackOneNetworkSession.loadNetworks();
netSel.innerHTML = networks.map(n =>
`<option value="${n.id}">${escapeHtml(networkLabel(n))} · ${escapeHtml(n.status || 'OK')}</option>`
).join('') || '<option value="">— no networks —</option>';
const want = (selectId || BackOneNetworkSession.getNetworkId() || '').toLowerCase();
if (want && networks.some(n => (n.id || '').toLowerCase() === want)) {
netSel.value = networks.find(n => (n.id || '').toLowerCase() === want).id;
await BackOneNetworkSession.selectNetwork(netSel.value);
} else if (networks.length === 1) {
netSel.value = networks[0].id;
await BackOneNetworkSession.selectNetwork(networks[0].id);
} else if (networks.length > 0 && BackOneNetworkSession.getNetworkId()) {
netSel.value = BackOneNetworkSession.getNetworkId();
}
updateCoreBadge(statusEl);
}
netSel.addEventListener('change', async () => {
if (!netSel.value) return;
statusEl.textContent = 'Switching network…';
await BackOneNetworkSession.selectNetwork(netSel.value);
await BackOneNetworkSession.syncFromCore();
updateCoreBadge(statusEl);
});
joinBtn.addEventListener('click', async () => {
const id = BackOnePeerDirectory.normalizeNetworkId(joinInput.value);
if (!id) {
statusEl.textContent = 'Enter a 16-digit hexadecimal network ID';
return;
}
joinBtn.disabled = true;
leaveBtn.disabled = true;
statusEl.textContent = `Joining ${id}…`;
try {
await BackOneNetworkSession.joinNetwork(id);
joinInput.value = '';
await reloadNetworkList(id);
} catch (err) {
statusEl.textContent = err.message || 'Join failed';
} finally {
joinBtn.disabled = false;
leaveBtn.disabled = false;
}
});
leaveBtn.addEventListener('click', async () => {
const id = netSel.value || BackOneNetworkSession.getNetworkId();
if (!id) {
statusEl.textContent = 'Select a network to leave';
return;
}
if (!confirm(`Leave network ${networkLabel(BackOneNetworkSession.getActiveNetwork() || { id })}?`)) return;
joinBtn.disabled = true;
leaveBtn.disabled = true;
statusEl.textContent = `Leaving ${id}…`;
try {
await BackOneNetworkSession.leaveNetwork(id);
await reloadNetworkList();
} catch (err) {
statusEl.textContent = err.message || 'Leave failed';
} finally {
joinBtn.disabled = false;
leaveBtn.disabled = false;
}
});
BackOneNetworkSession.subscribe(() => updateCoreBadge(statusEl));
const refreshAuthUi = await initAuthTokenBar(statusEl, () => reloadNetworkList());
await reloadNetworkList();
if (refreshAuthUi) await refreshAuthUi();
coreRefreshTimer = setInterval(() => {
BackOneNetworkSession.syncFromCore().then(() => updateCoreBadge(statusEl));
}, 45000);
}
async function initAppShell() {
await BackOneAuth.init();
await initNetworkBar();
const nav = document.getElementById('addon-nav');
const panel = document.getElementById('addon-panel');
const addons = await BackOneAddonLoader.loadAddonManifest();
if (!addons.length) {
panel.innerHTML = '<p>No UI addons found. Install backone-addon-voice, backone-addon-video, or backone-addon-chat, then run: <code>sudo backone-updater rebuild-manifest</code> and restart backone.</p>';
return;
}
const navOrder = ['chat', 'video', 'voice'];
const sorted = [...addons].sort((a, b) => {
const ia = navOrder.indexOf(a.id);
const ib = navOrder.indexOf(b.id);
return (ia < 0 ? 99 : ia) - (ib < 0 ? 99 : ib);
});
for (const entry of sorted) {
const mod = await BackOneAddonLoader.loadAddonModule(entry);
if (mod) addonModules[entry.id] = mod;
const btn = document.createElement('button');
btn.dataset.addonId = entry.id;
btn.textContent = (entry.frontend && entry.frontend.navLabel) || entry.name || entry.id;
btn.onclick = () => {
nav.querySelectorAll('button').forEach(b => b.classList.remove('active'));
btn.classList.add('active');
panel.innerHTML = '';
location.hash = entry.id;
if (addonModules[entry.id] && addonModules[entry.id].mount) {
addonModules[entry.id].mount(panel);
} else {
panel.innerHTML = `<p>Addon "${entry.id}" loaded.</p>`;
}
};
addonButtons[entry.id] = btn;
nav.appendChild(btn);
}
const fromHash = hashAddonId();
if (fromHash && addonButtons[fromHash]) {
activateAddon(fromHash);
} else {
nav.querySelector('button')?.click();
}
}
window.addEventListener('hashchange', () => {
const id = hashAddonId();
if (id) activateAddon(id);
});
document.addEventListener('visibilitychange', () => {
if (document.visibilityState === 'visible') {
BackOneNetworkSession.syncFromCore();
}
});
document.addEventListener('DOMContentLoaded', initAppShell);
+178
View File
@@ -0,0 +1,178 @@
/**
* Single BackOne core network for all addons (same as backone-cli listnetworks/join).
*/
window.BackOneNetworkSession = (function () {
const NETWORK_KEY = 'backone.lastNetworkId';
const PEER_KEY = 'backone.lastPeerId';
let activeNetwork = null;
let selectedPeer = null;
let coreStatus = { online: false, address: '', networkCount: 0, leafPeerCount: 0, authenticated: false };
const listeners = new Set();
function emit() {
const snap = {
network: activeNetwork,
networkId: getNetworkId(),
peer: selectedPeer,
core: { ...coreStatus }
};
listeners.forEach(fn => {
try { fn(snap); } catch (_) { /* ignore */ }
});
}
function getNetworkId() {
return (localStorage.getItem(NETWORK_KEY) || '').toLowerCase();
}
function setNetworkId(id) {
const nwid = BackOnePeerDirectory.normalizeNetworkId(id);
if (nwid) {
localStorage.setItem(NETWORK_KEY, nwid);
} else {
localStorage.removeItem(NETWORK_KEY);
}
BackOnePeerDirectory.setLastNetworkId(nwid);
return nwid;
}
function getSelectedPeerId() {
return (localStorage.getItem(PEER_KEY) || '').toLowerCase();
}
function setSelectedPeer(peer) {
selectedPeer = peer || null;
if (peer && peer.peerId) {
localStorage.setItem(PEER_KEY, peer.peerId);
} else {
localStorage.removeItem(PEER_KEY);
}
emit();
}
function pickDefaultNetworkId(networks) {
if (!networks.length) return '';
const stored = getNetworkId();
if (stored && networks.some(n => (n.id || '').toLowerCase() === stored)) {
return stored;
}
const preferred = networks.find(n => n.status === 'OK')
|| networks.find(n => n.status === 'REQUESTING_CONFIGURATION')
|| networks[0];
return preferred ? (preferred.id || '').toLowerCase() : '';
}
/**
* On app start: read BackOne core state (same as backone-cli info / listnetworks / peers).
*/
async function syncFromCore() {
const token = await BackOneAuth.getToken();
const [status, networks, cliPeers] = await Promise.all([
BackOnePeerDirectory.loadCoreStatus(),
BackOnePeerDirectory.loadNetworks(),
BackOnePeerDirectory.loadCliPeers()
]);
coreStatus = {
online: !!(status && status.online),
address: (status && status.address) || '',
version: (status && status.version) || '',
networkCount: networks.length,
leafPeerCount: BackOnePeerDirectory.countLeafPeers(cliPeers),
authenticated: !!token
};
const pick = pickDefaultNetworkId(networks);
if (pick) {
setNetworkId(pick);
activeNetwork = networks.find(n => (n.id || '').toLowerCase() === pick)
|| await BackOnePeerDirectory.loadNetwork(pick);
} else {
setNetworkId('');
activeNetwork = null;
}
emit();
return { coreStatus, activeNetwork, networks };
}
async function refresh() {
const id = getNetworkId();
if (!id) {
activeNetwork = null;
emit();
return null;
}
activeNetwork = await BackOnePeerDirectory.loadNetwork(id);
if (!activeNetwork) {
activeNetwork = { id, name: id, status: 'UNKNOWN' };
}
emit();
return activeNetwork;
}
async function loadNetworks() {
return BackOnePeerDirectory.loadNetworks();
}
async function selectNetwork(nwid) {
const id = setNetworkId(nwid);
if (!id) {
await refresh();
return null;
}
return refresh();
}
async function joinNetwork(nwid) {
const id = BackOnePeerDirectory.normalizeNetworkId(nwid);
if (!id) throw new Error('Network ID must be 16 hexadecimal characters');
await BackOnePeerDirectory.joinNetwork(id);
return syncFromCore();
}
async function leaveNetwork(nwid) {
const id = BackOnePeerDirectory.normalizeNetworkId(nwid || getNetworkId());
if (!id) throw new Error('No network selected');
await BackOnePeerDirectory.leaveNetwork(id);
if (getNetworkId() === id) {
setNetworkId('');
selectedPeer = null;
localStorage.removeItem(PEER_KEY);
}
return syncFromCore();
}
function subscribe(fn) {
listeners.add(fn);
return () => listeners.delete(fn);
}
function requireNetworkId() {
const id = getNetworkId();
if (!id) throw new Error('Join a BackOne network first (same as backone-cli join)');
return id;
}
function getCoreStatus() {
return { ...coreStatus };
}
return {
getNetworkId,
getActiveNetwork: () => activeNetwork,
getCoreStatus,
getSelectedPeer: () => selectedPeer,
getSelectedPeerId,
setSelectedPeer,
selectNetwork,
joinNetwork,
leaveNetwork,
loadNetworks,
syncFromCore,
refresh,
subscribe,
requireNetworkId
};
})();
+266
View File
@@ -0,0 +1,266 @@
window.BackOnePeerDirectory = (function () {
const NETWORK_KEY = 'backone.lastNetworkId';
const API = {
peers: ['/api/backone/peers', '/app/api/peers', '/peer'],
networks: ['/api/backone/networks', '/app/api/networks', '/network'],
status: ['/api/backone/status', '/app/api/status', '/status'],
network: (id) => [
`/api/backone/network/${id}`,
`/app/api/network/${id}`,
`/network/${id}`
],
members: (id) => [
`/api/backone/network/${id}/members`,
`/app/api/network/${id}/members`,
`/unstable/controller/network/${id}/member`
],
join: (id) => [
`/api/backone/network/${id}`,
`/app/api/network/${id}`,
`/network/${id}`
],
leave: (id) => [
`/api/backone/network/${id}`,
`/app/api/network/${id}`,
`/network/${id}`
]
};
function apiGet(path) {
return BackOneAuth.apiGet(path);
}
/** Same data as `backone-cli -j peers` (GET /peer). */
async function loadCliPeers() {
try {
const peers = await BackOneAuth.apiGetFirst(API.peers);
return Array.isArray(peers) ? peers : [];
} catch (_) {
return [];
}
}
/** Same data as `backone-cli -j listnetworks` (GET /network). */
async function loadNetworks() {
try {
const nets = await BackOneAuth.apiGetFirst(API.networks);
return Array.isArray(nets) ? nets : [];
} catch (_) {
return [];
}
}
/** Same data as `backone-cli -j info` (GET /status). */
async function loadCoreStatus() {
try {
return await BackOneAuth.apiGetFirst(API.status);
} catch (_) {
try {
const res = await fetch('/health', { cache: 'no-store' });
return { online: res.ok };
} catch (_2) {
return { online: false };
}
}
}
function countLeafPeers(cliPeers) {
if (!Array.isArray(cliPeers)) return 0;
return cliPeers.filter(p => !p.role || p.role === 'LEAF').length;
}
async function loadNetwork(nwid) {
if (!nwid) return null;
const networks = await loadNetworks();
const found = networks.find(n => n.id === nwid);
if (found) return found;
try {
return await BackOneAuth.apiGetFirst(API.network(nwid));
} catch (_) {
return null;
}
}
function getLastNetworkId() {
return localStorage.getItem(NETWORK_KEY) || '';
}
function setLastNetworkId(nwid) {
if (nwid) localStorage.setItem(NETWORK_KEY, nwid);
}
function peerIsOnline(p) {
if (!p) return false;
const paths = p.paths || [];
return paths.some(x => x.active && !x.expired);
}
function peerLinkLabel(p) {
if (!p) return '';
if (p.tunneled) return 'RELAY';
const paths = p.paths || [];
for (const path of paths) {
if (path.preferred && path.active && !path.expired) {
return 'DIRECT';
}
}
for (const path of paths) {
if (path.active && !path.expired) {
return 'DIRECT';
}
}
return 'RELAY';
}
function peerPreferredPath(p) {
if (!p) return '';
const paths = p.paths || [];
for (const path of paths) {
if (path.preferred && path.address) return path.address;
}
for (const path of paths) {
if (path.active && path.address) return path.address;
}
return '';
}
function cliPeerMap(cliPeers) {
const map = {};
for (const p of cliPeers) {
const id = (p.address || '').toLowerCase();
if (id) map[id] = p;
}
return map;
}
async function loadMemberMap(nwid) {
if (!nwid) return {};
try {
const data = await BackOneAuth.apiGetFirst(API.members(nwid));
const map = {};
if (data && Array.isArray(data.data)) {
for (const m of data.data) {
const id = (m.id || '').toLowerCase();
if (id) map[id] = m;
}
}
return map;
} catch (_) {
return {};
}
}
function buildPeerEntry(peerId, member, cliPeer, networkId, networkName) {
const controllerName = member ? (member.name || '') : '';
const ips = member && member.ipAssignments ? member.ipAssignments : [];
return {
peerId,
networkId: networkId || '',
networkName: networkName || '',
displayName: BackOneContacts.getDisplayName(peerId, controllerName),
controllerName,
nickname: BackOneContacts.getNickname(peerId),
online: peerIsOnline(cliPeer),
latency: cliPeer && typeof cliPeer.latency === 'number' ? cliPeer.latency : null,
role: (cliPeer && cliPeer.role) || (member ? 'LEAF' : ''),
version: (cliPeer && cliPeer.version) || '',
link: cliPeer ? peerLinkLabel(cliPeer) : '',
path: cliPeer ? peerPreferredPath(cliPeer) : '',
ip: ips.length ? ips[0] : '',
onNetwork: !!member
};
}
async function loadPeersForNetwork(nwid) {
if (!nwid) return [];
const [cliPeers, members, selfStatus, network] = await Promise.all([
loadCliPeers(),
loadMemberMap(nwid),
loadCoreStatus().catch(() => ({})),
loadNetwork(nwid)
]);
await BackOneContacts.load();
const selfId = ((selfStatus && selfStatus.address) || '').toLowerCase();
const networkId = nwid;
const networkName = network ? (network.name || network.id || nwid) : nwid;
const cliById = cliPeerMap(cliPeers);
const out = [];
if (Object.keys(members).length > 0) {
for (const peerId of Object.keys(members)) {
const member = members[peerId];
if (!member || member.authorized === false) continue;
if (peerId === selfId) continue;
out.push(buildPeerEntry(peerId, member, cliById[peerId], networkId, networkName));
}
} else {
for (const peerId of Object.keys(cliById)) {
const p = cliById[peerId];
if (!peerId || peerId.length !== 10 || peerId === selfId) continue;
if (p.role && p.role !== 'LEAF') continue;
out.push(buildPeerEntry(peerId, null, p, networkId, networkName));
}
}
out.sort((a, b) => {
if (a.online !== b.online) return a.online ? -1 : 1;
return a.displayName.localeCompare(b.displayName);
});
return out;
}
function searchPeers(query, list) {
const q = (query || '').trim().toLowerCase();
if (!q) return list;
return list.filter(p =>
p.displayName.toLowerCase().includes(q) ||
p.peerId.includes(q) ||
(p.networkName || '').toLowerCase().includes(q) ||
(p.networkId || '').includes(q) ||
(p.controllerName || '').toLowerCase().includes(q) ||
(p.role || '').toLowerCase().includes(q) ||
(p.path || '').toLowerCase().includes(q) ||
(p.ip || '').toLowerCase().includes(q)
);
}
function normalizeNetworkId(input) {
const id = String(input || '').replace(/[^0-9a-fA-F]/g, '').toLowerCase();
return id.length === 16 ? id : '';
}
async function joinNetwork(nwid) {
const id = normalizeNetworkId(nwid);
if (!id) {
throw new Error('Network ID must be 16 hexadecimal characters');
}
return BackOneAuth.apiSendFirst(API.join(id), 'POST', {});
}
async function leaveNetwork(nwid) {
const id = normalizeNetworkId(nwid);
if (!id) {
throw new Error('Network ID must be 16 hexadecimal characters');
}
return BackOneAuth.apiSendFirst(API.leave(id), 'DELETE');
}
return {
loadCliPeers,
loadNetworks,
loadNetwork,
loadCoreStatus,
countLeafPeers,
loadPeersForNetwork,
getLastNetworkId,
setLastNetworkId,
joinNetwork,
leaveNetwork,
normalizeNetworkId,
searchPeers,
peerIsOnline,
peerLinkLabel
};
})();
+193
View File
@@ -0,0 +1,193 @@
window.BackOnePeerPicker = function PeerPicker(container, options) {
options = options || {};
const onSelect = options.onSelect || (() => {});
const onNetworkChange = options.onNetworkChange || (() => {});
const onAction = options.onAction || null;
const actionLabel = options.actionLabel || 'Select';
const showActionButton = options.showActionButton !== false;
const useSharedNetwork = options.useSharedNetwork !== false;
const layout = options.layout || 'default';
let selectedPeer = null;
let peers = [];
let currentNetwork = null;
let unsub = null;
if (layout === 'sidebar') {
container.innerHTML = `
<div class="peer-picker">
<div id="pp-network-banner" class="peer-info-box"></div>
<label class="peer-picker-row">
<input id="pp-search" type="search" placeholder="Name or peer ID" />
</label>
<ul id="pp-list" class="peer-list"></ul>
</div>`;
} else {
container.innerHTML = `
<div class="peer-picker">
<div class="peer-picker-core">BackOne core · same network as <code>backone-cli</code></div>
<div id="pp-network-banner" class="peer-network-banner"></div>
<label class="peer-picker-row">Search
<input id="pp-search" type="search" placeholder="Name or peer ID" />
</label>
<ul id="pp-list" class="peer-list"></ul>
<div id="pp-selected" class="peer-selected"></div>
${showActionButton ? `<button id="pp-action" type="button" disabled>${actionLabel}</button>` : ''}
</div>`;
}
const netBanner = container.querySelector('#pp-network-banner');
const searchInput = container.querySelector('#pp-search');
const listEl = container.querySelector('#pp-list');
const selectedEl = container.querySelector('#pp-selected');
const actionBtn = container.querySelector('#pp-action');
function networkLabel(n) {
if (!n) return '';
return n.name ? `${n.name} (${n.id})` : n.id;
}
function renderNetworkBanner() {
if (!useSharedNetwork) return;
const id = BackOneNetworkSession.getNetworkId();
const core = BackOneNetworkSession.getCoreStatus();
if (!core.online) {
netBanner.innerHTML = '<span class="peer-network-warn">BackOne offline — peers unavailable</span>';
return;
}
if (!currentNetwork || !id) {
netBanner.innerHTML = '<span class="peer-network-warn">No network — join one in the header bar</span>';
return;
}
const labelSuffix = `${peers.length} contact(s) · ${core.leafPeerCount} VL1 peer(s) from core`;
if (layout === 'sidebar') {
netBanner.textContent = `${networkLabel(currentNetwork)} · ${labelSuffix}`;
} else {
netBanner.textContent = `Network: ${networkLabel(currentNetwork)} · ${textSuffix}`;
}
}
function renderList() {
const q = searchInput.value;
const filtered = BackOnePeerDirectory.searchPeers(q, peers);
const netTag = currentNetwork ? (currentNetwork.name || currentNetwork.id) : '';
listEl.innerHTML = filtered.map(p => {
const lat = p.latency != null ? `${p.latency}ms` : '—';
const link = p.link || 'DIRECT';
const meta = [p.role || 'LEAF', lat, link, netTag].filter(Boolean).join(' · ');
return `
<li class="peer-item ${p.online ? 'online' : 'offline'} ${selectedPeer && selectedPeer.peerId === p.peerId ? 'active' : ''}" data-id="${p.peerId}">
<span class="peer-dot" title="${p.online ? 'Online' : 'Offline'}"></span>
<span class="peer-main">
<strong class="peer-name">${escapeHtml(p.displayName || p.peerId)}</strong>
<small class="peer-sub">${escapeHtml(meta)}</small>
</span>
<button type="button" class="peer-edit" title="Edit nickname">✎</button>
</li>`;
}).join('') || '<li class="peer-empty">No peers on this network</li>';
listEl.querySelectorAll('.peer-item').forEach(li => {
li.addEventListener('click', (ev) => {
if (ev.target.classList.contains('peer-edit')) return;
const id = li.dataset.id;
selectedPeer = peers.find(x => x.peerId === id) || null;
if (selectedPeer) BackOneNetworkSession.setSelectedPeer(selectedPeer);
renderList();
renderSelected();
if (selectedPeer) onSelect(selectedPeer, currentNetwork);
});
const editBtn = li.querySelector('.peer-edit');
if (editBtn) {
editBtn.addEventListener('click', async (ev) => {
ev.stopPropagation();
const id = li.dataset.id;
const peer = peers.find(x => x.peerId === id);
if (!peer) return;
const nick = prompt('Nickname for ' + peer.peerId, peer.nickname || peer.controllerName || '');
if (nick === null) return;
await BackOneContacts.setNickname(peer.peerId, nick);
BackOneContacts.invalidate();
await refreshPeers();
});
}
});
}
function renderSelected() {
if (layout === 'sidebar') return;
if (!selectedPeer) {
if (selectedEl) selectedEl.textContent = '';
if (actionBtn) actionBtn.disabled = true;
return;
}
const net = currentNetwork ? networkLabel(currentNetwork) : BackOneNetworkSession.getNetworkId();
if (selectedEl) {
selectedEl.innerHTML = `Selected: <strong>${escapeHtml(selectedPeer.displayName)}</strong> (${escapeHtml(selectedPeer.peerId)})${net ? ` on <em>${escapeHtml(net)}</em>` : ''}`;
}
if (actionBtn) actionBtn.disabled = !BackOneNetworkSession.getNetworkId();
}
function escapeHtml(s) {
return String(s).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
}
async function refreshPeers() {
currentNetwork = BackOneNetworkSession.getActiveNetwork();
if (useSharedNetwork && BackOneNetworkSession.getNetworkId() && !currentNetwork) {
currentNetwork = await BackOneNetworkSession.refresh();
}
renderNetworkBanner();
const nwid = BackOneNetworkSession.getNetworkId();
await BackOneContacts.load();
peers = nwid ? await BackOnePeerDirectory.loadPeersForNetwork(nwid) : [];
const lastPeerId = BackOneNetworkSession.getSelectedPeerId();
if (lastPeerId) {
selectedPeer = peers.find(p => p.peerId === lastPeerId) || BackOneNetworkSession.getSelectedPeer();
}
renderList();
renderSelected();
onNetworkChange(currentNetwork, peers);
}
searchInput.addEventListener('input', renderList);
if (actionBtn) {
actionBtn.addEventListener('click', () => {
if (selectedPeer && onAction) onAction(selectedPeer, currentNetwork);
});
}
if (useSharedNetwork) {
unsub = BackOneNetworkSession.subscribe(() => refreshPeers());
}
refreshPeers();
return {
getSelectedPeer: () => selectedPeer,
getSelectedNetwork: () => currentNetwork,
getPeers: () => peers,
refresh: refreshPeers,
destroy: () => { if (unsub) unsub(); }
};
};
window.BackOneCommLayout = {
connectionBarHtml(peer, network) {
const netLabel = network
? (network.name ? `${network.name} (${network.id})` : network.id)
: BackOneNetworkSession.getNetworkId() || '—';
const lat = peer && peer.latency != null ? `${peer.latency}ms` : '—';
const latClass = peer && peer.latency != null && peer.latency < 100 ? 'latency-ok' : '';
return `
<div class="comm-stat"><label>Network ID</label><span>${escapeHtml(netLabel)}</span></div>
<div class="comm-stat"><label>Peer ID</label><span>${peer ? escapeHtml(peer.peerId) : '—'}</span></div>
<div class="comm-stat"><label>Role</label><span>${peer ? escapeHtml(peer.role || 'LEAF') : '—'}</span></div>
<div class="comm-stat"><label>Transport</label><span>${peer ? escapeHtml(peer.link || 'DIRECT') : '—'}</span></div>
<div class="comm-stat"><label>Latency</label><span class="${latClass}">${lat}</span></div>
<div class="comm-stat"><label>Quality</label><span class="comm-quality"><span></span><span></span><span></span><span></span></span></div>`;
}
};
function escapeHtml(s) {
return String(s).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
}
+643
View File
@@ -0,0 +1,643 @@
:root {
--bg: #0f1419;
--surface: #1a2332;
--surface-2: #243044;
--border: #2d3a4d;
--text: #e7ecf1;
--muted: #8b9bb4;
--accent: #4a90d9;
--accent-dim: #2d5a8a;
--ok: #2ecc71;
--warn: #e6a23c;
--danger: #e74c3c;
}
* { box-sizing: border-box; }
body {
font-family: system-ui, -apple-system, sans-serif;
margin: 0;
background: var(--bg);
color: var(--text);
min-height: 100vh;
display: flex;
flex-direction: column;
}
.app-header {
display: flex;
align-items: center;
gap: 1rem;
padding: 0.65rem 1rem;
background: var(--surface);
border-bottom: 1px solid var(--border);
flex-wrap: wrap;
}
.app-brand {
display: flex;
align-items: center;
gap: 0.6rem;
}
.app-brand h1 {
margin: 0;
font-size: 1.15rem;
font-weight: 600;
}
.core-badge {
font-size: 0.75rem;
padding: 0.15rem 0.55rem;
border-radius: 999px;
font-weight: 500;
}
.core-badge.online {
background: rgba(46, 204, 113, 0.15);
color: var(--ok);
border: 1px solid rgba(46, 204, 113, 0.35);
}
.core-badge.offline {
background: rgba(231, 76, 60, 0.12);
color: var(--danger);
border: 1px solid rgba(231, 76, 60, 0.3);
}
.network-bar {
display: flex;
align-items: center;
gap: 0.45rem;
flex-wrap: wrap;
flex: 1;
min-width: 240px;
}
.network-bar-label { font-size: 0.82rem; color: var(--muted); }
.network-bar select,
.network-bar input,
.auth-token-bar input {
background: var(--bg);
color: var(--text);
border: 1px solid var(--border);
border-radius: 6px;
padding: 0.35rem 0.5rem;
font-size: 0.85rem;
}
.network-bar input { width: 9.5rem; }
.network-bar button,
.auth-token-bar button {
background: var(--accent-dim);
color: #fff;
border: none;
padding: 0.35rem 0.65rem;
border-radius: 6px;
cursor: pointer;
font-size: 0.85rem;
}
.network-bar-status {
font-size: 0.75rem;
color: #6b8cae;
flex: 1 1 100%;
}
.auth-token-bar {
display: flex;
align-items: center;
gap: 0.5rem;
flex: 1 1 100%;
}
.auth-token-hidden { display: none; }
.auth-token-label { font-size: 0.82rem; color: var(--muted); }
.auth-token-bar input { width: 14rem; }
.addon-nav {
display: flex;
gap: 0.35rem;
margin-left: auto;
}
.addon-nav button {
background: transparent;
color: var(--muted);
border: 1px solid transparent;
padding: 0.4rem 0.85rem;
border-radius: 6px;
cursor: pointer;
font-size: 0.88rem;
}
.addon-nav button:hover { color: var(--text); background: var(--surface-2); }
.addon-nav button.active {
background: var(--accent);
color: #fff;
border-color: var(--accent);
}
.addon-panel {
flex: 1;
display: flex;
flex-direction: column;
min-height: 0;
overflow: hidden;
}
.addon-panel > p {
padding: 1.5rem;
margin: 0;
}
/* —— comm layout (voice / video / chat) —— */
.comm-layout {
display: flex;
flex: 1;
min-height: 0;
overflow: hidden;
}
.comm-sidebar {
width: 300px;
flex-shrink: 0;
display: flex;
flex-direction: column;
border-right: 1px solid var(--border);
background: var(--bg);
min-height: 0;
}
.comm-sidebar-head {
padding: 1rem 1rem 0.5rem;
border-bottom: 1px solid var(--border);
}
.comm-sidebar-head h2 {
margin: 0 0 0.35rem;
font-size: 1.05rem;
font-weight: 600;
}
.comm-sidebar-sub {
margin: 0;
font-size: 0.72rem;
color: var(--muted);
text-transform: uppercase;
letter-spacing: 0.04em;
line-height: 1.4;
}
.comm-sidebar-body {
flex: 1;
min-height: 0;
overflow: auto;
padding: 0.75rem 1rem;
display: flex;
flex-direction: column;
}
.comm-sidebar-actions {
padding: 0.75rem 1rem 1rem;
border-top: 1px solid var(--border);
display: flex;
flex-direction: column;
gap: 0.5rem;
}
.comm-selected-label {
font-size: 0.78rem;
color: var(--muted);
margin-bottom: 0.15rem;
}
.comm-selected-label strong {
color: var(--text);
font-family: ui-monospace, monospace;
}
.comm-main {
flex: 1;
display: flex;
flex-direction: column;
min-width: 0;
min-height: 0;
background: #0a0e14;
}
.comm-main-head {
display: flex;
align-items: center;
justify-content: space-between;
padding: 0.65rem 1rem;
border-bottom: 1px solid var(--border);
background: var(--surface);
font-size: 0.85rem;
}
.comm-breadcrumb {
color: var(--muted);
}
.comm-breadcrumb strong { color: var(--text); }
.comm-head-status {
display: flex;
align-items: center;
gap: 0.75rem;
color: var(--muted);
font-size: 0.82rem;
}
.comm-head-status .connected {
color: var(--ok);
font-weight: 500;
}
.comm-stage {
flex: 1;
position: relative;
display: flex;
align-items: center;
justify-content: center;
min-height: 0;
background: #000;
}
.comm-stage video {
width: 100%;
height: 100%;
object-fit: contain;
background: #000;
}
.comm-remote-placeholder {
text-align: center;
color: var(--muted);
}
.comm-remote-placeholder .avatar {
width: 72px;
height: 72px;
border-radius: 50%;
background: var(--surface-2);
margin: 0 auto 0.75rem;
display: flex;
align-items: center;
justify-content: center;
font-size: 2rem;
color: #5a6a7d;
}
.comm-remote-placeholder .peer-id {
font-family: ui-monospace, monospace;
color: var(--text);
font-size: 0.95rem;
}
.comm-remote-tag {
position: absolute;
top: 12px;
left: 12px;
background: rgba(0, 0, 0, 0.55);
padding: 0.2rem 0.55rem;
border-radius: 4px;
font-size: 0.75rem;
display: flex;
align-items: center;
gap: 0.35rem;
}
.comm-remote-tag .dot {
width: 6px;
height: 6px;
border-radius: 50%;
background: var(--accent);
}
.comm-call-controls {
position: absolute;
bottom: 1.25rem;
left: 50%;
transform: translateX(-50%);
display: flex;
gap: 0.5rem;
padding: 0.45rem;
background: rgba(26, 35, 50, 0.92);
border-radius: 10px;
border: 1px solid var(--border);
}
.comm-call-controls button {
background: var(--surface-2);
color: var(--text);
border: 1px solid var(--border);
padding: 0.45rem 0.85rem;
border-radius: 6px;
cursor: pointer;
font-size: 0.82rem;
}
.comm-call-controls button.danger {
background: var(--danger);
border-color: var(--danger);
color: #fff;
}
.comm-connection-bar {
display: flex;
flex-wrap: wrap;
gap: 1.25rem 2rem;
padding: 0.65rem 1rem;
background: var(--surface);
border-top: 1px solid var(--border);
font-size: 0.78rem;
}
.comm-stat label {
display: block;
color: var(--muted);
font-size: 0.68rem;
text-transform: uppercase;
letter-spacing: 0.04em;
margin-bottom: 0.15rem;
}
.comm-stat span {
font-family: ui-monospace, monospace;
font-size: 0.82rem;
}
.comm-stat .latency-ok { color: var(--ok); }
.comm-quality {
display: inline-flex;
align-items: flex-end;
gap: 2px;
height: 14px;
}
.comm-quality span {
width: 3px;
background: var(--ok);
border-radius: 1px;
}
.comm-quality span:nth-child(1) { height: 4px; }
.comm-quality span:nth-child(2) { height: 7px; }
.comm-quality span:nth-child(3) { height: 10px; }
.comm-quality span:nth-child(4) { height: 14px; }
/* —— peer picker —— */
.peer-picker { display: flex; flex-direction: column; flex: 1; min-height: 0; }
.peer-info-box {
font-size: 0.82rem;
color: #9eb4d0;
margin-bottom: 0.75rem;
padding: 0.55rem 0.65rem;
background: rgba(45, 90, 138, 0.25);
border: 1px solid rgba(74, 144, 217, 0.25);
border-radius: 6px;
line-height: 1.35;
}
.peer-picker-row { display: block; margin-bottom: 0.65rem; }
.peer-picker-row input {
width: 100%;
margin-top: 0.25rem;
padding: 0.45rem 0.55rem;
background: var(--surface);
color: var(--text);
border: 1px solid var(--border);
border-radius: 6px;
font-size: 0.85rem;
}
.peer-list {
list-style: none;
padding: 0;
margin: 0;
--peer-row-height: 3.25rem;
max-height: calc(var(--peer-row-height) * 10);
flex: 0 1 auto;
overflow-x: hidden;
overflow-y: auto;
border: 1px solid var(--border);
border-radius: 6px;
scrollbar-gutter: stable;
}
.peer-item {
display: flex;
align-items: flex-start;
gap: 0.5rem;
min-height: var(--peer-row-height);
padding: 0.55rem 0.65rem;
cursor: pointer;
border-bottom: 1px solid var(--border);
box-sizing: border-box;
}
.peer-item:last-child { border-bottom: none; }
.peer-item:hover,
.peer-item.active {
background: var(--surface-2);
}
.peer-item.active { border-left: 3px solid var(--accent); padding-left: calc(0.65rem - 3px); }
.peer-dot {
width: 8px;
height: 8px;
border-radius: 50%;
background: #666;
flex-shrink: 0;
margin-top: 0.35rem;
}
.peer-item.online .peer-dot { background: var(--ok); }
.peer-main { flex: 1; min-width: 0; }
.peer-name {
display: block;
font-family: ui-monospace, monospace;
font-size: 0.88rem;
}
.peer-sub {
color: var(--muted);
font-size: 0.72rem;
display: block;
margin-top: 0.15rem;
}
.peer-edit {
background: transparent;
border: none;
color: var(--muted);
cursor: pointer;
padding: 0.2rem;
font-size: 0.85rem;
}
.peer-empty { padding: 1rem; color: var(--muted); font-size: 0.85rem; }
.peer-network-warn { color: var(--warn); }
.btn-primary {
background: var(--accent);
color: #fff;
border: none;
padding: 0.55rem 1rem;
border-radius: 6px;
cursor: pointer;
font-size: 0.88rem;
font-weight: 500;
}
.btn-primary:disabled {
opacity: 0.45;
cursor: not-allowed;
}
.btn-secondary {
background: var(--surface-2);
color: var(--text);
border: 1px solid var(--border);
padding: 0.55rem 1rem;
border-radius: 6px;
cursor: pointer;
font-size: 0.88rem;
}
/* —— chat —— */
.chat-thread-panel {
flex: 1;
display: flex;
flex-direction: column;
min-height: 0;
padding: 1rem;
}
.chat-thread-header {
padding-bottom: 0.5rem;
border-bottom: 1px solid var(--border);
margin-bottom: 0.5rem;
font-size: 0.9rem;
}
#chat-thread {
flex: 1;
overflow: auto;
border: 1px solid var(--border);
padding: 0.75rem;
border-radius: 6px;
margin-bottom: 0.5rem;
background: var(--surface);
}
.chat-compose {
display: flex;
flex-direction: column;
gap: 0.5rem;
}
.chat-compose textarea {
width: 100%;
padding: 0.5rem;
background: var(--surface);
color: var(--text);
border: 1px solid var(--border);
border-radius: 6px;
resize: vertical;
}
.chat-compose-row {
display: flex;
gap: 0.5rem;
align-items: center;
}
/* —— voice stage —— */
.voice-stage-inner {
text-align: center;
padding: 2rem;
}
.voice-stage-inner .avatar {
width: 96px;
height: 96px;
border-radius: 50%;
background: var(--surface-2);
margin: 0 auto 1rem;
display: flex;
align-items: center;
justify-content: center;
font-size: 2.5rem;
}
/* —— ringing popup —— */
.ringing-hidden { display: none; }
#ringing-popup {
position: fixed;
inset: 0;
background: rgba(0, 0, 0, 0.6);
display: flex;
align-items: center;
justify-content: center;
z-index: 1000;
}
#ringing-popup.ringing-hidden { display: none; }
.ringing-card {
background: var(--surface);
padding: 2rem;
border-radius: 12px;
text-align: center;
min-width: 280px;
}
.ringing-card button {
margin: 0.5rem;
padding: 0.6rem 1.2rem;
border: none;
border-radius: 6px;
cursor: pointer;
}
.ringing-accept { background: var(--ok); color: #000; }
.ringing-reject { background: var(--danger); color: #fff; }
.video-local-pip {
position: absolute;
bottom: 4.5rem;
right: 1rem;
width: 160px;
border-radius: 8px;
overflow: hidden;
border: 2px solid var(--border);
background: #000;
}
.video-local-pip video {
width: 100%;
height: auto;
display: block;
}
+6
View File
@@ -0,0 +1,6 @@
{
"id": "app-tray",
"name": "BackOne Tray",
"version": "1.17.0",
"requires": []
}
+9
View File
@@ -0,0 +1,9 @@
[Desktop Entry]
Type=Application
Name=BackOne Tray
Comment=Start BackOne tray icon at login
Exec=/usr/bin/backone-tray
Icon=network-workgroup
Terminal=false
X-GNOME-Autostart-enabled=true
Hidden=false
+8
View File
@@ -0,0 +1,8 @@
[Desktop Entry]
Type=Application
Name=BackOne Tray
Comment=BackOne system tray for voice, video, and chat
Exec=/usr/bin/backone-tray
Icon=network-workgroup
Terminal=false
Categories=Network;
+127
View File
@@ -0,0 +1,127 @@
#!/usr/bin/env python3
"""BackOne system tray — opens /app UI and addon deep links."""
import os
import signal
import subprocess
import sys
import threading
import urllib.request
import gi
gi.require_version('Gtk', '3.0')
try:
gi.require_version('AyatanaAppIndicator3', '0.1')
from gi.repository import AyatanaAppIndicator3 as AppIndicator3
except (ImportError, ValueError):
gi.require_version('AppIndicator3', '0.1')
from gi.repository import AppIndicator3
from gi.repository import GLib, Gtk
APP_URL = os.environ.get('BACKONE_APP_URL', 'http://127.0.0.1:9993/app/')
HEALTH_URL = os.environ.get('BACKONE_HEALTH_URL', 'http://127.0.0.1:9993/health')
ICON_NAME = 'network-workgroup'
class BackOneTray:
def __init__(self):
self._windows = []
self.indicator = AppIndicator3.Indicator.new(
'backone-tray',
ICON_NAME,
AppIndicator3.IndicatorCategory.APPLICATION_STATUS,
)
self.indicator.set_status(AppIndicator3.IndicatorStatus.ACTIVE)
self.indicator.set_menu(self._build_menu())
try:
self.indicator.connect('activate', lambda *_: self._open_url(APP_URL))
except TypeError:
pass
self._update_status()
GLib.timeout_add_seconds(30, self._on_timer)
def _build_menu(self):
menu = Gtk.Menu()
def add_item(label, callback):
item = Gtk.MenuItem(label=label)
item.connect('activate', lambda *_: callback())
item.show()
menu.append(item)
return item
add_item('Open BackOne App', lambda: self._open_url(APP_URL))
add_item('Voice', lambda: self._open_url(APP_URL + '#/voice'))
add_item('Video', lambda: self._open_url(APP_URL + '#/video'))
add_item('Chat', lambda: self._open_url(APP_URL + '#/chat'))
menu.append(Gtk.SeparatorMenuItem())
add_item('Restart BackOne Service', self._restart_service)
add_item('Quit Tray', Gtk.main_quit)
menu.show()
return menu
def _service_ok(self):
try:
with urllib.request.urlopen(HEALTH_URL, timeout=2) as resp:
return resp.status == 200
except Exception:
return False
def _update_status(self):
title = 'BackOne (online)' if self._service_ok() else 'BackOne (offline)'
self.indicator.set_title(title)
def _on_timer(self):
self._update_status()
return True
def _open_url(self, url):
if self._try_webkit(url):
return
for cmd in (
['xdg-open', url],
['gio', 'open', url],
['sensible-browser', url],
):
try:
subprocess.Popen(cmd, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
return
except OSError:
continue
def _try_webkit(self, url):
try:
gi.require_version('WebKit2', '4.1')
except ValueError:
try:
gi.require_version('WebKit2', '4.0')
except ValueError:
return False
from gi.repository import WebKit2
win = Gtk.Window(title='BackOne')
win.set_default_size(900, 640)
view = WebKit2.WebView()
view.load_uri(url)
win.add(view)
win.connect('destroy', lambda w: self._windows.remove(w) if w in self._windows else None)
self._windows.append(win)
win.show_all()
return True
def _restart_service(self):
subprocess.Popen(
['pkexec', 'systemctl', 'restart', 'backone'],
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL,
)
def main():
signal.signal(signal.SIGINT, signal.SIG_DFL)
BackOneTray()
Gtk.main()
if __name__ == '__main__':
sys.exit(main())
+10 -1
View File
@@ -1,5 +1,5 @@
Name: backone
Version: 1.14.1
Version: 1.17.1
Release: 1%{?dist}
Summary: BackOne global ethernet switch
@@ -155,6 +155,15 @@ chmod 0755 $RPM_BUILD_ROOT/etc/init.d/backone
%endif
%changelog
* Tue Jun 30 2026 Dedy Sutanto <dsutanto@backone.cloud> - 1.17.1
- Fix /api/backone routes and reinstallable Web UI packages
* Tue Jun 30 2026 Dedy Sutanto <dsutanto@backone.cloud> - 1.17.0
- Tray app, auto web server, peer contact UI
* Tue Jun 30 2026 Dedy Sutanto <dsutanto@backone.cloud> - 1.16.0
- Release notes
* Tue Oct 8 2024 Dedy Sutanto <dsutanto@backone.cloud> - 1.14.1
- see https://github.com/proitlab/BackOne for release notes
+34
View File
@@ -0,0 +1,34 @@
#!/bin/bash
set -euo pipefail
if [ $# -lt 1 ]; then
echo "Usage: $0 <version> [changelog message]" >&2
exit 1
fi
VERSION="$1"
MESSAGE="${2:-see https://github.com/proitlab/BackOne for release notes}"
ROOT="$(cd "$(dirname "$0")/../.." && pwd)"
IFS='.' read -r MAJ MIN REV <<< "${VERSION%%-*}"
REV="${REV:-0}"
sed -i "s/^#define ZEROTIER_ONE_VERSION_MAJOR .*/#define ZEROTIER_ONE_VERSION_MAJOR ${MAJ}/" "$ROOT/version.h"
sed -i "s/^#define ZEROTIER_ONE_VERSION_MINOR .*/#define ZEROTIER_ONE_VERSION_MINOR ${MIN}/" "$ROOT/version.h"
sed -i "s/^#define ZEROTIER_ONE_VERSION_REVISION .*/#define ZEROTIER_ONE_VERSION_REVISION ${REV}/" "$ROOT/version.h"
sed -i "s/^#define ZEROTIER_ONE_VERSION_BUILD .*/#define ZEROTIER_ONE_VERSION_BUILD 0/" "$ROOT/version.h"
echo "$VERSION" > "$ROOT/debian/version"
"$ROOT/ci/scripts/munge_debian_changelog.sh" "$ROOT/debian/changelog" "$VERSION" \
"Dedy Sutanto <dsutanto@backone.cloud>" "$MESSAGE"
"$ROOT/ci/scripts/munge_rpm_spec.sh" "$ROOT/backone.spec" "$VERSION" \
"Dedy Sutanto <dsutanto@backone.cloud>" "$MESSAGE"
for addon in comms-core voice video chat updater app-tray; do
if [ -f "$ROOT/addons/$addon/addon.json" ]; then
sed -i "s/\"version\": \"[^\"]*\"/\"version\": \"${VERSION}\"/" "$ROOT/addons/$addon/addon.json"
fi
done
echo "Bumped to ${VERSION}"
+1 -1
View File
@@ -31,7 +31,7 @@ else
fi
awk -v version=${VERSION} -v date=${DATE} -v name=${NAME} -v message=${MESSAGE} \
'BEGIN{print "zerotier-one (" version ") stable; urgency=medium\n\n * " message "\n\n -- " name " " date "\n" }{ print }' \
'BEGIN{print "backone (" version ") stable; urgency=medium\n\n * " message "\n\n -- " name " " date "\n" }{ print }' \
${FILE} > ${FILE}.new
mv ${FILE}.new ${FILE}
+53
View File
@@ -0,0 +1,53 @@
#!/bin/sh
# Keep ~/.backOneOneAuthToken and /var/lib/backone/authtoken.secret in sync (same as backone-cli).
set -e
BACKONE_HOME="${BACKONE_HOME:-/var/lib/backone}"
SECRET="${BACKONE_HOME}/authtoken.secret"
trim_token() {
tr -d '\n\r \t' < "$1"
}
sync_for_user() {
user="$1"
home="$2"
[ -n "$user" ] && [ -n "$home" ] && [ -d "$home" ] || return 0
user_token="${home}/.backOneOneAuthToken"
if [ -f "$user_token" ] && [ -s "$user_token" ]; then
ut="$(trim_token "$user_token")"
[ -n "$ut" ] || return 0
st=""
if [ -f "$SECRET" ]; then
st="$(trim_token "$SECRET" 2>/dev/null || true)"
fi
if [ "$ut" != "$st" ]; then
printf '%s' "$ut" > "$SECRET"
chown backone:backone "$SECRET" 2>/dev/null || true
chmod 0600 "$SECRET" 2>/dev/null || true
echo "Synced ${SECRET} from ${user_token}"
fi
return 0
fi
if [ -f "$SECRET" ]; then
install -o "$user" -g "$(id -gn "$user" 2>/dev/null || echo "$user")" -m 0600 "$SECRET" "$user_token" 2>/dev/null \
|| cp "$SECRET" "$user_token"
chown "$user":"$(id -gn "$user" 2>/dev/null || echo "$user")" "$user_token" 2>/dev/null || true
chmod 0600 "$user_token" 2>/dev/null || true
echo "Installed ${user_token} from ${SECRET}"
fi
}
if [ -n "${SUDO_USER:-}" ] && [ "$SUDO_USER" != "root" ]; then
home="$(getent passwd "$SUDO_USER" | cut -d: -f6)"
sync_for_user "$SUDO_USER" "$home"
fi
if [ -n "${USER:-}" ] && [ "$USER" != "root" ] && [ "$USER" != "${SUDO_USER:-}" ]; then
home="$(getent passwd "$USER" | cut -d: -f6)"
sync_for_user "$USER" "$home"
fi
exit 0
+6
View File
@@ -1120,6 +1120,12 @@ void EmbeddedNetworkController::configureHTTPControlPlane(
};
s.Get(memberListPath2, memberListGet2);
sv6.Get(memberListPath2, memberListGet2);
std::string appMemberListPath = "/app/api/network/([0-9a-fA-F]{16})/members";
s.Get(appMemberListPath, memberListGet2);
sv6.Get(appMemberListPath, memberListGet2);
std::string backoneMemberListPath = "/api/backone/network/([0-9a-fA-F]{16})/members";
s.Get(backoneMemberListPath, memberListGet2);
sv6.Get(backoneMemberListPath, memberListGet2);
auto memberGet = [&, setContent](const httplib::Request &req, httplib::Response &res) {
auto networkID = req.matches[1];
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
+2
View File
@@ -0,0 +1,2 @@
8131853dbc8c3be5171fa96353de7a884a79d3f1d6b3fbf48f99037f5f95fe27 ext/libpqxx-7.7.3/config/config.guess
1ee0b1581032c46aac6ebd1de74b130f35082fb45a12e9ead42ad5c775f9b64f ext/libpqxx-7.7.3/config/config.sub
@@ -0,0 +1,257 @@
backone (1.17.1) stable; urgency=medium
* Fix /api/backone routes and reinstallable Web UI packages
-- Dedy Sutanto <dsutanto@backone.cloud> Tue, 30 Jun 2026 12:02:07 +0700
backone (1.17.0) stable; urgency=medium
* Tray app, auto web server, peer contact UI
-- Dedy Sutanto <dsutanto@backone.cloud> Tue, 30 Jun 2026 09:55:07 +0700
backone (1.16.0) stable; urgency=medium
* Release notes
-- Dedy Sutanto <dsutanto@backone.cloud> Tue, 30 Jun 2026 08:59:49 +0700
backone (1.15.0) stable; urgency=medium
* Separated addon architecture: voice, video, chat, updater
-- Dedy Sutanto <dsutanto@backone.cloud> Tue, 30 Jun 2026 08:37:16 +0700
zerotier-one (1.14.1) unstable; urgency=medium
* See RELEASE-NOTES.md for release notes.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Wed, 11 Sep 2024 01:00:00 -0700
zerotier-one (1.14.0) unstable; urgency=medium
* See RELEASE-NOTES.md for release notes.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Tue, 19 Mar 2024 01:00:00 -0700
zerotier-one (1.12.2) unstable; urgency=medium
* See RELEASE-NOTES.md for release notes.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Tue, 12 Sep 2023 01:00:00 -0700
zerotier-one (1.12.1) unstable; urgency=medium
* See RELEASE-NOTES.md for release notes.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Fri, 25 Aug 2023 01:00:00 -0700
zerotier-one (1.12.0) unstable; urgency=medium
* See RELEASE-NOTES.md for release notes.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Thu, 17 Aug 2023 01:00:00 -0700
zerotier-one (1.10.6) unstable; urgency=medium
* See RELEASE-NOTES.md for release notes.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Tue, 21 Mar 2023 01:00:00 -0700
zerotier-one (1.10.5) unstable; urgency=medium
* See RELEASE-NOTES.md for release notes.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Fri, 10 Mar 2023 01:00:00 -0700
zerotier-one (1.10.4) unstable; urgency=medium
* See RELEASE-NOTES.md for release notes.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Mon, 06 Mar 2023 01:00:00 -0700
zerotier-one (1.10.3) unstable; urgency=medium
* See RELEASE-NOTES.md for release notes.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Sat, 21 Jan 2023 01:00:00 -0700
zerotier-one (1.10.2) unstable; urgency=medium
* See RELEASE-NOTES.md for release notes.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Thu, 13 Oct 2022 01:00:00 -0700
zerotier-one (1.10.1) unstable; urgency=medium
* See RELEASE-NOTES.md for release notes.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Mon, 27 Jun 2022 01:00:00 -0700
zerotier-one (1.10.0) unstable; urgency=medium
* See RELEASE-NOTES.md for release notes.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Fri, 03 Jun 2022 01:00:00 -0700
zerotier-one (1.8.10) unstable; urgency=medium
* See RELEASE-NOTES.md for release notes.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Tue, 10 May 2022 01:00:00 -0700
zerotier-one (1.8.9) unstable; urgency=medium
* See RELEASE-NOTES.md for release notes.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Mon, 25 Apr 2022 01:00:00 -0700
zerotier-one (1.8.8) unstable; urgency=medium
* See RELEASE-NOTES.md for release notes.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Mon, 11 Apr 2022 01:00:00 -0700
zerotier-one (1.8.7) unstable; urgency=medium
* See RELEASE-NOTES.md for release notes.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Mon, 21 Mar 2022 01:00:00 -0700
zerotier-one (1.8.6) unstable; urgency=medium
* See RELEASE-NOTES.md for release notes.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Mon, 07 Mar 2022 01:00:00 -0700
zerotier-one (1.8.5) unstable; urgency=medium
* See RELEASE-NOTES.md for release notes.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Fri, 17 Dec 2021 01:00:00 -0700
zerotier-one (1.8.4) unstable; urgency=medium
* See RELEASE-NOTES.md for release notes.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Mon, 23 Nov 2021 01:00:00 -0700
zerotier-one (1.8.3) unstable; urgency=medium
* See RELEASE-NOTES.md for release notes.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Mon, 15 Nov 2021 01:00:00 -0700
zerotier-one (1.8.2) unstable; urgency=medium
* See RELEASE-NOTES.md for release notes.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Mon, 08 Nov 2021 01:00:00 -0700
zerotier-one (1.8.1) unstable; urgency=medium
* See RELEASE-NOTES.md for release notes.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Wed, 20 Oct 2021 01:00:00 -0700
zerotier-one (1.8.0) unstable; urgency=medium
* See RELEASE-NOTES.md for release notes.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Wed, 15 Sep 2021 01:00:00 -0700
zerotier-one (1.6.6) unstable; urgency=medium
* Backport endpoint mitigation against address collision attack.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Tue, 21 Sep 2021 01:00:00 -0700
zerotier-one (1.6.5) unstable; urgency=medium
* Fix path filtering bug that could cause "software laser" effect.
* Fix printf overflow in CLI (not exploitable or security related)
* Fix Windows device enumeration issue.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Tue, 13 Apr 2021 01:00:00 -0700
zerotier-one (1.6.4) unstable; urgency=medium
* REALLY fix a problem causing nodes to go into a "coma" with some network configurations.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Tue, 15 Feb 2021 01:00:00 -0700
zerotier-one (1.6.3-1) unstable; urgency=medium
* Fix a problem causing nodes to go into a "coma" with some network configurations.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Tue, 02 Feb 2021 01:00:00 -0700
zerotier-one (1.6.2-2) unstable; urgency=medium
* This is a minor update to the 1.6.2 package to address issues with
running on ARMv6 CPUs like the Raspberry Pi Zero and original v1 Pi.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Tue, 31 Nov 2020 01:00:00 -0700
zerotier-one (1.6.2) unstable; urgency=medium
* See RELEASE-NOTES.md for release notes.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Mon, 30 Nov 2020 01:00:00 -0700
zerotier-one (1.6.1) unstable; urgency=medium
* See RELEASE-NOTES.md for release notes.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Tue, 24 Nov 2020 01:00:00 -0700
zerotier-one (1.6.0) unstable; urgency=medium
* See RELEASE-NOTES.md for release notes.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Thu, 19 Nov 2020 01:00:00 -0700
zerotier-one (1.5.0) unstable; urgency=medium
* Version 1.5.0 is actually 1.6.0-beta1
* See RELEASE-NOTES.md for release notes.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Mon, 05 Aug 2020 01:00:00 -0700
zerotier-one (1.4.6) unstable; urgency=medium
* Update default root server list
* Fix build flags on "armhf" (32-bit ARM) platforms for better
compatibility with Pi Zero and other devices.
* Fix license text in one.cpp.
* Add a clarification to LICENSE.txt.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Fri, 30 Aug 2019 01:00:00 -0700
zerotier-one (1.4.4) unstable; urgency=medium
* See https://github.com/zerotier/ZeroTierOne for release notes.
* License changed to BSL 1.1
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Fri, 23 Aug 2019 01:00:00 -0700
zerotier-one (1.4.2-2) unstable; urgency=medium
* See https://github.com/zerotier/ZeroTierOne for release notes.
* This is a new build that fixes a binary build issue with containers and SELinux
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Thu, 04 Aug 2019 01:00:00 -0700
zerotier-one (1.4.2) unstable; urgency=medium
* See https://github.com/zerotier/ZeroTierOne for release notes.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Thu, 04 Aug 2019 01:00:00 -0700
zerotier-one (1.4.0) unstable; urgency=medium
* See https://github.com/zerotier/ZeroTierOne for release notes.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Thu, 29 Jul 2019 01:00:00 -0700
# Older entries have been removed from this changelog.
# To read the complete changelog use `apt changelog backone-addon-chat`.
Whitespace-only changes.
@@ -0,0 +1,257 @@
backone (1.17.1) stable; urgency=medium
* Fix /api/backone routes and reinstallable Web UI packages
-- Dedy Sutanto <dsutanto@backone.cloud> Tue, 30 Jun 2026 12:02:07 +0700
backone (1.17.0) stable; urgency=medium
* Tray app, auto web server, peer contact UI
-- Dedy Sutanto <dsutanto@backone.cloud> Tue, 30 Jun 2026 09:55:07 +0700
backone (1.16.0) stable; urgency=medium
* Release notes
-- Dedy Sutanto <dsutanto@backone.cloud> Tue, 30 Jun 2026 08:59:49 +0700
backone (1.15.0) stable; urgency=medium
* Separated addon architecture: voice, video, chat, updater
-- Dedy Sutanto <dsutanto@backone.cloud> Tue, 30 Jun 2026 08:37:16 +0700
zerotier-one (1.14.1) unstable; urgency=medium
* See RELEASE-NOTES.md for release notes.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Wed, 11 Sep 2024 01:00:00 -0700
zerotier-one (1.14.0) unstable; urgency=medium
* See RELEASE-NOTES.md for release notes.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Tue, 19 Mar 2024 01:00:00 -0700
zerotier-one (1.12.2) unstable; urgency=medium
* See RELEASE-NOTES.md for release notes.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Tue, 12 Sep 2023 01:00:00 -0700
zerotier-one (1.12.1) unstable; urgency=medium
* See RELEASE-NOTES.md for release notes.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Fri, 25 Aug 2023 01:00:00 -0700
zerotier-one (1.12.0) unstable; urgency=medium
* See RELEASE-NOTES.md for release notes.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Thu, 17 Aug 2023 01:00:00 -0700
zerotier-one (1.10.6) unstable; urgency=medium
* See RELEASE-NOTES.md for release notes.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Tue, 21 Mar 2023 01:00:00 -0700
zerotier-one (1.10.5) unstable; urgency=medium
* See RELEASE-NOTES.md for release notes.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Fri, 10 Mar 2023 01:00:00 -0700
zerotier-one (1.10.4) unstable; urgency=medium
* See RELEASE-NOTES.md for release notes.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Mon, 06 Mar 2023 01:00:00 -0700
zerotier-one (1.10.3) unstable; urgency=medium
* See RELEASE-NOTES.md for release notes.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Sat, 21 Jan 2023 01:00:00 -0700
zerotier-one (1.10.2) unstable; urgency=medium
* See RELEASE-NOTES.md for release notes.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Thu, 13 Oct 2022 01:00:00 -0700
zerotier-one (1.10.1) unstable; urgency=medium
* See RELEASE-NOTES.md for release notes.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Mon, 27 Jun 2022 01:00:00 -0700
zerotier-one (1.10.0) unstable; urgency=medium
* See RELEASE-NOTES.md for release notes.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Fri, 03 Jun 2022 01:00:00 -0700
zerotier-one (1.8.10) unstable; urgency=medium
* See RELEASE-NOTES.md for release notes.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Tue, 10 May 2022 01:00:00 -0700
zerotier-one (1.8.9) unstable; urgency=medium
* See RELEASE-NOTES.md for release notes.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Mon, 25 Apr 2022 01:00:00 -0700
zerotier-one (1.8.8) unstable; urgency=medium
* See RELEASE-NOTES.md for release notes.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Mon, 11 Apr 2022 01:00:00 -0700
zerotier-one (1.8.7) unstable; urgency=medium
* See RELEASE-NOTES.md for release notes.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Mon, 21 Mar 2022 01:00:00 -0700
zerotier-one (1.8.6) unstable; urgency=medium
* See RELEASE-NOTES.md for release notes.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Mon, 07 Mar 2022 01:00:00 -0700
zerotier-one (1.8.5) unstable; urgency=medium
* See RELEASE-NOTES.md for release notes.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Fri, 17 Dec 2021 01:00:00 -0700
zerotier-one (1.8.4) unstable; urgency=medium
* See RELEASE-NOTES.md for release notes.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Mon, 23 Nov 2021 01:00:00 -0700
zerotier-one (1.8.3) unstable; urgency=medium
* See RELEASE-NOTES.md for release notes.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Mon, 15 Nov 2021 01:00:00 -0700
zerotier-one (1.8.2) unstable; urgency=medium
* See RELEASE-NOTES.md for release notes.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Mon, 08 Nov 2021 01:00:00 -0700
zerotier-one (1.8.1) unstable; urgency=medium
* See RELEASE-NOTES.md for release notes.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Wed, 20 Oct 2021 01:00:00 -0700
zerotier-one (1.8.0) unstable; urgency=medium
* See RELEASE-NOTES.md for release notes.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Wed, 15 Sep 2021 01:00:00 -0700
zerotier-one (1.6.6) unstable; urgency=medium
* Backport endpoint mitigation against address collision attack.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Tue, 21 Sep 2021 01:00:00 -0700
zerotier-one (1.6.5) unstable; urgency=medium
* Fix path filtering bug that could cause "software laser" effect.
* Fix printf overflow in CLI (not exploitable or security related)
* Fix Windows device enumeration issue.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Tue, 13 Apr 2021 01:00:00 -0700
zerotier-one (1.6.4) unstable; urgency=medium
* REALLY fix a problem causing nodes to go into a "coma" with some network configurations.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Tue, 15 Feb 2021 01:00:00 -0700
zerotier-one (1.6.3-1) unstable; urgency=medium
* Fix a problem causing nodes to go into a "coma" with some network configurations.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Tue, 02 Feb 2021 01:00:00 -0700
zerotier-one (1.6.2-2) unstable; urgency=medium
* This is a minor update to the 1.6.2 package to address issues with
running on ARMv6 CPUs like the Raspberry Pi Zero and original v1 Pi.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Tue, 31 Nov 2020 01:00:00 -0700
zerotier-one (1.6.2) unstable; urgency=medium
* See RELEASE-NOTES.md for release notes.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Mon, 30 Nov 2020 01:00:00 -0700
zerotier-one (1.6.1) unstable; urgency=medium
* See RELEASE-NOTES.md for release notes.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Tue, 24 Nov 2020 01:00:00 -0700
zerotier-one (1.6.0) unstable; urgency=medium
* See RELEASE-NOTES.md for release notes.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Thu, 19 Nov 2020 01:00:00 -0700
zerotier-one (1.5.0) unstable; urgency=medium
* Version 1.5.0 is actually 1.6.0-beta1
* See RELEASE-NOTES.md for release notes.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Mon, 05 Aug 2020 01:00:00 -0700
zerotier-one (1.4.6) unstable; urgency=medium
* Update default root server list
* Fix build flags on "armhf" (32-bit ARM) platforms for better
compatibility with Pi Zero and other devices.
* Fix license text in one.cpp.
* Add a clarification to LICENSE.txt.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Fri, 30 Aug 2019 01:00:00 -0700
zerotier-one (1.4.4) unstable; urgency=medium
* See https://github.com/zerotier/ZeroTierOne for release notes.
* License changed to BSL 1.1
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Fri, 23 Aug 2019 01:00:00 -0700
zerotier-one (1.4.2-2) unstable; urgency=medium
* See https://github.com/zerotier/ZeroTierOne for release notes.
* This is a new build that fixes a binary build issue with containers and SELinux
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Thu, 04 Aug 2019 01:00:00 -0700
zerotier-one (1.4.2) unstable; urgency=medium
* See https://github.com/zerotier/ZeroTierOne for release notes.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Thu, 04 Aug 2019 01:00:00 -0700
zerotier-one (1.4.0) unstable; urgency=medium
* See https://github.com/zerotier/ZeroTierOne for release notes.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Thu, 29 Jul 2019 01:00:00 -0700
# Older entries have been removed from this changelog.
# To read the complete changelog use `apt changelog backone-addon-comms-core`.
Whitespace-only changes.
@@ -0,0 +1,257 @@
backone (1.17.1) stable; urgency=medium
* Fix /api/backone routes and reinstallable Web UI packages
-- Dedy Sutanto <dsutanto@backone.cloud> Tue, 30 Jun 2026 12:02:07 +0700
backone (1.17.0) stable; urgency=medium
* Tray app, auto web server, peer contact UI
-- Dedy Sutanto <dsutanto@backone.cloud> Tue, 30 Jun 2026 09:55:07 +0700
backone (1.16.0) stable; urgency=medium
* Release notes
-- Dedy Sutanto <dsutanto@backone.cloud> Tue, 30 Jun 2026 08:59:49 +0700
backone (1.15.0) stable; urgency=medium
* Separated addon architecture: voice, video, chat, updater
-- Dedy Sutanto <dsutanto@backone.cloud> Tue, 30 Jun 2026 08:37:16 +0700
zerotier-one (1.14.1) unstable; urgency=medium
* See RELEASE-NOTES.md for release notes.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Wed, 11 Sep 2024 01:00:00 -0700
zerotier-one (1.14.0) unstable; urgency=medium
* See RELEASE-NOTES.md for release notes.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Tue, 19 Mar 2024 01:00:00 -0700
zerotier-one (1.12.2) unstable; urgency=medium
* See RELEASE-NOTES.md for release notes.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Tue, 12 Sep 2023 01:00:00 -0700
zerotier-one (1.12.1) unstable; urgency=medium
* See RELEASE-NOTES.md for release notes.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Fri, 25 Aug 2023 01:00:00 -0700
zerotier-one (1.12.0) unstable; urgency=medium
* See RELEASE-NOTES.md for release notes.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Thu, 17 Aug 2023 01:00:00 -0700
zerotier-one (1.10.6) unstable; urgency=medium
* See RELEASE-NOTES.md for release notes.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Tue, 21 Mar 2023 01:00:00 -0700
zerotier-one (1.10.5) unstable; urgency=medium
* See RELEASE-NOTES.md for release notes.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Fri, 10 Mar 2023 01:00:00 -0700
zerotier-one (1.10.4) unstable; urgency=medium
* See RELEASE-NOTES.md for release notes.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Mon, 06 Mar 2023 01:00:00 -0700
zerotier-one (1.10.3) unstable; urgency=medium
* See RELEASE-NOTES.md for release notes.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Sat, 21 Jan 2023 01:00:00 -0700
zerotier-one (1.10.2) unstable; urgency=medium
* See RELEASE-NOTES.md for release notes.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Thu, 13 Oct 2022 01:00:00 -0700
zerotier-one (1.10.1) unstable; urgency=medium
* See RELEASE-NOTES.md for release notes.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Mon, 27 Jun 2022 01:00:00 -0700
zerotier-one (1.10.0) unstable; urgency=medium
* See RELEASE-NOTES.md for release notes.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Fri, 03 Jun 2022 01:00:00 -0700
zerotier-one (1.8.10) unstable; urgency=medium
* See RELEASE-NOTES.md for release notes.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Tue, 10 May 2022 01:00:00 -0700
zerotier-one (1.8.9) unstable; urgency=medium
* See RELEASE-NOTES.md for release notes.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Mon, 25 Apr 2022 01:00:00 -0700
zerotier-one (1.8.8) unstable; urgency=medium
* See RELEASE-NOTES.md for release notes.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Mon, 11 Apr 2022 01:00:00 -0700
zerotier-one (1.8.7) unstable; urgency=medium
* See RELEASE-NOTES.md for release notes.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Mon, 21 Mar 2022 01:00:00 -0700
zerotier-one (1.8.6) unstable; urgency=medium
* See RELEASE-NOTES.md for release notes.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Mon, 07 Mar 2022 01:00:00 -0700
zerotier-one (1.8.5) unstable; urgency=medium
* See RELEASE-NOTES.md for release notes.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Fri, 17 Dec 2021 01:00:00 -0700
zerotier-one (1.8.4) unstable; urgency=medium
* See RELEASE-NOTES.md for release notes.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Mon, 23 Nov 2021 01:00:00 -0700
zerotier-one (1.8.3) unstable; urgency=medium
* See RELEASE-NOTES.md for release notes.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Mon, 15 Nov 2021 01:00:00 -0700
zerotier-one (1.8.2) unstable; urgency=medium
* See RELEASE-NOTES.md for release notes.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Mon, 08 Nov 2021 01:00:00 -0700
zerotier-one (1.8.1) unstable; urgency=medium
* See RELEASE-NOTES.md for release notes.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Wed, 20 Oct 2021 01:00:00 -0700
zerotier-one (1.8.0) unstable; urgency=medium
* See RELEASE-NOTES.md for release notes.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Wed, 15 Sep 2021 01:00:00 -0700
zerotier-one (1.6.6) unstable; urgency=medium
* Backport endpoint mitigation against address collision attack.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Tue, 21 Sep 2021 01:00:00 -0700
zerotier-one (1.6.5) unstable; urgency=medium
* Fix path filtering bug that could cause "software laser" effect.
* Fix printf overflow in CLI (not exploitable or security related)
* Fix Windows device enumeration issue.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Tue, 13 Apr 2021 01:00:00 -0700
zerotier-one (1.6.4) unstable; urgency=medium
* REALLY fix a problem causing nodes to go into a "coma" with some network configurations.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Tue, 15 Feb 2021 01:00:00 -0700
zerotier-one (1.6.3-1) unstable; urgency=medium
* Fix a problem causing nodes to go into a "coma" with some network configurations.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Tue, 02 Feb 2021 01:00:00 -0700
zerotier-one (1.6.2-2) unstable; urgency=medium
* This is a minor update to the 1.6.2 package to address issues with
running on ARMv6 CPUs like the Raspberry Pi Zero and original v1 Pi.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Tue, 31 Nov 2020 01:00:00 -0700
zerotier-one (1.6.2) unstable; urgency=medium
* See RELEASE-NOTES.md for release notes.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Mon, 30 Nov 2020 01:00:00 -0700
zerotier-one (1.6.1) unstable; urgency=medium
* See RELEASE-NOTES.md for release notes.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Tue, 24 Nov 2020 01:00:00 -0700
zerotier-one (1.6.0) unstable; urgency=medium
* See RELEASE-NOTES.md for release notes.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Thu, 19 Nov 2020 01:00:00 -0700
zerotier-one (1.5.0) unstable; urgency=medium
* Version 1.5.0 is actually 1.6.0-beta1
* See RELEASE-NOTES.md for release notes.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Mon, 05 Aug 2020 01:00:00 -0700
zerotier-one (1.4.6) unstable; urgency=medium
* Update default root server list
* Fix build flags on "armhf" (32-bit ARM) platforms for better
compatibility with Pi Zero and other devices.
* Fix license text in one.cpp.
* Add a clarification to LICENSE.txt.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Fri, 30 Aug 2019 01:00:00 -0700
zerotier-one (1.4.4) unstable; urgency=medium
* See https://github.com/zerotier/ZeroTierOne for release notes.
* License changed to BSL 1.1
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Fri, 23 Aug 2019 01:00:00 -0700
zerotier-one (1.4.2-2) unstable; urgency=medium
* See https://github.com/zerotier/ZeroTierOne for release notes.
* This is a new build that fixes a binary build issue with containers and SELinux
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Thu, 04 Aug 2019 01:00:00 -0700
zerotier-one (1.4.2) unstable; urgency=medium
* See https://github.com/zerotier/ZeroTierOne for release notes.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Thu, 04 Aug 2019 01:00:00 -0700
zerotier-one (1.4.0) unstable; urgency=medium
* See https://github.com/zerotier/ZeroTierOne for release notes.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Thu, 29 Jul 2019 01:00:00 -0700
# Older entries have been removed from this changelog.
# To read the complete changelog use `apt changelog backone-addon-updater`.
Whitespace-only changes.
@@ -0,0 +1,257 @@
backone (1.17.1) stable; urgency=medium
* Fix /api/backone routes and reinstallable Web UI packages
-- Dedy Sutanto <dsutanto@backone.cloud> Tue, 30 Jun 2026 12:02:07 +0700
backone (1.17.0) stable; urgency=medium
* Tray app, auto web server, peer contact UI
-- Dedy Sutanto <dsutanto@backone.cloud> Tue, 30 Jun 2026 09:55:07 +0700
backone (1.16.0) stable; urgency=medium
* Release notes
-- Dedy Sutanto <dsutanto@backone.cloud> Tue, 30 Jun 2026 08:59:49 +0700
backone (1.15.0) stable; urgency=medium
* Separated addon architecture: voice, video, chat, updater
-- Dedy Sutanto <dsutanto@backone.cloud> Tue, 30 Jun 2026 08:37:16 +0700
zerotier-one (1.14.1) unstable; urgency=medium
* See RELEASE-NOTES.md for release notes.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Wed, 11 Sep 2024 01:00:00 -0700
zerotier-one (1.14.0) unstable; urgency=medium
* See RELEASE-NOTES.md for release notes.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Tue, 19 Mar 2024 01:00:00 -0700
zerotier-one (1.12.2) unstable; urgency=medium
* See RELEASE-NOTES.md for release notes.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Tue, 12 Sep 2023 01:00:00 -0700
zerotier-one (1.12.1) unstable; urgency=medium
* See RELEASE-NOTES.md for release notes.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Fri, 25 Aug 2023 01:00:00 -0700
zerotier-one (1.12.0) unstable; urgency=medium
* See RELEASE-NOTES.md for release notes.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Thu, 17 Aug 2023 01:00:00 -0700
zerotier-one (1.10.6) unstable; urgency=medium
* See RELEASE-NOTES.md for release notes.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Tue, 21 Mar 2023 01:00:00 -0700
zerotier-one (1.10.5) unstable; urgency=medium
* See RELEASE-NOTES.md for release notes.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Fri, 10 Mar 2023 01:00:00 -0700
zerotier-one (1.10.4) unstable; urgency=medium
* See RELEASE-NOTES.md for release notes.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Mon, 06 Mar 2023 01:00:00 -0700
zerotier-one (1.10.3) unstable; urgency=medium
* See RELEASE-NOTES.md for release notes.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Sat, 21 Jan 2023 01:00:00 -0700
zerotier-one (1.10.2) unstable; urgency=medium
* See RELEASE-NOTES.md for release notes.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Thu, 13 Oct 2022 01:00:00 -0700
zerotier-one (1.10.1) unstable; urgency=medium
* See RELEASE-NOTES.md for release notes.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Mon, 27 Jun 2022 01:00:00 -0700
zerotier-one (1.10.0) unstable; urgency=medium
* See RELEASE-NOTES.md for release notes.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Fri, 03 Jun 2022 01:00:00 -0700
zerotier-one (1.8.10) unstable; urgency=medium
* See RELEASE-NOTES.md for release notes.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Tue, 10 May 2022 01:00:00 -0700
zerotier-one (1.8.9) unstable; urgency=medium
* See RELEASE-NOTES.md for release notes.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Mon, 25 Apr 2022 01:00:00 -0700
zerotier-one (1.8.8) unstable; urgency=medium
* See RELEASE-NOTES.md for release notes.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Mon, 11 Apr 2022 01:00:00 -0700
zerotier-one (1.8.7) unstable; urgency=medium
* See RELEASE-NOTES.md for release notes.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Mon, 21 Mar 2022 01:00:00 -0700
zerotier-one (1.8.6) unstable; urgency=medium
* See RELEASE-NOTES.md for release notes.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Mon, 07 Mar 2022 01:00:00 -0700
zerotier-one (1.8.5) unstable; urgency=medium
* See RELEASE-NOTES.md for release notes.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Fri, 17 Dec 2021 01:00:00 -0700
zerotier-one (1.8.4) unstable; urgency=medium
* See RELEASE-NOTES.md for release notes.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Mon, 23 Nov 2021 01:00:00 -0700
zerotier-one (1.8.3) unstable; urgency=medium
* See RELEASE-NOTES.md for release notes.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Mon, 15 Nov 2021 01:00:00 -0700
zerotier-one (1.8.2) unstable; urgency=medium
* See RELEASE-NOTES.md for release notes.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Mon, 08 Nov 2021 01:00:00 -0700
zerotier-one (1.8.1) unstable; urgency=medium
* See RELEASE-NOTES.md for release notes.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Wed, 20 Oct 2021 01:00:00 -0700
zerotier-one (1.8.0) unstable; urgency=medium
* See RELEASE-NOTES.md for release notes.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Wed, 15 Sep 2021 01:00:00 -0700
zerotier-one (1.6.6) unstable; urgency=medium
* Backport endpoint mitigation against address collision attack.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Tue, 21 Sep 2021 01:00:00 -0700
zerotier-one (1.6.5) unstable; urgency=medium
* Fix path filtering bug that could cause "software laser" effect.
* Fix printf overflow in CLI (not exploitable or security related)
* Fix Windows device enumeration issue.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Tue, 13 Apr 2021 01:00:00 -0700
zerotier-one (1.6.4) unstable; urgency=medium
* REALLY fix a problem causing nodes to go into a "coma" with some network configurations.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Tue, 15 Feb 2021 01:00:00 -0700
zerotier-one (1.6.3-1) unstable; urgency=medium
* Fix a problem causing nodes to go into a "coma" with some network configurations.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Tue, 02 Feb 2021 01:00:00 -0700
zerotier-one (1.6.2-2) unstable; urgency=medium
* This is a minor update to the 1.6.2 package to address issues with
running on ARMv6 CPUs like the Raspberry Pi Zero and original v1 Pi.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Tue, 31 Nov 2020 01:00:00 -0700
zerotier-one (1.6.2) unstable; urgency=medium
* See RELEASE-NOTES.md for release notes.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Mon, 30 Nov 2020 01:00:00 -0700
zerotier-one (1.6.1) unstable; urgency=medium
* See RELEASE-NOTES.md for release notes.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Tue, 24 Nov 2020 01:00:00 -0700
zerotier-one (1.6.0) unstable; urgency=medium
* See RELEASE-NOTES.md for release notes.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Thu, 19 Nov 2020 01:00:00 -0700
zerotier-one (1.5.0) unstable; urgency=medium
* Version 1.5.0 is actually 1.6.0-beta1
* See RELEASE-NOTES.md for release notes.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Mon, 05 Aug 2020 01:00:00 -0700
zerotier-one (1.4.6) unstable; urgency=medium
* Update default root server list
* Fix build flags on "armhf" (32-bit ARM) platforms for better
compatibility with Pi Zero and other devices.
* Fix license text in one.cpp.
* Add a clarification to LICENSE.txt.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Fri, 30 Aug 2019 01:00:00 -0700
zerotier-one (1.4.4) unstable; urgency=medium
* See https://github.com/zerotier/ZeroTierOne for release notes.
* License changed to BSL 1.1
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Fri, 23 Aug 2019 01:00:00 -0700
zerotier-one (1.4.2-2) unstable; urgency=medium
* See https://github.com/zerotier/ZeroTierOne for release notes.
* This is a new build that fixes a binary build issue with containers and SELinux
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Thu, 04 Aug 2019 01:00:00 -0700
zerotier-one (1.4.2) unstable; urgency=medium
* See https://github.com/zerotier/ZeroTierOne for release notes.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Thu, 04 Aug 2019 01:00:00 -0700
zerotier-one (1.4.0) unstable; urgency=medium
* See https://github.com/zerotier/ZeroTierOne for release notes.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Thu, 29 Jul 2019 01:00:00 -0700
# Older entries have been removed from this changelog.
# To read the complete changelog use `apt changelog backone-addon-video`.
Whitespace-only changes.
@@ -0,0 +1,257 @@
backone (1.17.1) stable; urgency=medium
* Fix /api/backone routes and reinstallable Web UI packages
-- Dedy Sutanto <dsutanto@backone.cloud> Tue, 30 Jun 2026 12:02:07 +0700
backone (1.17.0) stable; urgency=medium
* Tray app, auto web server, peer contact UI
-- Dedy Sutanto <dsutanto@backone.cloud> Tue, 30 Jun 2026 09:55:07 +0700
backone (1.16.0) stable; urgency=medium
* Release notes
-- Dedy Sutanto <dsutanto@backone.cloud> Tue, 30 Jun 2026 08:59:49 +0700
backone (1.15.0) stable; urgency=medium
* Separated addon architecture: voice, video, chat, updater
-- Dedy Sutanto <dsutanto@backone.cloud> Tue, 30 Jun 2026 08:37:16 +0700
zerotier-one (1.14.1) unstable; urgency=medium
* See RELEASE-NOTES.md for release notes.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Wed, 11 Sep 2024 01:00:00 -0700
zerotier-one (1.14.0) unstable; urgency=medium
* See RELEASE-NOTES.md for release notes.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Tue, 19 Mar 2024 01:00:00 -0700
zerotier-one (1.12.2) unstable; urgency=medium
* See RELEASE-NOTES.md for release notes.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Tue, 12 Sep 2023 01:00:00 -0700
zerotier-one (1.12.1) unstable; urgency=medium
* See RELEASE-NOTES.md for release notes.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Fri, 25 Aug 2023 01:00:00 -0700
zerotier-one (1.12.0) unstable; urgency=medium
* See RELEASE-NOTES.md for release notes.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Thu, 17 Aug 2023 01:00:00 -0700
zerotier-one (1.10.6) unstable; urgency=medium
* See RELEASE-NOTES.md for release notes.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Tue, 21 Mar 2023 01:00:00 -0700
zerotier-one (1.10.5) unstable; urgency=medium
* See RELEASE-NOTES.md for release notes.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Fri, 10 Mar 2023 01:00:00 -0700
zerotier-one (1.10.4) unstable; urgency=medium
* See RELEASE-NOTES.md for release notes.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Mon, 06 Mar 2023 01:00:00 -0700
zerotier-one (1.10.3) unstable; urgency=medium
* See RELEASE-NOTES.md for release notes.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Sat, 21 Jan 2023 01:00:00 -0700
zerotier-one (1.10.2) unstable; urgency=medium
* See RELEASE-NOTES.md for release notes.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Thu, 13 Oct 2022 01:00:00 -0700
zerotier-one (1.10.1) unstable; urgency=medium
* See RELEASE-NOTES.md for release notes.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Mon, 27 Jun 2022 01:00:00 -0700
zerotier-one (1.10.0) unstable; urgency=medium
* See RELEASE-NOTES.md for release notes.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Fri, 03 Jun 2022 01:00:00 -0700
zerotier-one (1.8.10) unstable; urgency=medium
* See RELEASE-NOTES.md for release notes.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Tue, 10 May 2022 01:00:00 -0700
zerotier-one (1.8.9) unstable; urgency=medium
* See RELEASE-NOTES.md for release notes.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Mon, 25 Apr 2022 01:00:00 -0700
zerotier-one (1.8.8) unstable; urgency=medium
* See RELEASE-NOTES.md for release notes.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Mon, 11 Apr 2022 01:00:00 -0700
zerotier-one (1.8.7) unstable; urgency=medium
* See RELEASE-NOTES.md for release notes.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Mon, 21 Mar 2022 01:00:00 -0700
zerotier-one (1.8.6) unstable; urgency=medium
* See RELEASE-NOTES.md for release notes.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Mon, 07 Mar 2022 01:00:00 -0700
zerotier-one (1.8.5) unstable; urgency=medium
* See RELEASE-NOTES.md for release notes.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Fri, 17 Dec 2021 01:00:00 -0700
zerotier-one (1.8.4) unstable; urgency=medium
* See RELEASE-NOTES.md for release notes.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Mon, 23 Nov 2021 01:00:00 -0700
zerotier-one (1.8.3) unstable; urgency=medium
* See RELEASE-NOTES.md for release notes.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Mon, 15 Nov 2021 01:00:00 -0700
zerotier-one (1.8.2) unstable; urgency=medium
* See RELEASE-NOTES.md for release notes.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Mon, 08 Nov 2021 01:00:00 -0700
zerotier-one (1.8.1) unstable; urgency=medium
* See RELEASE-NOTES.md for release notes.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Wed, 20 Oct 2021 01:00:00 -0700
zerotier-one (1.8.0) unstable; urgency=medium
* See RELEASE-NOTES.md for release notes.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Wed, 15 Sep 2021 01:00:00 -0700
zerotier-one (1.6.6) unstable; urgency=medium
* Backport endpoint mitigation against address collision attack.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Tue, 21 Sep 2021 01:00:00 -0700
zerotier-one (1.6.5) unstable; urgency=medium
* Fix path filtering bug that could cause "software laser" effect.
* Fix printf overflow in CLI (not exploitable or security related)
* Fix Windows device enumeration issue.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Tue, 13 Apr 2021 01:00:00 -0700
zerotier-one (1.6.4) unstable; urgency=medium
* REALLY fix a problem causing nodes to go into a "coma" with some network configurations.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Tue, 15 Feb 2021 01:00:00 -0700
zerotier-one (1.6.3-1) unstable; urgency=medium
* Fix a problem causing nodes to go into a "coma" with some network configurations.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Tue, 02 Feb 2021 01:00:00 -0700
zerotier-one (1.6.2-2) unstable; urgency=medium
* This is a minor update to the 1.6.2 package to address issues with
running on ARMv6 CPUs like the Raspberry Pi Zero and original v1 Pi.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Tue, 31 Nov 2020 01:00:00 -0700
zerotier-one (1.6.2) unstable; urgency=medium
* See RELEASE-NOTES.md for release notes.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Mon, 30 Nov 2020 01:00:00 -0700
zerotier-one (1.6.1) unstable; urgency=medium
* See RELEASE-NOTES.md for release notes.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Tue, 24 Nov 2020 01:00:00 -0700
zerotier-one (1.6.0) unstable; urgency=medium
* See RELEASE-NOTES.md for release notes.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Thu, 19 Nov 2020 01:00:00 -0700
zerotier-one (1.5.0) unstable; urgency=medium
* Version 1.5.0 is actually 1.6.0-beta1
* See RELEASE-NOTES.md for release notes.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Mon, 05 Aug 2020 01:00:00 -0700
zerotier-one (1.4.6) unstable; urgency=medium
* Update default root server list
* Fix build flags on "armhf" (32-bit ARM) platforms for better
compatibility with Pi Zero and other devices.
* Fix license text in one.cpp.
* Add a clarification to LICENSE.txt.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Fri, 30 Aug 2019 01:00:00 -0700
zerotier-one (1.4.4) unstable; urgency=medium
* See https://github.com/zerotier/ZeroTierOne for release notes.
* License changed to BSL 1.1
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Fri, 23 Aug 2019 01:00:00 -0700
zerotier-one (1.4.2-2) unstable; urgency=medium
* See https://github.com/zerotier/ZeroTierOne for release notes.
* This is a new build that fixes a binary build issue with containers and SELinux
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Thu, 04 Aug 2019 01:00:00 -0700
zerotier-one (1.4.2) unstable; urgency=medium
* See https://github.com/zerotier/ZeroTierOne for release notes.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Thu, 04 Aug 2019 01:00:00 -0700
zerotier-one (1.4.0) unstable; urgency=medium
* See https://github.com/zerotier/ZeroTierOne for release notes.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Thu, 29 Jul 2019 01:00:00 -0700
# Older entries have been removed from this changelog.
# To read the complete changelog use `apt changelog backone-addon-voice`.
Whitespace-only changes.
@@ -0,0 +1,257 @@
backone (1.17.1) stable; urgency=medium
* Fix /api/backone routes and reinstallable Web UI packages
-- Dedy Sutanto <dsutanto@backone.cloud> Tue, 30 Jun 2026 12:02:07 +0700
backone (1.17.0) stable; urgency=medium
* Tray app, auto web server, peer contact UI
-- Dedy Sutanto <dsutanto@backone.cloud> Tue, 30 Jun 2026 09:55:07 +0700
backone (1.16.0) stable; urgency=medium
* Release notes
-- Dedy Sutanto <dsutanto@backone.cloud> Tue, 30 Jun 2026 08:59:49 +0700
backone (1.15.0) stable; urgency=medium
* Separated addon architecture: voice, video, chat, updater
-- Dedy Sutanto <dsutanto@backone.cloud> Tue, 30 Jun 2026 08:37:16 +0700
zerotier-one (1.14.1) unstable; urgency=medium
* See RELEASE-NOTES.md for release notes.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Wed, 11 Sep 2024 01:00:00 -0700
zerotier-one (1.14.0) unstable; urgency=medium
* See RELEASE-NOTES.md for release notes.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Tue, 19 Mar 2024 01:00:00 -0700
zerotier-one (1.12.2) unstable; urgency=medium
* See RELEASE-NOTES.md for release notes.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Tue, 12 Sep 2023 01:00:00 -0700
zerotier-one (1.12.1) unstable; urgency=medium
* See RELEASE-NOTES.md for release notes.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Fri, 25 Aug 2023 01:00:00 -0700
zerotier-one (1.12.0) unstable; urgency=medium
* See RELEASE-NOTES.md for release notes.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Thu, 17 Aug 2023 01:00:00 -0700
zerotier-one (1.10.6) unstable; urgency=medium
* See RELEASE-NOTES.md for release notes.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Tue, 21 Mar 2023 01:00:00 -0700
zerotier-one (1.10.5) unstable; urgency=medium
* See RELEASE-NOTES.md for release notes.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Fri, 10 Mar 2023 01:00:00 -0700
zerotier-one (1.10.4) unstable; urgency=medium
* See RELEASE-NOTES.md for release notes.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Mon, 06 Mar 2023 01:00:00 -0700
zerotier-one (1.10.3) unstable; urgency=medium
* See RELEASE-NOTES.md for release notes.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Sat, 21 Jan 2023 01:00:00 -0700
zerotier-one (1.10.2) unstable; urgency=medium
* See RELEASE-NOTES.md for release notes.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Thu, 13 Oct 2022 01:00:00 -0700
zerotier-one (1.10.1) unstable; urgency=medium
* See RELEASE-NOTES.md for release notes.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Mon, 27 Jun 2022 01:00:00 -0700
zerotier-one (1.10.0) unstable; urgency=medium
* See RELEASE-NOTES.md for release notes.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Fri, 03 Jun 2022 01:00:00 -0700
zerotier-one (1.8.10) unstable; urgency=medium
* See RELEASE-NOTES.md for release notes.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Tue, 10 May 2022 01:00:00 -0700
zerotier-one (1.8.9) unstable; urgency=medium
* See RELEASE-NOTES.md for release notes.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Mon, 25 Apr 2022 01:00:00 -0700
zerotier-one (1.8.8) unstable; urgency=medium
* See RELEASE-NOTES.md for release notes.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Mon, 11 Apr 2022 01:00:00 -0700
zerotier-one (1.8.7) unstable; urgency=medium
* See RELEASE-NOTES.md for release notes.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Mon, 21 Mar 2022 01:00:00 -0700
zerotier-one (1.8.6) unstable; urgency=medium
* See RELEASE-NOTES.md for release notes.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Mon, 07 Mar 2022 01:00:00 -0700
zerotier-one (1.8.5) unstable; urgency=medium
* See RELEASE-NOTES.md for release notes.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Fri, 17 Dec 2021 01:00:00 -0700
zerotier-one (1.8.4) unstable; urgency=medium
* See RELEASE-NOTES.md for release notes.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Mon, 23 Nov 2021 01:00:00 -0700
zerotier-one (1.8.3) unstable; urgency=medium
* See RELEASE-NOTES.md for release notes.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Mon, 15 Nov 2021 01:00:00 -0700
zerotier-one (1.8.2) unstable; urgency=medium
* See RELEASE-NOTES.md for release notes.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Mon, 08 Nov 2021 01:00:00 -0700
zerotier-one (1.8.1) unstable; urgency=medium
* See RELEASE-NOTES.md for release notes.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Wed, 20 Oct 2021 01:00:00 -0700
zerotier-one (1.8.0) unstable; urgency=medium
* See RELEASE-NOTES.md for release notes.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Wed, 15 Sep 2021 01:00:00 -0700
zerotier-one (1.6.6) unstable; urgency=medium
* Backport endpoint mitigation against address collision attack.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Tue, 21 Sep 2021 01:00:00 -0700
zerotier-one (1.6.5) unstable; urgency=medium
* Fix path filtering bug that could cause "software laser" effect.
* Fix printf overflow in CLI (not exploitable or security related)
* Fix Windows device enumeration issue.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Tue, 13 Apr 2021 01:00:00 -0700
zerotier-one (1.6.4) unstable; urgency=medium
* REALLY fix a problem causing nodes to go into a "coma" with some network configurations.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Tue, 15 Feb 2021 01:00:00 -0700
zerotier-one (1.6.3-1) unstable; urgency=medium
* Fix a problem causing nodes to go into a "coma" with some network configurations.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Tue, 02 Feb 2021 01:00:00 -0700
zerotier-one (1.6.2-2) unstable; urgency=medium
* This is a minor update to the 1.6.2 package to address issues with
running on ARMv6 CPUs like the Raspberry Pi Zero and original v1 Pi.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Tue, 31 Nov 2020 01:00:00 -0700
zerotier-one (1.6.2) unstable; urgency=medium
* See RELEASE-NOTES.md for release notes.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Mon, 30 Nov 2020 01:00:00 -0700
zerotier-one (1.6.1) unstable; urgency=medium
* See RELEASE-NOTES.md for release notes.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Tue, 24 Nov 2020 01:00:00 -0700
zerotier-one (1.6.0) unstable; urgency=medium
* See RELEASE-NOTES.md for release notes.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Thu, 19 Nov 2020 01:00:00 -0700
zerotier-one (1.5.0) unstable; urgency=medium
* Version 1.5.0 is actually 1.6.0-beta1
* See RELEASE-NOTES.md for release notes.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Mon, 05 Aug 2020 01:00:00 -0700
zerotier-one (1.4.6) unstable; urgency=medium
* Update default root server list
* Fix build flags on "armhf" (32-bit ARM) platforms for better
compatibility with Pi Zero and other devices.
* Fix license text in one.cpp.
* Add a clarification to LICENSE.txt.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Fri, 30 Aug 2019 01:00:00 -0700
zerotier-one (1.4.4) unstable; urgency=medium
* See https://github.com/zerotier/ZeroTierOne for release notes.
* License changed to BSL 1.1
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Fri, 23 Aug 2019 01:00:00 -0700
zerotier-one (1.4.2-2) unstable; urgency=medium
* See https://github.com/zerotier/ZeroTierOne for release notes.
* This is a new build that fixes a binary build issue with containers and SELinux
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Thu, 04 Aug 2019 01:00:00 -0700
zerotier-one (1.4.2) unstable; urgency=medium
* See https://github.com/zerotier/ZeroTierOne for release notes.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Thu, 04 Aug 2019 01:00:00 -0700
zerotier-one (1.4.0) unstable; urgency=medium
* See https://github.com/zerotier/ZeroTierOne for release notes.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Thu, 29 Jul 2019 01:00:00 -0700
# Older entries have been removed from this changelog.
# To read the complete changelog use `apt changelog backone-app-shell`.
Whitespace-only changes.
@@ -0,0 +1,257 @@
backone (1.17.1) stable; urgency=medium
* Fix /api/backone routes and reinstallable Web UI packages
-- Dedy Sutanto <dsutanto@backone.cloud> Tue, 30 Jun 2026 12:02:07 +0700
backone (1.17.0) stable; urgency=medium
* Tray app, auto web server, peer contact UI
-- Dedy Sutanto <dsutanto@backone.cloud> Tue, 30 Jun 2026 09:55:07 +0700
backone (1.16.0) stable; urgency=medium
* Release notes
-- Dedy Sutanto <dsutanto@backone.cloud> Tue, 30 Jun 2026 08:59:49 +0700
backone (1.15.0) stable; urgency=medium
* Separated addon architecture: voice, video, chat, updater
-- Dedy Sutanto <dsutanto@backone.cloud> Tue, 30 Jun 2026 08:37:16 +0700
zerotier-one (1.14.1) unstable; urgency=medium
* See RELEASE-NOTES.md for release notes.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Wed, 11 Sep 2024 01:00:00 -0700
zerotier-one (1.14.0) unstable; urgency=medium
* See RELEASE-NOTES.md for release notes.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Tue, 19 Mar 2024 01:00:00 -0700
zerotier-one (1.12.2) unstable; urgency=medium
* See RELEASE-NOTES.md for release notes.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Tue, 12 Sep 2023 01:00:00 -0700
zerotier-one (1.12.1) unstable; urgency=medium
* See RELEASE-NOTES.md for release notes.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Fri, 25 Aug 2023 01:00:00 -0700
zerotier-one (1.12.0) unstable; urgency=medium
* See RELEASE-NOTES.md for release notes.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Thu, 17 Aug 2023 01:00:00 -0700
zerotier-one (1.10.6) unstable; urgency=medium
* See RELEASE-NOTES.md for release notes.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Tue, 21 Mar 2023 01:00:00 -0700
zerotier-one (1.10.5) unstable; urgency=medium
* See RELEASE-NOTES.md for release notes.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Fri, 10 Mar 2023 01:00:00 -0700
zerotier-one (1.10.4) unstable; urgency=medium
* See RELEASE-NOTES.md for release notes.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Mon, 06 Mar 2023 01:00:00 -0700
zerotier-one (1.10.3) unstable; urgency=medium
* See RELEASE-NOTES.md for release notes.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Sat, 21 Jan 2023 01:00:00 -0700
zerotier-one (1.10.2) unstable; urgency=medium
* See RELEASE-NOTES.md for release notes.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Thu, 13 Oct 2022 01:00:00 -0700
zerotier-one (1.10.1) unstable; urgency=medium
* See RELEASE-NOTES.md for release notes.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Mon, 27 Jun 2022 01:00:00 -0700
zerotier-one (1.10.0) unstable; urgency=medium
* See RELEASE-NOTES.md for release notes.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Fri, 03 Jun 2022 01:00:00 -0700
zerotier-one (1.8.10) unstable; urgency=medium
* See RELEASE-NOTES.md for release notes.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Tue, 10 May 2022 01:00:00 -0700
zerotier-one (1.8.9) unstable; urgency=medium
* See RELEASE-NOTES.md for release notes.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Mon, 25 Apr 2022 01:00:00 -0700
zerotier-one (1.8.8) unstable; urgency=medium
* See RELEASE-NOTES.md for release notes.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Mon, 11 Apr 2022 01:00:00 -0700
zerotier-one (1.8.7) unstable; urgency=medium
* See RELEASE-NOTES.md for release notes.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Mon, 21 Mar 2022 01:00:00 -0700
zerotier-one (1.8.6) unstable; urgency=medium
* See RELEASE-NOTES.md for release notes.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Mon, 07 Mar 2022 01:00:00 -0700
zerotier-one (1.8.5) unstable; urgency=medium
* See RELEASE-NOTES.md for release notes.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Fri, 17 Dec 2021 01:00:00 -0700
zerotier-one (1.8.4) unstable; urgency=medium
* See RELEASE-NOTES.md for release notes.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Mon, 23 Nov 2021 01:00:00 -0700
zerotier-one (1.8.3) unstable; urgency=medium
* See RELEASE-NOTES.md for release notes.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Mon, 15 Nov 2021 01:00:00 -0700
zerotier-one (1.8.2) unstable; urgency=medium
* See RELEASE-NOTES.md for release notes.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Mon, 08 Nov 2021 01:00:00 -0700
zerotier-one (1.8.1) unstable; urgency=medium
* See RELEASE-NOTES.md for release notes.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Wed, 20 Oct 2021 01:00:00 -0700
zerotier-one (1.8.0) unstable; urgency=medium
* See RELEASE-NOTES.md for release notes.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Wed, 15 Sep 2021 01:00:00 -0700
zerotier-one (1.6.6) unstable; urgency=medium
* Backport endpoint mitigation against address collision attack.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Tue, 21 Sep 2021 01:00:00 -0700
zerotier-one (1.6.5) unstable; urgency=medium
* Fix path filtering bug that could cause "software laser" effect.
* Fix printf overflow in CLI (not exploitable or security related)
* Fix Windows device enumeration issue.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Tue, 13 Apr 2021 01:00:00 -0700
zerotier-one (1.6.4) unstable; urgency=medium
* REALLY fix a problem causing nodes to go into a "coma" with some network configurations.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Tue, 15 Feb 2021 01:00:00 -0700
zerotier-one (1.6.3-1) unstable; urgency=medium
* Fix a problem causing nodes to go into a "coma" with some network configurations.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Tue, 02 Feb 2021 01:00:00 -0700
zerotier-one (1.6.2-2) unstable; urgency=medium
* This is a minor update to the 1.6.2 package to address issues with
running on ARMv6 CPUs like the Raspberry Pi Zero and original v1 Pi.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Tue, 31 Nov 2020 01:00:00 -0700
zerotier-one (1.6.2) unstable; urgency=medium
* See RELEASE-NOTES.md for release notes.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Mon, 30 Nov 2020 01:00:00 -0700
zerotier-one (1.6.1) unstable; urgency=medium
* See RELEASE-NOTES.md for release notes.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Tue, 24 Nov 2020 01:00:00 -0700
zerotier-one (1.6.0) unstable; urgency=medium
* See RELEASE-NOTES.md for release notes.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Thu, 19 Nov 2020 01:00:00 -0700
zerotier-one (1.5.0) unstable; urgency=medium
* Version 1.5.0 is actually 1.6.0-beta1
* See RELEASE-NOTES.md for release notes.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Mon, 05 Aug 2020 01:00:00 -0700
zerotier-one (1.4.6) unstable; urgency=medium
* Update default root server list
* Fix build flags on "armhf" (32-bit ARM) platforms for better
compatibility with Pi Zero and other devices.
* Fix license text in one.cpp.
* Add a clarification to LICENSE.txt.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Fri, 30 Aug 2019 01:00:00 -0700
zerotier-one (1.4.4) unstable; urgency=medium
* See https://github.com/zerotier/ZeroTierOne for release notes.
* License changed to BSL 1.1
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Fri, 23 Aug 2019 01:00:00 -0700
zerotier-one (1.4.2-2) unstable; urgency=medium
* See https://github.com/zerotier/ZeroTierOne for release notes.
* This is a new build that fixes a binary build issue with containers and SELinux
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Thu, 04 Aug 2019 01:00:00 -0700
zerotier-one (1.4.2) unstable; urgency=medium
* See https://github.com/zerotier/ZeroTierOne for release notes.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Thu, 04 Aug 2019 01:00:00 -0700
zerotier-one (1.4.0) unstable; urgency=medium
* See https://github.com/zerotier/ZeroTierOne for release notes.
-- Adam Ierymenko <adam.ierymenko@zerotier.com> Thu, 29 Jul 2019 01:00:00 -0700
# Older entries have been removed from this changelog.
# To read the complete changelog use `apt changelog backone-app-tray`.
Whitespace-only changes.
+12
View File
@@ -0,0 +1,12 @@
#!/bin/sh -e
case "$1" in
configure)
if [ -x /usr/lib/backone/addons/updater/backone-updater ]; then
/usr/lib/backone/addons/updater/backone-updater rebuild-manifest || true
elif [ -x /usr/lib/backone/addons/updater/rebuild-addon-manifest.sh ]; then
BACKONE_HOME=/var/lib/backone /usr/lib/backone/addons/updater/rebuild-addon-manifest.sh || true
fi
;;
esac
#DEBHELPER#
exit 0
+2
View File
@@ -0,0 +1,2 @@
misc:Depends=
misc:Pre-Depends=
+12
View File
@@ -0,0 +1,12 @@
Package: backone-addon-chat
Source: backone
Version: 1.17.1
Architecture: all
Maintainer: Dedy Sutanto <dsutanto@backone.cloud>
Installed-Size: 28
Depends: backone, backone-addon-comms-core, backone-app-shell
Section: net
Priority: optional
Homepage: https://backone.cloud
Description: BackOne chat P2P addon
Text chat with image and document attachments over BackOne.
+3
View File
@@ -0,0 +1,3 @@
cb73fba9ab4a23ff5f4dc1e3fa7d6520 usr/lib/backone/addons/chat/addon.json
6020963c701ef2f1c4c4c8a78f2d6845 usr/share/doc/backone-addon-chat/changelog.gz
463936e9518d72d5ce8f85b746e0b8b5 var/lib/backone/app/addons/chat/index.js
+12
View File
@@ -0,0 +1,12 @@
#!/bin/sh -e
case "$1" in
configure)
if [ -x /usr/lib/backone/addons/updater/backone-updater ]; then
/usr/lib/backone/addons/updater/backone-updater rebuild-manifest || true
elif [ -x /usr/lib/backone/addons/updater/rebuild-addon-manifest.sh ]; then
BACKONE_HOME=/var/lib/backone /usr/lib/backone/addons/updater/rebuild-addon-manifest.sh || true
fi
;;
esac
exit 0
+12
View File
@@ -0,0 +1,12 @@
#!/bin/sh -e
case "$1" in
configure)
if [ -x /usr/lib/backone/addons/updater/backone-updater ]; then
/usr/lib/backone/addons/updater/backone-updater rebuild-manifest || true
elif [ -x /usr/lib/backone/addons/updater/rebuild-addon-manifest.sh ]; then
BACKONE_HOME=/var/lib/backone /usr/lib/backone/addons/updater/rebuild-addon-manifest.sh || true
fi
;;
esac
#DEBHELPER#
exit 0
+12
View File
@@ -0,0 +1,12 @@
#!/bin/sh -e
case "$1" in
configure)
if [ -x /usr/lib/backone/addons/updater/backone-updater ]; then
/usr/lib/backone/addons/updater/backone-updater rebuild-manifest || true
elif [ -x /usr/lib/backone/addons/updater/rebuild-addon-manifest.sh ]; then
BACKONE_HOME=/var/lib/backone /usr/lib/backone/addons/updater/rebuild-addon-manifest.sh || true
fi
;;
esac
#DEBHELPER#
exit 0
+2
View File
@@ -0,0 +1,2 @@
misc:Depends=
misc:Pre-Depends=
+13
View File
@@ -0,0 +1,13 @@
Package: backone-addon-comms-core
Source: backone
Version: 1.17.1
Architecture: amd64
Maintainer: Dedy Sutanto <dsutanto@backone.cloud>
Installed-Size: 15
Depends: backone
Recommends: postgresql-client, redis-tools
Section: net
Priority: optional
Homepage: https://backone.cloud
Description: BackOne comms core addon (signaling backend)
Shared signaling backend for voice, video, and chat addons.
+3
View File
@@ -0,0 +1,3 @@
afd892e714b0637552f6fc56040e860a usr/lib/backone/addons/comms-core/addon.json
51ec6efb47fa207fa716611711defd8f usr/lib/backone/addons/comms-core/schema.sql
4b992da672501c1ed9da4a0c09d59986 usr/share/doc/backone-addon-comms-core/changelog.gz
+12
View File
@@ -0,0 +1,12 @@
#!/bin/sh -e
case "$1" in
configure)
if [ -x /usr/lib/backone/addons/updater/backone-updater ]; then
/usr/lib/backone/addons/updater/backone-updater rebuild-manifest || true
elif [ -x /usr/lib/backone/addons/updater/rebuild-addon-manifest.sh ]; then
BACKONE_HOME=/var/lib/backone /usr/lib/backone/addons/updater/rebuild-addon-manifest.sh || true
fi
;;
esac
exit 0
+12
View File
@@ -0,0 +1,12 @@
#!/bin/sh -e
case "$1" in
configure)
if [ -x /usr/lib/backone/addons/updater/backone-updater ]; then
/usr/lib/backone/addons/updater/backone-updater rebuild-manifest || true
elif [ -x /usr/lib/backone/addons/updater/rebuild-addon-manifest.sh ]; then
BACKONE_HOME=/var/lib/backone /usr/lib/backone/addons/updater/rebuild-addon-manifest.sh || true
fi
;;
esac
#DEBHELPER#
exit 0
+2
View File
@@ -0,0 +1,2 @@
misc:Depends=
misc:Pre-Depends=
+12
View File
@@ -0,0 +1,12 @@
Package: backone-addon-updater
Source: backone
Version: 1.17.1
Architecture: all
Maintainer: Dedy Sutanto <dsutanto@backone.cloud>
Installed-Size: 28
Depends: backone
Section: net
Priority: optional
Homepage: https://backone.cloud
Description: BackOne version updater addon
Migrates installations between BackOne and addon versions.
+8
View File
@@ -0,0 +1,8 @@
bee18cfe55163e6f4cf1d8a22c19dfa8 usr/lib/backone/addons/updater/addon.json
fbc9822a89457454f09b87ba35182052 usr/lib/backone/addons/updater/backone-updater
86727de17602883708f38bdc69d68196 usr/lib/backone/addons/updater/merge-local-conf.sh
1a944e1b0c1afdcff4b75c11f1a18ce0 usr/lib/backone/addons/updater/migrations/001_zerotier_to_backone.sh
5f8f8cd7bba7204f08b4d1a7e7b0dd4c usr/lib/backone/addons/updater/migrations/002_register_addon_host.sh
2fac4021a24721dee1bac76557171998 usr/lib/backone/addons/updater/migrations/003_enable_webserver_defaults.sh
9e532d47a1b291f26457aaff04864920 usr/lib/backone/addons/updater/rebuild-addon-manifest.sh
dbf8511a07dbd23850fe2553d78dcf6a usr/share/doc/backone-addon-updater/changelog.gz
+12
View File
@@ -0,0 +1,12 @@
#!/bin/sh -e
case "$1" in
configure)
if [ -x /usr/lib/backone/addons/updater/backone-updater ]; then
/usr/lib/backone/addons/updater/backone-updater rebuild-manifest || true
elif [ -x /usr/lib/backone/addons/updater/rebuild-addon-manifest.sh ]; then
BACKONE_HOME=/var/lib/backone /usr/lib/backone/addons/updater/rebuild-addon-manifest.sh || true
fi
;;
esac
exit 0
+1
View File
@@ -0,0 +1 @@
../lib/backone/addons/updater/backone-updater
+12
View File
@@ -0,0 +1,12 @@
#!/bin/sh -e
case "$1" in
configure)
if [ -x /usr/lib/backone/addons/updater/backone-updater ]; then
/usr/lib/backone/addons/updater/backone-updater rebuild-manifest || true
elif [ -x /usr/lib/backone/addons/updater/rebuild-addon-manifest.sh ]; then
BACKONE_HOME=/var/lib/backone /usr/lib/backone/addons/updater/rebuild-addon-manifest.sh || true
fi
;;
esac
#DEBHELPER#
exit 0
+2
View File
@@ -0,0 +1,2 @@
misc:Depends=
misc:Pre-Depends=
+12
View File
@@ -0,0 +1,12 @@
Package: backone-addon-video
Source: backone
Version: 1.17.1
Architecture: all
Maintainer: Dedy Sutanto <dsutanto@backone.cloud>
Installed-Size: 25
Depends: backone, backone-addon-comms-core, backone-addon-voice, backone-app-shell
Section: net
Priority: optional
Homepage: https://backone.cloud
Description: BackOne video call P2P addon
P2P video calling with ringing popup over the BackOne overlay.
+3
View File
@@ -0,0 +1,3 @@
b50ba09efcb821add4a37b940c457c92 usr/lib/backone/addons/video/addon.json
f2cf6c14a4db3363befe75d07dca482b usr/share/doc/backone-addon-video/changelog.gz
a589ab06e5b078b87e397c0a5e65e401 var/lib/backone/app/addons/video/index.js
+12
View File
@@ -0,0 +1,12 @@
#!/bin/sh -e
case "$1" in
configure)
if [ -x /usr/lib/backone/addons/updater/backone-updater ]; then
/usr/lib/backone/addons/updater/backone-updater rebuild-manifest || true
elif [ -x /usr/lib/backone/addons/updater/rebuild-addon-manifest.sh ]; then
BACKONE_HOME=/var/lib/backone /usr/lib/backone/addons/updater/rebuild-addon-manifest.sh || true
fi
;;
esac
exit 0
+12
View File
@@ -0,0 +1,12 @@
#!/bin/sh -e
case "$1" in
configure)
if [ -x /usr/lib/backone/addons/updater/backone-updater ]; then
/usr/lib/backone/addons/updater/backone-updater rebuild-manifest || true
elif [ -x /usr/lib/backone/addons/updater/rebuild-addon-manifest.sh ]; then
BACKONE_HOME=/var/lib/backone /usr/lib/backone/addons/updater/rebuild-addon-manifest.sh || true
fi
;;
esac
#DEBHELPER#
exit 0
+2
View File
@@ -0,0 +1,2 @@
misc:Depends=
misc:Pre-Depends=
+12
View File
@@ -0,0 +1,12 @@
Package: backone-addon-voice
Source: backone
Version: 1.17.1
Architecture: all
Maintainer: Dedy Sutanto <dsutanto@backone.cloud>
Installed-Size: 24
Depends: backone, backone-addon-comms-core, backone-app-shell
Section: net
Priority: optional
Homepage: https://backone.cloud
Description: BackOne voice call P2P addon
P2P voice calling with ringing popup over the BackOne overlay.
+3
View File
@@ -0,0 +1,3 @@
cb9c4fe7a9fe5cd4748522249933cde5 usr/lib/backone/addons/voice/addon.json
8c0d667eb292b835a170c3d46c8ce16a usr/share/doc/backone-addon-voice/changelog.gz
3b48d55aa718993b14ebd23b6e9bceaa var/lib/backone/app/addons/voice/index.js
+12
View File
@@ -0,0 +1,12 @@
#!/bin/sh -e
case "$1" in
configure)
if [ -x /usr/lib/backone/addons/updater/backone-updater ]; then
/usr/lib/backone/addons/updater/backone-updater rebuild-manifest || true
elif [ -x /usr/lib/backone/addons/updater/rebuild-addon-manifest.sh ]; then
BACKONE_HOME=/var/lib/backone /usr/lib/backone/addons/updater/rebuild-addon-manifest.sh || true
fi
;;
esac
exit 0
+15
View File
@@ -0,0 +1,15 @@
#!/bin/sh -e
case "$1" in
configure)
if [ -x /usr/lib/backone/addons/updater/backone-updater ]; then
/usr/lib/backone/addons/updater/backone-updater rebuild-manifest || true
elif [ -x /usr/lib/backone/addons/updater/rebuild-addon-manifest.sh ]; then
BACKONE_HOME=/var/lib/backone /usr/lib/backone/addons/updater/rebuild-addon-manifest.sh || true
fi
if [ -x /usr/lib/backone/addons/updater/merge-local-conf.sh ]; then
BACKONE_HOME=/var/lib/backone /usr/lib/backone/addons/updater/merge-local-conf.sh '{"settings":{"enableWebServer":true}}' || true
fi
;;
esac
#DEBHELPER#
exit 0
+2
View File
@@ -0,0 +1,2 @@
misc:Depends=
misc:Pre-Depends=
+12
View File
@@ -0,0 +1,12 @@
Package: backone-app-shell
Source: backone
Version: 1.17.1
Architecture: all
Maintainer: Dedy Sutanto <dsutanto@backone.cloud>
Installed-Size: 56
Depends: backone
Section: net
Priority: optional
Homepage: https://backone.cloud
Description: BackOne web app shell host
Minimal web UI host that dynamically loads separated BackOne addons.
+11
View File
@@ -0,0 +1,11 @@
ddc11210878af47483c00a896bc49c7f usr/share/doc/backone-app-shell/changelog.gz
3d02f4314de4d3628cb193610174fcde var/lib/backone/app/addon-loader.js
49d4fd44dcbabedd7037c9a59fb9da3a var/lib/backone/app/auth-store.js
fc714bc78bb3127f5bf96a4aaecf9684 var/lib/backone/app/contacts-store.js
7babf02e4ba1ca836f844acc5940d7b8 var/lib/backone/app/index.html
802d8d5241ba3587f8ba66e986de3395 var/lib/backone/app/main.js
07908ab7ebed73d83ce776fd5ce68e94 var/lib/backone/app/network-session.js
2c328cb61a8c74e9c64cac2e00ca37c5 var/lib/backone/app/peer-directory.js
b8229c43483474ac0f9eb47e756b0773 var/lib/backone/app/peer-picker.js
dbbb653bdddf96ee5d520c9bb4ab976b var/lib/backone/app/styles.css
99914b932bd37a50b983c5e7c90ae93b var/lib/backone/contacts.json
+15
View File
@@ -0,0 +1,15 @@
#!/bin/sh -e
case "$1" in
configure)
if [ -x /usr/lib/backone/addons/updater/backone-updater ]; then
/usr/lib/backone/addons/updater/backone-updater rebuild-manifest || true
elif [ -x /usr/lib/backone/addons/updater/rebuild-addon-manifest.sh ]; then
BACKONE_HOME=/var/lib/backone /usr/lib/backone/addons/updater/rebuild-addon-manifest.sh || true
fi
if [ -x /usr/lib/backone/addons/updater/merge-local-conf.sh ]; then
BACKONE_HOME=/var/lib/backone /usr/lib/backone/addons/updater/merge-local-conf.sh '{"settings":{"enableWebServer":true}}' || true
fi
;;
esac
exit 0
Loaded 100 of 145 files, more files were not shown because too many files have changed in this diff. Show more