coder/skills/sshd/SKILL.md
OpenSSH server and client on port 22 for remote access. Use when working with SSH access, remote login, or sshd configuration in containers/VMs.
npx skillsauth add overthinkos/overthink-plugins sshdInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
| Property | Value |
|----------|-------|
| Ports | 22 |
| Install files | layer.yml |
openssh-server (RPM / deb) — SSH daemonopenssh-clients (RPM) / openssh-client (deb, singular) — SSH client tools (ssh, scp, sftp)openssh (pac) — Arch metapackage bundling both daemon and clientsudo (rpm / pac / deb) — required for the NOPASSWD rule this layer writesrpm: (Fedora), pac: (Arch), deb: (Debian/Ubuntu) — full parity across all four supported package-format families. The openssh-server / openssh-clients naming differs per distro; package-existence tests use package_map: to resolve (see below).
Cross-distro package-test pattern: the sshd layer's
openssh-server-package check uses package_map: to resolve the right
name per distro — openssh-server on Fedora/Debian, openssh on Arch.
This is the canonical worked example for the package_map feature; see
/ov-eval:eval "Cross-distro package names (package_map:)" for the
mechanics and the priority ordering (fedora:43 > fedora when both
match).
- id: openssh-server-package
package: openssh-server # default
package_map:
arch: openssh
fedora: openssh-server
fedora:43: openssh-server
debian: openssh-server
ubuntu: openssh-server
installed: true
getent passwd 1000The sudoers drop-in at /etc/sudoers.d/ov-user targets the actual uid-1000 account, whatever it happens to be named on the running base image. The layer no longer hardcodes a literal user — instead it discovers the account name at build time via getent passwd 1000:
tasks:
- cmd: |
account=$(getent passwd 1000 | cut -d: -f1)
if [ -z "$account" ]; then
echo "sshd layer: no uid-1000 account found — refusing to write sudoers" >&2
exit 1
fi
printf '%s ALL=(ALL) NOPASSWD: ALL\n' "$account" > /etc/sudoers.d/ov-user
chmod 0440 /etc/sudoers.d/ov-user
user: root
This works uniformly across both user-policy modes:
| Image | Resolved account | Sudoers content |
|---|---|---|
| fedora-coder, arch-coder, debian-coder | user (create mode — /ov-image:image "user_policy") | user ALL=(ALL) NOPASSWD: ALL |
| ubuntu-coder | ubuntu (adopt mode — /ov-distros:ubuntu base_user:) | ubuntu ALL=(ALL) NOPASSWD: ALL |
Why not ${USER} substitution? The generator substitutes ${USER} in task fields (paths, URLs, etc.) but not inside cmd: command text — cmd: is passed verbatim to bash, and bash at RUN time doesn't have $USER exported. getent is the robust, fully-generic alternative. See /ov-image:layer "${VAR} substitution scope" and /ov-image:image "user_policy" for the full architectural context.
# image.yml -- typically used via bootc-base composition
my-image:
layers:
- sshd
bootc-base composition layer (used in bootc images)/ov-distros:aurora/ov-distros:bazzite (via bootc-base) — worked example exercising the dual-mode sudo test below/etc/sudoers.d/ov-user (the NOPASSWD rule written by this layer) is
root:root 0750 — the non-root test user (uid 1000 in containers)
cannot traverse /etc/sudoers.d/. A file: /etc/sudoers.d/ov-user; exists: true
test reports "missing" even when the file is present. Use
command: sudo -n -l; stdout: [{contains: NOPASSWD}] to verify the
semantic instead. See /ov-eval:eval Authoring Gotcha #10.127.0.0.1:${HOST_PORT:2222}, not
${CONTAINER_IP}:${HOST_PORT:2222}. See /ov-eval:eval Gotcha #1.runuser -u user -- wrapperov eval image runs with USER=1000 on container images but USER=0 on bootc images (bootc intentionally keeps USER=root because systemd manages user sessions via login). A naïve sudo -n -l; contains: NOPASSWD check fails on bootc — running as root prints root's Defaults block, which doesn't contain the literal string NOPASSWD. The layer's current test drops to user explicitly when running as root:
- id: sudoers-ov-user
command: |
if [ "$(id -u)" = "0" ]; then
runuser -u user -- sudo -n -l
else
sudo -n -l
fi
exit_status: 0
stdout:
- contains: "NOPASSWD"
Portability note: use runuser -u user -- <cmd>, not
runuser -l user -s /bin/bash -c '<cmd>'. On Arch util-linux (2.42+),
the -l … -c form swallows the wrapped command's stdout — reproduced
cleanly: runuser -l user -s /bin/bash -c 'sudo -n -l' prints nothing
and exits 0, while runuser -u user -- sudo -n -l prints the full
NOPASSWD listing. The layer was fixed to -u … -- after this was
caught during arch-ov bring-up. See /ov-eval:eval Authoring Gotcha #11.
/ov-distros:bootc-base -- composition that includes this layer/ov-distros:bootc-config -- the bootc boot wiring (tty1 autologin + systemd-user supervisord) that typically runs alongside this layer/ov-distros:cloud-init -- depends on sshd for VM provisioning/ov-distros:bazzite -- canonical bootc worked example that exercises the dual-mode sudo test/ov-coder:ubuntu-coder -- canonical adopt-mode example; sudoers correctly targets ubuntu via getent/ov-coder:debian-coder -- canonical create-mode deb-family example; sudoers targets user/ov-distros:ubuntu -- declares the base_user: block that makes ubuntu-coder run as ubuntu/ov-eval:eval -- declarative testing framework (gotchas #10 and #11, package_map:, exclude_distros:)/ov-image:image -- user_policy: field (create / adopt / auto) that drives which account this layer's sudoers targets/ov-image:layer -- layer authoring (${VAR} substitution scope, cmd: vs write:)Use when the user asks about:
development
Claude Code multi-agent support in Overthink — sub-agents, dynamic workflows, and agent teams, and how each drives the existing `ov eval` disposable beds to test and verify. MUST be invoked before authoring or invoking an ov sub-agent / dynamic workflow / agent team, wiring agent-lifecycle hooks, or asking "which primitive should drive the R10 beds?".
tools
Mounts a virtiofs share tagged `workspace` at /workspace inside a VM guest via a systemd .mount unit. Use when a kind:vm entity shares a host directory into the guest and you need it auto-mounted (and re-mounted at every boot).
development
MUST be invoked before any work involving: the `kind: android` schema kind, a `target: android` deploy, the `apk:` layer package format (installing Android apps declaratively), AndroidDeployTarget, an in-pod emulator OR a remote/physical adb-endpoint device, or nested `pod → android` deployment. The first-class Android device + app surface that sits above `ov eval adb`/`appium`.
tools
Use when committing, branching, pushing, merging, tagging, creating PRs, or approving/merging PRs with gh — the feat/-branch, R10-gated, never-force-push landing workflow across the main repo + the plugins submodule + image/<distro> submodules. Covers sync-to-upstream, branch/worktree pruning, the fork+PR path for contributors without write access, and cross-repo @github landing order.