Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions .github/workflows/validate.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
name: Validate framework

on:
push:
branches: [master]
pull_request:

jobs:
validate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Run validation gate
run: bash tests/validate.bash
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,6 @@ modules/docker/apache/images/*.png
# Ignore everything in aliases/
core/shells/bash/aliases/*
## But not this file
!core/shells/bash/aliases/.aliases
!core/shells/bash/aliases/.aliases
# myshell machine-local state (menu enable/disable bookkeeping)
.module_state/
10 changes: 10 additions & 0 deletions .shellcheckrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# shellcheck configuration - directive syntax only (https://www.shellcheck.net/wiki/Directive)
# Note: shellcheck cannot exclude files from an rc file; to skip a file,
# add "# shellcheck disable=all" below its shebang or omit it from the CLI invocation.

# Extension-less profile/dot-files (.appearance, .aliases, ...) have no shebang;
# treat everything as bash.
shell=bash

# Allow following source statements to files outside the checked file's directory
external-sources=true
137 changes: 137 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
# myshell — agent guide (repo root)

myshell is a personal bash framework: a `.bashrc` loader that sources functions, aliases
and profiles from this repo, plus self-contained "modules" (mostly docker compose stacks)
launched through aliases. Everything is bash; there is no build step.

**Before editing under `core/shells/bash/`, read `core/shells/bash/AGENTS.md`.
Before editing under `modules/`, read `modules/AGENTS.md`.**

## How the framework loads (the one chain that explains everything)

```
~/.bash_profile (machine file, NOT in repo)
└─ exports project_path=$HOME/Documents/myshell
└─ sources core/shells/bash/.bashrc, which:
1. runs core/shells/bash/jobs/public_ip.bash in background
2. sources core/shells/bash/functions/*.bash (explicit if-blocks, one per file)
3. sources EVERY file in core/shells/bash/aliases/ (glob loop)
4. sources core/shells/bash/profiles/.* (explicit if-blocks, one per file)
```

Module scripts are executed as `bash <script>` from aliases → they run in a **child
shell** and only see **exported** variables. `$project_path` is exported; never hardcode
`$HOME/Documents/myshell` in a script, always use `$project_path`.

## Repo map

| Path | What it is |
|---|---|
| `core/shells/bash/.bashrc` | The loader. Every new function/profile needs an if-block here. |
| `core/shells/bash/functions/` | 8 sourced function files (`myshell.bash` is the manager UI). |
| `core/shells/bash/aliases/.aliases` | THE tracked alias file. All module aliases live here. |
| `core/shells/bash/aliases/*.sh` | Machine-local aliases. **Git-ignored by design** — never put shared aliases there. |
| `core/shells/bash/profiles/.*` | Dot-file profiles (.appearance, .history, .path, ...). |
| `core/shells/bash/jobs/` | Background jobs run at shell start. |
| `modules/<category>/<module>/` | Self-contained tools: bw, docker, help, john, utils, yt-dlp. |
| `tests/validate.bash` | The validation gate. Run it before calling anything done. |
| `.module_state/` | Machine-local menu state (git-ignored). Bookkeeping only — nothing loads based on it. |

## Golden rules (each one caused a real bug — do not relearn them)

1. **Module aliases go ONLY in `core/shells/bash/aliases/.aliases`.** Every other file in
`aliases/` is git-ignored (`.gitignore` rule `core/shells/bash/aliases/*` with a single
`!.aliases` exception). An alias added to `.modules.sh` etc. silently never reaches git.
2. **Every alias that points into `modules/` must reference a file that exists.**
`myshell list` derives the command list from `.aliases` and hides dead targets, so a
broken alias becomes invisible instead of failing loudly. `tests/validate.bash` enforces
this. (Cause: a batch of `modules/k8s` aliases survived the module's deletion.)
3. **Use `docker compose` (plugin), never `docker-compose`.** With the plugin missing the
CLI fails with the misleading error `unknown shorthand flag: 'd' in -d`. If you see that
error, the compose plugin is missing — the script is not broken.
4. **The `myshell` user interface is exactly two entry points:** `myshell` (interactive
menu) and `myshell list`. Do NOT add CLI subcommands; the owner removed them on purpose
(2026-07-12). New capabilities go into the menu.
5. **The `c_green`/`c_red`/`c_blue`/... helpers in `myshell.bash` print ONLY their first
argument, with no newline and no printf formatting.** Passing `"%s" "$x"` prints a
literal `%s`. For formatted or colored lines use `printf` with escape codes directly.
6. **`.module_state/enabled.modules` is bookkeeping, not a gate.** One name per line:
module directory basenames (`alpine`, `mysql5`), flat modules by their own dir name
(`bw`, `yt-dlp`), profiles with their leading dot (`.appearance`). Nothing in the
loader reads it; disabling a module does not remove its command.
7. **`.shellcheckrc` accepts directives only** (`shell=bash`, `external-sources=true`).
No INI sections, no file excludes — those break every shellcheck run in the repo.
8. **Never edit files under `modules/docker/*/Docker/` that came from upstream images**
(e.g. `catalina.bash` — 600+ line Tomcat vendor configs). They are container config
payloads, not framework code.

## Cookbook (follow the recipe exactly; each step lists its coupled edits)

### Add a docker service module
1. `mkdir -p modules/docker/<name>` with `docker-compose.yaml` (copy `modules/docker/alpine/`
as reference: service with `container_name: <name>`, optional `build:` context `./Docker`).
2. Create `modules/docker/<name>/<name>.bash`:
```bash
#!/bin/bash
cd "$project_path/modules/docker/<name>" || exit 1
docker compose up -d --build --force-recreate
```
Optional shell access: `<name>_exec.bash` with `docker exec -it <name> /bin/sh`.
3. Add the alias in `core/shells/bash/aliases/.aliases`, INSIDE the docker-group guard
block (between `if [ "$docker_groups" = "$docker_str" ]` and its `else`):
`alias <name>="bash $project_path/modules/docker/<name>/<name>.bash"`
4. Verify: `cd modules/docker/<name> && docker compose config --quiet` then
`bash tests/validate.bash`.

One-shot tools (run-and-exit, like `k6`) use `docker compose run --rm <service> ...`
instead of `up -d`. See `modules/docker/k6/k6.bash`.

### Add a utility script
1. Create `modules/utils/<topic>/<script>.bash` (use `$project_path`, pass shellcheck).
2. Add alias in `.aliases` under `# Utils`.
3. `bash tests/validate.bash`.

### Add a core function file
1. Create `core/shells/bash/functions/<name>.bash`.
2. Add an if-block in `.bashrc` under `# Load functions` (copy an existing block).
3. **Coupled edit:** add `<name>` to the `func_files` list in
`core/shells/bash/functions/myshell.bash` (function `do_status`) AND bump the
`Total: %d/N functions` counter on the next line. Grep first: `grep -n 'func_files=' core/shells/bash/functions/myshell.bash`.
4. `bash tests/validate.bash`.

### Add a profile
1. Create `core/shells/bash/profiles/.<name>` (leading dot).
2. Add an if-block in `.bashrc` under `# Load profiles`.
3. `bash tests/validate.bash`.

## Validation protocol — non-negotiable before "done"

Ground every claim in tool output, not memory. In order:

1. `bash -n <every file you touched>` — syntax.
2. `shellcheck <touched .bash files>` — must be clean (repo `.shellcheckrc` applies).
Semgrep is available via MCP — scan touched files when you change logic.
3. `bash tests/validate.bash` — the full gate: syntax of all shell files, shellcheck on
core functions, loader integrity (.bashrc targets exist), alias→script integrity,
`docker compose config` for every docker module, and a `myshell` smoke test.
4. Quote the actual output in your report. If a check fails, fix and re-run; never report
a failing state as done.

CI runs the same gate (`.github/workflows/validate.yml`) plus manual docker image builds
(`manual.yml`, `schedule.yml` — do not touch those when doing framework work).

## Do NOT

- Do not resurrect `module_resolver.bash`, `framework_core.bash`, `env_core.bash` or any
`myshell_mod_*` function — removed on purpose (2026-07-12/13).
- Do not add subcommands to `myshell` (rule 4).
- Do not `git add -f` git-ignored files (machine-local aliases, `.module_state/`).
- Do not delete or stop running containers/services without being asked.
- Do not "fix" `modules/docker/*/Docker/` vendor payloads flagged by linters (rule 8).
- Do not reference `modules/k8s`, `modules/ssh`, `modules/minikube`, `openldap`,
`passbolt` — deleted; they must not reappear in aliases or docs.

## Commits

Conventional commits: `feat:` / `fix:` / `docs:` / `refactor:` / `chore:`.
All paths through `$project_path`. Run the validation protocol before every commit.
70 changes: 8 additions & 62 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

`myshell` is a Bash-first hybrid framework for building, bootstrapping, and operating a modular user environment.

It treats the shell runtime as the technical base, then layers optional profiles, user services, and operational modules on top of it. The result is a structured system that aims for two main properties:
It treats the shell runtime as the technical base, then layers optional profiles and operational modules on top of it. The result is a structured system that aims for two main properties:

- **modularity**: each component has a clear layer, a concrete purpose, and explicit activation
- **reproducibility**: the managed local environment and its modular composition can be reproduced consistently
Expand All @@ -14,7 +14,7 @@ It treats the shell runtime as the technical base, then layers optional profiles
`myshell` is designed to:
- bootstrap a Bash-based user environment
- manage shell runtime behavior through a structured core
- load optional profiles and user services explicitly
- load optional profiles explicitly
- expose operational modules through a stable framework structure
- integrate official external companion repositories such as `config_files`

Expand Down Expand Up @@ -42,18 +42,9 @@ Profiles are optional environment components loaded from `core/shells/bash/profi

Profiles are part of the framework core model. They are used for shell environment setup, bootstrap behavior, and managed configuration deployment.

Profiles are opt-in through explicit loader blocks in `.bashrc`.
Profile are opt-in through explicit loader blocks in `.bashrc`.

### Services
Services are optional components loaded from `core/shells/bash/services/`.

Services are also part of the framework core model, but are intended for persistent or service-like integrations that should not live as normal shell profiles.

Typical examples are `systemd --user` units and their companion scripts.

Services are opt-in through explicit loader blocks in `.bashrc`.

### Modules
### Modules
Modules are operational tooling components. They expose commands and workflows on top of the stable user-environment base provided by the framework.

Modules are part of the core identity of `myshell`, not an afterthought.
Expand Down Expand Up @@ -89,7 +80,7 @@ This means:
- the **experience of use**

What may vary:
- which profiles, services, and modules are enabled
- which profiles and modules are enabled
- which components are sourced from the official companion repository
- future official external integrations in the `myshell` ecosystem

Expand Down Expand Up @@ -122,12 +113,10 @@ Current top-level structure relevant to the framework model:
- framework runtime and shell entrypoints
- `core/shells/bash/profiles/`
- optional managed environment components
- `core/shells/bash/services/`
- optional managed service integrations
- `core/shells/bash/.bash_profile`
- framework entrypoint
- `core/shells/bash/.bashrc`
- explicit activation surface for profiles and services
- explicit activation surface for profiles
- `modules/`
- operational toolkit components
- `core/shells/pwsh/`
Expand Down Expand Up @@ -166,9 +155,9 @@ If you want to use the PowerShell profile from Windows:
2. Edit `$PROFILE` with the content from `core/shells/pwsh/Microsoft.PowerShell_profile.ps1`.
3. Set the required values in the `VARIABLE DECLARATION` section.

## Profiles and services
## Profiles and modules

`myshell` loads optional Bash components from `core/shells/bash/profiles/` and `core/shells/bash/services/` through explicit blocks in `core/shells/bash/.bashrc`.
`myshell` loads optional Bash components from `core/shells/bash/profiles/` through explicit blocks in `core/shells/bash/.bashrc`.

### Profiles currently loaded by default blocks in `.bashrc`

Expand All @@ -183,49 +172,6 @@ Examples include:
- `.config_files`
- `.ssh`

### Services currently supported

Examples include:
- `.hyprland-wallpaper`
- `.nvidia-fan`

These are framework-managed service integrations and are activated through `.bashrc` loader blocks.

### Current service behavior

`.hyprland-wallpaper`
- downloads `hypr-wallpaper.service` from `config_files`
- downloads `wallpaper-rotation.sh` from `config_files`
- installs them into local user paths
- runs `systemctl --user daemon-reload`
- runs `systemctl --user enable --now hypr-wallpaper.service` when needed

`.nvidia-fan`
- downloads `nvidia-fan.service` from `config_files`
- downloads `nvidiafan.sh` from `config_files`
- installs them into local user paths
- runs `systemctl --user daemon-reload`
- runs `systemctl --user enable --now nvidia-fan.service` when needed

### Service prerequisites

`.hyprland-wallpaper`
- `hyprpaper`
- `hyprctl`
- a valid `~/.config/hypr/hyprpaper.conf`
- a local wallpaper directory configured in the script through `WALLPAPER_DIR`
- `mpvpaper` if video wallpapers are used
- monitor names and interval may need local adjustment

`.nvidia-fan`
- `nvidia-smi`
- `nvidia-settings`
- `sudo`
- `xhost`
- a deployed script at `~/.config/nvidia/nvidiafan.sh`
- `sudo` for `nvidia-settings` must work non-interactively in the session where the service runs
- the graphical session must be ready; the service definition uses a short startup delay to avoid login-time race conditions

## Companion repository: `config_files`

`config_files` is the official companion repository of `myshell`.
Expand Down
2 changes: 2 additions & 0 deletions core/shells/bash/.aliases-core
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
#!/bin/bash
# shellcheck disable=SC2143
9 changes: 1 addition & 8 deletions core/shells/bash/.bashrc
Original file line number Diff line number Diff line change
Expand Up @@ -64,14 +64,7 @@ if [ -f "$project_path"/core/shells/bash/profiles/.config_files ]; then
fi
if [ -f "$project_path"/core/shells/bash/profiles/.ssh ]; then
. "$project_path"/core/shells/bash/profiles/.ssh
fi
# Load services
if [ -f "$project_path"/core/shells/bash/services/.hyprland-wallpaper ]; then
. "$project_path"/core/shells/bash/services/.hyprland-wallpaper
fi
if [ -f "$project_path"/core/shells/bash/services/.nvidia-fan ]; then
. "$project_path"/core/shells/bash/services/.nvidia-fan
fi
fi
# Bash time lapse ends
final_result=$(date +%s%3N)
elapsed_time=$((final_result - initial_result))
Expand Down
67 changes: 67 additions & 0 deletions core/shells/bash/AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
# core/shells/bash — agent guide

Read the root `AGENTS.md` first; this file adds the internals of the loader area.

## File map

| File | Purpose |
|---|---|
| `.bashrc` | Loader. Sourced by `~/.bash_profile` (machine file). Times its own startup. |
| `.bash_profile` | Reference login-shell profile (repo copy; the live one is `~/.bash_profile`). |
| `functions/checks.bash` | `checks` — system info dashboard (OS, CPU, docker, net). |
| `functions/colors.bash` | Color helper functions (`red`, `cyan`, `nocolor`, ...) used by checks. |
| `functions/crlf_to_lf.bash` | CRLF→LF converter helper. |
| `functions/shfmt.bash`, `yamlfmt.bash`, `hadolint.bash`, `shellcheck.bash` | Wrappers that run the respective linters via docker/binary. |
| `functions/myshell.bash` | The `myshell` manager: menu + list. See below. |
| `aliases/.aliases` | Tracked aliases (facilities, git, terraform, bw, docker modules, john, utils, yt-dlp). |
| `aliases/*.sh` | Machine-local, git-ignored. Never add shared aliases here. |
| `profiles/.*` | 9 dot-file profiles, each with an explicit loader block in `.bashrc`. |
| `jobs/public_ip.bash` | Backgrounded at shell start by `.bashrc`. |
| `jobs/online_users.bash` | Present but its loader line in `.bashrc` is commented out. |

## myshell.bash internals

- Dispatch: `myshell` → `do_menu`; `myshell list` → `do_cmds_list`. Nothing else (owner
decision — do not extend the CLI).
- `do_menu`: numbered menu, options 1-4 → `do_status`, `do_manage_modules`,
`do_manage_profiles`, `do_cmds_list`; `q` quits. `read -r || return` guards EOF.
- `do_manage_modules` / `do_manage_profiles`: arrow-key menus. **Redraw invariant:** each
frame prints exactly `${#names[@]} + 5` lines and the redraw does `\033[<lines>A\033[J`.
If you add or remove a printed line inside the loop you MUST update the `lines`
variable, or the screen corrupts. First frame skips the cursor-up (`first` flag).
- Key handling uses `IFS= read -rsn1` (silent, no stty juggling). ESC sequences: read 2
more bytes with `-t 0.05`; bare ESC returns to menu. Enter/T toggle, E enable,
D disable, Q back.
- Categories with subdirectories toggle every child module (`_cat_enable`/`_cat_disable`);
flat modules (`bw`, `yt-dlp` — scripts directly in the category dir) are tracked by
their own directory name.
- `_mod_disable` rewrites the state file via a temp file NEXT TO it (`${_MF}.tmp_$$`) and
treats `grep -v` exit 1 (no lines left) as success. Keep it that way: writing the temp
file to `/tmp` and swallowing failures caused silent no-op disables.
- Color helpers `c_green`/`c_red`/... print only `$1`, no newline, no format expansion.
For colored formatted rows use `printf '\033[0;32m...\033[0m\n' args` directly.
- `do_cmds_list` parses `alias X="... modules/..."` lines from `aliases/.aliases` and
`aliases/*.sh`, dedupes by name, and skips targets that do not exist on disk.

## Editing rules for this area

- New function file ⇒ three coupled edits: the file, the `.bashrc` if-block, and the
`func_files` list + `Total: %d/N` counter in `myshell.bash:do_status`.
- `.bashrc` loader blocks reference files that MUST exist — `tests/validate.bash` checks
every `$project_path/...` it sources.
- Profiles are dot-files; the state file records them WITH the leading dot.
- Anything printed by `_mod_enable`/`_mod_disable` inside the interactive menus must stay
redirected to `/dev/null` (extra lines break the redraw arithmetic).

## How to verify changes here (copy-paste)

```bash
bash -n core/shells/bash/functions/myshell.bash
shellcheck core/shells/bash/functions/myshell.bash
# menu smoke test with scripted keystrokes (2=modules, arrow down, q back, q quit):
bash -c 'export project_path=$PWD MY_SHELL_ENV_DIR=$(mktemp -d);
source core/shells/bash/functions/myshell.bash;
printf "2\n\x1b[Bqq\n" | myshell >/dev/null && echo MENU_OK;
myshell list | head -5'
bash tests/validate.bash
```
Loading
Loading