Accessing encrypted remotes from iOS


#1

Hi,

I’m currently using GSuite business for unlimited backups to Google Drive. I’m using DriveStream to do the actual transfer, and use a subfolder (g:\encrypted) as the input to a crypt remote.

How can I browse encrypted content on iOS? Since there isn’t an iOS app available, the only solution I can think of is using a VPS to host a rclone serving e.g, ftp / http / webdav. However, this isn’t as efficient as having an app that is able to directly talk to Google Drive and perform the decryption.

  1. Does anyone have any other suggestions as to how to do this?
  2. Are there any technical limitations on iOS that would make writing an app difficult?

#2

That would certainly work.

rclone builds under IOS I know that. So someone could write a frontend for it which runs on IOS.


#3

Hm, the first way I can think of that comes to mind would be to spawn rclone to do various things, e.g rclone ls, then parse its output.

I’m not familiar with how iOS works, so this assumes iOS lets you spawn another subprocess like you can in most other languages.

Does Rclone have a e.g, json based API which would be easier to use?


#4

Yes it does - check out https://rclone.org/rc/

It would be possible to build, eg a react native frontent for rclone using that. I’ve build a proof of concept react app that way.


#5

Hm, so if an iOS app can run a rclone subprocess, then technically this is possible.

I’d like to try doing this, since I’ve been looking around for an idea to work on to learn iOS.


#6

That would be interesting!

I do build an IOS binary on circleCI

https://1496-17803236-gh.circle-artifacts.com/0/tmp/rclone.dist/rclone-ios-5.0-arm64
https://1496-17803236-gh.circle-artifacts.com/0/tmp/rclone.dist/rclone-ios-5.0-armv7

I haven’t tried them though as I don’t have an IOS device, but I think they should work…


#7

I understand why this likely hasn’t been attempted before; iOS doesn’t allow running other binaries from your application, so that precludes using the remote control API unfortunately.

The only way is to create glue code to call Rclone’s functions from iOS, and I don’t think rclone is designed as a package to be easily used by other programs.


#8

Actually, I just realized that such an app wouldn’t require the full rclone remote control API; just enough to do the following:

  • Reads in a rclone configuration file from desktop
  • Browsing potentially encrypted remotes
  • Download files or folders
  • Streaming media

Nice to have:

  • upload files and folders
  • Move, rename, and delete files and folders

#9

rclone is quite modular with a separation of concerns between the backends the operations and the cli interface. You could build a go program which used rclone’s operations quite easily.


#10

Can you point me to which go packages I should look at to do the following?

  • Reads in a rclone configuration file
  • List dirs in a specified remote
  • Download the specified file/folder on a remote

#11

The best thing to do is to start looking tracing the code from the command you are interested in.

So if you are interested in listing, then start here

There is an overview of which bits of code do what here


#12

Thanks, that’s really helpful. I’ll get started by implementing a way of reading config files, and listing remotes in iOS.


#13

I see the functions for listing a directory in a remote, but couldn’t figure out how it read from configuration files.

If I use the NewFsSrc function, does it automatically load configuration from the default path if not already loaded? I’d need to be able to control at least where the config is auto-read from.


#14

Yes it will read the config from the default place. I htink you can set it by overriding config.ConfigPath. It might be easier to set environment variables RCLONE_CONFIG or pass the --config flag to start with.


#15

I have a prototype app that lets you paste an existing config, which gets saved on the phone. I can also make rclone load the configuration from that file on the phone, and I also see the list of expected remotes in the config.

However, when I try to list a remote’s contents in JSON format (path: “google-drive-iphone:/public”), I get the puzzling log that the remote is not found, even though the app correctly sees that “google-drive-iphone” is a recognized remote:

2018/12/25 16:28:48 NOTICE: Config file "/var/mobile/.rclone.conf" not found - using defaults
2018/12/25 16:28:48 Failed to create file system for "google-drive-iphone:/public": didn't find section in config file

It seems that for some reason, its trying to read the config file again from a default location when I create a new Fs, but can’t figure out why.

This is the relevant code in mobile.go which gets called by the application:

package mobile

import (
	"encoding/json"
	"sort"
	"strings"

	"rclone/fs"
	"rclone/fs/config"
	"rclone/fs/operations"

	"github.com/ncw/rclone/cmd"
	"github.com/pkg/errors"
)

// LoadConfig loads the config file
func LoadConfig(path string) bool {
	config.ConfigPath = path
	err := config.LoadConfig() // modified this from the original function to expose errors
	return err == nil
}

func ListRemotes() string {
	remotes := config.FileSections()
	sort.Strings(remotes)
	return strings.Join(remotes, "\n") // gomobile doesn't support string[]
}

func ListJson(path string) string {
	var opt operations.ListJSONOpt
	fsrc := cmd.NewFsSrc([]string{path})
	var b strings.Builder
	b.WriteString("[")
	isFirst := true
	err := operations.ListJSON(fsrc, "", &opt, func(item *operations.ListJSONItem) error {
		out, err := json.Marshal(item)
		if err != nil {
			return errors.Wrap(err, "failed to marshal list object")
		}

		if isFirst {
			isFirst = false
		} else {
			b.WriteString(",\n")
		}
		b.WriteString(string(out))
		return nil
	})

	if err != nil {
		return "[]"
	}

	b.WriteString("]")
	return b.String()
}

#16

Good work :smile:

Hmm, the config file will get reloaded unless fs/config.configFile != nil - does your modified version set it?


#17

Yeah, this is the modified function.

// LoadConfig loads the config file
func LoadConfig() error {
	// Load configuration file.
	var err error
	configFile, err = LoadConfigFile()
	if err == errorConfigFileNotFound {
		fs.Logf(nil, "Config file %q not found - using defaults", ConfigPath)
		configFile, _ = goconfig.LoadFromReader(&bytes.Buffer{})
	} else if err != nil {
		log.Fatalf("Failed to load config file %q: %v", ConfigPath, err)
		return err
	} else {
		fs.Debugf(nil, "Using config file from %q", ConfigPath)
	}

	// Start the token bucket limiter
	accounting.StartTokenBucket()

	// Start the bandwidth update ticker
	accounting.StartTokenTicker()

	// Start the transactions per second limiter
	fshttp.StartHTTPTokenBucket()
	return nil
}

I’ll add more log statements to try to figure out if the variable got reset somehow.


#18

Did you figure out the problem?