Compare commits
25 Commits
c0edb54370
...
c3d3ad26ce
Author | SHA1 | Date | |
---|---|---|---|
c3d3ad26ce | |||
d4df0a1abd | |||
e0b228799f | |||
bd6a026e9c | |||
7dc36feaa3 | |||
39019f5df0 | |||
db97400377 | |||
79465f89d4 | |||
2a57f86add | |||
3462964cb0 | |||
8ccd734c02 | |||
97cf440ce5 | |||
1447b62755 | |||
ef6c079a09 | |||
6eb98de362 | |||
4860335cae | |||
348eddd3da | |||
972b24c411 | |||
b99622b2cf | |||
007fc6ac10 | |||
c0bbec80e1 | |||
96f1fa0b8c | |||
c8ba6f3d1b | |||
88e070d88c | |||
ca93b8f56d |
5
.gitignore
vendored
Normal file
5
.gitignore
vendored
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
*.egg-info
|
||||||
|
*.swp
|
||||||
|
__pycache__
|
||||||
|
build
|
||||||
|
venv
|
283
README.md
283
README.md
@ -6,24 +6,31 @@ 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 10 (buster)"
|
PRETTY_NAME="Debian GNU/Linux 12 (bookworm)"
|
||||||
|
|
||||||
$ 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.2 LTS"
|
PRETTY_NAME="Ubuntu 20.04.6 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 centos7 xclock
|
$ runon rocky9 xterm
|
||||||
[xclock launched!]
|
[xterm launched!]
|
||||||
|
|
||||||
|
|
||||||
Install
|
Install
|
||||||
@ -31,67 +38,101 @@ Install
|
|||||||
|
|
||||||
Installation has been tested on:
|
Installation has been tested on:
|
||||||
|
|
||||||
* Debian 10 (buster)
|
* Debian 11 (bullseye)
|
||||||
* Ubuntu 20.04 (Focal)
|
* Debian 12 (bookworm)
|
||||||
|
* RockyLinux 9.3 (Blue Onyx)
|
||||||
|
|
||||||
However it should work straightforward on any equivalent system.
|
However it should work straightforward on any equivalent system.
|
||||||
|
|
||||||
### Docker Install
|
### Podman Install
|
||||||
|
|
||||||
sudo apt install docker
|
If not already installed, just run as root the suitable command for your system:
|
||||||
sudo systemctl enable --now docker
|
|
||||||
|
|
||||||
Check that your are member of `docker` group:
|
apt install podman
|
||||||
|
dnf install podman
|
||||||
|
|
||||||
sudo adduser <user> docker
|
To check that podman is correctly installed, just try:
|
||||||
|
|
||||||
If needed, you need to logout and login again for the new group to
|
$ podman run -it hello-world
|
||||||
become active.
|
Resolved "hello-world" as an alias (/etc/containers/registries.conf.d/shortnames.conf)
|
||||||
|
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
|
||||||
|
|
||||||
sudo apt install python3-docker python3-dockerpty python3-xdg
|
There is no specific dependency, you just need to insure to have:
|
||||||
|
|
||||||
|
* 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` (or wherever directory which is in your
|
local install, in your `~/.local/bin`
|
||||||
PATH):
|
|
||||||
|
|
||||||
cd <runon>
|
cd <runon>
|
||||||
./install local
|
./install
|
||||||
|
|
||||||
or
|
If you plan to work `runon` development, you can
|
||||||
|
pass `--dev` to install links to your current git clone:
|
||||||
|
|
||||||
./install local <your_bin_path>
|
./install --dev
|
||||||
|
|
||||||
system install, for all users:
|
each user can have its own configuration in `~/.config/runon/runon.conf`.
|
||||||
|
|
||||||
cd <runon>
|
You can keep your configuration in several places, the 1st one which is
|
||||||
sudo ./install system
|
find is used:
|
||||||
|
|
||||||
|
* `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
|
||||||
|
|
||||||
simply pass `-u` to install command you have used, eg.:
|
To uninstall, just run:
|
||||||
|
|
||||||
|
./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 centos7 -l
|
runos -l centos7
|
||||||
|
|
||||||
now calling `centos7 ...` is equivalent to call `runos centos7 ...`:
|
now calling `centos7 ...` is equivalent to call `runos centos7 ...`:
|
||||||
|
|
||||||
centos7 xclock
|
centos7 xterm
|
||||||
|
|
||||||
|
|
||||||
Usage
|
Usage
|
||||||
@ -101,7 +142,8 @@ 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, password, home directory, ...)
|
* user environment (uid, gid, home directory, ...)
|
||||||
|
* password less sudo support
|
||||||
* X support to run graphical applications
|
* X support to run graphical applications
|
||||||
|
|
||||||
### Basic usage
|
### Basic usage
|
||||||
@ -112,11 +154,7 @@ keeping:
|
|||||||
|
|
||||||
### available options
|
### available options
|
||||||
|
|
||||||
* `-v` verbose output, this is really usefull when running new
|
* `-v` verbose output, display information on the current startup step
|
||||||
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,
|
||||||
@ -126,6 +164,38 @@ 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:
|
||||||
@ -134,84 +204,98 @@ Just run:
|
|||||||
|
|
||||||
while start an insteractive shell in the container system:
|
while start an insteractive shell in the container system:
|
||||||
|
|
||||||
gilles@host:~$ runon centos8
|
$ runon ubuntu22.04
|
||||||
(centos8) gilles@host:~$ cat /etc/os-release
|
(ubuntu22.04) gilles@host:~$ cat /etc/os-release
|
||||||
NAME="CentOS Linux"
|
PRETTY_NAME="Ubuntu 22.04.3 LTS"
|
||||||
VERSION="8"
|
NAME="Ubuntu"
|
||||||
ID="centos"
|
VERSION_ID="22.04"
|
||||||
ID_LIKE="rhel fedora"
|
VERSION="22.04.3 LTS (Jammy Jellyfish)"
|
||||||
VERSION_ID="8"
|
VERSION_CODENAME=jammy
|
||||||
PLATFORM_ID="platform:el8"
|
ID=ubuntu
|
||||||
PRETTY_NAME="CentOS Linux 8"
|
ID_LIKE=debian
|
||||||
ANSI_COLOR="0;31"
|
HOME_URL="https://www.ubuntu.com/"
|
||||||
CPE_NAME="cpe:/o:centos:centos:8"
|
SUPPORT_URL="https://help.ubuntu.com/"
|
||||||
HOME_URL="https://centos.org/"
|
BUG_REPORT_URL="https://bugs.launchpad.net/ubuntu/"
|
||||||
BUG_REPORT_URL="https://bugs.centos.org/"
|
PRIVACY_POLICY_URL="https://www.ubuntu.com/legal/terms-and-policies/privacy-policy"
|
||||||
CENTOS_MANTISBT_PROJECT="CentOS-8"
|
UBUNTU_CODENAME=jammy
|
||||||
CENTOS_MANTISBT_PROJECT_VERSION="8"
|
(ubuntu22.04) gilles@host:~$ xclock
|
||||||
(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
|
||||||
(centos8) gilles@host:~$ exit
|
(ubuntu22.04) 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.conf` file, which describes supported
|
Configuration is done in `runon.yaml` file, which describes supported
|
||||||
distribution in .INI format.
|
distribution in YAML format.
|
||||||
|
|
||||||
### Example config
|
### Example config
|
||||||
|
|
||||||
```
|
```
|
||||||
[DEFAULT]
|
rh_base: &rh_base
|
||||||
environment =
|
dockerfile:
|
||||||
HOME
|
- RUN dnf install -y sudo
|
||||||
USER
|
- RUN echo "Defaults lecture = never" >> /etc/sudoers
|
||||||
DISPLAY
|
- RUN echo "ALL ALL=(ALL) NOPASSWD:ALL" >> /etc/sudoers
|
||||||
debian_chroot=${osname}
|
- 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 {}"
|
||||||
|
|
||||||
binds =
|
rocky8:
|
||||||
/etc/passwd:ro
|
<<: *rh_base
|
||||||
/etc/group:ro
|
image: docker.io/rockylinux:8
|
||||||
/etc/shadow:ro
|
|
||||||
/tmp/.X11-unix:ro
|
|
||||||
/home/${user}
|
|
||||||
|
|
||||||
[centos8]
|
rocky9:
|
||||||
dockerfile =
|
<<: *rh_base
|
||||||
FROM centos:8
|
image: docker.io/rockylinux:9
|
||||||
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 section `[osname]` defines a distribution which can be used by runon.
|
Each entry which contains an `image:` field defines a distribution which can
|
||||||
The `[DEFAULT]` section defines default values which is used if not
|
be used by runon. In the above example the other entries are used as templates
|
||||||
overriden in individual section.
|
for real entries.
|
||||||
|
|
||||||
|
|
||||||
### 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.
|
||||||
@ -230,12 +314,15 @@ overriden in individual section.
|
|||||||
* `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:
|
||||||
|
|
||||||
* `${user}` the current username
|
* `{osname}` the executed distribution.
|
||||||
* `${osname}` the executed distribution.
|
* `{user}` the current username
|
||||||
|
* `{uid}` the current UID
|
||||||
|
* `{home}` the user's home directory
|
||||||
|
|
||||||
|
|
||||||
### Binds
|
### Binds
|
||||||
|
|
||||||
|
130
install
130
install
@ -1,103 +1,41 @@
|
|||||||
#!/usr/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
local_bin_dir=~/local/bin
|
srcdir=$(dirname $(readlink -f $0))
|
||||||
local_config_dir=~/.config/runon
|
venvdir=~/.local/lib/runon
|
||||||
system_bin_dir=/usr/local/bin
|
bindir=~/.local/bin
|
||||||
system_config_dir=/etc/runon
|
configdir=~/.config/runon
|
||||||
|
|
||||||
op=install
|
if [ "$1" == "--dev" ]; then
|
||||||
bin_dir=$local_bin_dir
|
editable=--editable
|
||||||
config_dir=$local_config_dir
|
devmode=1
|
||||||
|
|
||||||
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
|
||||||
|
|
||||||
set -e
|
if [ ! -d $venvdir/bin/activate ]; then
|
||||||
|
echo "create virtualenv $venvdir..."
|
||||||
if [ "$op" = "install" ]; then
|
python3 -m venv $venvdir
|
||||||
do_exec install -d $bin_dir
|
|
||||||
do_exec install -d $config_dir
|
|
||||||
#rm -f $bin_dir/runon
|
|
||||||
#rm -f $config_dir/runon
|
|
||||||
do_exec install runon $bin_dir
|
|
||||||
do_exec install runon.conf $config_dir
|
|
||||||
elif [ "$op" = "installdev" ]; then
|
|
||||||
do_exec install -d $bin_dir
|
|
||||||
do_exec install -d $config_dir
|
|
||||||
do_exec ln -s -f $(realpath runon) $bin_dir/
|
|
||||||
do_exec ln -s -f $(realpath runon.conf) $config_dir/
|
|
||||||
elif [ "$op" = "uninstall" ]; then
|
|
||||||
# find all symlinks targetting to runon, and remove them
|
|
||||||
find $bin_dir -type l | while read l; do
|
|
||||||
if [ "$(readlink $l)" = "runon" ]; then
|
|
||||||
do_exec rm $l
|
|
||||||
fi
|
fi
|
||||||
done
|
source $venvdir/bin/activate
|
||||||
|
|
||||||
do_exec rm -f $bin_dir/runon
|
echo "populate $venvdir..."
|
||||||
do_exec rm -f $config_dir/runon.conf
|
python3 -m pip install --upgrade pip
|
||||||
test -d $bin_dir && do_exec rmdir --parents --ignore-fail-on-non-empty $bin_dir 2> /dev/null
|
python3 -m pip install wheel
|
||||||
test -d $config_dir && do_exec rmdir --parents --ignore-fail-on-non-empty $config_dir 2> /dev/null
|
python3 -m pip install $editable $srcdir
|
||||||
|
|
||||||
|
echo "create links in $bindir..."
|
||||||
|
mkdir -p $bindir
|
||||||
|
ln -sf $venvdir/bin/runon $bindir/
|
||||||
|
|
||||||
|
echo "install base config in $configdir..."
|
||||||
|
mkdir -p $configdir
|
||||||
|
if [ -n "$devmode" ]; then
|
||||||
|
ln -s $srcdir/runon.default.yaml $configdir/
|
||||||
|
else
|
||||||
|
cp -p $srcdir/runon.default.yaml $configdir/
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "done."
|
||||||
|
|
||||||
|
if [[ ":$PATH:" != *":$(readlink -f $bindir):"* ]]; then
|
||||||
|
echo ""
|
||||||
|
echo "WARNING: $bindir is not in your PATH"
|
||||||
fi
|
fi
|
||||||
|
217
runon
217
runon
@ -1,217 +0,0 @@
|
|||||||
#!/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
110
runon.conf
@ -1,110 +0,0 @@
|
|||||||
[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
|
|
||||||
|
|
107
runon.default.yaml
Normal file
107
runon.default.yaml
Normal file
@ -0,0 +1,107 @@
|
|||||||
|
|
||||||
|
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
|
||||||
|
|
0
runon/__init__.py
Normal file
0
runon/__init__.py
Normal file
230
runon/runon.py
Executable file
230
runon/runon.py
Executable file
@ -0,0 +1,230 @@
|
|||||||
|
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
|
||||||
|
|
22
setup.py
Normal file
22
setup.py
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
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",
|
||||||
|
],
|
||||||
|
}
|
||||||
|
)
|
16
tests.sh
Executable file
16
tests.sh
Executable file
@ -0,0 +1,16 @@
|
|||||||
|
#!/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]"
|
||||||
|
|
25
uninstall
Executable file
25
uninstall
Executable file
@ -0,0 +1,25 @@
|
|||||||
|
#!/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."
|
Loading…
Reference in New Issue
Block a user