├── .appveyor.yml ├── .gitignore ├── .travis.yml ├── demo ├── alpine-base.service ├── alpine │ ├── base │ │ ├── build.py │ │ ├── etc │ │ │ ├── inittab │ │ │ ├── motd │ │ │ └── network │ │ │ │ └── interfaces │ │ ├── root │ │ │ ├── .ssh │ │ │ │ └── readme.md │ │ │ └── readme.md │ │ └── setup.py │ └── readme.md ├── archux-base.service ├── archux │ ├── base │ │ ├── build.py │ │ ├── etc │ │ │ ├── ca-certificates │ │ │ │ └── trust-source │ │ │ │ │ └── anchors │ │ │ │ │ └── proxy-cert.pem │ │ │ ├── locale.conf │ │ │ ├── locale.gen │ │ │ ├── nsswitch.conf │ │ │ ├── pacman.d │ │ │ │ ├── hooks │ │ │ │ │ └── remove_cache.hook │ │ │ │ └── mirrorlist │ │ │ └── systemd │ │ │ │ └── network │ │ │ │ └── macvlan.network │ │ ├── root │ │ │ ├── .ssh │ │ │ │ └── readme.md │ │ │ └── readme.md │ │ └── setup.py │ └── readme.md ├── readme.md ├── ubuntu-base.service └── ubuntu │ ├── base │ ├── build.py │ ├── etc │ │ ├── nsswitch.conf │ │ └── systemd │ │ │ └── network │ │ │ └── macvlan.network │ ├── root │ │ ├── .ssh │ │ │ └── readme.md │ │ └── readme.md │ └── setup.py │ └── readme.md ├── licence ├── pyproject.toml ├── readme.md ├── src ├── main │ ├── nspawn │ │ ├── __init__.py │ │ ├── app │ │ │ ├── __init__.py │ │ │ ├── engine │ │ │ │ ├── __init__.py │ │ │ │ ├── build.py │ │ │ │ ├── invoke.py │ │ │ │ ├── main.py │ │ │ │ ├── parser.py │ │ │ │ └── setup.py │ │ │ ├── hatcher │ │ │ │ ├── __init__.py │ │ │ │ ├── main.py │ │ │ │ ├── parser.py │ │ │ │ └── service │ │ │ │ │ ├── __init__.py │ │ │ │ │ ├── image-proxy │ │ │ │ │ ├── arkon.py │ │ │ │ │ ├── build.py │ │ │ │ │ ├── etc │ │ │ │ │ │ ├── hostname │ │ │ │ │ │ ├── inittab │ │ │ │ │ │ ├── motd │ │ │ │ │ │ ├── network │ │ │ │ │ │ │ └── interfaces │ │ │ │ │ │ └── squid │ │ │ │ │ │ │ ├── ca │ │ │ │ │ │ │ ├── proxy-cert.pem │ │ │ │ │ │ │ ├── proxy-key.pem │ │ │ │ │ │ │ └── readme.md │ │ │ │ │ │ │ ├── script │ │ │ │ │ │ │ ├── store_id_prog.py │ │ │ │ │ │ │ ├── store_id_prog.sh │ │ │ │ │ │ │ ├── store_id_test.py │ │ │ │ │ │ │ └── store_id_test.sh │ │ │ │ │ │ │ ├── squid.conf │ │ │ │ │ │ │ ├── store_id_prog.py │ │ │ │ │ │ │ └── store_id_test.py │ │ │ │ │ ├── readme.md │ │ │ │ │ ├── root │ │ │ │ │ │ ├── .config │ │ │ │ │ │ │ ├── htop │ │ │ │ │ │ │ │ └── htoprc │ │ │ │ │ │ │ ├── mc │ │ │ │ │ │ │ │ ├── ini │ │ │ │ │ │ │ │ └── panels.ini │ │ │ │ │ │ │ └── readme.md │ │ │ │ │ │ ├── .ssh │ │ │ │ │ │ │ └── readme.md │ │ │ │ │ │ └── readme.md │ │ │ │ │ ├── setup.py │ │ │ │ │ └── unsetup.py │ │ │ │ │ ├── image-server │ │ │ │ │ ├── arkon.py │ │ │ │ │ ├── build.py │ │ │ │ │ ├── etc │ │ │ │ │ │ ├── hostname │ │ │ │ │ │ ├── inittab │ │ │ │ │ │ ├── motd │ │ │ │ │ │ ├── network │ │ │ │ │ │ │ └── interfaces │ │ │ │ │ │ └── nginx │ │ │ │ │ │ │ └── conf.d │ │ │ │ │ │ │ ├── arkon.conf │ │ │ │ │ │ │ ├── default.conf │ │ │ │ │ │ │ ├── image-tls.sh │ │ │ │ │ │ │ └── image.conf │ │ │ │ │ ├── readme.md │ │ │ │ │ ├── root │ │ │ │ │ │ ├── .config │ │ │ │ │ │ │ ├── htop │ │ │ │ │ │ │ │ └── htoprc │ │ │ │ │ │ │ ├── mc │ │ │ │ │ │ │ │ ├── ini │ │ │ │ │ │ │ │ └── panels.ini │ │ │ │ │ │ │ └── readme.md │ │ │ │ │ │ ├── .ssh │ │ │ │ │ │ │ └── readme.md │ │ │ │ │ │ └── readme.md │ │ │ │ │ ├── setup.py │ │ │ │ │ └── unsetup.py │ │ │ │ │ └── image-syncer │ │ │ │ │ ├── arkon.py │ │ │ │ │ ├── build.py │ │ │ │ │ ├── etc │ │ │ │ │ ├── file_sync_s3 │ │ │ │ │ │ └── arkon.ini │ │ │ │ │ ├── hostname │ │ │ │ │ ├── inittab │ │ │ │ │ ├── motd │ │ │ │ │ └── network │ │ │ │ │ │ └── interfaces │ │ │ │ │ ├── readme.md │ │ │ │ │ ├── root │ │ │ │ │ ├── .config │ │ │ │ │ │ ├── htop │ │ │ │ │ │ │ └── htoprc │ │ │ │ │ │ ├── mc │ │ │ │ │ │ │ ├── ini │ │ │ │ │ │ │ └── panels.ini │ │ │ │ │ │ └── readme.md │ │ │ │ │ ├── .ssh │ │ │ │ │ │ └── readme.md │ │ │ │ │ └── readme.md │ │ │ │ │ ├── setup.py │ │ │ │ │ └── unsetup.py │ │ │ └── nsenter │ │ │ │ ├── __init__.py │ │ │ │ ├── main.py │ │ │ │ └── parser.py │ │ ├── base │ │ │ ├── __init__.py │ │ │ ├── engine.py │ │ │ ├── image.py │ │ │ ├── machine.py │ │ │ ├── overlay.py │ │ │ ├── profile.py │ │ │ └── token.py │ │ ├── build │ │ │ └── __init__.py │ │ ├── builder │ │ │ ├── __init__.py │ │ │ ├── engine.py │ │ │ └── syntax.py │ │ ├── config.ini │ │ ├── packer │ │ │ ├── __init__.py │ │ │ ├── base.py │ │ │ ├── tar.py │ │ │ └── zip.py │ │ ├── setup │ │ │ └── __init__.py │ │ ├── setuper │ │ │ ├── __init__.py │ │ │ ├── engine.py │ │ │ └── syntax.py │ │ ├── support │ │ │ ├── __init__.py │ │ │ ├── aspect.py │ │ │ ├── config.py │ │ │ ├── files.py │ │ │ ├── header.py │ │ │ ├── parser.py │ │ │ ├── process.py │ │ │ ├── proxy.py │ │ │ └── typing.py │ │ ├── template │ │ │ ├── __init__.py │ │ │ ├── loader.py │ │ │ ├── machine-default.service │ │ │ ├── machine-experimental.service │ │ │ └── readme.md │ │ ├── tool │ │ │ ├── __init__.py │ │ │ ├── files.py │ │ │ ├── network.py │ │ │ ├── readme.md │ │ │ └── stamp.py │ │ ├── transport │ │ │ ├── __init__.py │ │ │ ├── auth.py │ │ │ ├── base.py │ │ │ ├── file.py │ │ │ └── http.py │ │ └── wrapper │ │ │ ├── __init__.py │ │ │ ├── base.py │ │ │ ├── curl.py │ │ │ ├── machinectl.py │ │ │ ├── nsenter.py │ │ │ ├── sudo.py │ │ │ ├── systemctl.py │ │ │ ├── systemd_nspawn.py │ │ │ ├── tar.py │ │ │ └── zip.py │ └── readme.md ├── readme.md ├── test │ ├── nspawn_test │ │ ├── __init__.py │ │ ├── app │ │ │ ├── __init__.py │ │ │ ├── engine │ │ │ │ ├── __init__.py │ │ │ │ ├── main_verify.py │ │ │ │ └── parser_test.py │ │ │ ├── hatcher │ │ │ │ ├── __init__.py │ │ │ │ ├── main_test.py │ │ │ │ └── parser_test.py │ │ │ └── nsenter │ │ │ │ ├── __init__.py │ │ │ │ └── parser_test.py │ │ ├── base │ │ │ ├── __init__.py │ │ │ ├── image_test.py │ │ │ ├── machine_test.py │ │ │ ├── overlay_test.py │ │ │ ├── profile_test.py │ │ │ └── token_test.py │ │ ├── builder │ │ │ ├── __init__.py │ │ │ ├── engine_test.py │ │ │ └── syntax_test.py │ │ ├── network.py │ │ ├── packer │ │ │ ├── __init__.py │ │ │ └── base_test.py │ │ ├── server.py │ │ ├── setuper │ │ │ ├── __init__.py │ │ │ ├── engine_test.py │ │ │ └── syntax_test.py │ │ ├── support │ │ │ ├── __init__.py │ │ │ ├── aspect_test.py │ │ │ ├── config_test.py │ │ │ ├── files_test.py │ │ │ ├── header_test.py │ │ │ ├── parser_test.py │ │ │ ├── process_test.py │ │ │ ├── proxy_test.py │ │ │ └── typing_test.py │ │ ├── template │ │ │ ├── __init__.py │ │ │ └── loader_test.py │ │ ├── tool │ │ │ ├── __init__.py │ │ │ ├── network_test.py │ │ │ └── stamp_test.py │ │ ├── transport │ │ │ ├── __init__.py │ │ │ ├── auth_test.py │ │ │ ├── base_test.py │ │ │ ├── curl-head-1.txt │ │ │ ├── curl-head-2.txt │ │ │ ├── http_test.py │ │ │ └── transport_xxx.py │ │ └── wrapper │ │ │ ├── __init__.py │ │ │ ├── base_test.py │ │ │ ├── curl_test.py │ │ │ ├── machinectl_test.py │ │ │ ├── nsenter_test.py │ │ │ ├── sudo_test.py │ │ │ ├── systemctl_test.py │ │ │ ├── systemd_nspawn_test.py │ │ │ └── zip_test.py │ └── readme.md └── verify │ ├── image │ ├── alpine │ │ ├── base │ │ │ ├── a.py │ │ │ ├── build.py │ │ │ ├── etc │ │ │ │ ├── hostname │ │ │ │ ├── inittab │ │ │ │ ├── motd │ │ │ │ └── network │ │ │ │ │ └── interfaces │ │ │ ├── nsenter.txt │ │ │ ├── root │ │ │ │ ├── .ssh │ │ │ │ │ └── readme.md │ │ │ │ └── readme.md │ │ │ ├── setup-desure.py │ │ │ ├── setup-ensure.py │ │ │ ├── setup-nsenter.py │ │ │ ├── setup-rebuild.py │ │ │ ├── setup-update.py │ │ │ ├── setup.py │ │ │ └── unsetup.py │ │ ├── mail │ │ │ ├── build.py │ │ │ ├── setup.py │ │ │ └── unsetup.py │ │ └── proxy │ │ │ ├── build.py │ │ │ ├── etc │ │ │ ├── hostname │ │ │ ├── inittab │ │ │ ├── motd │ │ │ └── network │ │ │ │ └── interfaces │ │ │ ├── setup.py │ │ │ └── unsetup.py │ ├── archux │ │ ├── base │ │ │ ├── build.py │ │ │ ├── etc │ │ │ │ ├── ca-certificates │ │ │ │ │ └── trust-source │ │ │ │ │ │ └── anchors │ │ │ │ │ │ └── proxy-cert.pem │ │ │ │ ├── locale.conf │ │ │ │ ├── locale.gen │ │ │ │ ├── nsswitch.conf │ │ │ │ ├── pacman.d │ │ │ │ │ ├── hooks │ │ │ │ │ │ └── remove_cache.hook │ │ │ │ │ └── mirrorlist │ │ │ │ └── systemd │ │ │ │ │ └── network │ │ │ │ │ └── macvlan.network │ │ │ ├── mount.txt │ │ │ ├── root │ │ │ │ └── .config │ │ │ │ │ ├── htop │ │ │ │ │ └── htoprc │ │ │ │ │ └── mc │ │ │ │ │ ├── ini │ │ │ │ │ └── panels.ini │ │ │ ├── setup-desure.py │ │ │ ├── setup-ensure.py │ │ │ ├── setup-nsenter.py │ │ │ ├── setup-rebuild.py │ │ │ ├── setup-update.py │ │ │ └── setup.py │ │ ├── http │ │ │ ├── build.py │ │ │ ├── etc │ │ │ │ └── nginx │ │ │ │ │ ├── conf.d │ │ │ │ │ └── roundcube.conf │ │ │ │ │ ├── html │ │ │ │ │ └── index.html │ │ │ │ │ ├── nginx.conf │ │ │ │ │ └── tls │ │ │ │ │ ├── server-cert.pem │ │ │ │ │ ├── server-key.pem │ │ │ │ │ ├── server-req.pem │ │ │ │ │ └── server.sh │ │ │ ├── home │ │ │ │ ├── conf │ │ │ │ │ └── roundcube │ │ │ │ │ │ └── readme.md │ │ │ │ └── logs │ │ │ │ │ ├── nginx │ │ │ │ │ └── readme.md │ │ │ │ │ └── roundcube │ │ │ │ │ └── readme.md │ │ │ ├── opt │ │ │ │ └── build.sh │ │ │ ├── setup-desure.py │ │ │ ├── setup-ensure.py │ │ │ ├── setup-nsenter.py │ │ │ ├── setup-rebuild.py │ │ │ ├── setup-update.py │ │ │ ├── setup.py │ │ │ └── usr │ │ │ │ └── share │ │ │ │ └── webapps │ │ │ │ └── roundcubemail │ │ │ │ ├── composer.json │ │ │ │ └── plugins-xxx │ │ │ │ ├── arkon │ │ │ │ ├── arkon.css │ │ │ │ ├── arkon.js │ │ │ │ └── arkon.php │ │ │ │ └── managesieve │ │ │ │ └── config.inc.php │ │ └── mail │ │ │ ├── build.py │ │ │ ├── etc │ │ │ └── readme.md │ │ │ ├── setup.py │ │ │ └── unsetup.py │ ├── hatcher-ensure.py │ └── ubuntu │ │ └── base │ │ ├── a.py │ │ ├── build.py │ │ ├── etc │ │ ├── nsswitch.conf │ │ └── systemd │ │ │ └── network │ │ │ └── macvlan.network │ │ ├── root │ │ └── .config │ │ │ ├── htop │ │ │ └── htoprc │ │ │ └── mc │ │ │ ├── ini │ │ │ └── panels.ini │ │ ├── setup-desure.py │ │ ├── setup-ensure.py │ │ ├── setup-update.py │ │ └── setup.py │ ├── library │ ├── __init__.py │ ├── app │ │ ├── __init__.py │ │ ├── arkon.py │ │ ├── platform_verify.py │ │ └── start.py │ ├── aws │ │ ├── __init__.py │ │ ├── store │ │ │ ├── dir1 │ │ │ │ └── dir2 │ │ │ │ │ └── file-1.md │ │ │ └── dir2 │ │ │ │ └── dir3 │ │ │ │ └── file-2.md │ │ └── sync.py │ ├── overlayfs │ │ ├── __init__.py │ │ ├── a-dir │ │ │ └── a-dir.md │ │ ├── dir-1 │ │ │ └── systemd │ │ │ │ └── file-1.md │ │ ├── dir-2 │ │ │ └── systemd │ │ │ │ └── file-2.md │ │ ├── dir-3 │ │ │ └── systemd │ │ │ │ └── file-3.md │ │ ├── main.py │ │ ├── root │ │ │ └── systemd │ │ │ │ └── root.md │ │ └── work │ │ │ └── work.md │ ├── so20094215.py │ └── syncer │ │ ├── __init__.py │ │ ├── store │ │ └── readme.md │ │ ├── sync.ini │ │ ├── sync.py │ │ └── verify.py │ ├── mount_lowdir_plus.sh │ └── readme.md ├── tool ├── arkon.sh ├── github_advance.py ├── github_squash.py ├── install_local.py ├── invoke_release.py ├── perform_tox.sh ├── provision_env.sh ├── readme.md └── tox_verify.py └── tox.ini /.appveyor.yml: -------------------------------------------------------------------------------- 1 | environment: 2 | matrix: 3 | - APPVEYOR_BUILD_WORKER_IMAGE: ubuntu1804 4 | PYTHON_VERSION: 3.8 5 | TOXENV: py38 6 | stack: python $(PYTHON_VERSION) 7 | clone_depth: 10 8 | branches: 9 | only: 10 | - master 11 | - /develop/ 12 | build: false 13 | install: 14 | - sudo apt-get -y update 15 | - sudo apt-get -y install attr systemd-container 16 | - pip install tox 17 | test_script: 18 | - echo $HOME 19 | - uname -a 20 | - ip addr 21 | - python --version 22 | - systemctl --version 23 | - getfattr --dump /home 24 | - tox 25 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | /build/ 12 | /develop-eggs/ 13 | /dist/ 14 | /downloads/ 15 | /eggs/ 16 | /.eggs/ 17 | /lib/ 18 | /lib64/ 19 | /parts/ 20 | /sdist/ 21 | /var/ 22 | /wheels/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | MANIFEST 27 | 28 | # PyInstaller 29 | # Usually these files are written by a python script from a template 30 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 31 | *.manifest 32 | *.spec 33 | 34 | # Installer logs 35 | pip-log.txt 36 | pip-delete-this-directory.txt 37 | 38 | # Unit test / coverage reports 39 | htmlcov/ 40 | .tox/ 41 | .coverage 42 | .coverage.* 43 | .cache 44 | nosetests.xml 45 | coverage.xml 46 | *.cover 47 | .hypothesis/ 48 | .pytest_cache/ 49 | 50 | # Translations 51 | *.mo 52 | *.pot 53 | 54 | # Django stuff: 55 | *.log 56 | local_settings.py 57 | db.sqlite3 58 | 59 | # Flask stuff: 60 | instance/ 61 | .webassets-cache 62 | 63 | # Scrapy stuff: 64 | .scrapy 65 | 66 | # Sphinx documentation 67 | docs/_build/ 68 | 69 | # PyBuilder 70 | target/ 71 | 72 | # Jupyter Notebook 73 | .ipynb_checkpoints 74 | 75 | # pyenv 76 | .python-version 77 | 78 | # celery beat schedule file 79 | celerybeat-schedule 80 | 81 | # SageMath parsed files 82 | *.sage.py 83 | 84 | # Environments 85 | .env 86 | .venv 87 | env/ 88 | venv/ 89 | ENV/ 90 | env.bak/ 91 | venv.bak/ 92 | .pyenv/ 93 | 94 | # Spyder project settings 95 | .spyderproject 96 | .spyproject 97 | 98 | # Rope project settings 99 | .ropeproject 100 | 101 | # mkdocs documentation 102 | /site 103 | 104 | # mypy 105 | .mypy_cache/ 106 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | dist: xenial 2 | sudo: true 3 | language: python 4 | git: 5 | depth: 10 6 | branches: 7 | only: 8 | - master 9 | - /develop/ 10 | matrix: 11 | include: 12 | - python: 3.8 13 | env: TOXENV=py38 14 | install: 15 | - sudo apt-get -y update 16 | - sudo apt-get -y install attr pigz systemd-container 17 | - pip install tox devrepo 18 | script: 19 | - echo $HOME 20 | - uname -a 21 | - ip addr 22 | - python --version 23 | - systemctl --version 24 | - getfattr --dump /home 25 | - tox 26 | -------------------------------------------------------------------------------- /demo/alpine/base/build.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | # activate build dsl 4 | from nspawn.build import * 5 | 6 | # define image url 7 | import platform 8 | epoch = "3.9" 9 | release = f"{epoch}.4" 10 | hardware = platform.machine() 11 | image_url = f"file://localhost/tmp/nspawn/repo/alpine/base/default-{release}-{hardware}.tar.gz" 12 | alpine_url = f"http://dl-cdn.alpinelinux.org/alpine/v{epoch}/releases/{hardware}/alpine-minirootfs-{release}-{hardware}.tar.gz" 13 | 14 | # declare image identity 15 | IMAGE(image_url) 16 | 17 | # provision dependency image 18 | PULL(alpine_url) 19 | 20 | # termination for alpine /sbin/init (i.e. /bin/busybox) 21 | WITH(KillSignal="SIGUSR1") 22 | 23 | # copy local resources 24 | COPY("/etc") 25 | COPY("/root") 26 | 27 | # template local resources 28 | CAST("/root/readme.md", variable="template varialbe") 29 | 30 | # invoke some bild program 31 | RUN(["/usr/bin/env"]) 32 | 33 | # invoke build shell script 34 | SH("apk update") 35 | SH("apk upgrade") 36 | SH("apk add tzdata") 37 | SH("apk add ca-certificates") 38 | SH("apk add busybox-initscripts") 39 | SH("apk add mc htop pwgen") 40 | SH("apk add iputils iproute2") 41 | SH("apk add dhcpcd openssh ") 42 | SH("rc-update add syslog") 43 | SH("rc-update add dhcpcd") 44 | SH("rc-update add sshd") 45 | SH(""" 46 | echo 'PubkeyAuthentication yes' >> /etc/ssh/sshd_config 47 | echo 'PasswordAuthentication no' >> /etc/ssh/sshd_config 48 | user=root; pass=$(pwgen 64 1); echo $user:$pass | chpasswd 49 | """) 50 | 51 | # publish image to image url 52 | PUSH() 53 | -------------------------------------------------------------------------------- /demo/alpine/base/etc/inittab: -------------------------------------------------------------------------------- 1 | # /etc/inittab 2 | # https://github.com/alpinelinux/alpine-baselayout/blob/master/inittab 3 | 4 | ::sysinit:/sbin/rc sysinit 5 | ::wait:/sbin/rc default 6 | 7 | # Set up a couple of getty's 8 | #tty1::respawn:/sbin/getty 38400 tty1 9 | #tty2::respawn:/sbin/getty 38400 tty2 10 | #tty3::respawn:/sbin/getty 38400 tty3 11 | 12 | # Put a getty on the serial port 13 | #ttyS0::respawn:/sbin/getty -L ttyS0 115200 vt100 14 | 15 | # Put a getty on the console 16 | console::respawn:/sbin/getty 115200,38400,9600 console 17 | 18 | # Stuff to do for the 3-finger salute 19 | ::ctrlaltdel:/sbin/reboot 20 | 21 | # Stuff to do before rebooting 22 | ::shutdown:/sbin/rc shutdown 23 | -------------------------------------------------------------------------------- /demo/alpine/base/etc/motd: -------------------------------------------------------------------------------- 1 | # 2 | # message of the day 3 | # 4 | -------------------------------------------------------------------------------- /demo/alpine/base/etc/network/interfaces: -------------------------------------------------------------------------------- 1 | # 2 | # https://wiki.alpinelinux.org/wiki/Configure_Networking 3 | # 4 | 5 | auto lo 6 | iface lo inet loopback 7 | -------------------------------------------------------------------------------- /demo/alpine/base/root/.ssh/readme.md: -------------------------------------------------------------------------------- 1 | 2 | ### note 3 | 4 | ensure proper chmod: no go=rx on */.ssh path 5 | -------------------------------------------------------------------------------- /demo/alpine/base/root/readme.md: -------------------------------------------------------------------------------- 1 | 2 | ### root 3 | 4 | variable="{{variable}}" 5 | -------------------------------------------------------------------------------- /demo/alpine/base/setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | # activate setup dsl 4 | from nspawn.setup import * 5 | 6 | # define image url 7 | import platform 8 | epoch = "3.9" 9 | release = f"{epoch}.4" 10 | hardware = platform.machine() 11 | image_url = f"file://localhost/tmp/nspawn/repo/alpine/base/default-{release}-{hardware}.tar.gz" 12 | 13 | # discover network interface 14 | network_face = TOOL.select_interface() 15 | 16 | # declare machine image 17 | IMAGE(url=image_url) 18 | 19 | # provide machine identity 20 | MACHINE(name="alpine-base") # sets default hostname 21 | 22 | # customize machine service 23 | WITH( 24 | # Hostname="alpase", # needs systemd v 239 25 | Boot="yes", # locate /bin/init automatically 26 | Quiet="yes", # suppress "press to escape" message 27 | KeepUnit="yes", # use service unit as nspawn scope 28 | Register="yes", # expose service unit with machinectl 29 | MACVLAN=network_face, # create a "macvlan" interface of the specified network interface and add it to the container 30 | ) 31 | 32 | # configure machine ssh access 33 | WITH(BindReadOnly="/root/.ssh/authorized_keys") 34 | -------------------------------------------------------------------------------- /demo/alpine/readme.md: -------------------------------------------------------------------------------- 1 | 2 | ### alpine linux 3 | 4 | https://alpinelinux.org/ 5 | -------------------------------------------------------------------------------- /demo/archux/base/build.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | # activate build dsl 4 | from nspawn.build import * 5 | 6 | # define image url 7 | version_path = "2019/11/01" 8 | version_dots = "2019.11.01" 9 | version_dash = "2019-11-01" 10 | archux_url = "https://archive.archlinux.org" 11 | booter_url = f"{archux_url}/iso/{version_dots}/archlinux-bootstrap-{version_dots}-x86_64.tar.gz" 12 | mirror_url = f"{archux_url}/repos/{version_path}/$repo/os/$arch" 13 | 14 | # declare image identity 15 | IMAGE(url=f"file://localhost/tmp/nspawn/repo/archlinux/base/{version_dash}.tar.gz") 16 | 17 | # provision dependency image 18 | PULL(url=booter_url) 19 | 20 | # configure container profile 21 | WITH( 22 | Boot="yes", # auto-find image init program 23 | Quiet="yes", # suppress "press to escape" message 24 | KeepUnit="yes", # use service unit as nspawn scope 25 | Register="yes", # expose service unit with machinectl 26 | ) 27 | 28 | # copy local resources 29 | COPY(path="/etc") 30 | 31 | # template local resources 32 | CAST(path="/etc/pacman.d/mirrorlist", mirror_url=mirror_url) 33 | 34 | SH(script="pacman-key --init") 35 | SH(script="pacman-key --populate") 36 | SH(script="pacman --sync --needed --noconfirm --refresh") 37 | SH(script="pacman --sync --needed --noconfirm mc htop") 38 | SH(script="pacman --sync --needed --noconfirm iputils iproute2") 39 | SH(script="pacman --sync --needed --noconfirm openssh") 40 | SH(script="systemctl enable systemd-networkd") 41 | SH(script="systemctl enable systemd-resolved") 42 | SH(script="systemctl enable sshd") 43 | 44 | # publish image 45 | PUSH() 46 | -------------------------------------------------------------------------------- /demo/archux/base/etc/ca-certificates/trust-source/anchors/proxy-cert.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIICnDCCAYQCCQConVycCDtlJDANBgkqhkiG9w0BAQsFADAQMQ4wDAYDVQQKDAVw 3 | cm94eTAeFw0xNjA3MTYyMTQxMjRaFw00MzEyMDIyMTQxMjRaMBAxDjAMBgNVBAoM 4 | BXByb3h5MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA2d3Kk+4nMENY 5 | 1tf1uHuJ25pLfLxpUIawiujWJBWnGd+Fw/kgm3sXiaA0vqTAINOqAg8c4r2Ur1Ne 6 | E1RYc4LEvqWSlcCSJOp+azTlXWF7Ns7hEfxYz74SChFUfrpxaGnlqvHjHBQIsneV 7 | YjGF895+8D64DhDe8T4/3fLaWJX/0znp1HAPFoQfxFo6smenixnIdIxTbVY9y/WY 8 | dZaaCZv18pMdgTZtfNFuJQzqSxOXowFOZ0cMDSMKHGrqL5BHBJpffVKmzMpkVmEt 9 | MHh+sO48TY9pV2K8yGQbBPIujCRr0hi1g6q9me9W6D210EBApd1jEJwmM17TAhu8 10 | 03KZzNwCGwIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQDHtl/2fBHzRnDaelpFsnJS 11 | m3dOaaoWZdCtiiXyRXBNHVPjn3IFA+volKMt8PAsTkuWMEbH2V1SxZc5Zs8eomlk 12 | vPVVL8lJSWI/HiXdQG8sCiEH9TA5/fO9db5f0b0Mx2IHec6qwfJHdDVhk61CIa/w 13 | nu0/POV20DzXSWVA19i6mL9WJS3NQUkyPkY701QzPvbCvJlCnx8kZxB6i/eLdH8g 14 | p/v2teLKiHcxC8qV/LJAAost9PgRsE8BRx2QwsuR8uhe7W+QYEa4s5tZ7hFKjWQn 15 | UBkUCvkBgW7Kpsdd2Jba4zbVz0sYzgEP6Z+69J101kmSxGMFx6YveQQ/ZazSPxqs 16 | -----END CERTIFICATE----- 17 | -------------------------------------------------------------------------------- /demo/archux/base/etc/locale.conf: -------------------------------------------------------------------------------- 1 | 2 | # default system locale 3 | 4 | LANG=en_US.UTF-8 5 | -------------------------------------------------------------------------------- /demo/archux/base/etc/locale.gen: -------------------------------------------------------------------------------- 1 | 2 | # available locale 3 | 4 | en_US.UTF-8 UTF-8 5 | -------------------------------------------------------------------------------- /demo/archux/base/etc/nsswitch.conf: -------------------------------------------------------------------------------- 1 | # Begin /etc/nsswitch.conf 2 | 3 | passwd: compat mymachines systemd 4 | group: compat mymachines systemd 5 | shadow: compat 6 | 7 | publickey: files 8 | 9 | #hosts: files mymachines resolve [!UNAVAIL=return] dns myhostname 10 | #hosts: files mymachines resolve dns myhostname 11 | hosts: files mymachines dns resolve myhostname 12 | networks: files 13 | 14 | protocols: files 15 | services: files 16 | ethers: files 17 | rpc: files 18 | 19 | netgroup: files 20 | 21 | # End /etc/nsswitch.conf 22 | -------------------------------------------------------------------------------- /demo/archux/base/etc/pacman.d/hooks/remove_cache.hook: -------------------------------------------------------------------------------- 1 | 2 | [Trigger] 3 | Operation = Upgrade 4 | Operation = Install 5 | Operation = Remove 6 | Type = Package 7 | Target = * 8 | 9 | [Action] 10 | Description = remove_cache 11 | When = PostTransaction 12 | Exec = /usr/bin/rm -rf /var/cache/pacman/pkg 13 | -------------------------------------------------------------------------------- /demo/archux/base/etc/pacman.d/mirrorlist: -------------------------------------------------------------------------------- 1 | 2 | # 3 | # https://wiki.archlinux.org/index.php/Arch_Linux_Archive 4 | # 5 | 6 | Server={{mirror_url}} 7 | -------------------------------------------------------------------------------- /demo/archux/base/etc/systemd/network/macvlan.network: -------------------------------------------------------------------------------- 1 | 2 | # internal network 3 | 4 | [Match] 5 | Name=mv-* 6 | 7 | [Network] 8 | DHCP=ipv4 9 | IPForward=ipv4 10 | LinkLocalAddressing=no 11 | -------------------------------------------------------------------------------- /demo/archux/base/root/.ssh/readme.md: -------------------------------------------------------------------------------- 1 | 2 | ### note 3 | 4 | ensure proper chmod: no go=rx on */.ssh path 5 | -------------------------------------------------------------------------------- /demo/archux/base/root/readme.md: -------------------------------------------------------------------------------- 1 | 2 | ### root 3 | 4 | variable="{{variable}}" 5 | -------------------------------------------------------------------------------- /demo/archux/base/setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | # activate setup dsl 4 | from nspawn.setup import * 5 | 6 | # define image url 7 | version_path = "2019/06/01" 8 | version_dots = "2019.06.01" 9 | version_dash = "2019-06-01" 10 | archux_url = "https://archive.archlinux.org" 11 | booter_url = f"{archux_url}/iso/{version_dots}/archlinux-bootstrap-{version_dots}-x86_64.tar.gz" 12 | mirror_url = f"{archux_url}/repos/{version_path}/$repo/os/$arch" 13 | 14 | # discover network interface 15 | network_face = TOOL.select_interface() 16 | 17 | # declare machine image 18 | IMAGE(url=f"file://localhost/tmp/nspawn/repo/archlinux/base/{version_dash}.tar.gz") 19 | 20 | # provide machine identity 21 | MACHINE(name=f"archux-base") # sets default hostname 22 | 23 | # customize machine service 24 | WITH( 25 | # Hostname="archlinux-base", # needs systemd v 239 26 | MACVLAN=network_face, # create a "macvlan" interface of the specified network interface and add it to the container 27 | ) 28 | 29 | # configure machine ssh access 30 | WITH(BindReadOnly="/root/.ssh/authorized_keys") 31 | -------------------------------------------------------------------------------- /demo/archux/readme.md: -------------------------------------------------------------------------------- 1 | 2 | ### archlinux 3 | 4 | https://www.archlinux.org/ 5 | -------------------------------------------------------------------------------- /demo/readme.md: -------------------------------------------------------------------------------- 1 | 2 | ### demo 3 | 4 | Example build and setup scripts. 5 | -------------------------------------------------------------------------------- /demo/ubuntu/base/build.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | # activate build dsl 4 | from nspawn.build import * 5 | 6 | # define image url 7 | name = "bionic" 8 | version = "18.04" 9 | image_url = f"file://localhost/tmp/nspawn/repo/ubuntu/base/{name}-{version}.tar.gz" 10 | booter_url = f"https://cloud-images.ubuntu.com/minimal/releases/{name}/release/ubuntu-{version}-minimal-cloudimg-amd64-root.tar.xz" 11 | 12 | # declare image identity 13 | IMAGE(image_url) 14 | 15 | # provision dependency image 16 | PULL(booter_url) 17 | 18 | # configure container profile 19 | WITH( 20 | Boot="yes", # auto-find image init program 21 | Quiet="yes", # suppress "press to escape" message 22 | KeepUnit="yes", # use service unit as nspawn scope 23 | Register="yes", # expose service unit with machinectl 24 | ) 25 | 26 | # copy local resources 27 | COPY("/etc") 28 | 29 | # cleanup interfering resources 30 | SH("rm -rf /etc/resolv.conf /etc/securetty") 31 | 32 | # invoke build shell script 33 | SH("apt-get update") 34 | SH("apt-get install -y mc htop") 35 | SH("apt-get install -y iputils-ping iproute2") 36 | SH("apt-get install -y openssh-server") 37 | SH("apt-get purge -y unattended-upgrades") 38 | SH("systemctl disable networkd-dispatcher") 39 | SH("systemctl enable systemd-networkd") 40 | SH("systemctl enable systemd-resolved") 41 | SH("systemctl enable ssh") 42 | 43 | # publish image 44 | PUSH() 45 | -------------------------------------------------------------------------------- /demo/ubuntu/base/etc/nsswitch.conf: -------------------------------------------------------------------------------- 1 | # Begin /etc/nsswitch.conf 2 | 3 | passwd: compat mymachines systemd 4 | group: compat mymachines systemd 5 | shadow: compat 6 | 7 | publickey: files 8 | 9 | #hosts: files mymachines resolve [!UNAVAIL=return] dns myhostname 10 | #hosts: files mymachines resolve dns myhostname 11 | hosts: files mymachines dns resolve myhostname 12 | networks: files 13 | 14 | protocols: files 15 | services: files 16 | ethers: files 17 | rpc: files 18 | 19 | netgroup: files 20 | 21 | # End /etc/nsswitch.conf 22 | -------------------------------------------------------------------------------- /demo/ubuntu/base/etc/systemd/network/macvlan.network: -------------------------------------------------------------------------------- 1 | 2 | # internal network 3 | 4 | [Match] 5 | Name=mv-* 6 | 7 | [Network] 8 | DHCP=ipv4 9 | IPForward=ipv4 10 | LinkLocalAddressing=no 11 | -------------------------------------------------------------------------------- /demo/ubuntu/base/root/.ssh/readme.md: -------------------------------------------------------------------------------- 1 | 2 | ### note 3 | 4 | ensure proper chmod: no go=rx on */.ssh path 5 | -------------------------------------------------------------------------------- /demo/ubuntu/base/root/readme.md: -------------------------------------------------------------------------------- 1 | 2 | ### root 3 | 4 | variable="{{variable}}" 5 | -------------------------------------------------------------------------------- /demo/ubuntu/base/setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | # activate setup dsl 4 | from nspawn.setup import * 5 | 6 | # define image url 7 | name = "bionic" 8 | version = "18.04" 9 | image_url = f"file://localhost/tmp/nspawn/repo/ubuntu/base/{name}-{version}.tar.gz" 10 | booter_url = f"https://cloud-images.ubuntu.com/minimal/releases/{name}/release/ubuntu-{version}-minimal-cloudimg-amd64-root.tar.xz" 11 | 12 | # discover network interface 13 | network_face = TOOL.select_interface() 14 | 15 | # declare image identity 16 | IMAGE(image_url) 17 | 18 | # provide machine identity 19 | MACHINE(name=f"ubuntu-base") # sets default hostname 20 | 21 | WITH( 22 | # Hostname="ubuntase", # needs systemd v 239 23 | MACVLAN=network_face, 24 | ) 25 | 26 | # configure machine ssh access 27 | WITH(BindReadOnly="/root/.ssh/authorized_keys") 28 | -------------------------------------------------------------------------------- /demo/ubuntu/readme.md: -------------------------------------------------------------------------------- 1 | 2 | ### ubuntu 3 | 4 | https://www.ubuntu.com 5 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | 2 | [project] 3 | 4 | name = "nspawn" 5 | description = "Containers with systemd-nspawn" 6 | version = "0.8.4" 7 | # dynamic = [ "version" ] # TODO 8 | 9 | requires-python = ">=3.11" 10 | 11 | readme = "readme.md" 12 | license.file = "licence" 13 | 14 | keywords = [ 15 | "nspawn", 16 | "container", 17 | "systemd", 18 | "systemd-nspawn", 19 | ] 20 | 21 | authors = [ 22 | { name="Andrei Pozolotin", email="andrei.pozolotin@gmail.com" } 23 | ] 24 | 25 | classifiers = [ 26 | "Development Status :: 4 - Beta", 27 | "Environment :: Console", 28 | "Intended Audience :: Developers", 29 | "Intended Audience :: Information Technology", 30 | "Intended Audience :: System Administrators", 31 | "License :: OSI Approved :: Apache Software License", 32 | "Operating System :: POSIX :: Linux", 33 | "Topic :: System :: Systems Administration", 34 | "Topic :: Terminals", 35 | "Topic :: Utilities", 36 | "Programming Language :: Python", 37 | "Programming Language :: Python :: 3 :: Only", 38 | "Programming Language :: Python :: 3.11", 39 | "Programming Language :: Python :: 3.12", 40 | "Programming Language :: Python :: 3.13", 41 | "Topic :: Utilities", 42 | ] 43 | 44 | dependencies = [ 45 | "pyyaml>=6", 46 | "jinja2>=3", 47 | ] 48 | 49 | [project.optional-dependencies] 50 | test = [ 51 | "tox", 52 | "pytest", 53 | ] 54 | 55 | [project.urls] 56 | Source = "https://github.com/random-python/nspawn" 57 | Homepage = "https://github.com/random-python/nspawn" 58 | Documentation = "https://github.com/random-python/nspawn" 59 | 60 | [project.scripts] 61 | nspawn-build = "nspawn.app.engine.build:main" 62 | nspawn-setup = "nspawn.app.engine.setup:main" 63 | nspawn-hatch = "nspawn.app.hatcher.main:main" 64 | nspawn-enter = "nspawn.app.nsenter.main:main" 65 | 66 | [build-system] 67 | 68 | requires = [ 69 | "setuptools>=76", 70 | "setuptools_scm>=8", 71 | ] 72 | 73 | build-backend = "setuptools.build_meta" 74 | 75 | [tool.setuptools.packages.find] 76 | where = [ "src/main" ] 77 | include = ["nspawn*"] 78 | namespaces = false 79 | 80 | [tool.setuptools_scm] 81 | # enable 82 | -------------------------------------------------------------------------------- /src/main/nspawn/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Setup global configuration 3 | """ 4 | 5 | import os 6 | import sys 7 | import logging 8 | from argparse import ArgumentParser 9 | from configparser import ConfigParser 10 | 11 | 12 | def with_merge_parser( 13 | argent_parser:ArgumentParser, 14 | config_parser:ConfigParser=None, 15 | ) -> ArgumentParser: 16 | """ 17 | Attach an action to store command argument into configuration parser 18 | """ 19 | 20 | from nspawn.support.parser import StoreIntoConfigParser 21 | 22 | argent_parser.add_argument( 23 | "--config", 24 | help=""" 25 | Define config.ini override tokens, 26 | using TOKEN format: [section]key=value, 27 | i.e. [logging]level=error 28 | """, 29 | metavar='TOKEN', 30 | nargs='*', 31 | action=StoreIntoConfigParser, 32 | config_parser=config_parser, 33 | ) 34 | 35 | return argent_parser 36 | 37 | 38 | def make_merge_parser(config_parser:ConfigParser) -> ArgumentParser: 39 | argent_parser = ArgumentParser() 40 | with_merge_parser(argent_parser, config_parser) 41 | return argent_parser 42 | 43 | 44 | def ensure_environment() -> None: 45 | """ 46 | Provide environment variables expected by 'config.ini' 47 | """ 48 | if os.environ.get('HOME', None) is None: 49 | os.environ['HOME'] = '/root' 50 | 51 | 52 | def produce_config_parser() -> ConfigParser: 53 | """ 54 | Setup global configuration 55 | """ 56 | try: 57 | ensure_environment() 58 | from nspawn.support.config import RichConfigParser 59 | # 60 | config_parser = RichConfigParser() 61 | argent_parser = make_merge_parser(config_parser) 62 | # start with default options 63 | config_dir = os.path.dirname(os.path.abspath(__file__)) 64 | config_path = os.path.join(config_dir, "config.ini") 65 | config_parser.read(config_path) 66 | # apply first-pass override 67 | argent_parser.parse_known_args() 68 | # apply external config override 69 | config_path_list = config_parser.get_list('config', 'path_list') 70 | config_parser.read(config_path_list) 71 | # apply second-pass override 72 | argent_parser.parse_known_args() 73 | # 74 | logging.basicConfig( 75 | level=config_parser['logging']['level'].strip().upper(), 76 | datefmt=config_parser['logging']['datefmt'].strip(), 77 | format=config_parser['logging']['format'].strip(), 78 | ) 79 | return config_parser 80 | except Exception as error: 81 | sys.exit(f"Config error: {error}") 82 | 83 | 84 | CONFIG = produce_config_parser() 85 | -------------------------------------------------------------------------------- /src/main/nspawn/app/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/random-python/nspawn/305268430526a17ce8e3e96e5f2f501cecdb6d92/src/main/nspawn/app/__init__.py -------------------------------------------------------------------------------- /src/main/nspawn/app/engine/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/random-python/nspawn/305268430526a17ce8e3e96e5f2f501cecdb6d92/src/main/nspawn/app/engine/__init__.py -------------------------------------------------------------------------------- /src/main/nspawn/app/engine/build.py: -------------------------------------------------------------------------------- 1 | """ 2 | Build engine application entry 3 | """ 4 | 5 | from nspawn.app.engine.main import BuildMain 6 | 7 | if __name__ == "__main__": 8 | BuildMain().perform() 9 | -------------------------------------------------------------------------------- /src/main/nspawn/app/engine/invoke.py: -------------------------------------------------------------------------------- 1 | """ 2 | Invoke engine proper when started via engine script 3 | """ 4 | 5 | import os 6 | import sys 7 | import logging 8 | 9 | from nspawn.support.files import discover_entry_script 10 | from nspawn.support.files import discover_project_root 11 | 12 | logger = logging.getLogger(__name__) 13 | 14 | this_dir:str = os.path.dirname(os.path.abspath(__file__)) 15 | 16 | 17 | def env_get(name: str) -> str: 18 | return os.environ.get(name, None) 19 | 20 | 21 | def env_set(name: str, value: str) -> None: 22 | os.environ[name] = value 23 | 24 | 25 | class InvokeState(): 26 | """ 27 | Maintain engine invocation state 28 | """ 29 | 30 | invoke_key = "NSPAWN_ENGINE_INVOKE" 31 | 32 | @staticmethod 33 | def invoke_get() -> str: 34 | return env_get(InvokeState.invoke_key) 35 | 36 | @staticmethod 37 | def invoke_set(invoke_value: str="true") -> None: 38 | env_set(InvokeState.invoke_key, invoke_value) 39 | 40 | 41 | class PythonPath(): 42 | """ 43 | Inject project repository during development 44 | """ 45 | 46 | python_key = 'PYTHONPATH' 47 | 48 | @staticmethod 49 | def value() -> str: 50 | return env_get(PythonPath.python_key) 51 | 52 | @staticmethod 53 | def ensure_project() -> None: 54 | project_root = discover_project_root() 55 | if project_root: 56 | PythonPath.inject_project(project_root) 57 | 58 | @staticmethod 59 | def inject_project(project_root:str) -> None: 60 | path_main = f"{project_root}/src/main" 61 | path_test = f"{project_root}/src/test" 62 | module_path = f"{path_main}:{path_test}" 63 | python_path = env_get(PythonPath.python_key) 64 | if python_path: 65 | python_path = f"{module_path}:{python_path}" 66 | else: 67 | python_path = f"{module_path}" 68 | env_set(PythonPath.python_key, python_path) 69 | logger.debug(f"Python path: {python_path}") 70 | 71 | 72 | def invoke_main(file_name: str) -> None: 73 | "invoke engine proper when started via engine script" 74 | if InvokeState.invoke_get(): 75 | # running inside the engine 76 | return 77 | else: 78 | InvokeState.invoke_set() 79 | PythonPath.ensure_project() 80 | try: 81 | main_file = f"{this_dir}/{file_name}" 82 | script_file = discover_entry_script() 83 | command = [ 84 | sys.executable, 85 | main_file, 86 | '--script', 87 | script_file, 88 | ] + sys.argv[1:] 89 | logger.info(f"Invoke engine: {command}") 90 | os.execlp(sys.executable, *command) 91 | except Exception as error: 92 | sys.exit(f"Invoke failure: {error}") 93 | -------------------------------------------------------------------------------- /src/main/nspawn/app/engine/main.py: -------------------------------------------------------------------------------- 1 | """ 2 | Main program code 3 | """ 4 | 5 | import abc 6 | import sys 7 | import logging 8 | import traceback 9 | from argparse import ArgumentParser 10 | 11 | from nspawn import CONFIG 12 | from nspawn.app.engine import parser 13 | from nspawn.app.engine import invoke 14 | from nspawn.base.engine import Engine 15 | from nspawn.support.parser import ParseResult 16 | 17 | logger = logging.getLogger(__name__) 18 | 19 | 20 | class Main(abc.ABC): 21 | 22 | @abc.abstractclassmethod 23 | def engine(self) -> Engine: 24 | pass 25 | 26 | @abc.abstractclassmethod 27 | def parser(self) -> ArgumentParser: 28 | pass 29 | 30 | def verify(self): 31 | from nspawn.wrapper.base import missing_required_list 32 | missing_list = missing_required_list() 33 | if len(missing_list) > 0: 34 | raise RuntimeError(f"Missing required commands: {missing_list}") 35 | 36 | def perform(self) -> None: 37 | try: 38 | self.verify() 39 | invoke.InvokeState.invoke_set() 40 | parser = self.parser() 41 | space, extra = parser.parse_known_args() 42 | parse_result = ParseResult(space, extra) 43 | engine = self.engine() 44 | engine.init_args(parse_result) 45 | engine.parse_script() 46 | engine.perform_intent() 47 | except Exception as error: 48 | trace_error = CONFIG['main'].getboolean('trace_error') 49 | if trace_error: 50 | traceback.print_exc() 51 | sys.exit(f"Engine failure: {error}") 52 | 53 | 54 | class BuildMain(Main): 55 | 56 | def engine(self): 57 | import nspawn.builder.engine 58 | return nspawn.builder.engine.ENGINE 59 | 60 | def parser(self): 61 | return parser.build_parser() 62 | 63 | 64 | class SetupMain(Main): 65 | 66 | def engine(self): 67 | import nspawn.setuper.engine 68 | return nspawn.setuper.engine.ENGINE 69 | 70 | def parser(self): 71 | return parser.setup_parser() 72 | -------------------------------------------------------------------------------- /src/main/nspawn/app/engine/setup.py: -------------------------------------------------------------------------------- 1 | """ 2 | Setup engine application entry 3 | """ 4 | 5 | from nspawn.app.engine.main import SetupMain 6 | 7 | if __name__ == "__main__": 8 | SetupMain().perform() 9 | -------------------------------------------------------------------------------- /src/main/nspawn/app/hatcher/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/random-python/nspawn/305268430526a17ce8e3e96e5f2f501cecdb6d92/src/main/nspawn/app/hatcher/__init__.py -------------------------------------------------------------------------------- /src/main/nspawn/app/hatcher/parser.py: -------------------------------------------------------------------------------- 1 | """ 2 | Hatcher command parser 3 | """ 4 | 5 | import argparse 6 | import textwrap 7 | from nspawn import with_merge_parser 8 | 9 | 10 | def hatcher_parser() -> argparse.ArgumentParser: 11 | 12 | parser = argparse.ArgumentParser( 13 | prog='nspawn-hatch', 14 | description=textwrap.dedent( 15 | """\ 16 | Containers with systemd-nspawn :: service provisioning tool. 17 | """), 18 | formatter_class=argparse.RawDescriptionHelpFormatter, 19 | ) 20 | 21 | with_merge_parser(parser) 22 | 23 | commands = parser.add_subparsers( 24 | title='required command', 25 | help='Select one of:', 26 | required=True, 27 | dest='command', 28 | ) 29 | 30 | command_list = commands.add_parser( 31 | 'list', 32 | help='List included services', 33 | ) 34 | 35 | def with_service(parser): 36 | parser.add_argument( 37 | "service", 38 | help='Service name', 39 | ) 40 | 41 | command_ensure = commands.add_parser( 42 | 'ensure', 43 | help='Provision included service', 44 | ) 45 | with_service(command_ensure) 46 | 47 | command_desure = commands.add_parser( 48 | 'desure', 49 | help='Un-Provision included service', 50 | ) 51 | with_service(command_desure) 52 | 53 | command_update = commands.add_parser( 54 | 'update', 55 | help='Re-Provision included service', 56 | ) 57 | with_service(command_update) 58 | 59 | parser.epilog = textwrap.dedent( 60 | f"""\ 61 | command usage:\n 62 | {command_list.format_usage()} 63 | {command_ensure.format_usage()} 64 | {command_desure.format_usage()} 65 | {command_update.format_usage()} 66 | """ 67 | ) 68 | 69 | return parser 70 | -------------------------------------------------------------------------------- /src/main/nspawn/app/hatcher/service/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/random-python/nspawn/305268430526a17ce8e3e96e5f2f501cecdb6d92/src/main/nspawn/app/hatcher/service/__init__.py -------------------------------------------------------------------------------- /src/main/nspawn/app/hatcher/service/image-proxy/arkon.py: -------------------------------------------------------------------------------- 1 | """ 2 | Service shared config. 3 | """ 4 | 5 | import platform 6 | from nspawn import CONFIG 7 | 8 | epoch = "3.11" 9 | release = f"{epoch}.3" 10 | hardware = platform.machine() 11 | 12 | provision = CONFIG['storage']['provision'] 13 | image_url = f"file://localhost/{provision}/alpine/image-proxy/default-{release}-{hardware}.tar.gz" 14 | alpine_url = f"http://dl-cdn.alpinelinux.org/alpine/v{epoch}/releases/{hardware}/alpine-minirootfs-{release}-{hardware}.tar.gz" 15 | service_config = CONFIG['hatcher/image-proxy'] 16 | -------------------------------------------------------------------------------- /src/main/nspawn/app/hatcher/service/image-proxy/build.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | """ 4 | Service build script. 5 | """ 6 | 7 | from nspawn.build import * 8 | 9 | # load shared config 10 | import os 11 | import runpy 12 | this_dir = os.path.dirname(os.path.abspath(__file__)) 13 | arkon = runpy.run_path(f"{this_dir}/arkon.py") 14 | image_url = arkon['image_url'] 15 | alpine_url = arkon['alpine_url'] 16 | service_config = arkon['service_config'] 17 | 18 | machine_name = service_config['machine_name'] 19 | service_gid = service_config['service_gid'] 20 | service_uid = service_config['service_uid'] 21 | service_home = service_config['service_home'] 22 | service_log_dir = service_config['service_log_dir'] 23 | service_store_dir = service_config['service_store_dir'] 24 | 25 | # 26 | # perfrom build 27 | # 28 | 29 | IMAGE(image_url) 30 | 31 | PULL(alpine_url) 32 | 33 | WITH( 34 | Boot='yes', 35 | KillSignal="SIGUSR1", 36 | ) 37 | 38 | # default config 39 | COPY("/etc") 40 | COPY("/root") 41 | 42 | # configure host name 43 | CAST("/etc/hostname", 44 | machine_name=machine_name, 45 | ) 46 | 47 | # configure proxy server 48 | # CAST("/etc/nginx/conf.d/image.conf", 49 | # service_store_dir=service_store_dir, 50 | # service_user_file=service_user_file, 51 | # ) 52 | 53 | # basic system setup 54 | SH("apk update") 55 | SH("apk upgrade") 56 | SH("apk add tzdata") 57 | SH("apk add ca-certificates") 58 | SH("apk add busybox-initscripts") 59 | SH("apk add mc htop pwgen") 60 | SH("apk add syslog-ng logrotate") 61 | # SH("apk add iputils iproute2") 62 | SH("apk add dhcpcd openssh") 63 | SH("apk add squid openssl") 64 | 65 | # ensure system services 66 | SH("rc-update add syslog-ng") 67 | SH("rc-update add dhcpcd") 68 | SH("rc-update add squid") 69 | SH("rc-update add sshd") 70 | 71 | # store_id_program 72 | SH("apk add python3") 73 | 74 | # container ssh access 75 | SH(""" 76 | echo 'PubkeyAuthentication yes' >> /etc/ssh/sshd_config 77 | echo 'PasswordAuthentication no' >> /etc/ssh/sshd_config 78 | user=root; pass=$(pwgen -s 64 1); echo $user:$pass | chpasswd 79 | """) 80 | 81 | # configure squid 82 | SH(""" 83 | chmod +x /etc/squid/*.py 84 | /usr/lib/squid/security_file_certgen -c -s /etc/squid/crtd -M 4MB 85 | chown -R squid:squid /etc/squid/crtd 86 | """) 87 | 88 | PUSH() 89 | -------------------------------------------------------------------------------- /src/main/nspawn/app/hatcher/service/image-proxy/etc/hostname: -------------------------------------------------------------------------------- 1 | {{machine_name}} 2 | -------------------------------------------------------------------------------- /src/main/nspawn/app/hatcher/service/image-proxy/etc/inittab: -------------------------------------------------------------------------------- 1 | # /etc/inittab 2 | # https://git.alpinelinux.org/aports/tree/main/alpine-baselayout/inittab 3 | 4 | ::sysinit:/sbin/openrc sysinit 5 | ::sysinit:/sbin/openrc boot 6 | ::wait:/sbin/openrc default 7 | 8 | # Set up a couple of getty's 9 | #tty1::respawn:/sbin/getty 38400 tty1 10 | #tty2::respawn:/sbin/getty 38400 tty2 11 | #tty3::respawn:/sbin/getty 38400 tty3 12 | 13 | # Put a getty on the serial port 14 | #ttyS0::respawn:/sbin/getty -L ttyS0 115200 vt100 15 | 16 | # Put a getty on the console 17 | console::respawn:/sbin/getty 115200,38400,9600 console 18 | 19 | # Stuff to do for the 3-finger salute 20 | ::ctrlaltdel:/sbin/reboot 21 | 22 | # Stuff to do before rebooting 23 | ::shutdown:/sbin/openrc shutdown 24 | -------------------------------------------------------------------------------- /src/main/nspawn/app/hatcher/service/image-proxy/etc/motd: -------------------------------------------------------------------------------- 1 | # 2 | # image-proxy 3 | # 4 | -------------------------------------------------------------------------------- /src/main/nspawn/app/hatcher/service/image-proxy/etc/network/interfaces: -------------------------------------------------------------------------------- 1 | # 2 | # https://wiki.alpinelinux.org/wiki/Configure_Networking 3 | # 4 | 5 | auto lo 6 | iface lo inet loopback 7 | -------------------------------------------------------------------------------- /src/main/nspawn/app/hatcher/service/image-proxy/etc/squid/ca/proxy-cert.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIICnDCCAYQCCQConVycCDtlJDANBgkqhkiG9w0BAQsFADAQMQ4wDAYDVQQKDAVw 3 | cm94eTAeFw0xNjA3MTYyMTQxMjRaFw00MzEyMDIyMTQxMjRaMBAxDjAMBgNVBAoM 4 | BXByb3h5MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA2d3Kk+4nMENY 5 | 1tf1uHuJ25pLfLxpUIawiujWJBWnGd+Fw/kgm3sXiaA0vqTAINOqAg8c4r2Ur1Ne 6 | E1RYc4LEvqWSlcCSJOp+azTlXWF7Ns7hEfxYz74SChFUfrpxaGnlqvHjHBQIsneV 7 | YjGF895+8D64DhDe8T4/3fLaWJX/0znp1HAPFoQfxFo6smenixnIdIxTbVY9y/WY 8 | dZaaCZv18pMdgTZtfNFuJQzqSxOXowFOZ0cMDSMKHGrqL5BHBJpffVKmzMpkVmEt 9 | MHh+sO48TY9pV2K8yGQbBPIujCRr0hi1g6q9me9W6D210EBApd1jEJwmM17TAhu8 10 | 03KZzNwCGwIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQDHtl/2fBHzRnDaelpFsnJS 11 | m3dOaaoWZdCtiiXyRXBNHVPjn3IFA+volKMt8PAsTkuWMEbH2V1SxZc5Zs8eomlk 12 | vPVVL8lJSWI/HiXdQG8sCiEH9TA5/fO9db5f0b0Mx2IHec6qwfJHdDVhk61CIa/w 13 | nu0/POV20DzXSWVA19i6mL9WJS3NQUkyPkY701QzPvbCvJlCnx8kZxB6i/eLdH8g 14 | p/v2teLKiHcxC8qV/LJAAost9PgRsE8BRx2QwsuR8uhe7W+QYEa4s5tZ7hFKjWQn 15 | UBkUCvkBgW7Kpsdd2Jba4zbVz0sYzgEP6Z+69J101kmSxGMFx6YveQQ/ZazSPxqs 16 | -----END CERTIFICATE----- 17 | -------------------------------------------------------------------------------- /src/main/nspawn/app/hatcher/service/image-proxy/etc/squid/ca/proxy-key.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIIEowIBAAKCAQEA2d3Kk+4nMENY1tf1uHuJ25pLfLxpUIawiujWJBWnGd+Fw/kg 3 | m3sXiaA0vqTAINOqAg8c4r2Ur1NeE1RYc4LEvqWSlcCSJOp+azTlXWF7Ns7hEfxY 4 | z74SChFUfrpxaGnlqvHjHBQIsneVYjGF895+8D64DhDe8T4/3fLaWJX/0znp1HAP 5 | FoQfxFo6smenixnIdIxTbVY9y/WYdZaaCZv18pMdgTZtfNFuJQzqSxOXowFOZ0cM 6 | DSMKHGrqL5BHBJpffVKmzMpkVmEtMHh+sO48TY9pV2K8yGQbBPIujCRr0hi1g6q9 7 | me9W6D210EBApd1jEJwmM17TAhu803KZzNwCGwIDAQABAoIBAQCA3TsxfLAZT/yA 8 | N4TT9d540i7MPPVd+Az8XPQsq9IG77tDS1wKGyi08tm/2dr6j7gDN8UyKEiF253N 9 | eRpIWJTe3zhw81J+G2VjLApuzXX/2SYvaT9uwVISOmXr/4OM6vY5wpE+zmrGhQ0K 10 | 45gDwxr6J17ekwQLVXKZaUfVFgQXfxZzysIwmV47DV72ZCiHXU+GUx0Us/P8XGZz 11 | 1WiNhs/lRR4xDoYO3oPTxWYwxKVD1hkv0J4zSEqITdUHI7KrHJ3rqwVZ9BS6ea8B 12 | 5+NjKt3XYTk//mvp4p2Tud9TNwYxqqzCtXb8UH3jgv/spvhZnr5683+98Bgs71v4 13 | JZGJd4jhAoGBAO+BL8p1XYltH25UICAUbWWkq7bRE6ZS/y4wXqxi4Tep1LWxDHGq 14 | O/cBAyMsD87z7N4hVbwkI8qbZi36eXdQpfCsqZCQLlOtpr9dHC09W8prQXJpP7Vo 15 | QjCYDsCd2Og5nAoxUO6U3sW/+Gbc9URiXtQif8bppUgosDuItX6Z+3H3AoGBAOjf 16 | FzsEqekFV4ahg70rxWjWW73sKZjsX2az4IpwV7MHwMlaoiF04a9W6Gav/rLVoBg3 17 | FbNQsNvpJiSZGKYGpHbKeQ4sYW3/TgiFn/Ywgz+0ZjXmB3ottqtT7bzvSIULQomU 18 | N+EGH/QBxsB2nY5XeJZr0BBzzpUKB2hpPURPDmf9AoGAVAvLj4p8Fnu6qebPlzJT 19 | 6XVN5Mudz6IUdkr3Dhts2TG5uUC2EzUf8d/jD/aoXYFvv8uG4Iz2GmVKCBx7f4lH 20 | khulLVvLRgYMG986wg1kJrZYsyOH/crmX1Mc6i76LRP6VAFWW0LWoiJpsv8a+rJi 21 | gKLSoCThoV7V6S5iMlkqyaUCgYBwGyYV1XLuqRGv0IWZRj8s7zUPQ2frJK/ww1uv 22 | idHOcEcRKD6xGJGlVZNgDA3zjgAaaDpcLtZVp/Ii4EkLdFssjOCmQBtPVv77/0g4 23 | vrYQrwodnHWxDdzIsgjbUd55nigUD3SvMcGkOE60TOYgpl95GsH5iabD/SX/z9oc 24 | zmpDCQKBgAMlR5EyD556y0KsOoJ/MvmfbAf7C/hlU6EnlWG12BVNIhHjpuuDE/zU 25 | MPD05rxk/NjJgsvc5RPooHWNJWpJ/UQ90USD1xHKLKSjInBVF+ZhSPFgZunaiLO6 26 | 3yHI0DB3u7N9VfThvmIFe1vZ6APcV7LVGAH9zXpn76J4F3OQSYFp 27 | -----END RSA PRIVATE KEY----- 28 | -------------------------------------------------------------------------------- /src/main/nspawn/app/hatcher/service/image-proxy/etc/squid/ca/readme.md: -------------------------------------------------------------------------------- 1 | 2 | ### ssl bump support 3 | -------------------------------------------------------------------------------- /src/main/nspawn/app/hatcher/service/image-proxy/etc/squid/script/store_id_prog.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import re 4 | import sys 5 | from urllib.parse import urlparse, urlunparse 6 | 7 | domain_mapper = dict([ 8 | (re.compile("(.+)[.](.+)[.]archive.org"), "local-squid.archive.org"), 9 | ]) 10 | 11 | 12 | def squash_domain(base_url:str) -> str: 13 | "" 14 | source_url = urlparse(base_url) 15 | source_host = source_url.hostname 16 | if source_host is not None: 17 | for pattern, target_host in domain_mapper.items(): 18 | if pattern.match(source_host): 19 | target_loc = source_url.netloc.replace(source_host, target_host) 20 | target_url = source_url._replace(netloc=target_loc) 21 | return urlunparse(target_url) 22 | return base_url 23 | 24 | 25 | def strip_query(full_url:str) -> str: 26 | "" 27 | term_url = full_url.split('?') 28 | base_url = term_url[0] 29 | return base_url 30 | 31 | 32 | def produce_id(term_line:str) -> str: 33 | "" 34 | term_list = term_line.split(' ') 35 | full_url = term_list[0] 36 | base_url = strip_query(full_url) 37 | store_id = squash_domain(base_url) 38 | return store_id 39 | 40 | 41 | def invoke_main(): 42 | "" 43 | log_dir = "/var/log/squid" 44 | log_file = f"{log_dir}/store_id_prog.log" 45 | with open(log_file, "a") as logger: 46 | for term_line in sys.stdin: 47 | store_id = produce_id(term_line) 48 | sys.stdout.write(f"OK store-id={store_id}\n") 49 | sys.stdout.flush() 50 | logger.write(f"{store_id} <- {term_line}\n") 51 | 52 | 53 | if __name__ == '__main__': 54 | invoke_main() 55 | -------------------------------------------------------------------------------- /src/main/nspawn/app/hatcher/service/image-proxy/etc/squid/script/store_id_prog.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | #set -x 5 | 6 | # http://wiki.squid-cache.org/Features/StoreID 7 | 8 | # http://www.squid-cache.org/Doc/config/store_id_program/ 9 | 10 | # archive site pattern 11 | #readonly prefix="http://download.oracle.com/otn-pub" 12 | 13 | # transform input url to store id response 14 | #readonly command="s%${prefix}/(.+)\?.+%OK store-id=${prefix}/\1%" 15 | #readonly command="s%(${prefix})/(.+)(\?).+%OK store-id=\1/\2%" 16 | 17 | [[ "$log_dir" ]] || log_dir="/var/log/squid" 18 | 19 | log() { 20 | [[ -e "$log_dir" ]] || return 0 21 | echo "$1" >> "$log_dir/store_id_prog.log" 22 | } 23 | 24 | # line: head'?'tail' 'rest 25 | 26 | while IFS='\n'; read line; do 27 | log "### $(date)" 28 | log "1: $line" 29 | 30 | IFS=' '; set -- $line 31 | line=$1 # no rest 32 | log "2: $line" 33 | 34 | IFS='?'; set -- $line 35 | line=$1 # no tail 36 | log "3: $line" 37 | 38 | echo "OK store-id=$line" 39 | done 40 | -------------------------------------------------------------------------------- /src/main/nspawn/app/hatcher/service/image-proxy/etc/squid/script/store_id_test.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import store_id_prog 4 | 5 | entry_list = [ 6 | ( 7 | "http://download.oracle.com/otn-pub/java/jce/8/jce_policy-8.zip 192.168.1.103/192.168.1.103 - GET myip=192.168.1.137 myport=3128", 8 | "http://download.oracle.com/otn-pub/java/jce/8/jce_policy-8.zip", 9 | ), 10 | ( 11 | "http://download.oracle.com/otn-pub/java/jce/8/jce_policy-8.zip?AuthParam=1468794966_3ec82d3de3b479a3fc7faec9ca20180b 192.168.1.103/work3 - GET myip=192.168.1.137 myport=3128", 12 | "http://download.oracle.com/otn-pub/java/jce/8/jce_policy-8.zip", 13 | ), 14 | ( 15 | "https://ia803006.us.archive.org/0/items/archlinux_pkg_python-pytoml/python-pytoml-0.1.21-3-any.pkg.tar.xz 192.168.1.103/192.168.1.103 - GET myip=192.168.1.137 myport=3128", 16 | "https://local-squid.archive.org/0/items/archlinux_pkg_python-pytoml/python-pytoml-0.1.21-3-any.pkg.tar.xz", 17 | ), 18 | ] 19 | 20 | for entry in entry_list: 21 | source = entry[0] 22 | target = entry[1] 23 | print(f"---") 24 | print(f"source: {source}") 25 | print(f"target: {target}") 26 | assert target == store_id_prog.produce_id(source) 27 | -------------------------------------------------------------------------------- /src/main/nspawn/app/hatcher/service/image-proxy/etc/squid/script/store_id_test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # http://wiki.squid-cache.org/Features/StoreID 4 | 5 | base=$(dirname $0) 6 | 7 | #url="http://download.oracle.com/otn-pub/java/jce/8/jce_policy-8.zip?AuthParam=1465952700_368f05a55369f4f4e95e8430a2d1b3a3" 8 | #url="http://download.oracle.com/otn-pub/java/jdk/8u92-b14/jdk-8u92-linux-x64.tar.gz?AuthParam=1465954431_534cee932ed1b6efc4c08bdd483c5591" 9 | #echo "$url" | $base/store_id.sh 10 | 11 | export log_dir="$base" 12 | 13 | invoke() { 14 | echo "line 0: $line" 15 | echo $line | $base/store_id_prog.sh 16 | busybox sh -c "echo $line" | busybox sh -c "$base/store_id_prog.sh" 17 | } 18 | 19 | echo 20 | 21 | line="http://download.oracle.com/otn-pub/java/jce/8/jce_policy-8.zip 192.168.1.103/192.168.1.103 - GET myip=192.168.1.137 myport=3128" 22 | invoke 23 | 24 | echo 25 | 26 | line="http://download.oracle.com/otn-pub/java/jce/8/jce_policy-8.zip?AuthParam=1468794966_3ec82d3de3b479a3fc7faec9ca20180b 192.168.1.103/work3 - GET myip=192.168.1.137 myport=3128" 27 | invoke 28 | -------------------------------------------------------------------------------- /src/main/nspawn/app/hatcher/service/image-proxy/etc/squid/store_id_prog.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import re 4 | import sys 5 | from urllib.parse import urlparse, urlunparse 6 | 7 | domain_mapper = dict([ 8 | (re.compile("(.+)[.](.+)[.]archive.org"), "archive.org.internal"), 9 | ]) 10 | 11 | 12 | def squash_domain(base_url:str) -> str: 13 | "" 14 | source_url = urlparse(base_url) 15 | source_host = source_url.hostname 16 | if source_host is not None: 17 | for pattern, target_host in domain_mapper.items(): 18 | if pattern.match(source_host): 19 | target_loc = source_url.netloc.replace(source_host, target_host) 20 | target_url = source_url._replace(netloc=target_loc) 21 | return urlunparse(target_url) 22 | return base_url 23 | 24 | 25 | def strip_query(full_url:str) -> str: 26 | "" 27 | term_url = full_url.split('?') 28 | base_url = term_url[0] 29 | return base_url 30 | 31 | 32 | def produce_id(term_line:str) -> str: 33 | "" 34 | term_list = term_line.split(' ') 35 | full_url = term_list[0] 36 | base_url = strip_query(full_url) 37 | store_id = squash_domain(base_url) 38 | return store_id 39 | 40 | 41 | def invoke_main(): 42 | "" 43 | for term_line in sys.stdin: 44 | store_id = produce_id(term_line) 45 | sys.stdout.write(f"OK store-id={store_id}\n") 46 | sys.stdout.flush() 47 | 48 | 49 | if __name__ == '__main__': 50 | invoke_main() 51 | -------------------------------------------------------------------------------- /src/main/nspawn/app/hatcher/service/image-proxy/etc/squid/store_id_test.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import store_id_prog 4 | 5 | entry_list = [ 6 | ( 7 | "http://download.oracle.com/otn-pub/java/jce/8/jce_policy-8.zip 192.168.1.103/192.168.1.103 - GET myip=192.168.1.137 myport=3128", 8 | "http://download.oracle.com/otn-pub/java/jce/8/jce_policy-8.zip", 9 | ), 10 | ( 11 | "http://download.oracle.com/otn-pub/java/jce/8/jce_policy-8.zip?AuthParam=1468794966_3ec82d3de3b479a3fc7faec9ca20180b 192.168.1.103/work3 - GET myip=192.168.1.137 myport=3128", 12 | "http://download.oracle.com/otn-pub/java/jce/8/jce_policy-8.zip", 13 | ), 14 | ( 15 | "https://ia803006.us.archive.org/0/items/archlinux_pkg_python-pytoml/python-pytoml-0.1.21-3-any.pkg.tar.xz 192.168.1.103/192.168.1.103 - GET myip=192.168.1.137 myport=3128", 16 | "https://archive.org.internal/0/items/archlinux_pkg_python-pytoml/python-pytoml-0.1.21-3-any.pkg.tar.xz", 17 | ), 18 | ( 19 | "https://archive.archlinux.org/repos/2020/01/01/community/os/x86_64/python-wheel-0.33.6-3-any.pkg.tar.xz 192.168.1.103/work3 - GET myip=192.168.1.137 myport=3128", 20 | "https://archive.archlinux.org/repos/2020/01/01/community/os/x86_64/python-wheel-0.33.6-3-any.pkg.tar.xz", 21 | ), 22 | ] 23 | 24 | for entry in entry_list: 25 | source = entry[0] 26 | target = entry[1] 27 | print(f"---") 28 | print(f"source: {source}") 29 | print(f"target: {target}") 30 | assert target == store_id_prog.produce_id(source) 31 | -------------------------------------------------------------------------------- /src/main/nspawn/app/hatcher/service/image-proxy/readme.md: -------------------------------------------------------------------------------- 1 | 2 | ### image proxy 3 | 4 | hatcher image proxy build and setup scripts 5 | -------------------------------------------------------------------------------- /src/main/nspawn/app/hatcher/service/image-proxy/root/.config/htop/htoprc: -------------------------------------------------------------------------------- 1 | # Beware! This file is rewritten by htop when settings are changed in the interface. 2 | # The parser is also very primitive, and not human-friendly. 3 | fields=0 48 17 18 38 39 40 2 46 47 49 1 4 | sort_key=46 5 | sort_direction=1 6 | hide_threads=0 7 | hide_kernel_threads=1 8 | hide_userland_threads=0 9 | shadow_other_users=0 10 | show_thread_names=0 11 | show_program_path=1 12 | highlight_base_name=0 13 | highlight_megabytes=1 14 | highlight_threads=1 15 | tree_view=1 16 | header_margin=1 17 | detailed_cpu_time=0 18 | cpu_count_from_zero=0 19 | update_process_names=0 20 | account_guest_in_cpu_meter=0 21 | color_scheme=0 22 | delay=15 23 | left_meters=LeftCPUs2 Memory Swap 24 | left_meter_modes=1 1 1 25 | right_meters=RightCPUs2 Tasks LoadAverage Uptime 26 | right_meter_modes=1 2 2 2 27 | -------------------------------------------------------------------------------- /src/main/nspawn/app/hatcher/service/image-proxy/root/.config/mc/panels.ini: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/random-python/nspawn/305268430526a17ce8e3e96e5f2f501cecdb6d92/src/main/nspawn/app/hatcher/service/image-proxy/root/.config/mc/panels.ini -------------------------------------------------------------------------------- /src/main/nspawn/app/hatcher/service/image-proxy/root/.config/readme.md: -------------------------------------------------------------------------------- 1 | 2 | ### note 3 | 4 | default user app settings 5 | -------------------------------------------------------------------------------- /src/main/nspawn/app/hatcher/service/image-proxy/root/.ssh/readme.md: -------------------------------------------------------------------------------- 1 | 2 | ### note 3 | 4 | ensure proper chmod: no go=rx on */.ssh path 5 | -------------------------------------------------------------------------------- /src/main/nspawn/app/hatcher/service/image-proxy/root/readme.md: -------------------------------------------------------------------------------- 1 | 2 | ### root 3 | 4 | variable="{{variable}}" 5 | -------------------------------------------------------------------------------- /src/main/nspawn/app/hatcher/service/image-proxy/setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | """ 4 | Service setup script. 5 | """ 6 | 7 | from nspawn.setup import * 8 | 9 | # load shared config 10 | import os 11 | import runpy 12 | this_dir = os.path.dirname(os.path.abspath(__file__)) 13 | arkon = runpy.run_path(f"{this_dir}/arkon.py") 14 | image_url = arkon['image_url'] 15 | alpine_url = arkon['alpine_url'] 16 | service_config = arkon['service_config'] 17 | 18 | machine_name = service_config['machine_name'] 19 | service_gid = service_config['service_gid'] 20 | service_uid = service_config['service_uid'] 21 | service_home = service_config['service_home'] 22 | service_log_dir = service_config['service_log_dir'] 23 | service_store_dir = service_config['service_store_dir'] 24 | 25 | service_etc = f"{service_home}/etc" 26 | service_etc_tmp = f"{service_home}/etc_tmp" 27 | 28 | # discover host network 29 | network_face = TOOL.select_interface() 30 | 31 | # 32 | # perform setup 33 | # 34 | 35 | IMAGE(image_url) 36 | 37 | MACHINE(machine_name) 38 | 39 | WITH( 40 | Quiet="yes", 41 | KeepUnit="yes", 42 | Register="yes", 43 | MACVLAN=network_face, 44 | ) 45 | 46 | # admin access 47 | WITH(BindReadOnly="/root/.ssh/authorized_keys") 48 | 49 | # system logging 50 | WITH(Bind=f"{service_log_dir}:/var/log/") 51 | 52 | # storage folder 53 | WITH(Bind=f"{service_store_dir}:/var/cache/squid/") 54 | 55 | # expose settings 56 | WITH(Overlay=f"+/etc:{service_etc}:{service_etc_tmp}:/etc") 57 | 58 | # setup permissions 59 | SH(f""" 60 | mkdir -p "{service_home}" 61 | mkdir -p "{service_store_dir}" 62 | mkdir -p "{service_log_dir}/squid" 63 | chmod -R o-rwx "{service_home}" 64 | chown -R {service_uid}:{service_gid} "{service_home}" 65 | """) 66 | -------------------------------------------------------------------------------- /src/main/nspawn/app/hatcher/service/image-proxy/unsetup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import os 4 | 5 | this_dir = os.path.dirname(os.path.abspath(__file__)) 6 | 7 | os.system(f"{this_dir}/setup.py --action desure") 8 | -------------------------------------------------------------------------------- /src/main/nspawn/app/hatcher/service/image-server/arkon.py: -------------------------------------------------------------------------------- 1 | """ 2 | Service shared config. 3 | """ 4 | 5 | import platform 6 | from nspawn import CONFIG 7 | 8 | epoch = "3.11" 9 | release = f"{epoch}.3" 10 | hardware = platform.machine() 11 | 12 | provision = CONFIG['storage']['provision'] 13 | image_url = f"file://localhost/{provision}/alpine/image-server/default-{release}-{hardware}.tar.gz" 14 | alpine_url = f"http://dl-cdn.alpinelinux.org/alpine/v{epoch}/releases/{hardware}/alpine-minirootfs-{release}-{hardware}.tar.gz" 15 | service_config = CONFIG['hatcher/image-server'] 16 | -------------------------------------------------------------------------------- /src/main/nspawn/app/hatcher/service/image-server/build.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | """ 4 | Service build script. 5 | """ 6 | 7 | from nspawn.build import * 8 | 9 | # load shared config 10 | import os 11 | import runpy 12 | this_dir = os.path.dirname(os.path.abspath(__file__)) 13 | arkon = runpy.run_path(f"{this_dir}/arkon.py") 14 | image_url = arkon['image_url'] 15 | alpine_url = arkon['alpine_url'] 16 | service_config = arkon['service_config'] 17 | 18 | machine_name = service_config['machine_name'] 19 | service_gid = service_config['service_gid'] 20 | service_uid = service_config['service_uid'] 21 | service_home = service_config['service_home'] 22 | service_log_dir = service_config['service_log_dir'] 23 | service_store_dir = service_config['service_store_dir'] 24 | service_user_file = service_config['service_user_file'] 25 | 26 | # 27 | # perfrom build 28 | # 29 | 30 | IMAGE(image_url) 31 | 32 | PULL(alpine_url) 33 | 34 | WITH( 35 | Boot='yes', 36 | KillSignal="SIGUSR1", 37 | ) 38 | 39 | # nginx storage folder 40 | WITH(Bind=service_store_dir + '/') 41 | 42 | # nginx web access credentials 43 | WITH(BindReadOnly=service_user_file) 44 | 45 | # system logging 46 | WITH(Bind=f"{service_log_dir}:/var/log/") 47 | 48 | # default config 49 | COPY("/etc") 50 | COPY("/root") 51 | 52 | # configure host name 53 | CAST("/etc/hostname", 54 | machine_name=machine_name, 55 | ) 56 | 57 | # configure web server 58 | CAST("/etc/nginx/conf.d/image.conf", 59 | service_store_dir=service_store_dir, 60 | service_user_file=service_user_file, 61 | ) 62 | 63 | # basic system setup 64 | SH("apk update") 65 | SH("apk upgrade") 66 | SH("apk add tzdata") 67 | SH("apk add ca-certificates") 68 | SH("apk add busybox-initscripts") 69 | SH("apk add mc htop pwgen") 70 | SH("apk add syslog-ng logrotate") 71 | # SH("apk add iputils iproute2") # breaks some dependency 72 | SH("apk add dhcpcd openssh") 73 | SH("apk add nginx openssl") 74 | 75 | # ensure system services 76 | SH("rc-update add syslog-ng") 77 | SH("rc-update add dhcpcd") 78 | SH("rc-update add nginx") 79 | SH("rc-update add crond") 80 | SH("rc-update add sshd") 81 | 82 | # web tls 83 | SH("/etc/nginx/conf.d/image-tls.sh") 84 | 85 | # container ssh access 86 | SH(""" 87 | echo 'PubkeyAuthentication yes' >> /etc/ssh/sshd_config 88 | echo 'PasswordAuthentication no' >> /etc/ssh/sshd_config 89 | user=root; pass=$(pwgen -s 64 1); echo $user:$pass | chpasswd 90 | """) 91 | 92 | PUSH() 93 | -------------------------------------------------------------------------------- /src/main/nspawn/app/hatcher/service/image-server/etc/hostname: -------------------------------------------------------------------------------- 1 | {{machine_name}} 2 | -------------------------------------------------------------------------------- /src/main/nspawn/app/hatcher/service/image-server/etc/inittab: -------------------------------------------------------------------------------- 1 | # /etc/inittab 2 | # https://git.alpinelinux.org/aports/tree/main/alpine-baselayout/inittab 3 | 4 | ::sysinit:/sbin/openrc sysinit 5 | ::sysinit:/sbin/openrc boot 6 | ::wait:/sbin/openrc default 7 | 8 | # Set up a couple of getty's 9 | #tty1::respawn:/sbin/getty 38400 tty1 10 | #tty2::respawn:/sbin/getty 38400 tty2 11 | #tty3::respawn:/sbin/getty 38400 tty3 12 | 13 | # Put a getty on the serial port 14 | #ttyS0::respawn:/sbin/getty -L ttyS0 115200 vt100 15 | 16 | # Put a getty on the console 17 | console::respawn:/sbin/getty 115200,38400,9600 console 18 | 19 | # Stuff to do for the 3-finger salute 20 | ::ctrlaltdel:/sbin/reboot 21 | 22 | # Stuff to do before rebooting 23 | ::shutdown:/sbin/openrc shutdown 24 | -------------------------------------------------------------------------------- /src/main/nspawn/app/hatcher/service/image-server/etc/motd: -------------------------------------------------------------------------------- 1 | # 2 | # image-server 3 | # 4 | -------------------------------------------------------------------------------- /src/main/nspawn/app/hatcher/service/image-server/etc/network/interfaces: -------------------------------------------------------------------------------- 1 | # 2 | # https://wiki.alpinelinux.org/wiki/Configure_Networking 3 | # 4 | 5 | auto lo 6 | iface lo inet loopback 7 | -------------------------------------------------------------------------------- /src/main/nspawn/app/hatcher/service/image-server/etc/nginx/conf.d/arkon.conf: -------------------------------------------------------------------------------- 1 | # 2 | # inclued in html{ ... } 3 | # 4 | 5 | # https://github.com/linuxserver/docker-letsencrypt/issues/67 6 | #client_body_temp_path /var/tmp/nginx_srv/client; 7 | #proxy_temp_path /var/tmp/nginx_srv/proxy; 8 | #fastcgi_temp_path /var/tmp/nginx_srv/fastcgi; 9 | #uwsgi_temp_path /var/tmp/nginx_srv/uwsgi; 10 | #scgi_temp_path /var/tmp/nginx_srv/scgi; 11 | -------------------------------------------------------------------------------- /src/main/nspawn/app/hatcher/service/image-server/etc/nginx/conf.d/default.conf: -------------------------------------------------------------------------------- 1 | # 2 | # disable default 3 | # 4 | -------------------------------------------------------------------------------- /src/main/nspawn/app/hatcher/service/image-server/etc/nginx/conf.d/image-tls.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | # 4 | # image server 5 | # 6 | 7 | # generate tls keys 8 | # https://smoothnet.org/squid-proxy-with-ssl-bump/ 9 | # https://github.com/vmware/ansible-kubernetes-ca/blob/master/tasks/main.yml 10 | 11 | set -e -u 12 | 13 | dir=$(dirname $0) 14 | ca_key=$dir/image-key.pem 15 | ca_csr=$dir/image-req.pem 16 | ca_cert=$dir/image-cert.pem 17 | ca_subj="/O=image" 18 | 19 | openssl genrsa -out $ca_key 2048 20 | openssl req -new -batch -key $ca_key -out $ca_csr -subj $ca_subj 21 | openssl x509 -req -days 10000 -in $ca_csr -signkey $ca_key -out $ca_cert 22 | -------------------------------------------------------------------------------- /src/main/nspawn/app/hatcher/service/image-server/etc/nginx/conf.d/image.conf: -------------------------------------------------------------------------------- 1 | # 2 | # image server 3 | # 4 | 5 | server { 6 | 7 | listen 80; 8 | server_name image image.lan; 9 | 10 | gzip off; 11 | 12 | auth_basic "image"; 13 | auth_basic_user_file {{service_user_file}}; 14 | 15 | sendfile on; 16 | sendfile_max_chunk 1M; 17 | 18 | client_max_body_size 2G; 19 | client_body_buffer_size 2M; 20 | client_header_buffer_size 2K; 21 | 22 | client_body_temp_path /tmp; 23 | 24 | location / { 25 | 26 | root {{service_store_dir}}; 27 | 28 | autoindex on; 29 | autoindex_localtime on; 30 | autoindex_exact_size off; 31 | 32 | dav_methods PUT; 33 | dav_access user:rw group:r; 34 | create_full_put_path on; 35 | 36 | } 37 | } 38 | 39 | server { 40 | 41 | listen 443 ssl; 42 | server_name image image.lan; 43 | 44 | ssl_certificate /etc/nginx/conf.d/image-cert.pem; 45 | ssl_certificate_key /etc/nginx/conf.d/image-key.pem; 46 | 47 | gzip off; 48 | 49 | auth_basic "image"; 50 | auth_basic_user_file {{service_user_file}}; 51 | 52 | sendfile on; 53 | sendfile_max_chunk 1M; 54 | 55 | client_max_body_size 1G; 56 | client_body_buffer_size 1M; 57 | client_header_buffer_size 1K; 58 | 59 | client_body_temp_path /var/tmp; 60 | 61 | location / { 62 | 63 | root {{service_store_dir}}; 64 | 65 | autoindex on; 66 | autoindex_localtime on; 67 | autoindex_exact_size off; 68 | 69 | dav_methods PUT; 70 | dav_access user:rw group:r; 71 | create_full_put_path on; 72 | 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/main/nspawn/app/hatcher/service/image-server/readme.md: -------------------------------------------------------------------------------- 1 | 2 | ### image server 3 | 4 | hatcher image server build and setup scripts 5 | -------------------------------------------------------------------------------- /src/main/nspawn/app/hatcher/service/image-server/root/.config/htop/htoprc: -------------------------------------------------------------------------------- 1 | # Beware! This file is rewritten by htop when settings are changed in the interface. 2 | # The parser is also very primitive, and not human-friendly. 3 | fields=0 48 17 18 38 39 40 2 46 47 49 1 4 | sort_key=46 5 | sort_direction=1 6 | hide_threads=0 7 | hide_kernel_threads=1 8 | hide_userland_threads=0 9 | shadow_other_users=0 10 | show_thread_names=0 11 | show_program_path=1 12 | highlight_base_name=0 13 | highlight_megabytes=1 14 | highlight_threads=1 15 | tree_view=1 16 | header_margin=1 17 | detailed_cpu_time=0 18 | cpu_count_from_zero=0 19 | update_process_names=0 20 | account_guest_in_cpu_meter=0 21 | color_scheme=0 22 | delay=15 23 | left_meters=LeftCPUs2 Memory Swap 24 | left_meter_modes=1 1 1 25 | right_meters=RightCPUs2 Tasks LoadAverage Uptime 26 | right_meter_modes=1 2 2 2 27 | -------------------------------------------------------------------------------- /src/main/nspawn/app/hatcher/service/image-server/root/.config/mc/panels.ini: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/random-python/nspawn/305268430526a17ce8e3e96e5f2f501cecdb6d92/src/main/nspawn/app/hatcher/service/image-server/root/.config/mc/panels.ini -------------------------------------------------------------------------------- /src/main/nspawn/app/hatcher/service/image-server/root/.config/readme.md: -------------------------------------------------------------------------------- 1 | 2 | ### note 3 | 4 | default user app settings 5 | -------------------------------------------------------------------------------- /src/main/nspawn/app/hatcher/service/image-server/root/.ssh/readme.md: -------------------------------------------------------------------------------- 1 | 2 | ### note 3 | 4 | ensure proper chmod: no go=rx on */.ssh path 5 | -------------------------------------------------------------------------------- /src/main/nspawn/app/hatcher/service/image-server/root/readme.md: -------------------------------------------------------------------------------- 1 | 2 | ### root 3 | 4 | variable="{{variable}}" 5 | -------------------------------------------------------------------------------- /src/main/nspawn/app/hatcher/service/image-server/setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | """ 4 | Service setup script. 5 | """ 6 | 7 | from nspawn.setup import * 8 | 9 | # load shared config 10 | import os 11 | import runpy 12 | this_dir = os.path.dirname(os.path.abspath(__file__)) 13 | arkon = runpy.run_path(f"{this_dir}/arkon.py") 14 | image_url = arkon['image_url'] 15 | alpine_url = arkon['alpine_url'] 16 | service_config = arkon['service_config'] 17 | 18 | machine_name = service_config['machine_name'] 19 | service_gid = service_config['service_gid'] 20 | service_uid = service_config['service_uid'] 21 | service_home = service_config['service_home'] 22 | service_log_dir = service_config['service_log_dir'] 23 | service_store_dir = service_config['service_store_dir'] 24 | service_user_file = service_config['service_user_file'] 25 | 26 | user_file = service_config['service_user_file'] 27 | user_dir = os.path.dirname(user_file) 28 | user_data = """ 29 | # nginx user login database 30 | # http://nginx.org/en/docs/http/ngx_http_auth_basic_module.html 31 | default:{PLAIN}default 32 | """ 33 | 34 | # discover host network 35 | network_face = TOOL.select_interface() 36 | 37 | # 38 | # perform setup 39 | # 40 | 41 | IMAGE(image_url) 42 | 43 | MACHINE(machine_name) 44 | 45 | WITH( 46 | Quiet="yes", 47 | KeepUnit="yes", 48 | Register="yes", 49 | MACVLAN=network_face, 50 | ) 51 | 52 | WITH(BindReadOnly="/root/.ssh/authorized_keys") 53 | 54 | # setup storage 55 | SH(f""" 56 | mkdir -p "{service_home}" 57 | mkdir -p "{service_store_dir}" 58 | mkdir -p "{service_log_dir}" 59 | mkdir -p "{service_log_dir}/nginx" 60 | mkdir -p "{user_dir}" 61 | chmod -R o-rwx "{service_home}" 62 | chown -R {service_uid}:{service_gid} "{service_home}" 63 | """) 64 | 65 | # setup http access 66 | SH(f""" 67 | if [ -s "{user_file}" ] ; then 68 | echo "Keeping exising user file: {user_file}" 69 | else 70 | echo "Writing default user file: {user_file}" 71 | echo "{user_data}" > "{user_file}" 72 | fi 73 | """) 74 | -------------------------------------------------------------------------------- /src/main/nspawn/app/hatcher/service/image-server/unsetup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import os 4 | 5 | this_dir = os.path.dirname(os.path.abspath(__file__)) 6 | 7 | os.system(f"{this_dir}/setup.py --action desure") 8 | -------------------------------------------------------------------------------- /src/main/nspawn/app/hatcher/service/image-syncer/arkon.py: -------------------------------------------------------------------------------- 1 | """ 2 | Service shared config. 3 | """ 4 | 5 | import platform 6 | from nspawn import CONFIG 7 | 8 | epoch = "3.11" 9 | release = f"{epoch}.3" 10 | hardware = platform.machine() 11 | 12 | provision = CONFIG['storage']['provision'] 13 | image_url = f"file://localhost/{provision}/alpine/image-syncer/default-{release}-{hardware}.tar.gz" 14 | alpine_url = f"http://dl-cdn.alpinelinux.org/alpine/v{epoch}/releases/{hardware}/alpine-minirootfs-{release}-{hardware}.tar.gz" 15 | service_config = CONFIG['hatcher/image-syncer'] 16 | -------------------------------------------------------------------------------- /src/main/nspawn/app/hatcher/service/image-syncer/build.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | """ 4 | Service build script. 5 | """ 6 | 7 | from nspawn.build import * 8 | 9 | # load shared config 10 | import os 11 | import runpy 12 | this_dir = os.path.dirname(os.path.abspath(__file__)) 13 | arkon = runpy.run_path(f"{this_dir}/arkon.py") 14 | image_url = arkon['image_url'] 15 | alpine_url = arkon['alpine_url'] 16 | service_config = arkon['service_config'] 17 | 18 | machine_name = service_config['machine_name'] 19 | service_log_dir = service_config['service_log_dir'] 20 | service_store_dir = service_config['service_store_dir'] 21 | 22 | 23 | # 24 | # perfrom build 25 | # 26 | 27 | IMAGE(image_url) 28 | 29 | PULL(alpine_url) 30 | 31 | WITH( 32 | Boot='yes', 33 | KillSignal="SIGUSR1", 34 | ) 35 | 36 | # nginx storage folder 37 | WITH(Bind=service_store_dir + '/') 38 | 39 | # system logging 40 | WITH(Bind=f"{service_log_dir}:/var/log/") 41 | 42 | # default config 43 | COPY("/etc") 44 | COPY("/root") 45 | 46 | # configure host name 47 | CAST("/etc/hostname", 48 | machine_name=machine_name, 49 | ) 50 | 51 | # configure amazon file sync 52 | CAST("/etc/file_sync_s3/arkon.ini", 53 | service_config=service_config, 54 | ) 55 | 56 | # basic system setup 57 | SH("apk update") 58 | SH("apk upgrade") 59 | SH("apk add tzdata") 60 | SH("apk add ca-certificates") 61 | SH("apk add busybox-initscripts") 62 | SH("apk add mc htop pwgen") 63 | SH("apk add syslog-ng logrotate") 64 | SH("apk add dhcpcd openssh") 65 | 66 | # provide amazon file sync 67 | SH("apk add inotify-tools") 68 | SH("apk add python3 py3-pip") 69 | SH("pip3 install file_sync_s3") 70 | SH("file_sync_s3_install") 71 | 72 | # ensure system services 73 | SH("rc-update add syslog-ng") 74 | SH("rc-update add dhcpcd") 75 | SH("rc-update add crond") 76 | SH("rc-update add sshd") 77 | 78 | # container ssh access 79 | SH(""" 80 | echo 'PubkeyAuthentication yes' >> /etc/ssh/sshd_config 81 | echo 'PasswordAuthentication no' >> /etc/ssh/sshd_config 82 | user=root; pass=$(pwgen -s 64 1); echo $user:$pass | chpasswd 83 | """) 84 | 85 | PUSH() 86 | -------------------------------------------------------------------------------- /src/main/nspawn/app/hatcher/service/image-syncer/etc/file_sync_s3/arkon.ini: -------------------------------------------------------------------------------- 1 | # 2 | # service configuration 3 | # 4 | 5 | # 6 | # https://aws.amazon.com/about-aws/global-infrastructure/regions_az/ 7 | # 8 | [amazon/access] 9 | 10 | # aws s3 credentials 11 | access_key = {{service_config['service_access_key']}} 12 | 13 | # aws s3 credentials 14 | secret_key = {{service_config['service_secret_key']}} 15 | 16 | # aws s3 region name, i.e.: us-east-1 17 | region_name = {{service_config['service_region_name']}} 18 | 19 | # aws s3 bucket name, i.e.: storage.example.com 20 | bucket_name = {{service_config['service_bucket_name']}} 21 | 22 | # aws s3 object canned acl, https://docs.aws.amazon.com/AmazonS3/latest/dev/acl-overview.html#canned-acl 23 | object_mode = {{service_config['service_object_mode']}} 24 | 25 | # 26 | # watcher settings 27 | # 28 | [folder/watcher] 29 | 30 | # location of monitored folder 31 | folder_path = {{service_config['service_store_dir']}} 32 | 33 | # file event reaction window, seconds 34 | watcher_timeout@int = 3 35 | 36 | # enable recursive folder watch 37 | watcher_recursive@bool = yes 38 | 39 | # match expression for file inclusion 40 | regex_include@list = 41 | {% filter indent(width=4) %} 42 | {{service_config['service_include_list']}} 43 | {% endfilter %} 44 | 45 | # match expression for file exclusion 46 | regex_exclude@list = 47 | {% filter indent(width=4) %} 48 | {{service_config['service_exclude_list']}} 49 | {% endfilter %} 50 | 51 | # enable file expiration 52 | keeper_expire@bool = {{service_config['service_use_expire']}} 53 | 54 | # expire files older then, days 55 | keeper_diem_span@int = {{service_config['service_expire_days']}} 56 | 57 | # file expiration scanning period, time delta 58 | keeper_scan_period@timedelta = {{service_config['service_expire_period']}} 59 | 60 | -------------------------------------------------------------------------------- /src/main/nspawn/app/hatcher/service/image-syncer/etc/hostname: -------------------------------------------------------------------------------- 1 | {{machine_name}} 2 | -------------------------------------------------------------------------------- /src/main/nspawn/app/hatcher/service/image-syncer/etc/inittab: -------------------------------------------------------------------------------- 1 | # /etc/inittab 2 | # https://git.alpinelinux.org/aports/tree/main/alpine-baselayout/inittab 3 | 4 | ::sysinit:/sbin/openrc sysinit 5 | ::sysinit:/sbin/openrc boot 6 | ::wait:/sbin/openrc default 7 | 8 | # Set up a couple of getty's 9 | #tty1::respawn:/sbin/getty 38400 tty1 10 | #tty2::respawn:/sbin/getty 38400 tty2 11 | #tty3::respawn:/sbin/getty 38400 tty3 12 | 13 | # Put a getty on the serial port 14 | #ttyS0::respawn:/sbin/getty -L ttyS0 115200 vt100 15 | 16 | # Put a getty on the console 17 | console::respawn:/sbin/getty 115200,38400,9600 console 18 | 19 | # Stuff to do for the 3-finger salute 20 | ::ctrlaltdel:/sbin/reboot 21 | 22 | # Stuff to do before rebooting 23 | ::shutdown:/sbin/openrc shutdown 24 | -------------------------------------------------------------------------------- /src/main/nspawn/app/hatcher/service/image-syncer/etc/motd: -------------------------------------------------------------------------------- 1 | # 2 | # image-syncer 3 | # 4 | -------------------------------------------------------------------------------- /src/main/nspawn/app/hatcher/service/image-syncer/etc/network/interfaces: -------------------------------------------------------------------------------- 1 | # 2 | # https://wiki.alpinelinux.org/wiki/Configure_Networking 3 | # 4 | 5 | auto lo 6 | iface lo inet loopback 7 | -------------------------------------------------------------------------------- /src/main/nspawn/app/hatcher/service/image-syncer/readme.md: -------------------------------------------------------------------------------- 1 | 2 | ### image syncer 3 | 4 | hatcher amazon s3 image syncer build and setup scripts 5 | -------------------------------------------------------------------------------- /src/main/nspawn/app/hatcher/service/image-syncer/root/.config/htop/htoprc: -------------------------------------------------------------------------------- 1 | # Beware! This file is rewritten by htop when settings are changed in the interface. 2 | # The parser is also very primitive, and not human-friendly. 3 | fields=0 48 17 18 38 39 40 2 46 47 49 1 4 | sort_key=46 5 | sort_direction=1 6 | hide_threads=0 7 | hide_kernel_threads=1 8 | hide_userland_threads=0 9 | shadow_other_users=0 10 | show_thread_names=0 11 | show_program_path=1 12 | highlight_base_name=0 13 | highlight_megabytes=1 14 | highlight_threads=1 15 | tree_view=1 16 | header_margin=1 17 | detailed_cpu_time=0 18 | cpu_count_from_zero=0 19 | update_process_names=0 20 | account_guest_in_cpu_meter=0 21 | color_scheme=0 22 | delay=15 23 | left_meters=LeftCPUs2 Memory Swap 24 | left_meter_modes=1 1 1 25 | right_meters=RightCPUs2 Tasks LoadAverage Uptime 26 | right_meter_modes=1 2 2 2 27 | -------------------------------------------------------------------------------- /src/main/nspawn/app/hatcher/service/image-syncer/root/.config/mc/panels.ini: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/random-python/nspawn/305268430526a17ce8e3e96e5f2f501cecdb6d92/src/main/nspawn/app/hatcher/service/image-syncer/root/.config/mc/panels.ini -------------------------------------------------------------------------------- /src/main/nspawn/app/hatcher/service/image-syncer/root/.config/readme.md: -------------------------------------------------------------------------------- 1 | 2 | ### note 3 | 4 | default user app settings 5 | -------------------------------------------------------------------------------- /src/main/nspawn/app/hatcher/service/image-syncer/root/.ssh/readme.md: -------------------------------------------------------------------------------- 1 | 2 | ### note 3 | 4 | ensure proper chmod: no go=rx on */.ssh path 5 | -------------------------------------------------------------------------------- /src/main/nspawn/app/hatcher/service/image-syncer/root/readme.md: -------------------------------------------------------------------------------- 1 | 2 | ### root 3 | 4 | variable="{{variable}}" 5 | -------------------------------------------------------------------------------- /src/main/nspawn/app/hatcher/service/image-syncer/setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | """ 4 | Service setup script. 5 | """ 6 | 7 | from nspawn.setup import * 8 | 9 | # load shared config 10 | import os 11 | import runpy 12 | this_dir = os.path.dirname(os.path.abspath(__file__)) 13 | arkon = runpy.run_path(f"{this_dir}/arkon.py") 14 | image_url = arkon['image_url'] 15 | service_config = arkon['service_config'] 16 | 17 | machine_name = service_config['machine_name'] 18 | service_home = service_config['service_home'] 19 | service_store_dir = service_config['service_store_dir'] 20 | 21 | # discover host network 22 | network_face = TOOL.select_interface() 23 | 24 | # 25 | # perform setup 26 | # 27 | 28 | IMAGE(image_url) 29 | 30 | MACHINE(machine_name) 31 | 32 | WITH( 33 | Quiet="yes", 34 | KeepUnit="yes", 35 | Register="yes", 36 | MACVLAN=network_face, 37 | ) 38 | 39 | # remote machine access 40 | WITH(BindReadOnly="/root/.ssh/authorized_keys") 41 | 42 | # setup storage 43 | SH(f""" 44 | mkdir -p "{service_home}" 45 | mkdir -p "{service_store_dir}" 46 | """) 47 | 48 | # verify storage 49 | SH(f""" 50 | ls -las "{service_home}" 51 | """) 52 | -------------------------------------------------------------------------------- /src/main/nspawn/app/hatcher/service/image-syncer/unsetup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import os 4 | 5 | this_dir = os.path.dirname(os.path.abspath(__file__)) 6 | 7 | os.system(f"{this_dir}/setup.py --action desure") 8 | -------------------------------------------------------------------------------- /src/main/nspawn/app/nsenter/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/random-python/nspawn/305268430526a17ce8e3e96e5f2f501cecdb6d92/src/main/nspawn/app/nsenter/__init__.py -------------------------------------------------------------------------------- /src/main/nspawn/app/nsenter/main.py: -------------------------------------------------------------------------------- 1 | """ 2 | NSenter application entry. 3 | """ 4 | 5 | import sys 6 | 7 | from nspawn.app.nsenter.parser import nsenter_parser 8 | from nspawn.wrapper.systemctl import SYSTEM_CTL 9 | from nspawn.wrapper.nsenter import NSENTER 10 | 11 | 12 | def main(): 13 | 14 | parser = nsenter_parser() 15 | space = parser.parse_args() 16 | 17 | machine = space.machine 18 | script = space.script 19 | 20 | service = f"{machine}.service" 21 | 22 | if not SYSTEM_CTL.has_active(service): 23 | sys.exit(f"Service is not active: {service}") 24 | 25 | if not SYSTEM_CTL.has_machine(service): 26 | sys.exit(f"Service is not a machine: {service}") 27 | 28 | NSENTER.execute_invoke(machine, script) 29 | 30 | 31 | if __name__ == "__main__": 32 | main() 33 | -------------------------------------------------------------------------------- /src/main/nspawn/app/nsenter/parser.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import textwrap 3 | from nspawn import with_merge_parser 4 | 5 | 6 | def nsenter_parser() -> argparse.ArgumentParser: 7 | 8 | parser = argparse.ArgumentParser( 9 | prog='nspawn-enter', 10 | description=textwrap.dedent( 11 | """\ 12 | Containers with systemd-nspawn :: machine interaction tool. 13 | """), 14 | formatter_class=argparse.RawDescriptionHelpFormatter, 15 | ) 16 | 17 | with_merge_parser(parser) 18 | 19 | required = parser.add_argument_group('required arguments') 20 | 21 | required.add_argument( 22 | "machine", 23 | help="Provide machine name", 24 | ) 25 | 26 | optional = parser.add_argument_group('optional arguments') 27 | 28 | optional.add_argument( 29 | "script", 30 | help="Optional shell script", 31 | nargs='?', 32 | default=None, 33 | ) 34 | 35 | return parser 36 | -------------------------------------------------------------------------------- /src/main/nspawn/base/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/random-python/nspawn/305268430526a17ce8e3e96e5f2f501cecdb6d92/src/main/nspawn/base/__init__.py -------------------------------------------------------------------------------- /src/main/nspawn/build/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Build DSL 3 | """ 4 | 5 | # invoke engine 6 | import nspawn.app.engine.invoke 7 | nspawn.app.engine.invoke.invoke_main('build.py') 8 | 9 | # publish build dsl 10 | from nspawn import tool as TOOL 11 | from nspawn.builder.syntax import * 12 | 13 | __all__ = [ 14 | 'TOOL', 15 | 'IMAGE', 16 | 'PULL', 17 | 'EXEC', 18 | 'WITH', 19 | 'FETCH', 20 | 'COPY', 21 | 'CAST', 22 | 'RUN', 23 | 'SH', 24 | 'PUSH', 25 | ] 26 | -------------------------------------------------------------------------------- /src/main/nspawn/builder/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/random-python/nspawn/305268430526a17ce8e3e96e5f2f501cecdb6d92/src/main/nspawn/builder/__init__.py -------------------------------------------------------------------------------- /src/main/nspawn/builder/syntax.py: -------------------------------------------------------------------------------- 1 | import logging 2 | from nspawn.base import token 3 | from nspawn.builder.engine import ENGINE 4 | from nspawn.support.aspect import aspect_logger 5 | 6 | level = logging.DEBUG 7 | 8 | 9 | @aspect_logger(level) 10 | def IMAGE(*args, **kwargs) -> None: 11 | ENGINE.register_intent(token.ImageIntent(*args, **kwargs)) 12 | 13 | 14 | @aspect_logger(level) 15 | def PULL(*args, **kwargs) -> None: 16 | ENGINE.register_intent(token.PullIntent(*args, **kwargs)) 17 | 18 | 19 | @aspect_logger(level) 20 | def EXEC(*args, **kwargs) -> None: 21 | ENGINE.register_intent(token.ExecIntent(*args, **kwargs)) 22 | 23 | 24 | @aspect_logger(level) 25 | def WITH(*args, **kwargs) -> None: 26 | ENGINE.register_intent(token.ProfileIntent(*args, **kwargs)) 27 | 28 | 29 | @aspect_logger(level) 30 | def FETCH(*args, **kwargs) -> None: 31 | ENGINE.register_intent(token.FetchIntent(*args, **kwargs)) 32 | 33 | 34 | @aspect_logger(level) 35 | def CAST(*args, **kwargs) -> None: 36 | ENGINE.register_intent(token.TemplateIntent(*args, **kwargs)) 37 | 38 | 39 | @aspect_logger(level) 40 | def COPY(*args, **kwargs) -> None: 41 | ENGINE.register_intent(token.CopyIntent(*args, **kwargs)) 42 | 43 | 44 | @aspect_logger(level) 45 | def RUN(*args, **kwargs) -> None: 46 | ENGINE.register_intent(token.RunIntent(*args, **kwargs)) 47 | 48 | 49 | @aspect_logger(level) 50 | def SH(*args, **kwargs) -> None: 51 | ENGINE.register_intent(token.ShellIntent(*args, **kwargs)) 52 | 53 | 54 | @aspect_logger(level) 55 | def PUSH(*args, **kwargs) -> None: 56 | ENGINE.register_intent(token.PushIntent(*args, **kwargs)) 57 | 58 | -------------------------------------------------------------------------------- /src/main/nspawn/packer/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/random-python/nspawn/305268430526a17ce8e3e96e5f2f501cecdb6d92/src/main/nspawn/packer/__init__.py -------------------------------------------------------------------------------- /src/main/nspawn/packer/tar.py: -------------------------------------------------------------------------------- 1 | # 2 | # 3 | # 4 | 5 | import logging 6 | from typing import List 7 | 8 | from nspawn import CONFIG 9 | from nspawn.packer.base import Provider 10 | from nspawn.support.files import make_temp_path 11 | from nspawn.wrapper.sudo import SUDO 12 | from nspawn.wrapper.tar import Tar 13 | from nspawn.support.header import synchronize_header 14 | 15 | logger = logging.getLogger(__name__) 16 | 17 | 18 | class ProviderTar(Provider): 19 | 20 | @classmethod 21 | def suffix_list(cls) -> List[str]: 22 | return CONFIG.get_list('wrapper/tar', 'suffix_list') 23 | 24 | def archive(self, archive_path:str, extract_path:str) -> None: 25 | logger.debug(f"pack.archive.tar: {extract_path}") 26 | SUDO.folder_assert(extract_path) 27 | archive_temp = make_temp_path("tar-archive-temp") 28 | SUDO.parent_ensure(archive_temp) 29 | tar = Tar() 30 | tar.with_archive(archive_temp) 31 | tar.with_extract(extract_path) 32 | tar.with_packer(archive_path) 33 | tar.with_make_pack() # keep last 34 | tar.execute_unit_sert() 35 | SUDO.files_move(archive_temp, archive_path) 36 | synchronize_header(extract_path, archive_path) 37 | 38 | def extract(self, archive_path:str, extract_path:str) -> None: 39 | logger.debug(f"pack.extract.tar: {extract_path}") 40 | SUDO.file_assert(archive_path) 41 | extract_temp = make_temp_path("tar-extract-temp") 42 | SUDO.folder_ensure(extract_temp) 43 | tar = Tar() 44 | tar.with_archive(archive_path) 45 | tar.with_extract(extract_temp) 46 | tar.with_packer(archive_path) 47 | tar.with_make_unpack() # keep last 48 | tar.execute_unit_sert() 49 | if SUDO.folder_check(extract_path): 50 | # user rsync to preserve active machines 51 | SUDO.files_sync_full(extract_temp, extract_path) 52 | SUDO.files_delete(extract_temp) 53 | else: 54 | # user move for efficiency 55 | SUDO.files_move(extract_temp, extract_path) 56 | synchronize_header(archive_path, extract_path) 57 | 58 | # 59 | # 60 | # 61 | -------------------------------------------------------------------------------- /src/main/nspawn/packer/zip.py: -------------------------------------------------------------------------------- 1 | # 2 | # 3 | # 4 | 5 | from nspawn.wrapper.sudo import SUDO 6 | from nspawn.support.files import make_temp_path 7 | from nspawn.packer.base import Provider 8 | from typing import List 9 | from nspawn.wrapper.zip import CmdZip 10 | from nspawn.wrapper.zip import CmdUnZip 11 | 12 | 13 | class ProviderZip(Provider): 14 | 15 | @classmethod 16 | def suffix_list(cls) -> List[str]: 17 | return ['.zip'] 18 | 19 | def archive(self, archive_path:str, extract_path:str) -> None: 20 | SUDO.folder_assert(extract_path) 21 | archive_temp = make_temp_path("zip-archive-temp") 22 | SUDO.parent_ensure(archive_temp) 23 | zipper = CmdZip() 24 | zipper.with_archive(archive_temp) 25 | zipper.with_extract(extract_path) 26 | zipper.execute_unit_sert() 27 | SUDO.files_move(archive_temp, archive_path) 28 | 29 | def extract(self, archive_path:str, extract_path:str) -> None: 30 | SUDO.file_assert(archive_path) 31 | extract_temp = make_temp_path("zip-extract-temp") 32 | SUDO.folder_ensure(extract_temp) 33 | zipper = CmdUnZip() 34 | zipper.with_archive(archive_path) 35 | zipper.with_extract(extract_temp) 36 | zipper.execute_unit_sert() 37 | SUDO.files_move(extract_temp, extract_path) 38 | 39 | # 40 | # 41 | # 42 | -------------------------------------------------------------------------------- /src/main/nspawn/setup/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Setup DSL 3 | """ 4 | 5 | # invoke engine 6 | import nspawn.app.engine.invoke 7 | nspawn.app.engine.invoke.invoke_main('setup.py') 8 | 9 | # publish setup dsl 10 | from nspawn import tool as TOOL 11 | from nspawn.setuper.syntax import * 12 | 13 | __all__ = [ 14 | 'TOOL', 15 | 'IMAGE', 16 | 'MACHINE', 17 | 'WITH', 18 | 'EXEC', 19 | 'COPY', 20 | 'CAST', 21 | 'RUN', 22 | 'SH', 23 | ] 24 | -------------------------------------------------------------------------------- /src/main/nspawn/setuper/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/random-python/nspawn/305268430526a17ce8e3e96e5f2f501cecdb6d92/src/main/nspawn/setuper/__init__.py -------------------------------------------------------------------------------- /src/main/nspawn/setuper/syntax.py: -------------------------------------------------------------------------------- 1 | import logging 2 | from nspawn.base import token 3 | from nspawn.setuper.engine import ENGINE 4 | from nspawn.support.aspect import aspect_logger 5 | 6 | level = logging.DEBUG 7 | 8 | 9 | @aspect_logger(level) 10 | def IMAGE(*args, **kwargs) -> None: 11 | ENGINE.register_intent(token.ImageIntent(*args, **kwargs)) 12 | 13 | 14 | @aspect_logger(level) 15 | def MACHINE(*args, **kwargs) -> None: 16 | ENGINE.register_intent(token.MachineIntent(*args, **kwargs)) 17 | 18 | 19 | @aspect_logger(level) 20 | def EXEC(*args, **kwargs) -> None: 21 | ENGINE.register_intent(token.ExecIntent(*args, **kwargs)) 22 | 23 | 24 | @aspect_logger(level) 25 | def WITH(*args, **kwargs) -> None: 26 | ENGINE.register_intent(token.ProfileIntent(*args, **kwargs)) 27 | 28 | @aspect_logger(level) 29 | def CAST(*args, **kwargs) -> None: 30 | ENGINE.register_intent(token.TemplateIntent(*args, **kwargs)) 31 | 32 | 33 | @aspect_logger(level) 34 | def COPY(*args, **kwargs) -> None: 35 | ENGINE.register_intent(token.CopyIntent(*args, **kwargs)) 36 | 37 | @aspect_logger(level) 38 | def RUN(*args, **kwargs) -> None: 39 | ENGINE.register_intent(token.RunIntent(*args, **kwargs)) 40 | 41 | 42 | @aspect_logger(level) 43 | def SH(*args, **kwargs) -> None: 44 | ENGINE.register_intent(token.ShellIntent(*args, **kwargs)) 45 | -------------------------------------------------------------------------------- /src/main/nspawn/support/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/random-python/nspawn/305268430526a17ce8e3e96e5f2f501cecdb6d92/src/main/nspawn/support/__init__.py -------------------------------------------------------------------------------- /src/main/nspawn/support/aspect.py: -------------------------------------------------------------------------------- 1 | 2 | import logging 3 | import functools 4 | from typing import Callable, Any 5 | 6 | logger = logging.getLogger(__name__) 7 | 8 | 9 | def render_args_text(*args, **kwargs) -> str: 10 | args_list = list(map(lambda x: f"'{x}'", args)) 11 | kwargs_list = list(map(lambda kv: f"{kv[0]}='{kv[1]}'", kwargs.items())) 12 | total_list = args_list + kwargs_list 13 | total_text = ', '.join(total_list) 14 | return total_text 15 | 16 | 17 | def aspect_logger(level=logging.INFO, with_args=True, with_trap=True) -> Callable: 18 | 19 | def decorator(function:Callable) -> Callable: 20 | 21 | func_id = function.__name__ 22 | 23 | def invoker(*args, **kwargs) -> Any: 24 | args_text = render_args_text(*args, **kwargs) if with_args else "" 25 | logger.log(level, f"{func_id}({args_text})") 26 | try: 27 | return function(*args, **kwargs) 28 | except Exception as error: 29 | if with_trap: 30 | logger.error(f"{func_id}() failure: {repr(error)}") 31 | raise error 32 | 33 | return invoker 34 | 35 | return decorator 36 | -------------------------------------------------------------------------------- /src/main/nspawn/support/config.py: -------------------------------------------------------------------------------- 1 | """ 2 | Custom configuration parser 3 | """ 4 | 5 | import io 6 | import os 7 | import configparser 8 | from typing import Any, List, Text, Mapping 9 | from jinja2 import Template 10 | 11 | 12 | def render_config_parser(config_parser: configparser.ConfigParser): 13 | return {section: dict(config_parser[section]) for section in config_parser.sections()} 14 | 15 | 16 | class EnvironmentInterpolation(configparser.ExtendedInterpolation): 17 | 18 | def before_get(self, parser, section, option, value, defaults): 19 | # environment varialbes 20 | value = os.path.expandvars(value) 21 | # config file variables 22 | value = super().before_get(parser, section, option, value, defaults) 23 | return value 24 | 25 | 26 | class RichConfigParser(configparser.ConfigParser): 27 | 28 | def __init__(self): 29 | super().__init__(self, interpolation=EnvironmentInterpolation()) 30 | 31 | def __str__(self): 32 | text = io.StringIO() 33 | for section in self.sections(): 34 | text.write(f"[{section}]\n") 35 | for (name, value) in self.items(section): 36 | text.write(f"{name}={value}\n") 37 | return text.getvalue() 38 | 39 | def get_list(self, section, option) -> List[Text]: 40 | value = self.get(section, option) 41 | return produce_list(value) 42 | 43 | def get_list_int(self, section, option) -> List[int]: 44 | result = self.get_list(section, option) 45 | result = produce_list(result) 46 | result = map(int, result) 47 | return result 48 | 49 | def get_template(self, section, option, context) -> Text: 50 | result = self.get(section, option) 51 | template = Template(result) 52 | result = template.render(context=context) 53 | return result 54 | 55 | def get_template_list(self, section, option, context) -> List[Text]: 56 | result = self.get_template(section, option, context) 57 | return produce_list(result) 58 | 59 | 60 | def produce_list(text:Text) -> List[Text]: 61 | result = text.splitlines() 62 | result = map(Text.strip, result) 63 | result = filter(None, result) 64 | return list(result) 65 | -------------------------------------------------------------------------------- /src/main/nspawn/support/files.py: -------------------------------------------------------------------------------- 1 | import os 2 | import inspect 3 | import hashlib 4 | 5 | 6 | def make_temp_path(prefix) -> str: 7 | from nspawn import CONFIG 8 | from nspawn.tool.stamp import build_stamp 9 | tempdir = CONFIG['storage']['tempdir'] 10 | temptime = build_stamp() 11 | temppath = f"{prefix}-{temptime}" 12 | return os.path.join(tempdir, temppath) 13 | 14 | 15 | def make_file_digest(path, block=2 ** 16) -> str: 16 | hasher = hashlib.sha256() 17 | with open(path, "rb") as file: 18 | while True: 19 | buffer = file.read(block) 20 | if not buffer: 21 | break 22 | hasher.update(buffer) 23 | return hasher.hexdigest() 24 | 25 | 26 | def discover_entry_dir() -> str: 27 | return os.path.dirname(discover_entry_script()) 28 | 29 | 30 | def discover_entry_script() -> str: 31 | this_stack = inspect.stack() 32 | script_file = this_stack[-1].filename 33 | script_path = os.path.abspath(script_file) 34 | return script_path 35 | 36 | 37 | def discover_project_root() -> str: 38 | """ 39 | Project repository during development 40 | """ 41 | src_main = '/src/main/' 42 | this_dir:str = os.path.dirname(os.path.abspath(__file__)) 43 | if src_main in this_dir: 44 | return this_dir.split(src_main)[0] 45 | else: 46 | return None 47 | -------------------------------------------------------------------------------- /src/main/nspawn/support/typing.py: -------------------------------------------------------------------------------- 1 | """ 2 | Common class traits 3 | """ 4 | 5 | import logging 6 | import functools 7 | from enum import Enum 8 | from typing import Type, List 9 | from nspawn.support.process import make_solo_line 10 | 11 | 12 | def type_name(instance:object) -> str: 13 | """ 14 | Object class name 15 | """ 16 | return type(instance).__name__ 17 | 18 | 19 | def enum_name_list(enum_klaz:Type[Enum]) -> List[str]: 20 | """ 21 | List of enumeration member names 22 | """ 23 | return list(enum_klaz.__members__.keys()) 24 | 25 | 26 | class WithTypeNameTrait: 27 | """ 28 | Trait: extract class name 29 | """ 30 | 31 | def type_name(self) -> str: 32 | return type_name(self) 33 | 34 | 35 | class WithTypeLoggerTrait: 36 | """ 37 | Trait: provide logger with class based name 38 | """ 39 | 40 | logger:logging.Logger 41 | 42 | def __init__(self, *args, **kwargs): 43 | self.logger = logging.getLogger(self.__class__.__name__) 44 | 45 | def react_stdout(self, line:str) -> None: 46 | line = make_solo_line(line) 47 | self.logger.info(f"[stdout] {line}") 48 | 49 | def react_stderr (self, line:str) -> None: 50 | line = make_solo_line(line) 51 | self.logger.info(f"[stderr] {line}") 52 | 53 | 54 | class cached_method: 55 | """ 56 | Decorator: class method with result cache 57 | """ 58 | 59 | CACHE = "__cached_method__" 60 | 61 | def __init__(self, function): 62 | self.function = function 63 | functools.update_wrapper(self, function) 64 | 65 | def __get__(self, instance, cls=None): 66 | if instance is None: 67 | return self.function 68 | return functools.partial(self, instance) 69 | 70 | def __call__(self, *args, **kwargs): 71 | instance = args[0] 72 | 73 | if not hasattr(instance, self.CACHE): 74 | object.__setattr__(instance, self.CACHE, dict()) 75 | 76 | cache = getattr(instance, self.CACHE) 77 | 78 | key = (self.function, frozenset(args[1:]), frozenset(kwargs.items())) 79 | 80 | if not key in cache: 81 | cache[key] = self.function(*args, **kwargs) 82 | 83 | value = cache[key] 84 | 85 | return value 86 | -------------------------------------------------------------------------------- /src/main/nspawn/template/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/random-python/nspawn/305268430526a17ce8e3e96e5f2f501cecdb6d92/src/main/nspawn/template/__init__.py -------------------------------------------------------------------------------- /src/main/nspawn/template/loader.py: -------------------------------------------------------------------------------- 1 | """ 2 | """ 3 | 4 | import os 5 | from jinja2 import Template, Environment, FileSystemLoader 6 | from nspawn.base.machine import MachineResult 7 | 8 | 9 | def this_dir(): 10 | return os.path.dirname(os.path.abspath(__file__)) 11 | 12 | 13 | def this_enviro() -> Environment: 14 | return Environment(loader=FileSystemLoader(this_dir()), trim_blocks=True) 15 | 16 | 17 | def this_template(path:str) -> Template: 18 | return this_enviro().get_template(path) 19 | 20 | 21 | def file_template(path:str) -> Template: 22 | with open(path) as file: 23 | text = file.read() 24 | return Template(text, trim_blocks=True) 25 | 26 | 27 | def machine_service(machine:MachineResult) -> str: 28 | path = machine.machine_template() 29 | if os.path.isabs(path): 30 | template = file_template(path) 31 | else: 32 | template = this_template(path) 33 | text = template.render(machine=machine) 34 | return text 35 | -------------------------------------------------------------------------------- /src/main/nspawn/template/machine-default.service: -------------------------------------------------------------------------------- 1 | 2 | # Machine Container with systemd-nspawn. 3 | 4 | [Unit] 5 | 6 | Description={{machine.machine_name()}} 7 | 8 | Documentation=https://github.com/random-python/nspawn 9 | 10 | PartOf=machines.target 11 | Before=machines.target 12 | After=network.target 13 | After=network-online.target 14 | Requires=network-online.target 15 | 16 | # Verify machine root overlay resources: 17 | {% for entry in machine.dependency_list() %} 18 | AssertPathExists={{entry}} 19 | {% endfor %} 20 | 21 | # Extra entries for [Unit] section: 22 | {% for entry in machine.unit_conf_list() %} 23 | {{entry}} 24 | {% endfor %} 25 | 26 | [Service] 27 | 28 | # Release machine root overlay mount: 29 | Environment="SYSTEMD_NSPAWN_LOCK=false" 30 | 31 | # Ensure host bind/overlay resources: 32 | {% for entry in machine.host_ensure_list() %} 33 | ExecStartPre={{entry}} 34 | {% endfor %} 35 | 36 | # Container resource create: 37 | {% for entry in machine.resource_create_list() %} 38 | ExecStartPre={{entry}} 39 | {% endfor %} 40 | 41 | # Container settings origin report: 42 | {% for entry in machine.nspawn_report_list() %} 43 | # {{entry}} 44 | {% endfor %} 45 | 46 | # Container instance launch: 47 | ExecStart={{machine.nspawn_exec_start()}} \ 48 | --machine={{machine.machine_name()}} \ 49 | --directory={{machine.machine_directory()}} \ 50 | {% for entry in machine.nspawn_option_list() %} 51 | {{entry}}{{" \\" if not loop.last}} 52 | {% endfor %} 53 | 54 | # Container instance finish: 55 | ExecStop={{machine.nspawn_exec_stop()}} 56 | 57 | # Container resource delete: 58 | {% for entry in machine.resource_delete_list() %} 59 | ExecStopPost={{entry}} 60 | {% endfor %} 61 | 62 | # Machine name for journal: 63 | SyslogIdentifier={{machine.machine_name()}} 64 | 65 | Type=notify 66 | KillMode=mixed 67 | Slice=machine.slice 68 | Delegate=yes 69 | TasksMax=16384 70 | 71 | # Timing convention: 72 | RestartSec=3s 73 | TimeoutStartSec=15s 74 | TimeoutStopSec=15s 75 | 76 | WatchdogSec=3min 77 | 78 | # Return code 133 = 128 + 5 = + SIGTRAP 79 | SuccessExitStatus=133 80 | RestartForceExitStatus=133 81 | 82 | # Extra entries for [Service] section: 83 | {% for entry in machine.service_conf_list() %} 84 | {{entry}} 85 | {% endfor %} 86 | 87 | [Install] 88 | 89 | WantedBy=machines.target 90 | 91 | # Extra entries for [Install] section: 92 | {% for entry in machine.install_conf_list() %} 93 | {{entry}} 94 | {% endfor %} 95 | -------------------------------------------------------------------------------- /src/main/nspawn/template/machine-experimental.service: -------------------------------------------------------------------------------- 1 | 2 | # machine container 3 | 4 | [Unit] 5 | Description={{machine.machine_name()}} 6 | 7 | PartOf=machines.target 8 | Before=machines.target 9 | 10 | After=network-online.target 11 | Requires=network-online.target 12 | 13 | #After=network.target systemd-networkd.service systemd-resolved.service 14 | #RequiresMountsFor=/var/lib/machines 15 | 16 | # Ensure overlay resources: 17 | {% for entry in machine.dependency_list() %} 18 | AssertPathExists={{entry}} 19 | {% endfor %} 20 | 21 | [Service] 22 | 23 | # Release overlay mount. 24 | Environment="SYSTEMD_NSPAWN_LOCK=false" 25 | 26 | # Container resource create: 27 | {% for entry in machine.resource_create_list() %} 28 | ExecStartPre={{entry}} 29 | {% endfor %} 30 | 31 | # Container instance launch: 32 | ExecStart={{machine.nspawn_exec_start()}} \ 33 | --machine={{machine.machine_name()}} \ 34 | --directory={{machine.machine_directory()}} \ 35 | {% for entry in machine.nspawn_option_list() %} 36 | {{entry}}{{" \\" if not loop.last}} 37 | {% endfor %} 38 | 39 | ExecStop={{machine.nspawn_exec_stop()}} 40 | 41 | # Container resource delete: 42 | {% for entry in machine.resource_delete_list() %} 43 | ExecStopPost={{entry}} 44 | {% endfor %} 45 | 46 | KillMode=mixed 47 | Type=notify 48 | Slice=machine.slice 49 | Delegate=yes 50 | TasksMax=16384 51 | 52 | RestartSec=1s 53 | TimeoutStartSec=3s 54 | TimeoutStopSec=3s 55 | 56 | WatchdogSec=3min 57 | 58 | SuccessExitStatus=133 59 | RestartForceExitStatus=133 60 | 61 | # Enforce a strict device policy, similar to the one nspawn configures when it 62 | # allocates its own scope unit. Make sure to keep these policies in sync if you 63 | # change them! 64 | DevicePolicy=closed 65 | DeviceAllow=/dev/net/tun rwm 66 | DeviceAllow=char-pts rw 67 | 68 | # nspawn itself needs access to /dev/loop-control and /dev/loop, to implement 69 | # the --image= option. Add these here, too. 70 | DeviceAllow=/dev/loop-control rw 71 | DeviceAllow=block-loop rw 72 | DeviceAllow=block-blkext rw 73 | 74 | # nspawn can set up LUKS encrypted loopback files, in which case it needs 75 | # access to /dev/mapper/control and the block devices /dev/mapper/*. 76 | DeviceAllow=/dev/mapper/control rw 77 | DeviceAllow=block-device-mapper rw 78 | 79 | [Install] 80 | 81 | WantedBy=machines.target 82 | -------------------------------------------------------------------------------- /src/main/nspawn/template/readme.md: -------------------------------------------------------------------------------- 1 | 2 | ### service template 3 | 4 | package-provided default service templates 5 | -------------------------------------------------------------------------------- /src/main/nspawn/tool/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Tools for DSL use 3 | """ 4 | 5 | from nspawn.tool.files import * 6 | from nspawn.tool.stamp import * 7 | from nspawn.tool.network import * 8 | -------------------------------------------------------------------------------- /src/main/nspawn/tool/files.py: -------------------------------------------------------------------------------- 1 | 2 | from nspawn import CONFIG 3 | 4 | 5 | def nspawn_tempdir() -> str: 6 | return CONFIG['storage']['tempdir'] 7 | -------------------------------------------------------------------------------- /src/main/nspawn/tool/readme.md: -------------------------------------------------------------------------------- 1 | 2 | ### build/setup support functions 3 | -------------------------------------------------------------------------------- /src/main/nspawn/tool/stamp.py: -------------------------------------------------------------------------------- 1 | import os 2 | from datetime import datetime 3 | 4 | # 5 | # environment variable: default build epoch 6 | # 7 | NSPAWN_BUILD_EPOCH = "NSPAWN_BUILD_EPOCH" 8 | 9 | 10 | def date_time_iso(instant:datetime) -> str: 11 | return "{:04d}{:02d}{:02d}T{:02d}{:02d}{:02d}".format( 12 | instant.year, instant.month, instant.day, 13 | instant.hour, instant.minute, instant.second, 14 | ) 15 | 16 | 17 | def date_path(instant:datetime) -> str: 18 | return "{:04d}/{:02d}/{:02d}".format( 19 | instant.year, instant.month, instant.day, 20 | ) 21 | 22 | 23 | def date_dash(instant:datetime) -> str: 24 | return "{:04d}-{:02d}-{:02d}".format( 25 | instant.year, instant.month, instant.day, 26 | ) 27 | 28 | 29 | def date_dots(instant:datetime) -> str: 30 | return "{:04d}.{:02d}.{:02d}".format( 31 | instant.year, instant.month, instant.day, 32 | ) 33 | 34 | 35 | def build_epoch(instant:datetime=None, day:int=1) -> datetime: 36 | if instant is None: 37 | epoch_date = os.environ.get(NSPAWN_BUILD_EPOCH) 38 | if epoch_date is None: 39 | instant = datetime.now().replace(day=day) 40 | else: 41 | instant = datetime.strptime(epoch_date, "%Y-%m-%d") 42 | return instant 43 | 44 | 45 | def build_stamp(instant:datetime=datetime.now()) -> str: 46 | return instant.strftime("%Y%m%d-%H%M%S-%f") 47 | -------------------------------------------------------------------------------- /src/main/nspawn/transport/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/random-python/nspawn/305268430526a17ce8e3e96e5f2f501cecdb6d92/src/main/nspawn/transport/__init__.py -------------------------------------------------------------------------------- /src/main/nspawn/transport/file.py: -------------------------------------------------------------------------------- 1 | 2 | import logging 3 | from typing import Mapping 4 | from urllib.parse import urlparse 5 | 6 | from nspawn.transport import base 7 | from nspawn.wrapper.sudo import SUDO 8 | from nspawn.support.header import synchronize_header, discover_header 9 | 10 | logger = logging.getLogger(__name__) 11 | 12 | 13 | class ProviderFile(base.Provider): 14 | 15 | def remote_head(self, remote_url:str) -> Mapping[str, str]: 16 | "extract path metadata stored in xattr" 17 | return discover_header(remote_url) 18 | 19 | def get(self, local_url:str, remote_url:str) -> None: 20 | logger.debug(f"file.get.fetch: {remote_url}") 21 | local, remote = self.parse(local_url, remote_url) 22 | SUDO.files_sync_full(remote.path, local.path) 23 | synchronize_header(remote.path, local.path) 24 | 25 | def put(self, local_url:str, remote_url:str) -> None: 26 | logger.debug(f"file.put.upload: {remote_url}") 27 | local, remote = self.parse(local_url, remote_url) 28 | SUDO.files_sync_full(local.path, remote.path) 29 | synchronize_header(local.path, remote.path) 30 | -------------------------------------------------------------------------------- /src/main/nspawn/wrapper/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/random-python/nspawn/305268430526a17ce8e3e96e5f2f501cecdb6d92/src/main/nspawn/wrapper/__init__.py -------------------------------------------------------------------------------- /src/main/nspawn/wrapper/curl.py: -------------------------------------------------------------------------------- 1 | """ 2 | Wrapper for curl 3 | https://curl.haxx.se/docs/manpage.html 4 | """ 5 | 6 | from nspawn.wrapper.base import Base 7 | from nspawn.wrapper.sudo import Sudo 8 | 9 | 10 | class Curl(Base): 11 | 12 | base = Sudo() 13 | 14 | def __init__(self): 15 | super().__init__('wrapper/curl') 16 | 17 | def with_url(self, url): 18 | self.with_option('url', url) 19 | return self 20 | 21 | def with_file_get(self, path): 22 | self.with_option('output', path) 23 | return self 24 | 25 | def with_file_put(self, path): 26 | self.with_option('upload-file', path) 27 | return self 28 | 29 | def with_file_head(self, path): 30 | self.with_option('output', path) 31 | self.with_option('head') 32 | return self 33 | 34 | def with_dump_header(self, path): 35 | self.with_option('dump-header', path) 36 | return self 37 | 38 | def with_header(self, text): 39 | self.with_option('header', text) 40 | return self 41 | 42 | # needs curl 7.49 43 | def with_connect_to(self, source_host, source_port, target_host, target_port): 44 | self.with_option('connect-to', f"{source_host}:{source_port}:{target_host}:{target_port}") 45 | return self 46 | 47 | def with_auth_basic(self, username, password): 48 | self.with_option('user', f"{username}:{password}") 49 | return self 50 | 51 | def with_auth_token(self, token): 52 | self.with_header(f"Authorization: Token {token}") 53 | return self 54 | 55 | def with_content_type(self, content_type): 56 | self.with_header(f"Content-Type: {content_type}") 57 | return self 58 | 59 | def with_no_proxy(self, entry_list:str): 60 | self.with_option('noproxy', entry_list) 61 | return self 62 | 63 | def with_proxy_entry(self, proxy_entry:str): 64 | self.with_option('proxy', proxy_entry) 65 | return self 66 | -------------------------------------------------------------------------------- /src/main/nspawn/wrapper/machinectl.py: -------------------------------------------------------------------------------- 1 | """ 2 | Wrapper for machinectl 3 | https://www.freedesktop.org/software/systemd/man/machinectl.html 4 | """ 5 | 6 | from nspawn.wrapper.base import Base 7 | from nspawn.wrapper.sudo import Sudo 8 | from typing import List 9 | from dataclasses import dataclass 10 | 11 | 12 | @dataclass(frozen=True) 13 | class MachineDescriptor(): 14 | MACHINE:str 15 | CLASS:str 16 | SERVICE:str 17 | OS:str 18 | VERSION:str 19 | ADDRESSES:str 20 | 21 | 22 | def descriptor_from_line(line:str) -> MachineDescriptor: 23 | term_list = line.split() 24 | term_size = len(term_list) 25 | invalid = '<>' 26 | return MachineDescriptor( 27 | MACHINE=term_list[0] if term_size > 0 else invalid, 28 | CLASS=term_list[1] if term_size > 1 else invalid, 29 | SERVICE=term_list[2] if term_size > 2 else invalid, 30 | OS=term_list[3] if term_size > 3 else invalid, 31 | VERSION=term_list[4] if term_size > 4 else invalid, 32 | ADDRESSES=term_list[5] if term_size > 5 else invalid, 33 | ) 34 | 35 | 36 | class MachineCtl(Base): 37 | 38 | base = Sudo() 39 | 40 | def __init__(self): 41 | super().__init__('wrapper/machinectl') 42 | 43 | def status(self, machine:str): 44 | command = ['status', machine] 45 | return self.execute_unit(command) 46 | 47 | def start(self, machine:str): 48 | command = ['start', machine] 49 | return self.execute_unit(command) 50 | 51 | def stop(self, machine:str): 52 | command = ['stop', machine] 53 | return self.execute_unit(command) 54 | 55 | def shell(self, machine, script=['pwd']): 56 | script = ['shell', '--quiet', machine] + script 57 | return self.execute_unit(script) 58 | 59 | def show(self, machine:str): 60 | command = ['show', machine] 61 | return self.execute_unit(command) 62 | 63 | def show_property(self, machine, name): 64 | command = ['show', '--name', name, '--value', machine] 65 | return self.execute_unit(command) 66 | 67 | def pid_get(self, machine:str) -> str: 68 | result = self.show_property(machine, 'Leader') 69 | result.assert_return() 70 | return result.stdout.strip() 71 | 72 | def list(self) -> List[MachineDescriptor]: 73 | command = ['list', '--no-legend'] 74 | result = self.execute_unit(command) 75 | line_list = result.stdout.splitlines() 76 | meta_list = list(map(descriptor_from_line, line_list)) 77 | return meta_list 78 | 79 | def has_machine(self, machine:str) -> bool: 80 | meta_list = self.list() 81 | machine_list = list(map(lambda store: store.MACHINE, meta_list)) 82 | return machine in machine_list 83 | 84 | 85 | MACHINE_CTL = MachineCtl() 86 | -------------------------------------------------------------------------------- /src/main/nspawn/wrapper/nsenter.py: -------------------------------------------------------------------------------- 1 | """ 2 | Wrapper for nsenter 3 | http://man7.org/linux/man-pages/man1/nsenter.1.html 4 | """ 5 | 6 | import os 7 | import sys 8 | 9 | from nspawn.wrapper.base import Base 10 | from nspawn.wrapper.sudo import Sudo 11 | from nspawn.wrapper.systemctl import SystemCtl 12 | 13 | 14 | class NSEnter(Base): 15 | 16 | base = Sudo() 17 | 18 | systemctl = SystemCtl() 19 | 20 | def __init__(self): 21 | super().__init__('wrapper/nsenter') 22 | 23 | def execute_invoke(self, machine:str, script:str=None) -> None: 24 | service = f"{machine}.service" 25 | init_pid = self.systemctl.machine_init_pid(service) 26 | root_dir = f"/var/lib/machines/{machine}" 27 | work_dir = f"/var/lib/machines/{machine}" 28 | TERM = os.environ.get('TERM') 29 | option_list = [ 30 | f'-t{init_pid}', 31 | f'-r{root_dir}', 32 | f'-w{work_dir}', 33 | '/usr/bin/env', '-i', 34 | f'TERM={TERM}', 35 | 'sh' 36 | ] 37 | if script: 38 | option_list.extend(['-c', script]) 39 | command = self.full_command(option_list) 40 | try: 41 | self.logger.info(f"Invoke nsenter: {command}") 42 | os.execlp(command[0], *command) 43 | except Exception as error: 44 | sys.exit(f"Invoke failure: {error}") 45 | 46 | 47 | NSENTER = NSEnter() 48 | -------------------------------------------------------------------------------- /src/main/nspawn/wrapper/systemd_nspawn.py: -------------------------------------------------------------------------------- 1 | """ 2 | Wrapper for systemd-nspawn 3 | https://www.freedesktop.org/software/systemd/man/systemd-nspawn.html 4 | """ 5 | 6 | from typing import List 7 | 8 | from nspawn.wrapper.base import Base 9 | from nspawn.wrapper.sudo import Sudo 10 | from nspawn.support.process import Command, ExecuteResult 11 | from nspawn.wrapper.systemctl import SYSTEM_CTL 12 | from nspawn.base.profile import PartitionBucket, render_option_list 13 | 14 | 15 | class SystemdNspawn(Base): 16 | 17 | base = Sudo() 18 | 19 | def __init__(self): 20 | super().__init__('wrapper/systemd_nspawn') 21 | version = SYSTEM_CTL.property_VersionNumber() 22 | bucket = PartitionBucket.parse_option_list(version, self.option_list) 23 | if bucket.has_supported(): 24 | supported_list = render_option_list(bucket.supported_list()) 25 | self.with_params(self.binary, supported_list) 26 | if bucket.has_unsupported(): 27 | unsupported_list = render_option_list(bucket.unsupported_list()) 28 | self.logger.warning(f"Removing unsuppoted options: {unsupported_list}") 29 | 30 | def machine_command(self, machine:str, command:Command) -> Command: 31 | return ['--machine', machine] + command 32 | 33 | def execute_unit(self, machine:str, command:Command) -> ExecuteResult: 34 | return super().execute_unit( 35 | command=self.machine_command(machine, command), 36 | ) 37 | 38 | def execute_flow(self, machine:str, command:Command) -> ExecuteResult: 39 | return super().execute_flow( 40 | command=self.machine_command(machine, command), 41 | ) 42 | 43 | 44 | SYSTEMD_NSPAWN = SystemdNspawn() 45 | -------------------------------------------------------------------------------- /src/main/nspawn/wrapper/tar.py: -------------------------------------------------------------------------------- 1 | # 2 | # 3 | # 4 | 5 | from nspawn.wrapper.base import Base 6 | from nspawn.wrapper.sudo import Sudo 7 | 8 | 9 | def compressor_for(file:str) -> str: 10 | if file.endswith('.tar.gz'): 11 | return 'pigz' 12 | if file.endswith('.tar.xz'): 13 | return 'xz' 14 | if file.endswith('.tar.zst'): 15 | return 'zstd' 16 | else: 17 | raise RuntimeError(f"Missing compressor for {file}") 18 | 19 | 20 | class Tar(Base): 21 | 22 | base = Sudo() 23 | 24 | def __init__(self): 25 | super().__init__('wrapper/tar') 26 | 27 | def with_archive(self, path): 28 | self.with_option('file', path) 29 | return self 30 | 31 | def with_extract(self, path): 32 | self.with_option('directory', path) 33 | return self 34 | 35 | def with_compress(self, path): 36 | self.with_option('use-compress-program', path) 37 | return self 38 | 39 | def with_make_pack(self): 40 | self.with_option('create', '.') 41 | return self 42 | 43 | def with_make_unpack(self): 44 | self.with_option('extract') 45 | return self 46 | 47 | def with_packer(self, file): 48 | self.with_compress(compressor_for(file)) 49 | return self 50 | 51 | # 52 | # 53 | # 54 | -------------------------------------------------------------------------------- /src/main/nspawn/wrapper/zip.py: -------------------------------------------------------------------------------- 1 | # 2 | # 3 | # 4 | 5 | from nspawn.wrapper.base import Base 6 | from nspawn.wrapper.sudo import Sudo 7 | 8 | 9 | class CmdZip(Base): 10 | 11 | base = Sudo() 12 | 13 | def __init__(self): 14 | super().__init__('wrapper/zip') 15 | 16 | def with_archive(self, path): 17 | self.with_option('xxx', path) 18 | return self 19 | 20 | def with_extract(self, path): 21 | self.with_option('xxx', path) 22 | return self 23 | 24 | def with_make_pack(self): 25 | return self 26 | 27 | def with_make_unpack(self): 28 | raise RuntimeError("wrong operation") 29 | 30 | 31 | class CmdUnZip(Base): 32 | 33 | base = Sudo() 34 | 35 | def __init__(self): 36 | super().__init__('wrapper/unzip') 37 | 38 | def with_archive(self, path:str): 39 | assert path.endswith(".zip") 40 | self.option_list.extend([path]) 41 | return self 42 | 43 | def with_extract(self, path:str): 44 | self.option_list.extend(['-d', path]) 45 | return self 46 | 47 | def with_make_pack(self): 48 | raise RuntimeError("wrong operation") 49 | 50 | def with_make_unpack(self): 51 | return self 52 | 53 | 54 | # 55 | # 56 | # 57 | -------------------------------------------------------------------------------- /src/main/readme.md: -------------------------------------------------------------------------------- 1 | 2 | ### project main sources 3 | -------------------------------------------------------------------------------- /src/readme.md: -------------------------------------------------------------------------------- 1 | 2 | ### project sources 3 | -------------------------------------------------------------------------------- /src/test/nspawn_test/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/random-python/nspawn/305268430526a17ce8e3e96e5f2f501cecdb6d92/src/test/nspawn_test/__init__.py -------------------------------------------------------------------------------- /src/test/nspawn_test/app/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/random-python/nspawn/305268430526a17ce8e3e96e5f2f501cecdb6d92/src/test/nspawn_test/app/__init__.py -------------------------------------------------------------------------------- /src/test/nspawn_test/app/engine/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/random-python/nspawn/305268430526a17ce8e3e96e5f2f501cecdb6d92/src/test/nspawn_test/app/engine/__init__.py -------------------------------------------------------------------------------- /src/test/nspawn_test/app/engine/main_verify.py: -------------------------------------------------------------------------------- 1 | 2 | import nspawn.app.engine 3 | 4 | 5 | def main(): 6 | print("hello-kitty") 7 | 8 | 9 | main() 10 | -------------------------------------------------------------------------------- /src/test/nspawn_test/app/engine/parser_test.py: -------------------------------------------------------------------------------- 1 | 2 | from nspawn.app.engine.parser import * 3 | 4 | 5 | def test_setup_action(): 6 | print() 7 | print(enum_name_list(SetupAction)) 8 | 9 | 10 | def test_build_parser(): 11 | print() 12 | parser = build_parser() 13 | help = parser.format_help() 14 | print(help) 15 | 16 | 17 | def test_setup_parser(): 18 | print() 19 | parser = setup_parser() 20 | help = parser.format_help() 21 | print(help) 22 | -------------------------------------------------------------------------------- /src/test/nspawn_test/app/hatcher/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/random-python/nspawn/305268430526a17ce8e3e96e5f2f501cecdb6d92/src/test/nspawn_test/app/hatcher/__init__.py -------------------------------------------------------------------------------- /src/test/nspawn_test/app/hatcher/main_test.py: -------------------------------------------------------------------------------- 1 | 2 | from nspawn.app.hatcher.main import * 3 | 4 | 5 | def test_service_image(): 6 | print() 7 | image_path = service_image('image-server') 8 | print(image_path) 9 | 10 | 11 | def test_perform_list(): 12 | print() 13 | perform_list() 14 | -------------------------------------------------------------------------------- /src/test/nspawn_test/app/hatcher/parser_test.py: -------------------------------------------------------------------------------- 1 | import re 2 | import pytest 3 | 4 | from nspawn import CONFIG 5 | from nspawn.app.hatcher.parser import * 6 | 7 | 8 | def test_parser_help(): 9 | print() 10 | parser = hatcher_parser() 11 | help_text = parser.format_help() 12 | print(help_text) 13 | 14 | 15 | def test_empty_args(): 16 | print() 17 | parser = hatcher_parser() 18 | args = [] 19 | with pytest.raises(BaseException) as trap_info: 20 | parser.parse_args(args) 21 | assert trap_info.type == SystemExit 22 | 23 | 24 | def test_command_list(): 25 | print() 26 | parser = hatcher_parser() 27 | args = ['list'] 28 | space = parser.parse_args(args) 29 | print(space) 30 | assert space.command == 'list' 31 | assert not hasattr(space, 'service') 32 | 33 | 34 | def test_command_ensure(): 35 | print() 36 | parser = hatcher_parser() 37 | args = ['ensure', 'machine'] 38 | space = parser.parse_args(args) 39 | print(space) 40 | assert space.command == 'ensure' 41 | assert space.service == 'machine' 42 | 43 | 44 | def test_command_desure(): 45 | print() 46 | parser = hatcher_parser() 47 | args = ['desure', 'machine'] 48 | space = parser.parse_args(args) 49 | print(space) 50 | assert space.command == 'desure' 51 | assert space.service == 'machine' 52 | 53 | 54 | def test_command_update(): 55 | print() 56 | parser = hatcher_parser() 57 | args = ['update', 'machine'] 58 | space = parser.parse_args(args) 59 | print(space) 60 | assert space.command == 'update' 61 | assert space.service == 'machine' 62 | 63 | 64 | # def test_sync_regex(): 65 | # print() 66 | # service_sync_regex = CONFIG['hatcher/image-syncer']['service_sync_regex'] 67 | # matcher = re.compile(service_sync_regex) 68 | # assert matcher.match("index.html") 69 | # assert not matcher.match("index.html.test") 70 | # assert matcher.match("base/arch/package.tar.gz") 71 | # assert not matcher.match("base/arch/package.tar.gz.test") 72 | -------------------------------------------------------------------------------- /src/test/nspawn_test/app/nsenter/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/random-python/nspawn/305268430526a17ce8e3e96e5f2f501cecdb6d92/src/test/nspawn_test/app/nsenter/__init__.py -------------------------------------------------------------------------------- /src/test/nspawn_test/app/nsenter/parser_test.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from nspawn.app.nsenter.parser import * 4 | 5 | 6 | def test_parser_help(): 7 | print() 8 | parser = nsenter_parser() 9 | help_text = parser.format_help() 10 | print(help_text) 11 | 12 | 13 | def test_command(): 14 | print() 15 | parser = nsenter_parser() 16 | args = ['machine', 'ls -las "a b c"'] 17 | space = parser.parse_args(args) 18 | print(space) 19 | assert space.machine == 'machine' 20 | assert space.script == 'ls -las "a b c"' 21 | -------------------------------------------------------------------------------- /src/test/nspawn_test/base/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/random-python/nspawn/305268430526a17ce8e3e96e5f2f501cecdb6d92/src/test/nspawn_test/base/__init__.py -------------------------------------------------------------------------------- /src/test/nspawn_test/base/machine_test.py: -------------------------------------------------------------------------------- 1 | import platform 2 | from nspawn.wrapper.sudo import SUDO 3 | from nspawn.tool import stamp 4 | from nspawn.base.machine import * 5 | 6 | build_stamp = stamp.build_stamp() 7 | 8 | epoch = "3.10" 9 | release = f"{epoch}.3" 10 | hardware = platform.machine() 11 | image_url = f"file://localhost/tmp/nspawn/repo/alpine/base/default-{release}-{hardware}.tar.gz" 12 | booter_url = f"http://dl-cdn.alpinelinux.org/alpine/v{epoch}/releases/{hardware}/alpine-minirootfs-{release}-{hardware}.tar.gz" 13 | 14 | 15 | def test_machine_result(): 16 | print() 17 | 18 | machine_name = f"a-nspawn-tester-{build_stamp}" 19 | machine_template = CONFIG['machine']['template'] 20 | machine_meta = MachineMeta(machine_name, machine_template) 21 | machine_result = machine_result_from_url(booter_url, machine_meta) 22 | print(machine_result) 23 | 24 | machine_directory = machine_result.machine_directory() 25 | 26 | resource_create_list = machine_result.resource_create_list() 27 | resource_delete_list = machine_result.resource_delete_list() 28 | 29 | cmd_report = f"ls -las {machine_directory}".split() 30 | cmd_ensure = f"mkdir -p {machine_directory}".split() 31 | cmd_desure = f"rm -rf {machine_directory}".split() 32 | 33 | systemd_nspawn = system_command('systemd-nspawn') 34 | 35 | machine_command = [ 36 | systemd_nspawn, 37 | f'--quiet', 38 | f'--machine={machine_name}', 39 | f'--directory={machine_directory}', 40 | f'/usr/bin/env', 41 | ] 42 | 43 | try: 44 | print(f"invoke: {cmd_ensure}") 45 | SUDO.execute_unit_sert(cmd_ensure) 46 | result = SUDO.execute_unit_sert(cmd_report) 47 | print(result.stdout) 48 | for command in resource_create_list: 49 | print(f"invoke: {command}") 50 | SUDO.execute_unit_sert(command) 51 | print(f"invoke: {cmd_report}") 52 | result = SUDO.execute_unit_sert(cmd_report) 53 | print(result.stdout) 54 | print(f"invoke: {machine_command}") 55 | result = SUDO.execute_unit_sert(machine_command) 56 | print(result.stdout) 57 | finally: 58 | for command in resource_delete_list: 59 | print(f"invoke: {command}") 60 | print(command) 61 | SUDO.execute_unit_sert(command) 62 | SUDO.execute_unit_sert(cmd_desure) 63 | -------------------------------------------------------------------------------- /src/test/nspawn_test/base/overlay_test.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/random-python/nspawn/305268430526a17ce8e3e96e5f2f501cecdb6d92/src/test/nspawn_test/base/overlay_test.py -------------------------------------------------------------------------------- /src/test/nspawn_test/builder/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/random-python/nspawn/305268430526a17ce8e3e96e5f2f501cecdb6d92/src/test/nspawn_test/builder/__init__.py -------------------------------------------------------------------------------- /src/test/nspawn_test/builder/engine_test.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/random-python/nspawn/305268430526a17ce8e3e96e5f2f501cecdb6d92/src/test/nspawn_test/builder/engine_test.py -------------------------------------------------------------------------------- /src/test/nspawn_test/builder/syntax_test.py: -------------------------------------------------------------------------------- 1 | 2 | from nspawn.builder.syntax import * 3 | 4 | def test_syntax(): 5 | print() 6 | IMAGE() 7 | PULL() 8 | EXEC() 9 | WITH() 10 | FETCH() 11 | CAST() 12 | COPY() 13 | RUN() 14 | SH() 15 | PUSH() 16 | -------------------------------------------------------------------------------- /src/test/nspawn_test/network.py: -------------------------------------------------------------------------------- 1 | import socket 2 | from contextlib import closing 3 | 4 | 5 | def localhost_free_port() -> int: 6 | with closing(socket.socket(socket.AF_INET, socket.SOCK_STREAM)) as ser: 7 | ser.bind(('localhost', 0)) 8 | ser.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) 9 | return ser.getsockname()[1] 10 | -------------------------------------------------------------------------------- /src/test/nspawn_test/packer/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/random-python/nspawn/305268430526a17ce8e3e96e5f2f501cecdb6d92/src/test/nspawn_test/packer/__init__.py -------------------------------------------------------------------------------- /src/test/nspawn_test/packer/base_test.py: -------------------------------------------------------------------------------- 1 | 2 | from nspawn.packer.base import * 3 | 4 | 5 | def test_packer_provider(): 6 | archive_list = [ 7 | 'http://host/path/archux_url.tar.gz', 8 | ] 9 | for archux_url in archive_list: 10 | provider = packer_provider(archux_url) 11 | print(provider) 12 | -------------------------------------------------------------------------------- /src/test/nspawn_test/server.py: -------------------------------------------------------------------------------- 1 | import os 2 | import threading 3 | from http.server import HTTPServer, SimpleHTTPRequestHandler 4 | 5 | TESTER_SERVER_DIR = "/tmp/nspawn.tester-server" 6 | 7 | 8 | class HttpHandler(SimpleHTTPRequestHandler): 9 | 10 | def __init__(self, *args, **kwargs): 11 | super().__init__(*args, directory=TESTER_SERVER_DIR, **kwargs) 12 | 13 | def do_PUT(self): 14 | path = self.translate_path(self.path) 15 | if path.endswith('/'): 16 | self.send_response(405, "Method Not Allowed") 17 | else: 18 | base_path = os.path.join(TESTER_SERVER_DIR, path) 19 | os.makedirs(os.path.dirname(base_path), exist_ok=True) 20 | length = int(self.headers['Content-Length']) 21 | with open(base_path, 'wb') as file: 22 | file.write(self.rfile.read(length)) 23 | self.send_response(201, "Created") 24 | self.end_headers() 25 | 26 | 27 | class HttpServer(threading.Thread): 28 | server = None 29 | 30 | def __init__(self, addr="127.0.0.1", port=8888): 31 | super().__init__() 32 | addr_port = (addr, port) 33 | self.server = HTTPServer(addr_port, HttpHandler) 34 | self.setDaemon(True) 35 | 36 | def execute_unit(self): 37 | self.server.serve_forever() 38 | 39 | def stop(self): 40 | self.server.shutdown() 41 | self.join() 42 | -------------------------------------------------------------------------------- /src/test/nspawn_test/setuper/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/random-python/nspawn/305268430526a17ce8e3e96e5f2f501cecdb6d92/src/test/nspawn_test/setuper/__init__.py -------------------------------------------------------------------------------- /src/test/nspawn_test/setuper/engine_test.py: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/test/nspawn_test/setuper/syntax_test.py: -------------------------------------------------------------------------------- 1 | 2 | from nspawn.setuper.syntax import * 3 | 4 | 5 | def test_syntax(): 6 | print() 7 | IMAGE() 8 | MACHINE() 9 | EXEC() 10 | COPY() 11 | CAST() 12 | WITH() 13 | -------------------------------------------------------------------------------- /src/test/nspawn_test/support/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/random-python/nspawn/305268430526a17ce8e3e96e5f2f501cecdb6d92/src/test/nspawn_test/support/__init__.py -------------------------------------------------------------------------------- /src/test/nspawn_test/support/aspect_test.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from nspawn.support.aspect import * 4 | 5 | 6 | def test_aspect_logger(): 7 | print() 8 | 9 | @aspect_logger() 10 | def NORMAL(*args, **kwargs) -> None: 11 | pass 12 | 13 | NORMAL('a', 'b', k1='v1', k2='v2') 14 | 15 | @aspect_logger() 16 | def EXPLODE(*args, **kwargs) -> None: 17 | raise RuntimeError("hello-kitty") 18 | 19 | with pytest.raises(Exception) as trap_info: 20 | EXPLODE('a', 'b', k1='v1', k2='v2') 21 | print(trap_info) 22 | -------------------------------------------------------------------------------- /src/test/nspawn_test/support/config_test.py: -------------------------------------------------------------------------------- 1 | from nspawn import CONFIG 2 | from nspawn.support.config import * 3 | 4 | 5 | def test_config(): 6 | print() 7 | print(CONFIG) 8 | 9 | 10 | def test_template(): 11 | print() 12 | section = 'wrapper/nsenter' 13 | option = 'option_list' 14 | config_list = CONFIG.get_list(section, option) 15 | print(config_list) 16 | context = dict( 17 | machine_name="machine", 18 | command_list=['/bin/ls', '-las'], 19 | ) 20 | command_list = CONFIG.get_template_list(section, option, context) 21 | print(command_list) 22 | 23 | def test_render_config(): 24 | print() 25 | print(render_config_parser(CONFIG)) 26 | -------------------------------------------------------------------------------- /src/test/nspawn_test/support/files_test.py: -------------------------------------------------------------------------------- 1 | from nspawn.wrapper.sudo import Sudo 2 | 3 | from nspawn.support.files import * 4 | 5 | def test_stack(): 6 | print() 7 | stack = inspect.stack() 8 | for info in stack: 9 | print(info) 10 | 11 | def test_make_temp_file(): 12 | print() 13 | prefix = "tester" 14 | temp_file = make_temp_path(prefix) 15 | print(temp_file) 16 | 17 | 18 | def test_make_file_digest(): 19 | print() 20 | sudo = Sudo() 21 | text = "" 22 | path = "/tmp/test_make_file_digest" 23 | sudo.file_save(path, text) 24 | digest = make_file_digest(path) 25 | sudo.files_delete(path) 26 | assert digest == "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" 27 | 28 | 29 | def test_project_root(): 30 | print() 31 | project_root = discover_project_root() 32 | print(project_root) 33 | 34 | def test_discover_entry_script(): 35 | print() 36 | entry_path = discover_entry_script() 37 | print(entry_path) 38 | 39 | def test_discover_entry_dir(): 40 | print() 41 | entry_path = discover_entry_dir() 42 | print(entry_path) 43 | -------------------------------------------------------------------------------- /src/test/nspawn_test/support/header_test.py: -------------------------------------------------------------------------------- 1 | 2 | from nspawn.support.header import * 3 | 4 | 5 | def test_header(): 6 | print() 7 | head_dict = { 8 | 'etag':'some-hash', 9 | 'last-modified':'some-time', 10 | 'content-length':'some-size', 11 | 'nspawn-digest':'some-text', 12 | } 13 | assert head_dict[Header.etag] == 'some-hash' 14 | assert head_dict[Header.last_modified] == 'some-time' 15 | assert head_dict[Header.content_length] == 'some-size' 16 | assert head_dict[Header.nspawn_digest] == 'some-text' 17 | 18 | 19 | def test_compare_head(): 20 | print() 21 | assert compare_header({ 22 | }, { 23 | }) == HeadComp.undetermined 24 | assert compare_header({ 25 | 'etag':'123' 26 | }, { 27 | 'etag':'"123"' 28 | }) == HeadComp.same 29 | assert compare_header({ 30 | 'last-modified':'some-time', 31 | 'content-length':'some-size', 32 | }, { 33 | 'last-modified':'some-time', 34 | 'content-length':'some-size', 35 | }) == HeadComp.same 36 | assert compare_header({ 37 | 'last-modified':'some-time', 38 | 'content-length':'some-size-1', 39 | }, { 40 | 'last-modified':'some-time', 41 | 'content-length':'some-size-2', 42 | }) == HeadComp.different 43 | assert compare_header({ 44 | 'last-modified':'some-time', 45 | }, { 46 | 'content-length':'some-size', 47 | }) == HeadComp.undetermined 48 | 49 | -------------------------------------------------------------------------------- /src/test/nspawn_test/support/process_test.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from nspawn.support.process import * 4 | 5 | 6 | def test_make_solo_line(): 7 | print() 8 | text = """ 9 | a 10 | b 11 | c 12 | """ 13 | line = make_solo_line(text) 14 | print(line) 15 | 16 | 17 | def test_run_result(): 18 | print() 19 | command = ['/usr/bin/env', 'sh', '-c', 'echo hello-kitty'] 20 | result = ExecuteResult(command=command) 21 | print(result) 22 | result = ExecuteResult(command=command, stdout='', stderr='') 23 | print(result) 24 | result = ExecuteResult(rc=1, command=command, stdout='a b') 25 | print(result) 26 | result = ExecuteResult(rc=2, command=command, stderr='1 2') 27 | print(result) 28 | result = ExecuteResult(rc=3, command=command, 29 | stdout=""" 30 | a 31 | b 32 | """, 33 | stderr=""" 34 | 1 35 | 2 36 | """, 37 | error=RuntimeError('hello-kitty') 38 | ) 39 | print(result) 40 | with pytest.raises(Exception) as trap_info: 41 | result.assert_return() 42 | print(trap_info) 43 | 44 | 45 | def test_execute_shell(): 46 | print() 47 | script = "read var; echo var=$var; for step in {1..3}; do echo step=$step; sleep 0.1; done" 48 | exectute_shell(script, stdin="hello") 49 | 50 | 51 | def test_execute_program(): 52 | print() 53 | command = ['ls', '-las'] 54 | exectute_program(command) 55 | 56 | 57 | def test_command(): 58 | print() 59 | command = Command(['ls', '-las', 'hello kitty "with kittens"', "a a 'b b'"]) 60 | print(command) 61 | -------------------------------------------------------------------------------- /src/test/nspawn_test/support/proxy_test.py: -------------------------------------------------------------------------------- 1 | 2 | from nspawn.support.proxy import * 3 | 4 | 5 | def test_proxy_config(): 6 | print() 7 | 8 | entry_map = dict( 9 | no_proxy="www.google.com", 10 | http_proxy="http://proxy:3128", 11 | ) 12 | 13 | config = ProxyConfig(entry_map) 14 | 15 | print(config) 16 | print(config.no_proxy()) 17 | print(config.proxy_for('http')) 18 | 19 | 20 | def test_proxy_discovery(): 21 | print() 22 | 23 | config = discover_proxy_config() 24 | config = discover_proxy_config() 25 | config = discover_proxy_config() 26 | 27 | print(config) 28 | -------------------------------------------------------------------------------- /src/test/nspawn_test/support/typing_test.py: -------------------------------------------------------------------------------- 1 | from nspawn.support.typing import * 2 | from dataclasses import dataclass 3 | 4 | 5 | @dataclass(frozen=True) 6 | class Basic(): 7 | value:int 8 | 9 | @cached_method 10 | def making_value(self, arg1:str='arg1', arg2:str='arg2') -> int: 11 | print(f"making_value : {self}") 12 | return self.value 13 | 14 | 15 | def test_cached_method(): 16 | print() 17 | 18 | basic = Basic(101) 19 | 20 | print(basic.making_value()) 21 | print(basic.making_value()) 22 | print(basic.making_value()) 23 | -------------------------------------------------------------------------------- /src/test/nspawn_test/template/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/random-python/nspawn/305268430526a17ce8e3e96e5f2f501cecdb6d92/src/test/nspawn_test/template/__init__.py -------------------------------------------------------------------------------- /src/test/nspawn_test/template/loader_test.py: -------------------------------------------------------------------------------- 1 | 2 | from nspawn.template.loader import * 3 | 4 | from nspawn.base.machine import MachineMeta, machine_result_from_url 5 | from nspawn.base.machine import MachineStore 6 | from nspawn.base.machine import MachineResult 7 | from nspawn import CONFIG 8 | import platform 9 | from nspawn.tool import stamp 10 | 11 | build_stamp = stamp.build_stamp() 12 | 13 | epoch = "3.10" 14 | release = f"{epoch}.3" 15 | hardware = platform.machine() 16 | image_url = f"file://localhost/tmp/nspawn/repo/alpine/base/default-{release}-{hardware}.tar.gz" 17 | booter_url = f"http://dl-cdn.alpinelinux.org/alpine/v{epoch}/releases/{hardware}/alpine-minirootfs-{release}-{hardware}.tar.gz" 18 | 19 | 20 | def test_this_template(): 21 | print() 22 | file_name = CONFIG['machine']['template'] 23 | machine_template = f"{file_name}" 24 | template = this_template(machine_template) 25 | print(template) 26 | 27 | 28 | def test_file_template(): 29 | print() 30 | file_name = CONFIG['machine']['template'] 31 | machine_template = f"{this_dir()}/{file_name}" 32 | template = file_template(machine_template) 33 | print(template) 34 | 35 | 36 | def test_machine_service(): 37 | print() 38 | 39 | machine_name = f"tester-{build_stamp}" 40 | machine_template = CONFIG['machine']['template'] 41 | machine_meta = MachineMeta(machine_name, machine_template) 42 | machine_result = machine_result_from_url(booter_url, machine_meta) 43 | machine_result.profile_bucket.with_command(['/bin/init']) 44 | print(machine_result) 45 | 46 | service_text = machine_service(machine_result) 47 | print(service_text) 48 | -------------------------------------------------------------------------------- /src/test/nspawn_test/tool/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/random-python/nspawn/305268430526a17ce8e3e96e5f2f501cecdb6d92/src/test/nspawn_test/tool/__init__.py -------------------------------------------------------------------------------- /src/test/nspawn_test/tool/network_test.py: -------------------------------------------------------------------------------- 1 | 2 | from nspawn.tool.network import * 3 | 4 | 5 | def test_interface_list(): 6 | print() 7 | face_list = interface_list() 8 | print(f"face_list={face_list}") 9 | 10 | 11 | def test_has_internet(): 12 | print() 13 | has_net = has_internet() 14 | print(f"has_net={has_net}") 15 | 16 | 17 | def test_public_address(): 18 | print() 19 | address = public_address() 20 | print(f"public_address={address}") 21 | 22 | 23 | def test_amzon_public_address(): 24 | print() 25 | address = amazon_public_address() 26 | print(f"public_address={address}") 27 | -------------------------------------------------------------------------------- /src/test/nspawn_test/tool/stamp_test.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from nspawn.tool.stamp import * 4 | 5 | # from datetime import datetime 6 | 7 | source = "2019-05-06T19:18:22" 8 | instant = datetime.fromisoformat(source) 9 | 10 | 11 | def test_date_time_iso(): 12 | target = date_time_iso(instant) 13 | print() 14 | print(target) 15 | assert target == "20190506T191822" 16 | 17 | 18 | def test_date_path(): 19 | target = date_path(instant) 20 | print() 21 | print(target) 22 | assert target == "2019/05/06" 23 | 24 | 25 | def test_date_dots(): 26 | target = date_dots(instant) 27 | print() 28 | print(target) 29 | assert target == "2019.05.06" 30 | 31 | 32 | def test_date_dash(): 33 | target = date_dash(instant) 34 | print() 35 | print(target) 36 | assert target == "2019-05-06" 37 | 38 | 39 | def test_build_stamp(): 40 | stamp_value = build_stamp(instant) 41 | print() 42 | print(stamp_value) 43 | assert stamp_value == "20190506-191822-000000" 44 | 45 | 46 | def test_build_epoch_default(): 47 | stamp_value = build_epoch() 48 | build_value = datetime.now().replace(day=1) 49 | print() 50 | print(stamp_value) 51 | assert stamp_value.date() == build_value.date() 52 | 53 | 54 | def test_build_epoch_environ(): 55 | os.environ[NSPAWN_BUILD_EPOCH] = "2020-12-01" 56 | stamp_value = build_epoch() 57 | print() 58 | print(stamp_value) 59 | assert stamp_value == datetime(2020, 12, 1, 0, 0, 0) 60 | -------------------------------------------------------------------------------- /src/test/nspawn_test/transport/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/random-python/nspawn/305268430526a17ce8e3e96e5f2f501cecdb6d92/src/test/nspawn_test/transport/__init__.py -------------------------------------------------------------------------------- /src/test/nspawn_test/transport/base_test.py: -------------------------------------------------------------------------------- 1 | 2 | import pytest 3 | import urllib 4 | 5 | from nspawn.transport.base import * 6 | 7 | 8 | def test_url_file(): 9 | print() 10 | url = "file://localhost/path/folder?t1=123,t2=234#a=b" 11 | result = urllib.parse.urlparse(url) 12 | print(result) 13 | assert result.hostname == 'localhost' 14 | assert result.port == None 15 | 16 | 17 | def test_url_http(): 18 | print() 19 | url = "http://image:8080/path/folder?t1=123,t2=234#a=b" 20 | result = urllib.parse.urlparse(url) 21 | print(result) 22 | assert result.hostname == 'image' 23 | assert result.port == 8080 24 | 25 | 26 | def test_http_head(): 27 | print() 28 | remote_url = "https://raw.githubusercontent.com/random-python/nspawn/master/readme.md" 29 | provider = transport_provider(remote_url) 30 | head_dict = provider.remote_head(remote_url) 31 | for key, value in head_dict.items(): 32 | print(f"{key}={value}") 33 | 34 | def test_http_fail(): 35 | print() 36 | remote_url = "https://raw.githubusercontent.com/random-python/nspawn/master/readme-invalid.md" 37 | provider = transport_provider(remote_url) 38 | with pytest.raises(AssertionError): 39 | provider.remote_head(remote_url) 40 | -------------------------------------------------------------------------------- /src/test/nspawn_test/transport/curl-head-1.txt: -------------------------------------------------------------------------------- 1 | HTTP/1.1 200 OK 2 | Server: nginx 3 | Content-Type: application/octet-stream 4 | Last-Modified: Tue, 20 Aug 2019 10:30:44 GMT 5 | ETag: "5d5bcbd4-295ebd" 6 | Via: 1.1 varnish 7 | Content-Length: 2711229 8 | Accept-Ranges: bytes 9 | Date: Thu, 05 Sep 2019 14:01:15 GMT 10 | Via: 1.1 varnish 11 | X-Served-By: cache-jfk8129-JFK, cache-mdw17323-MDW 12 | X-Cache: HIT, HIT 13 | X-Cache-Hits: 1, 1 14 | X-Timer: S1567692076.516996,VS0,VE1 15 | Age: 161115 16 | Warning: 113 proxy (squid/3.5.27) This cache hit is still fresh and more than 1 day old 17 | X-Cache: HIT from proxy 18 | X-Cache-Lookup: HIT from proxy:3128 19 | Connection: keep-alive 20 | 21 | -------------------------------------------------------------------------------- /src/test/nspawn_test/transport/curl-head-2.txt: -------------------------------------------------------------------------------- 1 | HTTP/1.1 200 Connection established 2 | 3 | HTTP/1.1 200 OK 4 | Server: nginx/1.16.1 5 | Date: Thu, 05 Sep 2019 18:42:20 GMT 6 | Content-Type: application/gzip 7 | Content-Length: 154047390 8 | Last-Modified: Sun, 01 Sep 2019 05:14:58 GMT 9 | ETag: "5d6b53d2-92e939e" 10 | Strict-Transport-Security: max-age=31536000; includeSubdomains; preload 11 | Accept-Ranges: bytes 12 | Age: 2054 13 | X-Cache: HIT from proxy 14 | X-Cache-Lookup: HIT from proxy:3128 15 | Connection: keep-alive 16 | 17 | -------------------------------------------------------------------------------- /src/test/nspawn_test/transport/http_test.py: -------------------------------------------------------------------------------- 1 | 2 | import os 3 | 4 | from nspawn.transport.http import * 5 | 6 | this_dir = os.path.dirname(__file__) 7 | 8 | def test_with_auth_url(): 9 | print() 10 | curl = Curl() 11 | remote_url = "http://image/path/package.tar.gz" 12 | with_auth_url(curl, remote_url) 13 | print(curl) 14 | assert 'http://nspawn-image-server/path/package.tar.gz' in curl.full_command() 15 | assert 'default:default' in curl.full_command() 16 | 17 | 18 | def test_parse_header_file_1(): 19 | print() 20 | header_path = f"{this_dir}/curl-head-1.txt" 21 | header_dict = parse_header_file(header_path) 22 | print(header_dict) 23 | assert header_dict['etag'] == '"5d5bcbd4-295ebd"' 24 | assert header_dict['content-length'] == '2711229' 25 | assert header_dict['last-modified'] == 'Tue, 20 Aug 2019 10:30:44 GMT' 26 | 27 | 28 | def test_parse_header_file_2(): 29 | print() 30 | header_path = f"{this_dir}/curl-head-2.txt" 31 | header_dict = parse_header_file(header_path) 32 | print(header_dict) 33 | assert header_dict['etag'] == '"5d6b53d2-92e939e"' 34 | assert header_dict['content-length'] == '154047390' 35 | assert header_dict['last-modified'] == 'Sun, 01 Sep 2019 05:14:58 GMT' 36 | -------------------------------------------------------------------------------- /src/test/nspawn_test/wrapper/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/random-python/nspawn/305268430526a17ce8e3e96e5f2f501cecdb6d92/src/test/nspawn_test/wrapper/__init__.py -------------------------------------------------------------------------------- /src/test/nspawn_test/wrapper/base_test.py: -------------------------------------------------------------------------------- 1 | 2 | from nspawn.wrapper.base import * 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/test/nspawn_test/wrapper/curl_test.py: -------------------------------------------------------------------------------- 1 | from nspawn.wrapper.curl import Curl 2 | 3 | 4 | def test_curl_head(): 5 | url = "http://checkip.amazonaws.com/" 6 | path = "/tmp/test_curl_head.txt" 7 | curl = Curl() 8 | curl.with_url(url) 9 | curl.with_file_head(path) 10 | result = curl.execute_unit() 11 | print(result) 12 | -------------------------------------------------------------------------------- /src/test/nspawn_test/wrapper/machinectl_test.py: -------------------------------------------------------------------------------- 1 | 2 | from nspawn.wrapper.machinectl import * 3 | 4 | 5 | def test_list(): 6 | print() 7 | mctl = MachineCtl() 8 | meta_list = mctl.list() 9 | list(map(print, meta_list)) 10 | 11 | 12 | def test_has_machine(): 13 | print() 14 | mctl = MachineCtl() 15 | meta_list = mctl.list() 16 | if meta_list: 17 | store = meta_list[0] 18 | machine = store.MACHINE 19 | print(mctl.has_machine(machine)) 20 | assert mctl.has_machine(machine) 21 | assert not mctl.has_machine("") 22 | -------------------------------------------------------------------------------- /src/test/nspawn_test/wrapper/nsenter_test.py: -------------------------------------------------------------------------------- 1 | 2 | from nspawn.wrapper.nsenter import * 3 | 4 | 5 | def test_nsenter(): 6 | print() 7 | machine = "nspawn-image-server" 8 | script = "id; env; exit" 9 | # NSENTER.execute_invoke(machine, script) 10 | -------------------------------------------------------------------------------- /src/test/nspawn_test/wrapper/sudo_test.py: -------------------------------------------------------------------------------- 1 | from nspawn.wrapper.sudo import Sudo 2 | 3 | 4 | def test_sudo(): 5 | sudo = Sudo() 6 | result = sudo.execute_unit(['ls', '-las']) 7 | # print(result.stdout) 8 | # print(result.stderr) 9 | 10 | 11 | def test_sudo_folder_check(): 12 | sudo = Sudo() 13 | assert sudo.folder_check("/tmp") is True 14 | 15 | 16 | def test_sudo_folder_assert(): 17 | sudo = Sudo() 18 | sudo.folder_assert("/tmp") 19 | 20 | 21 | def test_sudo_folder_transfer(): 22 | sudo = Sudo() 23 | source = "/tmp/nspawn-tester-source" 24 | target = "/tmp/nspawn-tester-target" 25 | sudo.folder_ensure(source) 26 | sudo.files_sync_full(source, target) 27 | sudo.files_delete(source) 28 | sudo.files_delete(target) 29 | 30 | 31 | def test_sudo_file_save_load(): 32 | sudo = Sudo() 33 | source = "abrakadabra" 34 | file = "/tmp/test_sudo_file_save_load" 35 | sudo.file_save(file, source) 36 | target = sudo.file_load(file) 37 | sudo.files_delete(file) 38 | assert source == target 39 | 40 | 41 | def test_sudo_xattr_get_set(): 42 | sudo = Sudo() 43 | name = 'tester' 44 | source = "abra '{' kadabra '}' abra" 45 | file = "/var/test-sudo-xattr-get-set" 46 | sudo.file_save(file, source) 47 | sudo.xattr_set(file, name, source) 48 | target = sudo.xattr_get(file, name) 49 | sudo.files_delete(file) 50 | assert source == target 51 | 52 | 53 | def test_sudo_xattr_load_save(): 54 | sudo = Sudo() 55 | assert sudo.xattr_space() == 'user.nspawn.' 56 | assert sudo.xattr_regex() == '^user[.]nspawn[.]' 57 | source = dict( 58 | num1='1', 59 | num2='2.0', 60 | one="one ':' one ':' one", 61 | two="two '{'} two [']' two", 62 | any="hello '{'} (###) [']' kitty", 63 | ) 64 | file = "/var/test-sudo-xattr-load-save" 65 | sudo.file_save(file, "") 66 | sudo.xattr_save(file, source) 67 | target = sudo.xattr_load(file) 68 | sudo.files_delete(file) 69 | assert source == target 70 | -------------------------------------------------------------------------------- /src/test/nspawn_test/wrapper/systemctl_test.py: -------------------------------------------------------------------------------- 1 | 2 | from nspawn.wrapper.systemctl import * 3 | 4 | 5 | def test_version_number(): 6 | print() 7 | sysctl = SystemCtl() 8 | version = sysctl.property_VersionNumber() 9 | print(f"version={version}") 10 | 11 | 12 | def xxxx_machine_init_pid(): 13 | print() 14 | sysctl = SystemCtl() 15 | service = 'systemd-journald.service' 16 | service_pid = sysctl.machine_init_pid(service) 17 | print(f"service_pid={service_pid}") 18 | 19 | 20 | def test_status_present(): 21 | print() 22 | sysctl = SystemCtl() 23 | service = 'systemd-journald.service' 24 | result = sysctl.status(service) 25 | print(result) 26 | assert sysctl.has_unit(service) 27 | assert sysctl.has_active(service) 28 | assert sysctl.has_enabled(service) 29 | 30 | 31 | def test_status_missing(): 32 | print() 33 | sysctl = SystemCtl() 34 | service = 'systemd-answer-42.service' 35 | result = sysctl.status(service) 36 | print(result) 37 | assert not sysctl.has_unit(service) 38 | assert not sysctl.has_active(service) 39 | assert not sysctl.has_enabled(service) 40 | 41 | 42 | def test_parse_ExecInfo(): 43 | print() 44 | value = """{ path=/usr/bin/systemd-nspawn ; argv[]=/usr/bin/systemd-nspawn --machine=alpa-base --directory=/var/lib/machines/alpa-base --kill-signal=SIGUSR1 --setenv=TEST_1=solid-value --setenv=TEST_2=value with space --setenv=TEST_3=value : with : colon --setenv=TEST_4=value " with " double quote --quiet --keep-unit --register=yes --network-macvlan=wire0 /sbin/init ; ignore_errors=no ; start_time=[n/a] ; stop_time=[n/a] ; pid=0 ; library=(null) ; status=0/0 }""" 45 | exec_info = parse_ExecInfo(value) 46 | print(exec_info) 47 | 48 | 49 | def test_show_exec_info(): 50 | print() 51 | sysctl = SystemCtl() 52 | service = 'systemd-journald.service' 53 | exec_start = sysctl.show_exec_info('ExecStart', service) 54 | print(exec_start) 55 | has_stop = sysctl.has_property('ExecStop', service) 56 | print(has_stop) 57 | assert has_stop == False 58 | 59 | 60 | def test_has_machine(): 61 | print() 62 | sysctl = SystemCtl() 63 | service = 'systemd-journald.service' 64 | has_machine = sysctl.has_machine(service) 65 | print(has_machine) 66 | assert has_machine == False 67 | -------------------------------------------------------------------------------- /src/test/nspawn_test/wrapper/systemd_nspawn_test.py: -------------------------------------------------------------------------------- 1 | from nspawn.tool.stamp import build_stamp 2 | 3 | from nspawn.wrapper.sudo import * 4 | 5 | from nspawn.wrapper.systemd_nspawn import * 6 | 7 | 8 | # see https://github.com/systemd/systemd/blob/master/src/nspawn/nspawn.c#L4814 9 | def test_empty_invoke(): 10 | print() 11 | machine = f"tester-{build_stamp()}" 12 | command = ['/usr/bin/env'] 13 | 14 | root_dir = f"/var/lib/machines/{machine}" 15 | check_dir = f"{root_dir}/usr" # expected by systemd 16 | SUDO.folder_ensure(check_dir) 17 | 18 | result = SYSTEMD_NSPAWN.execute_flow(machine, command) 19 | print(result) 20 | 21 | SUDO.files_delete(root_dir) 22 | 23 | assert result.rc == 1 24 | -------------------------------------------------------------------------------- /src/test/nspawn_test/wrapper/zip_test.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/random-python/nspawn/305268430526a17ce8e3e96e5f2f501cecdb6d92/src/test/nspawn_test/wrapper/zip_test.py -------------------------------------------------------------------------------- /src/test/readme.md: -------------------------------------------------------------------------------- 1 | 2 | ### project unit test sources 3 | -------------------------------------------------------------------------------- /src/verify/image/alpine/base/a.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | import subprocess 4 | 5 | 6 | def has_import(): 7 | try: 8 | import nspawn 9 | return True 10 | except ImportError: 11 | return False 12 | 13 | 14 | def ensure_import(): 15 | if has_import(): 16 | return 17 | try: 18 | command = ['git', 'rev-parse', '--show-toplevel'] 19 | project = subprocess.check_output(command).decode('utf-8').strip() 20 | path_main = f"{project}/src/main" 21 | path_test = f"{project}/src/test" 22 | sys.path.insert(0, path_main) 23 | sys.path.insert(0, path_test) 24 | except Exception as error: 25 | sys.exit(f"Development error: {str(error)}") 26 | 27 | 28 | ensure_import() 29 | -------------------------------------------------------------------------------- /src/verify/image/alpine/base/build.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | # import os, runpy 4 | # this_dir = os.path.dirname(os.path.abspath(__file__)) 5 | # runpy.run_path(f"{this_dir}/a.py") 6 | 7 | from nspawn.build import * 8 | 9 | import platform 10 | 11 | epoch = "3.10" 12 | release = f"{epoch}.3" 13 | hardware = platform.machine() 14 | 15 | # declare image identity 16 | IMAGE(f"file://localhost/tmp/nspawn/repo/alpine/base/default-{release}-{hardware}.tar.gz") 17 | 18 | # provision dependency image 19 | PULL(f"http://dl-cdn.alpinelinux.org/alpine/v{epoch}/releases/{hardware}/alpine-minirootfs-{release}-{hardware}.tar.gz") 20 | 21 | # alpine termination for /sbin/init (i.e. /bin/busybox) 22 | WITH(KillSignal="SIGUSR1") 23 | 24 | # provide environment varialbes 25 | WITH(Environment='TEST_1=solid-value') 26 | WITH(Environment='TEST_2=value with space') 27 | WITH(Environment='TEST_3=value : with : colon') 28 | WITH(Environment='TEST_4=value " with " double quote') 29 | 30 | # download remote resources 31 | # FETCH(url=f"http://aaa.bbb", path="/root/ssh") 32 | 33 | # copy local resources 34 | COPY("/etc") 35 | COPY("/root") 36 | 37 | # template local resources 38 | CAST("/root/readme.md", variable="template varialbe") 39 | 40 | SH("env|sort") 41 | SH("ip addr") 42 | SH("cat /etc/resolv.conf") 43 | SH("ping -c 1 www.google.com") 44 | # SH("ping -c 1 work1") 45 | # SH("ping -c 1 work3") 46 | # SH("ping -c 1 nspawn-proxy-server") 47 | 48 | # download and extract remote resource 49 | FETCH( 50 | url="https://github.com/random-python/nspawn/archive/master.zip", 51 | source="nspawn-master", 52 | target="/opt/nspawn", 53 | ) 54 | 55 | # verify extract of remote resource 56 | SH("ls -las /opt/nspawn") 57 | 58 | # invoke program 59 | RUN(["/usr/bin/env"]) 60 | 61 | # invoke shell script 62 | SH("apk update") 63 | SH("apk upgrade") 64 | SH("apk add tzdata") 65 | SH("apk add ca-certificates") 66 | SH("apk add busybox-initscripts") 67 | SH("apk add rsyslog") 68 | SH("apk add mc htop pwgen") 69 | SH("apk add iputils iproute2") 70 | SH("apk add dhcpcd openssh ") 71 | 72 | SH("rc-update add rsyslog") 73 | SH("rc-update add dhcpcd") 74 | SH("rc-update add sshd") 75 | 76 | SH(""" 77 | echo 'PubkeyAuthentication yes' >> /etc/ssh/sshd_config 78 | echo 'PasswordAuthentication no' >> /etc/ssh/sshd_config 79 | user=root; pass=$(pwgen -s 64 1); echo $user:$pass | chpasswd 80 | """) 81 | 82 | # publish image 83 | PUSH() 84 | -------------------------------------------------------------------------------- /src/verify/image/alpine/base/etc/hostname: -------------------------------------------------------------------------------- 1 | {{machine_name}} 2 | -------------------------------------------------------------------------------- /src/verify/image/alpine/base/etc/inittab: -------------------------------------------------------------------------------- 1 | # /etc/inittab 2 | # https://git.alpinelinux.org/aports/tree/main/alpine-baselayout/inittab 3 | 4 | ::sysinit:/sbin/openrc sysinit 5 | ::sysinit:/sbin/openrc boot 6 | ::wait:/sbin/openrc default 7 | 8 | # Set up a couple of getty's 9 | #tty1::respawn:/sbin/getty 38400 tty1 10 | #tty2::respawn:/sbin/getty 38400 tty2 11 | #tty3::respawn:/sbin/getty 38400 tty3 12 | 13 | # Put a getty on the serial port 14 | #ttyS0::respawn:/sbin/getty -L ttyS0 115200 vt100 15 | 16 | # Put a getty on the console 17 | console::respawn:/sbin/getty 115200,38400,9600 console 18 | 19 | # Stuff to do for the 3-finger salute 20 | ::ctrlaltdel:/sbin/reboot 21 | 22 | # Stuff to do before rebooting 23 | ::shutdown:/sbin/openrc shutdown 24 | -------------------------------------------------------------------------------- /src/verify/image/alpine/base/etc/motd: -------------------------------------------------------------------------------- 1 | # 2 | # message of the day 3 | # 4 | -------------------------------------------------------------------------------- /src/verify/image/alpine/base/etc/network/interfaces: -------------------------------------------------------------------------------- 1 | # 2 | # https://wiki.alpinelinux.org/wiki/Configure_Networking 3 | # 4 | 5 | auto lo 6 | iface lo inet loopback 7 | 8 | # register host in dhcp 9 | udhcpc_opts -h $HOSTNAME 10 | -------------------------------------------------------------------------------- /src/verify/image/alpine/base/nsenter.txt: -------------------------------------------------------------------------------- 1 | 2 | /usr/bin/sudo -u root /usr/bin/nsenter -m -u -i -n -p -t10469 -r/var/lib/machines/alpa-base -w/var/lib/machines/alpa-base /usr/bin/env -i TERM=xterm sh 3 | -------------------------------------------------------------------------------- /src/verify/image/alpine/base/root/.ssh/readme.md: -------------------------------------------------------------------------------- 1 | 2 | ### note 3 | 4 | ensure proper chmod: no go=rx on */.ssh path 5 | -------------------------------------------------------------------------------- /src/verify/image/alpine/base/root/readme.md: -------------------------------------------------------------------------------- 1 | 2 | ### root 3 | 4 | variable="{{variable}}" 5 | -------------------------------------------------------------------------------- /src/verify/image/alpine/base/setup-desure.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import os 4 | 5 | this_dir = os.path.dirname(os.path.abspath(__file__)) 6 | 7 | os.system(f"{this_dir}/setup.py --action desure") 8 | -------------------------------------------------------------------------------- /src/verify/image/alpine/base/setup-ensure.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import os 4 | 5 | this_dir = os.path.dirname(os.path.abspath(__file__)) 6 | 7 | os.system(f"{this_dir}/setup.py --action ensure") 8 | -------------------------------------------------------------------------------- /src/verify/image/alpine/base/setup-nsenter.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import os 4 | 5 | this_dir = os.path.dirname(os.path.abspath(__file__)) 6 | 7 | os.system(f"{this_dir}/setup.py --action=nsenter --trace-error=yes") 8 | -------------------------------------------------------------------------------- /src/verify/image/alpine/base/setup-rebuild.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import os 4 | 5 | this_dir = os.path.dirname(os.path.abspath(__file__)) 6 | 7 | os.system(f"{this_dir}/setup.py --action=desure --trace-error=yes") 8 | 9 | os.system(f"{this_dir}/build.py --trace-error=yes") 10 | 11 | os.system(f"{this_dir}/setup.py --action=ensure --trace-error=yes") 12 | -------------------------------------------------------------------------------- /src/verify/image/alpine/base/setup-update.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import os 4 | 5 | this_dir = os.path.dirname(os.path.abspath(__file__)) 6 | 7 | os.system(f"{this_dir}/setup.py --action=update --trace-error=yes") 8 | -------------------------------------------------------------------------------- /src/verify/image/alpine/base/setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | # import os, runpy 4 | # this_dir = os.path.dirname(os.path.abspath(__file__)) 5 | # runpy.run_path(f"{this_dir}/a.py") 6 | 7 | from nspawn.setup import * 8 | 9 | import platform 10 | 11 | epoch = "3.10" 12 | release = f"{epoch}.3" 13 | hardware = platform.machine() 14 | 15 | machine_name = "alpa-base" 16 | network_face = TOOL.select_interface() 17 | 18 | IMAGE(f"file://localhost/tmp/nspawn/repo/alpine/base/default-{release}-{hardware}.tar.gz") 19 | 20 | MACHINE( 21 | # define machine name 22 | name=machine_name, 23 | # extra entries for [Unit] section 24 | unit_conf=[ 25 | "Description=hello-kitty", # override description 26 | ], 27 | # extra entries for [Service] section 28 | service_conf=[ 29 | "CPUQuota=10%", # throttle processor usage 30 | ], 31 | # extra entries for [Install] section 32 | install_conf=[ 33 | "# user comment: hello-kitty", # inject user comment 34 | ], 35 | ) 36 | 37 | WITH( 38 | # Hostname="alpase", # needs systemd v 239 39 | Boot='yes', # auto detect /bin/init program 40 | Quiet="yes", # suppress "press to escape" message 41 | KeepUnit="yes", # use service unit as nspawn scope 42 | Register="yes", # expose service unit with machinectl 43 | MACVLAN=network_face, 44 | # Capability='all', 45 | ) 46 | 47 | # use host ssh login for container 48 | WITH(BindReadOnly="/root/.ssh/authorized_keys") 49 | 50 | # alpine system entry 51 | # EXEC(['/sbin/init']) 52 | # EXEC(['/bin/ls', '-Rlas', f"/root"]) 53 | 54 | # external config 55 | config_dir = f"{TOOL.nspawn_tempdir()}/machine/{machine_name}" 56 | 57 | # externally configurable hostname 58 | hostname_path = f"{config_dir}/etc/hostname" 59 | WITH(BindReadOnly=f"{hostname_path}:/etc/hostname") 60 | CAST(source="/etc/hostname", target=hostname_path, machine_name=machine_name) 61 | 62 | # externally exposed message log 63 | messages_path = f"{config_dir}/var/log/messages" 64 | WITH(Bind=f"{messages_path}:/var/log/messages") 65 | -------------------------------------------------------------------------------- /src/verify/image/alpine/base/unsetup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import os 4 | 5 | this_dir = os.path.dirname(os.path.abspath(__file__)) 6 | 7 | os.system(f"{this_dir}/setup.py --action desure") 8 | -------------------------------------------------------------------------------- /src/verify/image/alpine/mail/build.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from nspawn.build import * 4 | 5 | import platform 6 | 7 | epoch = "3.10" 8 | release = f"{epoch}.3" 9 | hardware = platform.machine() 10 | 11 | # declare image identity 12 | IMAGE(f"file://localhost/tmp/nspawn/repo/alpine/mail/default-{release}-{hardware}.tar.gz") 13 | 14 | # provision dependency image 15 | PULL(f"file://localhost/tmp/nspawn/repo/alpine/base/default-{release}-{hardware}.tar.gz") 16 | 17 | # invoke shell script 18 | SH("apk add postfix") 19 | SH("rc-update add postfix") 20 | 21 | # publish image 22 | PUSH() 23 | -------------------------------------------------------------------------------- /src/verify/image/alpine/mail/setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | # import os, runpy 4 | # this_dir = os.path.dirname(os.path.abspath(__file__)) 5 | # runpy.run_path(f"{this_dir}/a.py") 6 | 7 | from nspawn.setup import * 8 | 9 | import platform 10 | 11 | epoch = "3.10" 12 | release = f"{epoch}.3" 13 | hardware = platform.machine() 14 | 15 | machine_name = "alpa-base" 16 | network_face = TOOL.select_interface() 17 | 18 | IMAGE(f"file://localhost/tmp/nspawn/repo/alpine/base/default-{release}-{hardware}.tar.gz") 19 | 20 | MACHINE( 21 | # define machine name 22 | name=machine_name, 23 | # extra entries for [Unit] section 24 | unit_conf=[ 25 | "Description=hello-kitty", # override description 26 | ], 27 | # extra entries for [Service] section 28 | service_conf=[ 29 | "CPUQuota=10%", # throttle processor usage 30 | ], 31 | # extra entries for [Install] section 32 | install_conf=[ 33 | "# user comment: hello-kitty", # inject user comment 34 | ], 35 | ) 36 | 37 | WITH( 38 | # Hostname="alpase", # needs systemd v 239 39 | Boot='yes', # auto detect /bin/init program 40 | Quiet="yes", # suppress "press to escape" message 41 | KeepUnit="yes", # use service unit as nspawn scope 42 | Register="yes", # expose service unit with machinectl 43 | MACVLAN=network_face, 44 | # Capability='all', 45 | ) 46 | 47 | # use host ssh login for container 48 | WITH(BindReadOnly="/root/.ssh/authorized_keys") 49 | 50 | # alpine system entry 51 | # EXEC(['/sbin/init']) 52 | # EXEC(['/bin/ls', '-Rlas', f"/root"]) 53 | 54 | # external config 55 | config_dir = f"{TOOL.nspawn_tempdir()}/machine/{machine_name}" 56 | 57 | # externally configurable hostname 58 | hostname_path = f"{config_dir}/etc/hostname" 59 | WITH(BindReadOnly=f"{hostname_path}:/etc/hostname") 60 | CAST(source="/etc/hostname", target=hostname_path, machine_name=machine_name) 61 | 62 | # externally exposed message log 63 | messages_path = f"{config_dir}/var/log/messages" 64 | WITH(Bind=f"{messages_path}:/var/log/messages") 65 | -------------------------------------------------------------------------------- /src/verify/image/alpine/mail/unsetup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import os 4 | 5 | this_dir = os.path.dirname(os.path.abspath(__file__)) 6 | 7 | os.system(f"{this_dir}/setup.py --action desure") 8 | -------------------------------------------------------------------------------- /src/verify/image/alpine/proxy/build.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from nspawn.build import * 4 | 5 | import platform 6 | 7 | epoch = "3.10" 8 | release = f"{epoch}.3" 9 | hardware = platform.machine() 10 | 11 | # declare image identity 12 | IMAGE(f"file://localhost/tmp/nspawn/repo/alpine/proxy/default-{release}-{hardware}.tar.gz") 13 | 14 | # provision dependency image 15 | PULL(f"file://localhost/tmp/nspawn/repo/alpine/base/default-{release}-{hardware}.tar.gz") 16 | 17 | # invoke shell script 18 | SH("apk add squid") 19 | SH("rc-update add squid") 20 | 21 | # publish image 22 | PUSH() 23 | -------------------------------------------------------------------------------- /src/verify/image/alpine/proxy/etc/hostname: -------------------------------------------------------------------------------- 1 | {{machine_name}} 2 | -------------------------------------------------------------------------------- /src/verify/image/alpine/proxy/etc/inittab: -------------------------------------------------------------------------------- 1 | # /etc/inittab 2 | # https://git.alpinelinux.org/aports/tree/main/alpine-baselayout/inittab 3 | 4 | ::sysinit:/sbin/openrc sysinit 5 | ::sysinit:/sbin/openrc boot 6 | ::wait:/sbin/openrc default 7 | 8 | # Set up a couple of getty's 9 | #tty1::respawn:/sbin/getty 38400 tty1 10 | #tty2::respawn:/sbin/getty 38400 tty2 11 | #tty3::respawn:/sbin/getty 38400 tty3 12 | 13 | # Put a getty on the serial port 14 | #ttyS0::respawn:/sbin/getty -L ttyS0 115200 vt100 15 | 16 | # Put a getty on the console 17 | console::respawn:/sbin/getty 115200,38400,9600 console 18 | 19 | # Stuff to do for the 3-finger salute 20 | ::ctrlaltdel:/sbin/reboot 21 | 22 | # Stuff to do before rebooting 23 | ::shutdown:/sbin/openrc shutdown 24 | -------------------------------------------------------------------------------- /src/verify/image/alpine/proxy/etc/motd: -------------------------------------------------------------------------------- 1 | # 2 | # message of the day 3 | # 4 | -------------------------------------------------------------------------------- /src/verify/image/alpine/proxy/etc/network/interfaces: -------------------------------------------------------------------------------- 1 | # 2 | # https://wiki.alpinelinux.org/wiki/Configure_Networking 3 | # 4 | 5 | auto lo 6 | iface lo inet loopback 7 | 8 | # register host in dhcp 9 | udhcpc_opts -h $HOSTNAME 10 | -------------------------------------------------------------------------------- /src/verify/image/alpine/proxy/setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | # import os, runpy 4 | # this_dir = os.path.dirname(os.path.abspath(__file__)) 5 | # runpy.run_path(f"{this_dir}/a.py") 6 | 7 | from nspawn.setup import * 8 | 9 | import platform 10 | 11 | epoch = "3.10" 12 | release = f"{epoch}.3" 13 | hardware = platform.machine() 14 | 15 | machine_name = "alpa-proxy" 16 | network_face = TOOL.select_interface() 17 | 18 | IMAGE(f"file://localhost/tmp/nspawn/repo/alpine/proxy/default-{release}-{hardware}.tar.gz") 19 | 20 | MACHINE( 21 | # define machine name 22 | name=machine_name, 23 | # extra entries for [Unit] section 24 | unit_conf=[ 25 | "Description=hello-kitty", # override description 26 | ], 27 | # extra entries for [Service] section 28 | service_conf=[ 29 | "CPUQuota=10%", # throttle processor usage 30 | ], 31 | # extra entries for [Install] section 32 | install_conf=[ 33 | "# user comment: hello-kitty", # inject user comment 34 | ], 35 | ) 36 | 37 | WITH( 38 | # Hostname="alpase", # needs systemd v 239 39 | Boot='yes', # auto detect /bin/init program 40 | Quiet="yes", # suppress "press to escape" message 41 | KeepUnit="yes", # use service unit as nspawn scope 42 | Register="yes", # expose service unit with machinectl 43 | MACVLAN=network_face, 44 | # Capability='all', 45 | ) 46 | 47 | # use host ssh login for container 48 | WITH(BindReadOnly="/root/.ssh/authorized_keys") 49 | 50 | # alpine system entry 51 | # EXEC(['/sbin/init']) 52 | # EXEC(['/bin/ls', '-Rlas', f"/root"]) 53 | 54 | # external config 55 | config_dir = f"{TOOL.nspawn_tempdir()}/machine/{machine_name}" 56 | 57 | # externally configurable hostname 58 | hostname_path = f"{config_dir}/etc/hostname" 59 | WITH(BindReadOnly=f"{hostname_path}:/etc/hostname") 60 | CAST(source="/etc/hostname", target=hostname_path, machine_name=machine_name) 61 | 62 | # externally exposed message log 63 | messages_path = f"{config_dir}/var/log/messages" 64 | WITH(Bind=f"{messages_path}:/var/log/messages") 65 | -------------------------------------------------------------------------------- /src/verify/image/alpine/proxy/unsetup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import os 4 | 5 | this_dir = os.path.dirname(os.path.abspath(__file__)) 6 | 7 | os.system(f"{this_dir}/setup.py --action desure") 8 | -------------------------------------------------------------------------------- /src/verify/image/archux/base/build.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from nspawn.build import * 4 | 5 | build_epoch = TOOL.build_epoch() 6 | version_path = TOOL.date_path(build_epoch) 7 | version_dots = TOOL.date_dots(build_epoch) 8 | version_dash = TOOL.date_dash(build_epoch) 9 | 10 | archux_url = "https://archive.archlinux.org" 11 | booter_url = f"{archux_url}/iso/{version_dots}/archlinux-bootstrap-{version_dots}-x86_64.tar.gz" 12 | mirror_url = f"{archux_url}/repos/{version_path}/$repo/os/$arch" 13 | 14 | # declare image identity 15 | IMAGE(url=f"file://localhost/tmp/nspawn/repo/archux/base/{version_dash}.tar.gz") 16 | 17 | # provision dependency image 18 | PULL(url=booter_url) 19 | 20 | # configure container profile 21 | WITH( 22 | Boot="yes", # auto-find image init program 23 | Quiet="yes", # suppress "press to escape" message 24 | KeepUnit="yes", # use service unit as nspawn scope 25 | Register="yes", # expose service unit with machinectl 26 | ) 27 | 28 | # copy local resources 29 | COPY(path="/etc") 30 | COPY(path="/root") 31 | 32 | # template local resources 33 | CAST(path="/etc/pacman.d/mirrorlist", mirror_url=mirror_url) 34 | 35 | # activate proxy tls 36 | SH("update-ca-trust") 37 | 38 | # ensure gpg keys 39 | SH(script="pacman-key --init") 40 | SH(script="pacman-key --populate") 41 | 42 | # update package repository 43 | SH(script="pacman --sync --needed --noconfirm --refresh") 44 | 45 | # update database 46 | SH("pacman --sync --refresh") 47 | 48 | # install packages 49 | SH("pacman --sync --needed --noconfirm " 50 | "mc htop file " 51 | "iputils iproute2 " 52 | "openssh " 53 | ) 54 | 55 | # enable services 56 | SH("systemctl enable " 57 | "systemd-networkd " 58 | "systemd-resolved " 59 | "sshd " 60 | ) 61 | 62 | # publish image 63 | PUSH() 64 | -------------------------------------------------------------------------------- /src/verify/image/archux/base/etc/ca-certificates/trust-source/anchors/proxy-cert.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIICnDCCAYQCCQConVycCDtlJDANBgkqhkiG9w0BAQsFADAQMQ4wDAYDVQQKDAVw 3 | cm94eTAeFw0xNjA3MTYyMTQxMjRaFw00MzEyMDIyMTQxMjRaMBAxDjAMBgNVBAoM 4 | BXByb3h5MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA2d3Kk+4nMENY 5 | 1tf1uHuJ25pLfLxpUIawiujWJBWnGd+Fw/kgm3sXiaA0vqTAINOqAg8c4r2Ur1Ne 6 | E1RYc4LEvqWSlcCSJOp+azTlXWF7Ns7hEfxYz74SChFUfrpxaGnlqvHjHBQIsneV 7 | YjGF895+8D64DhDe8T4/3fLaWJX/0znp1HAPFoQfxFo6smenixnIdIxTbVY9y/WY 8 | dZaaCZv18pMdgTZtfNFuJQzqSxOXowFOZ0cMDSMKHGrqL5BHBJpffVKmzMpkVmEt 9 | MHh+sO48TY9pV2K8yGQbBPIujCRr0hi1g6q9me9W6D210EBApd1jEJwmM17TAhu8 10 | 03KZzNwCGwIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQDHtl/2fBHzRnDaelpFsnJS 11 | m3dOaaoWZdCtiiXyRXBNHVPjn3IFA+volKMt8PAsTkuWMEbH2V1SxZc5Zs8eomlk 12 | vPVVL8lJSWI/HiXdQG8sCiEH9TA5/fO9db5f0b0Mx2IHec6qwfJHdDVhk61CIa/w 13 | nu0/POV20DzXSWVA19i6mL9WJS3NQUkyPkY701QzPvbCvJlCnx8kZxB6i/eLdH8g 14 | p/v2teLKiHcxC8qV/LJAAost9PgRsE8BRx2QwsuR8uhe7W+QYEa4s5tZ7hFKjWQn 15 | UBkUCvkBgW7Kpsdd2Jba4zbVz0sYzgEP6Z+69J101kmSxGMFx6YveQQ/ZazSPxqs 16 | -----END CERTIFICATE----- 17 | -------------------------------------------------------------------------------- /src/verify/image/archux/base/etc/locale.conf: -------------------------------------------------------------------------------- 1 | 2 | # default system locale 3 | 4 | LANG=en_US.UTF-8 5 | -------------------------------------------------------------------------------- /src/verify/image/archux/base/etc/locale.gen: -------------------------------------------------------------------------------- 1 | 2 | # available locale 3 | 4 | en_US.UTF-8 UTF-8 5 | -------------------------------------------------------------------------------- /src/verify/image/archux/base/etc/nsswitch.conf: -------------------------------------------------------------------------------- 1 | # Begin /etc/nsswitch.conf 2 | 3 | passwd: compat mymachines systemd 4 | group: compat mymachines systemd 5 | shadow: compat 6 | 7 | publickey: files 8 | 9 | #hosts: files mymachines resolve [!UNAVAIL=return] dns myhostname 10 | #hosts: files mymachines resolve dns myhostname 11 | hosts: files mymachines dns resolve myhostname 12 | networks: files 13 | 14 | protocols: files 15 | services: files 16 | ethers: files 17 | rpc: files 18 | 19 | netgroup: files 20 | 21 | # End /etc/nsswitch.conf 22 | -------------------------------------------------------------------------------- /src/verify/image/archux/base/etc/pacman.d/hooks/remove_cache.hook: -------------------------------------------------------------------------------- 1 | 2 | [Trigger] 3 | Operation = Upgrade 4 | Operation = Install 5 | Operation = Remove 6 | Type = Package 7 | Target = * 8 | 9 | [Action] 10 | Description = remove_cache 11 | When = PostTransaction 12 | Exec = /usr/bin/rm -rf /var/cache/pacman/pkg 13 | -------------------------------------------------------------------------------- /src/verify/image/archux/base/etc/pacman.d/mirrorlist: -------------------------------------------------------------------------------- 1 | 2 | # 3 | # https://wiki.archlinux.org/index.php/Arch_Linux_Archive 4 | # 5 | 6 | Server={{mirror_url}} 7 | -------------------------------------------------------------------------------- /src/verify/image/archux/base/etc/systemd/network/macvlan.network: -------------------------------------------------------------------------------- 1 | 2 | # internal network 3 | 4 | [Match] 5 | Name=mv-* 6 | 7 | [Network] 8 | DHCP=ipv4 9 | IPForward=ipv4 10 | LinkLocalAddressing=no 11 | -------------------------------------------------------------------------------- /src/verify/image/archux/base/mount.txt: -------------------------------------------------------------------------------- 1 | overlay on /var/lib/machines/a-nspawn-build-20190913-165840-107558 2 | type overlay ( 3 | rw, 4 | relatime, 5 | lowerdir= 6 | /var/lib/nspawn/extract/a-nspawn-build-20190913-165840-107558/: 7 | /var/lib/nspawn/extract/image/arch/base/2019-09-01.tar.gz/: 8 | /var/lib/nspawn/extract/archive.archlinux.org/iso/2019.09.01/archlinux-bootstrap-2019.09.01-x86_64.tar.gz/root.x86_64: 9 | /var/lib/nspawn/runtime/a-nspawn-build-20190913-165840-107558/zero, 10 | upperdir= 11 | /var/lib/nspawn/runtime/a-nspawn-build-20190913-165840-107558/root, 12 | workdir= 13 | /var/lib/nspawn/runtime/a-nspawn-build-20190913-165840-107558/work, 14 | xino=off) 15 | -------------------------------------------------------------------------------- /src/verify/image/archux/base/root/.config/htop/htoprc: -------------------------------------------------------------------------------- 1 | # Beware! This file is rewritten by htop when settings are changed in the interface. 2 | # The parser is also very primitive, and not human-friendly. 3 | fields=0 48 17 18 38 39 40 2 46 47 49 1 4 | sort_key=46 5 | sort_direction=1 6 | hide_threads=0 7 | hide_kernel_threads=1 8 | hide_userland_threads=0 9 | shadow_other_users=0 10 | show_thread_names=0 11 | show_program_path=1 12 | highlight_base_name=0 13 | highlight_megabytes=1 14 | highlight_threads=1 15 | tree_view=1 16 | header_margin=1 17 | detailed_cpu_time=0 18 | cpu_count_from_zero=0 19 | update_process_names=0 20 | account_guest_in_cpu_meter=0 21 | color_scheme=0 22 | delay=15 23 | left_meters=LeftCPUs2 Memory Swap 24 | left_meter_modes=1 1 1 25 | right_meters=RightCPUs2 Tasks LoadAverage Uptime 26 | right_meter_modes=1 2 2 2 27 | -------------------------------------------------------------------------------- /src/verify/image/archux/base/root/.config/mc/panels.ini: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/random-python/nspawn/305268430526a17ce8e3e96e5f2f501cecdb6d92/src/verify/image/archux/base/root/.config/mc/panels.ini -------------------------------------------------------------------------------- /src/verify/image/archux/base/setup-desure.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import os 4 | 5 | this_dir = os.path.dirname(os.path.abspath(__file__)) 6 | 7 | os.system(f"{this_dir}/setup.py --action desure") 8 | -------------------------------------------------------------------------------- /src/verify/image/archux/base/setup-ensure.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import os 4 | 5 | this_dir = os.path.dirname(os.path.abspath(__file__)) 6 | 7 | os.system(f"{this_dir}/setup.py --action ensure") 8 | -------------------------------------------------------------------------------- /src/verify/image/archux/base/setup-nsenter.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import os 4 | 5 | this_dir = os.path.dirname(os.path.abspath(__file__)) 6 | 7 | os.system(f"{this_dir}/setup.py --action=nsenter --trace-error=yes") 8 | -------------------------------------------------------------------------------- /src/verify/image/archux/base/setup-rebuild.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import os 4 | 5 | this_dir = os.path.dirname(os.path.abspath(__file__)) 6 | 7 | os.system(f"{this_dir}/setup.py --action=desure --trace-error=yes") 8 | 9 | os.system(f"{this_dir}/build.py --trace-error=yes") 10 | 11 | os.system(f"{this_dir}/setup.py --action=ensure --trace-error=yes") 12 | -------------------------------------------------------------------------------- /src/verify/image/archux/base/setup-update.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import os 4 | 5 | this_dir = os.path.dirname(os.path.abspath(__file__)) 6 | 7 | os.system(f"{this_dir}/setup.py --action=update --trace-error=yes") 8 | -------------------------------------------------------------------------------- /src/verify/image/archux/base/setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from nspawn.setup import * 4 | 5 | build_epoch = TOOL.build_epoch() 6 | version_dash = TOOL.date_dash(build_epoch) 7 | 8 | network_face = TOOL.select_interface() 9 | 10 | IMAGE(url=f"file://localhost/tmp/nspawn/repo/archux/base/{version_dash}.tar.gz") 11 | 12 | MACHINE(name=f"arch-base") 13 | 14 | WITH( 15 | # Hostname="archase", # needs systemd v 239 16 | MACVLAN=network_face, 17 | ) 18 | 19 | WITH(Bind=f"/root/.ssh/authorized_keys") 20 | -------------------------------------------------------------------------------- /src/verify/image/archux/http/build.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from nspawn.build import * 4 | 5 | build_epoch = TOOL.build_epoch() 6 | version_dash = TOOL.date_dash(build_epoch) 7 | 8 | IMAGE(f"file://localhost/tmp/nspawn/repo/archux/http/{version_dash}.tar.gz") 9 | 10 | PULL(f"file://localhost/tmp/nspawn/repo/archux/base/{version_dash}.tar.gz") 11 | 12 | SH("pacman --sync --needed --noconfirm " 13 | "nginx " 14 | ) 15 | 16 | SH("systemctl enable " 17 | "nginx.service " 18 | ) 19 | 20 | COPY("/etc") 21 | COPY("/opt") 22 | COPY("/usr") 23 | 24 | PUSH() 25 | -------------------------------------------------------------------------------- /src/verify/image/archux/http/etc/nginx/conf.d/roundcube.conf: -------------------------------------------------------------------------------- 1 | 2 | location /mail { 3 | 4 | # install folder 5 | alias /usr/share/webapps/roundcubemail; 6 | 7 | include /etc/nginx/mime.types; 8 | 9 | # default entry page 10 | index index.php index.html; 11 | 12 | # favicon 13 | location ~ ^/mail/favicon.ico$ { 14 | root /usr/share/webapps/roundcubemail/skins/classic/images; 15 | log_not_found off; 16 | access_log off; 17 | expires max; 18 | } 19 | 20 | # robots file 21 | location ~ ^/mail/robots.txt { 22 | allow all; 23 | log_not_found off; 24 | access_log off; 25 | } 26 | 27 | # hide protected directories 28 | location ~ ^/mail/(config|temp|logs)/ { 29 | deny all; 30 | } 31 | location ~ ^/mail/(README|INSTALL|LICENSE|CHANGELOG|UPGRADING)$ { 32 | deny all; 33 | } 34 | location ~ ^/mail/(bin|SQL)/ { 35 | deny all; 36 | } 37 | 38 | # hide .md files 39 | location ~ ^/mail/(.+\.md)$ { 40 | deny all; 41 | } 42 | 43 | # hide all dot files 44 | location ~ ^/mail/\. { 45 | deny all; 46 | access_log off; 47 | log_not_found off; 48 | } 49 | 50 | # roundcube fastcgi config 51 | location ~ ^/mail(/.*\.php)$ { 52 | try_files $uri =404; 53 | include /etc/nginx/fastcgi.conf; 54 | fastcgi_index index.php; 55 | fastcgi_pass unix:/run/php-fpm/php-fpm.sock; 56 | # ??? 57 | fastcgi_split_path_info ^/mail/(.+\.php)(/.*)$; 58 | # required when using 'alias' 59 | fastcgi_param SCRIPT_FILENAME $request_filename; 60 | fastcgi_param PATH_INFO $fastcgi_path_info; 61 | fastcgi_param PHP_VALUE open_basedir="/home/logs:/home/conf/roundcube:/tmp/:/var/cache/roundcubemail:/usr/share/webapps/roundcubemail:/etc/webapps/roundcubemail:/usr/share/pear/:/var/log/roundcubemail"; 62 | } 63 | 64 | } 65 | -------------------------------------------------------------------------------- /src/verify/image/archux/http/etc/nginx/html/index.html: -------------------------------------------------------------------------------- 1 | 2 | hello kitty 3 | -------------------------------------------------------------------------------- /src/verify/image/archux/http/etc/nginx/nginx.conf: -------------------------------------------------------------------------------- 1 | 2 | worker_processes auto; 3 | 4 | events { 5 | use epoll; 6 | worker_connections 2048; 7 | } 8 | 9 | http { 10 | 11 | server { 12 | 13 | access_log off; 14 | error_log syslog:server=unix:/dev/log; 15 | 16 | listen 80; 17 | 18 | server_name _; 19 | 20 | return 301 https://$host$request_uri; 21 | 22 | } 23 | 24 | server { 25 | 26 | access_log off; 27 | error_log syslog:server=unix:/dev/log; 28 | 29 | root /etc/ngnix/html; 30 | 31 | listen 443 ssl; 32 | 33 | server_name mail-cube mail-cube.lan mail-cube.carrotgarden.com; 34 | 35 | ssl_certificate /etc/nginx/tls/server-cert.pem; 36 | ssl_certificate_key /etc/nginx/tls/server-key.pem; 37 | 38 | include /etc/nginx/conf.d/*.conf; 39 | 40 | } 41 | 42 | } 43 | -------------------------------------------------------------------------------- /src/verify/image/archux/http/etc/nginx/tls/server-cert.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIICnjCCAYYCCQC9vu1rnsBB5TANBgkqhkiG9w0BAQsFADARMQ8wDQYDVQQKDAZz 3 | ZXJ2ZXIwHhcNMTcxMDAzMDA1OTM2WhcNNDUwMjE4MDA1OTM2WjARMQ8wDQYDVQQK 4 | DAZzZXJ2ZXIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDQs4aNHoIW 5 | VG/PdRanq41QAb+eWlS4q/la4V5W/SiHSg28LlSzXZx9nB3wDMRTRwLChD/iudV4 6 | PEclb+xkJYySrc14ihTqnMQn1pTIYdbNHyezy/lliO1pqsJ3HNDTf88lLmYVup1w 7 | sPo2Uld1pggMjcxltBLNJxC9lL5bXear8mTYQ5fO19v3VoMnXtXWYMeQ6r9Toyb2 8 | DqsSWoai9DtfuUS2idL2va8278tW6sijM9p1Q4O0MNPeqG4Aj5Muu+dGLuhgfOIc 9 | 1Cn2hpognRQD3huyZSeE018lt6aJMJHfgn96OdGf2BhCuoDDzLY+ht8VY03vZ2uW 10 | f8mTJsMtTZiFAgMBAAEwDQYJKoZIhvcNAQELBQADggEBABFFW5vQ/obD6LDjzItj 11 | OyKagcfN1GWrwa1PeL/H9HMbGfJbpgZr2JAQj4WiOoxC0J01BX0khxnse+AZPPwd 12 | DyhIO7SjJ4XQgIyk1ooLT3VVk5ZehJp2d9khCQ1IC8HeaIsLXZHsvWpGbDjlEc9l 13 | 9qNyB0BI6f/tw0xq5oN2nZugYi7q/7+eX63oB5Ve66MgJZv7PesW1cHjngndYZnX 14 | V2G9GoA4e9nZusFh38kjz/s07iIOLv+Sxb2fkZCwAhNqQfXZOqxv5x+8TS1yGTPp 15 | VFlv4qR8/08R/GWvQUB/5e+PGeDFgZgosm7TVJYAZ7bqUveHiBg03MyFvvRiuxSb 16 | MuQ= 17 | -----END CERTIFICATE----- 18 | -------------------------------------------------------------------------------- /src/verify/image/archux/http/etc/nginx/tls/server-key.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIIEpAIBAAKCAQEA0LOGjR6CFlRvz3UWp6uNUAG/nlpUuKv5WuFeVv0oh0oNvC5U 3 | s12cfZwd8AzEU0cCwoQ/4rnVeDxHJW/sZCWMkq3NeIoU6pzEJ9aUyGHWzR8ns8v5 4 | ZYjtaarCdxzQ03/PJS5mFbqdcLD6NlJXdaYIDI3MZbQSzScQvZS+W13mq/Jk2EOX 5 | ztfb91aDJ17V1mDHkOq/U6Mm9g6rElqGovQ7X7lEtonS9r2vNu/LVurIozPadUOD 6 | tDDT3qhuAI+TLrvnRi7oYHziHNQp9oaaIJ0UA94bsmUnhNNfJbemiTCR34J/ejnR 7 | n9gYQrqAw8y2PobfFWNN72drln/JkybDLU2YhQIDAQABAoIBAF/Lp90qvceGJnRx 8 | aF66LfkldFE5YV4LAkAm5mjDd1DyOu+/zH0wYN5/RHalRZB8LdYfYjut1UCFWpri 9 | jv1BBw9k28WSjBtMrfUjV9PKpRAuVsj+vZFPkrpH+U83WNtb9Tc53v3mVNIfShqZ 10 | HTARb+xjQgkE6QRmBBchKWdC6NjPbA/3bXRl/p+qwP4E/XUMdWCKAWytXN6KbmZe 11 | phkSNp6+6SbvsC9mnA4IeT7QsozWq2MfenNQTi2booFN0rnqxsX9sErrL7Uq/Eh4 12 | fHQ05YHTydgBXkRY3SHHSmr5q760s95dhN3qLnedwgBkn+1xCz0mq6hRHvf1RaI5 13 | zfhDlBkCgYEA7rc2OYiCszp8P5BiUTr+uuYSD1dsCoe+KI+TgFifYQC4SYhFai/s 14 | H+PpAFt7ctZHAVgU1ClIC5wL2TaPp5STd6OnP7hKeBbyRRfEBmSr1uZC2gMvYW4w 15 | JJ2FofClweh5xmevGvgVgIpCJl2uWk+oUnoMZSDxbDtQehvHgwMGJf8CgYEA38/4 16 | +6lX8RWvn3mXvwUP0lBWq73Ls2aCGTeomwgjMNv8m50p0HQ/QmNGuglBeKUJXzes 17 | 5Mq6eYry6AsqwoAwf2xMv+sbiKOi34NwwN2dHE/JsLnAkhx/+/6Z1tORPWa7xR0V 18 | ILrL966P/1KkaDQ8NI9XT53lVRPVlWxhwD28qXsCgYEA3ztJZAf6YWyh4Dc/GcNX 19 | jqOz49DW4goKk5egUfeOI4Iu0+SxNRnXcAMLUyA5kyw6flFGORjZnLGyis/xCr9w 20 | IANUVcwbJ64dpOsSjv/5ih8OCR3NYwlN0A/WdZe7ClhB+H3hTBEO2oNwVLxqXV38 21 | hBRKHw/tdE8LdQUhT6y2RTUCgYBMrn7KCq08xodThl99bQiMsEXhtbdzEe4FIdK8 22 | uz8DYHvd/Sz0ZL+/yS/UUZOC+FiLrRGpmePl/LtY0WqnFTCIl9x77NuUJyyIlAov 23 | qj5IEvYxfJMD/IvFFOg0eGxMs0QmNt6kRhvYEpD3k6MMqMjv4SSy/JA0ZAVLmsSr 24 | DvvyvQKBgQDL83ka1y+PfuTtbllPLrgxZQ9OPA2or21TtHVaUKxFOUhiA3ksLL7G 25 | cSv08Iu1WXUIfDM91A5HR1B6gSAgumu0BTzaedYVb7g+jbzQZnJ4dE8MR6eIIwcu 26 | TZ/70EilgXxKyAo2Imup0lhZz8lTu/kpEx1noaDOql9fguUfrVFykQ== 27 | -----END RSA PRIVATE KEY----- 28 | -------------------------------------------------------------------------------- /src/verify/image/archux/http/etc/nginx/tls/server-req.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE REQUEST----- 2 | MIICVjCCAT4CAQAwETEPMA0GA1UECgwGc2VydmVyMIIBIjANBgkqhkiG9w0BAQEF 3 | AAOCAQ8AMIIBCgKCAQEA0LOGjR6CFlRvz3UWp6uNUAG/nlpUuKv5WuFeVv0oh0oN 4 | vC5Us12cfZwd8AzEU0cCwoQ/4rnVeDxHJW/sZCWMkq3NeIoU6pzEJ9aUyGHWzR8n 5 | s8v5ZYjtaarCdxzQ03/PJS5mFbqdcLD6NlJXdaYIDI3MZbQSzScQvZS+W13mq/Jk 6 | 2EOXztfb91aDJ17V1mDHkOq/U6Mm9g6rElqGovQ7X7lEtonS9r2vNu/LVurIozPa 7 | dUODtDDT3qhuAI+TLrvnRi7oYHziHNQp9oaaIJ0UA94bsmUnhNNfJbemiTCR34J/ 8 | ejnRn9gYQrqAw8y2PobfFWNN72drln/JkybDLU2YhQIDAQABoAAwDQYJKoZIhvcN 9 | AQELBQADggEBAFH11MNaf1t8EHtl4JWrMUpOnZD/KUD1PxNAv62kJQuMvRTWjiJH 10 | x/WEjPJj0BOYJ4bwCaowwjk7Jz/LaECESaySVr9Ss9ezya8IsjmAA65iwDpNxqNE 11 | xjAKAaQYHFCv8U+JrZGZ6K4R0B/FUmkBCdXk0ME4arIY5yhrYoFSSbOYEgH1tybf 12 | RNvBV3iSTUeUrHZVkaFHzhDcHXxckvxkPxD4FRXWG2svxMoQEPCojzyA2BOWRrL+ 13 | f6LHkugsP1pTFliqj2HgzUeG+ZoKvgb2rrAIdg9ROZ6r37geK2Ug7keZ9yBqvWki 14 | NvlDHYiFRQi+SgZwzWIQmosD99hpM0tLblw= 15 | -----END CERTIFICATE REQUEST----- 16 | -------------------------------------------------------------------------------- /src/verify/image/archux/http/etc/nginx/tls/server.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e -u 4 | 5 | # https://smoothnet.org/squid-proxy-with-ssl-bump/ 6 | 7 | # https://github.com/vmware/ansible-kubernetes-ca/blob/master/tasks/main.yml 8 | 9 | dir=$(dirname $0) 10 | ca_key=$dir/server-key.pem 11 | ca_req=$dir/server-req.pem 12 | ca_cert=$dir/server-cert.pem 13 | ca_subj="/O=server" 14 | 15 | openssl genrsa -out $ca_key 2048 16 | openssl req -new -batch -key $ca_key -out $ca_req -subj $ca_subj 17 | openssl x509 -req -days 10000 -in $ca_req -signkey $ca_key -out $ca_cert 18 | -------------------------------------------------------------------------------- /src/verify/image/archux/http/home/conf/roundcube/readme.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/random-python/nspawn/305268430526a17ce8e3e96e5f2f501cecdb6d92/src/verify/image/archux/http/home/conf/roundcube/readme.md -------------------------------------------------------------------------------- /src/verify/image/archux/http/home/logs/nginx/readme.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/random-python/nspawn/305268430526a17ce8e3e96e5f2f501cecdb6d92/src/verify/image/archux/http/home/logs/nginx/readme.md -------------------------------------------------------------------------------- /src/verify/image/archux/http/home/logs/roundcube/readme.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/random-python/nspawn/305268430526a17ce8e3e96e5f2f501cecdb6d92/src/verify/image/archux/http/home/logs/roundcube/readme.md -------------------------------------------------------------------------------- /src/verify/image/archux/http/opt/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e -x 4 | 5 | base_dir="/usr/share/webapps/roundcubemail" 6 | plug_dir="$base_dir/plugins" 7 | 8 | setup() { 9 | cd "$base_dir" 10 | rm -f composer.lock 11 | composer install --no-dev 12 | } 13 | 14 | clone() { 15 | local "$@" 16 | cd "$plug_dir" 17 | rm -r -f "$dir" 18 | git clone "$url" "$dir" 19 | } 20 | 21 | # php installer (use arch) 22 | #curl -sS https://getcomposer.org/installer | php 23 | 24 | ### external 25 | 26 | clone dir=carddav url=https://github.com/random-cuber/carddav.git 27 | 28 | cd "$base_dir/plugins/carddav" 29 | composer install 30 | 31 | clone dir=contextmenu url=https://github.com/random-cuber/contextmenu.git 32 | clone dir=identity_smtp url=https://github.com/random-cuber/identity_smtp.git 33 | # clone dir=keyboard_shortcuts url=https://github.com/random-cuber/keyboard_shortcuts.git 34 | # clone dir=keyboard_shortcuts_ng url=https://github.com/random-cuber/keyboard_shortcuts_ng.git 35 | clone dir=automatic_addressbook url=https://github.com/random-cuber/automatic_addressbook.git 36 | 37 | ### internal 38 | 39 | clone dir=hotkeys url=https://github.com/random-cuber/hotkeys.git 40 | clone dir=responses url=https://github.com/random-cuber/responses.git 41 | clone dir=styled_popups url=https://github.com/random-cuber/styled_popups.git 42 | # clone dir=font_awesome url=https://github.com/random-cuber/font_awesome.git 43 | clone dir=contextmenu_folder url=https://github.com/random-cuber/contextmenu_folder.git 44 | 45 | -------------------------------------------------------------------------------- /src/verify/image/archux/http/setup-desure.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import os 4 | 5 | this_dir = os.path.dirname(os.path.abspath(__file__)) 6 | 7 | os.system(f"{this_dir}/setup.py --action desure") 8 | -------------------------------------------------------------------------------- /src/verify/image/archux/http/setup-ensure.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import os 4 | 5 | this_dir = os.path.dirname(os.path.abspath(__file__)) 6 | 7 | os.system(f"{this_dir}/setup.py --action ensure") 8 | -------------------------------------------------------------------------------- /src/verify/image/archux/http/setup-nsenter.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import os 4 | 5 | this_dir = os.path.dirname(os.path.abspath(__file__)) 6 | 7 | os.system(f"{this_dir}/setup.py --action=nsenter --trace-error=yes") 8 | -------------------------------------------------------------------------------- /src/verify/image/archux/http/setup-rebuild.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import os 4 | 5 | this_dir = os.path.dirname(os.path.abspath(__file__)) 6 | 7 | os.system(f"{this_dir}/setup.py --action=desure --trace-error=yes") 8 | 9 | os.system(f"{this_dir}/build.py --trace-error=yes") 10 | 11 | os.system(f"{this_dir}/setup.py --action=ensure --trace-error=yes") 12 | -------------------------------------------------------------------------------- /src/verify/image/archux/http/setup-update.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import os 4 | 5 | this_dir = os.path.dirname(os.path.abspath(__file__)) 6 | 7 | os.system(f"{this_dir}/setup.py --action=update --trace-error=yes") 8 | -------------------------------------------------------------------------------- /src/verify/image/archux/http/setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from nspawn.setup import * 4 | 5 | build_epoch = TOOL.build_epoch() 6 | version_dash = TOOL.date_dash(build_epoch) 7 | 8 | machine_name = "arch-http" 9 | machine_home = f"/home/{machine_name}" 10 | 11 | SH(f""" 12 | mkdir -p "{machine_home}" 13 | chown -R http:http "{machine_home}" 14 | """) 15 | 16 | network_face = TOOL.select_interface() 17 | 18 | # 19 | # 20 | # 21 | 22 | IMAGE(f"file://localhost/tmp/nspawn/repo/archlinux/http/{version_dash}.tar.gz") 23 | 24 | MACHINE(machine_name) 25 | 26 | WITH( 27 | MACVLAN=network_face, 28 | ) 29 | 30 | bind_list = [ 31 | (machine_home, '/home/'), 32 | ] 33 | 34 | for source, target in bind_list: 35 | WITH(Bind=f"{source}:{target}") 36 | 37 | WITH(Bind="/root/.ssh/authorized_keys") 38 | -------------------------------------------------------------------------------- /src/verify/image/archux/http/usr/share/webapps/roundcubemail/composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "roundcube/roundcubemail", 3 | "description": "The Roundcube Webmail suite", 4 | "license": "GPL-3.0+", 5 | "repositories": [ 6 | { 7 | "type": "composer", 8 | "url": "https://plugins.roundcube.net/" 9 | } 10 | ], 11 | "require": { 12 | "php": ">=5.4.0", 13 | "pear/pear-core-minimal": "~1.10.1", 14 | "pear/net_socket": "~1.2.1", 15 | "pear/auth_sasl": "~1.1.0", 16 | "pear/net_idna2": "~0.2.0", 17 | "pear/mail_mime": "~1.10.0", 18 | "pear/net_smtp": "~1.7.1", 19 | "pear/crypt_gpg": "~1.6.0", 20 | "pear/net_sieve": "~1.4.0", 21 | "roundcube/plugin-installer": "~0.1.6", 22 | "endroid/qrcode": "~1.6.5" 23 | }, 24 | "require-dev": { 25 | "phpunit/phpunit": "^4.8.36 || ^5.7.21" 26 | }, 27 | "suggest": { 28 | "pear/net_ldap2": "~2.2.0 required for connecting to LDAP", 29 | "kolab/net_ldap3": "~1.0.6 required for connecting to LDAP" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/verify/image/archux/http/usr/share/webapps/roundcubemail/plugins-xxx/arkon/arkon.css: -------------------------------------------------------------------------------- 1 | 2 | /* */ 3 | 4 | #topline, #toplogo { 5 | display: none; 6 | visibility: hidden; 7 | } 8 | -------------------------------------------------------------------------------- /src/verify/image/archux/http/usr/share/webapps/roundcubemail/plugins-xxx/arkon/arkon.php: -------------------------------------------------------------------------------- 1 | output; 11 | $storage = $rcmail->storage; 12 | 13 | if ($rcmail->output->type == 'html') { 14 | $this->include_script('arkon.js'); 15 | $this->include_stylesheet('arkon.css'); 16 | } 17 | 18 | if ($rcmail->task == 'mail') { 19 | // 20 | } 21 | 22 | } 23 | 24 | } 25 | 26 | ?> 27 | -------------------------------------------------------------------------------- /src/verify/image/archux/mail/build.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from nspawn.build import * 4 | 5 | build_epoch = TOOL.build_epoch() 6 | version_dash = TOOL.date_dash(build_epoch) 7 | 8 | getmail_src = 'getmail-5.14' 9 | getmail_dst = '/opt/getmail' 10 | getmail_url = f"http://pyropus.ca/software/getmail/old-versions/{getmail_src}.tar.gz" 11 | 12 | IMAGE(f"file://localhost/tmp/nspawn/repo/archux/mail/{version_dash}.tar.gz") 13 | 14 | PULL(f"file://localhost/tmp/nspawn/repo/archux/base/{version_dash}.tar.gz") 15 | 16 | SH("pacman --sync --needed --noconfirm " 17 | "python2 " 18 | "postfix " 19 | ) 20 | 21 | COPY("/etc") 22 | 23 | FETCH(url=getmail_url, source=getmail_src, target=getmail_dst) 24 | 25 | SH(f""" 26 | cd {getmail_dst} 27 | python2 setup.py install 28 | """) 29 | 30 | PUSH() 31 | -------------------------------------------------------------------------------- /src/verify/image/archux/mail/etc/readme.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/random-python/nspawn/305268430526a17ce8e3e96e5f2f501cecdb6d92/src/verify/image/archux/mail/etc/readme.md -------------------------------------------------------------------------------- /src/verify/image/archux/mail/setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from nspawn.setup import * 4 | 5 | build_epoch = TOOL.build_epoch() 6 | version_dash = TOOL.date_dash(build_epoch) 7 | 8 | machine_name = "arch-http" 9 | machine_home = f"/home/{machine_name}" 10 | 11 | SH(f""" 12 | mkdir -p "{machine_home}" 13 | chown -R http:http "{machine_home}" 14 | """) 15 | 16 | network_face = TOOL.select_interface() 17 | 18 | # 19 | # 20 | # 21 | 22 | IMAGE(f"file://localhost/tmp/nspawn/repo/archlinux/http/{version_dash}.tar.gz") 23 | 24 | MACHINE(machine_name) 25 | 26 | WITH( 27 | MACVLAN=network_face, 28 | ) 29 | 30 | bind_list = [ 31 | (machine_home, '/home/'), 32 | ] 33 | 34 | for source, target in bind_list: 35 | WITH(Bind=f"{source}:{target}") 36 | 37 | WITH(Bind="/root/.ssh/authorized_keys") 38 | -------------------------------------------------------------------------------- /src/verify/image/archux/mail/unsetup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import os 4 | 5 | this_dir = os.path.dirname(os.path.abspath(__file__)) 6 | 7 | os.system(f"{this_dir}/setup.py --action desure") 8 | -------------------------------------------------------------------------------- /src/verify/image/hatcher-ensure.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/random-python/nspawn/305268430526a17ce8e3e96e5f2f501cecdb6d92/src/verify/image/hatcher-ensure.py -------------------------------------------------------------------------------- /src/verify/image/ubuntu/base/a.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | import subprocess 4 | 5 | 6 | def has_import(): 7 | try: 8 | import nspawn 9 | return True 10 | except ImportError: 11 | return False 12 | 13 | 14 | def ensure_import(): 15 | if has_import(): 16 | return 17 | try: 18 | command = ['git', 'rev-parse', '--show-toplevel'] 19 | project = subprocess.check_output(command).decode('utf-8').strip() 20 | path_main = f"{project}/src/main" 21 | path_test = f"{project}/src/test" 22 | sys.path.insert(0, path_main) 23 | sys.path.insert(0, path_test) 24 | except Exception as error: 25 | sys.exit(f"Development error: {str(error)}") 26 | 27 | 28 | ensure_import() 29 | -------------------------------------------------------------------------------- /src/verify/image/ubuntu/base/build.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import os, runpy 4 | this_dir = os.path.dirname(os.path.abspath(__file__)) 5 | runpy.run_path(f"{this_dir}/a.py") 6 | 7 | from nspawn.build import * 8 | 9 | name = "bionic" 10 | version = "18.04" 11 | image_url = f"file://localhost/tmp/nspawn/repo/ubuntu/base/{name}-{version}.tar.gz" 12 | booter_url = f"https://cloud-images.ubuntu.com/minimal/releases/{name}/release/ubuntu-{version}-minimal-cloudimg-amd64-root.tar.xz" 13 | 14 | # declare image identity 15 | IMAGE(image_url) 16 | 17 | # provision dependency image 18 | PULL(booter_url) 19 | 20 | # configure container profile 21 | WITH( 22 | Boot="yes", # auto-find image init program 23 | Quiet="yes", # suppress "press to escape" message 24 | KeepUnit="yes", # use service unit as nspawn scope 25 | Register="yes", # expose service unit with machinectl 26 | ) 27 | 28 | # copy local resources 29 | COPY("/etc") 30 | COPY(path="/root") 31 | 32 | SH("rm -rf /etc/resolv.conf /etc/securetty") 33 | 34 | SH("apt-get update") 35 | SH("apt-get install -y mc htop") 36 | SH("apt-get install -y iputils-ping iproute2") 37 | SH("apt-get install -y openssh-server") 38 | SH("apt-get purge -y unattended-upgrades") 39 | SH("systemctl disable networkd-dispatcher") 40 | SH("systemctl enable systemd-networkd") 41 | SH("systemctl enable systemd-resolved") 42 | SH("systemctl enable ssh") 43 | 44 | # publish image 45 | PUSH() 46 | -------------------------------------------------------------------------------- /src/verify/image/ubuntu/base/etc/nsswitch.conf: -------------------------------------------------------------------------------- 1 | # Begin /etc/nsswitch.conf 2 | 3 | passwd: compat mymachines systemd 4 | group: compat mymachines systemd 5 | shadow: compat 6 | 7 | publickey: files 8 | 9 | #hosts: files mymachines resolve [!UNAVAIL=return] dns myhostname 10 | #hosts: files mymachines resolve dns myhostname 11 | hosts: files mymachines dns resolve myhostname 12 | networks: files 13 | 14 | protocols: files 15 | services: files 16 | ethers: files 17 | rpc: files 18 | 19 | netgroup: files 20 | 21 | # End /etc/nsswitch.conf 22 | -------------------------------------------------------------------------------- /src/verify/image/ubuntu/base/etc/systemd/network/macvlan.network: -------------------------------------------------------------------------------- 1 | 2 | # internal network 3 | 4 | [Match] 5 | Name=mv-* 6 | 7 | [Network] 8 | DHCP=ipv4 9 | IPForward=ipv4 10 | LinkLocalAddressing=no 11 | -------------------------------------------------------------------------------- /src/verify/image/ubuntu/base/root/.config/htop/htoprc: -------------------------------------------------------------------------------- 1 | # Beware! This file is rewritten by htop when settings are changed in the interface. 2 | # The parser is also very primitive, and not human-friendly. 3 | fields=0 48 17 18 38 39 40 2 46 47 49 1 4 | sort_key=46 5 | sort_direction=1 6 | hide_threads=0 7 | hide_kernel_threads=1 8 | hide_userland_threads=0 9 | shadow_other_users=0 10 | show_thread_names=0 11 | show_program_path=1 12 | highlight_base_name=0 13 | highlight_megabytes=1 14 | highlight_threads=1 15 | tree_view=1 16 | header_margin=1 17 | detailed_cpu_time=0 18 | cpu_count_from_zero=0 19 | update_process_names=0 20 | account_guest_in_cpu_meter=0 21 | color_scheme=0 22 | delay=15 23 | left_meters=LeftCPUs2 Memory Swap 24 | left_meter_modes=1 1 1 25 | right_meters=RightCPUs2 Tasks LoadAverage Uptime 26 | right_meter_modes=1 2 2 2 27 | -------------------------------------------------------------------------------- /src/verify/image/ubuntu/base/root/.config/mc/panels.ini: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/random-python/nspawn/305268430526a17ce8e3e96e5f2f501cecdb6d92/src/verify/image/ubuntu/base/root/.config/mc/panels.ini -------------------------------------------------------------------------------- /src/verify/image/ubuntu/base/setup-desure.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import os 4 | 5 | this_dir = os.path.dirname(os.path.abspath(__file__)) 6 | 7 | os.system(f"{this_dir}/setup.py --action desure") 8 | -------------------------------------------------------------------------------- /src/verify/image/ubuntu/base/setup-ensure.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import os 4 | 5 | this_dir = os.path.dirname(os.path.abspath(__file__)) 6 | 7 | os.system(f"{this_dir}/setup.py --action ensure") 8 | -------------------------------------------------------------------------------- /src/verify/image/ubuntu/base/setup-update.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import os 4 | 5 | this_dir = os.path.dirname(os.path.abspath(__file__)) 6 | 7 | os.system(f"{this_dir}/setup.py --action update") 8 | -------------------------------------------------------------------------------- /src/verify/image/ubuntu/base/setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import os, runpy 4 | this_dir = os.path.dirname(os.path.abspath(__file__)) 5 | runpy.run_path(f"{this_dir}/a.py") 6 | 7 | from nspawn.setup import * 8 | 9 | name = "bionic" 10 | version = "18.04" 11 | image_url = f"file://localhost/tmp/nspawn/repo/ubuntu/base/{name}-{version}.tar.gz" 12 | booter_url = f"https://cloud-images.ubuntu.com/minimal/releases/{name}/release/ubuntu-{version}-minimal-cloudimg-amd64-root.tar.xz" 13 | 14 | network_face = TOOL.select_interface() 15 | 16 | # declare image identity 17 | IMAGE(image_url) 18 | 19 | MACHINE(name=f"ubun-base") 20 | 21 | WITH( 22 | # Hostname="ubunase", # needs systemd v 239 23 | MACVLAN=network_face, 24 | ) 25 | -------------------------------------------------------------------------------- /src/verify/library/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/random-python/nspawn/305268430526a17ce8e3e96e5f2f501cecdb6d92/src/verify/library/__init__.py -------------------------------------------------------------------------------- /src/verify/library/app/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/random-python/nspawn/305268430526a17ce8e3e96e5f2f501cecdb6d92/src/verify/library/app/__init__.py -------------------------------------------------------------------------------- /src/verify/library/app/arkon.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | import errno 4 | 5 | master_key = "NSPAWN_MASTER_PROCESS" 6 | 7 | master_main = os.path.abspath(__file__) 8 | 9 | 10 | def invoke_main(): 11 | try: 12 | args = [sys.executable] + [master_main] + sys.argv 13 | print(args) 14 | os.execlp(args[0], *args) 15 | except OSError as e: 16 | sys.exit('Invoker failure') 17 | 18 | 19 | def master_ensure(): 20 | 21 | master_value = os.environ.get(master_key) 22 | print(f"master_ensure={master_value}") 23 | 24 | if master_value: 25 | print("YES") 26 | else: 27 | print("NO") 28 | os.environ[master_key] = 'MASTER' 29 | invoke_main() 30 | 31 | 32 | master_ensure() 33 | -------------------------------------------------------------------------------- /src/verify/library/app/platform_verify.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import platform 4 | 5 | print(f"platform={platform.platform()}") 6 | 7 | print(f"system={platform.system()}") 8 | 9 | print(f"architecture={platform.architecture()}") 10 | 11 | print(f"machine={platform.machine()}") 12 | -------------------------------------------------------------------------------- /src/verify/library/aws/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/random-python/nspawn/305268430526a17ce8e3e96e5f2f501cecdb6d92/src/verify/library/aws/__init__.py -------------------------------------------------------------------------------- /src/verify/library/aws/store/dir1/dir2/file-1.md: -------------------------------------------------------------------------------- 1 | 2 | file-1 3 | file-1 4 | file-1 5 | -------------------------------------------------------------------------------- /src/verify/library/aws/store/dir2/dir3/file-2.md: -------------------------------------------------------------------------------- 1 | 2 | file-2 3 | file-2 4 | -------------------------------------------------------------------------------- /src/verify/library/overlayfs/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/random-python/nspawn/305268430526a17ce8e3e96e5f2f501cecdb6d92/src/verify/library/overlayfs/__init__.py -------------------------------------------------------------------------------- /src/verify/library/overlayfs/a-dir/a-dir.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/random-python/nspawn/305268430526a17ce8e3e96e5f2f501cecdb6d92/src/verify/library/overlayfs/a-dir/a-dir.md -------------------------------------------------------------------------------- /src/verify/library/overlayfs/dir-1/systemd/file-1.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/random-python/nspawn/305268430526a17ce8e3e96e5f2f501cecdb6d92/src/verify/library/overlayfs/dir-1/systemd/file-1.md -------------------------------------------------------------------------------- /src/verify/library/overlayfs/dir-2/systemd/file-2.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/random-python/nspawn/305268430526a17ce8e3e96e5f2f501cecdb6d92/src/verify/library/overlayfs/dir-2/systemd/file-2.md -------------------------------------------------------------------------------- /src/verify/library/overlayfs/dir-3/systemd/file-3.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/random-python/nspawn/305268430526a17ce8e3e96e5f2f501cecdb6d92/src/verify/library/overlayfs/dir-3/systemd/file-3.md -------------------------------------------------------------------------------- /src/verify/library/overlayfs/main.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | this_dir = os.path.dirname(os.path.abspath(__file__)) 4 | 5 | 6 | def invoke_main(): 7 | try: 8 | print(f"this_dir={this_dir}") 9 | 10 | dir_1 = f"{this_dir}/dir-1" 11 | dir_2 = f"{this_dir}/dir-2" 12 | dir_3 = f"{this_dir}/dir-3" 13 | work = f"{this_dir}/work" 14 | root = f"{this_dir}/root" 15 | result = f"{this_dir}/a-dir" 16 | 17 | options = f"lowerdir={dir_3}:{dir_2}:{dir_1},upperdir={root},workdir={work}" 18 | 19 | mount_cmd = f"mount -v -t overlay -o {options} overlay {result}" 20 | report_cmd = f"ls -lasR {result}" 21 | umount_cmd = f"umount -v {result}" 22 | 23 | os.system(f"sudo {mount_cmd}") 24 | os.system(f"sudo {report_cmd}") 25 | os.system(f"sudo {umount_cmd}") 26 | 27 | except Exception as error: 28 | print(f"failure: {error}") 29 | 30 | 31 | invoke_main() 32 | -------------------------------------------------------------------------------- /src/verify/library/overlayfs/root/systemd/root.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/random-python/nspawn/305268430526a17ce8e3e96e5f2f501cecdb6d92/src/verify/library/overlayfs/root/systemd/root.md -------------------------------------------------------------------------------- /src/verify/library/overlayfs/work/work.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/random-python/nspawn/305268430526a17ce8e3e96e5f2f501cecdb6d92/src/verify/library/overlayfs/work/work.md -------------------------------------------------------------------------------- /src/verify/library/so20094215.py: -------------------------------------------------------------------------------- 1 | 2 | def define_parser(): 3 | import argparse 4 | parser = argparse.ArgumentParser( 5 | prog='main', 6 | formatter_class=argparse.RawDescriptionHelpFormatter, 7 | ) 8 | commands = parser.add_subparsers( 9 | title="required commands", 10 | help='Select one of:', 11 | ) 12 | command_list = commands.add_parser( 13 | 'list', 14 | help='List included services', 15 | ) 16 | command_ensure = commands.add_parser( 17 | 'ensure', 18 | help='Provision included service', 19 | ) 20 | command_ensure.add_argument( 21 | "service", 22 | help='Service name', 23 | ) 24 | import textwrap 25 | parser.epilog = textwrap.dedent( 26 | f"""\ 27 | commands usage:\n 28 | {command_list.format_usage()} 29 | {command_ensure.format_usage()} 30 | """ 31 | ) 32 | return parser 33 | 34 | parser = define_parser() 35 | 36 | parser.print_help() 37 | -------------------------------------------------------------------------------- /src/verify/library/syncer/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/random-python/nspawn/305268430526a17ce8e3e96e5f2f501cecdb6d92/src/verify/library/syncer/__init__.py -------------------------------------------------------------------------------- /src/verify/library/syncer/store/readme.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/random-python/nspawn/305268430526a17ce8e3e96e5f2f501cecdb6d92/src/verify/library/syncer/store/readme.md -------------------------------------------------------------------------------- /src/verify/library/syncer/sync.ini: -------------------------------------------------------------------------------- 1 | 2 | [bucket] 3 | 4 | service_store_dir = ./store 5 | service_bucket_name = image-server 6 | service_bucket_mode = public-read 7 | service_sync_config = 8 | service_include_list = *.md 9 | service_exclude_list = 10 | service_use_expire = true 11 | service_expire_days = 5 12 | -------------------------------------------------------------------------------- /src/verify/library/syncer/verify.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import os 4 | import time 5 | 6 | from library.syncer.sync import * 7 | 8 | resource_one = "./store/readme.md" 9 | 10 | accesstime, modifiedtime = time.time(), time.time() - 86400 * 3 11 | 12 | os.utime(resource_one, (accesstime, modifiedtime)) 13 | 14 | invoke_module() 15 | -------------------------------------------------------------------------------- /src/verify/mount_lowdir_plus.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # 4 | # verify option="lowerdir+":" 5 | # https://docs.kernel.org/filesystems/overlayfs.html 6 | # 7 | 8 | lower1="/var/lib/nspawn/extract/localhost//var/lib/nspawn/tempdir/provision/alpine/image-server/default-3.11.3-x86_64.tar.gz/" 9 | lower2="/var/lib/nspawn/extract/dl-cdn.alpinelinux.org/alpine/v3.11/releases/x86_64/alpine-minirootfs-3.11.3-x86_64.tar.gz/" 10 | lower3="/var/lib/nspawn/runtime/nspawn-image-server/zero" 11 | upper0="/var/lib/nspawn/runtime/nspawn-image-server/root" 12 | work0="/var/lib/nspawn/runtime/nspawn-image-server/work" 13 | base0="/var/lib/machines/nspawn-image-server" 14 | 15 | #sudo ls -las $base0 16 | #sudo ls -las $wor0 17 | #sudo ls -las $upper0 18 | #sudo ls -las $lower1 19 | #sudo ls -las $lower2 20 | #sudo ls -las $lower3 21 | 22 | sudo umount $base0 23 | 24 | #sudo sync 25 | 26 | sudo /usr/bin/mount \ 27 | -t overlay \ 28 | -o xino=on \ 29 | -o index=off \ 30 | -o metacopy=off \ 31 | -o lowerdir+=$lower1 \ 32 | -o lowerdir+=$lower2 \ 33 | -o lowerdir+=$lower3 \ 34 | -o upperdir=$upper0 \ 35 | -o workdir=$work0 \ 36 | overlay $base0 37 | 38 | # 39 | # 40 | # 41 | -------------------------------------------------------------------------------- /src/verify/readme.md: -------------------------------------------------------------------------------- 1 | 2 | ### project integration tests 3 | -------------------------------------------------------------------------------- /tool/arkon.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e -u 4 | 5 | readonly base_dir=$( cd $( dirname "$0" )/.. && pwd ) 6 | 7 | readonly pyenv_dir=$base_dir/.pyenv 8 | 9 | cd $base_dir 10 | [ -d $pyenv_dir ] && source $pyenv_dir/bin/activate 11 | 12 | echo "### base_dir=$base_dir" 13 | echo "### pyenv_dir=$pyenv_dir" 14 | -------------------------------------------------------------------------------- /tool/github_advance.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | # 4 | # dependency: 5 | # * python-sh 6 | # 7 | 8 | import datetime 9 | import logging 10 | from sh import git 11 | from sh import contrib 12 | 13 | logging.basicConfig(level=logging.INFO) 14 | 15 | 16 | instant = datetime.datetime.now().isoformat() 17 | message = f"develop {instant}" 18 | 19 | git("add", "--all") 20 | git("commit", "--quiet", f"--message='{message}'") 21 | git(f"push") 22 | 23 | # 24 | # 25 | # 26 | -------------------------------------------------------------------------------- /tool/github_squash.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | """ 4 | Squash github commits starting from a point 5 | """ 6 | 7 | from devrepo import shell 8 | 9 | point = "d15a7bb70647828e42ad5da79379b8239e0ffadd" 10 | message = "develop" 11 | 12 | shell(f"git reset --soft {point}") 13 | shell(f"git add --all") 14 | shell(f"git commit --message='{message}'") 15 | shell(f"git push --force --follow-tags") 16 | 17 | # 18 | # 19 | # 20 | -------------------------------------------------------------------------------- /tool/install_local.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | """ 4 | Use local install for manual testing 5 | """ 6 | 7 | from devrepo import base_dir 8 | from devrepo import shell 9 | 10 | project_dir = base_dir() 11 | 12 | shell("sudo python setup.py install") 13 | shell("sudo rm -rf build") 14 | -------------------------------------------------------------------------------- /tool/invoke_release.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | """ 4 | PyPi release 5 | """ 6 | 7 | # dependency: 8 | # * https://archlinux.org/packages/extra/any/python-build/ 9 | # * https://archlinux.org/packages/extra/any/twine/ 10 | # * $HOME/.pypirc 11 | 12 | import os 13 | 14 | project_dir = "./.." # base_dir() 15 | 16 | os.chdir(project_dir) 17 | os.system("rm -rf dist/") 18 | os.system("python -m build --wheel") # keep order 19 | os.system("twine upload dist/*") 20 | 21 | # 22 | # 23 | # 24 | -------------------------------------------------------------------------------- /tool/perform_tox.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # 4 | # 5 | # 6 | 7 | source ./arkon.sh 8 | 9 | tox 10 | -------------------------------------------------------------------------------- /tool/provision_env.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # 4 | # 5 | # 6 | 7 | source ./arkon.sh 8 | 9 | sudo rm -rf $pyenv_dir 10 | mkdir -p $pyenv_dir 11 | 12 | python3 -m venv $pyenv_dir 13 | 14 | source $pyenv_dir/bin/activate 15 | 16 | pip install --upgrade pip wheel setuptools 17 | 18 | pip install -r requirements.txt 19 | pip install -r requirements-dev.txt 20 | -------------------------------------------------------------------------------- /tool/readme.md: -------------------------------------------------------------------------------- 1 | 2 | ### development support 3 | -------------------------------------------------------------------------------- /tool/tox_verify.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | """ 4 | CI tox integration tests 5 | """ 6 | 7 | from devrepo import shell 8 | 9 | shell(f"nspawn-hatch list") 10 | shell(f"nspawn-hatch update image-server") 11 | shell(f"cat /etc/systemd/system/nspawn-image-server.service") 12 | shell(f"systemctl --no-pager status nspawn-image-server.service") 13 | 14 | shell(f"nspawn-enter nspawn-image-server 'uname -a'") 15 | 16 | shell(f"demo/alpine/base/build.py") 17 | shell(f"demo/alpine/base/setup.py") 18 | shell(f"cat /etc/systemd/system/alpine-base.service") 19 | shell(f"systemctl --no-pager status alpine-base.service") 20 | 21 | # TODO archux site is slow 22 | # shell(f"demo/archux/base/build.py") 23 | # shell(f"demo/archux/base/setup.py") 24 | # shell(f"cat /etc/systemd/system/archux-base.service") 25 | # shell(f"systemctl --no-pager status archux-base.service") 26 | 27 | shell(f"demo/ubuntu/base/build.py") 28 | shell(f"demo/ubuntu/base/setup.py") 29 | shell(f"cat /etc/systemd/system/ubuntu-base.service") 30 | shell(f"systemctl --no-pager status ubuntu-base.service") 31 | 32 | shell(f"machinectl --no-pager") 33 | 34 | shell(f"nspawn-hatch desure image-server") 35 | shell(f"demo/alpine/base/setup.py --action=desure") 36 | shell(f"demo/archux/base/setup.py --action=desure") 37 | shell(f"demo/ubuntu/base/setup.py --action=desure") 38 | 39 | shell(f"machinectl --no-pager") 40 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | 2 | [tox] 3 | 4 | envlist = 5 | py313 6 | 7 | [testenv] 8 | 9 | passenv = HOME 10 | 11 | deps = 12 | pytest 13 | devrepo 14 | 15 | commands = 16 | pytest 17 | python tool/tox_verify.py 18 | 19 | [pytest] 20 | 21 | testpaths = 22 | src/test 23 | 24 | python_files = 25 | *_test.py 26 | 27 | python_functions = 28 | test_* 29 | --------------------------------------------------------------------------------