malvbox 1.0.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- malvbox-1.0.0/.gitignore +4 -0
- malvbox-1.0.0/PKG-INFO +53 -0
- malvbox-1.0.0/README.md +44 -0
- malvbox-1.0.0/build.sh +23 -0
- malvbox-1.0.0/pyproject.toml +22 -0
- malvbox-1.0.0/src/malvbox/__init__.py +1 -0
- malvbox-1.0.0/src/malvbox/cli.py +20 -0
- malvbox-1.0.0/src/malvbox/malvbox.sh +234 -0
malvbox-1.0.0/.gitignore
ADDED
malvbox-1.0.0/PKG-INFO
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: malvbox
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Exegol-like offensive container CLI wired to the malveillance platform VPN
|
|
5
|
+
License-Expression: MIT
|
|
6
|
+
Keywords: docker,exegol,offensive-security,pentest,wireguard
|
|
7
|
+
Requires-Python: >=3.8
|
|
8
|
+
Description-Content-Type: text/markdown
|
|
9
|
+
|
|
10
|
+
# malvbox (wrapper installable)
|
|
11
|
+
|
|
12
|
+
Distribue la commande `malvbox` **comme Exegol** : via `pipx`, sans que
|
|
13
|
+
l'utilisateur ne manipule un fichier script.
|
|
14
|
+
|
|
15
|
+
## Pour l'utilisateur final
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
pipx install malvbox # une fois publié sur PyPI
|
|
19
|
+
malvbox start redteam --vpn ~/malveillance.conf -s ~/partage
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Prérequis : `docker`, `bash` (déjà présents sous Linux/WSL/macOS), et le module
|
|
23
|
+
noyau `wireguard`. Le paquet n'embarque que le lanceur ; l'arsenal est dans
|
|
24
|
+
l'image Docker `ghcr.io/loic051n7/malvbox:default`, tirée automatiquement.
|
|
25
|
+
|
|
26
|
+
## Pour toi (mainteneur)
|
|
27
|
+
|
|
28
|
+
Le paquet embarque le script bash canonique (`../malvbox`) et l'exécute. Pour
|
|
29
|
+
(re)construire après une modif du script :
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
cd wrapper
|
|
33
|
+
./build.sh # resync + build dans dist/
|
|
34
|
+
./build.sh --publish # + upload PyPI (twine)
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Test local avant publication :
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
pipx install ./wrapper
|
|
41
|
+
malvbox version
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
### Diffusion sans exposer le code source
|
|
45
|
+
|
|
46
|
+
- **PyPI public** (comme Exegol) : `pipx install malvbox`. Le code reste
|
|
47
|
+
téléchargeable (open source), mais l'utilisateur ne gère aucun fichier.
|
|
48
|
+
- **Index privé** (`--repository-url`) ou dépôt Git privé
|
|
49
|
+
(`pipx install git+https://…`) : diffusion restreinte si tu ne veux pas
|
|
50
|
+
rendre la source publique.
|
|
51
|
+
|
|
52
|
+
Dans tous les cas, l'image Docker (l'arsenal) reste sur GHCR et se pull toute
|
|
53
|
+
seule — rien d'autre à télécharger.
|
malvbox-1.0.0/README.md
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# malvbox (wrapper installable)
|
|
2
|
+
|
|
3
|
+
Distribue la commande `malvbox` **comme Exegol** : via `pipx`, sans que
|
|
4
|
+
l'utilisateur ne manipule un fichier script.
|
|
5
|
+
|
|
6
|
+
## Pour l'utilisateur final
|
|
7
|
+
|
|
8
|
+
```bash
|
|
9
|
+
pipx install malvbox # une fois publié sur PyPI
|
|
10
|
+
malvbox start redteam --vpn ~/malveillance.conf -s ~/partage
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Prérequis : `docker`, `bash` (déjà présents sous Linux/WSL/macOS), et le module
|
|
14
|
+
noyau `wireguard`. Le paquet n'embarque que le lanceur ; l'arsenal est dans
|
|
15
|
+
l'image Docker `ghcr.io/loic051n7/malvbox:default`, tirée automatiquement.
|
|
16
|
+
|
|
17
|
+
## Pour toi (mainteneur)
|
|
18
|
+
|
|
19
|
+
Le paquet embarque le script bash canonique (`../malvbox`) et l'exécute. Pour
|
|
20
|
+
(re)construire après une modif du script :
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
cd wrapper
|
|
24
|
+
./build.sh # resync + build dans dist/
|
|
25
|
+
./build.sh --publish # + upload PyPI (twine)
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Test local avant publication :
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
pipx install ./wrapper
|
|
32
|
+
malvbox version
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
### Diffusion sans exposer le code source
|
|
36
|
+
|
|
37
|
+
- **PyPI public** (comme Exegol) : `pipx install malvbox`. Le code reste
|
|
38
|
+
téléchargeable (open source), mais l'utilisateur ne gère aucun fichier.
|
|
39
|
+
- **Index privé** (`--repository-url`) ou dépôt Git privé
|
|
40
|
+
(`pipx install git+https://…`) : diffusion restreinte si tu ne veux pas
|
|
41
|
+
rendre la source publique.
|
|
42
|
+
|
|
43
|
+
Dans tous les cas, l'image Docker (l'arsenal) reste sur GHCR et se pull toute
|
|
44
|
+
seule — rien d'autre à télécharger.
|
malvbox-1.0.0/build.sh
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Resynchronise le script bash canonique puis construit le paquet malvbox.
|
|
3
|
+
# ./build.sh -> copie ../malvbox et build le wheel dans dist/
|
|
4
|
+
# ./build.sh --publish -> build + upload sur PyPI (twine)
|
|
5
|
+
# Utilise un venv local (.buildenv) pour éviter PEP 668 (externally-managed).
|
|
6
|
+
set -euo pipefail
|
|
7
|
+
cd "$(dirname "$0")"
|
|
8
|
+
|
|
9
|
+
cp ../malvbox src/malvbox/malvbox.sh
|
|
10
|
+
echo "✓ script synchronisé ($(wc -l < src/malvbox/malvbox.sh) lignes)"
|
|
11
|
+
|
|
12
|
+
VENV=.buildenv
|
|
13
|
+
[ -d "$VENV" ] || python3 -m venv "$VENV"
|
|
14
|
+
"$VENV/bin/pip" install --quiet --upgrade pip build twine
|
|
15
|
+
|
|
16
|
+
rm -rf dist
|
|
17
|
+
"$VENV/bin/python" -m build
|
|
18
|
+
echo "✓ paquet construit dans dist/"
|
|
19
|
+
|
|
20
|
+
if [ "${1:-}" = "--publish" ]; then
|
|
21
|
+
"$VENV/bin/twine" upload dist/*
|
|
22
|
+
echo "✓ publié"
|
|
23
|
+
fi
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "malvbox"
|
|
7
|
+
version = "1.0.0"
|
|
8
|
+
description = "Exegol-like offensive container CLI wired to the malveillance platform VPN"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.8"
|
|
11
|
+
license = "MIT"
|
|
12
|
+
keywords = ["pentest", "docker", "exegol", "wireguard", "offensive-security"]
|
|
13
|
+
|
|
14
|
+
[project.scripts]
|
|
15
|
+
malvbox = "malvbox.cli:main"
|
|
16
|
+
|
|
17
|
+
[tool.hatch.build.targets.wheel]
|
|
18
|
+
packages = ["src/malvbox"]
|
|
19
|
+
|
|
20
|
+
# Embarque le script bash comme donnée de paquet (le point d'entrée l'exécute).
|
|
21
|
+
[tool.hatch.build.targets.wheel.force-include]
|
|
22
|
+
"src/malvbox/malvbox.sh" = "malvbox/malvbox.sh"
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "1.0.0"
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"""malvbox — thin launcher.
|
|
2
|
+
|
|
3
|
+
The whole CLI lives in the bundled ``malvbox.sh``; this entry point just execs
|
|
4
|
+
it with bash, forwarding all arguments. Packaging it this way lets users install
|
|
5
|
+
the command with ``pipx install malvbox`` (like Exegol) without ever handling a
|
|
6
|
+
raw script file.
|
|
7
|
+
"""
|
|
8
|
+
import os
|
|
9
|
+
import shutil
|
|
10
|
+
import sys
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def main() -> None:
|
|
15
|
+
script = Path(__file__).with_name("malvbox.sh")
|
|
16
|
+
bash = shutil.which("bash")
|
|
17
|
+
if bash is None:
|
|
18
|
+
sys.exit("malvbox: 'bash' is required but was not found in PATH "
|
|
19
|
+
"(on Windows, run malvbox from WSL).")
|
|
20
|
+
os.execv(bash, [bash, str(script), *sys.argv[1:]])
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# ═════════════════════════════════════════════════════════════════════════════
|
|
3
|
+
# malvbox — Exegol-like CLI for the malveillance platform.
|
|
4
|
+
# One image (Kali + Exegol-Free arsenal + wordlists + resources) that wires
|
|
5
|
+
# straight onto the platform WireGuard VPN.
|
|
6
|
+
#
|
|
7
|
+
# malvbox start redteam --vpn ~/malveillance.conf -s ~/share
|
|
8
|
+
# malvbox list | stop <name> | rm <name> [--purge]
|
|
9
|
+
# malvbox pull | build
|
|
10
|
+
# ═════════════════════════════════════════════════════════════════════════════
|
|
11
|
+
set -euo pipefail
|
|
12
|
+
|
|
13
|
+
MALVBOX_VERSION="1.0"
|
|
14
|
+
IMAGE_REPO="${MALVBOX_IMAGE_REPO:-ghcr.io/loic051n7/malvbox}"
|
|
15
|
+
image_ref() { [ -n "${MALVBOX_IMAGE:-}" ] && echo "$MALVBOX_IMAGE" || echo "$IMAGE_REPO:default"; }
|
|
16
|
+
PREFIX="malvbox-"
|
|
17
|
+
SELF="$(readlink -f "${BASH_SOURCE[0]}" 2>/dev/null || echo "${BASH_SOURCE[0]}")"
|
|
18
|
+
HERE="$(cd "$(dirname "$SELF")" && pwd)"
|
|
19
|
+
|
|
20
|
+
c() { printf '\033[%sm%s\033[0m' "$1" "$2"; }
|
|
21
|
+
info() { echo "$(c '1;36' '›') $*"; }
|
|
22
|
+
err() { echo "$(c '1;31' '✗') $*" >&2; }
|
|
23
|
+
die() { err "$*"; exit 1; }
|
|
24
|
+
cname() { echo "${PREFIX}$1"; }
|
|
25
|
+
|
|
26
|
+
# ── Encadré "container summary" façon Exegol ─────────────────────────────────
|
|
27
|
+
BOX_L=16; BOX_V=64
|
|
28
|
+
_dl() { local s; s=$(printf '%*s' "$1" ''); printf '%s' "${s// /─}"; }
|
|
29
|
+
tbord() { printf '%s%s%s%s%s\n' "$1" "$(_dl 18)" "$2" "$(_dl $((BOX_V+2)))" "$3"; }
|
|
30
|
+
trow() { # label value [ansi-code]
|
|
31
|
+
local val="$2" n=${#2} disp
|
|
32
|
+
[ "$n" -gt "$BOX_V" ] && { val="${2:0:$((BOX_V-1))}…"; n=$BOX_V; }
|
|
33
|
+
disp="$val"; [ -n "${3:-}" ] && disp="$(c "$3" "$val")"
|
|
34
|
+
printf '│ %*s │ %s%*s │\n' "$BOX_L" "$1" "$disp" "$((BOX_V-n))" ''
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
usage() {
|
|
38
|
+
cat <<EOF
|
|
39
|
+
$(c '1;35' malvbox) — offensive box + VPN
|
|
40
|
+
|
|
41
|
+
Usage: malvbox <command> [options]
|
|
42
|
+
|
|
43
|
+
start <name> [--vpn <conf|url>] [-s <dir>]
|
|
44
|
+
create the box and open a shell — or, if it exists, start it and open
|
|
45
|
+
a shell. Run it again anytime to get back in. Graphical tools
|
|
46
|
+
(Firefox…) display on your screen automatically.
|
|
47
|
+
exec <name> <cmd…> run a command in the box (no interactive shell)
|
|
48
|
+
restart <name> restart the box and open a shell
|
|
49
|
+
stop <name> stop a box (workspace kept)
|
|
50
|
+
update <name> [start opts] pull the latest image and recreate the box
|
|
51
|
+
info <name> show a box's status and mounts
|
|
52
|
+
list list boxes and their state
|
|
53
|
+
rm <name> [--purge] remove a box (--purge: drop its workspace too)
|
|
54
|
+
pull pull the latest image
|
|
55
|
+
build build the image locally
|
|
56
|
+
version print malvbox and image version
|
|
57
|
+
|
|
58
|
+
start options
|
|
59
|
+
-s, --share share a host dir into the box (repeatable):
|
|
60
|
+
~/loot -> /root/work/loot
|
|
61
|
+
~/loot:/opt/x -> explicit mountpoint
|
|
62
|
+
~/loot:ro -> read-only
|
|
63
|
+
--vpn WireGuard .conf path OR URL (/api/user/vpn/config)
|
|
64
|
+
--cookie session cookie if --vpn is a protected URL
|
|
65
|
+
--full-tunnel route all traffic through the VPN
|
|
66
|
+
--no-pull use the local image, skip pull
|
|
67
|
+
EOF
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
need_docker() { command -v docker >/dev/null || die "docker not found in PATH"; }
|
|
71
|
+
|
|
72
|
+
resolve_conf() { # <src> <cookie> -> local .conf path on stdout
|
|
73
|
+
local src="$1" cookie="${2:-}" tmp
|
|
74
|
+
if [[ "$src" =~ ^https?:// ]]; then
|
|
75
|
+
tmp="$(mktemp --suffix=.conf)"
|
|
76
|
+
if [ -n "$cookie" ]; then curl -fsSL --cookie "$cookie" "$src" -o "$tmp" || die "VPN download failed (bad cookie?)"
|
|
77
|
+
else curl -fsSL "$src" -o "$tmp" || die "VPN download failed (private config: pass --cookie)"; fi
|
|
78
|
+
grep -q '\[Interface\]' "$tmp" || die "downloaded file is not a WireGuard config"
|
|
79
|
+
echo "$tmp"
|
|
80
|
+
else
|
|
81
|
+
[ -f "$src" ] || die "VPN config not found: $src"
|
|
82
|
+
echo "$src"
|
|
83
|
+
fi
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
cmd_start() {
|
|
87
|
+
need_docker
|
|
88
|
+
local name="" vpn="" cookie="" full=0 nopull=0
|
|
89
|
+
local -a run_cmd=() shares=()
|
|
90
|
+
while [ $# -gt 0 ]; do case "$1" in
|
|
91
|
+
-n|--name) name="$2"; shift 2 ;;
|
|
92
|
+
--vpn) vpn="$2"; shift 2 ;;
|
|
93
|
+
--cookie) cookie="$2"; shift 2 ;;
|
|
94
|
+
--full-tunnel) full=1; shift ;;
|
|
95
|
+
-s|--share) shares+=("$2"); shift 2 ;;
|
|
96
|
+
--image) MALVBOX_IMAGE="$2"; shift 2 ;;
|
|
97
|
+
--no-pull) nopull=1; shift ;;
|
|
98
|
+
--) shift; run_cmd=("$@"); break ;;
|
|
99
|
+
-*) die "unknown option: $1" ;;
|
|
100
|
+
*) [ -z "$name" ] && { name="$1"; shift; } || die "unexpected argument: $1" ;;
|
|
101
|
+
esac; done
|
|
102
|
+
[ -n "$name" ] || { usage; die "a name is required: malvbox start <name>"; }
|
|
103
|
+
# Display forwarding is always on when the host exposes one (WSLg / X11), so
|
|
104
|
+
# graphical tools (Firefox…) can open their window on your screen.
|
|
105
|
+
local gui=0
|
|
106
|
+
{ [ -d /mnt/wslg ] || [ -e /tmp/.X11-unix ] || [ -n "${DISPLAY:-}" ]; } && gui=1
|
|
107
|
+
local image cn; image="$(image_ref)"; cn="$(cname "$name")"
|
|
108
|
+
|
|
109
|
+
# Already running -> open another shell. Stopped -> start it, then shell.
|
|
110
|
+
if docker ps --format '{{.Names}}' | grep -qx "$cn"; then
|
|
111
|
+
exec docker exec -it "$cn" zsh
|
|
112
|
+
fi
|
|
113
|
+
if docker ps -a --format '{{.Names}}' | grep -qx "$cn"; then
|
|
114
|
+
docker start "$cn" >/dev/null; exec docker exec -it "$cn" zsh
|
|
115
|
+
fi
|
|
116
|
+
|
|
117
|
+
local -a args=(run -it --name "$cn" --hostname "$name"
|
|
118
|
+
-v "${PREFIX}${name}-work:/root/work"
|
|
119
|
+
--cap-add NET_ADMIN --device /dev/net/tun
|
|
120
|
+
--sysctl net.ipv4.conf.all.src_valid_mark=1)
|
|
121
|
+
|
|
122
|
+
# Shared dirs (host<->box, rw, live). Default mountpoint in the home (~), kept
|
|
123
|
+
# separate from ~/work where you actually work.
|
|
124
|
+
local sh hostp contp ro; local -a share_disp=()
|
|
125
|
+
for sh in "${shares[@]:-}"; do
|
|
126
|
+
[ -z "$sh" ] && continue
|
|
127
|
+
ro=""; case "$sh" in *:ro) ro=":ro"; sh="${sh%:ro}" ;; esac
|
|
128
|
+
if [[ "$sh" == *:/* ]]; then hostp="${sh%%:*}"; contp="${sh#*:}"
|
|
129
|
+
else hostp="$sh"; contp="/root/$(basename "$sh")"; fi
|
|
130
|
+
hostp="$(cd "$hostp" 2>/dev/null && pwd || echo "$hostp")"
|
|
131
|
+
[ -e "$hostp" ] || die "share not found: $hostp"
|
|
132
|
+
args+=(-v "$hostp:$contp$ro")
|
|
133
|
+
share_disp+=("$hostp -> $contp (${ro:+ro}${ro:-rw})")
|
|
134
|
+
done
|
|
135
|
+
|
|
136
|
+
# GUI: X11 forwarding + a root-owned runtime dir so Firefox runs as root.
|
|
137
|
+
if [ "$gui" = 1 ]; then
|
|
138
|
+
args+=(-e "DISPLAY=${DISPLAY:-:0}" -v /tmp/.X11-unix:/tmp/.X11-unix
|
|
139
|
+
-e XDG_RUNTIME_DIR=/run/user/0 -e MOZ_ENABLE_WAYLAND=0)
|
|
140
|
+
fi
|
|
141
|
+
|
|
142
|
+
# VPN: mount the .conf; the entrypoint brings the tunnel up.
|
|
143
|
+
local vpn_disp="none" conf=""
|
|
144
|
+
if [ -n "$vpn" ]; then
|
|
145
|
+
conf="$(resolve_conf "$vpn" "$cookie")"
|
|
146
|
+
args+=(-v "$conf:/vpn/wg.conf:ro")
|
|
147
|
+
vpn_disp="$conf"
|
|
148
|
+
if [ "$full" = 1 ]; then args+=(-e VPN_FULL_TUNNEL=1); vpn_disp="$conf (full-tunnel)"; fi
|
|
149
|
+
fi
|
|
150
|
+
|
|
151
|
+
if [ "$nopull" = 0 ]; then
|
|
152
|
+
docker pull "$image" >/dev/null 2>&1 || info "pull failed (offline?) — using local image"
|
|
153
|
+
fi
|
|
154
|
+
|
|
155
|
+
echo
|
|
156
|
+
echo "$(c '1;33' ★) $(c '1;37' 'Container summary')"
|
|
157
|
+
tbord ┌ ┬ ┐
|
|
158
|
+
trow Name "$name" '1;37'
|
|
159
|
+
trow Image "$image"
|
|
160
|
+
tbord ├ ┼ ┤
|
|
161
|
+
if [ "$gui" = 1 ]; then trow "Console GUI" "on ✓ (X11)" '1;32'; else trow "Console GUI" "off" '2'; fi
|
|
162
|
+
if [ "$vpn_disp" = none ]; then trow VPN "off" '2'; else trow VPN "$vpn_disp" '1;32'; fi
|
|
163
|
+
trow Network "bridge"
|
|
164
|
+
trow Resources "/opt/resources"
|
|
165
|
+
if [ ${#share_disp[@]} -gt 0 ]; then
|
|
166
|
+
trow Shares "${share_disp[0]}"
|
|
167
|
+
for ((i=1;i<${#share_disp[@]};i++)); do trow "" "${share_disp[i]}"; done
|
|
168
|
+
else
|
|
169
|
+
trow Shares "none" '2'
|
|
170
|
+
fi
|
|
171
|
+
trow Workspace "/root/work (${PREFIX}${name}-work)"
|
|
172
|
+
trow Privileged "off ✓ (cap NET_ADMIN)"
|
|
173
|
+
tbord └ ┴ ┘
|
|
174
|
+
echo
|
|
175
|
+
|
|
176
|
+
exec docker "${args[@]}" "$image" "${run_cmd[@]:-zsh}"
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
cmd_exec() { need_docker; [ $# -ge 2 ] || die "usage: malvbox exec <name> <cmd...>"
|
|
180
|
+
local name="$1"; shift
|
|
181
|
+
exec docker exec -it "$(cname "$name")" "$@"; }
|
|
182
|
+
|
|
183
|
+
cmd_restart() { need_docker; [ $# -ge 1 ] || die "usage: malvbox restart <name>"
|
|
184
|
+
local cn; cn="$(cname "$1")"; docker restart "$cn" >/dev/null || die "no such box: $1"
|
|
185
|
+
exec docker exec -it "$cn" zsh; }
|
|
186
|
+
|
|
187
|
+
cmd_update() { need_docker; [ $# -ge 1 ] || die "usage: malvbox update <name> [start options]"
|
|
188
|
+
local name="$1"; shift
|
|
189
|
+
info "pulling latest image…"; docker pull "$(image_ref)" || info "pull failed — keeping current image"
|
|
190
|
+
docker rm -f "$(cname "$name")" >/dev/null 2>&1 || true # workspace volume is kept
|
|
191
|
+
info "recreating '$name' on the latest image…"
|
|
192
|
+
cmd_start "$name" --no-pull "$@"; }
|
|
193
|
+
|
|
194
|
+
cmd_info() { need_docker; [ $# -ge 1 ] || die "usage: malvbox info <name>"
|
|
195
|
+
local cn; cn="$(cname "$1")"
|
|
196
|
+
docker ps -a --filter "name=^/${cn}$" --format 'BOX {{.Names}} · {{.Status}} · {{.Image}}' | sed "s/${PREFIX}//"
|
|
197
|
+
docker inspect "$cn" --format 'mounts:{{range .Mounts}}
|
|
198
|
+
{{if .Name}}{{.Name}}{{else}}{{.Source}}{{end}} -> {{.Destination}}{{end}}' 2>/dev/null || die "no such box: $1"; }
|
|
199
|
+
|
|
200
|
+
cmd_stop() { need_docker; [ $# -ge 1 ] || die "usage: malvbox stop <name>"
|
|
201
|
+
docker stop "$(cname "$1")" >/dev/null && info "stopped $1"; }
|
|
202
|
+
|
|
203
|
+
cmd_rm() { need_docker; [ $# -ge 1 ] || die "usage: malvbox rm <name> [--purge]"
|
|
204
|
+
local name="$1" purge=0; [ "${2:-}" = "--purge" ] && purge=1
|
|
205
|
+
docker rm -f "$(cname "$name")" >/dev/null 2>&1 || true
|
|
206
|
+
if [ "$purge" = 1 ]; then docker volume rm "${PREFIX}${name}-work" >/dev/null 2>&1 || true
|
|
207
|
+
info "removed $name (+ workspace)"
|
|
208
|
+
else info "removed $name (workspace kept)"; fi; }
|
|
209
|
+
|
|
210
|
+
cmd_list() { need_docker
|
|
211
|
+
docker ps -a --filter "name=^/${PREFIX}" \
|
|
212
|
+
--format 'table {{.Names}}\t{{.Status}}\t{{.Image}}' | sed "1s/NAMES/BOX/;s/${PREFIX}//"; }
|
|
213
|
+
|
|
214
|
+
cmd_pull() { need_docker; exec docker pull "$(image_ref)"; }
|
|
215
|
+
cmd_build() { need_docker
|
|
216
|
+
[ -f "$HERE/Dockerfile" ] || die "no Dockerfile next to this script — 'build' needs the source repo. To just use malvbox, run 'malvbox pull'."
|
|
217
|
+
# --progress=plain streams the per-repo clone counter ([ 42%] 71/168 …) live.
|
|
218
|
+
exec docker build --progress=plain -t "$(image_ref)" "$HERE"; }
|
|
219
|
+
|
|
220
|
+
case "${1:-}" in
|
|
221
|
+
start) shift; cmd_start "$@" ;;
|
|
222
|
+
exec) shift; cmd_exec "$@" ;;
|
|
223
|
+
restart) shift; cmd_restart "$@" ;;
|
|
224
|
+
stop) shift; cmd_stop "$@" ;;
|
|
225
|
+
update) shift; cmd_update "$@" ;;
|
|
226
|
+
info) shift; cmd_info "$@" ;;
|
|
227
|
+
list|ls) shift; cmd_list "$@" ;;
|
|
228
|
+
rm|remove) shift; cmd_rm "$@" ;;
|
|
229
|
+
pull) shift; cmd_pull "$@" ;;
|
|
230
|
+
build) shift; cmd_build "$@" ;;
|
|
231
|
+
version|--version|-v) echo "malvbox $MALVBOX_VERSION · image $(image_ref)" ;;
|
|
232
|
+
""|-h|--help|help) usage ;;
|
|
233
|
+
*) err "unknown command: $1"; usage; exit 1 ;;
|
|
234
|
+
esac
|