> ## Documentation Index
> Fetch the complete documentation index at: https://docs.dedaluslabs.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# SSH

> Open an SSH session to a Dedalus Machine

export const Pop = ({content, children}) => {
  const [open, setOpen] = useState(false);
  const [hoverOpen, setHoverOpen] = useState(false);
  const [isDark, setIsDark] = useState(false);
  const triggerRef = useRef(null);
  const portalRef = useRef(null);
  const closeTimer = useRef(null);
  const isOpen = open || hoverOpen;
  useEffect(() => {
    const probe = () => setIsDark(document.documentElement.classList.contains("dark"));
    probe();
    const obs = new MutationObserver(probe);
    obs.observe(document.documentElement, {
      attributes: true,
      attributeFilter: ["class"]
    });
    return () => obs.disconnect();
  }, []);
  const onEnter = () => {
    clearTimeout(closeTimer.current);
    setHoverOpen(true);
  };
  const onLeave = () => {
    closeTimer.current = setTimeout(() => setHoverOpen(false), 150);
  };
  useEffect(() => {
    if (!isOpen) return;
    const node = document.createElement("div");
    node.setAttribute("role", "dialog");
    node.textContent = content;
    document.body.appendChild(node);
    portalRef.current = node;
    const dark = document.documentElement.classList.contains("dark");
    Object.assign(node.style, {
      position: "fixed",
      zIndex: "9999",
      maxWidth: "20rem",
      width: "max-content",
      padding: "0.5rem 0.75rem",
      fontSize: "0.875rem",
      lineHeight: "1.55",
      color: dark ? "rgb(244, 244, 245)" : "rgb(39, 39, 42)",
      backgroundColor: dark ? "rgba(24, 24, 27, 0.4)" : "rgba(255, 255, 255, 0.4)",
      backdropFilter: "blur(4px) saturate(160%)",
      WebkitBackdropFilter: "blur(4px) saturate(160%)",
      border: dark ? "1px solid rgba(255, 255, 255, 0.18)" : "1px solid rgba(0, 0, 0, 0.16)",
      boxShadow: "0 8px 32px rgba(0, 0, 0, 0.12)",
      opacity: "0",
      transform: "translateY(-4px)",
      transition: "opacity 180ms cubic-bezier(0.16, 1, 0.3, 1), transform 180ms cubic-bezier(0.16, 1, 0.3, 1)"
    });
    const place = () => {
      const r = triggerRef.current?.getBoundingClientRect();
      if (!r || !portalRef.current) return;
      portalRef.current.style.left = `${r.left}px`;
      portalRef.current.style.top = `${r.bottom + 8}px`;
    };
    place();
    requestAnimationFrame(() => {
      node.style.opacity = "1";
      node.style.transform = "translateY(0)";
    });
    node.addEventListener("mouseenter", onEnter);
    node.addEventListener("mouseleave", onLeave);
    window.addEventListener("scroll", place, true);
    window.addEventListener("resize", place);
    return () => {
      window.removeEventListener("scroll", place, true);
      window.removeEventListener("resize", place);
      node.remove();
      portalRef.current = null;
    };
  }, [isOpen, content]);
  useEffect(() => {
    if (!open) return;
    const dismiss = e => {
      const inside = triggerRef.current?.contains(e.target) || portalRef.current?.contains(e.target);
      if (!inside) {
        setOpen(false);
        setHoverOpen(false);
      }
    };
    const onKey = e => e.key === "Escape" && (setOpen(false), setHoverOpen(false));
    document.addEventListener("mousedown", dismiss);
    document.addEventListener("keydown", onKey);
    return () => {
      document.removeEventListener("mousedown", dismiss);
      document.removeEventListener("keydown", onKey);
    };
  }, [open]);
  return <button ref={triggerRef} type="button" onClick={() => setOpen(v => !v)} onMouseEnter={onEnter} onMouseLeave={onLeave} aria-expanded={isOpen} className="cursor-pointer bg-transparent border-0 p-0 text-inherit underline decoration-dotted underline-offset-[3px] transition-colors" style={{
    textDecorationColor: isDark ? "rgb(113, 113, 122)" : "rgb(161, 161, 170)"
  }}>
			{children}
		</button>;
};

A session is a short-lived authorization to connect over real <Pop content="Short-lived real OpenSSH access to a machine using your public key and returned connection details.">SSH</Pop>. The machine wakes if it was sleeping.

Use the CLI for interactive login. It creates the session, writes the returned user certificate and gateway host trust into temporary OpenSSH files, enables strict host checking, and launches `ssh`.

<CodeGroup>
  ```bash CLI theme={"theme":{"light":"github-light","dark":"github-dark"}}
  dedalus ssh <machine_id>
  ```

  ```python Python theme={"theme":{"light":"github-light","dark":"github-dark"}}
  from pathlib import Path

  pubkey = Path("~/.ssh/id_ed25519.pub").expanduser().read_text().strip()
  sess = client.machines.ssh.create(machine_id="<machine_id>", public_key=pubkey)
  if not sess.connection:
      raise RuntimeError(f"SSH session is {sess.status}; poll until ready")
  print(sess.connection)
  ```

  ```typescript TypeScript theme={"theme":{"light":"github-light","dark":"github-dark"}}
  import fs from "node:fs";

  const publicKey = fs.readFileSync(`${process.env.HOME}/.ssh/id_ed25519.pub`, "utf8").trim();
  const sess = await client.machines.ssh.create({
    machine_id: "<machine_id>",
    public_key: publicKey,
  });
  if (!sess.connection) throw new Error(`SSH session is ${sess.status}; poll until ready`);
  console.log(sess.connection);
  ```

  ```go Go theme={"theme":{"light":"github-light","dark":"github-dark"}}
  pubkey, _ := os.ReadFile(filepath.Join(os.Getenv("HOME"), ".ssh", "id_ed25519.pub"))
  sess, _ := client.Machines.SSH.New(ctx, dedalus.MachineSSHNewParams{
      MachineID: machineID,
      SSHSessionCreateParams: dedalus.SSHSessionCreateParams{
          PublicKey: strings.TrimSpace(string(pubkey)),
      },
  })
  if !sess.JSON.Connection.Valid() {
      panic(fmt.Sprintf("SSH session is %s; poll until ready", sess.Status))
  }
  fmt.Printf("%+v\n", sess.Connection)
  ```
</CodeGroup>

The Python, TypeScript, and Go SDKs return the raw SSH session contract for custom tools. If you launch OpenSSH yourself, use `connection.user_certificate` as `CertificateFile` and `connection.host_trust` as an `@cert-authority` entry in `UserKnownHostsFile`; do not skip strict host checking.

<Accordion title="Parameters">
  <ParamField path="machine_id" type="string" required>
    The machine to open SSH access to.
  </ParamField>

  <ParamField body="public_key" type="string" required>
    Your OpenSSH public key, such as the contents of `~/.ssh/id_ed25519.pub`.
  </ParamField>
</Accordion>
