First commit
This commit is contained in:
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
/matlabhub/config.yml
|
||||
/build/
|
||||
/matlabhub.egg-info/
|
||||
3
.idea/.gitignore
generated
vendored
Normal file
3
.idea/.gitignore
generated
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
# Default ignored files
|
||||
/shelf/
|
||||
/workspace.xml
|
||||
27
.idea/inspectionProfiles/Project_Default.xml
generated
Normal file
27
.idea/inspectionProfiles/Project_Default.xml
generated
Normal file
@@ -0,0 +1,27 @@
|
||||
<component name="InspectionProjectProfileManager">
|
||||
<profile version="1.0">
|
||||
<option name="myName" value="Project Default" />
|
||||
<inspection_tool class="PyPackageRequirementsInspection" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="ignoredPackages">
|
||||
<value>
|
||||
<list size="14">
|
||||
<item index="0" class="java.lang.String" itemvalue="os" />
|
||||
<item index="1" class="java.lang.String" itemvalue="multiprocessing" />
|
||||
<item index="2" class="java.lang.String" itemvalue="sys" />
|
||||
<item index="3" class="java.lang.String" itemvalue="pickle" />
|
||||
<item index="4" class="java.lang.String" itemvalue="re" />
|
||||
<item index="5" class="java.lang.String" itemvalue="collections" />
|
||||
<item index="6" class="java.lang.String" itemvalue="functools" />
|
||||
<item index="7" class="java.lang.String" itemvalue="inspect" />
|
||||
<item index="8" class="java.lang.String" itemvalue="json" />
|
||||
<item index="9" class="java.lang.String" itemvalue="javabridge" />
|
||||
<item index="10" class="java.lang.String" itemvalue="time" />
|
||||
<item index="11" class="java.lang.String" itemvalue="skimage" />
|
||||
<item index="12" class="java.lang.String" itemvalue="yaml" />
|
||||
<item index="13" class="java.lang.String" itemvalue="pytest-runner" />
|
||||
</list>
|
||||
</value>
|
||||
</option>
|
||||
</inspection_tool>
|
||||
</profile>
|
||||
</component>
|
||||
6
.idea/inspectionProfiles/profiles_settings.xml
generated
Normal file
6
.idea/inspectionProfiles/profiles_settings.xml
generated
Normal file
@@ -0,0 +1,6 @@
|
||||
<component name="InspectionProjectProfileManager">
|
||||
<settings>
|
||||
<option name="USE_PROJECT_PROFILE" value="false" />
|
||||
<version value="1.0" />
|
||||
</settings>
|
||||
</component>
|
||||
11
.idea/matlabhub.iml
generated
Normal file
11
.idea/matlabhub.iml
generated
Normal file
@@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="PYTHON_MODULE" version="4">
|
||||
<component name="NewModuleRootManager">
|
||||
<content url="file://$MODULE_DIR$">
|
||||
<excludeFolder url="file://$MODULE_DIR$/build" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/matlabhub.egg-info" />
|
||||
</content>
|
||||
<orderEntry type="jdk" jdkName="Python 3.10" jdkType="Python SDK" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
</component>
|
||||
</module>
|
||||
7
.idea/misc.xml
generated
Normal file
7
.idea/misc.xml
generated
Normal file
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.10" project-jdk-type="Python SDK" />
|
||||
<component name="PyCharmProfessionalAdvertiser">
|
||||
<option name="shown" value="true" />
|
||||
</component>
|
||||
</project>
|
||||
8
.idea/modules.xml
generated
Normal file
8
.idea/modules.xml
generated
Normal file
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectModuleManager">
|
||||
<modules>
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/matlabhub.iml" filepath="$PROJECT_DIR$/.idea/matlabhub.iml" />
|
||||
</modules>
|
||||
</component>
|
||||
</project>
|
||||
6
.idea/vcs.xml
generated
Normal file
6
.idea/vcs.xml
generated
Normal file
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
||||
35
README.md
Normal file
35
README.md
Normal file
@@ -0,0 +1,35 @@
|
||||
# Matlab Hub
|
||||
|
||||
Start proxied matlab instances from a webpage using Nginx.
|
||||
|
||||
## Preequisites
|
||||
|
||||
Linux with Matlab and Python3
|
||||
|
||||
## Installation
|
||||
|
||||
make matlabhub/config.py:
|
||||
|
||||
license_file: 'path_to_license.lic'
|
||||
port_range: [10000, 65536]
|
||||
|
||||
install:
|
||||
|
||||
sudo apt install nginx screen
|
||||
sudo pip install matlabhub
|
||||
sudo cp matlab /etc/nginx/sites-available/
|
||||
sudo ln -s /etc/nginx/sites-available/matlab /etc/nginx/sites-enabled/matlab
|
||||
sudo cp matlab.service /lib/systemd/system/
|
||||
sudo systemctl daemon-reload
|
||||
sudo systemctl enable nginx
|
||||
sudo systemctl enable matlab
|
||||
sudo service nginx start
|
||||
sudo service matlab start
|
||||
|
||||
## Configuration
|
||||
|
||||
Change the user, path to the socket file and the port (default: 8585) in matlab and matlab.service.
|
||||
|
||||
## Usage
|
||||
|
||||
Browse to http://127.0.0.1:8585
|
||||
23
matlab
Normal file
23
matlab
Normal file
@@ -0,0 +1,23 @@
|
||||
# nginx configuration
|
||||
|
||||
server {
|
||||
listen 8585 default_server;
|
||||
listen [::]:8585 default_server;
|
||||
|
||||
root /var/www/html;
|
||||
|
||||
index index.html;
|
||||
|
||||
server_name _;
|
||||
|
||||
location ~ ^/([0-9]+) {
|
||||
proxy_set_header Host '127.0.0.1';
|
||||
proxy_pass http://127.0.0.1:$1;
|
||||
}
|
||||
|
||||
location / {
|
||||
include proxy_params;
|
||||
proxy_pass http://unix:/tmp/matlabhub.sock;
|
||||
}
|
||||
}
|
||||
|
||||
17
matlab.service
Normal file
17
matlab.service
Normal file
@@ -0,0 +1,17 @@
|
||||
[Unit]
|
||||
Description=Matlab web server
|
||||
After=network-online.target firewalld.service
|
||||
Wants=network-online.target
|
||||
|
||||
[Service]
|
||||
User=matlab-user
|
||||
Type=simple
|
||||
ExecStart=/usr/local/bin/gunicorn --bind unix:/tmp/matlabhub.sock matlabhub.wsgi:application
|
||||
ExecStop=/bin/kill -s STOP $MAINPID
|
||||
ExecReload=/bin/kill -s HUP $MAINPID
|
||||
TimeoutSec=0
|
||||
RestartSec=2
|
||||
Restart=always
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
96
matlabhub/app.py
Executable file
96
matlabhub/app.py
Executable file
@@ -0,0 +1,96 @@
|
||||
#!/usr/bin/env python3.10
|
||||
|
||||
import os
|
||||
import sys
|
||||
import re
|
||||
import socket
|
||||
import subprocess
|
||||
import random
|
||||
import yaml
|
||||
from flask import Flask, redirect, render_template, request
|
||||
from time import sleep
|
||||
|
||||
with open(os.path.join(os.path.dirname(__file__), 'config.yml')) as conf_file:
|
||||
config = yaml.safe_load(conf_file)
|
||||
|
||||
|
||||
def is_port_in_use(port):
|
||||
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
|
||||
return s.connect_ex(('127.0.0.1', port)) == 0
|
||||
|
||||
|
||||
class Matlab:
|
||||
def __init__(self, port, group=None):
|
||||
self.port = int(port)
|
||||
self.group = group
|
||||
self.screen_name = f'matlab_{port}' if group is None else f'matlab_{group}_{port}'
|
||||
|
||||
def start(self):
|
||||
command = f'screen -S {self.screen_name} -d -m env MWI_BASE_URL="/{self.port}" ' \
|
||||
f'MWI_APP_PORT={self.port} MLM_LICENSE_FILE={config["license_file"]} matlab-proxy-app'
|
||||
subprocess.Popen(command, shell=True, start_new_session=True)
|
||||
|
||||
def stop(self):
|
||||
subprocess.run(f"screen -S {self.screen_name} -X stuff '^C'", shell=True)
|
||||
|
||||
|
||||
class Hub(dict):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.update({matlab.port: matlab for matlab in self.find_running()})
|
||||
|
||||
|
||||
def find_running(self):
|
||||
p = subprocess.run('screen -list', shell=True, capture_output=True)
|
||||
return [Matlab(port, group) for group, port in re.findall(r'\.matlab_([^_]+)_(\d+)', str(p.stdout))]
|
||||
|
||||
def new(self, group=None):
|
||||
group = re.escape(re.sub(r'[^\w]|_', '', group))
|
||||
ports = list(range(*config['port_range']))
|
||||
random.shuffle(ports)
|
||||
for port in ports:
|
||||
if port not in self and not is_port_in_use(port):
|
||||
self[port] = Matlab(port, group)
|
||||
self[port].start()
|
||||
sleep(5)
|
||||
return redirect(f'/{port}')
|
||||
|
||||
def stop(self, port):
|
||||
self.pop(port).stop()
|
||||
|
||||
def get_groups(self):
|
||||
return list(set(matlab.group for matlab in self.values()))
|
||||
|
||||
def get_ports(self, group=None):
|
||||
return [matlab.port for matlab in self.values() if matlab.group == group]
|
||||
|
||||
|
||||
sys.argv = sys.argv[:1]
|
||||
hub = Hub()
|
||||
app = Flask(__name__)
|
||||
|
||||
|
||||
@app.route('/')
|
||||
def index():
|
||||
return redirect('/default')
|
||||
|
||||
|
||||
@app.route('/<group>')
|
||||
def group(group):
|
||||
return render_template('index.html', ports=hub.get_ports(group), groups=hub.get_groups(), group=group)
|
||||
|
||||
|
||||
@app.route('/<group>/start/new')
|
||||
def group_start_new(group):
|
||||
return hub.new(group)
|
||||
|
||||
|
||||
@app.route('/<group>', methods=['POST'])
|
||||
def start_new_post(group):
|
||||
return hub.new(request.form['group'])
|
||||
|
||||
|
||||
@app.route('/<group>/stop/<port>')
|
||||
def stop(group, port):
|
||||
hub.stop(int(port))
|
||||
return redirect(f'/{group}')
|
||||
37
matlabhub/templates/index.html
Normal file
37
matlabhub/templates/index.html
Normal file
@@ -0,0 +1,37 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<style>
|
||||
.block {
|
||||
padding: 5px;
|
||||
margin: 5px;
|
||||
display: inline-block;
|
||||
border: 5px solid #193f6f;
|
||||
text-align: center;
|
||||
}
|
||||
</style>
|
||||
<title>Matlab Hub</title>
|
||||
</head>
|
||||
<body>
|
||||
<div class="block" align="center">
|
||||
<h1>Matlab Hub</h1>
|
||||
<br />
|
||||
<h4>Start new matlab in group:</h4>
|
||||
<form method="POST" target="_blank">
|
||||
<input name="group" value="{{group}}">
|
||||
<input type="submit" value="start">
|
||||
</form>
|
||||
<br />
|
||||
<h4>Groups:</h4>
|
||||
{% for group in groups %}
|
||||
<a href="/{{group}}">{{group}}</a><br />
|
||||
{% endfor %}
|
||||
<h4>Matlabs in group {{group}}:</h4>
|
||||
{% for port in ports %}
|
||||
<a href="/{{port}}" target="_blank">{{port}}</a> <a href="/{{group}}/stop/{{port}}">stop</a><br />
|
||||
{% endfor %}
|
||||
<br />
|
||||
<a href="https://github.com/wimpomp/matlabhub">Matlab Hub</a> by Wim Pomp
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
3
matlabhub/wsgi.py
Normal file
3
matlabhub/wsgi.py
Normal file
@@ -0,0 +1,3 @@
|
||||
import os
|
||||
import sys
|
||||
from matlabhub.app import app as application
|
||||
26
setup.py
Normal file
26
setup.py
Normal file
@@ -0,0 +1,26 @@
|
||||
import setuptools
|
||||
|
||||
with open('README.md', 'r') as fh:
|
||||
long_description = fh.read()
|
||||
|
||||
setuptools.setup(
|
||||
name='matlabhub',
|
||||
version='2022.7.0',
|
||||
author='Wim Pomp @ Lenstra lab NKI',
|
||||
author_email='w.pomp@nki.nl',
|
||||
description='matlabhub',
|
||||
long_description=long_description,
|
||||
long_description_content_type='text/markdown',
|
||||
url='https://github.com/wimpomp/matlabhub',
|
||||
packages=['matlabhub'],
|
||||
classifiers=[
|
||||
'Programming Language :: Python :: 3',
|
||||
'License :: OSI Approved :: GNU General Public License v3 (GPLv3)',
|
||||
'Operating System :: OS Independent',
|
||||
],
|
||||
python_requires='>=3.8',
|
||||
install_requires=['matlab-proxy', 'flask'],
|
||||
entry_points={'console_scripts': ['matlabhub=matlabhub:main'], },
|
||||
package_data={'': ['templates/*', 'config.yml']},
|
||||
include_package_data=True,
|
||||
)
|
||||
Reference in New Issue
Block a user