pentest_setup deploys a pen-testing-ready Bash environment on top of
common_core and
bash_setup. It ships:
- A set of pentest-specific dotfiles (
pentest.path.sh,pentest.env.sh,pentest.aliases.sh,pentest.keys,renew_tgt.sh, thescreenshothelper) deployed to${HOME}/.config/bash/. - A
pentest.shhook thatbash_setup'sbashrcalready sources, which pulls the above into every interactive shell. - Five interactive menus under
menus/that walk through preflight checks, environment setup, apt/python/go/ruby dependency installs, 60+ tool installs frommodules/tools/, and post-install cleanup. - A
config/layer that definesDATA_DIR,ENGAGEMENT_DIR,BACKUP_DIR,LOOT_DIR, and related engagement paths used by both pentest helpers andbash_setup'stgt.aliases.sh.
It is the fourth repo in a five-repo stack:
common_core → bash_setup → scripts → pentest_setup → pentest_menu
pentest_setup's install.sh will refuse to run unless the earlier repos
in the chain are in place. See Requirements.
- Bash 4+ (macOS users:
brew install bash) - Python 3.12+ for the tool-install modules that ship Python helpers
common_coreinstalled at${HOME}/.config/bash/lib/common_core/(install instructions)bash_setupinstalled (specifically: itsbashrcdeployed to${HOME}/.bashrc, which sources the${BASH_DIR}/pentest.shhook this repo deploys)- Recommended for development:
shellcheck(lint)shfmt(format) — must support-i 4 -ci -srbats(test)
# 1. Install common_core and bash_setup first (see those repos).
# 2. Then clone and install pentest_setup:
git clone https://github.com/tatanus/pentest_setup.git
cd pentest_setup
make install # equivalent to: bash install.shinstall.sh is interactive — it presents the five-menu flow
(00_preflight, 01_environment, 02_requirements, 03_tools,
04_post). Pass --dry-run for a no-op preview, or --auto for a full
non-interactive install. A full install over an existing one prompts for
confirmation when run interactively (a ${DATA_DIR}/.pentest_setup.version
marker records the prior install); pass -f/--force to update without
prompting.
After install, start a new shell or re-source bashrc to pick up the deployed dotfiles:
exec bash -lA convenience orchestrator, install_stack.sh, lives in the workspace
root (one level up — alongside the common_core/, bash_setup/, scripts/,
and pentest_setup/ repos, not inside any of them). Source it so it can
reload your current shell:
cd .. # to the workspace root
source ./install_stack.sh # common_core -> bash_setup -> scripts -> pentest_setup, then reloadIt runs in three phases:
- Clone/update every repo (into subdirectories beside itself, from
https://github.com/tatanus/<repo>.git) — nothing is installed yet. common_core/install_tools.sh— installs the external commands the whole stack shells out to but doesn't ship (git,curl,fzf,jq,tree, …). This must happen before any repo installer: pre-flight REQUIRESfzf, butfzfis otherwise only installed in step 3 ofpentest_setup's own run — after the check that demands it. Skip it with--no-tools; narrow it withTOOLS_ARGS="-g core,shell".- Each repo's
install.sh, non-interactively (-f/--auto), in dependency order.
Then it reloads your shell with a fresh login shell (exec bash -l) so every
updated dotfile/env/config is live — no manual exit/restart between steps.
Because it self-clones, a fresh machine only needs this one file to bootstrap
the whole stack. (An in-place re-source can't pick up changed files because the
deployed dotfiles use source-guards, so a clean login shell is used instead; it
replaces the current shell rather than requiring you to reopen it.)
install_tools.sh exits non-zero when any declared tool is still missing —
including optional ones — so the stack only warns on that and continues; a
genuinely required tool that's still absent is named by pre-flight.
source ./install_stack.sh --pull— alsogit pull --ff-onlyrepos that are already checked out.source ./install_stack.sh --no-clone— never clone; require every repo to be present already (the original behavior).source ./install_stack.sh --no-tools— skip theinstall_tools.shbootstrap (assume the external tools are already present).source ./install_stack.sh --no-reload— install everything, skip the reload.bash ./install_stack.sh— installs, but as a subprocess it can't update your current shell; it prints theexec bash -lcommand to run afterward.- Limit the pentest step:
PENTEST_ARGS="--env --req" source ./install_stack.sh. - Limit the tool bootstrap:
TOOLS_ARGS="-g core,shell" source ./install_stack.sh. - Repo paths default to the subdirectories alongside the script; override with
COMMON_CORE_SRC/BASH_SETUP_SRC/SCRIPTS_SRC/PENTEST_SETUP_SRC. Clone URLs viaSTACK_GIT_BASEor per-repo*_URL.
Set install-time knobs (PY_INSTALLER=uv, PYTHON_EXTRA_VERSIONS, DATA_DIR,
…) in ~/.config/bash/config.env before running — the pentest step runs
non-interactively and won't prompt (see Configuration).
Every tunable is an environment variable with a sensible default, so the
installer runs with zero configuration. To change any of them in one place,
use a config.env file:
cp ~/.config/bash/config.env.example ~/.config/bash/config.env
$EDITOR ~/.config/bash/config.env # uncomment and edit what you need
./install.sh # or `reload_env` in an open shellconfig.env is user-owned and never overwritten by an install/redeploy
(only the .example template is). It is sourced early by both the installer
(config/config.sh) and the interactive shell (dotfiles/pentest.env.sh), so
a knob set there applies to both. An uncommented line is authoritative over the
built-in default; install.sh flags (--dry-run, --quiet, --force) still
win over the file. Point PENTEST_CONFIG_ENV elsewhere to use a different path.
dotfiles/config.env.example is the authoritative, fully-commented list. The
main groups:
| Group | Key variables |
|---|---|
| Core behavior | DEBUG, QUIET, DRY_RUN, NO_DISPLAY, INTERACTIVE_MENU, PENTEST_FORCE, UTIL_LOG_LEVEL |
| Directories | DATA_DIR (single knob — the whole engagement tree derives from it), BASH_DIR |
| Proxy | PROXYCHAINS_CMD, PROXYCHAINS_CONFIG, PROXY |
| Python | PY_INSTALLER (pip|uv), PYTHON_EXTRA_VERSIONS, PYTHON_VERSION, PYTHON, INSTALL_PYTHON, COMPILE_PYTHON |
| Optional installs | INSTALL_METASPLOIT, INSTALL_NESSUS, SETUP_NESSUS, NESSUS_USER, NESSUS_PASSWORD |
| Network | NETWORK_IFACE, DNS_SERVER |
| Engagement | AD_DOMAIN, DNS_DOMAIN, DC_IP, DC_FQDN, LOW_PRIV_*, HIGH_PRIV_*, MACHINE_*, DA_*, and the *_FILE list paths |
Setting DATA_DIR relocates the entire tree — TOOLS_DIR, RECON_DIR,
OUTPUT_DIR, LOOT_DIR, and everything else derive from it automatically.
.
├── install.sh # menu-driven installer (entry point)
├── Makefile # quality gates + release automation
├── VERSION # date-based version: YYYY.MM.DD.N
├── CHANGELOG.md # Keep a Changelog
├── config/
│ ├── config.sh # DATA_DIR, ENGAGEMENT_DIR, BACKUP_DIR, etc.
│ └── lists.sh # PENTEST_FILES, APT/PIP/GO/RUBY package lists
├── dotfiles/ # files deployed to ${HOME}/.config/bash/
│ ├── pentest.sh # source hook for ${BASH_DIR}/pentest.sh
│ ├── pentest.path.sh # exports DATA_DIR, ENGAGEMENT_DIR, PATH adds
│ ├── pentest.env.sh # NETWORK_IFACE, DC_IP, credentials defaults
│ ├── pentest.alias.sh # pentest-flavored aliases
│ ├── pentest.keys # SSH/PGP key references
│ ├── renew_tgt.sh # cron-payload script (renews Kerberos TGTs)
│ └── screenshot.sh # interactive screenshot helper
├── menus/ # auto-sourced by install.sh in order
│ ├── 00_preflight.sh # tool/OS/root/common_core checks
│ ├── 01_environment.sh # dotfile deploy, dirs, cron jobs
│ ├── 02_requirements.sh # apt / pip / go / ruby package installs
│ ├── 03_tools.sh # interactive 60+ tool installer
│ └── 04_post.sh # cleanup + summary
├── modules/
│ ├── tools/ # one *.sh per tool, sourced by 03_tools.sh
│ └── scripts/ # payloads copied to ${TOOLS_DIR}/SCRIPTS/ at install
│ ├── misc/ # misc support scripts
│ └── msf/ # Metasploit runner + resource scripts (see below)
│ ├── msf_common_runner.rb # run_module(opts) entry point
│ ├── run_all_modules.sh # run every modules/*.rc via msfconsole
│ ├── modules/ # per-module *.rc (incl. all *_version scanners)
│ └── FILES/ # wordlists referenced by the .rc creds blocks
├── tests/ # BATS coverage (structural)
└── tools/
└── check_bash_style.sh # comprehensive style scan
modules/scripts/msf/ is deployed to ${TOOLS_DIR}/SCRIPTS/MSF/ by
menus/01_environment.sh::environment::setup_msf_scripts. It holds a shared
runner, msf_common_runner.rb, plus one resource script per module under
modules/*.rc.
Each .rc file is a thin wrapper: it defines an opts hash (module path,
plus a target_filter of proto + ports + service names) and hands it to
run_module(opts). The runner walks the MSF database, selects every open
host whose service matches the filter by name or port, sets RHOSTS
(and RPORT when the module supports it), and runs the module — spooling
output to ${DATA_DIR}/OUTPUT/TEE/. To add another module, copy an existing
.rc (e.g. smb_version.rc), change the module_path, and set the
proto/ports/services to match where that service is expected to run.
The *_version.rc set covers every auxiliary/.../*_version scanner in the
framework; proto and default port for each were taken from the module's
registered Opt::RPORT (or protocol mixin), then widened to the ports a scan
realistically labels. Alongside them is a set of unauthenticated
enumeration/discovery modules useful for an automated internal pass —
endpoint_mapper, mssql_ping, nbname, pipe_auditor, snmp_enum,
smtp_enum, ssh_enumusers, winrm_auth_methods, redis_server,
couchdb_enum, afp_server_info, ntp_monlist, sip_options,
ssdp_msearch, kerberos_enumusers, and more. Login/brute modules are
intentionally not included by default (account-lockout risk); add them
per-engagement by copying a .rc and filling in the user_file/pass_file
block.
To run the whole set, use run_all_modules.sh (deployed beside modules/):
./run_all_modules.sh # every modules/*.rc in one msfconsole session
./run_all_modules.sh -w engagement # select that msf workspace first
./run_all_modules.sh -p 'snmp_*.rc' # only matching modules
./run_all_modules.sh --isolated -t 5m # one console per module, 5-min cap each
./run_all_modules.sh --results # parse existing tee logs for hits, run nothing
./run_all_modules.sh --list # show what would run, then exitBy default it concatenates every module into one resource file and runs a
single msfconsole (paying startup once; each module still spools its own
OUTPUT/TEE/MSF_<module>.tee). --isolated runs one console per module so a
hang or crash cannot affect the rest, and --timeout bounds each run.
After a run it parses those MSF_*.tee logs (in ${DATA_DIR}/OUTPUT/TEE,
overridable with --tee-dir/MSF_TEE_DIR) for Metasploit success lines —
[+] / print_good — and prints them grouped by module: ANSI colors
stripped, hits de-duplicated, [*]/[-] noise dropped. Findings print to
stdout (so --results > hits.txt captures just them) and the summary to
stderr. Use --no-results to skip the parse, or -R/--results to parse
existing logs without running anything.
dotfiles/renew_tgt.sh looks like a duplicate of bash_setup's
tgt.aliases.sh::renewTGT but isn't — it's a self-contained cron payload
scheduled by menus/01_environment.sh::environment::setup_cron_jobs. It
must stay independent of any sourced shell environment because cron does
not load bashrc.
| Target | What it does |
|---|---|
make help |
Show all targets. |
make ci |
Format check + lint + tests. Non-mutating. Run before PRs. |
make fmt |
Auto-format with shfmt -i 4 -ci -sr. |
make fmt-check |
Verify formatting without writing; same flags as make fmt. |
make lint |
shellcheck -x across git ls-files '*.sh'. |
make test |
bats -r tests. |
make style |
Comprehensive style scan via tools/check_bash_style.sh. |
make install |
bash install.sh — deploy dotfiles, run setup menus. |
make show-version |
Print current VERSION. |
make release V=… |
Cut a release (see Releases). |
make release-today |
Cut a release using today's UTC date (YYYY.MM.DD.0). |
The mandated formatter flags are -i 4 -ci -sr. Do not add -bn or
-kp anywhere.
- Depends on:
common_core(installed at${HOME}/.config/bash/lib/common_core/util.sh) andbash_setup(specifically: itsbashrcmust be deployed so that${BASH_DIR}/pentest.shgets sourced). - Provides for
bash_setup:- The
pentest.shhook deployed to${BASH_DIR}/pentest.sh. DATA_DIRandENGAGEMENT_DIRexports (viapentest.path.sh), whichbash_setup'stgt.aliases.shreads. (tgt.aliases.shalso has a${HOME}/DATAsafe-default fallback so it does not crash ifpentest_setupis not installed.)
- The
Enforced by .shellcheckrc and tools/check_bash_style.sh:
- Bash 4+,
set -uo pipefail,IFS=$'\n\t'. - No
set -e— handle errors explicitly. - No
evaloutside heavily-audited metaprogramming. function name() { … }form; never barename() { … }.- All expansions quoted and braced (
"${var}","$@"). [[ … ]]not[ … ];command -vnotwhich;$(…)not backticks.- Source-guard idiom on every helper:
if [[ -z "${X_LOADED:-}" ]]; then declare -g X_LOADED=true # … fi
See CLAUDE.md (auto-generated) for the canonical policy hash chain.
The repo uses date-based four-part versioning (YYYY.MM.DD.N), tracked in
VERSION and CHANGELOG.md. To cut a release:
# 1. Land your changes as normal commits with `## [Unreleased]` notes.
git add …; git commit -m "feat(…): …"; git push
# 2. Cut the release. `make release` will:
# - run `make ci` (refuse if anything fails)
# - refuse on a dirty working tree
# - stamp `## [Unreleased]` -> `## [Vx] - YYYY-MM-DD` (UTC) in CHANGELOG
# - write VERSION
# - single commit `chore(release): cut Vx`
# - annotated tag `vVx`
# - `git push --follow-tags`
make release-today # uses today's UTC date.0
make release-today N=1 # second cut of the same UTC day -> .1
make release V=2026.06.27.0 # explicit versionThis project is licensed under the MIT License.