'paramiko ssh source ~/.bashrc

I need to automate a some start up processes on a remote machine running ROS. To do this, i'm trying to use paramiko to log into the remote machine via ssh and launch the launch file.

The issue that i'm having is that my ~/.bashrc file is not sourced.

I can source source /opt/ros/noetic/setup.bash and get roscore to work, but i can't then find any of my launch files as my work space is not sourced:

command = 'source /opt/ros/noetic/setup.bash && roscore'

my bashrc file contains both source /opt/ros/noetic/setup.bash and source source /home/ben/catkin_ws/devel/setup.bash but whenever i source this file as before, i can't even get roscore to work -

command = 'source ~/.bashrc && roscore'

Connected to 192.168.XX.XX
bash: roscore: command not found

A minimal working example -

#! /usr/bin/env python3
import paramiko
import numpy as np
import os

class Paramiko():

    def __init__(self, hostname, username, password, port):
        self.hostname = hostname
        self.username = username
        self.password = password
        self.port = port
        paramiko.util.log_to_file("paramiko.log")

    
    
    def ExecuteCommand(self, command):
        try:
            ssh = paramiko.SSHClient()
            ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
            ssh.connect(self.hostname, username = self.username, password = self.password)
            print("Connected to %s" % self.hostname)
        except paramiko.AuthenticationException:
            print("Failed to connect to %s due to wrong username/password" %self.hostname)
            exit(1)
        except Exception as e:
            print(e.message)    
            exit(2)

        try:
            stdin, stdout, stderr = ssh.exec_command(command)
        except Exception as e:
            print(e.message)

        err = ''.join(stderr.readlines())
        out = ''.join(stdout.readlines())
        final_output = str(out)+str(err)
        print(final_output)
    
        return final_output

def main():
    hostname = "192.168.XX.XX"
    username = "ben"
    password = "lol_nice_try"
    port = 22
    command = 'source ~/.bashrc && roslaunch some_package some_launchfile.launch'


    para = Paramiko(hostname, username, password, port)
    answer = para.ExecuteCommand(command)


if __name__ == "__main__":
    main()

I'm considering using a bash script to do it, or maybe using os.system to do it, but that would be a fresh start and have it's own problems.

Open to ideas. In doing some reading i'm lead to believe that the paramiko ssh isn't actually a login session?

I've tried setting try get_pty=true when calling exec_command as per Problems with python interpertor after ssh with paramiko into a remote machine but that doesn't do anything. I'm not even sure what that option actually does, as the paramiko documentation doesn't appear to have anything about it.

Another comment on that thread says something about having a dedicated profile, but isn't that what ~/.bashrc is?



Solution 1:[1]

Not an answer to the original question, but a work around is to use pexpect and pxssh -

from pexpect import pxssh
try:
    s = pxssh.pxssh()
    hostname = '192.168.XX.XX'
    username = 'ben'
    password = 'cmon_guy'
    s.login(hostname, username, password)
    s.sendline('roslaunch system_diagnostics system_diagnostics_example_node.launch') # run a command
    s.prompt()             # match the prompt
    print(s.after)        # print everything before the prompt. 
    s.logout()
except pxssh.ExceptionPxssh as e:
    print("pxssh failed on login.")
    print(e)

Not quite sure how to get it to output the ROS_INFO as it's being sent to the terminal, without closing the connection, but that's a question for another day at this point.

Solution 2:[2]

Run the command after launching an interactive session.

Modify the command like below:

command = 'bash --login -c "roscore"'

and then execute using paramiko.

From man bash

When bash is invoked as an interactive login shell, or as a non-interactive shell with the --login option, it first reads and executes commands from the file /etc/profile, if that file exists. After reading that file, it looks for ~/.bash_profile, ~/.bash_login, and ~/.profile, in that order, and reads and executes commands from the first one that exists and is readable. The --noprofile option may be used when the shell is started to inhibit this behavior.

Solution 3:[3]

you could alternatively use systemd and create services for roscore and every launch file you want to start and stop and then you can execute sudo systemctl start roscore.service to start your roscore you could do the same for other launch files this is an ros service template you could follow :

`
[Unit]
 Description=navigation
 After=NetworkManager.service time-sync.target
 [Service]
 Type=forking
 User=youruser 
 ExecStart=/bin/bash -c "bashfile_location/bashscript.bash & while ! echo exit  >       /dev/null; do sleep 1; done"
 Restart=on-failure
 [Install]
 WantedBy=multi-user.target`

and you should create a bash file to start roscore or really any other ros file node or launch

#!/bin/bash
source /opt/ros/noetic/setup.bash
source /home/username/catkin_ws/devel/setup.bash
source ~/.bashrc
roslaunch package launchfile

and don't forget chmod +x

sorry if you find my answer disorganized

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Ben Bird
Solution 2 Aakhil Shaik
Solution 3