Skip to content
Snippets Groups Projects
Commit 07b1a6d4 authored by Michael Loipführer's avatar Michael Loipführer
Browse files

management script

parent ac74ce39
No related branches found
No related tags found
No related merge requests found
*.env
__pycache__
containers.json
# Der Lustmolch
Allgemeiner container host für (semi-offizielle) Stusta websites.
## How-To
Alle management Befehle laufen entweder über `lustmolch.py` oder über `machinectl`.
### Container erstellen
```bash
python3 lustmolch.py create-container <container-name>
```
Der container wird als basic debian image in `/var/lib/machines` angelegt und `bootstrap.sh` wird ausgeführt.
Dabei werden nötige Pakete installiert, alle Konfigurationsfiles sowohl auf dem host als auch im Container abgelegt.
Außerdem wird im container **openssh-server** installiert und gestartet. Der Port wird dynamisch auf den ersten
freien Port ab **10022** in Inkrementen von **1000** gesetzt.
Die templates für Konfigurationsfiles liegen im directory **container**.
### Container VERNICHTEN
```bash
python3 lustmolch.py remove-container <container-name>
```
Der Container und alle Konfigurationsfiles auf dem Host werden gelöscht.
### SSH Key installieren
```bash
python3 lustmolch.py install-ssh-key <container-name> <path-to-ssh-key>
```
Der angegeben SSH key wird in `/root/.ssh/authorized_keys` kopiert. Es ist möglich den key als String-Parameter
zu übergeben, dabei muss das Flag `--key-string` gesetzt sein.
\ No newline at end of file
#!/usr/bin/env bash
apt-get update
apt-get -y upgrade
apt-get -y install dbus openssh-server vim
\ No newline at end of file
server {
listen 80;
server_name lustmolch.stusta.de;
server_name {{name}}.stusta.de;
charset utf-8;
location / {
alias /var/www/html;
location /static {
alias /var/www/{{name}};
}
}
}
\ No newline at end of file
[Network]
VirtualEthernet=no
[Files]
Bind=/var/www/{{name}}:/var/www
[Exec]
PrivateUsers=off
\ No newline at end of file
# $OpenBSD: sshd_config,v 1.103 2018/04/09 20:41:22 tj Exp $
# This is the sshd server system-wide configuration file. See
# sshd_config(5) for more information.
# This sshd was compiled with PATH=/usr/bin:/bin:/usr/sbin:/sbin
# The strategy used for options in the default sshd_config shipped with
# OpenSSH is to specify options with their default value where
# possible, but leave them commented. Uncommented options override the
# default value.
Port {{ssh_port}}
#AddressFamily any
#ListenAddress 0.0.0.0
#ListenAddress ::
#HostKey /etc/ssh/ssh_host_rsa_key
#HostKey /etc/ssh/ssh_host_ecdsa_key
#HostKey /etc/ssh/ssh_host_ed25519_key
# Ciphers and keying
#RekeyLimit default none
# Logging
#SyslogFacility AUTH
#LogLevel INFO
# Authentication:
#LoginGraceTime 2m
PermitRootLogin prohibit-password
#StrictModes yes
#MaxAuthTries 6
#MaxSessions 10
PubkeyAuthentication yes
# Expect .ssh/authorized_keys2 to be disregarded by default in future.
#AuthorizedKeysFile .ssh/authorized_keys .ssh/authorized_keys2
#AuthorizedPrincipalsFile none
#AuthorizedKeysCommand none
#AuthorizedKeysCommandUser nobody
# For this to work you will also need host keys in /etc/ssh/ssh_known_hosts
#HostbasedAuthentication no
# Change to yes if you don't trust ~/.ssh/known_hosts for
# HostbasedAuthentication
#IgnoreUserKnownHosts no
# Don't read the user's ~/.rhosts and ~/.shosts files
#IgnoreRhosts yes
# To disable tunneled clear text passwords, change to no here!
#PasswordAuthentication yes
#PermitEmptyPasswords no
# Change to yes to enable challenge-response passwords (beware issues with
# some PAM modules and threads)
ChallengeResponseAuthentication no
# Kerberos options
#KerberosAuthentication no
#KerberosOrLocalPasswd yes
#KerberosTicketCleanup yes
#KerberosGetAFSToken no
# GSSAPI options
#GSSAPIAuthentication no
#GSSAPICleanupCredentials yes
#GSSAPIStrictAcceptorCheck yes
#GSSAPIKeyExchange no
# Set this to 'yes' to enable PAM authentication, account processing,
# and session processing. If this is enabled, PAM authentication will
# be allowed through the ChallengeResponseAuthentication and
# PasswordAuthentication. Depending on your PAM configuration,
# PAM authentication via ChallengeResponseAuthentication may bypass
# the setting of "PermitRootLogin without-password".
# If you just want the PAM account and session checks to run without
# PAM authentication, then enable this but set PasswordAuthentication
# and ChallengeResponseAuthentication to 'no'.
UsePAM yes
#AllowAgentForwarding yes
#AllowTcpForwarding yes
#GatewayPorts no
X11Forwarding yes
#X11DisplayOffset 10
#X11UseLocalhost yes
#PermitTTY yes
PrintMotd no
#PrintLastLog yes
#TCPKeepAlive yes
#PermitUserEnvironment no
#Compression delayed
#ClientAliveInterval 0
#ClientAliveCountMax 3
#UseDNS no
#PidFile /var/run/sshd.pid
#MaxStartups 10:30:100
#PermitTunnel no
#ChrootDirectory none
#VersionAddendum none
# no default banner path
#Banner none
# Allow client to pass locale environment variables
AcceptEnv LANG LC_*
# override default of no subsystems
Subsystem sftp /usr/lib/openssh/sftp-server
# Example of overriding settings on a per-user basis
#Match User anoncvs
# X11Forwarding no
# AllowTcpForwarding no
# PermitTTY no
# ForceCommand cvs server
#!/usr/bin/env python3
import json
import shutil
from collections import namedtuple
from subprocess import run
from pathlib import Path
from jinja2 import Environment, PackageLoader
import click
env = Environment(loader=PackageLoader('lustmolch', 'container'))
cfg_template = namedtuple('cfg_template', ['source', 'path', 'filename'])
template_files_host = [
cfg_template('nginx', Path('/etc/nginx/sites-available'), '{name}'),
cfg_template('nspawn', Path('/etc/systemd/nspawn'), '{name}.nspawn')
]
template_files_container = [
cfg_template('sshd_config', Path('/etc/ssh'), 'sshd_config')
]
FLAVOUR = 'buster'
DEBIAN_MIRROR = 'http://mirror.stusta.de/debian'
www_root = Path('/var/www')
SSH_START_PORT = 10022
SSH_PORT_INCREMENT = 1000
def next_ssh_port(config_file, name):
"""
Return the next available port for the containers ssh server to run on.
If the container is already present in the list of installed containers
returns the configured port.
Args:
config_file: Path to container configuration file (containers.json)
name: Container name
Returns: SSH port
"""
if not Path(config_file).exists():
cfg = {}
else:
with open(config_file, 'r') as f:
cfg = json.load(f)
if name in cfg:
return cfg.get(name).get('ssh_port')
port = SSH_START_PORT
for container in cfg.items():
if container.get('ssh_port') >= port:
port = container.get('ssh_port') + SSH_PORT_INCREMENT
return port
def update_config(config_file, name, container):
if not Path(config_file).exists():
with open(config_file, 'w+') as f:
cfg = {name: container}
json.dump(cfg, f, indent=4)
else:
with open(config_file, 'r') as f:
cfg = json.load(f)
cfg[name] = container
with open(config_file, 'w') as f:
json.dump(cfg, f, indent=4)
@click.group()
def cli():
pass
@cli.command()
@click.option('--dry-run', is_flag=True, default=False)
@click.option('--config-file', default='containers.json', help='Container configuration file')
@click.argument('name')
def create_container(dry_run, config_file, name):
if dry_run:
click.echo(f'Doing a dry run')
# create shared folder for html static files
www_dir = www_root / name
click.echo(f'Creating shared www directory "{www_dir}"')
if not dry_run:
www_dir.mkdir(parents=True, exist_ok=True)
# place configuration files
context = {
'name': name,
'ssh_port': next_ssh_port(config_file, name)
}
for cfg in template_files_host:
template = env.get_template(cfg.source)
file_name = cfg.path / (cfg.filename.format(**context))
click.echo(f'Placing config file {file_name}')
if not dry_run:
with open(file_name, 'w+') as cfg_file:
cfg_file.write(template.render(context))
# create machine
machine_path = Path('/var/lib/machines', name)
click.echo(f'Running debootstrap')
if not dry_run:
run(['debootstrap', FLAVOUR, machine_path, DEBIAN_MIRROR], capture_output=True, check=True)
# start container for the first time
# click.echo(f'Starting container for the first time')
# if not dry_run:
# run(['systemd-nspawn', '-D', machine_path], check=True)
click.echo(f'Bootstrapping container')
if not dry_run:
# copy and run bootstrap shell script
script_location = '/opt/bootstrap.sh'
script_location_host = str(machine_path) + script_location
shutil.copy('container/bootstrap.sh', script_location_host)
Path(script_location_host).chmod(0o755)
run(['systemd-nspawn', '-D', str(machine_path), script_location], check=True)
click.echo(f'Copying config files into container')
if not dry_run:
for cfg in template_files_container:
template = env.get_template(cfg.source)
file_name = cfg.filename.format(**context)
click.echo(f'Placing config file {file_name}')
if not dry_run:
with open(Path(f'{machine_path}{cfg.path}/{file_name}'), 'w+') as f:
f.write(template.render(context))
click.echo(f'Starting container')
if not dry_run:
run(['machinectl', 'start', name], capture_output=True, check=True)
click.echo(f'Updating container configuration file')
if not dry_run:
update_config(config_file, name, container=context)
click.echo(f'All done, ssh server running on port {context["ssh_port"]}')
@cli.command()
@click.option('--config-file', default='containers.json', help='Container configuration file')
@click.option('--key-string', is_flag=True, default=False)
@click.argument('name')
@click.argument('key')
def install_ssh_key(config_file, key_string, name, key):
ssh_dir = Path('/var/lib/machines', name, 'root/.ssh')
authorized_keys = ssh_dir / 'authorized_keys'
if key_string:
key_string = key
else:
with open(key, 'r') as f:
key_string = f.read()
click.echo(f'Appending ssh key\n{key_string} to {authorized_keys}')
ssh_dir.mkdir(mode=0o700, parents=True, exist_ok=True)
with open(authorized_keys, 'a+') as f:
f.write(key_string)
authorized_keys.chmod(0o600)
@cli.command()
@click.option('--config-file', default='containers.json', help='Container configuration file')
@click.argument('name')
def remove_container(config_file, name):
machine_path = Path('/var/lib/machines', name)
# removing shared folder
www_dir = www_root / name
click.echo(f'Removing shared www folder')
try:
shutil.rmtree(www_dir, ignore_errors=True)
except OSError as e:
click.echo(f'{e} ignored when removing container')
# deleting placed config files
for cfg in template_files_host:
file_name = cfg.path / cfg.filename.format(name=name)
click.echo(f'Removing config file {file_name}')
try:
file_name.unlink()
except OSError as e:
click.echo(f'{e} ignored when removing file {file_name}')
# delete container itself
click.echo(f'Removing container')
try:
shutil.rmtree(machine_path, ignore_errors=True)
except OSError as e:
click.echo(f'{e} ignored when removing container')
# remove container from configuration file
click.echo(f'Updating configuration file')
try:
with open(config_file, 'r') as f:
cfg = json.load(f)
if name in cfg:
del cfg[name]
with open(config_file, 'w') as f:
json.dump(cfg, f, indent=4)
except OSError as e:
click.echo(f'{e} ignored when updating config file')
if __name__ == '__main__':
cli()
click >= 7.0
jinja2 >= 2.10
\ No newline at end of file
server {
listen 80;
server_name pot.stusta.de;
charset utf-8;
location / {
proxy_pass http://localhost:8000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Lustmolch</title>
</head>
<body>
<h1>THIS IS LUSTMOLCH</h1>
</body>
</html>
\ No newline at end of file
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment