diff --git a/.github/workflows/multibuild.yaml b/.github/workflows/multibuild.yaml index 2d257d16c..557be6198 100644 --- a/.github/workflows/multibuild.yaml +++ b/.github/workflows/multibuild.yaml @@ -158,8 +158,34 @@ jobs: path: ./packages/dart/tarballs/${{ matrix.output-name }}.tgz if-no-files-found: error + universal_sh: + if: startsWith(github.ref, 'refs/tags/v') + defaults: + run: + working-directory: ./packages/dart/sshnoports/bundles + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v4.1.2 + - run: | + write_metadata() { + start_line="# SCRIPT METADATA" + end_line="# END METADATA" + file=$1 + variable=$2 + value=$3 + # since this is linux only, sed -i is safe without a file ext. + sed -i "/$start_line/,/$end_line/s|$variable=\".*\"|$variable=\"$value\"|g" "$file" + } + REF=${{ github.ref }} + TAG=${REF:11} + write_metadata universal.sh sshnp_version "$TAG" + - uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4.3.1 + with: + name: universal.sh + path: ./packages/dart/sshnoports/bundles/universal.sh + if-no-files-found: error notify_on_completion: - needs: [main_build, other_build] + needs: [main_build, other_build, universal_sh] runs-on: ubuntu-latest steps: - name: Google Chat Notification @@ -171,7 +197,7 @@ jobs: notify_on_failure: if: failure() - needs: [main_build, other_build] + needs: [main_build, other_build, universal_sh] runs-on: ubuntu-latest steps: - name: Google Chat Notification diff --git a/packages/dart/sshnoports/bundles/core/config/sshnp-config-template.env b/packages/dart/sshnoports/bundles/core/config/sshnp-config-template.env deleted file mode 100644 index 963759a3c..000000000 --- a/packages/dart/sshnoports/bundles/core/config/sshnp-config-template.env +++ /dev/null @@ -1,51 +0,0 @@ -### Template Config File for SSH No Ports ### -## Entries do not require "" e.g -## FROM=@alice - -# Sending atSign's atKeys file if not in ~/.atsign/keys/ -KEY_FILE= - -# Sending (a.k.a. client) atSign -FROM= - -# Receiving (a.k.a. remote) device atSign -TO= - -# Receiving (a.k.a. remote) device name -DEVICE= - -# atSign of srvd daemon or FQDN/IP address to connect back to -HOST= - -# TCP port to connect back to (only required if --host specified a FQDN/IP) -PORT= - -# Reverse ssh port to listen on, on your local machine, by sshnp default finds a spare port -LOCAL_PORT= - -# Public key file from ~/.ssh to be appended to authorized_hosts on the remote device -# If no path is specified, the default is ~/.ssh/ -# To specify a file in the current directory use: ./ -SSH_PUBLIC_KEY= - -# Add these commands to the local ssh command -# Use "," to separate your options -LOCAL_SSH_OPTIONS= - -# More logging (true/false) -VERBOSE= - -# Use RSA 4096 keys rather than the default ED25519 keys (true/false) -RSA= - -# username to use in the ssh session on the remote host -REMOTE_USER_NAME= - -# local sshd port number -LOCAL_SSHD_PORT= - -# Use legacy daemon (true/false) -LEGACY_DAEMON= - -# Root Domain to use with AtClient -ROOT_DOMAIN= diff --git a/packages/dart/sshnoports/bundles/create-test-archive.sh b/packages/dart/sshnoports/bundles/create-test-archive.sh new file mode 100755 index 000000000..3caf7662e --- /dev/null +++ b/packages/dart/sshnoports/bundles/create-test-archive.sh @@ -0,0 +1,17 @@ +#!/bin/sh + +script_dir="$(dirname -- "$(readlink -f -- "$0")")" +time_stamp=$(date +%s) + +tempdir="$script_dir/temp-$time_stamp/sshnp" +outfile="$script_dir/sshnp-$time_stamp" + +mkdir -p "$tempdir" +cp -R "$script_dir"/core/* "$tempdir/" +cp -R "$script_dir"/shell/* "$tempdir/" + +if [ "$(uname)" = 'Darwin' ]; then + ditto -c -k --keepParent "$tempdir" "$outfile.zip" +else + tar -cvzf "$outfile.tgz" "$script_dir/temp-$time_stamp" +fi diff --git a/packages/dart/sshnoports/bundles/shell/README.md b/packages/dart/sshnoports/bundles/shell/README.md deleted file mode 100644 index 948b760be..000000000 --- a/packages/dart/sshnoports/bundles/shell/README.md +++ /dev/null @@ -1,36 +0,0 @@ -# SSH No Ports Suite - -## Installation - -To install a member of the SSH No Ports suite, use the `install.sh` script. -View the full usage of the installation script by running: - -```sh -./install.sh --help -``` - -### Common install commands - -To install the sshnp client: - -```sh -./install.sh sshnp -``` - -To install the sshnpd daemon using systemd (must be run as root): - -```sh -sudo ./install.sh systemd sshnpd -``` - -To install the sshnpd daemon using a headless cron job: - -```sh -./install.sh headless sshnpd -``` - -To install the sshnpd daemon into a detached tmux session: - -```sh -./install.sh tmux sshnpd -``` diff --git a/packages/dart/sshnoports/bundles/shell/headless/root_srvd.sh b/packages/dart/sshnoports/bundles/shell/headless/root_srvd.sh deleted file mode 100755 index fe42d27f1..000000000 --- a/packages/dart/sshnoports/bundles/shell/headless/root_srvd.sh +++ /dev/null @@ -1,20 +0,0 @@ -#!/bin/sh -# disable "var is referenced but not assigned" warning for template -# shellcheck disable=SC2154 - -# Configuration of srvd service -# This unit script is a template for the srvd background service. -# You can configure the service by editing the variables below. -# This service file covers the common configuration options for srvd. -# To see all available options, run `srvd` with no arguments. - -atsign="@my_rvd" # MANDATORY: Srvd atSign -internet_address="" # MANDATORY: Public FQDN or IP address of the machine running the srvd -v="-v" # Comment to disable verbose logging - -sleep 10 # allow machine to bring up network -export USER="$user" -while true; do - /usr/local/bin/srvd -a "$atsign" -i "$internet_address" "$v" - sleep 10 -done diff --git a/packages/dart/sshnoports/bundles/shell/headless/root_sshnpd.sh b/packages/dart/sshnoports/bundles/shell/headless/root_sshnpd.sh deleted file mode 100755 index e765e241d..000000000 --- a/packages/dart/sshnoports/bundles/shell/headless/root_sshnpd.sh +++ /dev/null @@ -1,33 +0,0 @@ -#!/bin/sh -# disable "var is referenced but not assigned" warning for template -# shellcheck disable=SC2154 - -# Configuration of sshnpd service -# This script is a template for the sshnpd background service. -# You can configure the service by editing the variables below. -# This service file covers the common configuration options for sshnpd. -# To see all available options, run `sshnpd` with no arguments. - -manager_atsign="@example_client" # MANDATORY: Manager/client address (atSign) -device_atsign="@example_device" # MANDATORY: Device address (atSign) -device_name="default" # Device name -user="$(whoami)" # MANDATORY: Username -v="-v" # Comment to disable verbose logging - -# Uncomment if you wish the daemon to update authorized_keys to include public -# keys sent by authorized manager atSigns -# s="-s" - -# Uncomment if you wish to have the daemon make various information visible to -# the manager atsign - e.g. username, version, etc - without the manager atSign -# needing to know this daemon's device name -# u="-u" - -sleep 10 # allow machine to bring up network -export USER="$user" -while true; do - # The line below runs the sshnpd service, with the options set above. - # You can edit this line to further customize the service to your needs. - /usr/local/bin/sshnpd -a "$device_atsign" -m "$manager_atsign" -d "$device_name" "$s" "$u" "$v" - sleep 10 -done diff --git a/packages/dart/sshnoports/bundles/shell/headless/srvd.sh b/packages/dart/sshnoports/bundles/shell/headless/srvd.sh index d1e86046c..dcf3eaaa0 100755 --- a/packages/dart/sshnoports/bundles/shell/headless/srvd.sh +++ b/packages/dart/sshnoports/bundles/shell/headless/srvd.sh @@ -1,20 +1,16 @@ #!/bin/sh # disable "var is referenced but not assigned" warning for template # shellcheck disable=SC2154 - -# Configuration of srvd service -# This unit script is a template for the srvd background service. -# You can configure the service by editing the variables below. -# This service file covers the common configuration options for srvd. -# To see all available options, run `srvd` with no arguments. - +# SCRIPT METADATA +binary_path="$HOME/.local/bin" atsign="@my_rvd" # MANDATORY: Srvd atSign internet_address="" # MANDATORY: Public FQDN or IP address of the machine running the srvd v="-v" # Comment to disable verbose logging +# END METADATA sleep 10 # allow machine to bring up network export USER="$user" while true; do - "$HOME"/.local/bin/srvd -a "$atsign" -i "$internet_address" "$v" + "$binary_path"/srvd -a "$atsign" -i "$internet_address" "$v" sleep 10 done diff --git a/packages/dart/sshnoports/bundles/shell/headless/sshnpd.sh b/packages/dart/sshnoports/bundles/shell/headless/sshnpd.sh index 05886824b..98b24f9dd 100755 --- a/packages/dart/sshnoports/bundles/shell/headless/sshnpd.sh +++ b/packages/dart/sshnoports/bundles/shell/headless/sshnpd.sh @@ -1,33 +1,22 @@ #!/bin/sh # disable "var is referenced but not assigned" warning for template # shellcheck disable=SC2154 - -# Configuration of sshnpd service -# This script is a template for the sshnpd background service. -# You can configure the service by editing the variables below. -# This service file covers the common configuration options for sshnpd. -# To see all available options, run `sshnpd` with no arguments. - +# SCRIPT METADATA +binary_path="$HOME/.local/bin" manager_atsign="@example_client" # MANDATORY: Manager/client address (atSign) device_atsign="@example_device" # MANDATORY: Device address (atSign) device_name="default" # Device name user="$(whoami)" # MANDATORY: Username v="-v" # Comment to disable verbose logging - -# Uncomment if you wish the daemon to update authorized_keys to include public -# keys sent by authorized manager atSigns -# s="-s" - -# Uncomment if you wish to have the daemon make various information visible to -# the manager atsign - e.g. username, version, etc - without the manager atSign -# needing to know this daemon's device name -# u="-u" +s="-s" # Comment to disable sending public keys +u="-u" # Comment to disable sending user information +# END METADATA sleep 10 # allow machine to bring up network export USER="$user" while true; do # The line below runs the sshnpd service, with the options set above. # You can edit this line to further customize the service to your needs. - "$HOME"/.local/bin/sshnpd -a "$device_atsign" -m "$manager_atsign" -d "$device_name" "$s" "$u" "$v" + "$binary_path"/sshnpd -a "$device_atsign" -m "$manager_atsign" -d "$device_name" "$s" "$u" "$v" sleep 10 done diff --git a/packages/dart/sshnoports/bundles/shell/install.sh b/packages/dart/sshnoports/bundles/shell/install.sh index 84a335dc1..253f1e736 100755 --- a/packages/dart/sshnoports/bundles/shell/install.sh +++ b/packages/dart/sshnoports/bundles/shell/install.sh @@ -2,382 +2,480 @@ # SYSTEM GIVENS # is_root() { - [ "$(id -u)" -eq 0 ] + [ "$(id -u)" -eq 0 ] } +unset binary_dir user_home=$HOME define_env() { - script_dir="$(dirname -- "$( readlink -f -- "$0"; )")" - bin_dir="/usr/local/bin" - systemd_dir="/etc/systemd/system" - if is_root; then - user="$SUDO_USER" - if [ -z "$user" ]; then - user="root" - else - # we are root, but via sudo - # so get home directory of SUDO_USER - user_home=$(sudo -u "$user" sh -c 'echo $HOME') - fi - else - user="$USER" - fi - user_bin_dir="$user_home/.local/bin" - user_sshnpd_dir="$user_home/.sshnpd" - user_log_dir="$user_sshnpd_dir/logs" - user_ssh_dir="$user_home/.ssh" + script_dir="$(dirname -- "$(readlink -f -- "$0")")" + bin_dir="/usr/local/bin" + systemd_dir="/etc/systemd/system" + if is_root; then + user="$SUDO_USER" + if [ -z "$user" ]; then + user="root" + else + # we are root, but via sudo + # so get home directory of SUDO_USER + user_home=$(sudo -u "$user" sh -c 'echo $HOME') + fi + else + user="$USER" + fi + launchd_dir="$user_home/Library/LaunchAgents" + user_bin_dir="$user_home/.local/bin" + user_sshnpd_dir="$user_home/.sshnpd" + user_log_dir="$user_sshnpd_dir/logs" + user_ssh_dir="$user_home/.ssh" } is_darwin() { - [ "$(uname)" = 'Darwin' ] + [ "$(uname)" = 'Darwin' ] +} + +sedi() { + if is_darwin; then + sed -i '' "$@" + else + sed -i "$@" + fi } no_mac() { - if is_darwin; then - echo "Error: this operation is only supported on linux" - exit 1 - fi + if is_darwin; then + echo "Error: this operation is only supported on linux" + exit 1 + fi } root_only() { - if ! is_root; then - echo "Error: this operation requires root privileges" - exit 1 - fi + if ! is_root; then + echo "Error: this operation requires root privileges" + exit 1 + fi } # USAGE # usage() { - if [ -z "$arg_zero" ]; then - arg_zero='install.sh' - fi - echo "$arg_zero [command]" - echo "Available commands:" - echo "at_activate - install at_activate" - echo "sshnp - install sshnp" - echo "sshnpd - install sshnpd" - echo "srv - install srv" - echo "srvd - install srvd" - echo "binaries - install all base binaries" - echo "" - echo "debug_srvd - install srvd with debugging enabled" - echo "debug - install all debug binaries" - echo "" - echo "all - install all binaries (base and debug)" - if ! is_darwin; then - echo "" - echo "systemd - install a systemd unit" - echo " available units: [sshnpd, srvd]" - fi - echo "" - echo "headless - install a headless cron job" - echo " available jobs: [sshnpd, srvd]" - echo "" - echo "tmux - install a service in a tmux session" - echo " available services: [sshnpd, srvd]" + if [ -z "$arg_zero" ]; then + arg_zero='install.sh' + fi + echo "$arg_zero [...options] [command]" + echo "Available commands:" + echo "at_activate - install at_activate" + echo "npt - install npt" + echo "sshnp - install sshnp" + echo "sshnpd - install sshnpd" + echo "srv - install srv" + echo "srvd - install srvd" + echo "binaries - install all base binaries (everything above)" + echo "" + echo "debug_srvd - install srvd with debugging enabled" + echo "debug - install all debug binaries" + echo "" + echo "all - install all binaries (everything above)" + if ! is_darwin; then + echo "" + echo "systemd - install a systemd unit" + echo " available units: [sshnpd, srvd]" + fi + if is_darwin; then + echo "" + echo "launchd - install a launchd unit" + echo " available units: [sshnpd]" + fi + echo "" + echo "headless - install a headless cron job" + echo " available jobs: [sshnpd, srvd]" + echo "" + echo "tmux - install a service in a tmux session" + echo " available services: [sshnpd, srvd]" + echo "" + echo "Options:" + echo "-b - override the directory in which the binaries are written to" + echo "-u - override the user to install the binaries for" } # SETUP AUTHORIZED KEYS # setup_authorized_keys() { - mkdir -p "$user_ssh_dir" - touch "$user_ssh_dir/authorized_keys" - chmod 644 "$user_ssh_dir/authorized_keys" + mkdir -p "$user_ssh_dir" + touch "$user_ssh_dir/authorized_keys" + chmod 644 "$user_ssh_dir/authorized_keys" } # INSTALL BINARIES # install_single_binary() { - if is_root; then - dest="$bin_dir" - else - dest="$user_bin_dir" - fi - mkdir -p "$dest" - if test -f "$dest/$1"; then - if test -f "$dest/$1.old"; then - if rm -f "$dest/$1.old" - then - echo "=> Removed $dest/$1.old" - else - echo "Failed to remove $dest/$1.old - aborting" - exit 1 - fi - fi - if mv "$dest/$1" "$dest/$1.old" - then - echo "=> Renamed existing binary $dest/$1 to $dest/$1.old" - else - echo "Failed to rename $dest/$1 to $dest/$1.old - aborting" - exit 1 - fi - fi - cp -f "$script_dir/$1" "$dest/$1" - - echo "=> Installed $1 to $dest" - if is_root & ! [ -f "$user_bin_dir/$1" ] ; then - mkdir -p "$user_bin_dir" - ln -sf "$dest/$1" "$user_bin_dir/$1" - echo "=> Linked $user_bin_dir/$1 to $dest/$1" - fi + if [ -n "$binary_dir" ]; then + dest="$binary_dir" + elif is_root; then + dest="$bin_dir" + else + dest="$user_bin_dir" + fi + mkdir -p "$dest" + if test -f "$dest/$1"; then + if test -f "$dest/$1.old"; then + if rm -f "$dest/$1.old"; then + echo "=> Removed $dest/$1.old" + else + echo "Failed to remove $dest/$1.old - aborting" + exit 1 + fi + fi + if mv "$dest/$1" "$dest/$1.old"; then + echo "=> Renamed existing binary $dest/$1 to $dest/$1.old" + else + echo "Failed to rename $dest/$1 to $dest/$1.old - aborting" + exit 1 + fi + fi + cp -f "$script_dir/$1" "$dest/$1" + + echo "=> Installed $1 to $dest" + if + is_root & + ! [ -f "$user_bin_dir/$1" ] + then + mkdir -p "$user_bin_dir" + ln -sf "$dest/$1" "$user_bin_dir/$1" + echo "=> Linked $user_bin_dir/$1 to $dest/$1" + fi } install_base_binaries() { - install_single_binary "at_activate" - install_single_binary "sshnp" - install_single_binary "sshnpd" - install_single_binary "srv" - install_single_binary "srvd" + install_single_binary "at_activate" + install_single_binary "sshnp" + install_single_binary "sshnpd" + install_single_binary "srv" + install_single_binary "srvd" + install_single_binary "npt" } install_debug_binary() { - if is_root; then - dest="$bin_dir" - else - dest="$user_bin_dir" - fi - mkdir -p "$dest" - cp "$script_dir/debug/$1" "$bin_dir/debug_$1" - echo "=> Installed debug_$1 to $dest" + if [ -n "$binary_dir" ]; then + dest="$binary_dir" + elif is_root; then + dest="$bin_dir" + else + dest="$user_bin_dir" + fi + mkdir -p "$dest" + cp "$script_dir/debug/$1" "$bin_dir/debug_$1" + echo "=> Installed debug_$1 to $dest" } install_debug_binaries() { - install_debug_binary "srvd" + install_debug_binary "srvd" } install_all_binaries() { - install_base_binaries - install_debug_binaries + install_base_binaries + install_debug_binaries } # SYSTEMD # post_systemd_message() { - echo "Systemd unit installed, make sure to configure the unit by editing $dest" - echo "Learn more in $script_dir/systemd/README.md" - echo "" - echo "To enable the service on next boot:" - echo " sudo systemctl enable $unit_name" - echo "" - echo "To start the service immediately:" - echo " sudo systemctl start $unit_name" + echo "Systemd unit installed, make sure to configure the unit by editing $dest" + echo "Learn more in $script_dir/systemd/README.md" + echo "" + echo "To enable the service on next boot:" + echo " sudo systemctl enable $unit_name" + echo "" + echo "To start the service immediately:" + echo " sudo systemctl start $unit_name" } install_systemd_unit() { - unit_name="$1" - no_mac - mkdir -p "$systemd_dir" - dest="$systemd_dir/$unit_name" - cp "$script_dir/systemd/$unit_name" "$dest" - post_systemd_message + unit_name="$1" + no_mac + mkdir -p "$systemd_dir" + dest="$systemd_dir/$unit_name" + cp "$script_dir/systemd/$unit_name" "$dest" + post_systemd_message } install_systemd_sshnpd() { - root_only - install_single_binary "sshnpd" - install_single_binary "srv" - install_systemd_unit "sshnpd.service" + root_only + install_single_binary "sshnpd" + install_single_binary "srv" + install_systemd_unit "sshnpd.service" } install_systemd_srvd() { - root_only - install_single_binary "srvd" - install_systemd_unit "srvd.service" + root_only + install_single_binary "srvd" + install_systemd_unit "srvd.service" } systemd() { - if is_darwin; then - echo "Unknown command: systemd"; - usage; - exit 1; - fi - case "$1" in - --help) usage; exit 0;; - sshnpd) install_systemd_sshnpd;; - srvd) install_systemd_srvd;; - *) - echo "Unknown systemd unit: $1"; - usage; - exit 1; - esac - setup_authorized_keys + if is_darwin; then + echo "Unknown command: systemd" + usage + exit 1 + fi + case "$1" in + --help) + usage + exit 0 + ;; + sshnpd) install_systemd_sshnpd ;; + srvd) install_systemd_srvd ;; + *) + echo "Unknown systemd unit: $1" + usage + exit 1 + ;; + esac + setup_authorized_keys +} + +# LAUNCHD SERVICES # +post_launchd_message() { + echo "Launchd unit installed, make sure to configure the unit by editing $dest" + echo "" + echo "To enable the service see \"Login Items\" in settings" +} + +install_launchd_unit() { + unit_name="$1" + mac_only + mkdir -p "$launchd_dir" + dest="$launchd_dir/$unit_name" + cp "$script_dir/launchd_dir/$unit_name" "$dest" + post_launchd_message +} + +install_launchd_sshnpd() { + install_single_binary "sshnpd" + install_single_binary "srv" + install_launchd_unit "com.atsign.sshnpd.plist" +} + +launchd() { + if ! is_darwin; then + echo "Unknown command: launchd" + usage + exit 1 + fi + case "$1" in + --help) + usage + exit 0 + ;; + sshnpd) install_launchd_sshnpd ;; + *) + echo "Unknown launchd unit: $1" + usage + exit 1 + ;; + esac + setup_authorized_keys } # HEADLESS SERVICES # post_headless_message() { - echo "Headless job installed, make sure to configure the job by editing $dest" - echo "Learn more in $script_dir/headless/README.md" - echo "" - echo "The job will start upon next system boot" - echo "To start the job immediately:" - echo " $command &" - echo "" - echo "Warning: logs stored at $log_file and $err_file have unlimited potential size" - echo "In a production environment, it's recommended that you install via systemd" - echo "or use a cron job to call logrotate on these log files if systemd is not available" + echo "Headless job installed, make sure to configure the job by editing $dest" + echo "Learn more in $script_dir/headless/README.md" + echo "" + echo "The job will start upon next system boot" + echo "To start the job immediately:" + echo " $command &" + echo "" + echo "Warning: logs stored at $log_file and $err_file have unlimited potential size" + echo "In a production environment, it's recommended that you install via systemd" + echo "or use a cron job to call logrotate on these log files if systemd is not available" } install_headless_job() { - job_name=$1 - mkdir -p "$user_bin_dir" - mkdir -p "$user_log_dir" - - dest="$user_bin_dir/$job_name.sh" - if ! [ -f "$dest" ]; then - if is_root; then - cp "$script_dir/headless/root_$job_name.sh" "$dest" - else - cp "$script_dir/headless/$job_name.sh" "$dest" - fi - fi - - log_file="$user_sshnpd_dir/logs/$job_name.log" - err_file="$user_sshnpd_dir/logs/$job_name.err" - - command="nohup $dest > $log_file 2> $err_file" - cron_entry="@reboot $command" - crontab_contents=$(crontab -l 2>/dev/null) - - if echo "$crontab_contents" | grep -Fxq "$cron_entry"; then - echo "=> cron job already installed, killing any old $job_name.sh processes" - pids=$(pgrep "$command") - if [ -n "$pids" ]; then - echo "$pids" | xargs kill - fi - else - echo "=> Installing cron job: $cron_entry" - (echo "$crontab_contents"; echo "$cron_entry") | crontab -; - fi - - echo "" - post_headless_message + job_name=$1 + mkdir -p "$user_bin_dir" + mkdir -p "$user_log_dir" + + dest="$user_bin_dir/$job_name.sh" + if ! [ -f "$dest" ]; then + cp "$script_dir/headless/$job_name.sh" "$dest" + if is_root; then + sedi "s|^binary_path=\".*\"$|binary_path=\"$bin_dir\"|g" "$dest" + fi + fi + + log_file="$user_sshnpd_dir/logs/$job_name.log" + err_file="$user_sshnpd_dir/logs/$job_name.err" + + command="nohup $dest > $log_file 2> $err_file" + cron_entry="@reboot $command" + crontab_contents=$(crontab -l 2>/dev/null) + + if echo "$crontab_contents" | grep -Fxq "$cron_entry"; then + echo "=> cron job already installed, killing any old $job_name.sh processes" + pids=$(pgrep "$command") + if [ -n "$pids" ]; then + echo "$pids" | xargs kill + fi + else + echo "=> Installing cron job: $cron_entry" + ( + echo "$crontab_contents" + echo "$cron_entry" + ) | crontab - + fi + + echo "" + post_headless_message } install_headless_sshnpd() { - install_single_binary "sshnpd" - install_single_binary "srv" - install_headless_job "sshnpd" + install_single_binary "sshnpd" + install_single_binary "srv" + install_headless_job "sshnpd" } install_headless_srvd() { - install_single_binary "srvd" - install_headless_job "srvd" + install_single_binary "srvd" + install_headless_job "srvd" } headless() { - case "$1" in - --help|'') usage; exit 0;; - sshnpd) install_headless_sshnpd;; - srvd) install_headless_srvd;; - *) - echo "Error: Unknown headless job: $1"; - usage; - exit 1; - esac - setup_authorized_keys + case "$1" in + --help | '') + usage + exit 0 + ;; + sshnpd) install_headless_sshnpd ;; + srvd) install_headless_srvd ;; + *) + echo "Error: Unknown headless job: $1" + usage + exit 1 + ;; + esac + setup_authorized_keys } # TMUX SESSION # post_tmux_message() { - # Explain what needs to be edited - # Provide initial startup command - echo "tmux service installed, make sure to configure the service by editing $dest" - echo "Learn more in $script_dir/headless/README.md" - echo "" - echo "To start the service immediately:" - echo " $command &" - echo "" - echo "Warning: sometimes tmux is not set to linger which will kill the tmux session on logout" - echo "Make sure your system is configured so that tmux sessions can linger, or disown the tmux process before logging out" + # Explain what needs to be edited + # Provide initial startup command + echo "tmux service installed, make sure to configure the service by editing $dest" + echo "Learn more in $script_dir/headless/README.md" + echo "" + echo "To start the service immediately:" + echo " $command &" + echo "" + echo "Warning: sometimes tmux is not set to linger which will kill the tmux session on logout" + echo "Make sure your system is configured so that tmux sessions can linger, or disown the tmux process before logging out" } install_tmux_service() { - service_name=$1 - mkdir -p "$user_bin_dir" - - dest="$user_bin_dir/$service_name.sh" - - # Only copy the "$service_name".sh script if it's not already there - if ! [ -f "$dest" ]; then - if is_root; then - cp "$script_dir/headless/root_$service_name.sh" "$dest" - else - cp "$script_dir/headless/$service_name.sh" "$dest" - fi - fi - - command="tmux new-session -d -s $service_name && tmux send-keys -t $service_name $dest C-m" - cron_entry="@reboot $command" - crontab_contents=$(crontab -l 2>/dev/null) - - if echo "$crontab_contents" | grep -Fxq "$cron_entry"; then - echo "=> Cron job already installed, will not re-install" - else - echo "=> Installing cron job: $cron_entry" - (echo "$crontab_contents"; echo "$cron_entry") | crontab -; - fi - - if (command tmux has-session -t "$service_name" 2> /dev/null); then - echo "=> Found existing tmux session for $service_name - will kill and restart it" - command tmux kill-session -t "$service_name" - command tmux new-session -d -s "$service_name" && command tmux send-keys -t "$service_name" "$dest" C-m - fi - - echo "" - post_tmux_message + service_name=$1 + mkdir -p "$user_bin_dir" + + dest="$user_bin_dir/$service_name.sh" + + # Only copy the "$service_name".sh script if it's not already there + if ! [ -f "$dest" ]; then + cp "$script_dir/headless/$service_name.sh" "$dest" + if is_root; then + sedi "s|^binary_path=\".*\"$|binary_path=\"$bin_dir\"|g" "$dest" + fi + fi + + command="tmux new-session -d -s $service_name && tmux send-keys -t $service_name $dest C-m" + cron_entry="@reboot $command" + crontab_contents=$(crontab -l 2>/dev/null) + + if echo "$crontab_contents" | grep -Fxq "$cron_entry"; then + echo "=> Cron job already installed, will not re-install" + else + echo "=> Installing cron job: $cron_entry" + ( + echo "$crontab_contents" + echo "$cron_entry" + ) | crontab - + fi + + if (command tmux has-session -t "$service_name" 2>/dev/null); then + echo "=> Found existing tmux session for $service_name - will kill and restart it" + command tmux kill-session -t "$service_name" + command tmux new-session -d -s "$service_name" && command tmux send-keys -t "$service_name" "$dest" C-m + fi + + echo "" + post_tmux_message } install_tmux_sshnpd() { - install_single_binary "sshnpd" - install_single_binary "srv" - install_tmux_service "sshnpd" + install_single_binary "sshnpd" + install_single_binary "srv" + install_tmux_service "sshnpd" } install_tmux_srvd() { - install_single_binary "srvd" - install_tmux_service "srvd" + install_single_binary "srvd" + install_tmux_service "srvd" } tmux() { - case "$1" in - --help|'') usage; exit 0;; - sshnpd) install_tmux_sshnpd;; - srvd) install_tmux_srvd;; - *) - echo "Unknown tmux service: $1"; - usage; - exit 1; - esac - setup_authorized_keys + case "$1" in + --help | '') + usage + exit 0 + ;; + sshnpd) install_tmux_sshnpd ;; + srvd) install_tmux_srvd ;; + *) + echo "Unknown tmux service: $1" + usage + exit 1 + ;; + esac + setup_authorized_keys } # MAIN # main() { - arg_zero=$0 - - define_env - - case "$1" in - --help|'') usage; exit 0;; - at_activate|sshnp|sshnpd|srv|srvd) install_single_binary "$1";; - binaries) install_base_binaries;; - debug_srvd) install_debug_srvd;; - debug) install_debug_binaries;; - all) install_all_binaries;; - systemd|headless|tmux) - command=$1; - shift 1; - $command "$@"; - ;; - *) - echo "Unknown command: $1"; - usage; - exit 1; - esac -} - -main "$@" \ No newline at end of file + arg_zero=$0 + + define_env + + case "$1" in + --help | '') + usage + exit 0 + ;; + -b) + binary_dir="$1" + shift + ;; + -u) + user="$1" + user_home=$(sudo -u "$user" sh -c 'echo $HOME') + shift + ;; + at_activate | npt | sshnp | sshnpd | srv | srvd) install_single_binary "$1" ;; + binaries) install_base_binaries ;; + debug_srvd) install_debug_binary "${1#"debug_"}" ;; # strips debug_ prefix from the command input + debug) install_debug_binaries ;; + all) install_all_binaries ;; + systemd | launchd | headless | tmux) + command=$1 + shift 1 + $command "$@" + ;; + *) + echo "Unknown command: $1" + usage + exit 1 + ;; + esac +} + +main "$@" diff --git a/packages/dart/sshnoports/bundles/shell/launchd/com.atsign.sshnpd.plist b/packages/dart/sshnoports/bundles/shell/launchd/com.atsign.sshnpd.plist new file mode 100644 index 000000000..69fef0043 --- /dev/null +++ b/packages/dart/sshnoports/bundles/shell/launchd/com.atsign.sshnpd.plist @@ -0,0 +1,21 @@ + + + + + Label + com.atsign.sshnpd + ProgramArguments + + /Users/[user]/.local/bin/sshnpd + -a + [device_atsign] + -m + [client_atsign] + -d + [device_name] + -su + + RunAtLoad + + + diff --git a/packages/dart/sshnoports/bundles/shell/magic/sshnp.sh b/packages/dart/sshnoports/bundles/shell/magic/sshnp.sh new file mode 100755 index 000000000..2f9ad09e6 --- /dev/null +++ b/packages/dart/sshnoports/bundles/shell/magic/sshnp.sh @@ -0,0 +1,21 @@ +#!/bin/bash +# SCRIPT METADATA +binary_path="$HOME/.local/bin" +client_atsign="" +device_atsign="" +host_atsign="" +devices=() +additional_args=() +# END METADATA + +unset d +select d in "${devices[@]}"; do + break +done + +if [ -z "$d" ]; then + echo "No device selected" + exit 1 +fi + +"$binary_path"/sshnp -f "$client_atsign" -t "$device_atsign" -h "$host_atsign" -d "$d" "${additional_args[@]}" "$@" diff --git a/packages/dart/sshnoports/bundles/shell/systemd/sshnpd.service b/packages/dart/sshnoports/bundles/shell/systemd/sshnpd.service index c09f404b5..bed7ee19f 100644 --- a/packages/dart/sshnoports/bundles/shell/systemd/sshnpd.service +++ b/packages/dart/sshnoports/bundles/shell/systemd/sshnpd.service @@ -34,13 +34,13 @@ Environment=device_atsign="@example_device" # Device name Environment=device_name="default" -# Uncomment if you wish the daemon to update authorized_keys to include public keys sent by authorized manager atSigns -; Environment=s="-s" +# Comment if you don't want the daemon to update authorized_keys to include public keys sent by authorized manager atSigns +Environment=s="-s" -# Uncomment if you wish to have the daemon make various information visible to the manager atsign - e.g. username, version, etc - without the manager atSign needing to know this daemon's device name -; Environment=u="-u" +# Comment if you don't want the daemon to share various information with the manager atsign - e.g. username, version, etc - without the manager atSign needing to know this daemon's device name +Environment=u="-u" -# Uncomment to enable verbose logging +# Comment to disable verbose logging Environment=v="-v" # The line below runs the sshnpd service, with the options set above. diff --git a/packages/dart/sshnoports/bundles/universal.sh b/packages/dart/sshnoports/bundles/universal.sh new file mode 100755 index 000000000..aab22dfc4 --- /dev/null +++ b/packages/dart/sshnoports/bundles/universal.sh @@ -0,0 +1,469 @@ +#!/bin/sh + +# SCRIPT METADATA +# DO NOT MODIFY/DELETE THIS BLOCK +script_version="3.0.0" +sshnp_version="5.1.0" +repo_url="https://github.com/atsign-foundation/sshnoports" +# END METADATA + +# N.B. Other than the variable definitions, and the call to the main function, +# nothing else should be writen outside the main function to avoid side effects + +### Environment based variables +arg_zero="$0" +unset script_dir +unset platform_name +unset system_arch +unset archive_ext +unset time_stamp +unset archive_path +unset as_root +unset bin_path +unset user + +### Input Variables +verbose=false +unset tmp_path +install_type="" +unset download_url +local_archive="" + +### Client/ Device Install Variables +client_atsign="" +device_atsign="" + +### Client Install Variables +unset magic_script +unset host_atsign +unset devices + +### Device Install Variables +device_name="" + +norm_atsign() { + # Prepend an @ to the front of the atsign if missing + atsign="@$(echo "$1" | sed -e 's/"//g' -e 's/^@//g')" + echo "$atsign" +} + +norm_version() { + # Ensure the version is in the format "tags/vX.Y.Z" (github version tag) + version="tags/v$(echo "$1" | sed -e 's/"//g' -e 's/^tags\///g' -e 's/^v//g')" + echo "$version" +} + +is_root() { + [ "$(id -u)" -eq 0 ] +} + +is_darwin() { + [ "$(uname)" = 'Darwin' ] +} + +sedi() { + if is_darwin; then + sed -i '' "$@" + else + sed -i "$@" + fi +} + +version() { + echo "Version: $script_version (Target: $sshnp_version)" +} + +usage() { + if [ -z "$arg_zero" ]; then + arg_zero='install.sh' + fi + version + echo "Usage: $arg_zero [options]" + echo " -h, --help Display help" + echo " -v, --verbose Verbose tracing" + echo " --version Display version" + echo " --temp-path Set the temporary path for downloads" + echo " -t, --type Set the install type (device, client, both)" + echo " --local Install from a local archive" +} + +parse_env() { + case "$(uname)" in + Darwin) platform_name='macos' ;; + Linux) platform_name='linux' ;; + *) + echo "Detected an unsupported platform: $(uname)" + echo "Please open an issue at: $repo_url" + echo "and provide the following information: $(uname -a)" exit 1 + ;; + esac + + case "$platform_name" in + macos) archive_ext="zip" ;; + linux) archive_ext="tgz" ;; + esac + + case "$(uname -m)" in + x86_64 | amd64 | x64) + system_arch="x64" + ;; + arm64 | aarch64) + system_arch="arm64" + ;; + arm | armv7l) + system_arch="armv7" + ;; + riscv64) + system_arch="riscv64" + ;; + *) + echo "Detected an unsupported architecture: $(uname -m)" + echo "Please open an issue at: $repo_url" + echo "and provide the following information: $(uname -a)" + exit 1 + ;; + esac + + time_stamp=$(date +%s) + + tmp_path="/tmp" + extract_path="$tmp_path/sshnp-$time_stamp" + archive_path="$extract_path.$archive_ext" + + if is_root; then + as_root=true + bin_path="/usr/local/bin" + user=$(logname) + else + as_root=false + bin_path="$HOME/.local/bin" + user="$USER" + fi +} + +is_valid_source_mode() { + [ "$1" = "download" ] || [ "$1" = "local" ] || [ "$1" = "build" ] +} + +is_valid_install_type() { + [ "$1" = "device" ] || [ "$1" = "client" ] || [ "$1" = "both" ] +} + +norm_install_type() { + case "$1" in + d*) + echo "device" + ;; + c*) + echo "client" + ;; + *) + echo "" + ;; + esac +} + +parse_args() { + while [ $# -gt 0 ]; do + case "$1" in + -h | --help) + usage + exit 0 + ;; + -v | --verbose) + verbose=true + set -x + ;; + --version) + version + exit 0 + ;; + --temp-path) + shift + mkdir -p "$1" + tmp_path="$1" + ;; + -t | --type) + shift + if is_valid_install_type "$1"; then + install_type="$1" + else + echo "Invalid install type: $1" + echo "Valid options are: device, client" exit 1 + fi + ;; + --local) + shift + if [ -f "$1" ]; then + local_archive="$1" + else + echo "Local archive not found: $1" + exit 1 + fi + ;; + *) + echo "Unexpected option: $1" + exit 1 + ;; + esac + shift + done +} + +get_user_inputs() { + if [ -z "$install_type" ]; then + unset install_type_input + while [ -z "$install_type" ]; do + echo "Install type (device, client, both): " 1>&2 + read -r install_type_input + install_type=$(norm_install_type "$install_type_input") + done + fi +} + +print_env() { + echo "Environment:" + echo "Platform Name: $platform_name" + echo "System Arch: $system_arch" + echo "Temporary Path: $tmp_path" + echo "As Root: $as_root" + echo "Binary Path: $bin_path" + echo "User: $user" +} + +get_download_url() { + unset download_urls + download_urls=$( + curl -fsSL "https://api.github.com/repos/atsign-foundation/noports/releases/$(norm_version $sshnp_version)" | + grep browser_download_url | + cut -d\" -f4 + ) + + if [ -z "$download_urls" ]; then + echo "Failed to get download url for sshnoports" + exit 1 + fi + + echo "$download_urls" | + grep "$platform_name" | grep "$system_arch" | + cut -d\" -f4 +} + +download_archive() { + read -r download_url + echo "Downloading archive from $download_url" + curl -sL "$download_url" -o "$archive_path" + if [ ! -f "$archive_path" ]; then + echo "Failed to download archive" + exit 1 + fi +} + +unpack_archive() { + case "$archive_ext" in + zip) + unzip -qo "$archive_path" -d "$extract_path" + ;; + tgz | tar.gz) + tar -zxf "$archive_path" -C "$extract_path" + ;; + esac +} + +cleanup() { + # These should be in the tmp directory, attempt to remove them anyway + rm -f "$archive_path" + rm -rf "$extract_path" +} + +write_metadata() { + start_line="# SCRIPT METADATA" + end_line="# END METADATA" + file=$1 + variable=$2 + value=$3 + sedi "/$start_line/,/$end_line/s|$variable=\".*\"|$variable=\"$value\"|g " "$file" +} + +write_metadata_array() { + # Takes a comma separated list and writes it into a bash array in the metadata of the script + start_line="# SCRIPT METADATA" + end_line="# END METADATA" + file=$1 + variable=$2 + value=$(echo "$3" | tr ',' ' ') + sedi "/$start_line/,/$end_line/s|$variable=(.*)|$variable=($value)|g" "$file" +} + +write_program_arguments_plist() { + # Takes a comma separated list and writes it into a string array in the plist document + start_line="ProgramArguments" + second_line="" + end_line="" + file=$1 + shift + string_array="" + while [ $# -gt 0 ]; do + string_array="$string_array\\ + $1" + shift + done + sedi "/ProgramArguments<\\/key>/,/<\\/array>/c\\ + $start_line\\ + $second_line$string_array\\ + $end_line" "$file" +} + +write_systemd_environment() { + file=$1 + variable=$2 + value=$3 + sedi "s|Environment=$variable=\".*\"|Environment=$variable=\"$value\"|g" "$file" +} + +get_client_device_atsigns() { + while [ -z "$client_atsign" ]; do + echo "Enter client atSign:" + read -r client_atsign + done + + while [ -z "$device_atsign" ]; do + echo "Enter device atSign:" + read -r device_atsign + done +} + +# CLIENT INSTALLATION # +client() { + magic_script="$bin_path"/@sshnp + # install the magic sshnp script + cp "$extract_path"/sshnp/magic/sshnp.sh "$magic_script" + chmod +x "$magic_script" + + get_client_device_atsigns + if [ -z "$host_atsign" ]; then + echo Pick your default region: + echo " am : Americas" + echo " ap : Asia Pacific" + echo " eu : Europe" + echo " @___ : Specify a custom region atSign" + echo "> " + read -r host_atsign + fi + + while ! echo "$host_atsign" | grep -Eq "@.*"; do + case "$host_atsign" in + [Aa][Mm]*) + host_atsign="@rv_am" + ;; + [Ee][Uu]*) + host_atsign="@rv_eu" + ;; + [Aa][Pp]*) + host_atsign="@rv_ap" + ;; + @*) + # Do nothing for custom region + ;; + *) + echo "Invalid region: $host_atsign" + echo "region: " + read -r host_atsign + ;; + esac + done + + done_input=false + + echo "Enter the device names you would like to include in the magic script" + echo "/done to finish" + while [ "$done_input" = false ]; do + echo "device: " + read -r device_name + if [ "$device_name" = "/done" ]; then + done_input=true + else + devices="$devices,$device_name" + fi + done + + write_metadata "$magic_script" "client_atsign" "$(norm_atsign "$client_atsign")" + write_metadata "$magic_script" "device_atsign" "$(norm_atsign "$device_atsign")" + write_metadata "$magic_script" "host_atsign" "$(norm_atsign "$host_atsign")" + write_metadata_array "$magic_script" "devices" "$devices" +} + +# DEVICE INSTALLATION # +device() { + unset device_install_type + if is_darwin; then + device_install_type="launchd" + elif is_root; then + device_install_type="systemd" + elif command -v tmux >/dev/null 2>&1; then + device_install_type="tmux" + else + device_install_type="headless" + fi + + get_client_device_atsigns + + while [ -z "$device_name" ]; do + echo "Enter device name:" + read -r device_name + done + + install_output=$("$extract_path"/sshnp/install.sh -b "$bin_path" -u "$user" "$device_install_type" sshnpd) + case "$device_install_type" in + launchd) + launchd_plist="$HOME/Library/LaunchAgents/com.atsign.sshnpd.plist" + write_program_arguments_plist "$launchd_plist" "$bin_path/sshnpd" "-f" "$(norm_atsign "$client_atsign")" "-t" "$(norm_atsign "$device_atsign")" "-d" "$device_name" "-su" + launchctl load "$launchd_plist" + launchctl start "$launchd_plist" + ;; + systemd) + systemd_service="/etc/systemd/system/sshnpd.service" + write_systemd_environment "$systemd_service" "manager_atsign" "$(norm_atsign "$client_atsign")" + write_systemd_environment "$systemd_service" "device_atsign" "$(norm_atsign "$device_atsign")" + write_systemd_environment "$systemd_service" "device_name" "$device_name" + systemctl enable sshnpd + systemctl start sshnpd + ;; + tmux | headless) + shell_script="$bin_path"/sshnpd.sh + metadata_write "$shell_script" "manager_atsign" "$(norm_atsign "$client_atsign")" + metadata_write "$shell_script" "device_atsign" "$(norm_atsign "$device_atsign")" + metadata_write "$shell_script" "device_name" "$device_name" + # split install output by lines, then grab the output after the line that says "To start immediately" + eval "$(echo "$install_output" | grep -A1 "To start .* immediately:" | tail -n1)" + ;; + esac +} + +main() { + trap cleanup EXIT + set -eu + parse_env + parse_args "$@" + + if [ $verbose = true ]; then print_env; fi + + if [ -n "$local_archive" ]; then + echo "Using local archive: $local_archive" + cp "$local_archive" "$archive_path" + else + download_url=$(get_download_url) + echo "Downloading archive from $download_url" + echo "$download_url" | download_archive + fi + + unpack_archive + + get_user_inputs + case "$install_type" in + client) client ;; + device) device ;; + esac +} + +main "$@" diff --git a/packages/dart/sshnoports/lib/src/version.dart b/packages/dart/sshnoports/lib/src/version.dart index d603e0da8..4cf79fd0e 100644 --- a/packages/dart/sshnoports/lib/src/version.dart +++ b/packages/dart/sshnoports/lib/src/version.dart @@ -1,2 +1,2 @@ // Generated code. Do not modify. -const packageVersion = '5.1.0'; +const packageVersion = '5.1.0-rc.5'; diff --git a/packages/dart/sshnoports/pubspec.yaml b/packages/dart/sshnoports/pubspec.yaml index e34c93a8f..37b69874a 100644 --- a/packages/dart/sshnoports/pubspec.yaml +++ b/packages/dart/sshnoports/pubspec.yaml @@ -1,7 +1,7 @@ name: sshnoports publish_to: none -version: 5.1.0 +version: 5.1.0-rc.5 environment: sdk: ">=3.0.0 <4.0.0"