using another computer remotely

Using Another Computer Remotely

Suppose we have a second computer whose operating system, processor, or tools we need. We may want to sit at our usual computer, wake the second computer, send it a project, build the project there, copy the result back, and let the second computer return to sleep. This article builds that workflow from the beginning for Linux, macOS, and Windows.

We will use SSH for the remote terminal and encrypted file transfers. On Windows, we will use MSYS2 as the Unix-like command-line and build environment. Windows' built-in OpenSSH service will accept the connection, then it can launch a build inside MSYS2.

The people and machines involved

The person asking another computer to do something is the operator. The computer at the operator's keyboard initiates an SSH connection, so SSH calls it the client. It is also commonly called the local machine.

The computer being contacted runs an SSH server, so SSH calls it the server. It is also called the remote machine, target machine, or host. “Client” and “server” describe the two machines' roles in this connection; they do not imply that either computer is permanently more important.

operator
   |
   | uses
   v
local machine / SSH client  -------- SSH -------->  remote machine / SSH server
                                                     |
                                                     v
                                             a particular user account

SSH access belongs to an account on the remote machine, not to the machine in general. In ssh ada@build-mac.local, ada is a user on the Mac, even if the local computer has a differently named user.

First case: both computers are already on

Begin with both computers awake and connected to the same local network. The remote machine needs an SSH server listening for connections. The local machine only needs an SSH client. No router port forwarding is needed when both machines are on the same local network.

Do not begin with Wake-on-LAN. First make SSH, authentication, file copying, and the build work while we can walk over to the target and see what it is doing. Power management is much easier to diagnose after this foundation works.

Prepare the command line on the local machine

Linux

Most desktop Linux installations already have the OpenSSH client. Check without assuming a particular distribution:

command -v ssh
ssh -V

If it is absent, install the OpenSSH client with the distribution's package manager. Package names are commonly openssh-client or openssh.

macOS

macOS includes an SSH client. Apple's Command Line Tools are not required merely to use SSH, but they provide the compiler and related tools needed for our build example:

ssh -V
xcode-select -p
cc --version

If xcode-select -p says that the tools are missing, request their installer:

xcode-select --install

Windows with MSYS2

Open the MSYS2 UCRT64 terminal. UCRT is Microsoft's current C runtime; the UCRT64 environment is a good general default for native 64-bit Windows software. Update MSYS2, then install the SSH client, rsync, and a C compiler:

pacman -Syu
pacman -S --needed openssh rsync mingw-w64-ucrt-x86_64-gcc

If MSYS2 asks us to close the terminal during a core update, close it, reopen UCRT64, and run the commands again. From this point, commands such as ssh, scp, rsync, and ssh-keygen look much like their Linux equivalents.

Enable the SSH server on the remote machine

Linux target

Check whether the OpenSSH server is installed:

command -v sshd

If it is absent, install the OpenSSH server with the distribution's package manager. The package is commonly named openssh-server or openssh. Distributions using systemd usually name the service either ssh or sshd:

systemctl list-unit-files | grep -E '^(ssh|sshd)\.service'

# Use the name that the preceding command displayed.
sudo systemctl enable --now sshd
sudo systemctl status sshd

On a distribution without systemd, enable sshd with that system's service manager. If a firewall is active, allow TCP port 22 from the local network only. The firewall command differs by distribution, so inspect the active firewall rather than blindly copying a command for a different system.

macOS target

macOS already contains an SSH server. Open System Settings → General → Sharing, enable Remote Login, and choose which users may log in. On older macOS versions, the path is System Preferences → Sharing → Remote Login.

It can also be enabled from a Mac terminal:

sudo systemsetup -setremotelogin on
sudo systemsetup -getremotelogin

Some macOS versions restrict systemsetup unless the terminal has Full Disk Access. The Sharing panel is the simpler fallback.

Windows target

MSYS2 supplies our tools and build environment, but the Windows OpenSSH service is the most direct way to accept a connection before anyone has logged into the desktop. In an Administrator PowerShell window, inspect and install the optional OpenSSH Server feature:

Get-WindowsCapability -Online | Where-Object Name -like 'OpenSSH*'
Add-WindowsCapability -Online -Name OpenSSH.Server~~~~0.0.1.0
Start-Service sshd
Set-Service -Name sshd -StartupType Automatic

Installation normally creates and enables the inbound firewall rule named OpenSSH-Server-In-TCP. Check that rule if connections time out. The initial SSH shell is Windows' configured OpenSSH shell, not automatically an MSYS2 shell; we will bridge into MSYS2 for builds later.

Find the remote account and network address

Run the relevant commands at the remote machine. Record both the username and one address on the local network.

Linux

whoami
ip -brief -4 address show

hostname -I is another convenient option when it is available. Ignore loopback address 127.0.0.1; it means “this same machine.” Home-network addresses commonly begin with 192.168., 10., or 172.16. through 172.31..

macOS

whoami
scutil --get LocalHostName
ipconfig getifaddr en0

If en0 has no address, use networksetup -listallhardwareports to find the device name for the active Wi-Fi or Ethernet port. A Mac whose local hostname is Build-Mac may also be reachable as Build-Mac.local through Bonjour.

Windows

From MSYS2, these Windows programs print the Windows account and network configuration:

powershell.exe -NoProfile -Command '[Environment]::UserName'
ipconfig.exe

Use the Windows account name, not the name of the MSYS2 environment. If the address changes later, make a DHCP reservation in the router or use a working local hostname instead of scattering a temporary address through scripts.

Make and verify the first connection

From the local machine, combine the remote username and address:

ssh ada@192.168.1.105

The first connection displays the server's host-key fingerprint. This fingerprint lets us determine whether the machine answering really is our target. Before entering the target account's password, compare it with the fingerprint printed directly on that target.

On a Linux or macOS target:

sudo ssh-keygen -lf /etc/ssh/ssh_host_ed25519_key.pub

On a Windows target, from Administrator PowerShell:

ssh-keygen.exe -lf "$env:ProgramData\ssh\ssh_host_ed25519_key.pub"

Compare the complete SHA256: value. After we accept the correct key, SSH records it in the local machine's ~/.ssh/known_hosts. A later “host identification has changed” warning deserves investigation; do not reflexively delete the old entry.

SSH negotiates encryption before asking for the account password. The password is therefore not sent as readable text across the network. Verifying the host key still matters: encryption to an impostor would protect the password from bystanders but not from the impostor itself.

Use a regular remote account and an SSH key

Normally connect to a regular user intended for this work, not Linux root, a macOS administrator acting with elevated privileges, or a Windows administrator. Use sudo or an Administrator terminal only for the individual operations that require it. A dedicated build account is useful when automation should have access to only build files and tools.

Create a key on the local machine:

ssh-keygen -t ed25519

The private file, normally ~/.ssh/id_ed25519, stays on the local machine. The public .pub file is the part installed for the remote account. Give the private key a passphrase unless unattended automation requires a separate, tightly restricted key.

Install the key on Linux or macOS

If ssh-copy-id is available on the local machine:

ssh-copy-id -i ~/.ssh/id_ed25519.pub ada@192.168.1.105

ssh-copy-id makes an ordinary encrypted SSH connection, authenticates once with the remote account's current password, and appends only the public key to that account's ~/.ssh/authorized_keys. It does not send the private key. If ssh-copy-id is unavailable, this portable command performs the essential operation:

cat ~/.ssh/id_ed25519.pub | ssh ada@192.168.1.105 \
  'umask 077; mkdir -p ~/.ssh; cat >> ~/.ssh/authorized_keys'

The manual command can add a duplicate if run repeatedly; ssh-copy-id normally checks first.

Install the key on Windows

For an ordinary, non-administrator Windows account, put the complete public-key line in C:\Users\the-user\.ssh\authorized_keys. Creating the file from a PowerShell session logged in as that target user avoids confusing ownership.

Windows OpenSSH treats accounts in the Administrators group differently by default: their keys go in C:\ProgramData\ssh\administrators_authorized_keys, and that file must be writable only by Administrators and SYSTEM:

icacls.exe "C:\ProgramData\ssh\administrators_authorized_keys" /inheritance:r `
  /grant "Administrators:F" /grant "SYSTEM:F"

A non-administrator build account is usually simpler and safer. Test a fresh connection before disabling any password-based recovery route.

Give the target a short SSH name

Add an entry to ~/.ssh/config on the local machine:

Host build-mac
    HostName 192.168.1.105
    User ada
    IdentityFile ~/.ssh/id_ed25519

Now all SSH-based tools can use the same name:

ssh build-mac

Run commands and move files

A command after the target name runs on the target instead of opening an interactive shell:

ssh build-mac 'uname -a'

Send a project with scp:

scp -r ./project build-mac:~/remote-build/

Copy a finished artifact back:

mkdir -p ./artifacts
scp build-mac:~/remote-build/project/out/my-program ./artifacts/

For repeated transfers, rsync can avoid retransmitting unchanged data. It must be installed on both ends, and the remote SSH shell must be able to find it. See Sending files between Linux computers for its source/destination syntax and the important meaning of a trailing slash.

Build on the remote computer

If we send source code to a Mac and invoke the Mac's compiler there, that is a remote native build. Doing the same project on several operating systems is often called a multi-platform build. It is not cross-compilation in the strict sense. Cross-compilation means that a compiler running on one platform directly produces code for a different target platform.

On a Linux or macOS target, a project can expose one build script:

ssh build-mac 'cd ~/remote-build/project && ./build.sh'

On the Mac, verify xcode-select -p and cc --version locally once before relying on an unattended build.

Run a build inside MSYS2 on a Windows target

An SSH session accepted by Windows does not automatically receive the environment established by the MSYS2 UCRT64 launcher. Create a small Windows command file such as C:\Users\build\run-msys2-build.cmd on the target:

@echo off
C:\msys64\usr\bin\env.exe MSYSTEM=UCRT64 CHERE_INVOKING=1 /usr/bin/bash -lc "cd /c/Users/build/remote-project && ./build.sh"

MSYSTEM=UCRT64 selects that MSYS2 environment. Bash's -l requests a login shell so the environment's startup setup runs, and -c supplies the command. Invoke the wrapper from the local machine:

ssh windows-build 'C:\Users\build\run-msys2-build.cmd'

Keep the wrapper under the build user's control and give it a narrow purpose. This is clearer than making every remote Windows shell pretend to be an interactive MSYS2 terminal.

Second case: the remote computer is asleep or off

SSH cannot connect to a computer that is not running. Wake-on-LAN, abbreviated WoL, is a separate mechanism: a network interface watches for a small “magic packet” containing its hardware address and asks the machine to wake.

Wake-on-LAN is not the same as remotely pressing the power button. Support depends on the firmware, network adapter, driver, connection type, and power state. Ethernet is the most reliable choice. Waking over Wi-Fi is supported only by some combinations of hardware and operating system.

For a repeatable on-demand build machine, prefer sleep, or hibernation on hardware that explicitly supports waking from it. Waking from a complete soft-off state is hardware-dependent. In particular, Windows officially supports ordinary WoL from sleep and explicit hibernation, not universally from Fast Startup's hybrid shutdown or the fully off state. Macs should likewise be treated as wake-from-sleep machines unless that exact model has been tested otherwise.

Record the target's hardware address

A network interface's MAC address identifies that interface on the local network. It is six bytes commonly written as a1:b2:c3:d4:e5:f6. It is not a secret.

On Linux:

ip link show

# If ethtool is installed, this is another useful check:
sudo ethtool -P enp3s0

On macOS:

networksetup -listallhardwareports

On Windows, from MSYS2 or a Windows terminal:

getmac.exe /v
ipconfig.exe /all

Record the address of the interface that will remain connected while the computer sleeps, normally wired Ethernet. Also find the local network's broadcast address. A network such as 192.168.1.0/24 commonly uses 192.168.1.255, but do not assume this if the subnet differs.

Send a Wake-on-LAN packet from any of the three systems

This site includes a small portable C program so the same command can be built on Linux, macOS, or Windows/MSYS2: download send_wake_on_lan.c.

Compile it on Linux or macOS:

cc -std=c11 -O2 send_wake_on_lan.c -o send-wol

Compile it in the MSYS2 UCRT64 terminal:

cc -std=c11 -O2 send_wake_on_lan.c -lws2_32 -o send-wol.exe

Send the packet, supplying the target MAC address and broadcast address:

./send-wol a1:b2:c3:d4:e5:f6 192.168.1.255

From MSYS2, use ./send-wol.exe. Omitting the second argument uses the limited broadcast address 255.255.255.255, but some networks require their directed broadcast address instead. The program sends UDP port 9 by convention; the magic packet itself is normally unauthenticated, so it should be treated as a local-network convenience rather than an authorization system.

Configure Wake-on-LAN on the target

Start in the machine's UEFI or older BIOS settings and enable an option such as Wake on LAN, Power on by PCI-E, or Resume by LAN. The operating system must then leave the adapter armed when it sleeps.

Linux target

If ethtool is installed, inspect the wired interface. Replace enp3s0 with its actual name:

sudo ethtool enp3s0

Under Supports Wake-on, the letter g means the adapter supports waking from a magic packet. Enable it for the current boot:

sudo ethtool -s enp3s0 wol g

A network manager or driver reload may reset that setting. If NetworkManager controls the connection, make it persistent on that connection profile:

nmcli connection show
sudo nmcli connection modify "Wired connection 1" 802-3-ethernet.wake-on-lan magic
sudo nmcli connection up "Wired connection 1"

Replace the profile name exactly. If systemd-networkd manages the interface instead, a .link file can use this shape:

[Match]
MACAddress=a1:b2:c3:d4:e5:f6

[Link]
WakeOnLan=magic

Configure the manager that actually owns the interface; do not layer several persistence mechanisms onto it without understanding which one wins.

Windows target

After enabling the firmware option, open the network adapter in Device Manager. Its Advanced page commonly has Wake on Magic Packet; its Power Management page commonly has Allow this device to wake the computer. Names vary by driver.

Administrator PowerShell can show and arm wake-capable devices:

powercfg /devicequery wake_programmable
powercfg /deviceenableawake "Exact adapter name"
powercfg /devicequery wake_armed

After an unexpected wake, powercfg /lastwake can help identify its source. Test sleep first, then explicit hibernation. Do not assume a normal Windows shutdown will remain remotely wakeable; Fast Startup and the fully off state have additional limitations even when the firmware advertises WoL.

macOS target

In System Settings, enable Wake for network access under Battery or Energy Saver. The exact panel name depends on the macOS version. The equivalent power-management setting can be inspected and enabled from Terminal:

pmset -g custom
sudo pmset -a womp 1

womp means wake on magic packet. For an older MacBook, test while it is connected to power, using Ethernet if possible, and initially leave the lid open. Some Macs support Wake on Wireless; inspect the Wi-Fi report rather than assuming it:

system_profiler SPAirPortDataType | grep "Wake On Wireless"

Apple's remote-wake behavior can also depend on whether the machines share a subnet or a Bonjour sleep proxy is present. This is another reason to establish the workflow on one local network first.

Test waking separately from building

  1. Confirm SSH works while the target is awake.
  2. Put the target to sleep manually.
  3. Send the magic packet from another machine.
  4. Watch the target directly and confirm that it wakes.
  5. Wait for SSH to become available.

After key authentication and the host key have already been set up, this loop waits until SSH is ready:

until ssh -o BatchMode=yes -o ConnectTimeout=2 build-mac true; do
    sleep 2
done

BatchMode=yes prevents a hidden password or confirmation prompt from making an automation appear to hang. Only after sleep works should we test hibernation or shutdown, and only if the hardware documentation claims to support that state.

An on-demand build sequence

A simple local script can now perform the sequence explicitly:

#!/bin/sh
set -eu

./send-wol a1:b2:c3:d4:e5:f6 192.168.1.255

until ssh -o BatchMode=yes -o ConnectTimeout=2 build-mac true; do
    sleep 2
done

scp -r ./project build-mac:~/remote-build/
ssh build-mac 'cd ~/remote-build/project && ./build.sh'
mkdir -p ./artifacts
scp build-mac:~/remote-build/project/out/my-program ./artifacts/

For Windows, replace the remote build command with the MSYS2 wrapper shown earlier. Real build scripts should also use a maximum wait time, preserve build logs, and stop before scheduling sleep or shutdown if copying the artifact fails.

Leave it available for 30 minutes, then power it down

Schedule the final power action only after the artifact has been copied safely. The following commands request a shutdown in 30 minutes.

Linux:

ssh -t linux-build 'sudo shutdown -h +30'

# Cancel before it occurs:
ssh -t linux-build 'sudo shutdown -c'

macOS:

ssh -t build-mac 'sudo shutdown -h +30'

Windows:

ssh windows-build 'shutdown.exe /s /t 1800'

# Cancel before it occurs:
ssh windows-build 'shutdown.exe /a'

These commands really ask for shutdown. That satisfies “turn it off,” but it may also make the next arbitrary network wake impossible. For a machine meant to be used repeatedly on demand, configure its normal power policy to sleep, run the build under the operating system's sleep-inhibition facility, keep it inhibited for another 30 minutes, and then allow normal sleep:

After the inhibitor exits, the operating system's configured idle policy decides when to sleep. Exact unattended “sleep now” automation is privileged and OS-specific. Prefer a root-owned system service, macOS launchd job, or Windows scheduled task that can perform only this one action. Do not store an administrator password in a script and do not grant password-free access to an arbitrary shell merely to make power management convenient.

Connecting from outside the local network

Broadcast Wake-on-LAN packets normally stay inside one local network, and exposing SSH or a Wake-on-LAN UDP port directly through a home router increases the attack surface. For remote use, connect to the home network through a VPN or keep a small, always-on machine there as an authenticated relay. That relay can send the local magic packet and provide a route to the build machine.

Do not forward a broadcast WoL port from the public Internet. The magic packet normally proves no identity, and routers deliberately restrict directed broadcasts.

What to check when it fails

Further reading


edit this page