landfall 0.4.0 → 0.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.mjs CHANGED
@@ -769,6 +769,6 @@ Runs on 127.0.0.1 only; the printed link logs the browser in.`,options:{port:{ty
769
769
 
770
770
  Without --packages/--images both are updated. Package upgrades cannot be undone;
771
771
  image updates can (\`lf undo\` puts the old image back). --compose replaces the
772
- compose plugin setup installed with the newest release (undoable too).`,options:{server:F,packages:{type:`boolean`,desc:`upgrade OS packages`},security:{type:`boolean`,desc:`only security updates (apt, dnf)`},reboot:{type:`boolean`,desc:`reboot afterwards if the upgrade needs it`},images:{type:`boolean`,desc:`pull newer images and recreate their containers`},compose:{type:`boolean`,desc:`update the compose plugin landfall installed to the newest release`}},async run(e,t){let{name:n,server:r}=V(t.server);if(t.compose){(e.length||t.packages||t.images)&&D(`--compose goes alone`);let i=await Hn(r,`updating docker compose`),a=await or(i),o=sr(a);if(!o.composeUpdate)return void(o.composeNote?w(o.composeNote):C(`compose ${a.compose} is the newest`));await j(`Update compose ${a.compose} to ${a.latestCompose} on ${n}?`)&&(S(`downloading docker compose`),await cr(i),C(`compose updated (lf undo puts the old one back)`));return}let i=!t.packages&&!t.images;e.length&&t.packages&&D(`sites only apply to --images`);let a=!e.length&&(t.packages||i||t.security),o=t.images||i||e.length>0;if(a){let e=await Hn(r,`upgrading packages`),i=await Gn(e),a=t.security?i.updates.filter(e=>e.security):i.updates;i.mgr===`none`?T(`no supported package manager, skipping packages`):a.length?(Aa({...i,updates:a},!1),t.security&&i.mgr===`apk`&&D(`apk has no security-only upgrades; drop --security`),await j(`Upgrade ${a.length} package${a.length===1?``:`s`} on ${n}?`)&&(S(`upgrading packages on ${n}`),await qn(e,{only:t.security&&i.mgr===`apt-get`?a.map(e=>e.name):void 0,what:t.security?`${a.length} security packages`:`${a.length} packages`,reboot:t.reboot}),C(`packages upgraded`),t.reboot&&w(`rebooting if required; give it a minute`))):C(t.security?`no security updates`:`packages up to date`)}if(o){let t=await ur(r);if(e.length){let r=e.filter(e=>!t.some(t=>t.name===e));r.length&&D(`no site or service ${r.join(`, `)} on ${n}`)}let i=e.length?t.filter(t=>e.includes(t.name)):t;for(let e of i.filter(e=>e.state!==`update`&&e.state!==`current`&&e.state!==`unknown`))w(x.dim(`${e.name}: ${e.note??e.state}`));let a=i.filter(t=>t.state===`update`||e.includes(t.name)&&t.state===`unknown`);a.length?(ja(a),await j(`Pull and recreate ${a.map(e=>e.name).join(`, `)}?`)&&(S(`updating images`),await dr(r,a),C(`images updated (lf undo puts the old ones back)`))):C(`images up to date`)}}});var Fa=`0.4.0`;const Ia=[Ti,Ni,Ai,Yr,Xr,Zr,ai,ni,$r,ei,ti,ii,ri,ci,li,ui,Na,Pa,ma,ha,xa,Ca,Da,ya,ga,va,ba,Vr,Hr,Wr,Gr,Kr,Ur,na,ra,ia,ea,ta,oa,aa,fa,pa,xi,Oa],La={ls:`sites list`,sites:`sites list`,servers:`server list`,"server ls":`server list`,"server ssh":`ssh`,"sites logs":`logs`,"sites env":`env`,"sites rollback":`rollback`,"sites restart":`restart`,"sites stop":`stop`,"sites start":`start`,"sites ls":`sites list`,"sites remove":`sites rm`,"server remove":`server rm`,uninstall:`server uninstall`,"traefik remove":`traefik rm`,traefik:`traefik status`,firewall:`firewall status`,"firewall remove":`firewall rm`,log:`history`,revert:`undo`,web:`ui`,info:`server info`,"sites domains":`domains`,"sites auth":`auth`,"sites open":`open`,upgrade:`update`},Ra=new Map(Ia.map(e=>[e.name,e]));function za(e){for(let t of[2,1]){let n=e.slice(0,t).join(` `),r=Ra.get(La[n]??n);if(r&&e.length>=t)return{cmd:r,rest:e.slice(t)}}return{rest:e}}function Ba(){let e=[[`Servers`,[`setup`,`server add`,`server list`,`server info`,`server config`,`server domain`,`server use`,`server rm`,`server uninstall`,`ssh`,`keygen`]],[`Traefik`,[`traefik setup`,`traefik status`,`traefik logs`,`traefik restart`,`traefik rm`]],[`Firewall`,[`firewall status`,`firewall enable`,`firewall disable`,`firewall allow`,`firewall deny`,`firewall update`,`firewall rm`]],[`Sites`,[`deploy`,`sites list`,`sites info`,`domains`,`auth`,`open`,`logs`,`env`,`rollback`,`restart`,`stop`,`start`,`sites rm`]],[`Updates`,[`updates`,`update`]],[`History`,[`history`,`undo`]],[`Dashboard`,[`ui`]]],t=[`${x.bold(`landfall`)} ${x.dim(Fa)} — set up servers and deploy sites behind Traefik + HTTPS`,``,`${x.bold(`Usage:`)} lf <command> [options] ${x.dim(`lf <command> --help for details`)}`,` lf ui ${x.dim(`web dashboard in your browser`)}`];for(let[n,r]of e){t.push(``,x.bold(n));for(let e of r){let n=Ra.get(e);t.push(` ${(e+(n.args?` `+n.args:``)).padEnd(32)} ${n.summary.split(`
772
+ compose plugin setup installed with the newest release (undoable too).`,options:{server:F,packages:{type:`boolean`,desc:`upgrade OS packages`},security:{type:`boolean`,desc:`only security updates (apt, dnf)`},reboot:{type:`boolean`,desc:`reboot afterwards if the upgrade needs it`},images:{type:`boolean`,desc:`pull newer images and recreate their containers`},compose:{type:`boolean`,desc:`update the compose plugin landfall installed to the newest release`}},async run(e,t){let{name:n,server:r}=V(t.server);if(t.compose){(e.length||t.packages||t.images)&&D(`--compose goes alone`);let i=await Hn(r,`updating docker compose`),a=await or(i),o=sr(a);if(!o.composeUpdate)return void(o.composeNote?w(o.composeNote):C(`compose ${a.compose} is the newest`));await j(`Update compose ${a.compose} to ${a.latestCompose} on ${n}?`)&&(S(`downloading docker compose`),await cr(i),C(`compose updated (lf undo puts the old one back)`));return}let i=!t.packages&&!t.images;e.length&&t.packages&&D(`sites only apply to --images`);let a=!e.length&&(t.packages||i||t.security),o=t.images||i||e.length>0;if(a){let e=await Hn(r,`upgrading packages`),i=await Gn(e),a=t.security?i.updates.filter(e=>e.security):i.updates;i.mgr===`none`?T(`no supported package manager, skipping packages`):a.length?(Aa({...i,updates:a},!1),t.security&&i.mgr===`apk`&&D(`apk has no security-only upgrades; drop --security`),await j(`Upgrade ${a.length} package${a.length===1?``:`s`} on ${n}?`)&&(S(`upgrading packages on ${n}`),await qn(e,{only:t.security&&i.mgr===`apt-get`?a.map(e=>e.name):void 0,what:t.security?`${a.length} security packages`:`${a.length} packages`,reboot:t.reboot}),C(`packages upgraded`),t.reboot&&w(`rebooting if required; give it a minute`))):C(t.security?`no security updates`:`packages up to date`)}if(o){let t=await ur(r);if(e.length){let r=e.filter(e=>!t.some(t=>t.name===e));r.length&&D(`no site or service ${r.join(`, `)} on ${n}`)}let i=e.length?t.filter(t=>e.includes(t.name)):t;for(let e of i.filter(e=>e.state!==`update`&&e.state!==`current`&&e.state!==`unknown`))w(x.dim(`${e.name}: ${e.note??e.state}`));let a=i.filter(t=>t.state===`update`||e.includes(t.name)&&t.state===`unknown`);a.length?(ja(a),await j(`Pull and recreate ${a.map(e=>e.name).join(`, `)}?`)&&(S(`updating images`),await dr(r,a),C(`images updated (lf undo puts the old ones back)`))):C(`images up to date`)}}});var Fa=`0.4.1`;const Ia=[Ti,Ni,Ai,Yr,Xr,Zr,ai,ni,$r,ei,ti,ii,ri,ci,li,ui,Na,Pa,ma,ha,xa,Ca,Da,ya,ga,va,ba,Vr,Hr,Wr,Gr,Kr,Ur,na,ra,ia,ea,ta,oa,aa,fa,pa,xi,Oa],La={ls:`sites list`,sites:`sites list`,servers:`server list`,"server ls":`server list`,"server ssh":`ssh`,"sites logs":`logs`,"sites env":`env`,"sites rollback":`rollback`,"sites restart":`restart`,"sites stop":`stop`,"sites start":`start`,"sites ls":`sites list`,"sites remove":`sites rm`,"server remove":`server rm`,uninstall:`server uninstall`,"traefik remove":`traefik rm`,traefik:`traefik status`,firewall:`firewall status`,"firewall remove":`firewall rm`,log:`history`,revert:`undo`,web:`ui`,info:`server info`,"sites domains":`domains`,"sites auth":`auth`,"sites open":`open`,upgrade:`update`},Ra=new Map(Ia.map(e=>[e.name,e]));function za(e){for(let t of[2,1]){let n=e.slice(0,t).join(` `),r=Ra.get(La[n]??n);if(r&&e.length>=t)return{cmd:r,rest:e.slice(t)}}return{rest:e}}function Ba(){let e=[[`Servers`,[`setup`,`server add`,`server list`,`server info`,`server config`,`server domain`,`server use`,`server rm`,`server uninstall`,`ssh`,`keygen`]],[`Traefik`,[`traefik setup`,`traefik status`,`traefik logs`,`traefik restart`,`traefik rm`]],[`Firewall`,[`firewall status`,`firewall enable`,`firewall disable`,`firewall allow`,`firewall deny`,`firewall update`,`firewall rm`]],[`Sites`,[`deploy`,`sites list`,`sites info`,`domains`,`auth`,`open`,`logs`,`env`,`rollback`,`restart`,`stop`,`start`,`sites rm`]],[`Updates`,[`updates`,`update`]],[`History`,[`history`,`undo`]],[`Dashboard`,[`ui`]]],t=[`${x.bold(`landfall`)} ${x.dim(Fa)} — set up servers and deploy sites behind Traefik + HTTPS`,``,`${x.bold(`Usage:`)} lf <command> [options] ${x.dim(`lf <command> --help for details`)}`,` lf ui ${x.dim(`web dashboard in your browser`)}`];for(let[n,r]of e){t.push(``,x.bold(n));for(let e of r){let n=Ra.get(e);t.push(` ${(e+(n.args?` `+n.args:``)).padEnd(32)} ${n.summary.split(`
773
773
  `)[0]}`)}}t.push(``,x.bold(`Quick start`),` lf setup vps --check ${x.dim(`# what is on vps, what setup would change`)}`,` lf setup vps ${x.dim("# docker + traefik on a server you reach with `ssh vps`")}`,` lf setup box --host 203.0.113.7 ${x.dim("# fresh box (root password once) → `ssh box` works")}`,` lf deploy ./dist -d example.com -s vps ${x.dim(`# publish a static site with HTTPS`)}`,` lf deploy ${x.dim(`# redeploy using ./.landfall.json`)}`,` lf deploy ./dist -y ${x.dim(`# no domain yet: serve at <name>.<ip>.sslip.io`)}`,` lf auth my-site --user anna ${x.dim(`# put a password in front of a site`)}`,` lf updates -s vps && lf update -s vps ${x.dim(`# pending OS/image updates, then apply them`)}`,` lf ls ${x.dim(`# what runs where`)}`,` lf history -s vps && lf undo -s vps ${x.dim(`# what landfall changed, and revert the last of it`)}`,``,x.dim(`Servers are ssh targets: any ~/.ssh/config alias works as --server.`)),console.log(t.join(`
774
774
  `))}async function Va(e){if(!e.length||e[0]===`help`||e[0]===`-h`||e[0]===`--help`)return Ba(),0;if(e[0]===`-v`||e[0]===`--version`||e[0]===`version`)return console.log(Fa),0;let{cmd:t,rest:n}=za(e);if(!t)return console.error(`${x.red(`error:`)} unknown command "${e.join(` `)}"\n`),Ba(),2;let{values:r,positionals:i,rest:a}=se(t,n);if(r.help)return console.log(ce(t)),0;k(!!r.yes),$e.cmd=le(t,i,r);let o=await t.run(i,r,a);return typeof o==`number`?o:0}async function Ha(e){try{return await Va(e)}catch(e){return e instanceof E?console.error(`${x.red(`error:`)} ${e.message}`):console.error(e),1}}Ha(process.argv.slice(2)).then(e=>process.stdout.write(``,()=>process.exit(e)));export{fe as A,Ue as C,U as D,K as E,Ee as F,V as I,pe as L,de as M,R as N,W as O,B as P,x as R,lt as S,q as T,jt as _,Vi as a,Ot as b,mi as c,or as d,ur as f,mn as g,fn as h,Ea as i,H as j,G as k,oi as l,En as m,wa as n,Pi as o,Gn as p,Sa as r,Ii as s,Fa as t,sr as u,Ct as v,ke as w,Q as x,kt as y,oe as z};
package/dist/index.html CHANGED
@@ -410,6 +410,8 @@ function openMenu(anchor, items) {
410
410
  all[e.key === "Home" ? 0 : all.length - 1].focus();
411
411
  }
412
412
  };
413
+ // Keep focus where it is on mousedown: a browser that does not focus clicked buttons would blur the menu and close it before the click.
414
+ m.onmousedown = (e) => e.preventDefault();
413
415
  m.addEventListener("focusout", (e) => {
414
416
  if (!m.contains(e.relatedTarget)) closeMenu(false);
415
417
  });
@@ -423,6 +425,8 @@ function openMenu(anchor, items) {
423
425
  function closeMenu(restore = true) {
424
426
  const m = $("#menu");
425
427
  if (!m) return;
428
+ // Removing the focused item fires focusout, which calls closeMenu again: drop the id first so that call finds nothing.
429
+ m.removeAttribute("id");
426
430
  m.remove();
427
431
  menuAnchor?.setAttribute("aria-expanded", "false");
428
432
  if (restore && menuAnchor?.isConnected) menuAnchor.focus({ preventScroll: true });
@@ -1685,6 +1689,9 @@ function settingsView(n) {
1685
1689
  if (d) run(["traefik", "setup", ...sv(n), "--dashboard", d], reload);
1686
1690
  }, "Apply");
1687
1691
  const sites = S.overview[n]?.data?.sites ?? [];
1692
+ // Sites behind a Traefik the server already had (proxy.env): lf traefik setup refuses to touch it.
1693
+ const proxy = S.overview[n]?.data?.proxy;
1694
+ const foreign = proxy && proxy !== "traefik" ? proxy : "";
1688
1695
  const external = (d) => S.overview[n]?.data?.others?.find((e) => e.domains.includes(d))?.name;
1689
1696
  const servedBy = (d) => sites.find((s) => s.domains.includes(d))?.meta.name;
1690
1697
  const serve = (d) => {
@@ -1721,8 +1728,8 @@ function settingsView(n) {
1721
1728
  ${x.suggest.length ? `<div class="note">${x.domains.length ? "Other domains" : "Domains"} your sites on this server already use: ${x.suggest.map((g) => `<b>${esc(g.domain)}</b> <span class="faint">(${g.hosts})</span> ${btn("Add", () => run(["server", "domain", n, "--add", g.domain], reload), "sm")}`).join(" ")}</div>` : ""}</div>
1722
1729
  </div>
1723
1730
  <div class="card">
1724
- <h2>Traefik <span class="actions">${btn("Dashboard…", dashboard, "sm")}</span></h2>
1725
- <div class="body">${rows([
1731
+ <h2>Traefik <span class="actions">${foreign ? "" : btn("Dashboard…", dashboard, "sm")}</span></h2>
1732
+ <div class="body">${foreign ? `<div class="note">Sites go through <b class="mono">${esc(foreign)}</b>, a Traefik landfall did not install and never changes: its dashboard is set up in its own config.</div>` : ""}${rows([
1726
1733
  ["image", esc(x.traefik.summary)],
1727
1734
  ["e-mail", esc(x.traefik.email ?? "none")],
1728
1735
  ["dashboard", x.traefik.dashboard ? `<a href="https://${esc(x.traefik.dashboard)}" target="_blank" rel="noopener">${esc(x.traefik.dashboard)}</a>` : "off"],
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "landfall",
3
- "version": "0.4.0",
3
+ "version": "0.4.1",
4
4
  "description": "Set up servers and deploy sites behind Traefik with automatic HTTPS",
5
5
  "author": "ph1p <me@ph1p.dev>",
6
6
  "license": "MIT",