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
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ flowchart TD
| `developer-gui` | VS Code, Ghostty, DBeaver. Privacy + AI-upsell de-nag profile for VSCode/VSCodium/Cursor off unless `-e vscode_privacy_enabled=true` |
| `developer-rust` / `-go` / `-python` / `-node` / `-typescript` / `-c` | Language toolchains |
| `infrastructure` | OpenTofu, OpenBao, AWS CLI, helm, terraform-docs, `k8s` (kubectl, kubectx, kubens, k9s, kind, argocd, dive, kustomize, kubeconform, kube-linter), `data` (clickhouse-client, rpk, valkey-cli, vector), `cloudflare` (flarectl, wrangler) |
| `contributor` | hyperi-ci + its check tools (semgrep, alint), gitleaks, trivy, hadolint, pip-audit, yamllint, ansible-lint, pre-commit, act |
| `contributor` | hyperi-ci + its check tools (semgrep, alint), gitleaks, trivy, hadolint, pip-audit, yamllint, ansible-lint, pre-commit, act, git-scrub, macbash |
| `soe` / `soe-gui` | HyperI org policy (opt-in) |
| `--full-stack` / `--infra` / `--languages [list]` | Persona bundles (see `--help`) |
| `winlike` / `maclike` | GNOME taskbar (winlike) or dock (maclike), winlike wins if both |
Expand Down Expand Up @@ -172,7 +172,7 @@ reports success -- the settings simply land where nobody sees them.
- `vscode-privacy` (off by default): strips the Copilot/AI upsell UI and the telemetry that stock VSCode ships enabled, across VSCode, VSCodium and Cursor. Enable with `-e vscode_privacy_enabled=true`. It merges one marked block into `settings.json` and never touches a comment or a key it does not manage, backs the file up before its first write, and `-e vscode_privacy_uninstall=true` takes only its own keys back out. Where you have set one of those keys yourself further down the file, yours wins and the run tells you which ones -- so it cannot look applied while changing nothing
- Languages: Rust, Go, Python, C/C++, Node.js, TypeScript (the Astral suite -- uv, ruff, ty -- ships in the base, as does Node.js: it is core tooling that semantic-release and CI need)
- `infrastructure`: OpenTofu + OpenBao (the OSS forks, no HashiCorp BUSL tools), AWS CLI v2, checkov, and terraform-docs for generating IaC module reference docs (engine-agnostic -- it reads `.tf` whichever binary runs it, and OpenTofu has no native `tofu docs`). Under `k8s`: kubectl + kubectx + kubens + k9s + kind + argocd + dive + kustomize + kubeconform + kube-linter. helm is NOT in that group -- it sits in `cloud`, so plain `--tags infrastructure` gets it whether or not you select `k8s`. The `data` group: clickhouse-client, rpk, valkey-cli, vector. The `cloudflare` group: flarectl + wrangler (flarectl builds from source on both platforms -- Cloudflare ships no binary -- so Linux needs `developer-go`)
- `contributor`: hyperi-ci and the tools its checks drive (semgrep, alint), gitleaks, trivy, hadolint, pip-audit, ansible-lint, pre-commit, act
- `contributor`: hyperi-ci and the tools its checks drive (semgrep, alint), gitleaks, trivy, hadolint, pip-audit, ansible-lint, pre-commit, act, and git-scrub for rewriting AI residue or a leaked secret out of git history -- gitleaks scans FULL history, so a secret removed from HEAD still fails the gate. macbash is here too: it flags the GNU-only bash constructs that break on macOS, and CONTRIBUTING.md asks for it on every shell change
- `soe` / `soe-gui`: HyperI org policy: VPN clients, Claude Code, Slack, LibreOffice, RDP client, telemetry-disable, auto-updates, GNOME taskbar
- `power-profile` (off by default, and deliberately not in `soe`): sleep, idle and lid policy, selected per machine. `always-on` (the default profile) never idle-suspends on mains power and does not sleep when the lid shuts -- for a repurposed laptop doing build work, or a desktop that has to answer ssh. `vm` never sleeps or suspends at all, for an unattended RDP guest that nobody can walk over and wake. Battery behaviour stays stock under `always-on`, because a machine that will not sleep in a bag cooks itself. Profiles are data files, so adding one is adding a file -- see [roles/power-profile/README.md](ansible/roles/power-profile/README.md)
- `arcane` (off by default): [Arcane](https://getarcane.app), a web UI for the containers on the box. Enable it with `-e soe_arcane_enabled=true` and you get a daemon on `http://localhost:3552` that comes back after a reboot and keeps itself updated. Works against docker-ce on Linux and colima on macOS. Bound to loopback because it holds the Docker socket, so whatever reaches that port owns the machine. Login is whatever Arcane seeds -- `arcane` / `arcane-admin` as upstream documents it. The role sets neither, and only clears the forced first-login password prompt, which it does by re-submitting that seeded password so the credentials stay unchanged. That needs the password policy relaxed to `basic` (`soe_arcane_password_policy`), because upstream's default `strong` policy rejects its own seeded password. There is still a login -- auto-login sits behind a `buildables` Go build tag that no published image is compiled with, so zero-auth is not available without building your own image
Expand Down
6 changes: 6 additions & 0 deletions ansible/inventories/localhost/group_vars/all.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,12 @@ hyperi_github_headers: >-
{{ {'Authorization': 'Bearer ' ~ hyperi_github_token}
if hyperi_github_token else {} }}

# The same token as a process environment, for tasks shelling out to a script
# that does its own release lookups. An exported token does not survive `become`
# to a different uid, so a task `environment:` is the only way it reaches them.
hyperi_github_env: >-
{{ {'GITHUB_TOKEN': hyperi_github_token} if hyperi_github_token else {} }}

hyperi_versions:
alint: v0.14.1
gitleaks: v8.30.1
Expand Down
110 changes: 110 additions & 0 deletions ansible/roles/contributor/tasks/git_scrub.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
---
# git-scrub - rewrites a git repository's whole history to strip AI coding-agent
# residue (artefact files, `Co-Authored-By` trailers, agent-credited identities)
# and redact secret/PII shapes out of blobs.
#
# In `contributor` rather than `soe` because it is what clears a gitleaks
# failure: gitleaks scans FULL history, so a secret removed from HEAD still
# fails `hyperi-ci check`. soe inherits it through meta/dependencies.
#
# GitHub release tarball on every platform (Tier 3 -- hyperi-update pulls the
# latest on each run). It is the only rung: not on crates.io, no git-scrub path
# on downloads.hyperi.io, and the release's git-scrub.rb is not in the hyperi-io
# tap. Move macOS to community.general.homebrew once that formula is tapped.
#
# The asset unpacks into a directory named after itself, so the extracted binary
# path carries the version. The tag has a leading `v`; the filename does not.

- name: Install git-scrub (re-fetched GitHub release, Tier 3)
block:
# --pinned takes the CI-exact tag from group_vars, latest otherwise, so a
# pinned install never depends on the GitHub API.
#
# hyperi-ci carries no git-scrub pin, so --pinned falls through to latest.
# Adding an entry here without the matching hyperi-ci pin turns the build
# red via check_version_pins.py.
- name: Get latest git-scrub version from GitHub API
ansible.builtin.uri:
url: https://api.github.com/repos/hyperi-io/git-scrub/releases/latest
return_content: true
headers: "{{ hyperi_github_headers }}"
register: contributor_git_scrub_release
check_mode: false
when: not (hyperi_pinned | default(false) and 'git-scrub' in (hyperi_versions | default({})))

- name: Resolve the git-scrub tag (pinned or latest)
ansible.builtin.set_fact:
contributor_git_scrub_ref: >-
{{ hyperi_versions['git-scrub']
if (hyperi_pinned | default(false) and 'git-scrub' in (hyperi_versions | default({})))
else contributor_git_scrub_release.json.tag_name }}

# hyperi_arch_deb already spells the architecture the way this release does.
- name: Build the git-scrub asset name
ansible.builtin.set_fact:
contributor_git_scrub_stem: >-
git-scrub-{{ contributor_git_scrub_ref | regex_replace('^v', '') }}-{{
'darwin' if ansible_facts['distribution'] == 'MacOSX' else 'linux' }}-{{ hyperi_arch_deb }}

# The tarball is KEPT, and that is what makes the role idempotent: get_url
# re-reports ok for an unchanged asset, so the extract and install below
# stay skipped on a converged box. Deleting it would re-download every run
# and report changed forever.
#
# The upstream binary cannot be asked instead -- v1.0.2 reports itself as
# "git-scrub 1.0.1", so a version comparison would reinstall on every run.
- name: Download the git-scrub tarball
ansible.builtin.get_url:
url: >-
https://github.com/hyperi-io/git-scrub/releases/download/{{ contributor_git_scrub_ref }}/{{ contributor_git_scrub_stem }}.tar.gz
dest: "/tmp/{{ contributor_git_scrub_stem }}.tar.gz"
mode: '0644'
register: contributor_git_scrub_download

- name: Check whether git-scrub is already installed
ansible.builtin.stat:
path: /usr/local/bin/git-scrub
register: contributor_git_scrub_installed

# Root-owned on Linux, absent on a fresh Apple Silicon box.
- name: Ensure /usr/local/bin exists
ansible.builtin.file:
path: /usr/local/bin
state: directory
mode: '0755'
become: true

# A new asset OR a missing binary: the second repairs drift on a box where
# the tarball is still cached but the binary was removed.
- name: Install git-scrub
when:
- not ansible_check_mode
- contributor_git_scrub_download.changed or not contributor_git_scrub_installed.stat.exists
block:
- name: Extract the git-scrub tarball
ansible.builtin.unarchive:
src: "/tmp/{{ contributor_git_scrub_stem }}.tar.gz"
dest: /tmp
remote_src: true

- name: Install the git-scrub binary
ansible.builtin.copy:
src: "/tmp/{{ contributor_git_scrub_stem }}/git-scrub"
dest: /usr/local/bin/git-scrub
mode: '0755'
remote_src: true
become: true

- name: Remove the extracted git-scrub directory
ansible.builtin.file:
path: "/tmp/{{ contributor_git_scrub_stem }}"
state: absent

rescue:
- name: Record that git-scrub did not install
# noqa: var-naming[no-role-prefix] -- deploy_warnings is the shared
# accumulator reported by playbooks/main.yml post_tasks.
ansible.builtin.set_fact:
deploy_warnings: >-
{{ deploy_warnings | default([])
+ ['git-scrub: ' ~ (ansible_failed_result.msg | default('download failed'))] }}
7 changes: 6 additions & 1 deletion ansible/roles/contributor/tasks/hyperi_ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,12 @@
become: "{{ ansible_facts['distribution'] != 'MacOSX' }}"
become_user: "{{ actual_user if ansible_facts['distribution'] != 'MacOSX' else omit }}"
environment:
PATH: "{{ user_home }}/.cargo/bin:/opt/homebrew/bin:/usr/local/bin:{{ ansible_facts['env'].PATH }}"
# /opt/homebrew/opt/rustup/bin: brew's rustup never links its shims into
# /opt/homebrew/bin, so without it no cargo resolves on a rustup Mac. This
# task has no distribution gate, so it runs there too.
PATH: >-
{{ user_home }}/.cargo/bin:/opt/homebrew/opt/rustup/bin:/opt/homebrew/bin:/usr/local/bin:{{
ansible_facts['env'].PATH }}
register: contributor_alint
changed_when: "'Installed package' in contributor_alint.stdout | default('')"
failed_when: false
Expand Down
83 changes: 83 additions & 0 deletions ansible/roles/contributor/tasks/macbash.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
---
# macbash - flags GNU/Linux-only bash constructs that break on macOS (BSD), and
# fixes many in place.
#
# In `contributor` because CONTRIBUTING.md requires it for every shell change in
# this repo, so an outside contributor needs it to pass review. soe inherits it
# through meta/dependencies.
#
# Linux takes the bare binary from downloads.hyperi.io rather than the .deb/.rpm
# beside it, because those carry the version in their filename and would need a
# release lookup that this path avoids entirely.
#
# Upstream publishes a .sha256 next to each asset, so this fetch verifies a
# digest instead of trusting the transport.

- name: Install macbash (macOS)
when: ansible_facts['distribution'] == 'MacOSX'
block:
- name: Tap the hyperi-io formulae (macOS)
community.general.homebrew_tap:
name: hyperi-io/tap
state: present
become: false
environment: "{{ homebrew_env }}"

- name: Install macbash via Homebrew (macOS)
community.general.homebrew:
name: macbash
state: present
become: false
environment: "{{ homebrew_env }}"

rescue:
- name: Record that macbash did not install (macOS)
# noqa: var-naming[no-role-prefix] -- deploy_warnings is the shared
# accumulator reported by playbooks/main.yml post_tasks.
ansible.builtin.set_fact:
deploy_warnings: >-
{{ deploy_warnings | default([])
+ ['macbash: ' ~ (ansible_failed_result.msg | default('brew install failed'))] }}

- name: Install macbash (Linux)
when: ansible_facts['distribution'] in ['Ubuntu', 'Fedora']
block:
- name: Build the macbash asset name
ansible.builtin.set_fact:
contributor_macbash_asset: "macbash-linux-{{ hyperi_arch_deb }}"

- name: Fetch the published macbash checksum
ansible.builtin.uri:
url: https://downloads.hyperi.io/macbash/latest/{{ contributor_macbash_asset }}.sha256
return_content: true
register: contributor_macbash_sha_file
check_mode: false

# The file lists the bare binary and the tarball, so anchor on the exact
# asset name or the tarball's digest is picked up instead.
- name: Resolve the macbash checksum
ansible.builtin.set_fact:
contributor_macbash_sha: >-
{{ (contributor_macbash_sha_file.content.splitlines()
| select('match', '^[0-9a-f]+ +' ~ contributor_macbash_asset ~ '$')
| first).split() | first }}

# The digest comparison is what makes this idempotent: the asset name carries
# no version, so nothing else would notice a republished binary.
- name: Install the macbash binary (Linux)
ansible.builtin.get_url:
url: https://downloads.hyperi.io/macbash/latest/{{ contributor_macbash_asset }}
dest: /usr/local/bin/macbash
checksum: "sha256:{{ contributor_macbash_sha }}"
owner: root
group: root
mode: '0755'
become: true

rescue:
- name: Record that macbash did not install (Linux)
# noqa: var-naming[no-role-prefix] -- see the note above.
ansible.builtin.set_fact:
deploy_warnings: >-
{{ deploy_warnings | default([])
+ ['macbash: ' ~ (ansible_failed_result.msg | default('download failed'))] }}
14 changes: 14 additions & 0 deletions ansible/roles/contributor/tasks/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,20 @@
tags: ['contributor', 'maid']
tags: ['contributor', 'maid']

- name: Install git-scrub (git-history scrubber)
ansible.builtin.include_tasks:
file: git_scrub.yml
apply:
tags: ['contributor', 'git-scrub']
tags: ['contributor', 'git-scrub']

- name: Install macbash (macOS bash portability checker)
ansible.builtin.include_tasks:
file: macbash.yml
apply:
tags: ['contributor', 'macbash']
tags: ['contributor', 'macbash']

- name: Verify the contributor toolchain
ansible.builtin.include_tasks:
file: verify.yml
Expand Down
16 changes: 16 additions & 0 deletions ansible/roles/contributor/tasks/verify.yml
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,19 @@
become: "{{ ansible_facts['distribution'] != 'MacOSX' }}"
become_user: "{{ actual_user if ansible_facts['distribution'] != 'MacOSX' else omit }}"

# Non-blocking: `hyperi-ci check` drives neither, and both installs already
# record a warning on failure.
- name: Check the HyperI developer tools
ansible.builtin.command: "{{ item }} --version"
environment:
PATH: "/opt/homebrew/bin:/usr/local/bin:{{ user_home }}/.local/bin:{{ ansible_facts['env'].PATH }}"
loop:
- git-scrub
- macbash
register: verify_hyperi_tools
changed_when: false
failed_when: false

- name: Display contributor tools verification
ansible.builtin.debug:
msg: |
Expand All @@ -50,3 +63,6 @@
{% for r in verify_ci_tools.results %}
- {{ r.item }}: {{ r.stdout_lines[0] | default('NOT AVAILABLE') if r.rc == 0 else 'NOT AVAILABLE -- hyperi-ci check will skip its pass' }}
{% endfor %}
{% for r in verify_hyperi_tools.results %}
- {{ r.item }}: {{ r.stdout_lines[0] | default('NOT AVAILABLE') if r.rc == 0 else 'NOT AVAILABLE' }}
{% endfor %}
5 changes: 5 additions & 0 deletions ansible/roles/developer-rust/defaults/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,11 @@ rust_governor_enabled: true
# One rust build at a time per user; later invocations wait for the lock.
rust_governor_serialize: true

# A daemon started inside a governed build inherits the lock and holds it for
# its own lifetime, which would otherwise hang every later cargo command. After
# this many seconds the build proceeds unserialised and says so.
rust_governor_lock_wait_seconds: 1800

# Percentages of the HOST's physical RAM (systemd resolves them all against
# RAM, the swap cap included), so the same numbers ship everywhere. High
# throttles and reclaims; Max kills the build -- the build dies, never the
Expand Down
Loading
Loading