Home

Awesome

Netmount

Websocket based file transfer system for ComputerCraft.

Connecting

Netmount uses the Basic HTTP authentication scheme to secure WebSocket connections, WebDAV connections, and API calls.

ComputerCraft

mount.lua

Netmount ships with a client program on the URL path /mount.lua This is the only non-password protected resource. To use, on a CC Computer run:

> wget run <url>/mount.lua [url=<url>] [username=<username>] [password=<password>] [path=<path>] [run=<program>]

or use the settings API:

> set netmount.url <url>
> set netmount.username <username>
> set netmount.password <password>
> set netmount.path <path>
> set netmount.run <program>
> wget run <url>/mount.lua

where:

For information on setting up a username and password, see Production Setup

API

In addition to mount.lua, a more programmatic method of using netmount is provided on the URL path: /api.lua. An example script outlining its basic functionality:

-- The api.lua file is well documented, please check it out if you're confused.
-- Replace localhost:4000 with your netmount provider!
local url = "http://localhost:4000/"
local handle = http.get(url.."api.lua") -- Get the latest version of the API
local _, nm = assert(pcall(assert(load(handle.readAll(), "nmapi", nil, _ENV)))) -- Quickly read, load, and execute in a very dumb way
handle.close() -- Close handle responsibly
state = nm.createState(url, settings.get("netmount.username"), settings.get("netmount.password")) -- Create a netmount "state" from the URL, username, and password
-- The state consists of the users credentials, the attributes of all files on the netmount server, and a few functions to transmit and receive messages from the server.
_G.nm = nm -- Expose the netmount API for tinkering
_G.state = state -- Expose the netmount user state for tinkering
_G.nfs = nm.createFs(state) -- Create a clone of the fs API, with the root being the root of your netmount user directory.
-- Note that unlike mount.lua this API only works on the remote files. You can't move or copy files locally without creating a remote and a local handle using fs.open (and nfs.open).
parallel.waitForAny(function() shell.run("lua") end, nm.getSyncHandler(state), nm.getConnectionHandler(state)) -- Run the lua program for tinkering in parallel with a syncHandler and connectionHandler.
-- The sync handler is a function that keeps the local netmount state in sync with the remote state. This MUST be put in parallel with your code in order for netmount to work. Ideally it should be run with no yielding between the state creation and its execution.
-- The connection handler is a function that will attempt to reconnect with the netmount server after a unexpected disconnect, repairing the state object in the process. This MAY be put in parallel with your code, netmount will work fine without it. However if the connection fails, you're responsible for creating a new state to reconnect.
state.close() -- Close the websocket behind the state responsibly.

WebDAV

By default, Netmount also provides a WebDAV server at the /webdav endpoint. The same URL used as an argument in mount.lua gets used here. If the host has configured a custom port for webdav, it's expected that this may be at a different location.

Configuration

Configuration is done with a .env file, and if using multi-user setups, with an additional JSON file.

.env File

The bare minimum required to get a single user setup working:

USERNAME=username
PASSWORD=password

where username and password are your choice of credentials.

Optional valid env values include:

JSON File

Supply a path to the JSON file using the USERLIST variable in the .env file. Remove USERNAME and PASSWORD values if they're present. They will be added to the json file like so:

{ // Don't forget to remove all of these comments if directly copying this file!!!
    "config": { // Global config. This object is optional
        "limit": 1000000, // 1 MB global limit
        "path": "./custom/path" // Custom path to global data
    },
    "users": [ 
        {
            "username": "username",
            "password": "password",
            "config": { // Per-user config. This object is optional
                "limit": 10000000, // 10 MB limit for this user
                "path": "./mypath" // By default all user data will be stored in a folder using the username in the default/global path. This changes that behavior.
            }
        },
        {
            "username": "foobarbat",
            "password": "verysecurepassword" // Due to netmount using basic http auth, please use a secure and long password!
        }

    ]
}

Users added to the JSON file once saved, assuming no syntax errors, get added without needing to restart the server. Auto removals are NYI. All limits and custom paths are respected by the WebDAV endpoint, no further configuration needed.

API

If using the USERLIST environment variable, an API on the /api path will be added. The API is only accessible by users whose config has the isAdministrator flag set to true. All requests return a 200 status code, with an ok boolean in the response object to flag whether the operation really was successful. Endpoints include:

user/add

POST Add a user with the given username, password, and optional config:

{
    "username": "foo",
    "password": "bar",
    "config": {
        "limit": 100000000
    }
}

Response:

{
    "ok": true
}

user/modify

POST Modify a users config:

{
    "username": "foo",
    "config": {
        "limit": 50000000
    }
}

Response:

{
    "ok": true
}

user/remove

POST Delete a user:

{
    "username": "foo"
}

Response:

{
    "ok": true
}

user/list

GET List all users:

{}

Response:

{
    "ok": true,
    "users": [
        {
            "username": "foo",
            "password": "bar",
            "config": {...}
        },
        ...
    ]
}

user/get

GET Get a user with the given username:

{
    "username": "foo"
}

Response:

{
    "ok": true,
    "user": {
        "username": "foo",
        "password": "bar",
        "config": {...}
    }
}

drive/capacity

GET Get the free and total capacity of the drive containing the data directory, in bytes:

{}

Response:

{
    "ok": true,
    "capacity": [ 300000, 1000000 ]
}

Workspace Setup

To install required packages:

$ yarn

Then run:

$ yarn run ts-node src/index.ts

The server runs on localhost:4000.

Production Setup

Follow Workspace Setup steps up until running the server.

Compile project:

$ yarn run build

For all further steps replace SITENAME with the domain name.

Casketfile

Casket is an optional step to enable automatic SSL cert handling, and proxying to the netmount from a domain. For more information see Casket's README

This example specifically takes into account Cloudflare's proxying system. If you are not using CF, the cloudflare block may be removed, and the import cloudflare line in the SITENAME block can be removed/commented out.

# List of cloudflare IPs to unwrap to real IPs.
(cloudflare) {
    realip {
        # Force cloudflare
        # https://www.cloudflare.com/en-gb/ips/

        from 173.245.48.0/20
        from 103.21.244.0/22
        from 103.22.200.0/22
        from 103.31.4.0/22
        from 141.101.64.0/18
        from 108.162.192.0/18
        from 190.93.240.0/20
        from 188.114.96.0/20
        from 197.234.240.0/22
        from 198.41.128.0/17
        from 162.158.0.0/15
        from 104.16.0.0/13
        from 104.24.0.0/14
        from 172.64.0.0/13
        from 131.0.72.0/22

        from 2400:cb00::/32
        from 2606:4700::/32
        from 2803:f800::/32
        from 2405:b500::/32
        from 2405:8100::/32
        from 2a06:98c0::/29
        from 2c0f:f248::/32

        strict
    }
}

https://SITENAME/ {
        import cloudflare # Comment out if not using cloudflare proxying
        tls self_signed # Self-sign the TLS. If not using cloudflare proxying, remove 'self_signed'.
        log /var/log/SITENAME.access.log
        proxy / 127.0.0.1:4000 {
            transparent # allow the app on 4000 to see visitor's IPs via X-Forwarded-For header
            websocket
        }
}

If logging access, run:

$ touch /var/log/SITENAME.access.log

This may have to be run as root, and chown'd to the www-data user.

Systemd

  1. Edit netmountcc.service to include your username and password on line 25.
  2. Move/Copy netmountcc.service to /etc/systemd/system/netmountcc.service. a. If not using casket, remove casket.service from line 3.
  3. Either: a. Put Netmount's working directory in /var/www, with www-data read & execute permissions. OR b. Create a symlink at /var/www/netmount to the working directory, with the same permissions as above.
  4. Create the data directory (by default ./data), and ensure www-data has read/write permissions to ./data.
  5. Enable/Start the service:
$ systemctl enable netmountcc
$ systemctl start netmountcc