Local network access of single-user Cylc UI Server?

Hello everyone,

Is it possible to access a single-user UI Server from another computer on the same network ?

I tried adding the line

c.ServerApp.local_hostnames = ['localhost', 'local.ip.of.2nd.computer']

to the jupyter_config.py file, then running cylc gui --no-browser and logging in at http://cylc.computer.ip:8888/cylc?token=unclaimed_token from the other machine using the generated token.

But it didn’t seem to do the trick. Yet I know the two machines can communicate, since I successfully accessed other web services (including a blank JupyterHub server).

If possible, I’d like to to be able to monitor/control the workflow without setting up the whole Jupyter Hub, only the Jupyter Server. I only need one user.

Thanks in advance !

Did the UI Server logs show any (failed) HTTP requests when you tried to access the URL? What did the browser show?

No, the logs didn’t show any attempt to connect to the server. Which is weird. The browser (Android Firefox) says something about timeout of request. No error code, but I guess HTTP 408 ?

If that helps, the command lsof -Pn -i4 returns :

[...]
python3.9 71271 elliotfontaine   24u  IPv4 0x17952947fedd7a43      0t0  TCP 127.0.0.1:8888 (LISTEN)
python3.9 71271 elliotfontaine   26u  IPv4 0x179529480615aa43      0t0  TCP 127.0.0.1:8888->127.0.0.1:58207 (ESTABLISHED)

Here’s the complete jupyter_config.py for more context. It is the default one, except for the last line.

jupyter_config.py
# Copyright (C) NIWA & British Crown (Met Office) & Contributors.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.

# Configuration file for jupyterhub.

from pathlib import Path
import pkg_resources

from cylc.uiserver import (
    __file__ as uis_pkg,
    getenv)
from cylc.uiserver.app import USER_CONF_ROOT
from cylc.uiserver.authorise import CylcAuthorizer


# the command the hub should spawn (i.e. the cylc uiserver itself)
c.Spawner.cmd = ['cylc', 'hubapp']

# the spawner to invoke this command
c.JupyterHub.spawner_class = 'jupyterhub.spawner.LocalProcessSpawner'

# environment variables to pass to the spawner (if defined)
c.Spawner.environment = getenv(
    # site config path override
    'CYLC_SITE_CONF_PATH',
    # used to specify the Cylc version if using a wrapper script
    'CYLC_VERSION',
    'CYLC_ENV_NAME',
    # may be used by Cylc UI developers to use a development UI build
    'CYLC_DEV',
)
# this auto-spawns uiservers without user interaction
c.JupyterHub.implicit_spawn_seconds = 0.01

# apply cylc styling to jupyterhub
c.JupyterHub.logo_file = str(Path(uis_pkg).parent / 'logo.svg')
c.JupyterHub.log_datefmt = '%Y-%m-%dT%H:%M:%S'  # ISO8601 (expanded)
c.JupyterHub.template_paths = [
    # custom HTML templates
    pkg_resources.resource_filename(
        'cylc.uiserver',
        'templates'
    )
]

# store JupyterHub runtime files in the user config directory
USER_CONF_ROOT.mkdir(parents=True, exist_ok=True)
c.JupyterHub.cookie_secret_file = f'{USER_CONF_ROOT / "cookie_secret"}'
c.JupyterHub.db_url = f'{USER_CONF_ROOT / "jupyterhub.sqlite"}'
c.ConfigurableHTTPProxy.pid_file = f'{USER_CONF_ROOT / "jupyterhub-proxy.pid"}'

# write Cylc logging to the user config directory
# NOTE: Parallel UIS instances will conflict over this file.
#       See https://github.com/cylc/cylc-uiserver/issues/240
c.CylcUIServer.logging_config = {
    'version': 1,
    'handlers': {
        'file': {
            'class': 'logging.handlers.RotatingFileHandler',
            'level': 'INFO',
            'filename': str(USER_CONF_ROOT / 'log' / 'log'),
            'mode': 'a',
            'backupCount': 5,
            'maxBytes': 10485760,
            'formatter': 'file_fmt',
        },
    },
    'loggers': {
        'CylcUIServer': {
            'level': 'INFO',
            'handlers': ['console', 'file'],
        },
        'cylc': {
            'level': 'INFO',
            'handlers': ['console', 'file'],
        },
    },
    'formatters': {
        'file_fmt': {
            'format': '%(asctime)s %(levelname)-8s %(message)s',
            'datefmt': '%Y-%m-%dT%H:%M:%S',
        }
    },
}


# Define the authorization-policy for Jupyter Server.
# This prevents users being granted full access to extensions such as Jupyter
# Lab as a result of being granted the ``access:servers`` permission in Jupyter
# Hub.
c.ServerApp.authorizer_class = CylcAuthorizer

c.ServerApp.local_hostnames = ['localhost', '100.72.145.195']

Yes, it’s possible, try setting the ServerApp.ip configuration to 0.0.0.0.

E.G: cylc gui --ip=0.0.0.0

Search for ServerApp.ip on this page for more info: Config file and command line options — Jupyter Server documentation

As always when opening up a server to wider access, ensure you’ve made the relevant security checks.

Note, the reason you didn’t see any 403 error codes is that, by default, Jupyter Server only listens on the local host, so ignores external requests.

If you’re looking to distribute the Cylc GUI, you might also want to look into cylc hub (aka Jupyter Hub with a pre-loaded configuration for spawning Cylc GUI servers). Jupyter Hub can integrate with your authentication system removing the need for token / password based authentication. It also listens on 0.0.0.0 by default.