├── LICENSE ├── vignettes ├── cover │ ├── remoter.pdf │ ├── remoter.png │ ├── remote_machines.pdf │ ├── build_hex.r │ ├── build_cover.sh │ ├── remoter.tex │ └── remote_machines.tex ├── pics │ ├── remoter.png │ └── remoter_relay.png ├── include │ ├── uch_small.png │ ├── 00-acknowledgement.tex │ ├── remoter.bib │ └── settings.tex ├── build_pdf.sh ├── remote_machines.Rnw └── remoter.Rnw ├── .gitignore ├── .Rbuildignore ├── man ├── showlog.Rd ├── has.sodium.Rd ├── is.secure.Rd ├── recvfile.Rd ├── sendfile.Rd ├── evalc.Rd ├── relay.Rd ├── rmc.Rd ├── lsc.Rd ├── remoter-package.Rd ├── c2s.Rd ├── exit.Rd ├── s2c.Rd ├── rhelp.Rd ├── client.Rd ├── batch.Rd ├── rDevices.Rd ├── server.Rd └── rDevices_rpng.Rd ├── R ├── zzz.r ├── getips.r ├── wcc_addhistory.r ├── 00_globalVariables.r ├── crypto.r ├── remoter-package.r ├── recvfile.r ├── sendfile.r ├── printing.r ├── logging.r ├── relay.r ├── exit.r ├── c2s.r ├── on_client_utils.r ├── utils.r ├── reactor.r ├── s2c.r ├── state.r ├── wcc_client_device.r ├── checks.r ├── comms.r ├── wcc_rpng.r ├── batch.r ├── wcc_rpng_utils.r ├── wcc_rhelp.r ├── client.r └── server.r ├── tests └── batch.R ├── .travis.yml ├── appveyor.yml ├── inst ├── CITATION └── LICENSE.md ├── LICENSE.md ├── DESCRIPTION ├── NAMESPACE ├── README.md └── ChangeLog /LICENSE: -------------------------------------------------------------------------------- 1 | YEAR: 2014-2021 2 | COPYRIGHT HOLDER: Drew Schmidt and Wei-Chen Chen 3 | -------------------------------------------------------------------------------- /vignettes/cover/remoter.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RBigData/remoter/HEAD/vignettes/cover/remoter.pdf -------------------------------------------------------------------------------- /vignettes/cover/remoter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RBigData/remoter/HEAD/vignettes/cover/remoter.png -------------------------------------------------------------------------------- /vignettes/pics/remoter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RBigData/remoter/HEAD/vignettes/pics/remoter.png -------------------------------------------------------------------------------- /vignettes/include/uch_small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RBigData/remoter/HEAD/vignettes/include/uch_small.png -------------------------------------------------------------------------------- /vignettes/pics/remoter_relay.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RBigData/remoter/HEAD/vignettes/pics/remoter_relay.png -------------------------------------------------------------------------------- /vignettes/cover/remote_machines.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RBigData/remoter/HEAD/vignettes/cover/remote_machines.pdf -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | *.html 3 | *.swp 4 | 5 | src/*.o 6 | src/*.so 7 | 8 | inst/doc/* 9 | 10 | *.aux 11 | *.bbl 12 | *.blg 13 | *.out 14 | *.toc 15 | *.backup 16 | 17 | TODO 18 | -------------------------------------------------------------------------------- /.Rbuildignore: -------------------------------------------------------------------------------- 1 | ^\.travis\.yml$ 2 | ^appveyor.yml$ 3 | TODO 4 | cover/build_cover.sh 5 | cover/remote_machines.tex 6 | cover/remoter.tex 7 | cover/remoter.png 8 | 9 | LICENSE.md 10 | -------------------------------------------------------------------------------- /man/showlog.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/logging.r 3 | \name{showlog} 4 | \alias{showlog} 5 | \title{showlog} 6 | \usage{ 7 | showlog() 8 | } 9 | \description{ 10 | Show the server log on the client. 11 | } 12 | -------------------------------------------------------------------------------- /vignettes/cover/build_hex.r: -------------------------------------------------------------------------------- 1 | hexSticker::sticker("remoter.png", package="remoter", 2 | p_size = 7, p_y = 1.6, s_x = 1, s_y = 0.85, s_width = 0.65, 3 | h_fill = "#448cff", h_color = "#2d3542", 4 | filename = "remoter-hex.pdf") 5 | 6 | system("convert -density 90 -trim remoter-hex.pdf -quality 100 -flatten -sharpen 0x1.0 remoter-hex.png") 7 | -------------------------------------------------------------------------------- /R/zzz.r: -------------------------------------------------------------------------------- 1 | .onLoad <- function(libname, pkgname) 2 | { 3 | ### Preload to global environment. 4 | invisible(eval(parse(text = "remoter:::init_state()"))) 5 | 6 | ### Load and set sodium then generate public/private keys. 7 | test <- requireNamespace("sodium", quietly=TRUE) 8 | set(withsodium, test) 9 | if (test) 10 | generate_keypair() 11 | 12 | invisible() 13 | } 14 | 15 | -------------------------------------------------------------------------------- /man/has.sodium.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/crypto.r 3 | \name{has.sodium} 4 | \alias{has.sodium} 5 | \title{has.sodium} 6 | \usage{ 7 | has.sodium() 8 | } 9 | \value{ 10 | Returns \code{TRUE} if the sodium package is available, and 11 | \code{FALSE} otherwise. 12 | } 13 | \description{ 14 | Report if the sodium package is availabe for use. 15 | } 16 | -------------------------------------------------------------------------------- /R/getips.r: -------------------------------------------------------------------------------- 1 | get_ips = function() 2 | { 3 | ip_in = tryCatch(getip::getip("internal"), error=identity) 4 | if (inherits(tryCatch(ip_in, error=identity), "error")) 5 | ip_in = "ERROR: couldn't determine internal IP" 6 | 7 | ip_ex = tryCatch(getip::getip("external"), error=identity) 8 | if (inherits(tryCatch(ip_ex, error=identity), "error")) 9 | ip_ex = "ERROR: couldn't determine external IP" 10 | 11 | return(list(ip_in=ip_in, ip_ex=ip_ex)) 12 | } 13 | -------------------------------------------------------------------------------- /man/is.secure.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/crypto.r 3 | \name{is.secure} 4 | \alias{is.secure} 5 | \title{is.secure} 6 | \usage{ 7 | is.secure() 8 | } 9 | \value{ 10 | Returns \code{TRUE} if messages between client and server are 11 | currently encrypted, and \code{FALSE} if not. If the client 12 | is not currently running (i.e., if executed from just a regular 13 | R prompt), then \code{NA} is returned. 14 | } 15 | \description{ 16 | Report if communications with the connected server are 17 | encrypted. 18 | } 19 | -------------------------------------------------------------------------------- /man/recvfile.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/recvfile.r 3 | \name{recvfile} 4 | \alias{recvfile} 5 | \title{recvfile} 6 | \usage{ 7 | recvfile(file_send, file_recv, verbose = TRUE) 8 | } 9 | \arguments{ 10 | \item{file_recv, file_send}{The file paths (as strings) for the input/sent file and the output/received 11 | file.} 12 | 13 | \item{verbose}{Should file transfer information be printed?} 14 | } 15 | \value{ 16 | Returns \code{TRUE} invisibly on successful exit. 17 | } 18 | \description{ 19 | Transfer file from server to client. 20 | } 21 | -------------------------------------------------------------------------------- /man/sendfile.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/sendfile.r 3 | \name{sendfile} 4 | \alias{sendfile} 5 | \title{sendfile} 6 | \usage{ 7 | sendfile(file_send, file_recv, verbose = TRUE) 8 | } 9 | \arguments{ 10 | \item{file_send, file_recv}{The file paths (as strings) for the input/sent file and the output/received 11 | file.} 12 | 13 | \item{verbose}{Should file transfer information be printed?} 14 | } 15 | \value{ 16 | Returns \code{TRUE} invisibly on successful exit. 17 | } 18 | \description{ 19 | Transfer file from client to server. 20 | } 21 | -------------------------------------------------------------------------------- /vignettes/cover/build_cover.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | PKGVER=`grep "Version:" ../../DESCRIPTION | sed -e "s/Version: //"` 4 | sed -i -e "s/thispackageversion}{.*}/thispackageversion}{${PKGVER}}/" cover.tex 5 | 6 | buildCover(){ 7 | xelatex $1 8 | xelatex $1 9 | ### NOTE compactPDF strips out the logo for some reason ??? 10 | #Rscript -e "tools::compactPDF('.', gs_quality='ebook')" 11 | } 12 | 13 | cleanCover(){ 14 | rm -rf *.dvi *.aux *.bbl *.blg *.log *.out *.toc *.idx *.lof *.lot *.ind *.ilg 15 | } 16 | 17 | rm -rf *.pdf 18 | cleanCover 19 | buildCover remoter.tex 20 | buildCover remote_machines.tex 21 | cleanCover 22 | 23 | -------------------------------------------------------------------------------- /tests/batch.R: -------------------------------------------------------------------------------- 1 | if (.Platform$OS.type != "windows") { 2 | system("${R_HOME}/bin${R_ARCH_BIN}/Rscript -e \"remoter::server()\"", wait=FALSE) 3 | } else { 4 | R.HOME <- Sys.getenv("R_HOME") 5 | R.ARCH.BIN <- Sys.getenv("R_ARCH_BIN") 6 | cmd <- paste(R.HOME, "/bin", R.ARCH.BIN, "/Rscript -e \"remoter::server()\"", 7 | sep = "") 8 | system(cmd, wait=FALSE) 9 | } 10 | 11 | script <- " 12 | 1+1 13 | 14 | 1+ 15 | 2 16 | shutdown() 17 | " 18 | 19 | if (file.access("./remoterserverlog")) { 20 | test <- capture.output(remoter::batch(script = script)) 21 | truth <- c("", "[1] 2 ", "[1] 3 ") 22 | stopifnot(all.equal(truth, test)) 23 | file.remove("./remoterserverlog") 24 | } 25 | 26 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: R 2 | sudo: required 3 | warnings_are_errors: true 4 | 5 | env: 6 | global: 7 | - CRAN: https://cran.rstudio.com 8 | - _R_CHECK_FORCE_SUGGESTS_=FALSE 9 | 10 | before_install: 11 | # - sudo add-apt-repository ppa:chris-lea/libsodium -y 12 | - sudo apt-get update -q 13 | - sudo apt-get install -qq libzmq3-dev 14 | - sudo apt-get install -qq libsodium-dev 15 | - Rscript -e "sessionInfo()" 16 | 17 | r_packages: 18 | - assertthat 19 | # - pbdZMQ 20 | - sodium 21 | 22 | r_github_packages: 23 | - snoweye/pbdZMQ 24 | 25 | r_check_args: --no-build-vignettes --no-manual 26 | 27 | notifications: 28 | email: 29 | on_success: no 30 | on_failure: no 31 | 32 | -------------------------------------------------------------------------------- /man/evalc.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/on_client_utils.r 3 | \name{evalc} 4 | \alias{evalc} 5 | \title{evalc} 6 | \usage{ 7 | evalc(expr) 8 | } 9 | \arguments{ 10 | \item{expr}{Expression to be evaluated on the client.} 11 | } 12 | \value{ 13 | Returns \code{TRUE} invisibly on successful exit. 14 | } 15 | \description{ 16 | A function to evaluate expressions on the client's R session. To 17 | eval expressions on the server, just use \code{eval()}. Instead of 18 | using this function, you could also just kill the client, do your 19 | local operations, then re-run your \code{client()} command. 20 | } 21 | \details{ 22 | Evaluate expressions on the client. 23 | } 24 | -------------------------------------------------------------------------------- /R/wcc_addhistory.r: -------------------------------------------------------------------------------- 1 | ### Add to history() and avoid repeatedly appending suffix. 2 | addhistory <- function(read) 3 | { 4 | tmp <- as.character(read) 5 | suffix <- paste0(" # ", getval(prompt)) 6 | if (!grepl(x = tmp, pattern = paste0(suffix, "$"), perl = TRUE)) 7 | tmp <- paste0(tmp, suffix) 8 | 9 | if (isWindows() && isRStudio()) 10 | { 11 | ### This adds history to native R but is insufficient for Rtudio IDE. 12 | # .rs.registerReplaceHook("timestamp", "utils", function(original, ...) 13 | # { 14 | # invisible(.Call("rs_timestamp", "bb")) 15 | # }) 16 | # timestamp() 17 | } 18 | else 19 | utils::timestamp(stamp = tmp, prefix = "", suffix = "", quiet = TRUE) 20 | 21 | invisible() 22 | } 23 | -------------------------------------------------------------------------------- /R/00_globalVariables.r: -------------------------------------------------------------------------------- 1 | ### For R CMD check 2 | utils::globalVariables(c( 3 | "whoami", "remote_addr", "public", "private", "theirs", "serverlog", 4 | "checkversion", "withsodium", "logfile", "continuation", "lasterror", 5 | "visible", "should_exit", "kill_interactive_server", "prompt_active", 6 | "ret", "lasterror", "withrstudioapi", "secure", "verbose", "showmsg", 7 | "socket", "password", "maxattempts", "status", "context", "port", 8 | "shouldwarn", "num_warnings", "prompt", "client_lasterror", 9 | "sendport", "recvport", "timer", ".rDevices", 10 | "need_auto_rhelp_on", "need_auto_rpng_off", 11 | "isbatch", "ret_addition", 12 | "client_called_exit", "client_called_shutdown", 13 | "sync", "objs_nm", "objs", "remote_objs", "clientpw", 14 | "serialversion" 15 | )) 16 | -------------------------------------------------------------------------------- /man/relay.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/relay.r 3 | \name{relay} 4 | \alias{relay} 5 | \title{Relay Launcher} 6 | \usage{ 7 | relay(addr, recvport = 55556, sendport = 55555, verbose = FALSE) 8 | } 9 | \arguments{ 10 | \item{addr}{The address of the server.} 11 | 12 | \item{recvport}{The port for receiving commands from the client.} 13 | 14 | \item{sendport}{The port for sending commands to the server.} 15 | 16 | \item{verbose}{Show verbose messaging.} 17 | } 18 | \value{ 19 | Returns \code{TRUE} invisibly on successful exit. 20 | } 21 | \description{ 22 | Launcher for the remoter relay. 23 | } 24 | \details{ 25 | The relay is an intermediary or "middleman" between the client 26 | and server meant for machines with split login/compute nodes. 27 | } 28 | -------------------------------------------------------------------------------- /vignettes/build_pdf.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | fixVersion(){ 4 | PKGVER=`grep "Version:" ../DESCRIPTION | sed -e "s/Version: //"` 5 | sed -i -e "s/myversion{.*}/myversion{${PKGVER}}/" $1 6 | } 7 | 8 | buildCover(){ 9 | cd cover 10 | ./build_cover.sh 11 | cd .. 12 | } 13 | 14 | cleanVignette(){ 15 | rm -f *.aux *.bbl *.blg *.log *.out *.toc *.dvi *.backup 16 | } 17 | 18 | buildVignette(){ 19 | cleanVignette 20 | 21 | fixVersion $1 22 | pdflatex $1 23 | bibname=`echo "$1" | sed -e 's/\..*//'` 24 | bibtex $bibname 25 | pdflatex $1 26 | pdflatex $1 27 | Rscript -e "tools::compactPDF('$1', gs_quality='ebook')" 28 | } 29 | 30 | publish(){ 31 | mv -f *.pdf ../inst/doc/ 32 | cp -f *.Rnw ../inst/doc/ 33 | } 34 | 35 | #buildCover 36 | 37 | buildVignette remoter.Rnw 38 | buildVignette remote_machines.Rnw 39 | 40 | cleanVignette 41 | publish 42 | 43 | -------------------------------------------------------------------------------- /man/rmc.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/on_client_utils.r 3 | \name{rmc} 4 | \alias{rmc} 5 | \title{rmc} 6 | \usage{ 7 | rmc(..., list = character(), envir) 8 | } 9 | \arguments{ 10 | \item{...}{Objects to be removed from the client's R session.} 11 | 12 | \item{list}{Character vector naming objects to be removed (as in \code{rm()}).} 13 | 14 | \item{envir}{Environment (as in \code{rm()}).} 15 | } 16 | \value{ 17 | Returns \code{TRUE} invisibly on successful exit. 18 | } 19 | \description{ 20 | A function to remove objects from the client's R session. To 21 | remove objects on the server, just use \code{rm()}. Instead of 22 | using this function, you could also just kill the client, do your 23 | local operations, then re-run your \code{client()} command. 24 | } 25 | \details{ 26 | Remove objects on the client. 27 | } 28 | -------------------------------------------------------------------------------- /man/lsc.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/on_client_utils.r 3 | \name{lsc} 4 | \alias{lsc} 5 | \title{ls on Client} 6 | \usage{ 7 | lsc(envir, all.names = FALSE, pattern) 8 | } 9 | \arguments{ 10 | \item{envir}{Environment (as in \code{ls()}).} 11 | 12 | \item{all.names}{Logical that determines if all names are returned or those beginning 13 | with a '.' are omitted (as in \code{ls()}).} 14 | 15 | \item{pattern}{Optional regular expression (as in \code{ls()}).} 16 | } 17 | \value{ 18 | Returns \code{TRUE} invisibly on successful exit. 19 | } 20 | \description{ 21 | A function to view environments on the client's R session. To 22 | view objects on the server, just use \code{ls()}. Instead of 23 | using this function, you could also just kill the client, do your 24 | local operations, then re-run your \code{client()} command. 25 | } 26 | \details{ 27 | View objects on the client. 28 | } 29 | -------------------------------------------------------------------------------- /man/remoter-package.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/remoter-package.r 3 | \docType{package} 4 | \name{remoter-package} 5 | \alias{remoter-package} 6 | \title{remoter} 7 | \description{ 8 | A set of utilities for client/server computing with R, controlling 9 | a remote R session (the server) from a local one (the client). Simply set 10 | up a server (see package vignette for more details) and connect to it from 11 | your local R session ('RStudio', terminal, etc). The client/server 12 | framework is a custom 'REPL' and runs entirely in your R session without the 13 | need for installing a custom environment on your system. Network 14 | communication is handled by the 'ZeroMQ' library by way of the 'pbdZMQ' 15 | package. 16 | } 17 | \references{ 18 | Project URL: \url{https://github.com/RBigData/remoter} 19 | } 20 | \author{ 21 | Drew Schmidt and Wei-Chen Chen 22 | } 23 | \keyword{package} 24 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | # DO NOT CHANGE the "init" and "install" sections below 2 | 3 | # Download script file from GitHub 4 | init: 5 | ps: | 6 | $ErrorActionPreference = "Stop" 7 | Invoke-WebRequest http://raw.github.com/krlmlr/r-appveyor/master/scripts/appveyor-tool.ps1 -OutFile "..\appveyor-tool.ps1" 8 | Import-Module '..\appveyor-tool.ps1' 9 | install: 10 | ps: Bootstrap 11 | 12 | environment: 13 | global: 14 | WARNINGS_ARE_ERRORS: 15 | RTOOLS_VERSION: 34 16 | USE_RTOOLS: true 17 | 18 | matrix: 19 | - R_VERSION: release 20 | R_ARCH: x64 21 | 22 | build_script: 23 | - Rscript -e "install.packages('devtools',repos='http://cran.r-project.org')" 24 | # - Rscript -e "devtools::install_github('snoweye/pbdZMQ')" 25 | - travis-tool.sh install_deps 26 | - R CMD build --no-build-vignettes --no-manual --no-resave-data . 27 | - R CMD INSTALL remoter*.tar.gz 28 | - R CMD check --as-cran --no-manual --ignore-vignettes remoter*.tar.gz 29 | -------------------------------------------------------------------------------- /inst/CITATION: -------------------------------------------------------------------------------- 1 | year <- sub("-.*", "", meta$Date) 2 | note <- sprintf("{R} package version %s", meta$Version) 3 | 4 | citEntry( 5 | entry = "Misc", 6 | title = "{remoter}: Remote {R}: Control a Remote {R} Session from a Local One", 7 | author = personList(as.person("Drew Schmidt"), 8 | as.person("Wei-Chen Chen")), 9 | year = year, 10 | note = note, 11 | url = "https://cran.r-project.org/package=remoter", 12 | textVersion = NULL 13 | ) 14 | 15 | citEntry( 16 | entry = "Manual", 17 | title = "Guide to the {remoter} Package", 18 | author = personList(as.person("Drew Schmidt")), 19 | year = "2016", 20 | note = "{R} Vignette", 21 | url = "https://cran.r-project.org/package=remoter", 22 | textVersion = NULL 23 | ) 24 | 25 | citEntry( 26 | entry = "Manual", 27 | title = "Using {remoter} with Remote Machines", 28 | author = personList(as.person("Drew Schmidt")), 29 | year = "2016", 30 | note = "{R} Vignette", 31 | url = "https://cran.r-project.org/package=remoter", 32 | textVersion = NULL 33 | ) 34 | -------------------------------------------------------------------------------- /man/c2s.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/c2s.r 3 | \name{c2s} 4 | \alias{c2s} 5 | \title{Client-to-Server Object Transfer} 6 | \usage{ 7 | c2s(object, newname, env = .GlobalEnv) 8 | } 9 | \arguments{ 10 | \item{object}{A local R object.} 11 | 12 | \item{newname}{The name the object should take when it is stored on the remote 13 | server. If left blank, the remote name will be the same as the 14 | original (local) object's name.} 15 | 16 | \item{env}{The environment into which the assignment will take place. The 17 | default is the remoter "working environment".} 18 | } 19 | \value{ 20 | Returns \code{TRUE} invisibly on successful exit. 21 | } 22 | \description{ 23 | This function allows you to pass an object from the local R 24 | session (the client) to server. 25 | } 26 | \details{ 27 | Localize R objects. 28 | } 29 | \examples{ 30 | \dontrun{ 31 | ### Prompts are listed to clarify when something is eval'd locally vs remotely 32 | > library(remoter) 33 | > x <- "some data" 34 | > remoter::connect("my.remote.server") 35 | remoter> x 36 | ### Error: object 'x' not found 37 | remoter> c2s(x) 38 | remoter> x 39 | ### [1] "some data" 40 | } 41 | 42 | } 43 | -------------------------------------------------------------------------------- /vignettes/cover/remoter.tex: -------------------------------------------------------------------------------- 1 | \documentclass{article}% modified from the TUG example by: 2 | %Jean-C\^ome Charpentier 2005 3 | 4 | % Compile with XeLaTeX 5 | \newcommand{\thispackageversion}{0.4-0} 6 | 7 | 8 | \usepackage[margin=0.5in]{geometry} 9 | \usepackage{graphicx} 10 | \usepackage{xcolor} 11 | \usepackage{pstricks} 12 | 13 | \begin{document} 14 | \thispagestyle{empty} 15 | 16 | \noindent 17 | \begin{pspicture}(0,13.5)(\linewidth,0) 18 | \psline[linewidth=3mm,linecolor=black](0,13.5)(\linewidth,13.5) 19 | \rput(\linewidth,13.5) 20 | {\pspolygon*(-3.6,0)(-1.4,0)(0,-1.4)(0,-3.6)} 21 | \rput(\linewidth,13.5) 22 | {\rput{-45}(-1,-1){\Large\textbf{\white Version}}} 23 | \rput(\linewidth,13.5) 24 | {\rput{-45}(-1.5,-1.5){\Large\textbf{\white \thispackageversion}}} 25 | 26 | \rput[l](0,-6.8){\textsl{\huge Just the Basics}} 27 | \psline[linewidth=3mm,linecolor=black](0,-3)(\linewidth,-3) 28 | \psline[linewidth=3mm,linecolor=black](0,-6)(\linewidth,-6) 29 | \rput[l](0,-4.5){\psscaleboxto(\textwidth,2){Guide to the remoter Package}} 30 | \rput[b]{0}(9.5,-1){\includegraphics{remoter.png}} 31 | \end{pspicture} 32 | 33 | \vfill\noindent 34 | \ \hfill {\large\textsl{Drew Schmidt}} 35 | \end{document} 36 | -------------------------------------------------------------------------------- /vignettes/cover/remote_machines.tex: -------------------------------------------------------------------------------- 1 | \documentclass{article}% modified from the TUG example by: 2 | %Jean-C\^ome Charpentier 2005 3 | 4 | % Compile with XeLaTeX 5 | \newcommand{\thispackageversion}{0.4-0} 6 | 7 | 8 | \usepackage[margin=0.5in]{geometry} 9 | \usepackage{graphicx} 10 | \usepackage{xcolor} 11 | \usepackage{pstricks} 12 | 13 | \begin{document} 14 | \thispagestyle{empty} 15 | 16 | \noindent 17 | \begin{pspicture}(0,13.5)(\linewidth,0) 18 | \psline[linewidth=3mm,linecolor=black](0,13.5)(\linewidth,13.5) 19 | \rput(\linewidth,13.5) 20 | {\pspolygon*(-3.6,0)(-1.4,0)(0,-1.4)(0,-3.6)} 21 | \rput(\linewidth,13.5) 22 | {\rput{-45}(-1,-1){\Large\textbf{\white Version}}} 23 | \rput(\linewidth,13.5) 24 | {\rput{-45}(-1.5,-1.5){\Large\textbf{\white \thispackageversion}}} 25 | 26 | \rput[l](0,-6.8){\textsl{\huge Taking R to the Cloud and Beyond}} 27 | \psline[linewidth=3mm,linecolor=black](0,-3)(\linewidth,-3) 28 | \psline[linewidth=3mm,linecolor=black](0,-6)(\linewidth,-6) 29 | \rput[l](0,-4.5){\psscaleboxto(\textwidth,2){Using remoter with Remote Machines}} 30 | \rput[b]{0}(9.5,-1){\includegraphics{remoter.png}} 31 | \end{pspicture} 32 | 33 | \vfill\noindent 34 | \ \hfill {\large\textsl{Drew Schmidt}} 35 | \end{document} 36 | -------------------------------------------------------------------------------- /R/crypto.r: -------------------------------------------------------------------------------- 1 | #' has.sodium 2 | #' 3 | #' Report if the sodium package is availabe for use. 4 | #' 5 | #' @return 6 | #' Returns \code{TRUE} if the sodium package is available, and 7 | #' \code{FALSE} otherwise. 8 | #' 9 | #' @export 10 | has.sodium <- function() 11 | { 12 | getval(withsodium) 13 | } 14 | 15 | 16 | 17 | #' is.secure 18 | #' 19 | #' Report if communications with the connected server are 20 | #' encrypted. 21 | #' 22 | #' @return 23 | #' Returns \code{TRUE} if messages between client and server are 24 | #' currently encrypted, and \code{FALSE} if not. If the client 25 | #' is not currently running (i.e., if executed from just a regular 26 | #' R prompt), then \code{NA} is returned. 27 | #' 28 | #' @export 29 | is.secure <- function() 30 | { 31 | if (iam("local")) 32 | NA 33 | else 34 | getval(secure) 35 | } 36 | 37 | 38 | 39 | generate_keypair <- function() 40 | { 41 | setkey(private, sodium::keygen()) 42 | setkey(public, sodium::pubkey(getkey(private))) 43 | 44 | invisible() 45 | } 46 | 47 | 48 | 49 | pwhash <- function(password) 50 | { 51 | if (!is.null(password)) 52 | argon2::pw_hash(password) 53 | else 54 | NULL 55 | } 56 | 57 | 58 | 59 | pwcheck <- function(pw) 60 | { 61 | argon2::pw_check(getval(password), pw) 62 | } 63 | -------------------------------------------------------------------------------- /R/remoter-package.r: -------------------------------------------------------------------------------- 1 | #' remoter 2 | #' 3 | #' A set of utilities for client/server computing with R, controlling 4 | #' a remote R session (the server) from a local one (the client). Simply set 5 | #' up a server (see package vignette for more details) and connect to it from 6 | #' your local R session ('RStudio', terminal, etc). The client/server 7 | #' framework is a custom 'REPL' and runs entirely in your R session without the 8 | #' need for installing a custom environment on your system. Network 9 | #' communication is handled by the 'ZeroMQ' library by way of the 'pbdZMQ' 10 | #' package. 11 | #' 12 | #' @references Project URL: \url{https://github.com/RBigData/remoter} 13 | #' @author Drew Schmidt and Wei-Chen Chen 14 | #' 15 | #' @name remoter-package 16 | #' 17 | #' @importFrom argon2 pw_hash pw_check 18 | #' @importFrom stats runif 19 | #' @importFrom utils capture.output globalVariables packageVersion help str 20 | #' @importFrom tools file_path_as_absolute Rd2txt 21 | #' @importFrom getPass getPass 22 | #' @importFrom grDevices dev.cur dev.list dev.next dev.prev dev.off dev.set 23 | #' dev.new dev.size png as.raster 24 | #' @importFrom graphics plot.new par plot rasterImage 25 | #' @importFrom png readPNG 26 | #' 27 | #' @docType package 28 | #' @keywords package 29 | NULL 30 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright 2014-2021 Drew Schmidt and Wei-Chen Chen 2 | 3 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 4 | 5 | 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 6 | 7 | 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 8 | 9 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 10 | -------------------------------------------------------------------------------- /inst/LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright 2014-2019 Drew Schmidt and Wei-Chen Chen 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions are met: 5 | 6 | 1. Redistributions of source code must retain the above copyright notice, 7 | this list of conditions and the following disclaimer. 8 | 9 | 2. Redistributions in binary form must reproduce the above copyright notice, 10 | this list of conditions and the following disclaimer in the documentation 11 | and/or other materials provided with the distribution. 12 | 13 | THIS SOFTWARE IS PROVIDED BY COPYRIGHT HOLDER ``AS IS'' AND ANY EXPRESS OR 14 | IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 15 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO 16 | EVENT SHALL COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 17 | INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 18 | BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 19 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 20 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE 21 | OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 22 | ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 23 | -------------------------------------------------------------------------------- /DESCRIPTION: -------------------------------------------------------------------------------- 1 | Package: remoter 2 | Type: Package 3 | Title: Remote R: Control a Remote R Session from a Local One 4 | Version: 0.5-0 5 | Description: A set of utilities for client/server computing with R, controlling 6 | a remote R session (the server) from a local one (the client). Simply set 7 | up a server (see package vignette for more details) and connect to it from 8 | your local R session ('RStudio', terminal, etc). The client/server 9 | framework is a custom 'REPL' and runs entirely in your R session without the 10 | need for installing a custom environment on your system. Network 11 | communication is handled by the 'ZeroMQ' library by way of the 'pbdZMQ' 12 | package. 13 | License: BSD 2-clause License + file LICENSE 14 | Depends: 15 | R (>= 4.0.0) 16 | Imports: 17 | stats, 18 | utils, 19 | tools, 20 | grDevices, 21 | graphics, 22 | png (>= 0.1-7), 23 | pbdZMQ (>= 0.3-6), 24 | getip (>= 0.1-0), 25 | getPass (>= 0.2-2), 26 | argon2 (>= 0.4-0) 27 | Suggests: 28 | sodium (>= 0.2) 29 | ByteCompile: yes 30 | Authors@R: c( 31 | person("Drew", "Schmidt", role=c("aut", "cre"), email="wrathematics@gmail.com"), 32 | person("Wei-Chen", "Chen", role="aut"), 33 | person("R Core team", role = "ctb", comment = "some functions are modified from the R source code")) 34 | URL: https://github.com/RBigData/remoter 35 | BugReports: https://github.com/RBigData/remoter/issues 36 | Maintainer: Drew Schmidt 37 | RoxygenNote: 7.1.2 38 | -------------------------------------------------------------------------------- /man/exit.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/exit.r 3 | \name{exit} 4 | \alias{exit} 5 | \alias{shutdown} 6 | \alias{kill} 7 | \title{exit} 8 | \usage{ 9 | exit(client.only = TRUE, q.server = TRUE) 10 | 11 | shutdown() 12 | 13 | kill(addr = "localhost", port = 55555) 14 | } 15 | \arguments{ 16 | \item{client.only}{Logical; if \code{TRUE}, then the client disconnects from 17 | the server. Otherwise, the server is shut down together 18 | with the client.} 19 | 20 | \item{q.server}{Logical; if \code{TRUE}, then the server calls \code{q("no")} 21 | after shutting down with the client. This is useful for cases 22 | where the server is running in an interactive R session, and you 23 | wish to shut the entire thing down.} 24 | 25 | \item{addr, port}{The server address and port, as in \code{server()}.} 26 | } 27 | \value{ 28 | Returns \code{TRUE} invisibly on successful exit. 29 | } 30 | \description{ 31 | This function cleanly shuts down the remoter server the client 32 | is currently connected to, as well as shutting down the client. 33 | One can also use \code{q()} (while the client is running), and 34 | this will not close the active R session on the client. 35 | } 36 | \details{ 37 | Exit the remoter client/server. 38 | 39 | 40 | The \code{shutdown()} function is shorthand for 41 | \code{exit(FALSE, TRUE)}. The \code{kill()} function is shorthand 42 | for running \code{batch()} with \code{script="shutdown()"}. 43 | } 44 | \seealso{ 45 | \code{\link{server}} and \code{\link{batch}} 46 | } 47 | -------------------------------------------------------------------------------- /NAMESPACE: -------------------------------------------------------------------------------- 1 | # Generated by roxygen2: do not edit by hand 2 | 3 | export("?") 4 | export(batch) 5 | export(c2s) 6 | export(client) 7 | export(dev.curc) 8 | export(dev.listc) 9 | export(dev.newc) 10 | export(dev.nextc) 11 | export(dev.off) 12 | export(dev.offc) 13 | export(dev.prevc) 14 | export(dev.setc) 15 | export(dev.sizec) 16 | export(evalc) 17 | export(exit) 18 | export(has.sodium) 19 | export(help) 20 | export(is.secure) 21 | export(kill) 22 | export(lsc) 23 | export(recvfile) 24 | export(relay) 25 | export(rhelp) 26 | export(rmc) 27 | export(rpng) 28 | export(rpng.new) 29 | export(rpng.off) 30 | export(s2c) 31 | export(sendfile) 32 | export(server) 33 | export(showlog) 34 | export(shutdown) 35 | importFrom(argon2,pw_check) 36 | importFrom(argon2,pw_hash) 37 | importFrom(getPass,getPass) 38 | importFrom(grDevices,as.raster) 39 | importFrom(grDevices,dev.cur) 40 | importFrom(grDevices,dev.list) 41 | importFrom(grDevices,dev.new) 42 | importFrom(grDevices,dev.next) 43 | importFrom(grDevices,dev.off) 44 | importFrom(grDevices,dev.prev) 45 | importFrom(grDevices,dev.set) 46 | importFrom(grDevices,dev.size) 47 | importFrom(grDevices,png) 48 | importFrom(graphics,par) 49 | importFrom(graphics,plot) 50 | importFrom(graphics,plot.new) 51 | importFrom(graphics,rasterImage) 52 | importFrom(png,readPNG) 53 | importFrom(stats,runif) 54 | importFrom(tools,Rd2txt) 55 | importFrom(tools,file_path_as_absolute) 56 | importFrom(utils,capture.output) 57 | importFrom(utils,globalVariables) 58 | importFrom(utils,help) 59 | importFrom(utils,packageVersion) 60 | importFrom(utils,str) 61 | -------------------------------------------------------------------------------- /R/recvfile.r: -------------------------------------------------------------------------------- 1 | #' recvfile 2 | #' 3 | #' Transfer file from server to client. 4 | #' 5 | #' @param file_recv,file_send 6 | #' The file paths (as strings) for the input/sent file and the output/received 7 | #' file. 8 | #' @param verbose 9 | #' Should file transfer information be printed? 10 | #' 11 | #' @return 12 | #' Returns \code{TRUE} invisibly on successful exit. 13 | #' 14 | #' @export 15 | recvfile = function(file_send, file_recv, verbose=TRUE) 16 | { 17 | if (missing(file_send) || !is.string(file_send) || missing(file_recv) || !is.string(file_recv)) 18 | { 19 | if (iam("local")) 20 | remoter_client_stop("'file_send' and 'file_recv' must each be a single string") 21 | 22 | return(invisible()) 23 | } 24 | 25 | if (iam("local")) 26 | exists = remoter_receive() 27 | else if (iam("remote")) 28 | { 29 | exists = file.exists(file_send) 30 | remoter_send(exists) 31 | } 32 | 33 | if (!exists) 34 | { 35 | if (iam("local")) 36 | { 37 | remoter_send(NULL) 38 | remoter_client_stop("'file_send' does not appear to exist") 39 | } 40 | else if (iam("remote")) 41 | remoter_receive() 42 | 43 | return(invisible()) 44 | } 45 | 46 | 47 | socket = getval(socket) 48 | 49 | if (iam("local")) 50 | { 51 | pbdZMQ::zmq.recvfile(file=file_recv, socket=socket, verbose=verbose) 52 | remoter_send(NULL) 53 | } 54 | else if (iam("remote")) 55 | { 56 | pbdZMQ::zmq.sendfile(file=file_send, socket=socket) 57 | remoter_receive() 58 | } 59 | 60 | invisible(TRUE) 61 | } 62 | -------------------------------------------------------------------------------- /man/s2c.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/s2c.r 3 | \name{s2c} 4 | \alias{s2c} 5 | \title{Server-to-Client Object Transfer} 6 | \usage{ 7 | s2c(object, newname, env = .GlobalEnv) 8 | } 9 | \arguments{ 10 | \item{object}{A remote R object.} 11 | 12 | \item{newname}{The name the object should take when it is stored on the local 13 | client's R session. Must be the form of a character string. 14 | If left blank, the local name will be the same as the original 15 | (remote) object's name.} 16 | 17 | \item{env}{The environment into which the assignment will take place. The 18 | default is the global environment.} 19 | } 20 | \value{ 21 | Returns \code{TRUE} invisibly on successful exit. 22 | } 23 | \description{ 24 | This function allows you to pass an object from the server to 25 | the local R session behind the client. 26 | } 27 | \details{ 28 | Localize R objects. 29 | 30 | 31 | A \code{newname}, if specified, must be passed as a string 32 | (not a literal; i.e., \code{"mynewname"}, not \code{mynewname}). 33 | The name must also be syntactically valid (see \code{?make.names}). 34 | } 35 | \examples{ 36 | \dontrun{ 37 | ### Prompts are listed to clarify when something is eval'd locally vs remotely 38 | > library(remoter) 39 | > y 40 | ### Error: object 'y' not found 41 | > remoter::connect("my.remote.server") 42 | remoter> x 43 | ### Error: object 'x' not found 44 | remoter> x <- "some data" 45 | remoter> x 46 | ### [1] "some data" 47 | remoter> s2c(x, "y") 48 | remoter> q() 49 | > y 50 | ### [1] "some data" 51 | } 52 | 53 | } 54 | -------------------------------------------------------------------------------- /R/sendfile.r: -------------------------------------------------------------------------------- 1 | #' sendfile 2 | #' 3 | #' Transfer file from client to server. 4 | #' 5 | #' @param file_send,file_recv 6 | #' The file paths (as strings) for the input/sent file and the output/received 7 | #' file. 8 | #' @param verbose 9 | #' Should file transfer information be printed? 10 | #' 11 | #' @return 12 | #' Returns \code{TRUE} invisibly on successful exit. 13 | #' 14 | #' @export 15 | sendfile = function(file_send, file_recv, verbose=TRUE) 16 | { 17 | if (missing(file_send) || !is.string(file_send) || missing(file_recv) || !is.string(file_recv)) 18 | { 19 | if (iam("local")) 20 | remoter_client_stop("'file_send' and 'file_recv' must each be a single string") 21 | 22 | return(invisible()) 23 | } 24 | 25 | if (iam("local")) 26 | { 27 | remoter_receive() 28 | exists = file.exists(file_send) 29 | remoter_send(exists) 30 | } 31 | else if (iam("remote")) 32 | { 33 | remoter_send(NULL) 34 | exists = remoter_receive() 35 | } 36 | 37 | if (!exists) 38 | { 39 | if (iam("local")) 40 | remoter_client_stop("'file_send' does not appear to exist") 41 | 42 | return(invisible()) 43 | } 44 | 45 | 46 | socket = getval(socket) 47 | 48 | if (iam("local")) 49 | { 50 | remoter_receive() 51 | pbdZMQ::zmq.sendfile(file=file_send, socket=socket, verbose=verbose) 52 | remoter_send(NULL) 53 | } 54 | else if (iam("remote")) 55 | { 56 | remoter_send(NULL) 57 | pbdZMQ::zmq.recvfile(file=file_recv, socket=socket) 58 | remoter_receive() 59 | } 60 | 61 | invisible(TRUE) 62 | } 63 | -------------------------------------------------------------------------------- /R/printing.r: -------------------------------------------------------------------------------- 1 | ### client printing tools 2 | remoter_client_stop <- function(msg) 3 | { 4 | set(client_lasterror, msg) 5 | cat("Error: ", msg, "\n") 6 | 7 | invisible() 8 | } 9 | 10 | 11 | 12 | remoter_repl_printer <- function() 13 | { 14 | ### cast addition first. 15 | addition <- get.status(ret_addition) 16 | if (!is.null(addition)) 17 | cat(paste(addition, collapse = "\n"), "\n") 18 | 19 | ### cast return second. 20 | if (get.status(visible)) 21 | cat(paste(get.status(ret), collapse="\n"), "\n") 22 | 23 | remoter_show_errors() 24 | remoter_show_warnings() 25 | 26 | return(invisible()) 27 | } 28 | 29 | 30 | 31 | remoter_show_errors <- function() 32 | { 33 | if (!is.null(get.status(lasterror))) 34 | cat(get.status(lasterror)) 35 | 36 | invisible() 37 | } 38 | 39 | 40 | 41 | remoter_show_warnings <- function(force=FALSE) 42 | { 43 | warnings <- get.status(warnings) 44 | nwarnings <- length(warnings) 45 | 46 | if (!is.null(warnings) && get.status(shouldwarn)) 47 | { 48 | if (nwarnings == 1) 49 | { 50 | cat("Warning message:\n") 51 | cat(warnings) 52 | } 53 | else if (nwarnings < 11 || force) 54 | { 55 | cat("Warning messages:\n") 56 | for (i in 1:nwarnings) 57 | { 58 | w <- warnings[i] 59 | cat(paste0(i, ": ", w, "\n")) 60 | } 61 | } 62 | else 63 | { 64 | cat(paste("There were", nwarnings, "warnings (use warnings() to see them)")) 65 | } 66 | cat("\n") 67 | } 68 | 69 | set.status(visible, FALSE) 70 | set.status(shouldwarn, FALSE) 71 | 72 | invisible() 73 | } 74 | -------------------------------------------------------------------------------- /R/logging.r: -------------------------------------------------------------------------------- 1 | logprint <- function(msg, checkverbose=FALSE, checkshowmsg=FALSE, preprint="", level="", timestamp=TRUE, forcemsg=FALSE) 2 | { 3 | if (identical(msg, magicmsg_first_connection)) 4 | return(invisible()) 5 | 6 | if (forcemsg || (getval(serverlog) && !checkverbose && !checkshowmsg) || (getval(verbose) && checkverbose) || (getval(showmsg) && checkshowmsg)) 7 | { 8 | if (timestamp) 9 | ts <- paste0("[", Sys.time(), "]: ") 10 | else 11 | ts <- "" 12 | 13 | logmsg <- paste0(preprint, ts, level, ifelse(level=="", "", ": "), msg, "\n") 14 | # cat(logmsg) 15 | message(logmsg, appendLF=FALSE) 16 | 17 | if (!forcemsg) 18 | logprint_file(logmsg) 19 | } 20 | 21 | invisible() 22 | } 23 | 24 | 25 | 26 | logfile_init <- function() 27 | { 28 | if (getval(serverlog)) 29 | { 30 | logfile = getval(logfile) 31 | append = !file.exists(logfile) 32 | cat("", file=logfile, append=append) 33 | 34 | logfile 35 | } 36 | else 37 | NULL 38 | } 39 | 40 | 41 | 42 | logprint_file <- function(logmsg) 43 | { 44 | if (getval(serverlog)) 45 | { 46 | cat(logmsg, file=getval(logfile), append=TRUE) 47 | utils::flush.console() 48 | } 49 | 50 | invisible() 51 | } 52 | 53 | 54 | 55 | #' showlog 56 | #' 57 | #' Show the server log on the client. 58 | #' 59 | #' @export 60 | showlog <- function() 61 | { 62 | if (getval(serverlog) && file.exists(getval(logfile))) 63 | log = readLines(getval(logfile)) 64 | else 65 | stop("no log file found!") 66 | 67 | c( 68 | paste("### logfile:", getval(logfile)), 69 | log 70 | ) 71 | } 72 | -------------------------------------------------------------------------------- /vignettes/include/00-acknowledgement.tex: -------------------------------------------------------------------------------- 1 | \section*{Acknowledgements and Disclaimer} 2 | Work for the \textbf{remoter} package is supported in part by the project 3 | *Harnessing Scalable Libraries for Statistical Computing on Modern Architectures 4 | and Bringing Statistics to Large Scale Computing* funded by the National 5 | Science Foundation Division of Mathematical Sciences under Grant No. 1418195. 6 | 7 | Any opinions, findings, and conclusions or recommendations expressed in this 8 | material are those of the authors and do not necessarily reflect the views of 9 | the National Science Foundation. The findings and conclusions in this article 10 | have not been formally disseminated by the U.S. Department of Health \& Human 11 | Services nor by the U.S. Department of Energy, and should not be construed to 12 | represent any determination or policy of University, Agency, Administration and 13 | National Laboratory. 14 | 15 | The \textbf{remoter} logo comes from the image 16 | ``\href{https://commons.wikimedia.org/wiki/File:Tr\%C3\%A5dtelefon-illustration.png\#/media/File:Tr\%C3\%A5dtelefon-illustration.png}{Tradtelefon-illustration}''. 17 | Licensed under Public Domain via Commons. 18 | 19 | This manual may be incorrect or out-of-date. The author(s) assume 20 | no responsibility for errors or omissions, or for damages resulting 21 | from the use of the information contained herein. 22 | 23 | This publication was typeset using \LaTeX. 24 | 25 | \vfill 26 | 27 | \null 28 | \vfill 29 | \copyright\ 2015--2017 Drew Schmidt. 30 | 31 | Permission is granted to make and distribute verbatim copies of 32 | this vignette and its source provided the copyright notice and 33 | this permission notice are preserved on all copies. 34 | -------------------------------------------------------------------------------- /man/rhelp.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/wcc_rhelp.r 3 | \name{rhelp} 4 | \alias{rhelp} 5 | \alias{help} 6 | \alias{?} 7 | \title{rhelp} 8 | \usage{ 9 | rhelp( 10 | topic, 11 | package = NULL, 12 | lib.loc = NULL, 13 | verbose = getOption("verbose"), 14 | try.all.packages = getOption("help.try.all.packages") 15 | ) 16 | 17 | help( 18 | topic, 19 | package = NULL, 20 | lib.loc = NULL, 21 | verbose = getOption("verbose"), 22 | try.all.packages = getOption("help.try.all.packages") 23 | ) 24 | 25 | `?`(e1, e2) 26 | } 27 | \arguments{ 28 | \item{topic, e1, e2}{A topic as in \code{utils::help()}} 29 | 30 | \item{package}{A package as in \code{utils::help()}} 31 | 32 | \item{lib.loc}{A lib location as in \code{utils::help()}} 33 | 34 | \item{verbose}{if verbose on/off as in \code{utils::help()}} 35 | 36 | \item{try.all.packages}{if try all packages as in \code{utils::help()}} 37 | } 38 | \description{ 39 | Provide the primary interface to the help systems as \code{utils::help()} 40 | } 41 | \details{ 42 | Remote R Help System 43 | } 44 | \examples{ 45 | \dontrun{ 46 | ### Prompts are listed to clarify when something is eval'd locally vs 47 | ### remotely 48 | > # suppressMessages(library(remoter, quietly = TRUE)) 49 | > # client() 50 | > remoter::client("192.168.56.101") 51 | 52 | remoter> rhelp("plot") 53 | remoter> rhelp(package = "remoter") 54 | remoter> rhelp("plot", package = "remoter") 55 | 56 | remoter> rhelp("dev.off") 57 | remoter> rhelp("dev.off", package = "remoter") 58 | remoter> rhelp("dev.off", package = "grDevices") 59 | 60 | remoter> help("par") 61 | 62 | remoter> ?`+` 63 | remoter> ?`?` 64 | remoter> ?"??" 65 | remoter> package?base 66 | remoter> `?`(package, remoter) 67 | 68 | 69 | remoter> q() 70 | > 71 | } 72 | 73 | } 74 | -------------------------------------------------------------------------------- /man/client.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/client.r 3 | \name{client} 4 | \alias{client} 5 | \title{Client Launcher} 6 | \usage{ 7 | client( 8 | addr = "localhost", 9 | port = 55555, 10 | password = NULL, 11 | prompt = "remoter", 12 | timer = FALSE, 13 | serialversion = NULL 14 | ) 15 | } 16 | \arguments{ 17 | \item{addr}{The remote host/address/endpoint.} 18 | 19 | \item{port}{The port (number) that will be used for communication between 20 | the client and server. The port value for the client and server 21 | must agree.} 22 | 23 | \item{password}{An initial password to pass to the server. If the server is not accepting 24 | passwords, then this argument is ignored. If the initial pasword is 25 | incorrect, then assuming the server's \code{maxretry>1}, then you will be 26 | interactively asked to enter the password.} 27 | 28 | \item{prompt}{The prompt to use to delineate the client from the normal R REPL.} 29 | 30 | \item{timer}{Logical; should the "performance prompt", which shows timing 31 | statistics after every command, be used?} 32 | 33 | \item{serialversion}{NULL or numeric; the workspace format version to use when serializing. 34 | NULL specifies the current default version. The only other supported 35 | values are 2 and 3.} 36 | } 37 | \value{ 38 | Returns \code{TRUE} invisibly on successful exit. 39 | } 40 | \description{ 41 | Connect to a remote server (launch the client). 42 | } 43 | \details{ 44 | The \code{port} values between the client and server must agree. 45 | If they do not, this can cause the client to hang. 46 | The client is a specialized REPL that intercepts commands sent 47 | through the R interpreter. These commands are then sent from the 48 | client to and evaluated on the server. 49 | The client communicates over ZeroMQ with the server using a REQ/REP pattern. 50 | Both commands (from client to server) and returns (from server 51 | to client) are handled in this way. 52 | 53 | To shut down the server and the client, see \code{exit()}. 54 | } 55 | -------------------------------------------------------------------------------- /man/batch.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/batch.r 3 | \name{batch} 4 | \alias{batch} 5 | \title{Batch Execution} 6 | \usage{ 7 | batch( 8 | addr = "localhost", 9 | port = 55555, 10 | password = NULL, 11 | file, 12 | script, 13 | timer = FALSE, 14 | serialversion = NULL 15 | ) 16 | } 17 | \arguments{ 18 | \item{addr}{The remote host/address/endpoint.} 19 | 20 | \item{port}{The port (number) that will be used for communication between 21 | the client and server. The port value for the client and server 22 | must agree.} 23 | 24 | \item{password}{An initial password to pass to the server. If the server is not accepting 25 | passwords, then this argument is ignored. If the initial pasword is 26 | incorrect, then assuming the server's \code{maxretry>1}, then you will be 27 | interactively asked to enter the password.} 28 | 29 | \item{file}{A character string pointing to the file you wish to execute/source. Either 30 | this or \code{script} (but not both) should be provided.} 31 | 32 | \item{script}{A character string containing the commands you wish to execute/source. Either 33 | this or \code{file} (but not both) should be provided.} 34 | 35 | \item{timer}{Logical; should the "performance prompt", which shows timing 36 | statistics after every command, be used?} 37 | 38 | \item{serialversion}{NULL or numeric; the workspace format version to use when serializing. 39 | NULL specifies the current default version. The only other supported 40 | values are 2 and 3.} 41 | } 42 | \value{ 43 | Returns \code{TRUE} invisibly on successful exit. 44 | } 45 | \description{ 46 | Run a local script on a remote server in batch. Similar to R's own 47 | \code{source()} function. 48 | } 49 | \details{ 50 | Note that \code{batch()} can not be run from inside an active connection. 51 | Its purpose is to bypass the need to start a connection via \code{client()} 52 | } 53 | \examples{ 54 | \dontrun{ 55 | library(remoter) 56 | ### NOTE first run a server via remoter::server() )in a separate R session. 57 | ### For simplicity, assume they are on the same machine. 58 | 59 | # Run a script in an R file on the local/client machine 60 | file <- "/path/to/an/R/script.r" 61 | batch(file=file) 62 | 63 | # Run a script stored in a character vector 64 | script <- "1+1" 65 | batch(script="1+1") 66 | } 67 | 68 | } 69 | -------------------------------------------------------------------------------- /man/rDevices.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/wcc_client_device.r 3 | \name{rDevices} 4 | \alias{rDevices} 5 | \alias{dev.curc} 6 | \alias{dev.listc} 7 | \alias{dev.nextc} 8 | \alias{dev.prevc} 9 | \alias{dev.offc} 10 | \alias{dev.setc} 11 | \alias{dev.newc} 12 | \alias{dev.sizec} 13 | \title{Local Graphic Devices} 14 | \usage{ 15 | dev.curc() 16 | 17 | dev.listc() 18 | 19 | dev.nextc(which = grDevices::dev.cur()) 20 | 21 | dev.prevc(which = grDevices::dev.cur()) 22 | 23 | dev.offc(which = grDevices::dev.cur()) 24 | 25 | dev.setc(which = grDevices::dev.cur()) 26 | 27 | dev.newc(..., noRstudioGD = FALSE) 28 | 29 | dev.sizec(units = c("in", "cm", "px")) 30 | } 31 | \arguments{ 32 | \item{which}{An integer specifying a device number as in \code{grDevices::dev.off()}} 33 | 34 | \item{...}{arguments to be passed to the device selected as in 35 | \code{grDevices::dev.new()}} 36 | 37 | \item{noRstudioGD}{as in \code{grDevices::dev.new()}} 38 | 39 | \item{units}{as in \code{grDevices::dev.size()}} 40 | } 41 | \description{ 42 | Functions for controlling graphic device locally when the client of 43 | remote R is on. All these functions are evaluated in local R from within 44 | the remote R prompt. 45 | 46 | \code{dev.curc()} locally evals \code{grDevices::dev.cur()}. 47 | 48 | \code{dev.listc()} locally evals \code{grDevices::dev.list()}. 49 | 50 | \code{dev.nextc()} locally evals \code{grDevices::dev.next()}. 51 | 52 | \code{dev.prevc()} locally evals \code{grDevices::dev.prev()}. 53 | 54 | \code{dev.offc()} locally evals \code{grDevices::dev.off()}. 55 | 56 | \code{dev.setc()} locally evals \code{grDevices::dev.set()}. 57 | 58 | \code{dev.newc()} locally eval \code{grDevices::dev.new()}. 59 | 60 | \code{dev.sizec()} locally evals \code{grDevices::dev.size()}. 61 | } 62 | \details{ 63 | Local Graphic Device Controlling Functions 64 | } 65 | \examples{ 66 | \dontrun{ 67 | ### Prompts are listed to clarify when something is eval'd locally vs 68 | ### remotely 69 | > library(remoter, quietly = TRUE) 70 | > client() 71 | 72 | remoter> rpng.new(plot(1:5)) 73 | remoter> dev.newc(width = 6, height = 4) 74 | remoter> a <- function() plot(iris$Sepal.Length, iris$Petal.Length) 75 | remoter> rpng.new(a, width = 6 * 72, height = 4 * 72) 76 | 77 | remoter> dev.curc() 78 | remoter> dev.listc() 79 | remoter> dev.offc() 80 | 81 | remoter> q() 82 | > 83 | } 84 | 85 | } 86 | \seealso{ 87 | \code{\link{rpng}()} 88 | } 89 | -------------------------------------------------------------------------------- /R/relay.r: -------------------------------------------------------------------------------- 1 | #' Relay Launcher 2 | #' 3 | #' Launcher for the remoter relay. 4 | #' 5 | #' @details 6 | #' The relay is an intermediary or "middleman" between the client 7 | #' and server meant for machines with split login/compute nodes. 8 | #' 9 | #' @param addr 10 | #' The address of the server. 11 | #' @param recvport 12 | #' The port for receiving commands from the client. 13 | #' @param sendport 14 | #' The port for sending commands to the server. 15 | #' @param verbose 16 | #' Show verbose messaging. 17 | #' 18 | #' @return 19 | #' Returns \code{TRUE} invisibly on successful exit. 20 | #' 21 | #' @export 22 | relay <- function(addr, recvport=55556, sendport=55555, verbose=FALSE) 23 | { 24 | validate_address(addr) 25 | addr <- scrub_addr(addr) 26 | validate_port(recvport, warn=FALSE) 27 | validate_port(sendport, warn=FALSE) 28 | check(recvport != sendport) 29 | check.is.flag(verbose) 30 | 31 | reset_state() 32 | 33 | set(whoami, "relay") 34 | set(remote_addr, addr) 35 | set(recvport, recvport) 36 | set(sendport, sendport) 37 | set(verbose, verbose) 38 | set(logfile, logfile_init()) 39 | 40 | logprint(paste("*** Launching relay ***"), preprint="\n") 41 | 42 | remoter_repl_relay() 43 | remoter_exit_server() 44 | 45 | invisible(TRUE) 46 | } 47 | 48 | 49 | 50 | remoter_repl_relay <- function() 51 | { 52 | ### client/relay comms 53 | ctxt.recv <- pbdZMQ::init.context() 54 | socket.recv <- pbdZMQ::init.socket(ctxt.recv, "ZMQ_REP") 55 | addr <- pbdZMQ::address("*", getval(recvport)) 56 | pbdZMQ::bind.socket(socket.recv, addr) 57 | 58 | ### relay/server comms 59 | ctxt.send <- pbdZMQ::init.context() 60 | socket.send <- pbdZMQ::init.socket(ctxt.send, "ZMQ_REQ") 61 | addr <- pbdZMQ::address(getval(remote_addr), getval(sendport)) 62 | pbdZMQ::connect.socket(socket.send, addr) 63 | 64 | while (TRUE) 65 | { 66 | ### receive from client, send to server 67 | data <- pbdZMQ::receive.socket(socket=socket.recv, unserialize=FALSE) 68 | logprint("Received message from client. Sending to server.", checkverbose=TRUE) 69 | pbdZMQ::send.socket(socket=socket.send, data=data, serialize=FALSE) 70 | 71 | ### receive from server, send to client 72 | data <- pbdZMQ::receive.socket(socket=socket.send, unserialize=FALSE) 73 | logprint("Received response from server. Sending to client.", checkverbose=TRUE) 74 | pbdZMQ::send.socket(socket=socket.recv, data=data, serialize=FALSE) 75 | } 76 | 77 | return(invisible()) 78 | } 79 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # remoter 2 | 3 | * **Version:** 0.5-0 4 | * **License:** [BSD 2-Clause](https://opensource.org/licenses/BSD-2-Clause) 5 | * **Project home**: https://github.com/RBigData/remoter 6 | * **Bug reports**: https://github.com/RBigData/remoter/issues 7 | 8 | 9 | Control a remote R session from your local R session. The package uses [**pbdZMQ**](https://github.com/snoweye/pbdZMQ) to handle the communication and networking. Encryption is supported if the **sodium** package is (optionally) installed. Details below. 10 | 11 | 12 | 13 | 14 | ## Installation 15 | 16 | You can install the stable version from CRAN using the usual `install.packages()`: 17 | 18 | ```r 19 | install.packages("remoter") 20 | ``` 21 | 22 | In order to be able to create and connect to secure servers, you need to also install the **sodium** package. The use of **sodium** is optional because it is a non-trivial systems dependency, but it is highly recommended. You can install it manually with a call to `install.packages("sodium")` or by installing **remoter** via: 23 | 24 | ```r 25 | install.packages("remoter", dependencies=TRUE) 26 | ``` 27 | 28 | The development version is maintained on GitHub: 29 | 30 | ```r 31 | remotes::install_github("RBigData/remoter") 32 | ``` 33 | 34 | To simplify installations on cloud systems, we also have a [Docker container](https://github.com/RBigData/docker) available. 35 | 36 | 37 | 38 | ## Usage 39 | 40 | For setting up a local server, you can do: 41 | 42 | ```r 43 | remoter::server() 44 | ``` 45 | 46 | And connect to it interactively via: 47 | 48 | ```r 49 | remoter::client() 50 | ``` 51 | 52 | There is also the option to pipe commands to the server in batch using the `batch()` function: 53 | 54 | ```r 55 | ### Passing an R script file 56 | remoter::batch(file="my_rscript_file.r") 57 | ### Passing in a script manually 58 | remoter::batch(script="1+1") 59 | ``` 60 | 61 | For more details, including working with remote machines, see the package vignette. 62 | 63 | 64 | 65 | ## Acknowledgements 66 | 67 | Initial work for the **remoter** package was supported in part by the project *Harnessing Scalable Libraries for Statistical Computing on Modern Architectures and Bringing Statistics to Large Scale Computing* funded by the National Science Foundation Division of Mathematical Sciences under Grant No. 1418195. 68 | 69 | Any opinions, findings, and conclusions or recommendations expressed in this material are those of the authors and do not necessarily reflect the views of the National Science Foundation. 70 | -------------------------------------------------------------------------------- /man/server.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/server.r 3 | \name{server} 4 | \alias{server} 5 | \title{Server Launcher} 6 | \usage{ 7 | server( 8 | port = 55555, 9 | password = NULL, 10 | maxretry = 5, 11 | secure = has.sodium(), 12 | logfile = NULL, 13 | verbose = FALSE, 14 | showmsg = FALSE, 15 | userpng = TRUE, 16 | sync = TRUE, 17 | serialversion = NULL 18 | ) 19 | } 20 | \arguments{ 21 | \item{port}{The port (number) that will be used for communication between 22 | the client and server. The port value for the client and server 23 | must agree. If the value is 0, then a random open port will be 24 | selected.} 25 | 26 | \item{password}{A password the client must enter before the user can process 27 | commands on the server. If the value is \code{NULL}, then no 28 | password checking takes place.} 29 | 30 | \item{maxretry}{The maximum number of retries for passwords before shutting 31 | everything down.} 32 | 33 | \item{secure}{Logical; enables encryption via public key cryptography of 34 | the 'sodium' package is available.} 35 | 36 | \item{logfile}{A file path to use for server logging. If the value is \code{NULL}, then no 37 | logging takes place.} 38 | 39 | \item{verbose}{Logical; enables the verbose logger.} 40 | 41 | \item{showmsg}{Logical; if TRUE, messages from the client are logged.} 42 | 43 | \item{userpng}{Logical; if TRUE, rpng is set as the default device for displaying.} 44 | 45 | \item{sync}{Logical; if TRUE, the client will have \code{str()}'d versions of server 46 | objects recreated in the global environment. This is useful in IDE's like 47 | RStudio, but it carries a performance penalty. For terminal users, this is 48 | not recommended.} 49 | 50 | \item{serialversion}{NULL or numeric; the workspace format version to use when serializing. 51 | NULL specifies the current default version. The only other supported 52 | values are 2 and 3.} 53 | } 54 | \value{ 55 | Returns \code{TRUE} invisibly on successful exit. 56 | } 57 | \description{ 58 | Launcher for the remoter server. 59 | } 60 | \details{ 61 | By a 'secure' server, we mean one that encrypts messages it 62 | sends and only accepts encrypted messages. Encryption uses 63 | public key cryptography, using the 'sodium' package. 64 | 65 | If the 'sodium' package is available to the server, then by 66 | default the server will be secure. If the package is not 67 | available, then you will not be able to start a secure server. 68 | If the server is secure, then a client can only connect if 69 | the client has the 'sodium' package available. 70 | } 71 | -------------------------------------------------------------------------------- /R/exit.r: -------------------------------------------------------------------------------- 1 | #' exit 2 | #' 3 | #' Exit the remoter client/server. 4 | #' 5 | #' @description 6 | #' This function cleanly shuts down the remoter server the client 7 | #' is currently connected to, as well as shutting down the client. 8 | #' One can also use \code{q()} (while the client is running), and 9 | #' this will not close the active R session on the client. 10 | #' 11 | #' @details 12 | #' The \code{shutdown()} function is shorthand for 13 | #' \code{exit(FALSE, TRUE)}. The \code{kill()} function is shorthand 14 | #' for running \code{batch()} with \code{script="shutdown()"}. 15 | #' 16 | #' @param client.only 17 | #' Logical; if \code{TRUE}, then the client disconnects from 18 | #' the server. Otherwise, the server is shut down together 19 | #' with the client. 20 | #' @param q.server 21 | #' Logical; if \code{TRUE}, then the server calls \code{q("no")} 22 | #' after shutting down with the client. This is useful for cases 23 | #' where the server is running in an interactive R session, and you 24 | #' wish to shut the entire thing down. 25 | #' @param addr,port 26 | #' The server address and port, as in \code{server()}. 27 | #' 28 | #' @return 29 | #' Returns \code{TRUE} invisibly on successful exit. 30 | #' 31 | #' @seealso \code{\link{server}} and \code{\link{batch}} 32 | #' 33 | #' @name exit 34 | #' @rdname exit 35 | NULL 36 | 37 | #' @rdname exit 38 | #' @export 39 | exit <- function(client.only=TRUE, q.server=TRUE) 40 | { 41 | if (!assert_nostop(is.flag(client.only))) 42 | return(invisible(FALSE)) 43 | if (!assert_nostop(is.flag(q.server))) 44 | return(invisible(FALSE)) 45 | 46 | if (!client.only || iam("local")) 47 | { 48 | set.status(should_exit, TRUE) 49 | 50 | if (iam("remote")) 51 | set(client_called_shutdown, TRUE) 52 | # logprint("client killed server") 53 | } 54 | else 55 | set(client_called_exit, TRUE) 56 | # logprint("client disconnected with call to exit()") 57 | 58 | if (!client.only) 59 | { 60 | if (iam("remote") && interactive()) 61 | { 62 | if (q.server) 63 | set(kill_interactive_server, TRUE) 64 | else 65 | set(kill_interactive_server, FALSE) 66 | } 67 | } 68 | 69 | return(invisible(TRUE)) 70 | } 71 | 72 | 73 | 74 | ### For internal consistency 75 | remoter_exit <- exit 76 | 77 | 78 | 79 | #' @rdname exit 80 | #' @export 81 | shutdown <- function() 82 | { 83 | exit(client.only = FALSE, q.server = TRUE) 84 | # FIXME 85 | ### Wipe out client as well when server is down. 86 | # if (!is.null(.pbdenv$password)) 87 | # getPass::zerobuff(.pbdenv$password) 88 | } 89 | 90 | 91 | 92 | #' @rdname exit 93 | #' @export 94 | kill <- function(addr="localhost", port=55555) 95 | { 96 | batch(addr=addr, port=port, script="shutdown()") 97 | } 98 | -------------------------------------------------------------------------------- /R/c2s.r: -------------------------------------------------------------------------------- 1 | #' Client-to-Server Object Transfer 2 | #' 3 | #' Localize R objects. 4 | #' 5 | #' @description 6 | #' This function allows you to pass an object from the local R 7 | #' session (the client) to server. 8 | #' 9 | #' @param object 10 | #' A local R object. 11 | #' @param newname 12 | #' The name the object should take when it is stored on the remote 13 | #' server. If left blank, the remote name will be the same as the 14 | #' original (local) object's name. 15 | #' @param env 16 | #' The environment into which the assignment will take place. The 17 | #' default is the remoter "working environment". 18 | #' 19 | #' @return 20 | #' Returns \code{TRUE} invisibly on successful exit. 21 | #' 22 | #' @examples 23 | #' \dontrun{ 24 | #' ### Prompts are listed to clarify when something is eval'd locally vs remotely 25 | #' > library(remoter) 26 | #' > x <- "some data" 27 | #' > remoter::connect("my.remote.server") 28 | #' remoter> x 29 | #' ### Error: object 'x' not found 30 | #' remoter> c2s(x) 31 | #' remoter> x 32 | #' ### [1] "some data" 33 | #' } 34 | #' 35 | #' @export 36 | c2s <- function(object, newname, env=.GlobalEnv) 37 | { 38 | if (missing(object)) 39 | { 40 | if (iam("local")) 41 | remoter_client_stop("must pass an object") 42 | 43 | return(invisible()) 44 | } 45 | 46 | test <- tryCatch(is.environment(env), error=identity) 47 | if (isFALSE(test) || inherits(test, "error")) 48 | { 49 | if (iam("local")) 50 | { 51 | if (isFALSE(test)) 52 | remoter_client_stop("invalid environment") 53 | else 54 | remoter_client_stop(gsub(test, pattern="(.*: |\\n)", replacement="")) 55 | } 56 | 57 | return(invisible()) 58 | } 59 | 60 | if (!missing(newname)) 61 | { 62 | if (!identical(make.names(newname), newname)) 63 | { 64 | if (iam("local")) 65 | remoter_client_stop("invalid 'newname'") 66 | 67 | return(invisible()) 68 | } 69 | } 70 | 71 | 72 | name <- as.character(substitute(object)) 73 | err <- ".__remoter_s2c_failure" 74 | 75 | if (iam("local")) 76 | { 77 | remoter_receive() 78 | 79 | value <- get0(name, ifnotfound=err) 80 | remoter_send(data=value) 81 | 82 | if (identical(value, err)) 83 | { 84 | cat(paste0("Error: object '", name, "' not found on the client\n")) 85 | return(invisible(FALSE)) 86 | } 87 | } 88 | else if (iam("remote")) 89 | { 90 | remoter_send(NULL) 91 | 92 | value <- remoter_receive() 93 | 94 | if (identical(value, err)) 95 | { 96 | remoter_send(FALSE) 97 | return(invisible(FALSE)) 98 | } 99 | 100 | if (!missing(newname)) 101 | name <- newname 102 | 103 | if (missing(env)) 104 | env <- sys.frame(-1) 105 | 106 | assign(x=name, value=value, envir=env) 107 | } 108 | 109 | return(invisible(TRUE)) 110 | } 111 | -------------------------------------------------------------------------------- /R/on_client_utils.r: -------------------------------------------------------------------------------- 1 | #' ls on Client 2 | #' 3 | #' View objects on the client. 4 | #' 5 | #' @description 6 | #' A function to view environments on the client's R session. To 7 | #' view objects on the server, just use \code{ls()}. Instead of 8 | #' using this function, you could also just kill the client, do your 9 | #' local operations, then re-run your \code{client()} command. 10 | #' 11 | #' @param envir 12 | #' Environment (as in \code{ls()}). 13 | #' @param all.names 14 | #' Logical that determines if all names are returned or those beginning 15 | #' with a '.' are omitted (as in \code{ls()}). 16 | #' @param pattern 17 | #' Optional regular expression (as in \code{ls()}). 18 | #' 19 | #' @return 20 | #' Returns \code{TRUE} invisibly on successful exit. 21 | #' 22 | #' @export 23 | lsc <- function(envir, all.names=FALSE, pattern) 24 | { 25 | if (missing(envir)) 26 | envir <- .GlobalEnv 27 | 28 | if (iam("local")) 29 | print(ls(envir=envir, all.names=all.names, pattern=pattern)) 30 | 31 | return(invisible(TRUE)) 32 | } 33 | 34 | 35 | 36 | #' rmc 37 | #' 38 | #' Remove objects on the client. 39 | #' 40 | #' @description 41 | #' A function to remove objects from the client's R session. To 42 | #' remove objects on the server, just use \code{rm()}. Instead of 43 | #' using this function, you could also just kill the client, do your 44 | #' local operations, then re-run your \code{client()} command. 45 | #' 46 | #' @param ... 47 | #' Objects to be removed from the client's R session. 48 | #' @param list 49 | #' Character vector naming objects to be removed (as in \code{rm()}). 50 | #' @param envir 51 | #' Environment (as in \code{rm()}). 52 | #' 53 | #' @return 54 | #' Returns \code{TRUE} invisibly on successful exit. 55 | #' 56 | #' @export 57 | rmc <- function(..., list=character(), envir) 58 | { 59 | ### TODO error checking 60 | if (iam("local")) 61 | { 62 | if (missing(envir)) 63 | envir <- .GlobalEnv 64 | 65 | ### FIXME this is disgusting 66 | objs <- match.call(expand.dots=TRUE) 67 | objs[[1]] <- NULL 68 | 69 | rm(list=as.character(objs), envir=envir) 70 | } 71 | 72 | return(invisible(TRUE)) 73 | } 74 | 75 | 76 | 77 | #' evalc 78 | #' 79 | #' Evaluate expressions on the client. 80 | #' 81 | #' @description 82 | #' A function to evaluate expressions on the client's R session. To 83 | #' eval expressions on the server, just use \code{eval()}. Instead of 84 | #' using this function, you could also just kill the client, do your 85 | #' local operations, then re-run your \code{client()} command. 86 | #' 87 | #' @param expr 88 | #' Expression to be evaluated on the client. 89 | #' 90 | #' @return 91 | #' Returns \code{TRUE} invisibly on successful exit. 92 | #' 93 | #' @export 94 | evalc <- function(expr) 95 | { 96 | ### TODO basically everything 97 | if (iam("local")) 98 | print(eval(expr=expr)) 99 | 100 | return(invisible(TRUE)) 101 | } 102 | -------------------------------------------------------------------------------- /R/utils.r: -------------------------------------------------------------------------------- 1 | test_connection <- function(addr, port, ntries=10, sleeptime=1) 2 | { 3 | ctx <- pbdZMQ::init.context() 4 | socket <- pbdZMQ::init.socket(ctx, "ZMQ_REQ") 5 | addr <- pbdZMQ::address(addr, port) 6 | 7 | 8 | for (i in 1:ntries) 9 | { 10 | test <- tryCatch( 11 | pbdZMQ::connect.socket(socket, addr), 12 | error=identity, warning=identity, message=identity 13 | ) 14 | 15 | if (inherits(test, "simpleWarning")) 16 | Sys.sleep(sleeptime) 17 | else 18 | break 19 | } 20 | 21 | rm(socket) 22 | rm(ctx) 23 | invisible(gc()) 24 | 25 | if (inherits(test, "simpleWarning")) 26 | stop( 27 | "Unable to connect to remote address. Make sure that 28 | * the server is running and able to accept connections (e.g. forwarding ports), 29 | * the port argument is correct, 30 | * the remote address is correct.") 31 | 32 | invisible(TRUE) 33 | } 34 | 35 | 36 | 37 | validate_address <- function(addr) 38 | { 39 | check.is.string(addr) 40 | 41 | if (grepl(addr, pattern="^.*://")) 42 | stop("Remote address should not include a protocol.") 43 | else if (grepl(addr, pattern=":")) 44 | stop("Remote address should not include ports.") 45 | 46 | addr 47 | } 48 | 49 | 50 | 51 | scrub_addr <- function(addr) 52 | { 53 | if (grepl(addr, pattern="/$")) 54 | addr <- substr(addr, 1L, nchar(addr)-1L) 55 | 56 | addr 57 | } 58 | 59 | 60 | 61 | validate_port <- function(port, warn=FALSE) 62 | { 63 | check.is.posint(port) 64 | check(port > 1023 && port < 65536) 65 | 66 | if (port != 0 && port < 49152 && warn) 67 | cat("WARNING: You are strongly encouraged to use port values between 49152 and 65536. See '?pbdZMQ::random_port' for details.") 68 | 69 | invisible(TRUE) 70 | } 71 | 72 | 73 | 74 | get_versions <- function() 75 | { 76 | pkgs <- c("pbdZMQ", "remoter") 77 | ret <- lapply(pkgs, packageVersion) 78 | names(ret) <- pkgs 79 | 80 | ret 81 | } 82 | 83 | 84 | 85 | compare_versions <- function(client, server) 86 | { 87 | if (client$pbdZMQ < server$pbdZMQ) 88 | return(FALSE) 89 | if (client$remoter < server$remoter) 90 | return(FALSE) 91 | 92 | TRUE 93 | } 94 | 95 | 96 | 97 | assert_nostop <- function(..., env = parent.frame()) 98 | { 99 | test <- tryCatch(check(...), error=identity) 100 | if (!is.logical(test)) 101 | { 102 | if (iam("local") || getval(debug)) 103 | { 104 | msg <- gsub(test, pattern="(^$|Error: )", replacement="") 105 | remoter_client_stop(msg) 106 | } 107 | 108 | return(FALSE) 109 | } 110 | else 111 | TRUE 112 | } 113 | 114 | 115 | 116 | isFALSE <- function(x) 117 | { 118 | identical(FALSE, x) 119 | } 120 | 121 | 122 | 123 | isWindows <- function() 124 | { 125 | tolower(.Platform$OS.type) == "windows" 126 | } 127 | 128 | 129 | 130 | isRStudio <- function() 131 | { 132 | tolower(.Platform$GUI) == "rstudio" 133 | } 134 | 135 | 136 | 137 | get_hostname = function() 138 | { 139 | Sys.info()[["nodename"]] 140 | } 141 | -------------------------------------------------------------------------------- /R/reactor.r: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2017 by Drew Schmidt 2 | # 3 | # Permission to use, copy, modify, and/or distribute this software for any 4 | # purpose with or without fee is hereby granted. 5 | # 6 | # THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH 7 | # REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY 8 | # AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, 9 | # INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM 10 | # LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR 11 | # OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR 12 | # PERFORMANCE OF THIS SOFTWARE 13 | 14 | 15 | is.badval <- function(x) 16 | { 17 | is.na(x) || is.nan(x) || is.infinite(x) 18 | } 19 | 20 | is.inty <- function(x) 21 | { 22 | abs(x - round(x)) < 1e-10 23 | } 24 | 25 | is.zero <- function(x) 26 | { 27 | abs(x) < 1e-10 28 | } 29 | 30 | is.negative <- function(x) 31 | { 32 | x < 0 33 | } 34 | 35 | is.annoying <- function(x) 36 | { 37 | length(x) != 1 || is.badval(x) 38 | } 39 | 40 | is.string <- function(x) 41 | { 42 | is.character(x) && !is.annoying(x) 43 | } 44 | 45 | is.flag <- function(x) 46 | { 47 | is.logical(x) && !is.annoying(x) 48 | } 49 | 50 | 51 | 52 | check.is.string <- function(x) 53 | { 54 | if (!is.string(x)) 55 | { 56 | nm <- deparse(substitute(x)) 57 | stop(paste0("argument '", nm, "' must be a single string"), call.=FALSE) 58 | } 59 | 60 | invisible(TRUE) 61 | } 62 | 63 | check.is.string.or.null <- function(x) 64 | { 65 | if (!is.null(x)) 66 | check.is.string(x); 67 | 68 | invisible(TRUE) 69 | } 70 | 71 | check.is.int <- function(x) 72 | { 73 | if (!is.numeric(x) || is.annoying(x) || !is.inty(x)) 74 | { 75 | nm <- deparse(substitute(x)) 76 | stop(paste0("argument '", nm, "' must be an integer"), call.=FALSE) 77 | } 78 | 79 | invisible(TRUE) 80 | } 81 | 82 | check.is.natnum <- function(x) 83 | { 84 | if (!is.numeric(x) || is.annoying(x) || !is.inty(x) || is.negative(x)) 85 | { 86 | nm <- deparse(substitute(x)) 87 | stop(paste0("argument '", nm, "' must be a natural number (0 or positive integer)"), call.=FALSE) 88 | } 89 | 90 | invisible(TRUE) 91 | } 92 | 93 | check.is.posint <- function(x) 94 | { 95 | if (!is.numeric(x) || is.annoying(x) || !is.inty(x) || is.negative(x) || is.zero(x)) 96 | { 97 | nm <- deparse(substitute(x)) 98 | stop(paste0("argument '", nm, "' must be a positive integer"), call.=FALSE) 99 | } 100 | 101 | invisible(TRUE) 102 | } 103 | 104 | check.is.flag <- function(x) 105 | { 106 | if (!is.flag(x)) 107 | { 108 | nm <- deparse(substitute(x)) 109 | stop(paste0("argument '", nm, "' must be TRUE or FALSE"), call.=FALSE) 110 | } 111 | 112 | invisible(TRUE) 113 | } 114 | 115 | 116 | 117 | check <- function(x) 118 | { 119 | if (!isTRUE(x)) 120 | { 121 | nm <- deparse(substitute(x)) 122 | stop(paste0("'", nm, "' is not TRUE"), call.=FALSE) 123 | } 124 | 125 | invisible(TRUE) 126 | } 127 | -------------------------------------------------------------------------------- /R/s2c.r: -------------------------------------------------------------------------------- 1 | #' Server-to-Client Object Transfer 2 | #' 3 | #' Localize R objects. 4 | #' 5 | #' @description 6 | #' This function allows you to pass an object from the server to 7 | #' the local R session behind the client. 8 | #' 9 | #' @details 10 | #' A \code{newname}, if specified, must be passed as a string 11 | #' (not a literal; i.e., \code{"mynewname"}, not \code{mynewname}). 12 | #' The name must also be syntactically valid (see \code{?make.names}). 13 | #' 14 | #' @param object 15 | #' A remote R object. 16 | #' @param newname 17 | #' The name the object should take when it is stored on the local 18 | #' client's R session. Must be the form of a character string. 19 | #' If left blank, the local name will be the same as the original 20 | #' (remote) object's name. 21 | #' @param env 22 | #' The environment into which the assignment will take place. The 23 | #' default is the global environment. 24 | #' 25 | #' @return 26 | #' Returns \code{TRUE} invisibly on successful exit. 27 | #' 28 | #' @examples 29 | #' \dontrun{ 30 | #' ### Prompts are listed to clarify when something is eval'd locally vs remotely 31 | #' > library(remoter) 32 | #' > y 33 | #' ### Error: object 'y' not found 34 | #' > remoter::connect("my.remote.server") 35 | #' remoter> x 36 | #' ### Error: object 'x' not found 37 | #' remoter> x <- "some data" 38 | #' remoter> x 39 | #' ### [1] "some data" 40 | #' remoter> s2c(x, "y") 41 | #' remoter> q() 42 | #' > y 43 | #' ### [1] "some data" 44 | #' } 45 | #' 46 | #' @export 47 | s2c <- function(object, newname, env=.GlobalEnv) 48 | { 49 | if (missing(object)) 50 | { 51 | if (iam("local")) 52 | remoter_client_stop("must pass an object") 53 | 54 | return(invisible()) 55 | } 56 | 57 | test <- tryCatch(is.environment(env), error=identity) 58 | if (isFALSE(test) || inherits(test, "error")) 59 | { 60 | if (iam("local")) 61 | { 62 | if (isFALSE(test)) 63 | remoter_client_stop("invalid environment") 64 | else 65 | remoter_client_stop(gsub(test, pattern="(.*: |\\n)", replacement="")) 66 | } 67 | 68 | return(invisible()) 69 | } 70 | 71 | if (!missing(newname)) 72 | { 73 | # test if 'newname' is a string or literal 74 | test <- try(is.character(newname), silent=TRUE) 75 | if (inherits(test, "try-error") || !test || !identical(make.names(newname), newname)) 76 | { 77 | if (iam("local")) 78 | remoter_client_stop("invalid 'newname'") 79 | 80 | return(invisible()) 81 | } 82 | } 83 | 84 | 85 | name <- as.character(substitute(object)) 86 | err <- ".__remoter_s2c_failure" 87 | 88 | if (iam("local")) 89 | { 90 | value <- remoter_receive() 91 | 92 | if (identical(value, err)) 93 | { 94 | cat(paste0("Error: object '", name, "' not found on the server\n")) 95 | return(invisible(FALSE)) 96 | } 97 | 98 | if (!missing(newname)) 99 | name <- newname 100 | 101 | assign(x=name, value=value, envir=env) 102 | } 103 | else if (iam("remote")) 104 | { 105 | val <- get0(name, envir=sys.frame(-1), ifnotfound=err) 106 | remoter_send(data=val, send.more=TRUE) 107 | } 108 | 109 | return(invisible(TRUE)) 110 | } 111 | -------------------------------------------------------------------------------- /R/state.r: -------------------------------------------------------------------------------- 1 | ### NOTE: all explicit references to .pbdenv should occure only in this file. 2 | ### To reference/modify state data, use the helper functions defined below. 3 | 4 | magicmsg_first_connection <- ".__remoter_first_connection" 5 | 6 | 7 | init_state <- function(envir = .GlobalEnv) 8 | { 9 | if (!exists(".pbdenv", envir = envir)) 10 | envir$.pbdenv <- new.env() 11 | 12 | reset_state() 13 | 14 | invisible() 15 | } 16 | 17 | 18 | 19 | reset_state <- function() 20 | { 21 | # options 22 | set(prompt, "remoter") 23 | set(timer, FALSE) 24 | set(port, 55555) 25 | set(remote_addr, "localhost") 26 | set(password, NULL) 27 | set(clientpw, NULL) 28 | set(maxattempts, 5) 29 | set(isbatch, FALSE) 30 | set(serialversion, NULL) 31 | 32 | # logs 33 | set(logfile, NULL) 34 | set(serverlog, TRUE) 35 | set(verbose, FALSE) 36 | set(showmsg, FALSE) 37 | 38 | # internals 39 | set(debug, FALSE) 40 | set(context, NULL) 41 | set(socket, NULL) 42 | set(client_lasterror, "") 43 | 44 | # Exiting stuff 45 | set(client_called_exit, FALSE) 46 | set(client_called_shutdown, FALSE) 47 | set(kill_interactive_server, TRUE) 48 | 49 | 50 | # Crypto 51 | # .pbdenv$withsodium <- FALSE 52 | set(secure, FALSE) 53 | # .pbdenv$keys$private <- NULL 54 | # .pbdenv$keys$public <- NULL 55 | .pbdenv$keys$theirs <- NULL 56 | 57 | 58 | # Track assignments 59 | # set(objs, character(0)) 60 | set(sync, FALSE) 61 | set(objs_nm, new.env()) 62 | set(objs, new.env()) 63 | 64 | 65 | # C/S state 66 | .pbdenv$status <- list( 67 | ret = invisible(), 68 | ret_addition = invisible(), 69 | visible = FALSE, 70 | lasterror = NULL, 71 | shouldwarn = FALSE, 72 | num_warnings = 0, 73 | warnings = NULL, 74 | prompt_active = FALSE, 75 | should_exit = FALSE, 76 | continuation = FALSE, 77 | need_auto_rpng_off = FALSE, 78 | need_auto_rhelp_on = FALSE, 79 | remote_objs = NULL, 80 | method_plot_rpng = "browseURL" # or "rasterImage" 81 | ) 82 | 83 | invisible() 84 | } 85 | 86 | 87 | 88 | ### just a pinch of sugar 89 | set <- function(var, val) 90 | { 91 | name <- as.character(substitute(var)) 92 | .pbdenv[[name]] <- val 93 | invisible() 94 | } 95 | 96 | getval <- function(var) 97 | { 98 | name <- as.character(substitute(var)) 99 | .pbdenv[[name]] 100 | } 101 | 102 | get.status <- function(var) 103 | { 104 | name <- as.character(substitute(var)) 105 | .pbdenv$status[[name]] 106 | } 107 | 108 | set.status <- function(var, val) 109 | { 110 | name <- as.character(substitute(var)) 111 | .pbdenv$status[[name]] <- val 112 | invisible() 113 | } 114 | 115 | getkey <- function(type) 116 | { 117 | name <- as.character(substitute(type)) 118 | stopifnot(name == "private" || name == "public" || name == "theirs") 119 | .pbdenv$keys[[name]] 120 | } 121 | 122 | setkey <- function(var, val) 123 | { 124 | name <- as.character(substitute(var)) 125 | .pbdenv$keys[[name]] <- val 126 | invisible() 127 | } 128 | 129 | iam <- function(name) 130 | { 131 | .pbdenv$whoami == name 132 | } 133 | 134 | inwhileloop <- function(name) 135 | { 136 | ### Check if in the client/server while(TRUE) loops. 137 | all.calls <- base::sys.calls() 138 | match.call <- paste0("^(\\s+)?remoter_repl_", name, "\\(") 139 | check <- grepl(x=all.calls, pattern=match.call, perl=TRUE) 140 | any(check) 141 | } 142 | -------------------------------------------------------------------------------- /vignettes/include/remoter.bib: -------------------------------------------------------------------------------- 1 | @MISC{pbdR2012, 2 | author = {Ostrouchov, G. and Chen, W.-C. and Schmidt, D. and Patel, P.}, 3 | title = {{Programming with Big Data in R}}, 4 | year = {2012}, 5 | url = {http://r-pbd.org/} 6 | } 7 | 8 | @Manual{pbdCS, 9 | title = {{pbdCS: 'pbdR' Client/Server Utilities}}, 10 | author = {Drew Schmidt and Wei-Chen Chen}, 11 | note = {R package version 0.1-0}, 12 | year = {2015}, 13 | url = {https://github.com/RBigData/pbdCS} 14 | } 15 | 16 | @Manual{remoter, 17 | title = {{remoter: Remote R: Control a Remote R Session from a Local One}}, 18 | author = {Drew Schmidt and Wei-Chen Chen}, 19 | note = {R package version 0.1-1}, 20 | year = {2015}, 21 | url = {https://github.com/wrathematics/remoter} 22 | } 23 | 24 | @Misc{Chen2015pbdZMQpackage, 25 | title = {{pbdZMQ: Programming with Big Data -- Interface to ZeroMQ}}, 26 | author = {Wei-Chen Chen and Drew Schmidt}, 27 | year = {2015}, 28 | note = {{R} Package, URL http://cran.r-project.org/package=pbdZMQ}, 29 | } 30 | 31 | @Manual{Chen2015pbdZMQvignette, 32 | title = {{A Quick Guide for the {pbdZMQ} Package (Ver. 0.1-0)}}, 33 | author = {Wei-Chen Chen and Drew Schmidt}, 34 | year = {2015}, 35 | note = {{R} Vignette, URL http://cran.r-project.org/package=pbdZMQ}, 36 | } 37 | 38 | @MISC{zeromq, 39 | author = {Hintjens, P.}, 40 | title = {The ZeroMQ Guide -- for C Developers}, 41 | year = {2013}, 42 | note = {}, 43 | url = {http://zguide.zeromq.org/page:all} 44 | } 45 | 46 | @Manual{sodium, 47 | title = {sodium: A Modern and Easy-to-Use Crypto Library}, 48 | author = {Jeroen Ooms}, 49 | year = {2015}, 50 | note = {R package version 0.2}, 51 | url = {https://CRAN.R-project.org/package=sodium}, 52 | } 53 | 54 | @MISC{libsodium, 55 | author = {}, 56 | title = {libsodium}, 57 | year = {2015}, 58 | note = {}, 59 | url = {https://github.com/jedisct1/libsodium} 60 | } 61 | 62 | @Misc{Chen2017pbdRPCpackage, 63 | title = {{pbdRPC}: Programming with Big Data -- Remote Procedure Call}, 64 | author = {Wei-Chen Chen}, 65 | year = {2017}, 66 | note = {{R} Package, URL https://cran.r-project.org/package=pbdMPI}, 67 | } 68 | 69 | @Manual{Chen2017pbdRPCvignette, 70 | title = {A Quick Guide for the {pbdRPC} Package}, 71 | author = {Wei-Chen Chen}, 72 | year = {2017}, 73 | note = {{R} Vignette, URL https://cran.r-project.org/package=pbdRPC}, 74 | } 75 | 76 | @Misc{argon2, 77 | title = {{argon2}: Secure Password Hashing}, 78 | author = {Drew Schmidt}, 79 | year = {2017}, 80 | note = {{R} package version 0.2-0}, 81 | url = {https://cran.r-project.org/package=argon2}, 82 | } 83 | 84 | @Misc{getPassPackage, 85 | title = {{getPass}: Masked User Input}, 86 | author = {Drew Schmidt and Wei-Chen Chen}, 87 | note = {{R} package version 0.2-1}, 88 | url = {https://cran.r-project.org/package=getPass}, 89 | year = {2017} 90 | } 91 | 92 | @Manual{getPassVignette, 93 | title = {Guide to the {getPass} Package}, 94 | author = {Drew Schmidt}, 95 | note = {{R} Vignette}, 96 | url = {https://cran.r-project.org/package=getPass}, 97 | year = {2017} 98 | } 99 | 100 | @Manual{remoterguide, 101 | title = {Guide to the {remoter} Package}, 102 | author = {Drew Schmidt}, 103 | note = {{R} Vignette}, 104 | url = {https://cran.r-project.org/package=remoter}, 105 | year = {2017} 106 | } 107 | 108 | @Manual{remotemachineguide, 109 | title = {Using {remoter} with Remote Machines}, 110 | author = {Drew Schmidt}, 111 | note = {{R} Vignette}, 112 | url = {https://cran.r-project.org/package=remoter}, 113 | year = {2017} 114 | } 115 | -------------------------------------------------------------------------------- /ChangeLog: -------------------------------------------------------------------------------- 1 | Release 0.5-0: 2 | * Changed server logging behavior - replaced argument log with logfile in server(). 3 | * Rollback tests. 4 | * Added flush.console() to push output buffer to osx and windows console. 5 | * Fixed no quit server when set to FALSE. 6 | * Tweaked logging. 7 | * Fixed error when batch() is used on a whitespace line (thanks Panagiotis Cheilaris). 8 | * Added sendfile() and recvfile(). 9 | * Fixed ggplot2 breaks server(). 10 | * Add more ways to check plotting with rpng and test ggplot2 errors. 11 | 12 | Release 0.4-0: 13 | * Fixed warning precedence for small port values. 14 | * Fixed client timer printing. 15 | * Added rDevices functions. 16 | * Added rpng() and its utility functions. 17 | * Added rhelp(), custom `?`, and its utility functions. 18 | * Added hotkey. 19 | * Added shutdown(). 20 | * Fixed Windows encoding problem. 21 | * Added option to kill client when 3 consecutive C-c's detected. 22 | * Fixed eternal warning print bug. 23 | * Fixed warning reset bug with options(warn = 1). 24 | * Added batch(). 25 | * Fixed s2c() newname bug that would drop the client. 26 | * Fixed log restarts on client connect. 27 | * Added kill(). 28 | * Diverted R message (warning, error, stop) to client. 29 | * Added wiping out password when server is down. 30 | * Added environment syncing. 31 | * Switched to LaTeX vignettes. 32 | * Added remote machines vignette. 33 | * Dropped assertthat for reactor. 34 | * Fixed multiline string input bug. 35 | * Use argon2 for password hashing. 36 | * Overhauled vignettes. 37 | * Display port and internal/external ip's on server creation via getip. 38 | * Fix some NULL and image file calls when dev.off() and auto_off are needed. 39 | 40 | Release 0.3-2: 41 | * Minor internal cleanup. 42 | * Improved timer behavior. 43 | * Fixed s2c() bug. 44 | * Fixed bug that affected attaching some package namespaces (WCC). 45 | * Fixed remoter namespace attach bug within the server. 46 | 47 | Release 0.3-1: 48 | * Added "timer" option to client. 49 | * Added "middleman" functionality via relay(). 50 | * Added blurb about relays to the vignette. 51 | * Moved password method with masking to getPass package. 52 | * Integrated getPass() into client authentication. 53 | * Explicitly namespaced internal use of pbdZMQ functions. 54 | 55 | Release 0.3-0: 56 | * Dumped log output to file. 57 | * Added showlog(). 58 | * Added "q.server" argument to exit() (WCC). 59 | * Complete refactoring of internals. 60 | * Better state management/abstraction. 61 | * Better clarity in client/server REPL's. 62 | * Significant improvements to error checking in s2c() and c2s(). 63 | 64 | Release 0.2-1: 65 | * Made encryption/secure servers/sodium optional. 66 | * Added note about ssh tunneling to vignette. 67 | * Significantly enhanced logger. 68 | * Fixed C-c client bug. 69 | 70 | Release 0.2-0: 71 | * Added encrypted communications via sodium. 72 | * Large rewrite of internal state system. 73 | * Vignette rewrite. 74 | * Added basic logger for server. 75 | * Disallowed privileged ports. 76 | * Better documentation and help. 77 | 78 | Release 0.1-2: 79 | * Changed param "remote_addr" to "addr" in client(). 80 | * Minor improvements to the repl. 81 | * Better error checking and handling for addresses. 82 | 83 | Release 0.1-1: 84 | * Added some remote checks, address validation in client. 85 | * Changed behavior of exit(); added "client.only" option. 86 | * Rearranged internals. 87 | * Added vignette. 88 | * Switched to pbdZMQ 0.2-1. 89 | 90 | Release 0.1-0: 91 | * Added s2c() and c2s(). 92 | * Added lsc(), rmc(), and evalc(). 93 | * Imported functionality from pbdCS. 94 | -------------------------------------------------------------------------------- /R/wcc_client_device.r: -------------------------------------------------------------------------------- 1 | #' Local Graphic Devices 2 | #' 3 | #' Local Graphic Device Controlling Functions 4 | #' 5 | #' @description 6 | #' 7 | #' Functions for controlling graphic device locally when the client of 8 | #' remote R is on. All these functions are evaluated in local R from within 9 | #' the remote R prompt. 10 | #' 11 | #' \code{dev.curc()} locally evals \code{grDevices::dev.cur()}. 12 | #' 13 | #' \code{dev.listc()} locally evals \code{grDevices::dev.list()}. 14 | #' 15 | #' \code{dev.nextc()} locally evals \code{grDevices::dev.next()}. 16 | #' 17 | #' \code{dev.prevc()} locally evals \code{grDevices::dev.prev()}. 18 | #' 19 | #' \code{dev.offc()} locally evals \code{grDevices::dev.off()}. 20 | #' 21 | #' \code{dev.setc()} locally evals \code{grDevices::dev.set()}. 22 | #' 23 | #' \code{dev.newc()} locally eval \code{grDevices::dev.new()}. 24 | #' 25 | #' \code{dev.sizec()} locally evals \code{grDevices::dev.size()}. 26 | #' 27 | #' 28 | #' @param which 29 | #' An integer specifying a device number as in \code{grDevices::dev.off()} 30 | #' @param ... 31 | #' arguments to be passed to the device selected as in 32 | #' \code{grDevices::dev.new()} 33 | #' @param noRstudioGD 34 | #' as in \code{grDevices::dev.new()} 35 | #' @param units 36 | #' as in \code{grDevices::dev.size()} 37 | #' 38 | #' @seealso \code{\link{rpng}()} 39 | #' 40 | #' @examples 41 | #' \dontrun{ 42 | #' ### Prompts are listed to clarify when something is eval'd locally vs 43 | #' ### remotely 44 | #' > library(remoter, quietly = TRUE) 45 | #' > client() 46 | #' 47 | #' remoter> rpng.new(plot(1:5)) 48 | #' remoter> dev.newc(width = 6, height = 4) 49 | #' remoter> a <- function() plot(iris$Sepal.Length, iris$Petal.Length) 50 | #' remoter> rpng.new(a, width = 6 * 72, height = 4 * 72) 51 | #' 52 | #' remoter> dev.curc() 53 | #' remoter> dev.listc() 54 | #' remoter> dev.offc() 55 | #' 56 | #' remoter> q() 57 | #' > 58 | #' } 59 | #' 60 | #' @rdname rDevices 61 | #' @name rDevices 62 | NULL 63 | 64 | 65 | 66 | #' @rdname rDevices 67 | #' @export 68 | dev.curc <- function() 69 | { 70 | evalc(grDevices::dev.cur()) 71 | } 72 | 73 | 74 | 75 | #' @rdname rDevices 76 | #' @export 77 | dev.listc <- function() 78 | { 79 | evalc(grDevices::dev.list()) 80 | } 81 | 82 | 83 | 84 | #' @rdname rDevices 85 | #' @export 86 | dev.nextc <- function(which = grDevices::dev.cur()) 87 | { 88 | evalc(grDevices::dev.next(which = which)) 89 | } 90 | 91 | 92 | 93 | #' @rdname rDevices 94 | #' @export 95 | dev.prevc <- function(which = grDevices::dev.cur()) 96 | { 97 | evalc(grDevices::dev.prev(which = which)) 98 | } 99 | 100 | 101 | 102 | #' @rdname rDevices 103 | #' @export 104 | dev.offc <- function(which = grDevices::dev.cur()) 105 | { 106 | if(iam("local")) 107 | tryCatch(grDevices::dev.off(which = which)) 108 | } 109 | 110 | 111 | 112 | #' @rdname rDevices 113 | #' @export 114 | dev.setc <- function(which = grDevices::dev.cur()) 115 | { 116 | evalc(grDevices::dev.set(which = which)) 117 | } 118 | 119 | 120 | 121 | #' @rdname rDevices 122 | #' @export 123 | dev.newc <- function(..., noRstudioGD = FALSE) 124 | { 125 | if(iam("local")) 126 | tryCatch(grDevices::dev.new(..., noRstudioGD = noRstudioGD)) 127 | } 128 | 129 | 130 | 131 | #' @rdname rDevices 132 | #' @export 133 | dev.sizec <- function(units = c("in", "cm", "px")) 134 | { 135 | evalc(grDevices::dev.size(units = units)) 136 | } 137 | 138 | 139 | 140 | ### For windows only? 141 | # bringToTopc <- function(which = grDevices::dev.cur(), stay = FALSE){ 142 | # if(iam("local")){ 143 | # tryCatch(grDevices::bringToTop(which = which, stay = stay)) 144 | # } 145 | # } 146 | 147 | -------------------------------------------------------------------------------- /R/checks.r: -------------------------------------------------------------------------------- 1 | remoter_check_password_local <- function() 2 | { 3 | first_send() 4 | needpw <- remoter_receive() 5 | 6 | while (needpw) 7 | { 8 | if (!is.null(getval(clientpw))) 9 | { 10 | pw <- getval(clientpw) 11 | set(clientpw, NULL) 12 | } 13 | else 14 | pw <- getPass::getPass() 15 | 16 | if (is.null(pw)) # C-c 17 | { 18 | remoter_send(NULL) 19 | remoter_receive() 20 | return(FALSE) 21 | } 22 | 23 | remoter_send(pw) 24 | check <- remoter_receive() 25 | 26 | if (isTRUE(check)) 27 | break 28 | else if (is.null(check)) 29 | stop("Max attempts reached; killing server...") 30 | 31 | cat("Sorry, try again.\n") 32 | } 33 | 34 | TRUE 35 | } 36 | 37 | 38 | 39 | remoter_check_password_remote <- function() 40 | { 41 | ### Initial a dummy in case receive nothing. 42 | pw <- "" 43 | 44 | if (is.null(getval(password))) 45 | { 46 | logprint(level="PASS", "alerting client no password required", checkverbose=TRUE) 47 | remoter_send(FALSE) 48 | } 49 | else 50 | { 51 | logprint("client attempting to connect...") 52 | logprint(level="PASS", "alerting client a password is required", checkverbose=TRUE) 53 | remoter_send(TRUE) 54 | 55 | attempts <- 2L 56 | while (TRUE) 57 | { 58 | logprint(level="PASS", "receiving password attempt", checkverbose=TRUE) 59 | pw <- remoter_receive() 60 | if (is.null(pw)) 61 | { 62 | logprint(level="PASS", "client disconnected", checkverbose=TRUE) 63 | remoter_send(NULL) 64 | return(FALSE) 65 | } 66 | 67 | if (pwcheck(pw)) 68 | { 69 | logprint("client password authenticated") 70 | remoter_send(TRUE) 71 | break 72 | } 73 | else if (attempts <= getval(maxattempts)) 74 | { 75 | logprint(level="PASS", "received bad password", checkverbose=TRUE) 76 | remoter_send(FALSE) 77 | } 78 | else 79 | { 80 | logprint(level="PASS", "alert client max password attempts reached", checkverbose=TRUE) 81 | remoter_send(NULL) 82 | logprint(paste0("received maxretry=", getval(maxattempts), " bad passwords; terminating self...")) 83 | if (getval(kill_interactive_server)) 84 | q("no") 85 | else 86 | stop("Max password attempts reached.") 87 | } 88 | 89 | attempts <- attempts + 1L 90 | } 91 | } 92 | 93 | # FIXME 94 | ### Overwrite whatever it received or not before quit this function. 95 | # getPass::zerobuff(pw) 96 | 97 | TRUE 98 | } 99 | 100 | 101 | 102 | remoter_check_version_local <- function() 103 | { 104 | remoter_send(get_versions()) 105 | check <- remoter_receive() 106 | 107 | if (!check) 108 | stop("Incompatible package versions; quitting client (perhaps you need to update and restart the server?)") 109 | 110 | invisible(TRUE) 111 | } 112 | 113 | 114 | 115 | remoter_check_version_remote <- function() 116 | { 117 | logprint("VERS: checking client package versions", checkverbose=TRUE) 118 | versions_client <- remoter_receive() 119 | versions_server <- get_versions() 120 | check <- compare_versions(versions_client, versions_server) 121 | 122 | logprint(level="VERS", "send version check result to client", checkverbose=TRUE) 123 | remoter_send(check) 124 | 125 | if (check) 126 | { 127 | logprint(level="VERS", "client version passes version check", checkverbose=TRUE) 128 | logprint("client connected") 129 | } 130 | else 131 | logprint("client/server version mismatch; client kicked.") 132 | 133 | invisible(TRUE) 134 | } 135 | -------------------------------------------------------------------------------- /R/comms.r: -------------------------------------------------------------------------------- 1 | ### When a client first connects: 2 | # * client sends magicmsg_first_connection 3 | # * server sends value of "secure" stored in the state (TRUE or FALSE) 4 | # * client either responds "" or hangs up if it can't continue 5 | # * key exchange if necessary 6 | # * next operation is to check if server needs a password... 7 | 8 | ### Comm pattern 9 | # * pattern is req/rep 10 | # * client always goes send/recv (unless send.more=TRUE) 11 | # * server always goes recv/send 12 | 13 | send_unsecure <- function(data, send.more=FALSE) 14 | { 15 | serialversion = getval(serialversion) 16 | if (is.null(serialversion)) 17 | pbdZMQ::send.socket(getval(socket), data=data, send.more=send.more) 18 | else 19 | pbdZMQ::send.socket(getval(socket), data=data, send.more=send.more, 20 | serialversion=serialversion) 21 | } 22 | 23 | 24 | 25 | send_secure <- function(data, send.more=FALSE) 26 | { 27 | serialized <- serialize(data, NULL, version=getval(serialversion)) 28 | encrypted <- sodium::auth_encrypt(serialized, getkey(private), getkey(theirs)) 29 | send_unsecure(data=encrypted, send.more=send.more) 30 | } 31 | 32 | 33 | 34 | receive_unsecure <- function() 35 | { 36 | msg <- pbdZMQ::receive.socket(getval(socket)) 37 | 38 | if (identical(msg, magicmsg_first_connection)) 39 | { 40 | first_receive() 41 | return(magicmsg_first_connection) 42 | } 43 | 44 | msg 45 | } 46 | 47 | 48 | 49 | receive_secure <- function() 50 | { 51 | encrypted <- pbdZMQ::receive.socket(getval(socket)) 52 | 53 | if (identical(encrypted, magicmsg_first_connection)) 54 | { 55 | first_receive() 56 | return(magicmsg_first_connection) 57 | } 58 | 59 | raw <- sodium::auth_decrypt(encrypted, getkey(private), getkey(theirs)) 60 | unserialize(raw) 61 | } 62 | 63 | 64 | 65 | remoter_send <- function(data, send.more=FALSE) 66 | { 67 | if (getval(secure)) 68 | send_secure(data=data, send.more=send.more) 69 | else 70 | send_unsecure(data=data, send.more=send.more) 71 | } 72 | 73 | 74 | 75 | remoter_receive <- function() 76 | { 77 | if (getval(secure)) 78 | receive_secure() 79 | else 80 | receive_unsecure() 81 | } 82 | 83 | 84 | 85 | first_send <- function() 86 | { 87 | send_unsecure(magicmsg_first_connection) 88 | security <- receive_unsecure() 89 | 90 | if (security && !has.sodium()) 91 | stop("remoter server communications are encrypted but the 'sodium' package is not detected on the client. Please install the 'sodium' package, or start an unsecure server.") 92 | else if (!security && has.sodium()) 93 | cat("WARNING: server not secure; communications are not encrypted.\n") 94 | 95 | set(secure, security) 96 | 97 | if (getval(secure)) 98 | { 99 | send_unsecure(NULL) 100 | setkey(theirs, receive_unsecure()) 101 | send_unsecure(getkey(public)) 102 | } 103 | else 104 | send_unsecure(NULL) 105 | 106 | invisible() 107 | } 108 | 109 | 110 | 111 | first_receive <- function() 112 | { 113 | logprint(level="INIT", "Receiving first connection from client...", checkverbose=TRUE) 114 | logprint(level="INIT", paste("alerting that server", ifelse(getval(secure), "is", "isn't"), "secure"), checkverbose=TRUE) 115 | send_unsecure(getval(secure)) 116 | 117 | logprint(level="INIT", "receiving security acknowledgement from client", checkverbose=TRUE) 118 | if (getval(secure)) 119 | { 120 | receive_unsecure() 121 | logprint(level="AUTH", "sending server public key", checkverbose=TRUE) 122 | send_unsecure(getkey(public)) 123 | logprint(level="AUTH", "receiving client public key", checkverbose=TRUE) 124 | setkey(theirs, receive_unsecure()) 125 | } 126 | else 127 | receive_unsecure() 128 | 129 | invisible() 130 | } 131 | -------------------------------------------------------------------------------- /man/rDevices_rpng.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/wcc_rpng.r, R/wcc_rpng_utils.r 3 | \name{rpng} 4 | \alias{rpng} 5 | \alias{rpng.new} 6 | \alias{rpng.off} 7 | \alias{dev.off} 8 | \title{rpng} 9 | \usage{ 10 | rpng.new( 11 | expr, 12 | filename = NULL, 13 | width = 587, 14 | height = 586, 15 | units = "px", 16 | pointsize = 12, 17 | bg = "white", 18 | res = 96, 19 | ... 20 | ) 21 | 22 | rpng.off(which = grDevices::dev.cur()) 23 | 24 | dev.off(which = grDevices::dev.cur()) 25 | } 26 | \arguments{ 27 | \item{expr}{An expression or a function generating a plot. This checks in the 28 | following orders: expression or ggplot. The ggplot 29 | are eval'd within the \code{rpng.new()}, while the expression is 30 | eval'd at \code{parent.frame()}.} 31 | 32 | \item{filename}{A temporary file to save the plot on server} 33 | 34 | \item{width}{width of the plot as in \code{grDevices::png()}} 35 | 36 | \item{height}{height of the plot as in \code{grDevices::png()}} 37 | 38 | \item{units}{units of the width and height as in \code{grDevices::png()}} 39 | 40 | \item{pointsize}{pointsze of the plotted text as in \code{grDevices::png()}} 41 | 42 | \item{bg}{background colour as in \code{grDevices::png()}} 43 | 44 | \item{res}{resolution as in \code{grDevices::png()}} 45 | 46 | \item{...}{additional arguments as in \code{grDevices::png()}} 47 | 48 | \item{which}{An integer specifying a device number as in \code{grDevices::dev.off()}} 49 | } 50 | \description{ 51 | Provide a graphic device locally for plots generated on server of Remote R 52 | 53 | \code{rpng()} generates locally a device/window. 54 | 55 | \code{rpng.new()} generates locally a device/window. 56 | 57 | \code{rpng.off()} turns off locally a device/window. 58 | 59 | \code{dev.off()} is an alias of \code{rpng.off()} in order to consisten 60 | with th original device function \code{grDevices::dev.off()}. 61 | } 62 | \details{ 63 | Remote R PNG Device 64 | } 65 | \examples{ 66 | \dontrun{ 67 | ### Prompts are listed to clarify when something is eval'd locally vs 68 | ### remotely 69 | > # suppressMessages(library(remoter, quietly = TRUE)) 70 | > # client() 71 | > remoter::client("192.168.56.101") 72 | 73 | ### Regular R plotting 74 | remoter> plot(1:5) 75 | remoter> rpng.off() 76 | 77 | ### Manually open a remoting plotting device 78 | remoter> rpng() 79 | remoter> plot(iris$Sepal.Length, iris$Petal.Length) 80 | remoter> rpng.off() 81 | 82 | ### Work with ggplot2-like plotting 83 | remoter> library(ggplot2) 84 | remoter> g1 <- ggplot(iris, aes(x = Sepal.Length, y = Petal.Length, 85 | remoter+ color = Species)) + 86 | remoter+ geom_point(aes(shape = Species)) 87 | 88 | ### Method 1a 89 | remoter> rpng() 90 | remoter> print(g1) 91 | remoter> rpng.off() 92 | 93 | ### Method 1b 94 | remoter> rpng() 95 | remoter> print(g1 + geom_line()) 96 | remoter> rpng.off() 97 | 98 | ### Method 1c 99 | remoter> g1 + geom_smooth(method = "lm") 100 | 101 | ### Testing aside 102 | remoter> rpng.new(plot(1:5)) 103 | 104 | ### Method 1d 105 | remoter> rpng.new(g1) 106 | 107 | ### Testing again 108 | remoter> b <- function() plot(iris$Sepal.Length, iris$Petal.Length) 109 | remoter> rpng.new(b) 110 | 111 | ### Work with other plotting device 112 | remoter> da <- data.frame(x = rnorm(100), y = rnorm(100)) 113 | remoter> g2 <- ggplot(da, aes(x, y)) + geom_point() 114 | 115 | ### Save on the server 116 | remoter> pdf() 117 | remoter> g2 118 | remoter> print(g2 + geom_line()) 119 | remoter> dev.off() 120 | 121 | ### Testing for errors with ggplot 122 | remoter> g3 <- ggplot(da, aes(x, yy)) + geom_point() 123 | remoter> g3 124 | 125 | remoter> rpng() 126 | remoter> print(g3) 127 | remoter> rpng.off() 128 | 129 | ### Disconnect the server and keep it alive 130 | remoter> q() 131 | > 132 | } 133 | 134 | } 135 | \seealso{ 136 | \code{\link{rDevices}} 137 | } 138 | -------------------------------------------------------------------------------- /R/wcc_rpng.r: -------------------------------------------------------------------------------- 1 | #' rpng 2 | #' 3 | #' Remote R PNG Device 4 | #' 5 | #' @description 6 | #' Provide a graphic device locally for plots generated on server of Remote R 7 | #' 8 | #' \code{rpng()} generates locally a device/window. 9 | #' 10 | #' \code{rpng.new()} generates locally a device/window. 11 | #' 12 | #' \code{rpng.off()} turns off locally a device/window. 13 | #' 14 | #' \code{dev.off()} is an alias of \code{rpng.off()} in order to consisten 15 | #' with th original device function \code{grDevices::dev.off()}. 16 | #' 17 | #' @param filename 18 | #' A temporary file to save the plot on server 19 | #' @param width 20 | #' width of the plot as in \code{grDevices::png()} 21 | #' @param height 22 | #' height of the plot as in \code{grDevices::png()} 23 | #' @param units 24 | #' units of the width and height as in \code{grDevices::png()} 25 | #' @param pointsize 26 | #' pointsze of the plotted text as in \code{grDevices::png()} 27 | #' @param bg 28 | #' background colour as in \code{grDevices::png()} 29 | #' @param res 30 | #' resolution as in \code{grDevices::png()} 31 | #' @param ... 32 | #' additional arguments as in \code{grDevices::png()} 33 | #' 34 | #' 35 | #' @seealso \code{\link{rDevices}} 36 | #' 37 | #' @examples 38 | #' \dontrun{ 39 | #' ### Prompts are listed to clarify when something is eval'd locally vs 40 | #' ### remotely 41 | #' > # suppressMessages(library(remoter, quietly = TRUE)) 42 | #' > # client() 43 | #' > remoter::client("192.168.56.101") 44 | #' 45 | #' ### Regular R plotting 46 | #' remoter> plot(1:5) 47 | #' remoter> rpng.off() 48 | #' 49 | #' ### Manually open a remoting plotting device 50 | #' remoter> rpng() 51 | #' remoter> plot(iris$Sepal.Length, iris$Petal.Length) 52 | #' remoter> rpng.off() 53 | #' 54 | #' ### Work with ggplot2-like plotting 55 | #' remoter> library(ggplot2) 56 | #' remoter> g1 <- ggplot(iris, aes(x = Sepal.Length, y = Petal.Length, 57 | #' remoter+ color = Species)) + 58 | #' remoter+ geom_point(aes(shape = Species)) 59 | #' 60 | #' ### Method 1a 61 | #' remoter> rpng() 62 | #' remoter> print(g1) 63 | #' remoter> rpng.off() 64 | #' 65 | #' ### Method 1b 66 | #' remoter> rpng() 67 | #' remoter> print(g1 + geom_line()) 68 | #' remoter> rpng.off() 69 | #' 70 | #' ### Method 1c 71 | #' remoter> g1 + geom_smooth(method = "lm") 72 | #' 73 | #' ### Testing aside 74 | #' remoter> rpng.new(plot(1:5)) 75 | #' 76 | #' ### Method 1d 77 | #' remoter> rpng.new(g1) 78 | #' 79 | #' ### Testing again 80 | #' remoter> b <- function() plot(iris$Sepal.Length, iris$Petal.Length) 81 | #' remoter> rpng.new(b) 82 | #' 83 | #' ### Work with other plotting device 84 | #' remoter> da <- data.frame(x = rnorm(100), y = rnorm(100)) 85 | #' remoter> g2 <- ggplot(da, aes(x, y)) + geom_point() 86 | #' 87 | #' ### Save on the server 88 | #' remoter> pdf() 89 | #' remoter> g2 90 | #' remoter> print(g2 + geom_line()) 91 | #' remoter> dev.off() 92 | #' 93 | #' ### Testing for errors with ggplot 94 | #' remoter> g3 <- ggplot(da, aes(x, yy)) + geom_point() 95 | #' remoter> g3 96 | #' 97 | #' remoter> rpng() 98 | #' remoter> print(g3) 99 | #' remoter> rpng.off() 100 | #' 101 | #' ### Disconnect the server and keep it alive 102 | #' remoter> q() 103 | #' > 104 | #' } 105 | #' 106 | #' @rdname rDevices_rpng 107 | #' @name rpng 108 | NULL 109 | 110 | #' @export 111 | rpng <- function(filename = tempfile(fileext = "_r.png"), 112 | width = 587, height = 586, units = "px", pointsize = 12, 113 | bg = "white", res = 96, ...) 114 | { 115 | if (!is.character(filename)) 116 | stop("filename should be in character.") 117 | else 118 | { 119 | ### Use NULL to delay opening a local device automatically 120 | rpng.new(NULL, filename = filename, width = width, height = height, 121 | units = units, pointsize = pointsize, bg = bg, res = res, 122 | ...) 123 | ### Use rpng.off() to close the remoter graphic device and 124 | ### open the local device manually. 125 | } 126 | 127 | invisible() 128 | } 129 | 130 | -------------------------------------------------------------------------------- /R/batch.r: -------------------------------------------------------------------------------- 1 | #' Batch Execution 2 | #' 3 | #' Run a local script on a remote server in batch. Similar to R's own 4 | #' \code{source()} function. 5 | #' 6 | #' @details 7 | #' Note that \code{batch()} can not be run from inside an active connection. 8 | #' Its purpose is to bypass the need to start a connection via \code{client()} 9 | #' 10 | #' @param addr 11 | #' The remote host/address/endpoint. 12 | #' @param port 13 | #' The port (number) that will be used for communication between 14 | #' the client and server. The port value for the client and server 15 | #' must agree. 16 | #' @param password 17 | #' An initial password to pass to the server. If the server is not accepting 18 | #' passwords, then this argument is ignored. If the initial pasword is 19 | #' incorrect, then assuming the server's \code{maxretry>1}, then you will be 20 | #' interactively asked to enter the password. 21 | #' @param file 22 | #' A character string pointing to the file you wish to execute/source. Either 23 | #' this or \code{script} (but not both) should be provided. 24 | #' @param script 25 | #' A character string containing the commands you wish to execute/source. Either 26 | #' this or \code{file} (but not both) should be provided. 27 | #' @param timer 28 | #' Logical; should the "performance prompt", which shows timing 29 | #' statistics after every command, be used? 30 | #' @param serialversion 31 | #' NULL or numeric; the workspace format version to use when serializing. 32 | #' NULL specifies the current default version. The only other supported 33 | #' values are 2 and 3. 34 | #' 35 | #' @examples 36 | #' \dontrun{ 37 | #' library(remoter) 38 | #' ### NOTE first run a server via remoter::server() )in a separate R session. 39 | #' ### For simplicity, assume they are on the same machine. 40 | #' 41 | #' # Run a script in an R file on the local/client machine 42 | #' file <- "/path/to/an/R/script.r" 43 | #' batch(file=file) 44 | #' 45 | #' # Run a script stored in a character vector 46 | #' script <- "1+1" 47 | #' batch(script="1+1") 48 | #' } 49 | #' 50 | #' @return 51 | #' Returns \code{TRUE} invisibly on successful exit. 52 | #' 53 | #' @export 54 | batch <- function(addr="localhost", port=55555, password=NULL, file, script, 55 | timer=FALSE, serialversion=NULL) 56 | { 57 | check.is.flag(timer) 58 | check(is.null(serialversion) || is.inty(serialversion)) 59 | validate_address(addr) 60 | addr <- scrub_addr(addr) 61 | validate_port(port, warn=FALSE) 62 | 63 | if (missing(file) && missing(script)) 64 | stop("At least one of the arguments 'script' or 'file' should be provided") 65 | else if (missing(file)) 66 | { 67 | check.is.string(script) 68 | src <- unlist(strsplit(script, split="\n")) 69 | } 70 | else if (missing(script)) 71 | { 72 | check.is.string(file) 73 | check(file.exists(file)) 74 | src <- readLines(file) 75 | } 76 | else 77 | stop("Only one of the arguments 'script' or 'file' should be provided") 78 | 79 | test_connection(addr, port) 80 | 81 | reset_state() 82 | 83 | set(whoami, "local") 84 | set(timer, timer) 85 | set(port, port) 86 | set(remote_addr, addr) 87 | set(clientpw, password) 88 | set(serialversion, serialversion) 89 | 90 | set(isbatch, TRUE) 91 | 92 | remoter_repl_batch(src=src) 93 | 94 | invisible(TRUE) 95 | } 96 | 97 | 98 | 99 | remoter_repl_batch <- function(src, env=globalenv()) 100 | { 101 | test <- remoter_init_client() 102 | if (!test) return(FALSE) 103 | 104 | timer <- getval(timer) 105 | EVALFUN <- timerfun(timer) 106 | 107 | len <- length(src) 108 | line <- 1L 109 | 110 | while (TRUE) 111 | { 112 | input <- character(0) 113 | set.status(continuation, FALSE) 114 | set.status(visible, FALSE) 115 | 116 | while (TRUE) 117 | { 118 | if (line > len) 119 | break 120 | 121 | tmp <- src[line] 122 | 123 | if (gsub(tmp, pattern=" +", replacement="") == "") 124 | { 125 | line <- line + 1L 126 | next 127 | } 128 | 129 | input <- c(input, src[line]) 130 | 131 | timing <- EVALFUN({ 132 | remoter_client_sendrecv(input=input, env=env) 133 | }) 134 | 135 | if (get.status(continuation)) 136 | { 137 | line <- line + 1L 138 | next 139 | } 140 | 141 | if (timer) 142 | { 143 | cat("## ") 144 | cat(input) 145 | cat("\n") 146 | } 147 | 148 | remoter_repl_printer() 149 | 150 | timerprint(timer, timing) 151 | 152 | break 153 | } 154 | 155 | line <- line + 1L 156 | 157 | if (line > len) 158 | break 159 | } 160 | 161 | set.status(prompt_active, FALSE) 162 | set.status(should_exit, FALSE) 163 | 164 | return(invisible()) 165 | } 166 | -------------------------------------------------------------------------------- /R/wcc_rpng_utils.r: -------------------------------------------------------------------------------- 1 | #' @param expr 2 | #' An expression or a function generating a plot. This checks in the 3 | #' following orders: expression or ggplot. The ggplot 4 | #' are eval'd within the \code{rpng.new()}, while the expression is 5 | #' eval'd at \code{parent.frame()}. 6 | #' @rdname rDevices_rpng 7 | #' @export 8 | rpng.new <- function(expr, filename = NULL, 9 | width = 587, height = 586, units = "px", pointsize = 12, 10 | bg = "white", res = 96, ...) 11 | { 12 | if (is.null(filename)) 13 | filename <- tempfile(fileext = "_r.png") 14 | 15 | ### Open a device 16 | grDevices::png(filename = filename, width = width, height = height, 17 | units = units, pointsize = pointsize, bg = bg, res = res, 18 | ...) 19 | graphics::plot.new() 20 | dv <- grDevices::dev.cur() 21 | 22 | ### Assign the opened device file name 23 | if (!exists(".rDevices", envir = .GlobalEnv)) 24 | eval(parse(text = "assign('.rDevices', list(''), envir = .GlobalEnv)")) 25 | 26 | .GlobalEnv$.rDevices[[dv]] <- filename 27 | 28 | ### Plot and sent to remote if expr is not NULL. 29 | ### Do not check on "language", but "symbol" is ok. 30 | tmp <- substitute(expr) 31 | if (!is.null(tmp)) 32 | { 33 | if (is.symbol(tmp)) 34 | { 35 | if (is.function(expr)) 36 | expr() 37 | else if (is.gg.ggplot(expr)) 38 | print(expr) 39 | else 40 | eval(expr, envir = parent.frame()) 41 | } 42 | else 43 | eval(expr, envir = parent.frame()) 44 | 45 | ### Call rpng.off() when expr is an object or a function. 46 | return(invisible(rpng.off(which = dv))) 47 | } 48 | 49 | invisible() 50 | } 51 | 52 | 53 | 54 | #' @param which 55 | #' An integer specifying a device number as in \code{grDevices::dev.off()} 56 | #' 57 | #' @rdname rDevices_rpng 58 | #' @export 59 | rpng.off <- function(which = grDevices::dev.cur()) 60 | { 61 | set.status(need_auto_rpng_off, FALSE) 62 | 63 | if (which == 1) 64 | { 65 | ret <- "dev.off(): Can not shut down device 1 (the null device)." 66 | return(ret) 67 | } 68 | 69 | ### The ggplot below does NOT need auto_rpng_off but need a dev.off() later. 70 | ### remoter> g <- ggplot(da, aes(x, y)) + geom_point() 71 | ### remoter> pdf() 72 | ### remoter> g 73 | ### remoter>dev.off() 74 | if (iam("remote") && inwhileloop("server") && is.rpng.open()) 75 | { 76 | ### Overwrite native R functions. 77 | set.status(need_auto_rpng_off, TRUE) 78 | grDevices::dev.off(which = which) 79 | filename <- .GlobalEnv$.rDevices[[which]] 80 | .GlobalEnv$.rDevices[[which]] <- '' 81 | ret <- png::readPNG(filename) 82 | return(invisible(ret)) 83 | } 84 | 85 | ### For normal cases, such as interactive and file devices, one may call 86 | ### dev.off() to turn off the display, so to keep consistent I need to 87 | ### call/return the native R function because dev.off() is hijack. 88 | ret <- grDevices::dev.off(which = which) 89 | return(ret) 90 | } 91 | 92 | 93 | 94 | #' @rdname rDevices_rpng 95 | #' @export 96 | dev.off <- rpng.off 97 | 98 | 99 | 100 | ### This is called only when .pbdenv$status$need_auto_rpng_off is TRUE. 101 | auto_rpng_off_local <- function(img) 102 | { 103 | if (is.array(img)) 104 | { 105 | if (get.status("method_plot_rpng") == "rasterImage") 106 | { 107 | ### Semi-transparency may not work in windows device 108 | # if (.Platform$OS.type == "windows") 109 | # img.r <- grDevices::as.raster(img[,, 1:3]) 110 | # else 111 | img.r <- grDevices::as.raster(img) 112 | 113 | ### This will overwrite the device if one is there, otherwise a new 114 | ### device will be created. 115 | graphics::plot.new() 116 | 117 | ### Set a empty plot. 118 | graphics::par(mar = rep(0, 4), xaxs = "i", yaxs = "i") 119 | img.dim <- dim(img.r) 120 | graphics::plot(NULL, NULL, type = "n", axes = FALSE, 121 | main = "", xlab = "", ylab = "", 122 | xlim = c(0, img.dim[1]), ylim = c(0, img.dim[2])) 123 | graphics::rasterImage(img.r, 0, 0, img.dim[1], img.dim[2]) 124 | } 125 | else if (get.status("method_plot_rpng") == "browseURL") 126 | { 127 | tmp.fn <- tempfile(fileext = ".png") 128 | png::writePNG(img, target = tmp.fn) 129 | # cat(paste("file://", tmp.fn, "\n", sep = "")) 130 | utils::browseURL(paste("file://", tmp.fn, sep = "")) 131 | } 132 | else 133 | { 134 | stop("method_plot_rpng should be either rasterImage or browseURL.\n") 135 | } 136 | } 137 | else 138 | { 139 | eval(parse(text = "assign('.rpng.img', img, envir = .GlobalEnv)")) 140 | stop("Check 'local' .GlobalEnv$.rpng.img for the incorrect (raster) image.\n") 141 | } 142 | } 143 | 144 | 145 | 146 | is.gg.ggplot <- function(x) 147 | { 148 | all(class(x) == c("gg", "ggplot")) 149 | } 150 | 151 | is.rpng.open <- function(which = grDevices::dev.cur()) 152 | { 153 | filename <- .GlobalEnv$.rDevices[[which]] 154 | return(!is.null(filename) && filename != '') 155 | } 156 | 157 | -------------------------------------------------------------------------------- /vignettes/include/settings.tex: -------------------------------------------------------------------------------- 1 | %------------------------------------------------------------------------------- 2 | % basic settings 3 | %------------------------------------------------------------------------------- 4 | 5 | \usepackage{longtable} 6 | \usepackage{booktabs} 7 | 8 | \usepackage{parskip} 9 | \setlength{\parskip}{.3cm} 10 | 11 | \makeatletter 12 | \newcommand\code{\bgroup\@makeother\_\@makeother\~\@makeother\$\@codex} 13 | \def\@codex#1{{\normalfont\ttfamily\hyphenchar\font=-1 #1}\egroup} 14 | \makeatother 15 | %%\let\code=\texttt 16 | \let\proglang=\textsf 17 | 18 | 19 | \usepackage[margin=1.1in]{geometry} 20 | \usepackage{graphicx} 21 | \usepackage{listings} 22 | \usepackage{hyperref} 23 | \usepackage{xcolor} 24 | \usepackage{xspace} 25 | 26 | \definecolor{gray}{rgb}{.6,.6,.6} 27 | \definecolor{dkgray}{rgb}{.3,.3,.3} 28 | \definecolor{grayish}{rgb}{.9, .9, .9} 29 | \definecolor{dkgreen}{rgb}{0,0.6,0} 30 | \definecolor{mauve}{rgb}{0.58,0,0.82} 31 | 32 | \hypersetup{ 33 | pdfnewwindow=true, 34 | colorlinks=true, 35 | linkcolor=blue, 36 | linkbordercolor=blue, 37 | citecolor=blue, 38 | filecolor=magenta, 39 | urlcolor=blue 40 | } 41 | 42 | \lstdefinelanguage{rr}{ 43 | language=R, 44 | basicstyle=\ttfamily\color{black}, 45 | backgroundcolor=\color{grayish}, 46 | frame=single, 47 | breaklines=true, 48 | keywordstyle=\color{blue}, 49 | commentstyle=\color{dkgreen}, 50 | stringstyle=\color{mauve}, 51 | numbers=left,%none, 52 | numberstyle=\tiny\color{dkgray}, 53 | stepnumber=1, 54 | numbersep=8pt, 55 | showspaces=false, 56 | showstringspaces=false, 57 | showtabs=false, 58 | rulecolor=\color{gray}, 59 | tabsize=4, 60 | captionpos=t, 61 | } 62 | 63 | 64 | 65 | %------------------------------------------------------------------------------- 66 | % title settings 67 | %------------------------------------------------------------------------------- 68 | 69 | \newcommand*{\plogo}{\includegraphics[scale=.5]{./include/uch_small}} 70 | 71 | \makeatletter 72 | \newcommand\mytitle[1]{\renewcommand\@mytitle{#1}} 73 | \newcommand\@mytitle{} 74 | \makeatother 75 | 76 | \makeatletter 77 | \newcommand\mysubtitle[1]{\renewcommand\@mysubtitle{#1}} 78 | \newcommand\@mysubtitle{} 79 | \makeatother 80 | 81 | \makeatletter 82 | \newcommand\myversion[1]{\renewcommand\@myversion{#1}} 83 | \newcommand\@myversion{} 84 | \makeatother 85 | 86 | 87 | \newcommand{\myauthor}[1]{\author{#1}} 88 | 89 | 90 | %------------------------------------------------------------------------------- 91 | % TITLE PAGE 92 | %------------------------------------------------------------------------------- 93 | 94 | \makeatletter 95 | \newcommand*{\titleGP}{\begingroup 96 | \centering 97 | \vspace*{\baselineskip} 98 | 99 | \rule{\textwidth}{1.6pt}\vspace*{-\baselineskip}\vspace*{2pt} 100 | \rule{\textwidth}{0.4pt}\\[\baselineskip] 101 | 102 | {\Huge \scshape\@mytitle \\[0.2\baselineskip] } 103 | 104 | \rule{\textwidth}{0.4pt}\vspace*{-\baselineskip}\vspace{3.2pt} 105 | \rule{\textwidth}{1.6pt}\\[\baselineskip] 106 | 107 | \scshape % Small caps 108 | \@mysubtitle \ \\[\baselineskip] % Tagline(s) or further 109 | \today\par % Location and year 110 | 111 | \vspace*{2\baselineskip} % Whitespace between location/year and editors 112 | 113 | {\large \@author \par} 114 | 115 | \vfill % Whitespace between editor names and publisher logo 116 | 117 | \plogo \\[0.3\baselineskip] % Publisher logo 118 | {Version \@myversion} \\[0.3\baselineskip] % Year published 119 | % {\large THE PUBLISHER}\par % Publisher 120 | 121 | \endgroup} 122 | \makeatother 123 | 124 | 125 | 126 | %------------------------------------------------------------------------------- 127 | % first few 128 | %------------------------------------------------------------------------------- 129 | 130 | \usepackage{lastpage} 131 | \usepackage{fancyhdr} 132 | 133 | \pagestyle{fancy} 134 | 135 | \newcommand{\prebodyheadfoot}{ 136 | \fancyhf{} % clear all header and footer fields 137 | \fancyfoot{} 138 | \renewcommand{\headrulewidth}{0pt} 139 | \renewcommand{\footrulewidth}{0pt} 140 | 141 | % redefinition of the plain style: 142 | \fancypagestyle{plain}{% 143 | \fancyhf{} % clear all header and footer fields 144 | \renewcommand{\headrulewidth}{0pt} 145 | \renewcommand{\footrulewidth}{0pt}} 146 | } 147 | 148 | \newcommand{\bodyheadfoot}{ 149 | \fancyhf{} % clear all header and footer fields 150 | % \fancyhead[L]{\slshape \rightmark} 151 | \fancyhead[L]{\slshape \leftmark} 152 | \fancyhead[R]{ \thepage\ of\ \pageref{LastPage}} 153 | \renewcommand{\headrulewidth}{1pt} 154 | \renewcommand{\footrulewidth}{0pt} 155 | 156 | % redefinition of the plain style: 157 | \fancypagestyle{plain}{% 158 | \fancyhf{} % clear all header and footer fields 159 | \renewcommand{\headrulewidth}{0pt} 160 | \renewcommand{\footrulewidth}{0pt}} 161 | } 162 | 163 | 164 | 165 | \newcommand{\makefirstfew}{% 166 | \prebodyheadfoot 167 | \restoregeometry 168 | 169 | 170 | \titleGP 171 | \newpage 172 | 173 | \input{./include/00-acknowledgement} 174 | \newpage 175 | \pagenumbering{roman} 176 | \tableofcontents 177 | \newpage 178 | 179 | 180 | \pagenumbering{arabic} 181 | \setcounter{page}{1} 182 | 183 | 184 | \bodyheadfoot 185 | \pagenumbering{arabic} 186 | \setcounter{page}{1} 187 | \pagestyle{fancy} 188 | } -------------------------------------------------------------------------------- /R/wcc_rhelp.r: -------------------------------------------------------------------------------- 1 | #' rhelp 2 | #' 3 | #' Remote R Help System 4 | #' 5 | #' @description 6 | #' Provide the primary interface to the help systems as \code{utils::help()} 7 | #' 8 | #' @param topic,e1,e2 9 | #' A topic as in \code{utils::help()} 10 | #' @param package 11 | #' A package as in \code{utils::help()} 12 | #' @param lib.loc 13 | #' A lib location as in \code{utils::help()} 14 | #' @param verbose 15 | #' if verbose on/off as in \code{utils::help()} 16 | #' @param try.all.packages 17 | #' if try all packages as in \code{utils::help()} 18 | #' 19 | #' @examples 20 | #' \dontrun{ 21 | #' ### Prompts are listed to clarify when something is eval'd locally vs 22 | #' ### remotely 23 | #' > # suppressMessages(library(remoter, quietly = TRUE)) 24 | #' > # client() 25 | #' > remoter::client("192.168.56.101") 26 | #' 27 | #' remoter> rhelp("plot") 28 | #' remoter> rhelp(package = "remoter") 29 | #' remoter> rhelp("plot", package = "remoter") 30 | #' 31 | #' remoter> rhelp("dev.off") 32 | #' remoter> rhelp("dev.off", package = "remoter") 33 | #' remoter> rhelp("dev.off", package = "grDevices") 34 | #' 35 | #' remoter> help("par") 36 | #' 37 | #' remoter> ?`+` 38 | #' remoter> ?`?` 39 | #' remoter> ?"??" 40 | #' remoter> package?base 41 | #' remoter> `?`(package, remoter) 42 | #' 43 | #' 44 | #' remoter> q() 45 | #' > 46 | #' } 47 | #' 48 | #' @rdname rhelp 49 | #' @name rhelp 50 | NULL 51 | 52 | #' @rdname rhelp 53 | #' @export 54 | rhelp <- function(topic, package = NULL, lib.loc = NULL, 55 | verbose = getOption("verbose"), 56 | try.all.packages = getOption("help.try.all.packages")) 57 | { 58 | 59 | ### The next are very stupid but works. 60 | if (missing(topic)) 61 | txt.head <- "utils::help(" 62 | else 63 | txt.head <- paste0("utils::help('", as.character(topic), "', ") 64 | 65 | if (is.null(package)) 66 | package <- "NULL" 67 | else 68 | package <- paste0("'", as.character(package), "'") 69 | 70 | if (is.null(lib.loc)) 71 | lib.loc <- "NULL" 72 | else 73 | lib.loc <- paste0("'", as.character(lib.loc), "'") 74 | 75 | if (iam("remote") && inwhileloop("server")) 76 | { 77 | help_type <- "text" 78 | } 79 | else 80 | help_type <- getOption("help_type") 81 | 82 | help_type <- paste0("'", as.character(help_type), "'") 83 | 84 | ### Execute the help command. 85 | txt <- paste0(txt.head, "package = ", package, ", ", 86 | "lib.loc = ", lib.loc, ", ", 87 | "verbose = ", verbose, ", ", 88 | "try.all.packages = ", try.all.packages, ", ", 89 | "help_type = ", help_type, ")") 90 | ret <- eval(parse(text = txt)) 91 | 92 | ### Return Rd when server is on 93 | if (iam("remote") && inwhileloop("server")) 94 | { 95 | ### Ask client to show 96 | if (class(ret) != "try-error") 97 | set.status(need_auto_rhelp_on, TRUE) 98 | 99 | ### Deal with "help_files_with_topic" or "packageInfo" 100 | if (class(ret) == "help_files_with_topic") 101 | Rd <- print.rhelp_files_with_topic(ret) 102 | else if (class(ret) == "packageInfo") 103 | Rd <- print.rpackageInfo(ret) 104 | else 105 | Rd <- ret 106 | 107 | ### Visible return is necessary because of retmoter_server_eval(). 108 | return(Rd) 109 | } 110 | else 111 | return(ret) 112 | } 113 | 114 | 115 | 116 | #' @rdname rhelp 117 | #' @export 118 | help <- rhelp 119 | 120 | 121 | 122 | #' @rdname rhelp 123 | #' @export 124 | `?` <- function(e1, e2) 125 | { 126 | ### The next are very stupid but works. 127 | if (missing(e2)) 128 | txt <- paste0("utils::`?`('", as.character(substitute(e1)), "', )") 129 | else 130 | txt <- paste0("utils::`?`('", as.character(substitute(e1)), "', '", 131 | as.character(substitute(e2)), "')") 132 | 133 | ### Execute the help command. 134 | ret <- eval(parse(text = txt)) 135 | 136 | ### Return Rd when server is on 137 | if (iam("remote") && inwhileloop("server")) 138 | { 139 | ### Ask client to show 140 | if (class(ret) != "try-error") 141 | set.status(need_auto_rhelp_on, TRUE) 142 | 143 | ### Deal with "help_files_with_topic" or "packageInfo" 144 | if (class(ret) == "help_files_with_topic") 145 | Rd <- print.rhelp_files_with_topic(ret) 146 | else 147 | Rd <- ret 148 | 149 | ### Visible return is necessary because of retmoter_server_eval(). 150 | return(Rd) 151 | } 152 | else 153 | return(ret) 154 | } 155 | 156 | 157 | 158 | auto_rhelp_on_local <- function(Rd) 159 | { 160 | ### Don not use "latin1" to encode the character string. 161 | encoding <- "UTF-8" 162 | Encoding(Rd) <- encoding 163 | 164 | ### Encoding in windows is inconsistent for the Rterm and file.show(). 165 | if (isWindows()) 166 | encoding <- "latin1" 167 | 168 | ### Cast Rd by class. 169 | if (isRStudio()) 170 | cat(Rd, sep = "\n") 171 | else 172 | { 173 | if (class(Rd) == "rhelp_files_with_topic") 174 | { 175 | temp <- tempfile("Rtxt") 176 | cat(Rd, file = temp, sep = "\n") 177 | file.show(temp, title = "R Help", delete.file = TRUE, encoding = encoding) 178 | } 179 | else if (class(Rd) == "rpackageInfo") 180 | { 181 | temp <- tempfile("RpackageInfo") 182 | cat(Rd, file = temp, sep = "\n") 183 | file.show(temp, title = "R Package", delete.file = TRUE, 184 | encoding = encoding) 185 | } 186 | else 187 | cat(Rd, sep = "\n") 188 | } 189 | 190 | invisible() 191 | } 192 | 193 | 194 | 195 | ### Hijack utils:::print.help_files_with_topic() 196 | print.rhelp_files_with_topic <- function(x, ...) 197 | { 198 | paths <- as.character(x) 199 | topic <- attr(x, "topic") 200 | 201 | if (!length(paths)) 202 | { 203 | ret <- c(gettextf("No documentation for %s in specified packages and libraries.", 204 | sQuote(topic))) 205 | return(invisible(ret)) 206 | } 207 | 208 | if (attr(x, "tried_all_packages")) 209 | { 210 | paths <- unique(dirname(dirname(paths))) 211 | msg <- gettextf("Help for topic %s is not in any loaded package but can be found in the following packages:", 212 | sQuote(topic)) 213 | ret <- c(strwrap(msg), "", 214 | paste(" ", formatDL(c(gettext("Package"), basename(paths)), 215 | c(gettext("Library"), dirname(paths)), 216 | indent = 22)), 217 | "Specify the package name to rhelp(topic, package = ...)") 218 | } 219 | else 220 | { 221 | ### Check multiple topics and disable menu selection. 222 | if (length(paths) > 1L) 223 | { 224 | msg <- gettextf("Help on topic %s was found in the following packages:", 225 | sQuote(topic)) 226 | paths <- dirname(dirname(paths)) 227 | txt <- formatDL(c("Package", basename(paths)), c("Library", 228 | dirname(paths)), indent = 22L) 229 | ret <- c(strwrap(msg), "", paste(" ", txt), "", 230 | "Specify the package name to rhelp(topic, package = ...)") 231 | } 232 | else 233 | { 234 | file <- paths 235 | pkgname <- basename(dirname(dirname(file))) 236 | .getHelpFile <- eval(parse(text = "utils:::.getHelpFile")) 237 | temp <- Rd2txt(.getHelpFile(file), out = tempfile("Rtxt"), 238 | package = pkgname, outputEncoding = "UTF-8") 239 | ret <- readLines(temp, warn = FALSE, encoding = "UTF-8") 240 | class(ret) <- "rhelp_files_with_topic" 241 | file.remove(temp) 242 | } 243 | } 244 | 245 | invisible(ret) 246 | } 247 | 248 | 249 | 250 | ### Hijack base::print.packageInfo() 251 | print.rpackageInfo <- function(x, ...) 252 | { 253 | temp <- tempfile("RpackageInfo") 254 | writeLines(format(x), temp) 255 | ret <- readLines(temp, warn = FALSE, encoding = "UTF-8") 256 | class(ret) <- "rpackageInfo" 257 | file.remove(temp) 258 | 259 | invisible(ret) 260 | } 261 | -------------------------------------------------------------------------------- /vignettes/remote_machines.Rnw: -------------------------------------------------------------------------------- 1 | %\VignetteIndexEntry{Using remoter with Remote Machines} 2 | \documentclass[]{article} 3 | 4 | 5 | \input{./include/settings} 6 | 7 | 8 | \mytitle{Using remoter with Remote Machines} 9 | \mysubtitle{} 10 | \myversion{0.5-0} 11 | \myauthor{ 12 | \centering 13 | Drew Schmidt \\ 14 | \texttt{wrathematics@gmail.com} 15 | } 16 | 17 | 18 | 19 | \begin{document} 20 | 21 | \begin{figure}[ht] 22 | \vspace{-.5in} 23 | \begin{minipage}[c]{8.5in} 24 | \hspace{-1.0in} 25 | \includegraphics[width=8in,height=10in]{./cover/remote_machines.pdf} 26 | \end{minipage} 27 | \end{figure} 28 | 29 | \makefirstfew 30 | 31 | 32 | 33 | \section{Introduction} 34 | \label{intro} 35 | 36 | This guide is for those who understand the basics of using \textbf{remoter}, 37 | and wish to learn how to interact with a remotely hosted server. For the 38 | basics, please first see the \textit{Guide to the remoter 39 | Package}~\cite{remoterguide} guide. 40 | 41 | Before we begin, a quick word about addresses and ports. 42 | 43 | An address should not include any protocols, like \texttt{tcp://} or 44 | \texttt{http://}. The address should also not contain any ports (denoted 45 | by a \texttt{:}), as this value goes in a separate argument. 46 | 47 | A port is a non-negative integer. The minimum value for a valid port is 48 | 1024 (values 1-1023 are privileged), and the maximum value is 65535. 49 | That said, you are strongly encouraged to use port values between 49152 50 | and 65535. The documentation for \textbf{pbdZMQ}~\cite{Chen2015pbdZMQpackage} 51 | discusses this in detail. Specifically, see \texttt{?pbdZMQ::random\_port}. 52 | 53 | Of course, all the usual issues apply. The server should be able to 54 | accept communications on the desired port. One way to handle this is by 55 | opening the desired port. Opening ports is very standard stuff, but 56 | dependent on the system you are using, so consult relevant documentation 57 | if you aren't sure what to do. Another way is by tunneling over ssh, 58 | which we discuss in a later section. 59 | 60 | 61 | 62 | \section{Creating a Remote Server} 63 | 64 | Before beginning, you need to spawn your server. This is something you only 65 | need to do once, and you need to do it in such a way that it is allowed to run 66 | persistently, even after you log off. 67 | 68 | The easiest setup is if your server is available via ssh, and probably running 69 | headless (without a monitor/GUI desktop environment). This is what your typical 70 | linux cloud instance looks like. In this case, 71 | we suggest you use a tool like tmux or screen. This way, you can re-attach your 72 | server session and easily read the logs live, which is very useful for 73 | debugging. However, this is not strictly necessary. 74 | 75 | If you are using something like tmux or screen, then your workflow would look 76 | something like: 77 | 78 | \begin{enumerate} 79 | \item ssh to your remote (you only need to do this once!) 80 | \item Start a \textbf{tmux} or \textbf{screen} session 81 | \item Start R and run \texttt{remoter::server()} (see \texttt{?server} for 82 | additional options). Or even better, run 83 | \texttt{Rscript\ -e\ remoter::server()} so the server dies if 84 | something goes wrong. 85 | \item Detach your tmux/screen session and log out. 86 | \end{enumerate} 87 | 88 | Alternatively, if you wish to avoid tmux/screen, you still need to ssh to your 89 | machine. Then you can run the R session in the background by a fork via 90 | something like: 91 | 92 | \begin{lstlisting}[language=rr] 93 | Rscript -e "remoter::server()" & 94 | \end{lstlisting} 95 | 96 | and disconnect. If for some reason the server becomes unreachable via 97 | \textbf{remoter}, you will have to manually connect again via ssh to kill the 98 | rogue process with the \texttt{kill} command. 99 | 100 | If you are running a full desktop environment, which is typical with Windows 101 | servers, you should be able to launch an R process (say RGui) and start the 102 | server there via \texttt{remoter::server()}. Admittedly, we have no experience 103 | with this configuration, so if you have experience here, we would love to hear 104 | from you. 105 | 106 | 107 | 108 | \section{Connecting to a Remote Server} 109 | 110 | Because \textbf{remoter} is just and R package to connect to the remote, you 111 | need only fire up your favorite R interface. This can be the terminal, RStudio, 112 | RGui (Windows), R.app (Mac), or even Emacs. Whatever your choice, connect as 113 | with a local server, but specifying the correct remote address (and possibly 114 | port): 115 | 116 | \begin{lstlisting}[language=rr] 117 | remoter::client("my.remote.address") 118 | \end{lstlisting} 119 | 120 | So for example, say you have set up a server (as described above) on EC2 121 | with address \texttt{"ec2-1-2-3-4.compute-1.amazonaws.com"}, listening 122 | on port \texttt{56789}. Then you would run: 123 | 124 | \begin{lstlisting}[language=rr] 125 | remoter::client("ec2-1-2-3-4.compute-1.amazonaws.com", port=56789) 126 | \end{lstlisting} 127 | 128 | That's it! Everything else should work just as when you were running the 129 | server locally. The only hiccup is opening up that port. If that is not 130 | possible for you for whatever reason, then you may need to set up an ssh tunnel, 131 | which we describe in the following section. 132 | 133 | 134 | 135 | \section{Tunneling Over ssh} 136 | 137 | If you can't or don't want to open up a port on a remote system, you can 138 | always tunnel over ssh (assuming of course you actually have legitimate 139 | access to the machine\ldots{}). 140 | 141 | The \textbf{pbdRPC} package~\cite{Chen2017pbdRPCpackage} has offers some basic 142 | tunneling functionality. At the time of writing, it is somewhat new and 143 | experimental. But the reader is encouraged to check the package's 144 | vignette~\cite{Chen2017pbdRPCvignette} for more details. 145 | 146 | Even without \textbf{pbdRPC}, creating a tunnel is not terribly difficult. 147 | Say you have user account \texttt{user} on remote with address 148 | \texttt{my.remote.machine}. Suppose your remote machine is running a 149 | \textbf{remoter} server, listening on port 55555. Then you can run: 150 | 151 | \begin{lstlisting}[language=rr] 152 | ssh user@my.remote.machine -L 55556:localhost:55555 -N 153 | \end{lstlisting} 154 | 155 | To be totally unambiguous: 156 | 157 | \begin{itemize} 158 | \item server port (running on remote): 55555 159 | \item client port (running on your laptop): 55556 160 | \end{itemize} 161 | 162 | This will allow you to connect to the remote machine as follows: 163 | 164 | \begin{lstlisting}[language=rr] 165 | remoter::client("localhost", port=55556) 166 | \end{lstlisting} 167 | 168 | You can also spawn the server in the ssh tunnel call. For example, you 169 | might run: 170 | 171 | \begin{lstlisting}[language=rr] 172 | ssh user@my.remote.machine -L 55556:localhost:55555 'Rscript -e "remoter::server(port=55555)"' 173 | \end{lstlisting} 174 | 175 | This will automatically launch a \textbf{remoter} server listening on 176 | port 55555, tunneled over \texttt{localhost:55566}. If you are working 177 | on a managed system, like a cluster or supercomputer, you might need to 178 | run something like \texttt{module\ load\ R} first: 179 | 180 | \begin{lstlisting}[language=rr] 181 | ssh user@my.remote.machine -L 55556:localhost:55555 'module load R && Rscript -e "remoter::server(port=55555)"' 182 | \end{lstlisting} 183 | 184 | 185 | 186 | \section{Working with Relays} 187 | 188 | When an intermediary between the client/server is necessary, it is generally 189 | preferable to work with ssh tunnels whenever possible. However, some use cases 190 | may require or suggest a different strategy. To that end, as of 191 | \textbf{remoter} version 0.3-1, we now offer a different kind of spawnable 192 | instalce: ``relays''. 193 | These serve as ``middlemen'' between the client and server, and are 194 | particularly useful for resources like clusters and supercomputers where 195 | the login and compute nodes are separate. Internally, the relay is a 196 | server that does nothing but pass messages between the client and 197 | server. Figure~\ref{fig:relays} shows the basic conceptual idea about how 198 | relays work. 199 | 200 | \begin{figure}[ht] 201 | \centering 202 | \includegraphics{pics/remoter_relay} 203 | \caption{Remoter relays and their relationship to the client and server. The 204 | diagram is composed of icons from the OSA Icon Library}\label{fig:relays} 205 | \end{figure} 206 | 207 | 208 | To spawn a relay, you can do: 209 | 210 | \begin{lstlisting}[language=rr] 211 | remoter::relay(addr=my.server.addr, sendport=my.server.port) 212 | \end{lstlisting} 213 | 214 | As the name suggests in the above example, \texttt{my.server.addr} and 215 | \texttt{sendport} represent the address and port of the server (what you 216 | would use for \texttt{addr} in \texttt{remoter::client()} if you could 217 | connect directly). Then the client will connect to the relay, not the 218 | server (that's the whole point!) something like: 219 | 220 | \begin{lstlisting}[language=rr] 221 | remoter::client(addr=my.relay.addr, port=my.relay.port) 222 | \end{lstlisting} 223 | 224 | Here \texttt{my.relay.addr} is the address of the relay, and 225 | \texttt{my.relay.port} should math the argument \texttt{recvport} used 226 | when creating the relay (default is 227 | \texttt{r\ as.integer(formals(remoter::relay){[}{[}"recvport"{]}{]})}). 228 | 229 | 230 | 231 | \addcontentsline{toc}{section}{References} 232 | \bibliography{./include/remoter} 233 | \bibliographystyle{plain} 234 | 235 | \end{document} 236 | -------------------------------------------------------------------------------- /R/client.r: -------------------------------------------------------------------------------- 1 | #' Client Launcher 2 | #' 3 | #' Connect to a remote server (launch the client). 4 | #' 5 | #' @details 6 | #' The \code{port} values between the client and server must agree. 7 | #' If they do not, this can cause the client to hang. 8 | #' The client is a specialized REPL that intercepts commands sent 9 | #' through the R interpreter. These commands are then sent from the 10 | #' client to and evaluated on the server. 11 | #' The client communicates over ZeroMQ with the server using a REQ/REP pattern. 12 | #' Both commands (from client to server) and returns (from server 13 | #' to client) are handled in this way. 14 | #' 15 | #' To shut down the server and the client, see \code{exit()}. 16 | #' 17 | #' @param addr 18 | #' The remote host/address/endpoint. 19 | #' @param port 20 | #' The port (number) that will be used for communication between 21 | #' the client and server. The port value for the client and server 22 | #' must agree. 23 | #' @param password 24 | #' An initial password to pass to the server. If the server is not accepting 25 | #' passwords, then this argument is ignored. If the initial pasword is 26 | #' incorrect, then assuming the server's \code{maxretry>1}, then you will be 27 | #' interactively asked to enter the password. 28 | #' @param prompt 29 | #' The prompt to use to delineate the client from the normal R REPL. 30 | #' @param timer 31 | #' Logical; should the "performance prompt", which shows timing 32 | #' statistics after every command, be used? 33 | #' @param serialversion 34 | #' NULL or numeric; the workspace format version to use when serializing. 35 | #' NULL specifies the current default version. The only other supported 36 | #' values are 2 and 3. 37 | #' 38 | #' @return 39 | #' Returns \code{TRUE} invisibly on successful exit. 40 | #' 41 | #' @export 42 | client <- function(addr="localhost", port=55555, password=NULL, 43 | prompt="remoter", timer=FALSE, serialversion=NULL) 44 | { 45 | check.is.flag(timer) 46 | check.is.string(prompt) 47 | check(is.null(serialversion) || is.inty(serialversion)) 48 | validate_address(addr) 49 | addr <- scrub_addr(addr) 50 | validate_port(port, warn=FALSE) 51 | 52 | test_connection(addr, port) 53 | 54 | reset_state() 55 | 56 | set(whoami, "local") 57 | set(prompt, prompt) 58 | set(timer, timer) 59 | set(port, port) 60 | set(remote_addr, addr) 61 | set(clientpw, password) 62 | set(serialversion, serialversion) 63 | 64 | set(isbatch, FALSE) 65 | 66 | ### Both axes are proportionally scaled to fit the new window size 67 | if (isWindows()) 68 | grDevices::windows.options(rescale = "fit") 69 | 70 | remoter_repl_client() 71 | 72 | invisible(TRUE) 73 | } 74 | 75 | 76 | 77 | remoter_readline <- function(input) 78 | { 79 | if (get.status(continuation)) 80 | symb <- "+ " 81 | else 82 | symb <- "> " 83 | 84 | prompt <- paste0(getval(prompt), symb) 85 | 86 | Cc_check <- ".__cantstopwontstop" 87 | 88 | Cc_ct <- 1L 89 | repeat 90 | { 91 | check <- tryCatch(read <- readline(prompt=prompt), interrupt=function(.) Cc_check) 92 | if (check == Cc_check && get.status(continuation)) 93 | return("remoter_client_halt") 94 | else if (check != Cc_check || Cc_ct >= 3L) 95 | break 96 | else 97 | { 98 | Cc_ct <- Cc_ct + 1 99 | cat("^C\n") 100 | } 101 | } 102 | 103 | if (!exists("read")) 104 | { 105 | cat("3 ctrl+c's detected; killing remoter client...\n") 106 | read <- "exit()" 107 | } 108 | 109 | ### Add to history() and avoid repeatedly appending suffix. 110 | addhistory(read) 111 | 112 | ret <- c(input, read) 113 | ret <- remoter_sanitize(inputs=ret) 114 | 115 | return(ret) 116 | } 117 | 118 | 119 | 120 | ### TODO use a proper parser... 121 | remoter_sanitize <- function(inputs) 122 | { 123 | for (i in 1:length(inputs)) 124 | { 125 | input <- inputs[i] 126 | if (grepl(x=input, pattern="^(\\s+)?(q|quit)\\(", perl=TRUE)) 127 | inputs[i] <- "exit(client.only=TRUE)" 128 | else if (grepl(x=input, pattern="(.pbdenv|remoter:::)", perl=TRUE) && !getval(debug)) 129 | { 130 | remoter_client_stop("I can't do that.") 131 | inputs[i] <- "invisible()" 132 | } 133 | else if (grepl(x=input, pattern="^(\\s+)?geterrmessage\\(", perl=TRUE)) 134 | inputs[i] <- getval(client_lasterror) 135 | else if (grepl(x=input, pattern="^(\\s+)?(\\?\\?|help.search\\(|help.start\\()", perl=TRUE)) 136 | { 137 | remoter_client_stop("Using help() to obtain help files from the server.") 138 | inputs[i] <- "invisible()" 139 | } 140 | else if (grepl(x=input, pattern="^(\\s+)?debug\\(", perl=TRUE)) 141 | { 142 | remoter_client_stop("debug mode is currently not supported.") 143 | inputs[i] <- "invisible()" 144 | } 145 | else if (grepl(x=input, pattern="^(\\s+)?warnings\\(", perl=TRUE)) 146 | { 147 | set.status(shouldwarn, TRUE) 148 | remoter_show_warnings(force=TRUE) 149 | inputs[i] <- "invisible()" 150 | } 151 | else if (input == "") 152 | inputs[i] <- "invisible()" 153 | else if (grepl(x=input, pattern="^(\\s+)?(remoter::)?(client|server|relay|batch)\\(", perl=TRUE)) 154 | { 155 | remoter_client_stop("can not spawn client/server/relay or launch a batch connection from inside the client") 156 | inputs[i] <- "invisible()" 157 | } 158 | else if (grepl(x=input, pattern="remoter_client_halt")) 159 | inputs[i] <- "invisible()" 160 | } 161 | 162 | return(inputs) 163 | } 164 | 165 | 166 | 167 | check4fun = function(input, fun) 168 | { 169 | all(grepl(x=input, pattern=paste0("^(\\s+)?", fun, "\\("), perl=TRUE)) 170 | } 171 | 172 | remoter_client_sendrecv <- function(input, env) 173 | { 174 | remoter_send(data=input) 175 | 176 | ### Special cases that need to be eval'd locally 177 | if (check4fun(input, "s2c")) 178 | eval(parse(text=input)) 179 | else if (check4fun(input, "c2s")) 180 | eval(parse(text=input)) 181 | else if (check4fun(input, "sendfile")) 182 | eval(parse(text=input)) 183 | else if (check4fun(input, "recvfile")) 184 | eval(parse(text=input)) 185 | else if (check4fun(input, "lsc")) 186 | eval(parse(text=input)) 187 | else if (check4fun(input, "rmc")) 188 | eval(parse(text=input)) 189 | else if (check4fun(input, "evalc")) 190 | eval(parse(text=input)) 191 | else if (check4fun(input, "dev.curc")) 192 | eval(parse(text=input)) 193 | else if (check4fun(input, "dev.listc")) 194 | eval(parse(text=input)) 195 | else if (check4fun(input, "dev.nextc")) 196 | eval(parse(text=input)) 197 | else if (check4fun(input, "dev.prevc")) 198 | eval(parse(text=input)) 199 | else if (check4fun(input, "dev.offc")) 200 | eval(parse(text=input)) 201 | else if (check4fun(input, "dev.setc")) 202 | eval(parse(text=input)) 203 | else if (check4fun(input, "dev.newc")) 204 | eval(parse(text=input)) 205 | else if (check4fun(input, "dev.sizec")) 206 | eval(parse(text=input)) 207 | 208 | ### Update status by the server's results. 209 | set(status, remoter_receive()) 210 | 211 | ### Update client's local env as necessary 212 | remote_objs <- get.status(remote_objs) 213 | if (!is.null(remote_objs)) 214 | { 215 | for (nm in ls(envir=remote_objs)) 216 | assign(nm, get(nm, envir=remote_objs), envir=env) 217 | } 218 | 219 | ### Because rpng.off() needs a call at the client to display image. 220 | if (get.status(need_auto_rpng_off)) 221 | auto_rpng_off_local(get.status(ret)) 222 | 223 | ### Because rhelp() needs a call at the client to cast help message. 224 | if (get.status(need_auto_rhelp_on)) 225 | auto_rhelp_on_local(get.status(ret)) 226 | 227 | ### Must come last! If client only wants to quit, server doesn't know 228 | ### about it, and resets the status on receive.socket() 229 | if (all(grepl(x=input, pattern="^(\\s+)?exit\\(", perl=TRUE))) 230 | eval(parse(text=input)) 231 | 232 | invisible() 233 | } 234 | 235 | 236 | 237 | remoter_init_client <- function() 238 | { 239 | set(context, pbdZMQ::init.context()) 240 | set(socket, pbdZMQ::init.socket(getval(context), "ZMQ_REQ")) 241 | addr <- pbdZMQ::address(getval(remote_addr), getval(port)) 242 | pbdZMQ::connect.socket(getval(socket), addr) 243 | 244 | test <- remoter_check_password_local() 245 | if (!test) 246 | return(FALSE) 247 | 248 | remoter_check_version_local() 249 | cat("\n") 250 | 251 | return(TRUE) 252 | } 253 | 254 | 255 | 256 | timerfun <- function(timer) 257 | { 258 | if (timer) 259 | EVALFUN <- function(expr) capture.output(system.time(expr)) 260 | else 261 | EVALFUN <- identity 262 | 263 | EVALFUN 264 | } 265 | 266 | 267 | 268 | timerprint <- function(timer, timing) 269 | { 270 | if (timer) 271 | cat(paste0(timing[-1], collapse="\n"), "\n") 272 | 273 | invisible() 274 | } 275 | 276 | 277 | 278 | remoter_client_objcleanup <- function(env) 279 | { 280 | names <- ls(envir=env) 281 | names <- names[grep("_REMOTE", names)] 282 | rm(list=names, envir=env) 283 | 284 | invisible() 285 | } 286 | 287 | 288 | 289 | remoter_repl_client <- function(env=globalenv()) 290 | { 291 | if (!interactive()) 292 | stop("You can only use the client interactively. Use bacth() to execute in batch.") 293 | 294 | test <- remoter_init_client() 295 | if (!test) 296 | return(FALSE) 297 | 298 | timer <- getval(timer) 299 | EVALFUN <- timerfun(timer) 300 | 301 | # a bit of a hack, but we pass a dumb message to the server to sync environments 302 | remoter_client_sendrecv(input="remoter_env_sync", env=env) 303 | 304 | while (TRUE) 305 | { 306 | input <- character(0) 307 | set.status(continuation, FALSE) 308 | set.status(visible, FALSE) 309 | 310 | while (TRUE) 311 | { 312 | input <- remoter_readline(input=input) 313 | if (identical(input[length(input)], "remoter_client_halt")) 314 | break 315 | 316 | timing <- EVALFUN({ 317 | remoter_client_sendrecv(input=input, env=env) 318 | }) 319 | 320 | if (get.status(continuation)) next 321 | 322 | remoter_repl_printer() 323 | 324 | timerprint(timer, timing) 325 | 326 | if (get.status(should_exit)) 327 | { 328 | remoter_client_objcleanup(env) 329 | return(invisible()) 330 | } 331 | 332 | break 333 | } 334 | } 335 | 336 | 337 | remoter_client_objcleanup(env) 338 | 339 | return(invisible()) 340 | } 341 | -------------------------------------------------------------------------------- /R/server.r: -------------------------------------------------------------------------------- 1 | #' Server Launcher 2 | #' 3 | #' Launcher for the remoter server. 4 | #' 5 | #' @details 6 | #' By a 'secure' server, we mean one that encrypts messages it 7 | #' sends and only accepts encrypted messages. Encryption uses 8 | #' public key cryptography, using the 'sodium' package. 9 | #' 10 | #' If the 'sodium' package is available to the server, then by 11 | #' default the server will be secure. If the package is not 12 | #' available, then you will not be able to start a secure server. 13 | #' If the server is secure, then a client can only connect if 14 | #' the client has the 'sodium' package available. 15 | #' 16 | #' @param port 17 | #' The port (number) that will be used for communication between 18 | #' the client and server. The port value for the client and server 19 | #' must agree. If the value is 0, then a random open port will be 20 | #' selected. 21 | #' @param password 22 | #' A password the client must enter before the user can process 23 | #' commands on the server. If the value is \code{NULL}, then no 24 | #' password checking takes place. 25 | #' @param maxretry 26 | #' The maximum number of retries for passwords before shutting 27 | #' everything down. 28 | #' @param secure 29 | #' Logical; enables encryption via public key cryptography of 30 | #' the 'sodium' package is available. 31 | #' @param logfile 32 | #' A file path to use for server logging. If the value is \code{NULL}, then no 33 | #' logging takes place. 34 | #' @param verbose 35 | #' Logical; enables the verbose logger. 36 | #' @param showmsg 37 | #' Logical; if TRUE, messages from the client are logged. 38 | #' @param userpng 39 | #' Logical; if TRUE, rpng is set as the default device for displaying. 40 | #' @param sync 41 | #' Logical; if TRUE, the client will have \code{str()}'d versions of server 42 | #' objects recreated in the global environment. This is useful in IDE's like 43 | #' RStudio, but it carries a performance penalty. For terminal users, this is 44 | #' not recommended. 45 | #' @param serialversion 46 | #' NULL or numeric; the workspace format version to use when serializing. 47 | #' NULL specifies the current default version. The only other supported 48 | #' values are 2 and 3. 49 | #' 50 | #' @return 51 | #' Returns \code{TRUE} invisibly on successful exit. 52 | #' 53 | #' @export 54 | server <- function(port=55555, password=NULL, maxretry=5, secure=has.sodium(), 55 | logfile=NULL, verbose=FALSE, showmsg=FALSE, userpng=TRUE, sync=TRUE, 56 | serialversion=NULL) 57 | { 58 | if (length(port) == 1 && port == 0) 59 | port <- pbdZMQ::random_open_port() 60 | 61 | validate_port(port, warn=TRUE) 62 | check(is.null(password) || is.string(password)) 63 | check(is.null(logfile) || is.string(logfile)) 64 | check.is.posint(maxretry) 65 | check.is.flag(secure) 66 | check.is.flag(verbose) 67 | check.is.flag(showmsg) 68 | check.is.flag(userpng) 69 | check.is.flag(sync) 70 | check(is.null(serialversion) || is.inty(serialversion)) 71 | 72 | if (!has.sodium() && secure) 73 | stop("secure servers can only be launched if the 'sodium' package is installed") 74 | if (is.null(logfile) && verbose) 75 | stop("option 'verbose' is enabled without a specified logfile") 76 | 77 | serverlog = !is.null(logfile) 78 | 79 | if (port == 0) 80 | port <- pbdZMQ::random_open_port() 81 | 82 | reset_state() 83 | set(whoami, "remote") 84 | set(logfile, logfile) 85 | set(serverlog, serverlog) 86 | set(verbose, verbose) 87 | set(showmsg, showmsg) 88 | set(port, port) 89 | set(secure, secure) 90 | set(sync, sync) 91 | set(password, pwhash(password)) 92 | set(serialversion, serialversion) 93 | 94 | logfile_init() 95 | 96 | ### Backup default device and set the rpng as a defult opening device. 97 | options(device.default = getOption("device")) 98 | if (userpng) 99 | options(device = remoter::rpng) 100 | 101 | eval(parse(text = "suppressMessages(library(remoter, quietly = TRUE))"), envir = globalenv()) 102 | 103 | options(warn = 1) 104 | 105 | ips = get_ips() 106 | logprint(paste("*** Launching", ifelse(getval(secure), "secure", "UNSECURE"), "server ***"), preprint="\n", forcemsg=TRUE) 107 | logprint(paste(" Internal IP: ", ips$ip_in), timestamp=FALSE, forcemsg=TRUE) 108 | logprint(paste(" External IP: ", ips$ip_ex), timestamp=FALSE, forcemsg=TRUE) 109 | logprint(paste(" Hostname: ", get_hostname()), timestamp=FALSE, forcemsg=TRUE) 110 | logprint(paste(" Port: ", port), timestamp=FALSE, forcemsg=TRUE) 111 | logprint(paste(" Version: ", packageVersion("remoter")), timestamp=FALSE, forcemsg=TRUE) 112 | 113 | rm("port", "password", "maxretry", "secure", "logfile", "verbose", "showmsg", 114 | "userpng", "sync", "serialversion") 115 | invisible(gc()) 116 | 117 | remoter_repl_server() 118 | remoter_exit_server() 119 | 120 | invisible(TRUE) 121 | } 122 | 123 | 124 | 125 | remoter_warning <- function(warn) 126 | { 127 | set.status(shouldwarn, TRUE) 128 | set.status(num_warnings, get.status(num_warnings) + 1L) 129 | 130 | set.status(warnings, append(get.status(warnings), conditionMessage(warn))) 131 | invokeRestart("muffleWarning") 132 | 133 | print(warn) 134 | } 135 | 136 | 137 | 138 | remoter_error <- function(err) 139 | { 140 | msg <- err$message 141 | set.status(continuation, grepl(msg, pattern="(unexpected end of input|unexpected INCOMPLETE_STRING)")) 142 | 143 | if (!get.status(continuation)) 144 | { 145 | msg <- sub(x=msg, pattern=" in eval\\(expr, envir, enclos\\) ", replacement="") 146 | set.status(lasterror, paste0("Error: ", msg, "\n")) 147 | } 148 | 149 | return(invisible()) 150 | } 151 | 152 | 153 | 154 | remoter_eval_filter_server <- function(msg) 155 | { 156 | if (all(grepl(x=msg, pattern="^(\\s+)?library\\(", perl=TRUE))) 157 | { 158 | msg <- paste0(" 159 | tmp <- file(tempfile()) 160 | sink(tmp, append=TRUE) 161 | sink(tmp, append=TRUE, type='message')\n", 162 | msg, "\n 163 | sink() 164 | sink(type='message') 165 | cat(paste(readLines(tmp), collapse='\n')) 166 | unlink(tmp) 167 | ") 168 | } 169 | 170 | msg 171 | } 172 | 173 | 174 | 175 | remoter_server_check_objs <- function(env, force=FALSE) 176 | { 177 | if (!getval(sync)) 178 | return(invisible()) 179 | 180 | objs_nm_new <- ls(envir=env) 181 | if (force || !identical(getval(objs_nm), objs_nm_new)) 182 | { 183 | set(objs_nm, objs_nm_new) 184 | for (nm in objs_nm_new) 185 | assign(paste0(nm, "_REMOTE"), capture.output(str(get(nm, envir=env))), envir=getval(objs)) 186 | } 187 | 188 | set.status(remote_objs, getval(objs)) 189 | } 190 | 191 | 192 | 193 | remoter_server_eval <- function(env) 194 | { 195 | set.status(shouldwarn, FALSE) 196 | set.status(continuation, FALSE) 197 | set.status(lasterror, NULL) 198 | set.status(need_auto_rpng_off, FALSE) 199 | set.status(need_auto_rhelp_on, FALSE) 200 | set.status(remote_objs, NULL) 201 | 202 | 203 | msg <- remoter_receive() 204 | 205 | logprint(level="RMSG", msg[length(msg)], checkshowmsg=TRUE) 206 | 207 | ### Run first-time checks 208 | if (length(msg)==1 && msg == magicmsg_first_connection) 209 | { 210 | test <- remoter_check_password_remote() 211 | if (!test) 212 | return(invisible()) 213 | 214 | remoter_check_version_remote() 215 | return(invisible()) 216 | } 217 | 218 | msg <- remoter_eval_filter_server(msg=msg) 219 | 220 | ### Sync environment if necessary 221 | if (length(msg) == 1 && msg == "remoter_env_sync") 222 | { 223 | remoter_server_check_objs(env, force=TRUE) 224 | remoter_send(getval(status)) 225 | return(invisible()) 226 | } 227 | 228 | 229 | ### Divert/sink `R message` (warning, error, stop) to stdout 230 | additionmsg <- 231 | capture.output({ 232 | sink(file = stdout(), type = "message") 233 | ret <- 234 | withCallingHandlers( 235 | tryCatch({ 236 | withVisible(eval(parse(text=msg), envir=env)) 237 | }, interrupt=identity, error=remoter_error 238 | ), warning=remoter_warning 239 | ) 240 | sink(file = NULL, type = "message") 241 | }) 242 | 243 | 244 | ### Do the above one more time for ggplot2 ... 245 | if (!is.null(ret)) 246 | { 247 | if (ret$visible && is.gg.ggplot(ret$value)) 248 | { 249 | ### When g is a gg.ggplot object, simply typing `g` just returns from 250 | ### the earlier call "tryCatch()" generating "additionmsg". 251 | ### i.e. identity(g) is returned to the "ret$value", no `print(g)` is 252 | ### executed. 253 | 254 | ### Note that `g <- ggplot(...) + ...` may not check missing objects or 255 | ### variables carefully at the time generate the object g. Really!!! 256 | 257 | ### i.e. the delay execution until printing for the ggplot object needs 258 | ### to be dealed with further below with checking. Otherwise, the 259 | ### `set.status(ret, utils::capture.output(ret$value))` in later call 260 | ### will break the sever because there is no error handler down there. 261 | 262 | ### Any S3 or S4 print methods can crash similarly if designed badly. 263 | ### Hopefully, ggplot2 is the only package. 264 | additionmsg <- 265 | capture.output({ 266 | sink(file = stdout(), type = "message") 267 | ret <- 268 | withCallingHandlers( 269 | tryCatch({ 270 | withVisible(eval(print(ret$value), envir=env)) 271 | }, interrupt=identity, error=remoter_error 272 | ), warning=remoter_warning 273 | ) 274 | sink(file = NULL, type = "message") 275 | }) 276 | } 277 | } 278 | 279 | ### Handle log printing for exit()/shutdown(): NOTE must happen outside of eval since we capture all output now 280 | if (getval(client_called_shutdown) == TRUE) 281 | logprint("client killed server") 282 | else if (getval(client_called_exit) == TRUE) 283 | { 284 | logprint("client disconnected with call to exit()") 285 | set(client_called_exit, FALSE) 286 | } 287 | 288 | 289 | ### Take care the `R output` from ret. 290 | if (!is.null(ret)) 291 | { 292 | set.status(visible, ret$visible) 293 | 294 | if (!ret$visible) 295 | set.status(ret, NULL) 296 | else 297 | set.status(ret, utils::capture.output(ret$value)) 298 | 299 | ### The output of this if is an image from a S3 method via print.ggplot(). 300 | if (ret$visible && is.gg.ggplot(ret$value) && is.rpng.open()) 301 | { 302 | ### The g below opens an rpng which needs auto_rpng_off_local. 303 | ### remoter> g <- ggplot(da, aes(x, y)) + geom_point() 304 | ### remoter> g 305 | ret$value <- rpng.off() 306 | ret$visible <- FALSE 307 | } 308 | 309 | ### The output of this if is an image from dev.off(). 310 | if (get.status(need_auto_rpng_off)) 311 | { 312 | set.status(ret, ret$value) 313 | set.status(visible, ret$visible) 314 | } 315 | 316 | ### The output is an Rd from help(). 317 | if (get.status(need_auto_rhelp_on)) 318 | { 319 | set.status(ret, ret$value) 320 | set.status(visible, FALSE) 321 | } 322 | } 323 | 324 | ### Take care the `R output` from cat/print/message 325 | if (length(additionmsg) == 0) 326 | set.status(ret_addition, NULL) 327 | else 328 | { 329 | set.status(ret_addition, additionmsg) 330 | ### Print to server if needed for debugging 331 | if (getval(verbose)) 332 | cat(additionmsg, sep = "\n") 333 | } 334 | 335 | 336 | remoter_server_check_objs(env) 337 | 338 | remoter_send(getval(status)) 339 | } 340 | 341 | 342 | 343 | remoter_init_server <- function() 344 | { 345 | set(context, pbdZMQ::init.context()) 346 | set(socket, pbdZMQ::init.socket(getval(context), "ZMQ_REP")) 347 | pbdZMQ::bind.socket(getval(socket), pbdZMQ::address("*", getval(port))) 348 | 349 | return(TRUE) 350 | } 351 | 352 | 353 | 354 | remoter_exit_server <- function() 355 | { 356 | if (getval(kill_interactive_server)) 357 | q("no") 358 | 359 | return(TRUE) 360 | } 361 | 362 | 363 | 364 | remoter_repl_server <- function(env=globalenv(), initfun=remoter_init_server, evalfun=remoter_server_eval) 365 | { 366 | initfun() 367 | 368 | while (TRUE) 369 | { 370 | set.status(continuation, FALSE) 371 | set.status(visible, FALSE) 372 | 373 | while (TRUE) 374 | { 375 | evalfun(env=env) 376 | 377 | if (get.status(continuation)) next 378 | 379 | if (get.status(should_exit)) 380 | return(invisible()) 381 | 382 | break 383 | } 384 | } 385 | 386 | return(invisible()) 387 | } 388 | -------------------------------------------------------------------------------- /vignettes/remoter.Rnw: -------------------------------------------------------------------------------- 1 | %\VignetteIndexEntry{Guide to the remoter Package} 2 | \documentclass[]{article} 3 | 4 | \input{./include/settings} 5 | 6 | 7 | \mytitle{Guide to the remoter Package} 8 | \mysubtitle{} 9 | \myversion{0.5-0} 10 | \myauthor{ 11 | \centering 12 | Drew Schmidt \\ 13 | \texttt{wrathematics@gmail.com} 14 | } 15 | 16 | 17 | 18 | \begin{document} 19 | 20 | \begin{figure}[ht] 21 | \vspace{-.5in} 22 | \begin{minipage}[c]{8.5in} 23 | \hspace{-1.0in} 24 | \includegraphics[width=8in,height=10in]{./cover/remoter.pdf} 25 | \end{minipage} 26 | \end{figure} 27 | 28 | \makefirstfew 29 | 30 | 31 | 32 | 33 | \section{Introduction}\label{introduction} 34 | 35 | % \begin{wrapfigure}{r}{0.4\textwidth} 36 | % \centering 37 | % \includegraphics[width=0.38\textwidth]{pics/remoter} 38 | % \end{wrapfigure} 39 | 40 | The \textbf{remoter} package \cite{remoter} allows you to control 41 | a remote R session from a local one. The local R session can be in a 42 | terminal, GUI, or IDE such as RStudio. The remote R session should be 43 | run in the background as, well, a server. 44 | 45 | The package uses \textbf{ZeroMQ} \cite{zeromq} by way of the R 46 | package \textbf{pbdZMQ} \cite{Chen2015pbdZMQpackage} to handle 47 | communication. Our use of pbdZMQ is specialized to client/server 48 | communications, but the package is very general. For more details about 49 | \textbf{pbdZMQ} see the \textbf{pbdZMQ} package vignette 50 | \cite{Chen2015pbdZMQvignette}. 51 | 52 | The work for remoter was born out of the \textbf{pbdCS} package 53 | \cite{pbdCS}, which is part of the Programming with Big Data in R 54 | (pbdR) project\cite{pbdR2012}. pbdR is a series of R packages 55 | that enable the usage of the R language on large distributed machines, 56 | like clusters and supercomputers. See 57 | \href{http://r-pbd.org}{r-pbd.org/}) for details. 58 | 59 | \subsection{Installation}\label{installation} 60 | 61 | You can install the stable version from CRAN using the usual 62 | \texttt{install.packages()}: 63 | 64 | \begin{lstlisting}[language=rr] 65 | install.packages("remoter") 66 | \end{lstlisting} 67 | 68 | In order to be able to create and connect to secure servers, you need to also 69 | install the \textbf{sodium} package. The use of \textbf{sodium} is optional 70 | because it is a non-trivial systems dependency, but it is highly recommended. 71 | You can install it manually with a call to \texttt{install.packages("sodium")} 72 | or by installing \textbf{remoter} via: 73 | 74 | \begin{lstlisting}[language=rr] 75 | install.packages("remoter", dependencies=TRUE) 76 | \end{lstlisting} 77 | 78 | For more information about the security features of \textbf{remoter}, see 79 | Section \ref{problems} below. 80 | 81 | The development version is maintained on GitHub, and can easily be installed by 82 | any of the packages that offer installations from GitHub: 83 | 84 | \begin{lstlisting}[language=rr] 85 | remotes::install_github("RBigData/remoter") 86 | \end{lstlisting} 87 | 88 | To simplify installations on cloud systems, we also have a 89 | \href{https://github.com/RBigData/pbdr-remoter}{Docker container} available. 90 | 91 | 92 | 93 | \subsection{Package Functions}\label{package-functions} 94 | 95 | The package contains numerous functions. Some should be called from 96 | regular R, and others only from inside a running client. 97 | 98 | The functions to call only from regular R (outside the client): 99 | 100 | 101 | \begin{longtable}[]{@{}ll@{}} 102 | \toprule 103 | Function & Description\tabularnewline 104 | \midrule 105 | \endhead 106 | \texttt{server()} & Create a server\tabularnewline 107 | \texttt{client()} & Interactively connect to a server\tabularnewline 108 | \texttt{batch()} & Send batch commands to\tabularnewline 109 | \texttt{relay()} & Launch an intermediary to relay commands between 110 | client/server\tabularnewline 111 | \bottomrule 112 | \end{longtable} 113 | 114 | Several of the functions to call only from inside the running client 115 | are: 116 | 117 | \begin{longtable}[]{@{}ll@{}} 118 | \toprule 119 | Function & Description\tabularnewline 120 | \midrule 121 | \endhead 122 | \texttt{c2s()} & Transport an object from the client to the 123 | server.\tabularnewline 124 | \texttt{s2c()} & Transport an object from the server to the 125 | client.\tabularnewline 126 | \texttt{exit()} & Disconnect the client from the server.\tabularnewline 127 | \texttt{shutdown()} & Disconnect from and shut down the 128 | server.\tabularnewline 129 | \texttt{showlog()} & View the server log.\tabularnewline 130 | \texttt{evalc()}, \texttt{lsc()}, \texttt{rmc()} & Client versions of 131 | \texttt{eval()}, \texttt{ls()}, and \texttt{rm()}\tabularnewline 132 | \bottomrule 133 | \end{longtable} 134 | 135 | We will discuss many of these functions throughout the remainder of this 136 | vignette. 137 | 138 | 139 | 140 | \section{Clients and Servers: Just the Basics} 141 | \label{clients-and-servers-just-the-basics} 142 | 143 | If you simply want to understand how \textbf{remoter} works, we do not 144 | need to involve remote computers right out the gate. Instead, we will 145 | create a local server and connect to it from another local R session. 146 | 147 | So the first thing to do is to start up 2 separate R sessions. One will 148 | be the \emph{server}, receiving commands, and the other will be the 149 | \emph{client}, sending them. 150 | 151 | \subsection{The Server}\label{the-server} 152 | 153 | In the R process designated to be the server, we will use the 154 | \texttt{server()} command to, well, start the server. Running this with 155 | no additional arguments will create a server. Optionally, one can 156 | specify a password via the \texttt{password} argument. Another useful 157 | feature is setting \texttt{showmsg=TRUE}, which will show in the server 158 | R process what messages are coming in. For now, let's run it with 159 | \texttt{showmsg=TRUE}: 160 | 161 | \begin{lstlisting}[language=rr] 162 | remoter::server(showmsg=TRUE) 163 | \end{lstlisting} 164 | 165 | That's it! That R session is now listening for commands. We can shut the 166 | server down in a few ways. Probably the best (particularly when dealing 167 | with remote machines) is from the client itself. More on this later. The 168 | other way is to kill the hosting R process. Finally, you can terminate 169 | the server with \texttt{ctrl+c}, but the other methods are preferred. 170 | 171 | \subsection{The Client}\label{the-client} 172 | 173 | Once the server is set up, we can connect to it with the 174 | \texttt{client()} command. Since we are connecting to a local server, 175 | the address we want to connect to is \texttt{"localhost"} (the default) 176 | or \texttt{"127.0.0.1"}. We will have to make sure that the 177 | \texttt{port} argument matches the listening port of our server, or 178 | we'll never connect. Finally, we can set the way the R prompt looks 179 | while the client is running by the \texttt{prompt} argument. You can set 180 | it to whatever you like, but disambiguating between your regular, local 181 | R session and the \textbf{remoter} client is very useful. Things can get 182 | confusing in a hurry if you aren't careful. 183 | 184 | So to connect, in our R session designated to be the client (the only 185 | one left), we would enter: 186 | 187 | \begin{lstlisting}[language=rr] 188 | remoter::client() 189 | \end{lstlisting} 190 | 191 | And you should be good to go. You can now enter R commands in the client 192 | and have them executed on the server. The following section will go into 193 | more detail about specifics on using the client/server setup. 194 | 195 | 196 | 197 | \section{Using remoter Interactively}\label{using-remoter} 198 | 199 | Before proceeding, make sure you understand how to set up a client and a 200 | server. See the previous section for details. 201 | 202 | \subsection{Philosophy}\label{philosophy} 203 | 204 | By default, all code entered to the client is executed on the remote 205 | server. There are several utility functions to help execute code on the 206 | local R session (see section below). But you should assume that anything 207 | entered into the client session, \emph{unless you explicitly specify to 208 | the contrary}, is executed only on the server. 209 | 210 | \subsection{Utility Functions}\label{utility-functions} 211 | 212 | There are a few utility functions available that have to do with 213 | handling execution of things locally or moving data between client and 214 | server. 215 | 216 | By default, all commands entered inside of the client are executed on 217 | the server. If you need to do some things in the local R session, you 218 | can kill the client and just reconnect when you're ready. Alternatively, 219 | you can use the \texttt{lsc()}, \texttt{rmc()}, and \texttt{evalc()} 220 | functions. These are client versions of \texttt{ls()}, \texttt{rm()}, 221 | and \texttt{eval()}. 222 | 223 | For moving data between client and server, there are the \texttt{s2c()} 224 | and \texttt{c2s()} commands which transfer from server to client and 225 | from client to server, respectively. These functions transfer data that 226 | resides in the memory of the client/server. To transfer a file, you can use 227 | \texttt{sendfile()} and \texttt{recvfile()}. This will transfer files in 228 | chunks rather than reading the entire file into memory. 229 | 230 | \subsection{Shutting Things Down}\label{shutting-things-down} 231 | 232 | To terminate the client, enter the command \texttt{exit()}. By default, 233 | this will terminate the local client only, and leave the server running. 234 | If you wish to also shut down the server with the client, you can run 235 | \texttt{exit(client.only=FALSE)}. For hopefully obvious reasons, you can 236 | not terminate the server and leave the client running. 237 | 238 | From the client side, running \texttt{exit()} will not shut down the 239 | interactive R session that was hosting the client. You can also 240 | disconnect the client from the server without shutting down the server 241 | by killing the client R session or executing \texttt{Ctrl-c} in the 242 | client. 243 | 244 | 245 | 246 | \section{Using remoter in Batch} 247 | 248 | Not every workflow works best interactively. This is why we also offer the 249 | \texttt{batch()} function. This allows you to pipe a script (either in a 250 | separate file, or typed out in the R session --- examples below) to a 251 | \textbf{remoter} server without having to interactively control things. 252 | 253 | Perhaps the simplest example is using the \texttt{script=} argument of 254 | \texttt{batch()}. 255 | 256 | \begin{lstlisting}[language=rr] 257 | remoter::batch(script="1+1") 258 | \end{lstlisting} 259 | 260 | Then, assuming a \textbf{remoter} session is running on the local 261 | machine\footnote{for using \texttt{batch()} with remote servers, the same 262 | caveats and rules apply as for \texttt{client()} --- see the \textit{Using 263 | remoter with Remote Machines}~\cite{remotemachineguide} guide for details} 264 | the scintillating result of "2" will be returned. 265 | 266 | You can also easily pipe off longer scripts stored in separate files. Say you 267 | have a script "myscript.r" like so: 268 | 269 | \begin{lstlisting}[language=rr, title=myscript.r] 270 | x <- 1 271 | y <- 2 272 | x + y 273 | \end{lstlisting} 274 | 275 | Then you can send it for evaluation to the \textbf{remoter} server by running: 276 | 277 | \begin{lstlisting}[language=rr] 278 | remoter::batch(file="myscript.r") 279 | \end{lstlisting} 280 | 281 | We conclude with a note of caution. If you have a "master script" that calls 282 | \texttt{source()} (or similar) on other scripts in different files, this will 283 | not work without modification. You should change your \texttt{source()} calls 284 | to the appropriate \texttt{batch()} call. In fact, you may think of 285 | \texttt{batch()} as \texttt{source()} for remote servers. 286 | 287 | 288 | 289 | \section{Security}\label{security} 290 | 291 | Security in \textbf{remoter} comes in two forms currently: 292 | 293 | \begin{enumerate} 294 | \item password credentialing 295 | \item public key encryption 296 | \end{enumerate} 297 | 298 | The password is declared when the server is spawned as a launch option 299 | in \texttt{remoter::server()}. Without the use of encryption, it will be 300 | transmitted from client to server unsecurely. 301 | 302 | Encryption is optional, and disabled by default. This is because 303 | encryption is handled by the \textbf{sodium} package 304 | \cite{sodium}, which uses the \textbf{libsodium} 305 | \cite{libsodium} library, which can be difficult to build on some 306 | platforms. 307 | 308 | If you have the \textbf{sodium} package installed \emph{on both the 309 | client and the server}, start the server with the option 310 | \texttt{secure=TRUE}, and your client will automatically connect 311 | securely. If the server was launched (by necessity or optionally) with 312 | \texttt{secure=FALSE}, then the client can not connect securely, even if 313 | the client machine has the \textbf{sodium} package installed. 314 | 315 | If ever in any doubt, use the \texttt{is.secure()} command from the 316 | client to see if communications are encrypted. 317 | 318 | 319 | % 320 | % \section{Comparisons to the futures Package} 321 | % 322 | % TODO 323 | 324 | 325 | 326 | \section{Problems, Bugs, and Other Maladies} 327 | \label{problems} 328 | 329 | The package should basically be useable, but there are some issues you 330 | might want to be aware of. 331 | 332 | \textbf{Problem}: I lost my internet connection and the client is no 333 | longer sending messages. 334 | 335 | \textbf{Solution}: Just \texttt{Ctrl+c} the client and re-run the 336 | \texttt{remoter::client()} call and you should be good to go. The server 337 | should still be running. You can therefore also have multiple clients 338 | connect to the same server, and they will share the same data (though 339 | they will not see each other's commands). I actually consider this a 340 | feature, but I'm not married to it and I could probably be convinced to 341 | change it. 342 | 343 | \textbf{Problem}: The up/down arrow keys don't work right in the R 344 | terminal when using the client. 345 | 346 | \textbf{Explanation}: That's because the client is just some R code 347 | sitting on top of the R REPL. This shouldn't be a problem if you're 348 | using an IDE like RStudio or the like, where you pass commands from an 349 | editor window to the R session. But as far as I am aware, this can not 350 | be fixed. 351 | 352 | \textbf{Problem}: There's no security! 353 | 354 | \textbf{Explanation}: Communications are optionally encrypted, if the 355 | \textbf{sodium} package is available. The reason it is optional is that 356 | \textbf{libsodium} is actually a fairly weighty systems dependency. This is a 357 | big problem for managed machines like clusters and supercomputers. 358 | You must have \textbf{sodium} installed on both 359 | the client and server machine, and start the server with the option 360 | \texttt{secure=TRUE} to use this, however. 361 | 362 | There is also a password system. Passwords are read in from the user/client 363 | (and optionally at server creation) with the \textbf{getPass} 364 | package~\cite{getPassPackage}. This will read with masking (i.e., without 365 | printing the password as it is typed). See the \textbf{getPass} package 366 | vignette~\cite{getPassVignette} for more details. 367 | 368 | Passwords are always hashed, whether or not the \textbf{sodium} package is 369 | available, as the hashing is done with the \textbf{argon2} 370 | package~\cite{argon2}. This is a reasonably new algorithm, and is believed to 371 | be very secure. 372 | 373 | All that said, I am not a security person, so it is entirely possible that I 374 | have messed something up. Just know that I'm trying my best, and that if you 375 | believe something to be in error, I'd really like to know about it. 376 | 377 | \textbf{Problem}: Something else is wrong! 378 | 379 | \textbf{Explanation}: Please be so kind as to 380 | \href{https://github.com/wrathematics/remoter/issues}{file an issue} 381 | describing the problem. Be as descriptive as possible. 382 | 383 | 384 | 385 | \addcontentsline{toc}{section}{References} 386 | \bibliography{./include/remoter} 387 | \bibliographystyle{plain} 388 | 389 | 390 | \end{document} 391 | --------------------------------------------------------------------------------