{"id":42,"date":"2026-10-22T22:57:23","date_gmt":"2026-10-22T20:57:23","guid":{"rendered":"https:\/\/wp-opsec.eagleeye-intelligence.com\/?page_id=42"},"modified":"2026-10-25T15:05:53","modified_gmt":"2026-10-25T14:05:53","slug":"linux-installation","status":"publish","type":"page","link":"https:\/\/wp-opsec.eagleeye-intelligence.com\/?page_id=42","title":{"rendered":"Linux Installation"},"content":{"rendered":"\n<style>\n.thessley-downloads{--td-ink:#f2f6fc;--td-muted:#cad5e3;--td-blue:#1976b5;--td-line:#40516a;color:var(--td-ink);font-family:inherit;background:#0c1422;padding:clamp(1rem,3vw,2rem);border-radius:18px;color-scheme:dark}\n.thessley-downloads *{box-sizing:border-box}\n.thessley-downloads .td-hero{padding:clamp(1.8rem,5vw,3.2rem);border-radius:22px;background:linear-gradient(128deg,#102b4d,#176b9e);color:#fff}\n.thessley-downloads .td-hero .td-kicker{margin:.1rem 0 .55rem;color:#b8e4f7;font-weight:800;letter-spacing:.12em;text-transform:uppercase}\n.thessley-downloads .td-hero p{max-width:65ch;color:#e4f0f7;line-height:1.65}\n.thessley-downloads section{margin:2.8rem 0}\n.thessley-downloads h2{font-size:clamp(1.55rem,3vw,2rem);color:#f5f8fc}\n.thessley-downloads h3{margin-top:1.8rem;color:#eef4fc}\n.thessley-downloads p,.thessley-downloads li{line-height:1.7;color:var(--td-muted)}\n.thessley-downloads a:not(.td-download){color:#9edaff;text-decoration:underline;text-underline-offset:.15em}\n.thessley-downloads a:not(.td-download):hover{color:#d1efff}\n.thessley-downloads .td-card{margin:1rem 0;padding:1.25rem 1.4rem;border:1px solid var(--td-line);border-radius:16px;background:#172235}\n.thessley-downloads .td-card h3{margin:.1rem 0 .4rem}\n.thessley-downloads .td-card p{margin:.45rem 0}\n.thessley-downloads .td-download{display:inline-block;margin:.45rem .7rem .25rem 0;padding:.7rem 1rem;border-radius:999px;background:#145a8d;color:#fff!important;font-weight:750;text-decoration:none}\n.thessley-downloads .td-download:hover{background:#0f456d}\n.thessley-downloads .td-warn{padding:1rem 1.2rem;border-left:4px solid #e3ad45;border-radius:0 10px 10px 0;background:#332a1b;color:#ffe6b2}\n.thessley-downloads pre{position:relative;overflow:auto;padding:3.1rem 1rem 1rem;border-radius:10px;background:#17243a;color:#f0f5f9;font-size:.9rem;line-height:1.55}\n.thessley-downloads pre code{display:block;min-width:max-content;white-space:pre;overflow-wrap:normal}\n.thessley-downloads .td-copy{position:absolute;top:.55rem;right:.6rem;display:inline-flex;align-items:center;gap:.4rem;padding:.4rem .6rem;border:1px solid #9ab2cc;border-radius:7px;background:#314966;color:#fff;font:inherit;font-size:.78rem;cursor:pointer}\n.thessley-downloads .td-copy:hover,.thessley-downloads .td-copy:focus-visible{background:#42658a;outline:2px solid #b4e6ff;outline-offset:2px}\n.thessley-downloads .td-copy svg{width:1rem;height:1rem;fill:none;stroke:currentColor;stroke-width:1.8;stroke-linecap:round;stroke-linejoin:round}\n.thessley-downloads .td-copy-label{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}\n.thessley-downloads :not(pre)>code{padding:.1em .3em;border-radius:4px;background:#25354b;color:#f4f7fb}\n\n.thessley-downloads .td-hero h1{margin:.2rem 0 .7rem;color:#fff;font-size:clamp(2rem,5vw,3rem)}\n.thessley-downloads details{margin:1rem 0;padding:0 1rem 1rem;border:1px solid var(--td-line);border-radius:12px;background:#121e30}\n.thessley-downloads summary{padding:1rem 0;color:#f5f8fc;font-size:1.1rem;font-weight:750;cursor:pointer}\n.thessley-downloads details[open]>summary{border-bottom:1px solid var(--td-line)}\n<\/style>\n<main class=\"thessley-downloads alignwide\">\n  <section class=\"td-hero\">\n    <p class=\"td-kicker\">Thessley computer installation<\/p>\n    <h1>Ubuntu 24.04 and 26.04 Linux<\/h1>\n    <p>Linux SSH QR approval service, web admin, and optional OpenVPN integration.<\/p>\n    <p><a href=\"?page_id=16\">All downloads<\/a><\/p>\n  <\/section>\n  \n  <section id=\"guided-install\">\n    <h2>Guided one-line install<\/h2>\n    <p>For Ubuntu 24.04 or 26.04 LTS on amd64 or ARM64, paste this command in a terminal. It adds the signed Thessley APT repository, installs the package, and starts the wizard. The HTTPS web admin page is enabled by default; enter n to skip it. Other optional components default to No. Existing SSH\/PAM and OpenVPN server configuration are not changed automatically.<\/p>\n    <p>Time zones are supported. The Linux source installer reports its UTC comparison as diagnostic information and continues if the clocks differ or the comparison is unavailable. Phone approvals use a short freshness window, so the computer and phone still need accurate, synchronized clocks. APT checks signed repository metadata dates before the installer runs; if <code>apt update<\/code> reports that a Release file is \u201cnot valid yet\u201d or expired, sync the system clock and rerun the install command. Do not disable APT signature or date checks.<\/p>\n    <details>\n      <summary>Check or sync Ubuntu time if APT reports a date error<\/summary>\n      <pre><code>timedatectl status\nsudo timedatectl set-ntp true\ntimedatectl timesync-status<\/code><button type=\"button\" class=\"td-copy\" aria-label=\"Copy command\">Copy command<\/button><\/pre>\n    <\/details>\n    <pre><code>sudo install -d -m 0755 \/etc\/apt\/keyrings &amp;&amp; curl -fsSL https:\/\/wp-opsec.eagleeye-intelligence.com\/wp-content\/uploads\/thessley-apt\/thessley-archive-keyring.gpg | sudo tee \/etc\/apt\/keyrings\/thessley-archive-keyring.gpg &gt;\/dev\/null &amp;&amp; sudo chmod 0644 \/etc\/apt\/keyrings\/thessley-archive-keyring.gpg &amp;&amp; echo &#x27;deb [arch=amd64,arm64 signed-by=\/etc\/apt\/keyrings\/thessley-archive-keyring.gpg] https:\/\/wp-opsec.eagleeye-intelligence.com\/wp-content\/uploads\/thessley-apt stable main&#x27; | sudo tee \/etc\/apt\/sources.list.d\/thessley.list &gt;\/dev\/null &amp;&amp; sudo apt update &amp;&amp; sudo apt install -y thessley-pam-linux-installer &amp;&amp; sudo thessley-pam-install<\/code><\/pre>\n  <\/section>\n  <section id=\"openvpn-quick-install\">\n    <h2>Guided OpenVPN server install<\/h2>\n    <p>First install the Thessley Linux service above. Then use this on a <strong>new Ubuntu 24.04 or 26.04 OpenVPN server<\/strong>. It verifies the installer checksum, asks for the public host, local account, tunnel subnet, LAN subnet (detected from the default-route interface when it is private, or overridden\/skipped), UDP port and first client name, creates the compatible OpenVPN configuration, enrolls the phone by QR and matching code, and writes a protected client profile. The installer asks explicitly for a new CA passphrase, or for the existing CA passphrase when resuming an interrupted setup. It keeps the CA and resumes safely; it does not replace it. Later web key creation and revocation also ask for that CA passphrase, which is used briefly and not stored. VPN approval and OpenVPN are enabled before phone enrollment begins, so if enrollment is interrupted the Admin key controls remain available while VPN logins stay refused until a phone is associated. It refuses to overwrite an existing server config or approval service. If UFW is active, it can add only the required UDP and TCP 47830 allow rules; it does not enable UFW, change router settings, or alter SSH\/PAM or existing OpenVPN files. VPN-to-LAN forwarding is a separate optional step.<\/p>\n    <pre><code>f=$(mktemp) &amp;&amp; (trap &#x27;rm -f &quot;$f&quot;&#x27; EXIT; curl -fsSL -o &quot;$f&quot; https:\/\/wp-opsec.eagleeye-intelligence.com\/wp-content\/uploads\/thessley-downloads\/install-openvpn-server.sh &amp;&amp; printf &#x27;%s  %s\\n&#x27; &#x27;40361903e9f07d2399bb40042d3169c1c9d5d51cc9439150419c0b74c9513820&#x27; &quot;$f&quot; | sha256sum -c - &amp;&amp; sudo bash &quot;$f&quot;)<\/code><\/pre>\n    <p>The server requires a client certificate and Thessley phone approval. After the first approval, OpenVPN reconnect tokens renew hourly and last up to 8 hours after renewal. The wizard prints the router ports to forward: the chosen UDP port and TCP 47830 for QR approval. Review <code>docs\/VPN.md<\/code> in the source archive before adapting an existing VPN.<\/p>\n  <\/section>\n  <details>\n    <summary>Alternative installs and detailed setup<\/summary>\n    <section id=\"linux-alternatives\">\n    <h2>Linux computer tools<\/h2>\n    <div class=\"td-card\">\n      <h3>Linux source package \u00b7 22 October 2026<\/h3>\n      <p><strong>Documented target: Ubuntu 24.04 or 26.04 LTS<\/strong> on x86-64 or ARM64. The source package can also run on Debian or Armbian with systemd and the required dependencies.<\/p>\n      <a class=\"td-download\" href=\"https:\/\/wp-opsec.eagleeye-intelligence.com\/wp-content\/uploads\/thessley-downloads\/thessley-pam-linux-2026.10.22.tar.gz\">Download Linux source package<\/a>\n      <a href=\"https:\/\/wp-opsec.eagleeye-intelligence.com\/wp-content\/uploads\/thessley-downloads\/SHA256SUMS.txt\">SHA-256 checksums<\/a>\n      <p>The maintained Git repository is hosted in GitLab. It currently requires project access; this public source archive is available without a GitLab account.<\/p>\n    <\/div>\n    <h3>Rollback<\/h3>\n    <p>Use the matching component uninstall steps to remove optional features. Keep the timed SSH configuration rollback armed when changing SSH or PAM:<\/p>\n    <pre><code>sudo apt remove thessley-pam-linux-installer<\/code><\/pre>\n    <h3>Install from the source archive instead<\/h3>\n    <p>Extract the archive and run the same guided setup. Its optional choices default to No:<\/p>\n    <pre><code>tar -xzf thessley-pam-linux-2026.10.22.tar.gz\ncd thessley-pam-linux\nsudo .\/guided-install.sh<\/code><\/pre>\n    <p>The shared approval service uses TCP 47810. The web admin page is enabled by default and uses TCP 47820; restrict it to trusted networks, or enter n in the installer to skip it. Enroll a phone before enabling any login integration.<\/p>\n    <h3>OpenVPN with Thessley QR approval<\/h3>\n    <p>This Ubuntu 24.04 or 26.04 setup requires a per-device OpenVPN client certificate and Thessley phone approval. OpenVPN Connect 3.x can open the QR approval page automatically. After the first phone approval, OpenVPN reconnect tokens renew hourly and expire within 8 hours of the last renewal. The client certificate is still checked on each connection.<\/p>\n    <p>If you already run OpenVPN, back up its configuration and merge the Thessley authentication directives into it; do not replace a working server configuration wholesale. The commands below describe a new server named <code>thessley<\/code>.<\/p>\n    <ol>\n      <li>Install OpenVPN, Easy-RSA, and the Thessley VPN approval service. The Thessley core service must already be installed:<\/li>\n    <\/ol>\n    <pre><code>sudo apt update\nsudo apt install openvpn easy-rsa\nsudo \/opt\/thessley-pam-linux\/install-vpn.sh<\/code><\/pre>\n    <ol start=\"2\">\n      <li>Create an OpenVPN certificate authority and server certificate. Protect the CA private key and keep an offline backup:<\/li>\n    <\/ol>\n    <pre><code>sudo make-cadir \/etc\/openvpn\/easy-rsa-thessley\nsudo -i\ncd \/etc\/openvpn\/easy-rsa-thessley\n.\/easyrsa init-pki\nEASYRSA_ALGO=ec EASYRSA_CURVE=prime256v1 .\/easyrsa build-ca\nEASYRSA_BATCH=1 EASYRSA_ALGO=ec EASYRSA_CURVE=prime256v1 .\/easyrsa build-server-full server nopass\nopenvpn --genkey secret pki\/ta.key\ninstall -d -m 0755 \/etc\/openvpn\/server\ninstall -o root -g root -m 0644 pki\/ca.crt \/etc\/openvpn\/server\/ca.crt\ninstall -o root -g root -m 0644 pki\/issued\/server.crt \/etc\/openvpn\/server\/server.crt\ninstall -o root -g root -m 0600 pki\/private\/server.key \/etc\/openvpn\/server\/server.key\ninstall -o root -g root -m 0600 pki\/ta.key \/etc\/openvpn\/server\/ta.key\nexit<\/code><\/pre>\n    <ol start=\"3\">\n      <li>Create <code>\/etc\/openvpn\/server\/thessley.conf<\/code> for a new VPN server. Choose a tunnel range that does not overlap your LAN or other VPNs:<\/li>\n    <\/ol>\n    <pre><code>sudo tee \/etc\/openvpn\/server\/thessley.conf &gt;\/dev\/null &lt;&lt;'EOF'\nport 1194\nproto udp\ndev tun\ntopology subnet\nserver 10.8.0.0 255.255.255.0\nca ca.crt\ncert server.crt\nkey server.key\ndh none\ntls-crypt ta.key\ntls-version-min 1.2\nremote-cert-tls client\nverify-client-cert require\nscript-security 2\ncrl-verify \/etc\/openvpn\/easy-rsa-thessley\/pki\/crl.pem\nmanagement \/run\/openvpn-server\/thessley.mgmt unix\nmanagement-client-user root\nscript-security 2\nauth-user-pass-verify \/usr\/local\/lib\/thessley-pam\/vpn-auth via-env\nauth-gen-token 28800 3600\nauth-gen-token-secret \/etc\/openvpn\/server\/thessley-auth-token.key\nreneg-sec 3600\npush \"reneg-sec 3600\"\nuser nobody\ngroup nogroup\nkeepalive 10 300\npersist-key\npersist-tun\nverb 3\nEOF<\/code><\/pre>\n    <ol start=\"4\">\n      <li>Set the public DNS name (or stable public IP) phones will use, then enroll the VPN phone while it is on the server\u2019s network. Replace <code>ubuntu<\/code> with the Linux account that will own this VPN login:<\/li>\n    <\/ol>\n    <pre><code>sudo thessley-pam vpn public-host vpn.example.org\nsudo thessley-pam vpn enroll ubuntu\nsudo thessley-pam vpn on\nsudo systemctl enable --now openvpn-server@thessley\nsudo thessley-pam vpn status\nsudo systemctl status --no-pager openvpn-server@thessley<\/code><\/pre>\n    <p>Point <code>vpn.example.org<\/code> at your public IP. Forward <strong>UDP 1194<\/strong> and <strong>TCP 47830<\/strong> on the router to this computer. TCP 47830 serves the expiring QR approval page and phone response; UDP 1194 carries the VPN. If UFW is active, run:<\/p>\n    <pre><code>sudo ufw allow 1194\/udp\nsudo ufw allow 47830\/tcp<\/code><\/pre>\n    <p>Avoid exposing the unrelated SSH and web-admin ports to the internet.<\/p>\n    <ol start=\"5\">\n      <li>Create a separate client profile for every device. Replace the account and device names. The generated <code>.ovpn<\/code> file contains that device\u2019s private key; transfer it over a trusted channel and remove the extra copy afterward:<\/li>\n    <\/ol>\n    <pre><code>sudo \/opt\/thessley-pam-linux\/vpn-new-client.sh ubuntu phone<\/code><\/pre>\n    <p>Import <code>ubuntu-phone.ovpn<\/code> into OpenVPN Connect or another compatible client. At sign-in, use <code>ubuntu<\/code> as the username (the password field is ignored), then approve the QR\/link on the enrolled phone. OpenVPN Connect 3.x opens the QR page; other clients may require opening the pending-authentication URL from their log. Review the full <code>docs\/VPN.md<\/code> guide in the Linux source archive before adapting routes or NAT for access to the rest of your LAN. The setup follows Ubuntu\u2019s <a href=\"https:\/\/documentation.ubuntu.com\/server\/how-to\/security\/install-openvpn\/\">OpenVPN guide<\/a> and OpenVPN\u2019s <a href=\"https:\/\/github.com\/OpenVPN\/easy-rsa\/blob\/master\/README.quickstart.md\">Easy-RSA quickstart<\/a>.<\/p>\n    <h3>Optional SSH integration with automatic rollback<\/h3>\n    <p>The SSH module install does not edit SSH or PAM policy. Keep your current SSH session open, arm rollback before editing either login configuration file, and make sure you can reach the computer locally or through another recovery path.<\/p>\n    <pre><code>sudo .\/install-ssh.sh\nsudo .\/ssh-config-rollback.sh arm 10<\/code><\/pre>\n    <p>After making the SSH\/PAM changes described in <code>docs\/SSH.md<\/code>, validate and reload:<\/p>\n    <pre><code>sudo sshd -t &amp;&amp; sudo systemctl reload ssh<\/code><\/pre>\n    <p>Open a new terminal and verify a fresh SSH login. <strong>To finalize and keep the changes<\/strong>, run this from the verified session before the timer expires:<\/p>\n    <pre><code>sudo .\/ssh-config-rollback.sh cancel<\/code><\/pre>\n    <p>If the test login fails or the timer expires, the saved SSH and PAM files are restored automatically and SSH is reloaded. Keep the original session open until the new session is verified. For other components, use their matching <code>uninstall-*.sh<\/code> script to roll back installation; read the component guide before enabling its login integration.<\/p>\n    <p><a href=\"https:\/\/wp-opsec.eagleeye-intelligence.com\/wp-content\/uploads\/thessley-downloads\/SHA256SUMS.txt\">Verify package checksums<\/a> \u00b7 <a href=\"https:\/\/gitlab.com\/martin.lundstrom\/thessley-pam-linux\">Linux GitLab repository (project access required)<\/a> \u00b7 <a href=\"https:\/\/gitlab.com\/martin.lundstrom\/thessley-passkey-android\">Android GitLab repository (project access required)<\/a><\/p>\n  <\/section>\n  <\/details>\n<\/main>\n<script>\n(() => {\n  document.querySelectorAll('.thessley-downloads pre').forEach((pre) => {\n    const code = pre.querySelector('code');\n    if (!code) return;\n    let button = pre.querySelector('.td-copy');\n    if (!button) {\n      button = document.createElement('button');\n      button.type = 'button';\n      button.className = 'td-copy';\n      button.setAttribute('aria-label', 'Copy command');\n      button.title = 'Copy command';\n      button.innerHTML = '<svg viewBox=\"0 0 24 24\" aria-hidden=\"true\"><rect x=\"8\" y=\"8\" width=\"12\" height=\"12\" rx=\"2\"><\/rect><path d=\"M16 8V5a1 1 0 0 0-1-1H5a1 1 0 0 0-1 1v10a1 1 0 0 0 1 1h3\"><\/path><\/svg><span class=\"td-copy-label\">Copy<\/span>';\n      pre.appendChild(button);\n    }\n    button.addEventListener('click', async () => {\n      const text = code.textContent.replace(\/\\n+$\/, '');\n      try {\n        let copied = false;\n        if (navigator.clipboard && window.isSecureContext) {\n          try {\n            await navigator.clipboard.writeText(text);\n            copied = true;\n          } catch (error) {\n            \/\/ Fall back for browsers that expose the API but deny clipboard access.\n          }\n        }\n        if (!copied) {\n          const field = document.createElement('textarea');\n          field.value = text;\n          field.setAttribute('readonly', '');\n          field.style.position = 'fixed';\n          field.style.opacity = '0';\n          field.style.fontSize = '16px';\n          document.body.appendChild(field);\n          field.focus();\n          field.select();\n          copied = document.execCommand('copy');\n          field.remove();\n          if (!copied) throw new Error('Copy failed');\n        }\n        button.setAttribute('aria-label', 'Copied');\n        button.title = 'Copied';\n        button.innerHTML = '<svg viewBox=\"0 0 24 24\" aria-hidden=\"true\"><path d=\"m5 12 4 4L19 6\"><\/path><\/svg><span class=\"td-copy-label\">Copied<\/span>';\n        window.setTimeout(() => {\n          button.setAttribute('aria-label', 'Copy command');\n          button.title = 'Copy command';\n          button.innerHTML = '<svg viewBox=\"0 0 24 24\" aria-hidden=\"true\"><rect x=\"8\" y=\"8\" width=\"12\" height=\"12\" rx=\"2\"><\/rect><path d=\"M16 8V5a1 1 0 0 0-1-1H5a1 1 0 0 0-1 1v10a1 1 0 0 0 1 1h3\"><\/path><\/svg><span class=\"td-copy-label\">Copy<\/span>';\n        }, 1600);\n      } catch (error) {\n        button.setAttribute('aria-label', 'Copy failed; select the command manually');\n        button.title = 'Copy failed; select the command manually';\n      }\n    });\n  });\n})();\n<\/script>\n\n\n\n","protected":false},"excerpt":{"rendered":"<p>Guided one-line installer and alternative setup instructions.<\/p>\n","protected":false},"author":0,"featured_media":0,"parent":16,"menu_order":0,"comment_status":"closed","ping_status":"closed","template":"","meta":{"footnotes":""},"class_list":["post-42","page","type-page","status-publish","hentry"],"_links":{"self":[{"href":"https:\/\/wp-opsec.eagleeye-intelligence.com\/index.php?rest_route=\/wp\/v2\/pages\/42","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/wp-opsec.eagleeye-intelligence.com\/index.php?rest_route=\/wp\/v2\/pages"}],"about":[{"href":"https:\/\/wp-opsec.eagleeye-intelligence.com\/index.php?rest_route=\/wp\/v2\/types\/page"}],"replies":[{"embeddable":true,"href":"https:\/\/wp-opsec.eagleeye-intelligence.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=42"}],"version-history":[{"count":11,"href":"https:\/\/wp-opsec.eagleeye-intelligence.com\/index.php?rest_route=\/wp\/v2\/pages\/42\/revisions"}],"predecessor-version":[{"id":110,"href":"https:\/\/wp-opsec.eagleeye-intelligence.com\/index.php?rest_route=\/wp\/v2\/pages\/42\/revisions\/110"}],"up":[{"embeddable":true,"href":"https:\/\/wp-opsec.eagleeye-intelligence.com\/index.php?rest_route=\/wp\/v2\/pages\/16"}],"wp:attachment":[{"href":"https:\/\/wp-opsec.eagleeye-intelligence.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=42"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}