Compare commits

..

No commits in common. "c3d3ad26ce337c8db1f1dde647b62da73846dd41" and "c0edb54370f23e27f71827d57b37a0ac9181a67a" have entirely different histories.

11 changed files with 523 additions and 626 deletions

5
.gitignore vendored
View File

@ -1,5 +0,0 @@
*.egg-info
*.swp
__pycache__
build
venv

283
README.md
View File

@ -6,31 +6,24 @@ licensed under GPL-2.0.
home: https://git.grandou.net/gilles/runon home: https://git.grandou.net/gilles/runon
Runon is a frontend to podman allowing to run seamlessly any application in
any linux based operating system, including graphical applications, while
keeping the host user environment.
the v1 legacy runon branch was based on docker. Current versions are now
relying on podman.
Quick HOWTO Quick HOWTO
----------- -----------
$ grep ^PRETTY_NAME /etc/os-release $ grep ^PRETTY_NAME /etc/os-release
PRETTY_NAME="Debian GNU/Linux 12 (bookworm)" PRETTY_NAME="Debian GNU/Linux 10 (buster)"
$ runon centos7 grep ^PRETTY_NAME /etc/os-release $ runon centos7 grep ^PRETTY_NAME /etc/os-release
PRETTY_NAME="CentOS Linux 7 (Core)" PRETTY_NAME="CentOS Linux 7 (Core)"
$ runon ubuntu20.04 grep ^PRETTY_NAME /etc/os-release $ runon ubuntu20.04 grep ^PRETTY_NAME /etc/os-release
PRETTY_NAME="Ubuntu 20.04.6 LTS" PRETTY_NAME="Ubuntu 20.04.2 LTS"
$ runon debian9 grep ^PRETTY_NAME /etc/os-release $ runon debian9 grep ^PRETTY_NAME /etc/os-release
PRETTY_NAME="Debian GNU/Linux 9 (stretch)" PRETTY_NAME="Debian GNU/Linux 9 (stretch)"
$ runon rocky9 xterm $ runon centos7 xclock
[xterm launched!] [xclock launched!]
Install Install
@ -38,101 +31,67 @@ Install
Installation has been tested on: Installation has been tested on:
* Debian 11 (bullseye) * Debian 10 (buster)
* Debian 12 (bookworm) * Ubuntu 20.04 (Focal)
* RockyLinux 9.3 (Blue Onyx)
However it should work straightforward on any equivalent system. However it should work straightforward on any equivalent system.
### Podman Install ### Docker Install
If not already installed, just run as root the suitable command for your system: sudo apt install docker
sudo systemctl enable --now docker
apt install podman Check that your are member of `docker` group:
dnf install podman
To check that podman is correctly installed, just try: sudo adduser <user> docker
$ podman run -it hello-world If needed, you need to logout and login again for the new group to
Resolved "hello-world" as an alias (/etc/containers/registries.conf.d/shortnames.conf) become active.
Trying to pull docker.io/library/hello-world:latest...
Getting image source signatures
Copying blob 719385e32844 done
Copying config 9c7a54a9a4 done
Writing manifest to image destination
Storing signatures
Hello from Docker!
This message shows that your installation appears to be working correctly.
[...]
If your encounter an error like this one:
Error: writing blob: adding layer with blob "sha256:3331450fb84fde695e565405a554d5cf213a33826da197b29aabde08be012f8b": Error processing tar file(exit status 1): potentially insufficient UIDs or GIDs available in user namespace (requested 0:42 for /etc/gshadow): Check /etc/subuid and /etc/subgid: lchown /etc/gshadow: invalid argument
it's likely that your user account has been create a while ago on a legacy
distribution release and has no support for `subuid` and `subgid`. You can
fix it easily with these commands:
sudo usermod --add-subgids 10000-75535 $USER
sudo usermod --add-subuids 10000-75535 $USER
podman system migrate
podman pull
If you're running on a Debian system, there is a good explanation of the above problem in `/usr/share/doc/podman/README.Debian`.
### Python Dependencies ### Python Dependencies
There is no specific dependency, you just need to insure to have: sudo apt install python3-docker python3-dockerpty python3-xdg
* a Python release 3.6 or better
* the python `venv` module
* the python `pip` module
### manual install ### manual install
cd <tools> cd <tools>
git clone https://git.grandou.net/gilles/runon git clone https://git.grandou.net/gilles/runon
local install, in your `~/.local/bin` local install, in your `~/local/bin` (or wherever directory which is in your
PATH):
cd <runon> cd <runon>
./install ./install local
If you plan to work `runon` development, you can or
pass `--dev` to install links to your current git clone:
./install --dev ./install local <your_bin_path>
each user can have its own configuration in `~/.config/runon/runon.conf`. system install, for all users:
You can keep your configuration in several places, the 1st one which is cd <runon>
find is used: sudo ./install system
* `runon.yaml` in the current directory
* `.runon.yaml` in the current directory
* `~/.config/runon/runon.yaml`
* `~/.config/runon/runon.default.yaml`, the configuration file installed by
default
each user can have its own configuration in `~/.config/runon/runon.conf`
if needed.
### uninstall ### uninstall
To uninstall, just run: simply pass `-u` to install command you have used, eg.:
./uninstall
./install local -u
./install local -u <dir>
sudo ./install system -u
### some convenient links ### some convenient links
you can create soft links to `runos` to simplify calls: you can create soft links to `runos` to simplify calls:
runos -l centos7 runos centos7 -l
now calling `centos7 ...` is equivalent to call `runos centos7 ...`: now calling `centos7 ...` is equivalent to call `runos centos7 ...`:
centos7 xterm centos7 xclock
Usage Usage
@ -142,8 +101,7 @@ With the default configuration, a seamless environment is set up,
allowing to transparently run commands in various environments, while allowing to transparently run commands in various environments, while
keeping: keeping:
* user environment (uid, gid, home directory, ...) * user environment (uid, gid, password, home directory, ...)
* password less sudo support
* X support to run graphical applications * X support to run graphical applications
### Basic usage ### Basic usage
@ -154,7 +112,11 @@ keeping:
### available options ### available options
* `-v` verbose output, display information on the current startup step * `-v` verbose output, this is really usefull when running new
containers for the first time, as the initial docker build can be
quite long (several minutes) especially with slow internet link.
If the command seems to be stalled, don't hesitate to interrupt it
(with `CTRL-C`) and to restart it with `-v`.
* `-u` forces the container image to be updated, useful if the * `-u` forces the container image to be updated, useful if the
distribution has been updated and you want to use it. Otherwise, distribution has been updated and you want to use it. Otherwise,
@ -164,38 +126,6 @@ keeping:
* `-c <configfile>` uses a custom config file, useful to try new * `-c <configfile>` uses a custom config file, useful to try new
distribution without breaking your running config. distribution without breaking your running config.
* `-l` create an executable link with the `osname` name. you can after
run the command with `osname [...]` instead of `runos osname [...]`
### Listing available distributions:
Just run:
$ runon list
Available distributions:
centos7
debian10
debian11
debian12
debian9
rocky8
rocky9
ubuntu20.04
ubuntu22.04
This lists all `osname` present in your current configuration file.
### Editing configuration:
you can easily open the current configuration file with:
$ runon edit
If you open the `runon.default.yaml` file, take care to save your changes in
a new `runon.yaml` file to avoid the default one, which could be overwritten
next time you install or update `runon`.
### Interactive shell ### Interactive shell
Just run: Just run:
@ -204,98 +134,84 @@ Just run:
while start an insteractive shell in the container system: while start an insteractive shell in the container system:
$ runon ubuntu22.04 gilles@host:~$ runon centos8
(ubuntu22.04) gilles@host:~$ cat /etc/os-release (centos8) gilles@host:~$ cat /etc/os-release
PRETTY_NAME="Ubuntu 22.04.3 LTS" NAME="CentOS Linux"
NAME="Ubuntu" VERSION="8"
VERSION_ID="22.04" ID="centos"
VERSION="22.04.3 LTS (Jammy Jellyfish)" ID_LIKE="rhel fedora"
VERSION_CODENAME=jammy VERSION_ID="8"
ID=ubuntu PLATFORM_ID="platform:el8"
ID_LIKE=debian PRETTY_NAME="CentOS Linux 8"
HOME_URL="https://www.ubuntu.com/" ANSI_COLOR="0;31"
SUPPORT_URL="https://help.ubuntu.com/" CPE_NAME="cpe:/o:centos:centos:8"
BUG_REPORT_URL="https://bugs.launchpad.net/ubuntu/" HOME_URL="https://centos.org/"
PRIVACY_POLICY_URL="https://www.ubuntu.com/legal/terms-and-policies/privacy-policy" BUG_REPORT_URL="https://bugs.centos.org/"
UBUNTU_CODENAME=jammy CENTOS_MANTISBT_PROJECT="CentOS-8"
(ubuntu22.04) gilles@host:~$ xclock CENTOS_MANTISBT_PROJECT_VERSION="8"
(centos8) gilles@host:~$ id
uid=1000(gilles) gid=1000(gilles) groups=1000(gilles)
(centos8) gilles@host:~$ sudo id
[sudo] password for gilles:
uid=0(root) gid=0(root) groups=0(root)
(centos8) gilles@host:~$ xclock
^C ^C
(ubuntu22.04) gilles@host:~$ exit (centos8) gilles@host:~$ exit
exit exit
gilles@host:~$
To help differentiate the environment on are running on, you can add this
snippet to your `.bashrc`:
# container
if [ -n "$container" ]; then
PS1="($container) $PS1"
fi
This now displays your `runon` name on your bash prompt.
Configuration Configuration
------------- -------------
Configuration is done in `runon.yaml` file, which describes supported Configuration is done in `runon.conf` file, which describes supported
distribution in YAML format. distribution in .INI format.
### Example config ### Example config
``` ```
rh_base: &rh_base [DEFAULT]
dockerfile: environment =
- RUN dnf install -y sudo HOME
- RUN echo "Defaults lecture = never" >> /etc/sudoers USER
- RUN echo "ALL ALL=(ALL) NOPASSWD:ALL" >> /etc/sudoers DISPLAY
- RUN dnf group install -y "Development Tools" debian_chroot=${osname}
pkginstall:
"RUN dnf install -y {}"
packages:
- xterm
- vim-X11
- git
- python3
- bash-completion
binds:
- /etc/timezone:ro
- /etc/localtime:ro
- "{home}"
environment:
- USER
- DISPLAY
- TERM
- container={osname}
centos7:
<<: *rh_base
image: docker.io/centos:7
dockerfile:
- RUN yum install -y sudo
- RUN echo "Defaults lecture = never" >> /etc/sudoers
- RUN echo "ALL ALL=(ALL) NOPASSWD:ALL" >> /etc/sudoers
- RUN yum group install -y "Development Tools"
pkginstall:
"RUN yum install -y {}"
rocky8: binds =
<<: *rh_base /etc/passwd:ro
image: docker.io/rockylinux:8 /etc/group:ro
/etc/shadow:ro
/tmp/.X11-unix:ro
/home/${user}
rocky9: [centos8]
<<: *rh_base dockerfile =
image: docker.io/rockylinux:9 FROM centos:8
RUN yum install dnf-plugins-core -y
RUN yum config-manager --set-enabled powertools -y
RUN yum install sudo -y
RUN echo "Defaults lecture = never" >> /etc/sudoers
RUN echo "ALL ALL=(ALL) ALL" >> /etc/sudoers
pkginstall = RUN yum install {} -y
packages = ksh csh xterm xorg-x11-apps xkeyboard-config git
[debian9]
dockerfile =
FROM debian:9
RUN apt-get update
RUN apt-get -y install sudo
RUN echo "Defaults lecture = never" >> /etc/sudoers
pkginstall = RUN apt-get -y install {}
packages = ksh csh xterm x11-apps libgtk-3-0 build-essential git
``` ```
Each entry which contains an `image:` field defines a distribution which can Each section `[osname]` defines a distribution which can be used by runon.
be used by runon. In the above example the other entries are used as templates The `[DEFAULT]` section defines default values which is used if not
for real entries. overriden in individual section.
### Config entries ### Config entries
* `image` the base image used to build the container.
* `dockerfile` the base content of dockerfile which will be used to * `dockerfile` the base content of dockerfile which will be used to
generate the running environment. There is usually no need to diverge generate the running environment. There is usually no need to diverge
from the ones given in example. from the ones given in example.
@ -314,15 +230,12 @@ for real entries.
* `environment` the list of environment variables you want to pass or * `environment` the list of environment variables you want to pass or
set in the container system. See below for a description set in the container system. See below for a description
Lines starting with `#` or are comments. Lines starting with `#` or `;` are comments.
Some substitution happens upon reading the configuration: Some substitution happens upon reading the configuration:
* `{osname}` the executed distribution. * `${user}` the current username
* `{user}` the current username * `${osname}` the executed distribution.
* `{uid}` the current UID
* `{home}` the user's home directory
### Binds ### Binds

134
install
View File

@ -1,41 +1,103 @@
#!/bin/bash #!/usr/bin/bash
srcdir=$(dirname $(readlink -f $0)) local_bin_dir=~/local/bin
venvdir=~/.local/lib/runon local_config_dir=~/.config/runon
bindir=~/.local/bin system_bin_dir=/usr/local/bin
configdir=~/.config/runon system_config_dir=/etc/runon
if [ "$1" == "--dev" ]; then op=install
editable=--editable bin_dir=$local_bin_dir
devmode=1 config_dir=$local_config_dir
function usage
{
echo "$0 [local|system] [-u|--uninstall] [<dest>]"
echo
echo " local install for local user, by default"
echo " system install for all users"
echo " dev install in dev mode (create links to source)"
echo " -u, --uninstall local or system uninstall"
echo " <dest> destination dir for binary"
echo
echo "default local paths:"
echo " binary: $local_bin_dir"
echo " config: $local_config_dir"
echo
echo "default system paths:"
echo " binary: $system_bin_dir"
echo " config: $system_config_dir"
echo
}
function do_exec
{
echo "$@"
"$@"
}
while [ $# -gt 0 ]; do
case $1 in
local)
bin_dir=$local_bin_dir
config_dir=$local_config_dir
;;
system)
bin_dir=$system_bin_dir
config_dir=$system_config_dir
;;
dev)
bin_dir=$local_bin_dir
config_dir=$local_config_dir
op=installdev
;;
-u)
op=uninstall
;;
--uninstall)
op=uninstall
;;
-h*)
usage
exit 0
;;
*) bin_dir=$1
;;
esac
shift
done
bin_dir=$(realpath $bin_dir)
config_dir=$(realpath $config_dir)
if [[ ":$PATH:" != *":$bin_dir:"* ]]; then
echo "WARNING: $bin_dir is not in your PATH, runon will not be automatically found."
echo
fi fi
if [ ! -d $venvdir/bin/activate ]; then set -e
echo "create virtualenv $venvdir..."
python3 -m venv $venvdir if [ "$op" = "install" ]; then
fi do_exec install -d $bin_dir
source $venvdir/bin/activate do_exec install -d $config_dir
#rm -f $bin_dir/runon
echo "populate $venvdir..." #rm -f $config_dir/runon
python3 -m pip install --upgrade pip do_exec install runon $bin_dir
python3 -m pip install wheel do_exec install runon.conf $config_dir
python3 -m pip install $editable $srcdir elif [ "$op" = "installdev" ]; then
do_exec install -d $bin_dir
echo "create links in $bindir..." do_exec install -d $config_dir
mkdir -p $bindir do_exec ln -s -f $(realpath runon) $bin_dir/
ln -sf $venvdir/bin/runon $bindir/ do_exec ln -s -f $(realpath runon.conf) $config_dir/
elif [ "$op" = "uninstall" ]; then
echo "install base config in $configdir..." # find all symlinks targetting to runon, and remove them
mkdir -p $configdir find $bin_dir -type l | while read l; do
if [ -n "$devmode" ]; then if [ "$(readlink $l)" = "runon" ]; then
ln -s $srcdir/runon.default.yaml $configdir/ do_exec rm $l
else fi
cp -p $srcdir/runon.default.yaml $configdir/ done
fi
do_exec rm -f $bin_dir/runon
echo "done." do_exec rm -f $config_dir/runon.conf
test -d $bin_dir && do_exec rmdir --parents --ignore-fail-on-non-empty $bin_dir 2> /dev/null
if [[ ":$PATH:" != *":$(readlink -f $bindir):"* ]]; then test -d $config_dir && do_exec rmdir --parents --ignore-fail-on-non-empty $config_dir 2> /dev/null
echo ""
echo "WARNING: $bindir is not in your PATH"
fi fi

217
runon Executable file
View File

@ -0,0 +1,217 @@
#!/usr/bin/env python3
import sys
import argparse
import docker
import dockerpty
import io
import json
import getpass
import platform
import os
import re
import xdg.BaseDirectory
import configparser
import subprocess
from pprint import pprint
def natural_sortkey(string):
tokenize = re.compile(r'(\d+)|(\D+)').findall
return tuple(int(num) if num else alpha for num, alpha in tokenize(string))
def read_ini(user_confname, osname=''):
ini_list = [ 'runon.conf', '.runon.conf', os.path.join(xdg.BaseDirectory.xdg_config_home, 'runon', 'runon.conf'), '/etc/runon/runon.conf' ]
defaults = {
'osname': osname,
'user': getpass.getuser()
}
if user_confname:
ini_list.insert(0, user_confname)
ini = configparser.ConfigParser(defaults=defaults, interpolation=configparser.ExtendedInterpolation())
ini.read(ini_list)
return ini
def list_osnames(user_confname):
ini = read_ini(user_confname)
return ini.sections()
def load_config(user_confname, osname):
ini = read_ini(user_confname, osname)
if not ini.has_section(osname):
print('ERROR: cannot find configuration for distribution "{}"'.format(osname))
sys.exit(1)
conf = {}
fields = [ 'dockerfile', 'pkginstall', 'packages', 'environment', 'binds', 'user', 'osname' ]
try:
for f in fields:
conf[f] = ini.get(osname, f)
except configparser.NoOptionError as e:
print('ERROR: {}'.format(e))
sys.exit(1)
for f in [ 'dockerfile', 'environment', 'binds' ]:
conf[f] = [ i for i in conf[f].split('\n') if i ]
for f in [ 'packages' ]:
conf[f] = conf[f].split()
return conf
def make_osname_link(binpath, osname):
link = os.path.join(os.path.dirname(binpath), osname)
try:
os.symlink('runon', link)
except FileExistsError:
pass
def make_image_name(osname):
user = getpass.getuser()
name = 'runon-{}-{}'.format(osname, user)
return name
def build_image(client, conf, update, verbose):
packages = conf['packages']
dockerfile = conf['dockerfile']
pkginstall = conf['pkginstall']
for p in packages:
dockerfile.append(pkginstall.format(p))
tag = make_image_name(conf['osname'])
try:
if verbose:
# fallback to external command 'docker build' as there is
# no way to follow the build progress with API.
print('Building image {} ...'.format(tag))
cmd = ['docker', 'build']
if update:
cmd.append('--no-cache')
cmd += ['-t', tag, '-']
ret = subprocess.run(cmd,
input='\n'.join(dockerfile).encode('utf-8'),
stderr=subprocess.STDOUT, check=True)
image = client.images.get(tag)
else:
with io.BytesIO('\n'.join(dockerfile).encode('utf-8')) as fd:
image, logs = client.images.build(tag=tag, fileobj=fd, rm=True, nocache=update)
if verbose:
print('Built image {} / {}'.format(image.tags[0], image.short_id))
for l in logs:
print(l.get('stream', '').strip('\n'))
except (docker.errors.BuildError, KeyboardInterrupt, subprocess.CalledProcessError, docker.errors.ImageNotFound) as e:
print('Build Error: {}'.format(e))
print()
print('with dockerfile:')
for line in dockerfile:
print(' {}'.format(line))
sys.exit(1)
return image
def create_container(client, image, conf, command, verbose):
volumes = {}
environment = {}
for mnt in conf['binds']:
mnt = mnt.split(':')
if mnt[-1] in ['ro','rw']:
mode = mnt[-1]
del mnt[-1]
else:
mode = 'rw'
mnt = mnt[:2]
bind = mnt[-1]
vol = mnt[0]
volumes[vol] = { 'bind': bind, 'mode': mode }
hostname = platform.node()
for v in conf['environment']:
e = v.split('=')
if len(e) == 1:
e.append(os.getenv(e[0]))
environment[e[0]] = e[1]
#environment['debian_chroot']=conf['osname']
user='{}:{}'.format(os.getuid(), os.getgid())
pwd=os.getcwd()
container = client.containers.create(image, command,
detach=False, stdin_open=True, tty=True,
auto_remove=True,
hostname=hostname,
volumes=volumes,
environment=environment,
user=user,
network_mode='host',
working_dir=pwd
)
return container
def run_container(client, container):
try:
dockerpty.start(client.api, container.id)
container.reload() # to update attrs fields
except docker.errors.APIError as e:
print('ERROR: {}'.format(e))
sys.exit(1)
ret = container.attrs['State']['ExitCode']
return ret
def main():
osname = None
run_name = os.path.basename(os.sys.argv[0])
if run_name == 'runos':
pass
elif run_name.startswith('runon'):
m = re.match('runon[-_]?(.*)$', run_name)
if m:
osname = m[1]
else:
osname = run_name
parser = argparse.ArgumentParser()
if osname:
parser.description = 'run commands on "{}" distribution'.format(osname)
else:
parser.description = 'run commands on any distribution'
parser.add_argument('osname',
help = 'distribution name to run on, '
'"list" to dump all available distributions')
parser.epilog = '(c) 2021 Gilles Grandou <gilles@grandou.net>'
parser.add_argument('-v', '--verbose', action='store_true',
help='verbose output')
parser.add_argument('-c', '--config',
help='specify config file')
parser.add_argument('-u', '--update', action='store_true',
help='force image update')
parser.add_argument('-l', '--link', action='store_true',
help='create a symlink to call "osname" as a shortcut to "runon osname"')
parser.add_argument('command', nargs='*', default=None,
help = 'command to execute')
args = parser.parse_args()
if osname:
args.osname = osname
if args.osname == 'list':
osnames = list_osnames(args.config)
print('Available distributions:')
for o in sorted(osnames, key=natural_sortkey):
print(' {}'.format(o))
print()
if args.link:
for o in osnames:
make_osname_link(sys.argv[0], args.osname)
return 0
client = docker.from_env()
conf = load_config(args.config, args.osname)
if args.link:
make_osname_link(sys.argv[0], args.osname)
image = build_image(client, conf, args.update, args.verbose)
container = create_container(client, image, conf, args.command, args.verbose)
ret = run_container(client, container)
return ret
if __name__ == '__main__':
ret = main()
sys.exit(ret)

110
runon.conf Normal file
View File

@ -0,0 +1,110 @@
[DEFAULT]
environment =
HOME
USER
DISPLAY
TERM
debian_chroot=${osname}
binds =
/etc/timezone:ro
/etc/localtime:ro
/etc/passwd:ro
/etc/group:ro
/etc/shadow:ro
/tmp/.X11-unix:ro
/home/${user}
[centos7]
dockerfile =
FROM centos:7
RUN yum install sudo -y
RUN echo "Defaults lecture = never" >> /etc/sudoers
RUN echo "ALL ALL=(ALL) NOPASSWD:ALL" >> /etc/sudoers
pkginstall = RUN yum install {} -y
packages = ksh csh xterm xorg-x11-apps xkeyboard-config git glibc-devel gtk2 gtk3 alsa-lib python2 python3 bash-completion redhat-lsb-core environment-modules
[centos8]
dockerfile =
FROM centos:8
RUN sed -i 's/mirrorlist/#mirrorlist/g' /etc/yum.repos.d/CentOS-*
RUN sed -i 's|#baseurl=http://mirror.centos.org|baseurl=http://vault.centos.org|g' /etc/yum.repos.d/CentOS-*
RUN yum install dnf-plugins-core -y
RUN yum config-manager --set-enabled powertools -y
RUN yum install sudo -y
RUN echo "Defaults lecture = never" >> /etc/sudoers
RUN echo "ALL ALL=(ALL) NOPASSWD:ALL" >> /etc/sudoers
pkginstall = RUN yum install {} -y
packages = ksh csh xterm xorg-x11-apps xkeyboard-config git glibc-devel gtk2 gtk3 alsa-lib python2 python3 bash-completion environment-modules
[debian8]
dockerfile =
FROM debian:8
ARG DEBIAN_FRONTEND=noninteractive
RUN apt-get update
RUN apt-get -y install apt-utils
RUN apt-get -y upgrade
RUN apt-get -y install sudo
RUN echo "Defaults lecture = never" >> /etc/sudoers
RUN echo "ALL ALL=(ALL) NOPASSWD:ALL" >> /etc/sudoers
pkginstall = RUN apt-get -y install {}
packages = ksh csh xterm x11-apps build-essential git libgtk2.0 libgtk-3-0 bash-completion tcl environment-modules
[debian9]
dockerfile =
FROM debian:9
ARG DEBIAN_FRONTEND=noninteractive
RUN apt-get update
RUN apt-get -y install apt-utils
RUN apt-get -y upgrade
RUN apt-get -y install sudo
RUN echo "Defaults lecture = never" >> /etc/sudoers
RUN echo "ALL ALL=(ALL) NOPASSWD:ALL" >> /etc/sudoers
pkginstall = RUN apt-get -y install {}
packages = ksh csh xterm x11-apps build-essential git libgtk2.0 libgtk-3-0 bash-completion tcl environment-modules
[debian10]
dockerfile =
FROM debian:10
ARG DEBIAN_FRONTEND=noninteractive
RUN apt-get update
RUN apt-get -y install apt-utils
RUN apt-get -y upgrade
RUN apt-get -y install sudo
RUN echo "Defaults lecture = never" >> /etc/sudoers
RUN echo "ALL ALL=(ALL) NOPASSWD:ALL" >> /etc/sudoers
pkginstall = RUN apt-get -y install {}
packages = ksh csh xterm x11-apps build-essential git libgtk2.0 libgtk-3-0 bash-completion tcl environment-modules
[debian11]
dockerfile =
FROM debian:bullseye
ARG DEBIAN_FRONTEND=noninteractive
RUN apt-get update
RUN apt-get -y install apt-utils
RUN apt-get -y install sudo
RUN echo "Defaults lecture = never" >> /etc/sudoers
pkginstall = RUN apt-get -y install {}
packages = ksh csh xterm x11-apps build-essential git libgtk2.0 libgtk-3-0 bash-completion tcl environment-modules
[ubuntu20.04]
dockerfile =
FROM ubuntu:20.04
ARG DEBIAN_FRONTEND=noninteractive
RUN apt-get update
RUN apt-get -y install apt-utils
RUN apt-get -y install sudo
pkginstall = RUN apt-get -y install {}
packages = ksh csh xterm x11-apps build-essential git libgtk2.0 libgtk-3.0 bash-completion tcl environment-modules
[ubuntu22.04]
dockerfile =
FROM ubuntu:22.04
ARG DEBIAN_FRONTEND=noninteractive
RUN apt-get update
RUN apt-get -y install apt-utils
RUN apt-get -y install sudo
pkginstall = RUN apt-get -y install {}
packages = ksh csh xterm x11-apps build-essential git libgtk2.0 libgtk-3.0 bash-completion tcl environment-modules

View File

@ -1,107 +0,0 @@
debian_base: &debian_base
dockerfile:
- ARG DEBIAN_FRONTEND=noninteractive
- RUN apt-get update
- RUN apt-get -y install apt-utils
- RUN apt-get -y upgrade
- RUN apt-get -y install sudo
- RUN echo "Defaults lecture = never" >> /etc/sudoers
- RUN echo "ALL ALL=(ALL) NOPASSWD:ALL" >> /etc/sudoers
pkginstall:
"RUN apt-get -y install {}"
packages:
- xterm
- x11-apps
- vim-gtk3
- git
- build-essential
- python3
- bash-completion
binds:
- /etc/timezone:ro
- /etc/localtime:ro
- "{home}"
environment:
- USER
- DISPLAY
- TERM
- container={osname}
debian9:
<<: *debian_base
image: docker.io/debian:9
dockerfile:
- ARG DEBIAN_FRONTEND=noninteractive
- RUN echo "deb http://archive.debian.org/debian stretch main" > /etc/apt/sources.list
- RUN apt-get update
- RUN apt-get -y upgrade
- RUN apt install -y --allow-downgrades libnettle6=3.3-1+b2 # default libnettle6 conflicts with libgtk-3.0
- RUN apt-get -y install sudo
- RUN echo "Defaults lecture = never" >> /etc/sudoers
- RUN echo "ALL ALL=(ALL) NOPASSWD:ALL" >> /etc/sudoers
debian10:
<<: *debian_base
image: docker.io/debian:10
debian11:
<<: *debian_base
image: docker.io/debian:11
debian12:
<<: *debian_base
image: docker.io/debian:12
ubuntu20.04:
<<: *debian_base
image: docker.io/ubuntu:20.04
ubuntu22.04:
<<: *debian_base
image: docker.io/ubuntu:22.04
rh_base: &rh_base
dockerfile:
- RUN dnf install -y sudo
- RUN echo "Defaults lecture = never" >> /etc/sudoers
- RUN echo "ALL ALL=(ALL) NOPASSWD:ALL" >> /etc/sudoers
- RUN dnf group install -y "Development Tools"
pkginstall:
"RUN dnf install -y {}"
packages:
- xterm
- vim-X11
- git
- python3
- bash-completion
binds:
- /etc/timezone:ro
- /etc/localtime:ro
- "{home}"
environment:
- USER
- DISPLAY
- TERM
- container={osname}
centos7:
<<: *rh_base
image: docker.io/centos:7
dockerfile:
- RUN yum install -y sudo
- RUN echo "Defaults lecture = never" >> /etc/sudoers
- RUN echo "ALL ALL=(ALL) NOPASSWD:ALL" >> /etc/sudoers
- RUN yum group install -y "Development Tools"
pkginstall:
"RUN yum install -y {}"
rocky8:
<<: *rh_base
image: docker.io/rockylinux:8
rocky9:
<<: *rh_base
image: docker.io/rockylinux:9

View File

View File

@ -1,230 +0,0 @@
import sys
import os
import argparse
import getpass
import pathlib
import xdg.BaseDirectory
import yaml
import datetime
import pytz
import subprocess
from pprint import pprint
def find_config_file(user_conf):
conf_list = [
'runon.yaml',
'.runon.yaml',
os.path.join(xdg.BaseDirectory.xdg_config_home, 'runon', 'runon.yaml'),
os.path.join(xdg.BaseDirectory.xdg_config_home, 'runon', 'runon.default.yaml')
]
if user_conf:
conf_list = [ user_conf ]
for conf in conf_list:
if os.path.exists(conf):
return conf
return None
def read_yaml(conf_file):
try:
with open(conf_file, 'r') as file:
conf = yaml.safe_load(file)
conf['stamp'] = datetime.datetime.fromtimestamp(os.path.getmtime(conf_file), tz=pytz.UTC)
return conf
except yaml.YAMLError as e:
print(f'ERROR: bad configuration file:')
print(e)
sys.exit(1)
return conf
def list_osnames(conf_file):
conf = read_yaml(conf_file)
osnames = []
for key in conf:
if (type(conf[key]) is dict) and conf[key].get('image'):
osnames.append(key)
return osnames
def load_config(conf_file, osname):
user_vars = {
'osname': osname,
'user': getpass.getuser(),
'uid': os.getuid(),
'home': pathlib.Path.home(),
}
conf = read_yaml(conf_file)
osconf = conf.get(osname)
if not osconf:
print(f"ERROR: cannot find configuration for distribution {osname}")
sys.exit(1)
osconf['stamp'] = conf.get('stamp')
osconf['osname'] = osname
for k in [ 'dockerfile', 'packages', 'environment', 'binds' ]:
if osconf.get(k):
osconf[k] = [ s.format(**user_vars) for s in osconf[k]]
return osconf
def make_osname_link(binpath, osname):
link = os.path.join(os.path.dirname(binpath), osname)
try:
os.symlink('runon', link)
except FileExistsError:
pass
def build_image(conf, update, verbose):
osname = conf.get('osname')
image_name = 'runon-{}'.format(osname)
cache_dir = os.path.join(xdg.BaseDirectory.xdg_cache_home, 'runon')
cache_file = os.path.join(cache_dir, image_name)
if not update and os.path.exists(cache_file):
ts_image = datetime.datetime.fromtimestamp(os.path.getmtime(cache_file), tz=pytz.UTC)
ts_conf = conf.get('stamp')
if verbose:
print('config: {}'.format(ts_conf))
print('image: {}'.format(ts_image))
if ts_image and ts_image > ts_conf:
if verbose:
print('image: {} up-to-date'.format(image_name))
return image_name
image = conf.get('image')
dockerfile = conf.get('dockerfile')
pkginstall = conf.get('pkginstall')
packages = conf.get('packages')
if not dockerfile:
dockerfile = []
if image:
dockerfile.insert(0, 'FROM {}'.format(image))
if packages:
for p in packages:
dockerfile.append(pkginstall.format(p))
if verbose:
print('Dockerfile:')
for l in dockerfile:
print(' * {}'.format(l))
print('Building image {} ...'.format(image_name))
cmd = ['podman', 'build']
if update:
cmd.append('--no-cache')
cmd += ['-t', image_name, '-']
ret = subprocess.run(cmd,
input='\n'.join(dockerfile).encode('utf-8'),
stderr=subprocess.STDOUT, check=True)
if not os.path.exists(cache_dir):
os.mkdir(cache_dir)
with open(cache_file, 'w') as file:
if verbose:
print('cache: {}'.format(cache_file))
file.write('')
return image_name
def run_image(name, conf, command, verbose):
volumes = {
}
environment = {}
if conf.get('binds'):
for mnt in conf['binds']:
mnt = mnt.split(':')
if mnt[-1] in ['ro','rw']:
mode = mnt[-1]
del mnt[-1]
else:
mode = 'rw'
mnt = mnt[:2]
bind = mnt[-1]
vol = mnt[0]
if os.path.exists(vol):
volumes[vol] = { 'bind': bind, 'mode': mode }
if conf.get('environment'):
for v in conf['environment']:
e = v.split('=')
if len(e) == 1:
e.append(os.getenv(e[0]))
environment[e[0]] = e[1]
cmd = ['podman', 'run', '--rm', '--interactive', '--tty', '--userns=keep-id', '--net=host' ]
for e in environment:
cmd += [ '-e', '{}={}'.format(e, environment[e]) ]
for v in volumes:
cmd += [ '-v', ':'.join([v, volumes[v]['bind'], volumes[v]['mode']]) ]
cmd += [ '--workdir', os.getcwd() ]
cmd += [ name ]
if command:
cmd += command
if verbose:
print('executing: {}\n'.format(' '.join(cmd)))
ret = subprocess.run(cmd)
return ret.returncode
def main():
osname = None
run_name = os.path.basename(os.sys.argv[0])
if run_name == "runon":
pass
elif run_name.startswith("runon_"):
osname = run_name[len("runon_"):]
else:
osname = run_name
parser = argparse.ArgumentParser()
if osname:
parser.description = 'run commands on "{}" distribution'.format(osname)
else:
parser.description = 'run commands on any distribution'
parser.add_argument('osname',
help = 'distribution name to run on, '
'"list" to dump all available distributions, '
'"edit" to open the current config file in a text editor.')
parser.epilog = '(c) 2021 Gilles Grandou <gilles@grandou.net>'
parser.add_argument('-v', '--verbose', action='store_true',
help='verbose output')
parser.add_argument('-c', '--config',
help='specify config file')
parser.add_argument('-u', '--update', action='store_true',
help='force image update')
parser.add_argument('-l', '--link', action='store_true',
help='create a symlink to call "osname" as a shortcut to "runon osname"')
parser.add_argument('command', nargs='*', default=None,
help = 'command to execute')
args = parser.parse_args()
if osname:
args.osname = osname
conf_file = find_config_file(args.config)
if not conf_file:
print('ERROR: config file not found')
sys.exit(1)
if args.osname == 'list':
osnames = list_osnames(conf_file)
print('Available distributions:')
for o in sorted(osnames):
print(' {}'.format(o))
print()
return 0
elif args.osname == 'edit':
cmd = [ 'xdg-open', conf_file ]
ret = subprocess.run(cmd, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL);
return 0
if args.link:
make_osname_link(sys.argv[0], args.osname)
conf = load_config(conf_file, args.osname)
image_name = build_image(conf, args.update, args.verbose)
ret = run_image(image_name, conf, args.command, args.verbose)
return ret

View File

@ -1,22 +0,0 @@
from setuptools import find_packages, setup
setup(
name = "runon",
version = "2.0.0.dev0",
maintainer = "Gilles Grandou",
maintainer_email = "gilles@grandou.net",
description = "Run your commands on any systems",
packages = find_packages(),
python_requires = ">=3.6",
install_requires = [
"pathlib",
"pyxdg",
"pyyaml",
"pytz",
],
entry_points={
"console_scripts": [
"runon=runon.runon:main",
],
}
)

View File

@ -1,16 +0,0 @@
#!/bin/bash -e
cat /etc/os-release
echo
set -x
sudo id
python3 --version
g++ --version | head -1
gvim --version | head -1
xterm -e /bin/bash -c "sleep 1"
date
set +x
echo "[OK]"

View File

@ -1,25 +0,0 @@
#!/bin/bash
srcdir=$(dirname $(readlink -f $0))
venvdir=~/.local/lib/runon
bindir=~/.local/bin
configdir=~/.config/runon
if [ -e $venvdir/bin/runon ]; then
echo "remove links from $bindir"
find -L $bindir -samefile $bindir/runon -exec rm -v {} \;
find -L $bindir -samefile $venvdir/bin/runon -exec rm -v {} \;
fi
if [ -d $venvdir ]; then
echo "remove virtualenv $venvdir"
rm -rf $venvdir
fi
if [ -e $configdir ]; then
echo "remove configs"
rm -vf $configdir/runon.default.yaml
rmdir -v $configdir || true
fi
echo "done."