1 Commits

594 changed files with 18220 additions and 444038 deletions
@@ -1,99 +0,0 @@
name: Setup Ansible Environment
description: Prepare the environment for A13labs CI/CD pipelines
inputs:
access-token:
description: "Bitwarden access token"
required: true
project-id:
description: "Bitwarden project ID"
required: true
organization-id:
description: "Bitwarden organization ID"
required: true
ansible-user:
description: "The user to use for Ansible"
required: true
ssh-auth-socket:
description: "The SSH_AUTH_SOCK environment variable"
default: "/tmp/ssh_agent.sock"
host-group:
description: "The host group to setup the Ansible environment"
default: "public"
outputs:
ssh-auth-sock:
description: "The SSH_AUTH_SOCK environment variable"
value: ${{ steps.start-ssh-agent.outputs.ssh-auth-sock }}
hosts:
description: "The list of hosts in the host group"
value: ${{ steps.read-hosts.outputs.hosts }}
runs:
using: "composite"
steps:
- name: Install required python packages
shell: bash
run: |
python3 -m pip install -r requirements/ansible.txt
- name: Prepare SSH key
shell: bash
env:
BW_ACCESS_TOKEN: ${{ inputs.access-token }}
BW_PROJECT_ID: ${{ inputs.project-id }}
BW_ORGANIZATION_ID: ${{ inputs.organization-id }}
run: |
sectool ssh unlock ansible
mkdir -p ~/.ssh
chmod 700 ~/.ssh
cp ssh-keys/ansible/id_ecdsa ~/.ssh/id_ecdsa
chmod 600 ~/.ssh/id_ecdsa
- name: Read hosts from inventory
id: read-hosts
shell: bash
env:
HOST_GROUP: ${{ inputs.host-group }}
run: |
ansible-inventory -i inventory/hosts --list --yaml --limit $HOST_GROUP | grep -oE '([a-zA-Z0-9-]+\.)+[a-zA-Z]{2,}' | sort -u > /tmp/ansible_hosts
echo "hosts=$(cat /tmp/ansible_hosts)" >> $GITEA_OUTPUT
- name: Read SSH host keys from remote hosts
shell: bash
run: |
mkdir -p ~/.ssh
for HOST in $(cat /tmp/ansible_hosts); do
ssh-keyscan "$HOST" >> ~/.ssh/known_hosts
done
chmod 600 ~/.ssh/known_hosts
- name: Enable SSH host key algorithms
shell: bash
run: |
echo "Host *" >> ~/.ssh/config
echo " HostKeyAlgorithms +sk-ecdsa-sha2-nistp256@openssh.com, sk-ssh-ed25519@openssh.com"
chmod 600 ~/.ssh/config
- name: Start SSH agent and add SSH key
id: start-ssh-agent
shell: bash
env:
BW_ACCESS_TOKEN: ${{ inputs.access-token }}
BW_PROJECT_ID: ${{ inputs.project-id }}
BW_ORGANIZATION_ID: ${{ inputs.organization-id }}
SSH_AUTH_SOCK: ${{ inputs.ssh-auth-socket }}
run: |
ssh-agent -a $SSH_AUTH_SOCK > /dev/null
echo 'sectool vault get SSH_PASSWORD' > ~/.ssh_askpass && chmod +x ~/.ssh_askpass
DISPLAY=None SSH_ASKPASS=~/.ssh_askpass ssh-add ~/.ssh/id_ecdsa < /dev/null
rm ~/.ssh_askpass
echo "ssh-auth-sock=$SSH_AUTH_SOCK" >> $GITEA_OUTPUT
- name: Check SSH Connection to remote hosts
shell: bash
env:
ANSIBLE_USER: ${{ inputs.ansible-user }}
SSH_AUTH_SOCK: ${{ inputs.ssh-auth-socket }}
run: |
for HOST in $(cat /tmp/ansible_hosts); do
ssh "$ANSIBLE_USER@$HOST" echo "SSH connection to "$HOST" successful"
done
-24
View File
@@ -1,24 +0,0 @@
name: Setup Environment
description: Prepare the environment for A13labs CI/CD pipelines
outputs:
changed_files:
description: The list of changed files in the current pull request
value: ${{ steps.get_changed_files.outputs.changed_files }}
runs:
using: "composite"
steps:
- name: Get changed files
id: get_changed_files
shell: bash
run: |
CHANGED_FILES=$(git diff --name-only ${{ gitea.event.before }} ${{ gitea.sha }})
echo "changed_files=$CHANGED_FILES" >> $GITEA_OUTPUT
- name: Setup Sectool
uses: ./.gitea/actions/setup-sectool
- name: Install Python packages
shell: bash
run: |
python3 -m pip install --upgrade pip
-54
View File
@@ -1,54 +0,0 @@
name: Setup K8S Environment
description: Prepare the environment for A13labs CI/CD pipelines
inputs:
access-token:
description: "Bitwarden access token"
required: true
project-id:
description: "Bitwarden project ID"
required: true
organization-id:
description: "Bitwarden organization ID"
required: true
ansible-user:
description: "The user to use for Ansible"
required: true
k8s-target:
description: "The target host to deploy the K8S applications"
default: "microk8s"
ssh-auth-sock:
description: "The SSH_AUTH_SOCK environment variable"
default: "/tmp/ssh_agent.sock"
runs:
using: "composite"
steps:
- name: Copy Kube Config from remote host
shell: bash
env:
BW_ACCESS_TOKEN: ${{ inputs.access-token }}
BW_PROJECT_ID: ${{ inputs.project-id }}
BW_ORGANIZATION_ID: ${{ inputs.organization-id }}
ANSIBLE_USER: ${{ inputs.ansible-user }}
SSH_AUTH_SOCK: ${{ inputs.ssh-auth-sock }}
K8S_TARGET: ${{ inputs.k8s-target }}
run: |
mkdir -p ~/.kube
ansible -i inventory/hosts -u $ANSIBLE_USER -m fetch -a "src=/home/$ANSIBLE_USER/.kube/config dest=~/.kube/config flat=yes" $K8S_TARGET
- name: Forward K8S API Server port
shell: bash
env:
BW_ACCESS_TOKEN: ${{ inputs.access-token }}
BW_PROJECT_ID: ${{ inputs.project-id }}
BW_ORGANIZATION_ID: ${{ inputs.organization-id }}
ANSIBLE_USER: ${{ inputs.ansible-user }}
SSH_AUTH_SOCK: ${{ inputs.ssh-auth-sock }}
K8S_TARGET: ${{ inputs.k8s-target }}
run: |
ANSIBLE_HOST=$(ansible -i inventory/hosts -u $ANSIBLE_USER -m shell -a "hostname" $K8S_TARGET | grep -oE '([a-zA-Z0-9-]+\.)+[a-zA-Z]{2,}' | head -1)
IP_ADDR=$(ansible -i inventory/hosts -u $ANSIBLE_USER -m shell -a "curl -s ifconfig.me" $K8S_TARGET | grep -oE '([0-9]+\.){3}[0-9]+')
ssh -f -N -L 16443:localhost:16443 "$ANSIBLE_USER@$ANSIBLE_HOST"
sed -i "s/server: https:\/\/$IP_ADDR:16443/server: https:\/\/localhost:16443/g" ~/.kube/config
@@ -1,19 +0,0 @@
name: Setup OpenTofu Environment
description: Prepare the environment for A13labs CI/CD pipelines
runs:
using: "composite"
steps:
- name: Setup OpenTofu
shell: bash
run: |
TOFU_VERSION="1.9.1"
curl -sL "https://github.com/opentofu/opentofu/releases/download/v${TOFU_VERSION}/tofu_${TOFU_VERSION}_linux_amd64.zip" -o /tmp/tofu.zip
unzip -o /tmp/tofu.zip -d /usr/local/bin
rm /tmp/tofu.zip
tofu version
- name: Install required python packages
shell: bash
run: |
python3 -m pip install -r requirements/opentofu.txt
-16
View File
@@ -1,16 +0,0 @@
name: Setup Sectool
description: Download and install the sectool binary from GitHub releases
runs:
using: "composite"
steps:
- name: Download sectool binary
shell: bash
run: |
LATEST=$(curl -s https://api.github.com/repos/a13labs/sectool/releases/latest)
ASSET_URL=$(echo "$LATEST" | jq -r '.assets[] | select(.name | test("linux.*x64")) | .browser_download_url')
curl -sL "$ASSET_URL" -o /tmp/sectool.zip
unzip -o /tmp/sectool.zip -d /usr/local/bin
rm /tmp/sectool.zip
chmod +x /usr/local/bin/sectool
sectool version
@@ -1,37 +0,0 @@
name: Ansible Validate On Pull Request
on:
pull_request:
branches:
- main
paths:
- "ansible/**"
types:
- opened
- synchronize
- reopened
jobs:
ansible-validate:
runs-on: cicd-base
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Setup environment
uses: ./.gitea/actions/setup-env
- name: Install Python packages
run: |
python3 -m pip install -r requirements/ansible.txt
- name: Ansible Lint
run: |
cd ansible
ansible-lint .
- name: Ansible Playbook Syntax Check
run: |
cd ansible
ansible-playbook --syntax-check playbook_*.yml
-39
View File
@@ -1,39 +0,0 @@
name: Terraform AWS IAC Validate on Pull Request
on:
pull_request:
branches:
- main
paths:
- "terraform/iac/aws/**"
types:
- opened
- synchronize
- reopened
jobs:
iac-validate:
runs-on: cicd-base
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Setup environment
uses: ./.gitea/actions/setup-env
- name: Setup OpenTofu environment
uses: ./.gitea/actions/setup-opentofu-env
- name: OpenTofu Init and Validate
env:
BW_PROJECT_ID: ${{ secrets.BW_PROJECT_ID }}
BW_ORGANIZATION_ID: ${{ secrets.BW_ORGANIZATION_ID }}
BW_ACCESS_TOKEN: ${{ secrets.BW_ACCESS_TOKEN }}
run: |
cd terraform/iac/aws
sectool -f ../../sectool.json exec tofu init
sectool -f ../../sectool.json exec tofu validate
sectool -f ../../sectool.json exec tofu fmt -check
sectool -f ../../sectool.json exec tofu plan
@@ -1,39 +0,0 @@
name: Terraform ClouDNS IAC Validate on Pull Request
on:
pull_request:
branches:
- main
paths:
- "terraform/iac/cloudns/**"
types:
- opened
- synchronize
- reopened
jobs:
iac-validate:
runs-on: cicd-base
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Setup environment
uses: ./.gitea/actions/setup-env
- name: Setup OpenTofu environment
uses: ./.gitea/actions/setup-opentofu-env
- name: OpenTofu Init and Validate
env:
BW_PROJECT_ID: ${{ secrets.BW_PROJECT_ID }}
BW_ORGANIZATION_ID: ${{ secrets.BW_ORGANIZATION_ID }}
BW_ACCESS_TOKEN: ${{ secrets.BW_ACCESS_TOKEN }}
run: |
cd terraform/iac/cloudns
sectool -f ../../sectool.json exec tofu init
sectool -f ../../sectool.json exec tofu validate
sectool -f ../../sectool.json exec tofu fmt -check
sectool -f ../../sectool.json exec tofu plan
@@ -1,39 +0,0 @@
name: Terraform Contabo IAC Validate on Pull Request
on:
pull_request:
branches:
- main
paths:
- "terraform/iac/contabo/**"
types:
- opened
- synchronize
- reopened
jobs:
iac-validate:
runs-on: cicd-base
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Setup environment
uses: ./.gitea/actions/setup-env
- name: Setup OpenTofu environment
uses: ./.gitea/actions/setup-opentofu-env
- name: OpenTofu Init and Validate
env:
BW_PROJECT_ID: ${{ secrets.BW_PROJECT_ID }}
BW_ORGANIZATION_ID: ${{ secrets.BW_ORGANIZATION_ID }}
BW_ACCESS_TOKEN: ${{ secrets.BW_ACCESS_TOKEN }}
run: |
cd terraform/iac/contabo
sectool -f ../../sectool.json exec tofu init
sectool -f ../../sectool.json exec tofu validate
sectool -f ../../sectool.json exec tofu fmt -check
sectool -f ../../sectool.json exec tofu plan
-63
View File
@@ -1,63 +0,0 @@
name: OpenTofu K8s Apps Validate on Pull Request
on:
pull_request:
branches:
- main
paths:
- "terraform/apps/**"
types:
- opened
- synchronize
- reopened
jobs:
iac-validate:
runs-on: cicd-base
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Setup environment
uses: ./.gitea/actions/setup-env
- name: Setup Ansible Environment
id: setup_ansible_env
uses: ./.gitea/actions/setup-ansible-env
with:
project-id: ${{ secrets.BW_PROJECT_ID }}
organization-id: ${{ secrets.BW_ORGANIZATION_ID }}
access-token: ${{ secrets.BW_ACCESS_TOKEN }}
ansible-user: ${{ secrets.ANSIBLE_USER }}
host-group: microk8s
- name: Setup OpenTofu Environment
uses: ./.gitea/actions/setup-opentofu-env
- name: Setup Kubernetes Environment
uses: ./.gitea/actions/setup-k8s-env
with:
project-id: ${{ secrets.BW_PROJECT_ID }}
organization-id: ${{ secrets.BW_ORGANIZATION_ID }}
access-token: ${{ secrets.BW_ACCESS_TOKEN }}
ansible-user: ${{ secrets.ANSIBLE_USER }}
- name: OpenTofu Init and Validate
env:
BW_PROJECT_ID: ${{ secrets.BW_PROJECT_ID }}
BW_ORGANIZATION_ID: ${{ secrets.BW_ORGANIZATION_ID }}
BW_ACCESS_TOKEN: ${{ secrets.BW_ACCESS_TOKEN }}
run: |
cd terraform/apps/dev-01
sectool -f ../../sectool.json exec tofu init
sectool -f ../../sectool.json exec tofu validate
sectool -f ../../sectool.json exec tofu fmt -check
sectool -f ../../sectool.json exec tofu plan
cd terraform/apps/prod-01
sectool -f ../../sectool.json exec tofu init
sectool -f ../../sectool.json exec tofu validate
sectool -f ../../sectool.json exec tofu fmt -check
sectool -f ../../sectool.json exec tofu plan
@@ -1,39 +0,0 @@
name: Terraform Scaleway IAC Validate on Pull Request
on:
pull_request:
branches:
- main
paths:
- "terraform/iac/scaleway/**"
types:
- opened
- synchronize
- reopened
jobs:
iac-validate:
runs-on: cicd-base
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Setup environment
uses: ./.gitea/actions/setup-env
- name: Setup OpenTofu environment
uses: ./.gitea/actions/setup-opentofu-env
- name: OpenTofu Init and Validate
env:
BW_PROJECT_ID: ${{ secrets.BW_PROJECT_ID }}
BW_ORGANIZATION_ID: ${{ secrets.BW_ORGANIZATION_ID }}
BW_ACCESS_TOKEN: ${{ secrets.BW_ACCESS_TOKEN }}
run: |
cd terraform/iac/scaleway
sectool -f ../../sectool.json exec tofu init
sectool -f ../../sectool.json exec tofu validate
sectool -f ../../sectool.json exec tofu fmt -check
sectool -f ../../sectool.json exec tofu plan
-130
View File
@@ -1,130 +0,0 @@
# Copilot Instructions for a13labs.infra
This repository manages infrastructure mostly with Ansible and OpenTofu/Terraform.
These instructions define how Terraform app modules should be developed, deployed, and debugged.
## Scope and Structure
- App roots:
- terraform/apps/dev-01
- terraform/apps/prod-01
- Reusable modules:
- terraform/modules/apps
- terraform/modules/db
- terraform/modules/utils
- New app modules should be created under terraform/modules/apps/<module-name>.
- Use terraform/modules/apps/template as the base shape when creating a new app module.
## Cluster References
Always cover both clusters in planning and reviews:
- dev-01
- App root: terraform/apps/dev-01
- Kubernetes context: microk8s-dev-01
- pro-01
- App root: terraform/apps/prod-01
- Kubernetes context: microk8s-prod-01
Note: The production folder name is prod-01, while discussions may refer to it as pro-01.
## Module Development Standards
When adding a new app module in terraform/modules/apps/<name>:
1. Create the standard files:
- provider.tf
- variables.tf
- config.tf
- main.tf
- ingress.tf (if exposed over HTTP/HTTPS)
2. Provider constraints should match repository conventions:
- required_version ~>1.8
- kubernetes provider version 2.36.0
3. Keep names consistent across namespace, service account, deployment labels, and service selectors.
4. Use locals in config.tf for derived paths and normalized values.
5. Treat secrets as Terraform inputs (sensitive variables), not hardcoded literals.
6. Prefer explicit image tags instead of latest for predictable rollouts.
## Security and Hardening Defaults
For Kubernetes deployments, prefer hardened defaults unless an app requires exceptions:
- Disable service account token automount at both SA and pod level.
- Use pod security context with non-root UID/GID and RuntimeDefault seccomp.
- Use container security context:
- allow_privilege_escalation = false
- privileged = false
- drop all capabilities
- read_only_root_filesystem = true
- Provide writable mounts only where required (for example cache path and /tmp via emptyDir).
## Integrating a Module into an App Root
For each cluster root (dev-01 and pro-01):
1. Register module in apps.tf.
2. Add needed locals in config.tf (FQDN, path, issuer, feature toggles).
3. Add DNS record entries when the app is externally reachable.
4. Add sensitive input variables in secrets.tf.
5. Map TF_VAR entries in sectool.env.
## Secrets Pattern (sectool + TF_VAR)
Secret flow is:
1. Declare sensitive variables in secrets.tf.
2. Map TF_VAR_<name> in terraform/apps/<cluster>/sectool.env.
3. Keep value source in vault/environment, not in repository.
4. Run OpenTofu through sectool so variables are injected.
Example workflow from an app root:
- sectool -f ../../../sectool.json exec tofu init
- sectool -f ../../../sectool.json exec tofu validate
- sectool -f ../../../sectool.json exec tofu plan
- sectool -f ../../../sectool.json exec tofu apply -auto-approve
## OpenTofu and Deployment Workflow
Preferred command flow per cluster root:
1. Format and validate first:
- tofu fmt
- sectool -f ../../../sectool.json exec tofu validate
2. Plan before apply:
- sectool -f ../../../sectool.json exec tofu plan
3. Apply:
- sectool -f ../../../sectool.json exec tofu apply -auto-approve
Targeted apply can be used for emergency recovery, then follow with a full plan to detect drift.
## Runtime Debugging Playbook (kubectl)
When a deployment fails (CrashLoopBackOff):
1. Inspect pod state and events:
- kubectl -n <ns> get pods -o wide
- kubectl -n <ns> describe pod <pod>
2. Check logs:
- kubectl -n <ns> logs <pod> --all-containers=true --tail=200
- kubectl -n <ns> logs <pod> --previous --all-containers=true --tail=200
3. Validate rollout:
- kubectl -n <ns> rollout status deployment/<name> --timeout=120s
4. If needed, test temporary env override with kubectl set env, verify recovery, then persist in Terraform module.
## Known SearXNG Behavior in This Repo
- SearXNG may fail on startup with IPv6 bind on hosts without IPv6 socket support.
- Set GRANIAN_HOST to 0.0.0.0 in module-managed configuration.
- Keep image tag pinned in app roots to avoid unexpected upstream behavior changes.
## Review Checklist for Copilot Changes
Before proposing Terraform changes:
1. Confirm changes cover both dev-01 and pro-01 impact.
2. Confirm secrets are wired via sensitive variables + sectool mapping.
3. Confirm image tags are pinned.
4. Confirm deployment hardening defaults are retained.
5. Confirm commands use sectool + tofu in examples and runbooks.
@@ -0,0 +1,53 @@
name: Ansible Apply because of host_vars change
on:
push:
branches:
- main
paths:
- ansible/host_vars/*.alexpires.me.yml
- ansible/host_vars/*.a13labs.me.yml
- ansible/host_vars/*.a13labs.pt.yml
jobs:
ansible-apply:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Setup environment
id: setup_env
uses: ./.github/actions/setup-env
- name: Setup Ansible environment
uses: ./.github/actions/setup-ansible-env
with:
project-id: ${{ secrets.BW_PROJECT_ID }}
organization-id: ${{ secrets.BW_ORGANIZATION_ID }}
access-token: ${{ secrets.BW_ACCESS_TOKEN }}
ansible-user: ${{ secrets.ANSIBLE_USER }}
trusted-hosts: ${{ secrets.TRUSTED_HOSTS }}
- name: Apply the playbook that triggered the workflow
env:
BW_PROJECT_ID: ${{ secrets.BW_PROJECT_ID }}
BW_ORGANIZATION_ID: ${{ secrets.BW_ORGANIZATION_ID }}
BW_ACCESS_TOKEN: ${{ secrets.BW_ACCESS_TOKEN }}
ANSIBLE_USER: ${{ secrets.ANSIBLE_USER }}
SSH_AUTH_SOCK: /tmp/ssh_agent.sock
CHANGED_FILES: ${{ steps.setup_env.outputs.CHANGED_FILES }}
run: |
cd ansible
sectool -f ../sectool.json exec ansible-playbook -u $ANSIBLE_USER playbook_fail2ban.yml
sectool -f ../sectool.json exec ansible-playbook -u $ANSIBLE_USER playbook_geoip.yml
sectool -f ../sectool.json exec ansible-playbook -u $ANSIBLE_USER playbook_duo_security.yml
sectool -f ../sectool.json exec ansible-playbook -u $ANSIBLE_USER playbook_encryption.yml
sectool -f ../sectool.json exec ansible-playbook -u $ANSIBLE_USER playbook_microk8s.yml
sectool -f ../sectool.json exec ansible-playbook -u $ANSIBLE_USER playbook_gitea.yml
sectool -f ../sectool.json exec ansible-playbook -u $ANSIBLE_USER playbook_nextcloud.yml
sectool -f ../sectool.json exec ansible-playbook -u $ANSIBLE_USER playbook_websites.yml
sectool -f ../sectool.json exec ansible-playbook -u $ANSIBLE_USER playbook_m3uproxy.yml
sectool -f ../sectool.json exec ansible-playbook -u $ANSIBLE_USER playbook_monitoring.yml
sectool -f ../sectool.json exec ansible-playbook -u $ANSIBLE_USER playbook_rustdesk.yml
@@ -0,0 +1,45 @@
name: Ansible Apply Playbook that Triggered the Workflow
on:
push:
branches:
- main
paths:
- ansible/playbook_*.yml
- '!ansible/playbook_websites.yml'
- '!ansible/playbook_hardening*.yml'
jobs:
ansible-apply:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Setup environment
id: setup_env
uses: ./.github/actions/setup-env
- name: Setup Ansible environment
uses: ./.github/actions/setup-ansible-env
with:
project-id: ${{ secrets.BW_PROJECT_ID }}
organization-id: ${{ secrets.BW_ORGANIZATION_ID }}
access-token: ${{ secrets.BW_ACCESS_TOKEN }}
ansible-user: ${{ secrets.ANSIBLE_USER }}
trusted-hosts: ${{ secrets.TRUSTED_HOSTS }}
- name: Apply the playbook that triggered the workflow
env:
BW_PROJECT_ID: ${{ secrets.BW_PROJECT_ID }}
BW_ORGANIZATION_ID: ${{ secrets.BW_ORGANIZATION_ID }}
BW_ACCESS_TOKEN: ${{ secrets.BW_ACCESS_TOKEN }}
ANSIBLE_USER: ${{ secrets.ANSIBLE_USER }}
SSH_AUTH_SOCK: /tmp/ssh_agent.sock
CHANGED_FILES: ${{ steps.setup_env.outputs.CHANGED_FILES }}
run: |
cd ansible
for PLAYBOOK in $(echo $CHANGED_FILES | grep -oP 'playbook_\K\w+'); do
sectool -f ../sectool.json exec ansible-playbook -u $ANSIBLE_USER playbook_$PLAYBOOK.yml
done
@@ -29,6 +29,7 @@ jobs:
organization-id: ${{ secrets.BW_ORGANIZATION_ID }}
access-token: ${{ secrets.BW_ACCESS_TOKEN }}
ansible-user: ${{ secrets.ANSIBLE_USER }}
trusted-hosts: ${{ secrets.TRUSTED_HOSTS }}
- name: Apply the playbook that triggered the workflow
env:
@@ -37,6 +37,7 @@ jobs:
organization-id: ${{ secrets.BW_ORGANIZATION_ID }}
access-token: ${{ secrets.BW_ACCESS_TOKEN }}
ansible-user: ${{ secrets.ANSIBLE_USER }}
trusted-hosts: ${{ secrets.TRUSTED_HOSTS }}
- name: Run hugo_build command
run: bin/hugo_build
@@ -1,11 +1,13 @@
name: OpenTofu K8S Apps apply
on:
schedule:
- cron: "0 2 * * 0"
push:
branches:
- main
paths:
- "terraform/apps/contabo/**"
- "terraform/k8sapps/**"
jobs:
iac-apply:
runs-on: ubuntu-latest
@@ -35,7 +37,6 @@ jobs:
- name: Setup Kubernetes Environment
uses: ./.github/actions/setup-k8s-env
with:
host: prod-01.alexpires.me
project-id: ${{ secrets.BW_PROJECT_ID }}
organization-id: ${{ secrets.BW_ORGANIZATION_ID }}
access-token: ${{ secrets.BW_ACCESS_TOKEN }}
+1 -2
View File
@@ -42,5 +42,4 @@ drafts/*
.molecule
*.auto.tfvars
dev/*
.ansible
*.x.c
.ansible
-486
View File
@@ -1,486 +0,0 @@
---
name: ansible-develop
description: >
Use when developing, writing, or debugging Ansible playbooks and roles in this
repository. Covers playbook and role conventions, inventory group targeting,
molecule-podman testing, variable scoping (defaults/vars/host_vars), Jinja2
templates, tag-based execution, and known patterns such as the podman shared
tasks helper and dual Debian/RedHat OS-family blocks.
---
# Ansible Playbook & Role Skill
## Purpose
Use this skill when creating new playbooks or roles in `ansible/`. It covers
project conventions for structure, testing, variable management, secrets, and
execution patterns used across all existing automation.
---
## Repository Layout
```
ansible/
ansible.cfg # inventory = ../inventory/hosts
requirements.yml # collections: ansible.posix, community.general,
# containers.podman, community.libvirt, ...
.yamllint.yml # 120-char line limit, extended default rules
playbook_*.yml # top-level entry points (run from repo root)
tasks/ # shared task fragments (e.g. tasks/podman.yml)
roles/
<name>/
defaults/main.yml # public defaults (overridable by callers)
vars/main.yml # internal values (non-overridable)
tasks/main.yml # entry point; include_ or block per sub-feature
handlers/main.yml # notify handlers (services, reloads)
templates/ # Jinja2 files (.j2 extension)
files/ # static files copied as-is
meta/main.yml # galaxy metadata, min_ansible_version, dependencies
molecule/default/ # tests: converge.yml, prepare.yml, verify.yml, molecule.yml
tests/inventory # small static inventory for role tests
tests/test.yml # molecule verifier
.yamllint.yml # (optional) role-level yamllint overrides
README.md # role documentation
inventory/
hosts # host groups (ini-format inventory)
host_vars/ # per-host YAML variable files
```
Playbooks live in `ansible/` and are run from the repository root. The
inventory path is relative: `../inventory/hosts` (as declared in
`ansible/ansible.cfg`).
---
## Inventory & Group Targeting
The inventory at `inventory/hosts` uses **ini-format groups**. Playbooks target
groups via `hosts: <groupname>`. Group names follow kebab-case convention.
Existing groups:
| Group | Hosts |
|-------------------|------------------------------------------------|
| `all` | prod-01, dev-01, vh-01, gpu-01 |
| `hardening` | prod-01, dev-01, vh-01, gpu-01 |
| `ubuntu` | prod-01, dev-01 |
| `fedora` | vh-01, gpu-01 |
| `microk8s` | prod-01, dev-01 |
| `monitoring` | prod-01 |
| `duo` | prod-01, dev-01, vh-01, gpu-01 |
| `virtualization` | vh-01 |
| `ollama` | gpu-01 |
| `comfyui` | gpu-01 |
| `microk8s` | prod-01, dev-01 |
| `certbot` | vh-01, gpu-01 |
| `nvidia` | gpu-01 |
| `amd` | gpu-01 |
| `public` | prod-01 |
| `private` | dev-01, vh-01, gpu-01 |
| `windows` | ci-01 |
When adding a new playbook, **add a new group** in `inventory/hosts` and target
it from the playbook.
---
## Playbook Patterns
Two patterns are used. Choose the one that fits the task.
### Pattern 1 — Role-based (preferred for reusable logic)
Use when the task is encapsulated in a role or may be reused across playbooks.
```yaml
---
- name: My feature setup
hosts: mygroup
gather_facts: true
roles:
- role: "my_role"
tags:
- roles
- roles::my_role
```
### Pattern 2 — Inline tasks
Use for single-shot playbooks that configure one specific host with custom logic
(e.g., virt_host, encryption).
```yaml
---
- name: My inline setup
hosts: mygroup
become: true
gather_facts: true
tasks:
- name: Ensure the system is Debian
ansible.builtin.assert:
that:
- ansible_os_family == 'Debian'
fail_msg: "This playbook only supports Debian hosts."
- name: Install required packages
become: true
ansible.builtin.apt:
name:
- package-one
- package-two
state: present
update_cache: true
```
### Shared task fragments
Complex operations (e.g. podman pod/container lifecycle) go into `tasks/` and
are included via `ansible.builtin.include_tasks`. See `tasks/podman.yml` for
the pattern: required-var checks, OS-family `set_fact`, `become_user` blocks,
and loop-based resource creation.
---
## Role Development
### Minimal role directory structure
```
roles/my_role/
defaults/main.yml # public defaults
tasks/main.yml # entry point
handlers/main.yml # notify handlers
templates/ # Jinja2 files
meta/main.yml # galaxy metadata
molecule/default/ # tests
```
### defaults/main.yml
Declare **all** public variables here with safe defaults. This is how callers
override behavior.
```yaml
---
my_feature_enabled: true
my_feature_port: 8080
my_feature_users: []
```
Use `lookup('env', 'VAR_NAME')` for secrets with empty default:
```yaml
my_api_key: "{{ lookup('env', 'MY_API_KEY') | default('') }}"
```
### tasks/main.yml
Use `block` + `when` for OS-family conditional logic:
```yaml
---
- name: Install packages (Debian)
when: ansible_os_family == 'Debian'
block:
- name: Install deps
become: true
ansible.builtin.apt:
name: "{{ item }}"
state: present
update_cache: true
loop:
- package-one
- name: Install packages (RedHat)
when: ansible_os_family == 'RedHat'
block:
- name: Install deps
become: true
ansible.builtin.dnf:
name: "{{ item }}"
state: present
loop:
- package-one
```
Use `include_tasks` for sub-features, tagged separately:
```yaml
- name: Include volume provisioning
ansible.builtin.include_tasks: volume.yml
tags:
- roles::my_role::volume
- name: Include KMS setup
ansible.builtin.include_tasks: kms.yml
tags:
- roles::my_role::kms
```
### meta/main.yml
Always declare `galaxy_info` with namespace `a13labs`, author
`Alexandre Pires`, and company `A13Labs`:
```yaml
---
galaxy_info:
author: Alexandre Pires
description: Role to do something
company: A13Labs
role_name: my_role
namespace: a13labs
license: MIT
min_ansible_version: "2.1"
```
### Tag conventions
Use nested tags following the pattern `roles::<role_name>::<feature>`. Top-level
playbooks use `roles` and `roles::<role_name>`.
---
## Molecule Testing (Podman)
Every role should have a `molecule/default/` test scenario using the Podman
driver.
### molecule.yml
```yaml
---
driver:
name: podman
platforms:
- name: instance
image: ubuntu:latest
pre_build_image: true
volumes:
- /sys/fs/cgroup:/sys/fs/cgroup:ro
privileged: true
provisioner:
name: ansible
env:
MY_ROLE_VAR: "test_value"
playbooks:
converge: converge.yml
prepare: prepare.yml
verifier:
name: ansible
```
### prepare.yml
Must install python3 and sudo using raw (facts are unavailable in containers):
```yaml
---
- name: Prepare
hosts: all
gather_facts: false
tasks:
- name: Update cache
ansible.builtin.raw: apt update
- name: Install required packages
ansible.builtin.raw: apt install -y python3 sudo
```
### converge.yml
```yaml
---
- name: Converge
hosts: all
gather_facts: true
tasks:
- name: Include my_role
ansible.builtin.include_role:
name: "my_role"
```
### Testing
Run from the role directory:
```bash
cd ansible/roles/my_role
molecule test
```
This runs: prepare -> converge -> verify -> destroy.
**Important**: Most containers lack systemd. Guard service-related tasks:
`when: ansible_service_mgr == "systemd"`.
---
## Secrets & Environment Variables
Secrets are injected via `sectool` using `sectool.json` at the repository root.
The `ANSIBLE_USER` env var must be set before opening opencode.
Secrets in roles use `lookup('env', 'VAR_NAME')``sectool` injects them
automatically when playbooks are run through `sectool exec`.
Pattern in role defaults:
```yaml
api_key: "{{ lookup('env', 'MY_API_KEY') | default('') }}"
```
Pattern in host_vars (for host-level secrets):
```yaml
cloudns_auth_id: "{{ lookup('env', 'CLOUDNS_AUTH_ID') }}"
cloudns_auth_password: "{{ lookup('env', 'CLOUDNS_PASSWORD') }}"
```
Never hardcode secrets. Always use `lookup('env', ...)` with a `| default('')`
fallback.
---
## Execution
All Ansible commands must be run from the **`ansible/` directory** using
`sectool` to inject secrets:
```bash
cd ansible/
# Role-based playbook
sectool -f ../sectool.json exec ansible-playbook -u $ANSIBLE_USER playbook_myfeature.yml
# Tag-based partial run
sectool -f ../sectool.json exec ansible-playbook -u $ANSIBLE_USER playbook_myfeature.yml --tags roles::my_role::volume
# Limit to specific group
sectool -f ../sectool.json exec ansible-playbook -u $ANSIBLE_USER playbook_myfeature.yml --limit mygroup
# Role tests (molecule runs independently, no sectool wrapper needed)
cd ansible/roles/my_role && molecule test
```
`$ANSIBLE_USER` must be set in the shell before opening opencode.
---
## Linting
### yamllint
```bash
cd ansible
yamllint -c .yamllint.yml .
```
Rules: 120-char line width (warning), forbid implicit/explicit octal, extended
default ruleset.
### ansible-lint
If available, run from the `ansible/` directory:
```bash
cd ansible
ansible-lint
```
---
## Known Patterns
### Dual OS-family support
Always support both Debian and RedHat families when packages differ:
```yaml
- name: Set binary path (Debian)
when: ansible_os_family == 'Debian'
ansible.builtin.set_fact:
podman_binary: /usr/bin/podman
- name: Set binary path (RedHat)
when: ansible_os_family == 'RedHat'
ansible.builtin.set_fact:
podman_binary: /usr/sbin/podman
```
### become_user blocks
When a task needs to run as a different user (e.g. podman operations), use a
`become: true` + `become_user` block:
```yaml
- name: Run tasks as podman user
become: true
become_user: "{{ podman_user }}"
block:
- name: Build images
containers.podman.podman_image:
...
```
### systemd service generation
Use `ansible.builtin.template` to generate `.service` files from `.j2`
templates, then `daemon_reload` + `systemd` enable:
```yaml
- name: Create systemd service file
become: true
ansible.builtin.template:
src: myservice.service.j2
dest: "/etc/systemd/system/my-service.service"
mode: "0644"
notify: Reload daemon
handlers:
- name: Reload daemon
changed_when: false
ansible.builtin.systemd:
daemon_reload: true
```
### nmcli bridge creation
Network bridge configuration uses `nmcli connection add` with `async` +
`wait_for` to handle reconnection after interface changes:
```yaml
- name: Create bridge
ansible.builtin.command: nmcli connection add ...
register: bridge_created
async: 10
poll: 0
- name: Wait for reconnection
ansible.builtin.wait_for:
host: "{{ bridge_ip }}"
port: 22
timeout: 30
delegate_to: localhost
```
---
## Review Checklist
Before proposing a new playbook or role:
- [ ] Playbook targets a defined group in `inventory/hosts` (add one if needed).
- [ ] Role has `defaults/main.yml` with all public variables + safe defaults.
- [ ] Secrets use `lookup('env', 'VAR') | default('')`, never hardcoded.
- [ ] Dual OS-family support when package names differ.
- [ ] Tasks tagged with `roles::<role_name>::<feature>`.
- [ ] Molecule `molecule/default/` scenario with prepare + converge.
- [ ] `molecule.yml` uses Podman driver with `privileged: true` + cgroup mount.
- [ ] Line length within 120 characters.
- [ ] `yamllint -c ansible/.yamllint.yml ansible/` passes.
- [ ] Service tasks guarded by `when: ansible_service_mgr == "systemd"` in
molecule containers.
@@ -1,301 +0,0 @@
---
name: terraform-develop-app
description: >
Use when developing, deploying, or debugging Terraform app modules in this
repository. Covers module scaffolding, secrets injection with sectool and
TF_VAR, OpenTofu command workflow, Kubernetes runtime debugging with kubectl,
hardening defaults for pod and container security contexts, and known quirks
such as the SearXNG IPv6 Granian bind issue. Also covers integrating modules
into dev-01 and prod-01 app roots including DNS records, sectool.env mapping,
and targeted apply recovery patterns.
---
# Terraform App Module Skill
## Purpose
Use this skill when developing, deploying, or debugging Terraform app modules in this repository. It covers module authoring conventions, secrets injection with sectool, OpenTofu command workflow, Kubernetes runtime debugging, and known behavioral quirks discovered in production.
---
## Repository Layout
```
terraform/
apps/
dev-01/ # dev cluster root
prod-01/ # prod cluster root
modules/
apps/ # reusable app modules
db/ # database modules
utils/ # utility modules
```
App roots are the deployment entry points. Reusable logic lives in modules and is consumed by app roots.
Use `terraform/modules/apps/template` as the baseline shape when creating a new module.
---
## Cluster References
| Name | Folder | Kubernetes context |
|--------|--------------------------|----------------------|
| dev-01 | terraform/apps/dev-01 | microk8s-dev-01 |
| pro-01 | terraform/apps/prod-01 | microk8s-prod-01 |
When planning or reviewing changes, always consider both clusters. The production folder is named `prod-01`; discussions may call it `pro-01`.
---
## Creating a New App Module
Create `terraform/modules/apps/<name>/` with these files:
### provider.tf
```hcl
terraform {
required_version = "~>1.8"
required_providers {
kubernetes = {
source = "hashicorp/kubernetes"
version = "2.36.0"
}
}
}
```
### variables.tf
Declare at minimum:
```hcl
variable "persistent_folder" {
description = "Path for persistent data on the host"
type = string
}
variable "fqdn" {
description = "FQDN for the service"
type = string
}
variable "tag" {
description = "Image tag to deploy"
type = string
default = "x.y.z" # always pin, never use latest
}
```
Add sensitive variables at module level only when the module itself needs to inject them into a Kubernetes secret. If the secret is passed from the app root, declare it sensitive there.
### config.tf
Use locals to normalize inputs. Do not append the app name to `persistent_folder` if the caller already passes an app-specific path:
```hcl
locals {
app_folder = var.persistent_folder
app_version = var.tag
app_fqdn = var.fqdn
}
```
### main.tf resource order
1. `kubernetes_namespace`
2. `kubernetes_service_account` — with `automount_service_account_token = false`
3. `kubernetes_config_map` (non-sensitive config)
4. `kubernetes_secret` (sensitive data)
5. `kubernetes_deployment`
6. `kubernetes_service`
### ingress.tf (when HTTP/HTTPS exposed)
```hcl
resource "kubernetes_ingress_v1" "<name>" {
metadata {
annotations = {
"kubernetes.io/ingress.class" = "public"
"cert-manager.io/cluster-issuer" = var.issuer
"kubernetes.io/tls-acme" = "true"
"nginx.ingress.kubernetes.io/ssl-redirect" = "true"
}
}
...
}
```
Accept `issuer` as a variable (default `"letsencrypt-prod"`) so the same module works for both clusters and internal CA scenarios.
---
## Hardening Defaults
Apply these to every new deployment unless there is a documented exception:
```hcl
spec {
automount_service_account_token = false
security_context {
run_as_non_root = true
run_as_user = <uid>
run_as_group = <gid>
fs_group = <gid>
seccomp_profile {
type = "RuntimeDefault"
}
}
container {
security_context {
allow_privilege_escalation = false
privileged = false
read_only_root_filesystem = true
capabilities {
drop = ["ALL"]
}
}
# provide writable scratch via emptyDir, not root filesystem
volume_mount {
name = "<name>-tmp"
mount_path = "/tmp"
}
}
volume {
name = "<name>-tmp"
empty_dir {}
}
}
```
---
## Integrating a Module into an App Root
Do this for each cluster root:
1. Add a `module` block in `apps.tf`:
```hcl
module "myapp" {
source = "../../modules/apps/myapp"
persistent_folder = local.myapp_folder
fqdn = local.myapp_fqdn
tag = "x.y.z"
secret_key = var.myapp_secret_key
}
```
2. Add locals in `config.tf`:
```hcl
myapp_folder = "/mnt/md0/myapp"
myapp_fqdn = "myapp.${local.lab_domain}"
```
3. Add a DNS CNAME record inside `local.zones[0].records`:
```hcl
{ name = "myapp", type = "CNAME", content = "dev-01.${local.lab_domain}." }
```
4. Add sensitive variable in `secrets.tf`:
```hcl
variable "myapp_secret_key" {
description = "Secret key for MyApp"
type = string
sensitive = true
}
```
5. Map the env variable in `sectool.env`:
```
TF_VAR_myapp_secret_key=$MYAPP_SECRET_KEY
```
---
## Secrets Pattern (sectool + TF_VAR)
Secrets are sourced from Bitwarden via `sectool` using `sectool.json` at the repository root.
Flow:
1. Store secret value in Bitwarden under the project referenced in `sectool.json`.
2. Declare the variable in `secrets.tf` with `sensitive = true`.
3. Map `TF_VAR_<name>=$ENV_VAR_NAME` in `terraform/apps/<cluster>/sectool.env`.
4. Run all OpenTofu commands via `sectool exec` so variables are injected automatically.
---
## OpenTofu Workflow
Run from the relevant app root (e.g., `terraform/apps/dev-01`):
```bash
# Initialise
sectool -f ../../../sectool.json exec tofu init
# Format and validate
tofu fmt
sectool -f ../../../sectool.json exec tofu validate
# Plan
sectool -f ../../../sectool.json exec tofu plan
# Apply
sectool -f ../../../sectool.json exec tofu apply -auto-approve
# Targeted apply (emergency recovery only — always follow with a full plan)
sectool -f ../../../sectool.json exec tofu apply -auto-approve -target=module.<name>
```
---
## Runtime Debugging Playbook
When a pod is in CrashLoopBackOff:
```bash
# 1. Inspect state and events
kubectl -n <ns> get pods -o wide
kubectl -n <ns> describe pod <pod>
# 2. Check current and previous logs
kubectl -n <ns> logs <pod> --all-containers=true --tail=200
kubectl -n <ns> logs <pod> --previous --all-containers=true --tail=200
# 3. Validate rollout after a fix
kubectl -n <ns> rollout status deployment/<name> --timeout=120s
# 4. Test a runtime env override before persisting in Terraform
kubectl -n <ns> set env deployment/<name> KEY=value
# verify recovery, then add KEY to the module ConfigMap and re-apply
```
---
## Known Quirks
### SearXNG — IPv6 bind failure
The `searxng/searxng` image defaults to `GRANIAN_HOST=::` (IPv6). On nodes without IPv6 socket support this causes an immediate crash:
```
RuntimeError: Address family not supported by protocol (os error 97)
```
Fix: set `GRANIAN_HOST = "0.0.0.0"` in the module ConfigMap.
---
## Review Checklist
Before proposing or applying any Terraform change:
- [ ] Changes scoped to both dev-01 and prod-01 where relevant.
- [ ] Sensitive inputs declared in `secrets.tf` + mapped in `sectool.env`.
- [ ] Image tag is pinned (not `latest`).
- [ ] Hardening defaults present (non-root, drop-all capabilities, read-only root fs).
- [ ] Commands in examples use `sectool -f ../../../sectool.json exec tofu`.
@@ -1,115 +0,0 @@
---
name: terraform-update-app-version
description: >
Use when updating a container image version for an app managed by Terraform in
this repository. Covers how to find where the tag is set, update dev-01 and
prod-01 app roots safely, run sectool-wrapped OpenTofu validation and plan,
apply changes, and verify rollout with kubectl.
---
# Terraform Update App Version Skill
## Purpose
Use this skill when you need to update the deployed container image version for an app module.
This repository usually sets the image tag in the app root module call (`terraform/apps/<cluster>/apps.tf`) and passes it to the module variable `tag`, then to `local.app_version` inside the module.
Always use pinned tags. Do not use `latest`.
---
## Scope
Update both clusters unless the request explicitly says otherwise:
- dev-01: `terraform/apps/dev-01`
- pro-01 folder: `terraform/apps/prod-01`
Note: production is often called "pro-01" in discussion, but the folder name is `prod-01`.
---
## Where to Change the Version
Common location:
- `terraform/apps/<cluster>/apps.tf`
Pattern:
```hcl
module "<app_name>" {
source = "../../modules/apps/<app_name>"
...
tag = "<image-tag>"
}
```
Module wiring pattern:
- `terraform/modules/apps/<app_name>/variables.tf` defines `variable "tag"`
- `terraform/modules/apps/<app_name>/config.tf` typically sets `local.app_version = var.tag`
- `terraform/modules/apps/<app_name>/main.tf` uses `${local.app_version}` in image reference
If tag is not in the app root, check the module defaults and root overrides before editing.
---
## Update Workflow
Run from each app root directory (`terraform/apps/dev-01` and `terraform/apps/prod-01`) as needed.
1. Edit `apps.tf` and set the new pinned tag.
2. Validate configuration:
```bash
tofu fmt
sectool -f ../../../sectool.json exec tofu validate
```
3. Review plan:
```bash
sectool -f ../../../sectool.json exec tofu plan
```
4. Apply:
```bash
sectool -f ../../../sectool.json exec tofu apply -auto-approve
```
5. Verify rollout:
```bash
kubectl --context microk8s-<cluster> -n <namespace> rollout status deployment/<deployment> --timeout=120s
kubectl --context microk8s-<cluster> -n <namespace> get pods -o wide
kubectl --context microk8s-<cluster> -n <namespace> logs deployment/<deployment> --all-containers=true --tail=200
```
---
## Emergency Recovery
If immediate recovery is required, a targeted apply can be used temporarily:
```bash
sectool -f ../../../sectool.json exec tofu apply -auto-approve -target=module.<app_name>
```
Afterward, always run a full plan to detect drift:
```bash
sectool -f ../../../sectool.json exec tofu plan
```
---
## Review Checklist
- [ ] New image tag is pinned and explicit.
- [ ] Both cluster roots were considered (dev-01 and prod-01).
- [ ] Commands were run with `sectool -f ../../../sectool.json exec tofu`.
- [ ] Rollout and pod health were verified with kubectl.
- [ ] Any runtime hotfix used during debugging was persisted back into Terraform.
-114
View File
@@ -1,114 +0,0 @@
# AGENTS.md - A13Labs Infrastructure
## Repo Structure
- **`ansible/`** — Playbooks, roles, inventory. Runs against bare-metal servers.
- **`terraform/`** — IaC (OpenTofu for K8s app deployments + cloud provisioning).
- **`inventory/hosts`** — Ansible inventory. All hosts and groups.
- **`requirements/`** — pip requirements files: `ansible.txt`, `dev.txt`, `opentofu.txt`.
- **`sectool`** — Secret manager (Bitwarden, configured in `sectool.json`). Both root and `ansible/` have their own `sectool.json` + `sectool.env`.
## Key Facts
- **No `Makefile`, no `justfile`, no pre-commit config.** Commands are run directly.
- **All GitHub workflows are `.disabled`** — CI is not active. Verification is local-only.
- **`ansible/ansible.cfg`** points to `../inventory/hosts` (relative to `ansible/` dir). Always run playbooks from the `ansible/` directory, or use `-i` to override.
- **YAML linting:** `ansible-lint` config at `ansible/.ansible-lint`. `.yamllint.yml` at `ansible/.yamllint.yml` (120 char line warning).
- **No `.pre-commit-config.yaml` exists** — linting is manual or CI-only.
## Ansible
### Required Environment vars
```sh
export ANSIBLE_USER=provision
export OBJC_DISABLE_INITIALIZE_FORK_SAFETY=YES # if on a MAC
```
### Running playbooks
```sh
cd ansible
sectool exec ansible-playbook -u $ANSIBLE_USER playbook_<name>.yml
```
### Role testing (Molecule + Podman - Linux Only)
```sh
cd ansible/roles/<role_name>
molecule test # create -> converge -> verify -> destroy
molecule converge # run converge only
molecule destroy # clean up
```
**Molecule gotchas:**
- Always run `molecule` commands **from the role directory**, not from repo root.
- Molecule containers use `podman` driver. No systemd by default — guard service tasks with `when: ansible_service_mgr == "systemd"`.
- `molecule/default/prepare.yml` must install python3 and sudo via `raw:` before Ansible can run Python modules.
- `provisioner.env.ANSIBLE_ROLES_PATH` should point up to the parent (e.g., `../../../`).
### Ansible-lint
```sh
ansible-lint # runs from repo root or ansible/
```
Excluded paths: `.git/`, `.github/`, `tests/`. Warns on: `line-length`, `yaml[line-length]`, `var-naming[no-role-prefix]`.
## Terraform / OpenTofu
### App deployments (K8s)
- `terraform/apps/dev-01/` — dev Kubernetes cluster (microk8s)
- `terraform/apps/prod-01/` — prod Kubernetes cluster (microk8s)
- Uses Kubernetes + TLS providers. `provider.tf` sets `insecure = true`.
### Cloud IaC
- `terraform/iac/aws/` — AWS infrastructure
- `terraform/iac/contabo/` — Contabo VPS
- `terraform/iac/cloudns/` — DNS (cloudns)
- `terraform/iac/scaleway/` — Scaleway infrastructure
### Secrets
Each terraform directory has its own `sectool.env`. Run `sectool` to load secrets before `tofu init/plan/apply`:
```sh
sectool env -d terraform/apps/dev-01/ -- tofu plan
```
### Module structure
- `terraform/modules/apps/` — application modules (Nextcloud, etc.)
- `terraform/modules/db/` — database modules (MariaDB with backups)
- `terraform/modules/utils/` — utilities (exporters, cert-checker, prometheus, trivy, etc.)
## Scripts (`scripts/`)
One-off utility scripts — not library code:
- `install-opnsense.sh` — boots OPNsense on a VM via virt-install (one-time router setup)
- `chronograf.sh``kubectl port-forward` wrapper for Chronograf pod
- `proxy-pt.sh``kubectl port-forward` wrapper for m3uproxy pod
- `steamos` — launches SteamOS gaming session with sunshine
- `cpu_power_monitor.py` — RAPL power monitoring (requires root)
## Environment Setup
```sh
# Ansible + dev tools
pip install -r requirements/ansible.txt -r requirements/dev.txt
# Terraform/OpenTofu deps
pip install -r requirements/opentofu.txt
```
## Contabo CLI
```sh
sectool exec cntb get instances --oauth2-clientid="$CONTABO_CLIENT_ID" ...
```
## Windows hosts
- Use `ConfigureRemotingForAnsible.ps1` in `scripts/` to enable WinRM on Windows targets.
- On MSYS2: install packages listed in `Readme.MD` (go, python, cryptography, paramiko, rpds-py, etc.).
- Python venv: `python -m venv .venv --system-site-packages`
## Style Conventions
- YAML: 120 char line length (warning threshold)
- Ansible roles follow galaxy conventions: `defaults/`, `tasks/`, `meta/`, `files/`, `templates/`
- `tasks/main.yml` is the entrypoint for every role
- Host vars: `ansible/host_vars/<hostname>.yml`
+1 -124
View File
@@ -26,127 +26,4 @@ Download latest version from [here](https://github.com/contabo/cntb/releases).
Setup:
```
sectool exec cntb get instances --oauth2-clientid="$CONTABO_CLIENT_ID" --oauth2-client-secret="$CONTABO_CLIENT_SECRET" --oauth2-user="$CONTABO_API_USER" --oauth2-password="$CONTABO_API_PASSWORD"
```
# Windows
## Create a Dedicated “Ansible” User (One-Time Setup) — Linux
```
# Create the provision user (Debian/Ubuntu)
sudo useradd -m -s /bin/bash -G sudo provision
sudo passwd provision
# OR (RHEL/CentOS/Fedora)
sudo useradd -m -s /bin/bash -G wheel provision
sudo passwd provision
```
```
# Allow passwordless sudo for the provision user (recommended for Ansible)
echo 'provision ALL=(ALL) NOPASSWD:ALL' | sudo tee /etc/sudoers.d/provision
sudo chmod 440 /etc/sudoers.d/provision
# Or edit safely:
sudo visudo -f /etc/sudoers.d/provision
# and add:
# provision ALL=(ALL) NOPASSWD:ALL
```
```
# Install SSH authorized key for the provision user (recommended over passwords)
sudo -u provision mkdir -p /home/provision/.ssh
sudo -u provision chmod 700 /home/provision/.ssh
echo "<your_public_key_here>" | sudo -u provision tee /home/provision/.ssh/authorized_keys
sudo -u provision chmod 600 /home/provision/.ssh/authorized_keys
```
Notes:
- Replace <your_public_key_here> with the actual public key.
- Use a strong password if you must set one; prefer SSH keys.
- For tighter security, restrict the sudoers entry to only the commands Ansible requires instead of ALL.
- Validate sudoers syntax with visudo to avoid lockout.
## Configure windows to allow Ansible to connect via WinRM
Copy the script located in scripts "ConfigureRemotingForAnsible.ps1" and run it on the target machine.
```
Set-ExecutionPolicy -Scope Process -ExecutionPolicy Bypass
.\ConfigureRemotingForAnsible.ps1
```
# Using MSYS2
Upgrade your system:
```
pacman -Syu
```
Install the following packages:
```
mingw-w64-ucrt-x86_64-go
mingw-w64-ucrt-x86_64-make
mingw-w64-ucrt-x86_64-python
ucrt-w64-ucrt-x86_64-gcc
mingw-w64-ucrt-x86_64-python-cryptography
mingw-w64-ucrt-x86_64-python-paramiko
mingw-w64-ucrt-x86_64-python-rpds-py
python-lxml
mingw-w64-ucrt-x86_64-python-ruamel-yaml
mingw-w64-ucrt-x86_64-python-sspilib
unzip
```
Create a python environment use:
```
python -m venv .venv --system-site-packages
```
# macOS Dev Station
Use the repository hybrid flow: bootstrap once with shell script, then manage day-2 changes with Ansible.
## 1) Bootstrap prerequisites on the Mac
```
./scripts/setup-dev-station.sh
```
This installs only prerequisites (Homebrew, base tools, Ansible) and avoids persistent shell side effects.
## 2) Configure the Mac with Ansible
1. Add or adjust the host in `inventory/hosts` under `[darwin_dev]`.
2. Configure host vars in `ansible/host_vars/mac-01.lab.alexpires.me.yml`.
3. Run:
```
cd ansible
sectool exec ansible-playbook playbook_macos_dev_station.yml --limit darwin_dev
```
The macOS role configures tools, tmux, code-server and opencode as system launchd daemons running under a dedicated service user. This allows services to start at boot without requiring an interactive login.
OpenCode server auth is injected from environment variables. Ensure `OPENCODE_SERVER_USERNAME` and `OPENCODE_SERVER_PASSWORD` are available through `ansible/sectool.env` values before running the playbook.
code-server password auth is also env-backed. Ensure `CODE_SERVER_PASSWORD` is available through `ansible/sectool.env` values before running the playbook.
Hard mode (system daemons + dedicated service user) also requires sudo rights. Use passwordless sudo for your ansible user or set `ANSIBLE_BECOME_PASSWORD` through `ansible/sectool.env` values before running the playbook.
## 3) Optional DNS entry in dev-01 CoreDNS
From `terraform/apps/dev-01`, you can inject an optional DNS record for the Mac dev station:
```
export TF_VAR_mac_dev_station_dns_name="mac-mini"
export TF_VAR_mac_dev_station_dns_type="A"
export TF_VAR_mac_dev_station_dns_target="10.19.4.250"
sectool -f ../../../sectool.json exec tofu validate
sectool -f ../../../sectool.json exec tofu plan
sectool -f ../../../sectool.json exec tofu apply -auto-approve
```
Use `CNAME` + hostname target instead of `A` when you want an alias.
```
+1 -1
View File
@@ -2,4 +2,4 @@
inventory = ../inventory/hosts
[ssh_connection]
ssh_args = -o ControlMaster=auto -o ControlPersist=60s
ssh_args = -o ControlMaster=auto -o ControlPersist=60s
+55
View File
@@ -0,0 +1,55 @@
#!/bin/bash
# check if sectool is available
command -v sectool >/dev/null 2>&1 || { echo >&2 "sectool is not installed. Aborting."; exit 1; }
# check if ansible is available
command -v ansible >/dev/null 2>&1 || { echo >&2 "ansible is not installed. Aborting."; exit 1; }
# check if ansible-playbook is available
command -v ansible-playbook >/dev/null 2>&1 || { echo >&2 "ansible-playbook is not installed. Aborting."; exit 1; }
function help {
echo "Usage: $0 -d <playbook>"
echo "Options:"
echo " -d, --dry-run: Run ansible-playbook in dry run mode (don't apply changes)"
exit 1
}
if [ -z $ANSIBLE_USER ]; then
echo "ANSIBLE_USER environment variable not set"
exit 1
fi
# parse arguments
while [ "$1" != "" ]; do
case $1 in
-d | --dry-run ) shift
DRY_RUN="-DC"
;;
-h | --help ) help
;;
* ) PLAYBOOK=$1
break
;;
esac
done
if [ -z "$PLAYBOOK" ]; then
echo "Playbook not specified"
help
else
if [ ! -f "$PLAYBOOK" ]; then
echo "Playbook $PLAYBOOK not found"
help
fi
fi
if [ -z "$DRY_RUN" ]; then
echo "Applying playbook $PLAYBOOK"
else
echo "Dry run playbook $PLAYBOOK"
fi
# execute ansible-playbook
sectool exec ansible-playbook -u $ANSIBLE_USER $DRY_RUN $PLAYBOOK
@@ -1,5 +1,3 @@
Match User git
Banner none
PermitTTY no
AuthorizedKeysCommandUser git
AuthorizedKeysCommand /usr/local/bin/k8s_gitea_auth -r gitea gitea-app -u %u -t %t -k %k
AuthorizedKeysCommand /usr/local/bin/gitea-auth -u %u -t %t -k %k
@@ -1,35 +0,0 @@
# Use NVIDIA's PyTorch image with CUDA support
FROM docker.io/pytorch/pytorch:2.1.0-cuda11.8-cudnn8-runtime
# Set environment variables
ENV PYTHONDONTWRITEBYTECODE=1
ENV PYTHONUNBUFFERED=1
# Install system dependencies
ENV DEBIAN_FRONTEND=noninteractive
RUN apt-get update && \
apt-get install -y tzdata git ffmpeg libgl1 libglib2.0-0 && \
rm -rf /var/lib/apt/lists/*
ARG UID=1000
ARG GID=1000
RUN groupadd --system --gid ${GID} worker && \
adduser --system --gid ${GID} --uid ${UID} --home /home/worker worker
WORKDIR /home/worker
USER worker
# Clone ComfyUI
RUN git clone https://github.com/comfyanonymous/ComfyUI.git .
# Install Python dependencies
RUN pip install --upgrade pip && \
pip install -r requirements.txt && \
rm -rf /home/worker/.cache/pip
# Expose port
EXPOSE 8188
# Run ComfyUI
CMD ["python", "main.py", "--listen", "0.0.0.0", "--port", "8188"]
@@ -1,11 +0,0 @@
FROM ghcr.io/ggml-org/llama.cpp:server-cuda
ARG UID=1000
ARG GID=1000
RUN groupadd --system --gid ${GID} worker && \
useradd --system --gid ${GID} --uid ${UID} --home /home/worker worker && \
mkdir -p /home/worker && chown worker:worker /home/worker
WORKDIR /home/worker
USER worker
ENTRYPOINT ["/app/llama-server"]
@@ -1,14 +0,0 @@
FROM ghcr.io/ggml-org/llama.cpp:server-rocm
ARG UID=1000
ARG GID=1000
RUN groupadd --system --gid ${GID} worker && \
useradd --system --gid ${GID} --uid ${UID} --home /home/worker worker && \
mkdir -p /home/worker && chown worker:worker /home/worker && \
cp /app/llama-server /home/worker/ && \
chmod +x /home/worker/llama-server && \
chown worker:worker /home/worker/llama-server
WORKDIR /home/worker
USER worker
ENTRYPOINT ["/home/worker/llama-server"]
@@ -1,12 +0,0 @@
FROM docker.io/ollama/ollama:latest
ARG UID=1000
ARG GID=1000
RUN groupadd --system --gid ${GID} worker && \
useradd --system --gid ${GID} --uid ${UID} --home /home/worker worker && \
mkdir -p /home/worker && chown worker:worker /home/worker
WORKDIR /home/worker
USER worker
ENTRYPOINT ["/bin/ollama"]
CMD ["serve"]
@@ -1,12 +0,0 @@
FROM docker.io/ollama/ollama:rocm
ARG UID=1000
ARG GID=1000
RUN groupadd --system --gid ${GID} worker && \
useradd --system --gid ${GID} --uid ${UID} --home /home/worker worker && \
mkdir -p /home/worker && chown worker:worker /home/worker
WORKDIR /home/worker
USER worker
ENTRYPOINT ["/bin/ollama"]
CMD ["serve"]
@@ -1,31 +0,0 @@
FROM local/gitea-runner-base:latest
# Base image sets USER runner; switch back to root for package installs
USER root
RUN apt-get update && apt-get install -y --no-install-recommends \
buildah \
fuse-overlayfs \
iptables \
podman \
skopeo \
slirp4netns \
uidmap \
&& rm -rf /var/lib/apt/lists/*
RUN mkdir -p /etc/containers /var/lib/containers /run/containers && \
printf '%s\n' '[storage]' 'driver = "vfs"' > /etc/containers/storage.conf && \
printf '%s\n' '[engine]' > /etc/containers/containers.conf && \
chown -R 1000:1000 /var/lib/containers
ENV BUILDAH_ISOLATION=chroot
ENV STORAGE_DRIVER=vfs
ARG TRIVY_VERSION=0.72.0
RUN curl -fsSL -o /tmp/trivy.tar.gz \
"https://github.com/aquasecurity/trivy/releases/download/v${TRIVY_VERSION}/trivy_${TRIVY_VERSION}_Linux-64bit.tar.gz" && \
tar -xzf /tmp/trivy.tar.gz -C /tmp && \
install -m 0755 /tmp/trivy /usr/local/bin/trivy && \
rm -f /tmp/trivy.tar.gz /tmp/trivy
# Default to non-root execution
USER runner
@@ -1,11 +0,0 @@
FROM local/gitea-runner-base:latest
# Base image sets USER runner; switch back to root for package installs
USER root
RUN apt-get update && apt-get install -y --no-install-recommends \
build-essential \
cmake \
&& rm -rf /var/lib/apt/lists/*
# Default to non-root execution
USER runner
@@ -1,38 +0,0 @@
# Stage 1: Extract the gitea-runner binary from the official Alpine-based image
FROM docker.io/gitea/runner:latest AS runner
# Stage 2: Debian-based image with native glibc support
FROM debian:bookworm-slim
COPY --from=runner /usr/local/bin/gitea-runner /usr/local/bin/gitea-runner
COPY --from=runner /usr/local/bin/run.sh /usr/local/bin/run.sh
RUN chmod +x /usr/local/bin/run.sh
ENTRYPOINT ["/usr/local/bin/run.sh"]
# Create dedicated runner user (UID 1000) for non-root container execution
RUN groupadd -g 1000 runner && \
useradd -u 1000 -g 1000 -m -d /home/runner -s /bin/bash runner
# Install runner tooling
RUN apt-get update && apt-get install -y --no-install-recommends \
bash \
ca-certificates \
curl \
git \
jq \
make \
nodejs \
openssh-client \
python3 \
python3-pip \
rsync \
unzip \
zip \
&& rm -rf /var/lib/apt/lists/*
# Set work directory
ENV RUNNER_WORKDIR=/home/runner
# Ensure container runs as non-root by default
USER runner
@@ -1,10 +0,0 @@
FROM local/gitea-runner-base:latest
# Base image sets USER runner; switch back to root for package installs
USER root
RUN apt-get update && apt-get install -y --no-install-recommends \
golang-go \
&& rm -rf /var/lib/apt/lists/*
# Default to non-root execution
USER runner
@@ -1,22 +0,0 @@
FROM mcr.microsoft.com/windows/servercore:ltsc2022
SHELL ["powershell", "-NoProfile", "-ExecutionPolicy", "Bypass", "-Command"]
# Base image for Windows Gitea runners with Git tools and gitea runner.
ARG ACT_RUNNER_VERSION=2.0.0
RUN Set-StrictMode -Version Latest; \
$ErrorActionPreference = 'Stop'; \
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; \
iex ((New-Object System.Net.WebClient).DownloadString('https://community.chocolatey.org/install.ps1')); \
choco config set commandExecutionTimeoutSeconds 14400; \
choco feature disable -n showDownloadProgress; \
choco feature enable -n allowGlobalConfirmation; \
choco install git --yes --no-progress --limit-output; \
New-Item -ItemType Directory -Path C:\gitea-runner -Force | Out-Null; \
$runnerUrl = 'https://gitea.com/gitea/runner/releases/download/v{0}/gitea-runner-{0}-windows-amd64.exe' -f $env:ACT_RUNNER_VERSION; \
Invoke-WebRequest -UseBasicParsing -Uri $runnerUrl -OutFile C:\gitea-runner\act_runner.exe
COPY ["start-runner.ps1", "C:/gitea-runner/start-runner.ps1"]
ENV PATH="C:\ProgramData\chocolatey\bin;C:\Program Files\Git\cmd;C:\Program Files\Git\bin;C:\Program Files\Git\usr\bin;C:\Windows\system32;C:\Windows;C:\Windows\System32\Wbem;C:\Windows\System32\WindowsPowerShell\v1.0\;C:\Windows\System32\OpenSSH\;C:\Users\ContainerAdministrator\AppData\Local\Microsoft\WindowsApps"
ENTRYPOINT ["C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe", "-NoProfile", "-ExecutionPolicy", "Bypass", "-File", "C:\\gitea-runner\\start-runner.ps1"]
@@ -1,8 +0,0 @@
FROM local/gitea-windows-runner:windows-base
SHELL ["powershell", "-NoProfile", "-ExecutionPolicy", "Bypass", "-Command"]
# C/C++ profile with cmake and ninja.
RUN choco feature enable -n allowGlobalConfirmation; \
choco install cmake --installargs 'ADD_CMAKE_TO_PATH=System'; \
choco install ninja
@@ -1,7 +0,0 @@
FROM local/gitea-windows-runner:windows-base
SHELL ["powershell", "-NoProfile", "-ExecutionPolicy", "Bypass", "-Command"]
# .NET profile for building and testing managed workloads.
RUN choco feature enable -n allowGlobalConfirmation; \
choco install dotnet-sdk --version=8.0.204
@@ -1,7 +0,0 @@
FROM local/gitea-windows-runner:windows-base
SHELL ["powershell", "-NoProfile", "-ExecutionPolicy", "Bypass", "-Command"]
# Go profile for Windows builds.
RUN choco feature enable -n allowGlobalConfirmation; \
choco install golang --version=1.22.4
@@ -1,9 +0,0 @@
FROM local/gitea-windows-runner:windows-msys
SHELL ["powershell", "-NoProfile", "-ExecutionPolicy", "Bypass", "-Command"]
# Dedicated profile for image builds. Docker daemon access is provided at runtime
# through the host named pipe mount configured in host_vars.
RUN Set-StrictMode -Version Latest; \
$ErrorActionPreference = 'Stop'; \
choco install docker-cli --yes --no-progress --limit-output
@@ -1,7 +0,0 @@
FROM local/gitea-windows-runner:windows-base
SHELL ["powershell", "-NoProfile", "-ExecutionPolicy", "Bypass", "-Command"]
# MSYS-focused profile with common GNU toolchain extras.
RUN choco feature enable -n allowGlobalConfirmation; \
choco install make msys2 unzip --yes --no-progress --limit-output
@@ -1,17 +0,0 @@
$ErrorActionPreference = "Stop"
$runner = "C:\gitea-runner\act_runner.exe"
$config = if ($env:CONFIG_FILE) { $env:CONFIG_FILE } else { "C:\runner-data\config.yaml" }
$runnerFile = if ($env:GITEA_RUNNER_FILE) { $env:GITEA_RUNNER_FILE } else { "C:\runner-data\.runner" }
if (-not (Test-Path -Path $runnerFile) -and $env:GITEA_INSTANCE_URL -and $env:GITEA_RUNNER_REGISTRATION_TOKEN) {
$labels = if ($env:GITEA_RUNNER_LABELS) { $env:GITEA_RUNNER_LABELS } else { "windows:host" }
$name = if ($env:GITEA_RUNNER_NAME) { $env:GITEA_RUNNER_NAME } else { $env:COMPUTERNAME }
& $runner register --no-interactive --instance $env:GITEA_INSTANCE_URL --token $env:GITEA_RUNNER_REGISTRATION_TOKEN --name $name --labels $labels --config $config
if ($LASTEXITCODE -ne 0) {
exit $LASTEXITCODE
}
}
& $runner daemon --config $config
exit $LASTEXITCODE
@@ -1,157 +0,0 @@
#!/bin/bash
set -euo pipefail
LOG_FILE="$HOME/.local/logs/session.log"
log() { echo "[$(date --iso-8601=seconds)] $*" | tee -a "$LOG_FILE"; }
CONF_FILE="$HOME/.config/gamescope/gamescope.conf"
[ -f "$CONF_FILE" ] && source "$CONF_FILE"
RESOLUTION=${RESOLUTION:-1920x1080}
WIDTH="${RESOLUTION%x*}"
HEIGHT="${RESOLUTION#*x}"
FRAMERATE=${FRAMERATE:-60}
export __GL_GSYNC_ALLOWED=1
export __GL_VRR_ALLOWED=1
export __GL_SHADER_DISK_CACHE=1
export __GL_SYNC_TO_VBLANK=0
export __GLX_VENDOR_LIBRARY_NAME=nvidia
export LIBVA_DRIVER_NAME=nvidia
export NVD_BACKEND=direct
GAMESCOPE_BIN="$(command -v gamescope || true)"
if [ -z "$GAMESCOPE_BIN" ]; then
log "ERROR: gamescope not found in PATH"
exit 127
fi
HDR_FLAG=""
if [[ "$HDR_ENABLED" == "1" || "$HDR_ENABLED" == "true" || "$HDR_ENABLED" == "yes" ]]; then
HDR_FLAG="--hdr-enabled"
log "HDR flag added: --hdr-enabled"
else
log "HDR disabled, no HDR flag added"
fi
# Create run directory file for startup and stats sockets
# shellcheck disable=SC2030 # (broken warning)
tmpdir="$([[ -n ${XDG_RUNTIME_DIR+x} ]] && mktemp -p "$XDG_RUNTIME_DIR" -d -t gamescope.XXXXXXX)"
socket="${tmpdir:+$tmpdir/startup.socket}"
stats="${tmpdir:+$tmpdir/stats.pipe}"
# Fail early if we don't have a proper runtime directory setup
# shellcheck disable=SC2031 # (broken warning)
if [[ -z $tmpdir || -z ${XDG_RUNTIME_DIR+x} ]]; then
echo >&2 "!! Failed to find run directory in which to create stats session sockets (is \$XDG_RUNTIME_DIR set?)"
exit 0
fi
# Setup MangoHud config file in temp directory
export MANGOHUD_CONFIGFILE="${tmpdir:+$tmpdir/mangohud.config}"
# Initially write no_display to our config file
# so we don't get mangoapp showing up before Steam initializes
# on OOBE and stuff.
mkdir -p "$(dirname "$MANGOHUD_CONFIGFILE")"
echo "no_display" > "$MANGOHUD_CONFIGFILE"
# Chromium (and therefore Steam) ignore XCursor and use on the GTK config
kwriteconfig6 --file gtk-3.0/settings.ini --group Settings --key gtk-cursor-theme-name steam
# Workaround for Steam login issue while Steam client change propagates out of Beta
touch ~/.steam/root/config/SteamAppData.vdf || true
# Increase open file limit for gamescope session
ulimit -n 524288
# 1048576 = 1M - passing it like that omits the 'M' suffix - xargs removes whitespace
free_disk_space_megs=$(df ~/ --output=avail -B1048576 | sed -n '2 p' | xargs)
minimum_free_disk_space_needed_megs=500
if [[ "$free_disk_space_megs" -lt "$minimum_free_disk_space_needed_megs" ]]; then
echo >&2 "gamescope-session: not enough disk space to proceed, trying to find game to delete"
find ~/.local/share/Steam/steamapps/common/ -mindepth 1 -maxdepth 1 -type d -printf "%T@ %p\0" | sort -n -z | while IFS= read -r -d $'\0' line; do
timestamp=${line%% *}
game_folder=${line#* }
[[ -d $game_folder ]] || continue
acf=$(grep -F -- "$(basename -- "$game_folder")" ~/.local/share/Steam/steamapps/*.acf | grep \"installdir\" | cut -d: -f1)
[[ -e "$acf" ]] || continue
echo >&2 "gamescope-session: deleting $(basename "$game_folder")"
appid=$(basename "$acf" | cut -d_ -f2 | cut -d. -f1)
# TODO leave a note for Steam to display UI to explain what happened, if this logic stays
# intentionally leave compatdata; could be unclouded save files there
rm -rf --one-file-system -- "$game_folder" "$acf" ~/.local/share/Steam/steamapps/shadercache/"$appid"
free_disk_space_megs=$(df ~/ --output=avail -B1048576 | sed -n '2 p' | xargs)
[[ "$free_disk_space_megs" -lt "$minimum_free_disk_space_needed_megs" ]] || break
done
fi
export GAMESCOPE_STATS="$stats"
mkfifo -- "$stats"
mkfifo -- "$socket"
# Attempt to claim global session if we're the first one running (e.g. /run/1000/gamescope)
linkname="gamescope-stats"
# shellcheck disable=SC2031 # (broken warning)
sessionlink="${XDG_RUNTIME_DIR:+$XDG_RUNTIME_DIR/}${linkname}" # Account for XDG_RUNTIME_DIR="" (notfragileatall)
lockfile="$sessionlink".lck
exec 9>"$lockfile" # Keep as an fd such that the lock lasts as long as the session if it is taken
if flock -n 9 && rm -f "$sessionlink" && ln -sf "$tmpdir" "$sessionlink"; then
# Took the lock. Don't blow up if those commands fail, though.
echo >&2 "Claimed global gamescope stats session at \"$sessionlink\""
else
echo >&2 "!! Failed to claim global gamescope stats session"
fi
read_gamescope_env() {
log "Waiting for gamescope to provide DISPLAY and GAMESCOPE_WAYLAND_DISPLAY via socket..."
if read -r -t 3 response_x_display response_wl_display <> "$socket"; then
log "Received gamescope environment:"
log " DISPLAY=$response_x_display"
log " GAMESCOPE_WAYLAND_DISPLAY=$response_wl_display"
export DISPLAY="$response_x_display"
export GAMESCOPE_WAYLAND_DISPLAY="$response_wl_display"
# Sync environment variables to systemd, then notify we are ready to launch subsequent services
log "Storing gamescope environment for systemd..."
env > $XDG_RUNTIME_DIR/gamescope-environment
systemd-notify --ready
fi
}
# Spawned in parallel to read values from gamescope
(read_gamescope_env &)
log "Starting Gamescope session..."
# Build gamescope argument array for safe quoting and readability
GAMESCOPE_ARGS=(
--generate-drm-mode fixed
--fade-out-duration 200
--cursor-scale-height 720
-f
-W "$WIDTH" -H "$HEIGHT"
-w "$WIDTH" -h "$HEIGHT"
--rt --mangoapp -e -R "$socket" -T "$stats"
)
# Append HDR flag if enabled
if [ -n "${HDR_FLAG:-}" ]; then
GAMESCOPE_ARGS+=("$HDR_FLAG")
fi
export XDG_CURRENT_DESKTOP=gamescope
export XDG_SESSION_TYPE=wayland
export DESKTOP_SESSION=gamescope
log "Launching Gamescope: $GAMESCOPE_BIN ${GAMESCOPE_ARGS[*]} -- $*"
sudo setcap 'CAP_SYS_NICE=eip' "$GAMESCOPE_BIN"
exec "$GAMESCOPE_BIN" "${GAMESCOPE_ARGS[@]}" >>"$LOG_FILE" 2>&1
@@ -1,9 +0,0 @@
#!/bin/bash
set -euo pipefail
LOGFILE="$HOME/.local/logs/jupiter-biosupdate.log"
log() { echo "[$(date --iso-8601=seconds)] $*" | tee -a "$LOGFILE"; }
log "Starting Jupiter BIOS update..."
log "Arguments passed: ${@}"
echo "Not applicable for this OS"
@@ -1,38 +0,0 @@
#!/usr/bin/env bash
set -euo pipefail
LOGFILE="$HOME/.local/logs/gamescope.log"
export PATH="$HOME/.local/bin:$PATH"
log() { echo "[$(date --iso-8601=seconds)] $*" | tee -a "$LOGFILE"; }
# SDDM sets this to wayland but apps using Gamescope must use x11
export XDG_SESSION_TYPE=x11
# Update the enviroment with DESKTOP_SESSION and all XDG variables
log "Updating systemd user environment with XDG variables"
dbus-update-activation-environment --systemd DESKTOP_SESSION `env | grep ^XDG_ | cut -d = -f 1`
# This makes it so that xdg-desktop-portal doesn't find any portal implementations and doesn't start them and makes
# them crash/exit because the dbus env has no DISPLAY. In turn this causes dbus calls to the portal which don't rely
# on implementations to hang (such as SDL talking to the real time portal)
# Plasma resets this variable when it starts
systemctl --user set-environment XDG_DESKTOP_PORTAL_DIR=""
# Remove these as they prevent gamescope-session from starting correctly
systemctl --user unset-environment DISPLAY XAUTHORITY
# If this shell script is killed then stop gamescope-session
trap 'systemctl --user stop gamescope-session.target' HUP INT TERM
# Start gamescope-session and wait
log "Starting Gamescope Session"
systemctl --user --wait start gamescope-session.target &
wait
log "Gamescope Session Ended - Performing Final Cleanup"
# The 'wait' above blocks until gamescope-session.target stops
# We want to wait until *everything* has finished. We know systemd will have
# queued a stop job on graphical-session-pre aleady
# by also queuing up a job we can block until that completes
systemctl --user stop graphical-session-pre.target
log "Gamescope Session Ended - Cleanup Complete"
@@ -1,64 +0,0 @@
#!/usr/bin/env bash
set -euo pipefail
LOGFILE="$HOME/.local/logs/desktop.log"
export PATH="$HOME/.local/bin:$PATH"
log() { echo "[$(date --iso-8601=seconds)] $*" | tee -a "$LOGFILE"; }
DESKTOP_BIN="$(command -v plasmashell || true)"
if [ -z "$DESKTOP_BIN" ]; then
log "ERROR: plasmashell not found in PATH"
exit 127
fi
export XDG_CURRENT_DESKTOP=plasma
export XDG_CURRENT_DESKTOP=KDE
export QT_QPA_PLATFORM=wayland
export GDK_BACKEND=wayland
export CLUTTER_BACKEND=wayland
export SDL_VIDEODRIVER=wayland
export MOZ_ENABLE_WAYLAND=1
export KDEDIRS=/usr
export KDE_FULL_SESSION=true
export DESKTOP_SESSION=plasma
unset DISPLAY
mkdir -p $XDG_RUNTIME_DIR
chmod 700 $XDG_RUNTIME_DIR
log "Launching Plasma: $DESKTOP_BIN"
systemd-run --user --scope --quiet \
--unit=plasma.scope \
--slice=desktop.slice \
"$DESKTOP_BIN" >>"$LOGFILE" 2>&1 &
plasma_scope_pid=$!
log "Plasma started with scope pid $plasma_scope_pid"
{
SUNSHINE_BIN="$(command -v sunshine || true)"
if [ -z "$SUNSHINE_BIN" ]; then
log "ERROR: sunshine not found in PATH"
else
SUNSHINE_LOG_FILE="$HOME/.local/logs/sunshine.log"
log "Found Sunshine: $SUNSHINE_BIN, starting after 5s delay"
sleep 5
systemd-run --user --scope --quiet \
--unit=sunshine.scope \
--slice=sunshine.slice \
"$SUNSHINE_BIN" "$HOME/.config/sunshine/sunshine.conf"
fi
} &
wait "$plasma_scope_pid"
status=$?
# Stop Sunshine gracefully
if systemctl --user is-active --quiet sunshine.scope; then
log "Stopping Sunshine"
systemctl --user stop sunshine.scope
fi
log "Plasma session ended."
exit "$status"
@@ -1,148 +0,0 @@
#!/usr/bin/env bash
set -euo pipefail
LOGFILE="$HOME/.local/logs/steam.log"
export PATH="$HOME/.local/bin:$PATH"
export SDL_VIDEO_MINIMIZE_ON_FOCUS_LOSS=0
# There is no way to set a color space for an NV12
# buffer in Wayland. And the color management protocol that is
# meant to let this happen is missing the color range...
# So just workaround this with an ENV var that Remote Play Together
# and Gamescope will use for now.
export GAMESCOPE_NV12_COLORSPACE=k_EStreamColorspace_BT601
export STEAM_GAMESCOPE_HDR_SUPPORTED=1
# Workaround older versions of vkd3d-proton setting this
# too low (desc.BufferCount), resulting in symptoms that are potentially like
# swapchain starvation.
export VKD3D_SWAPCHAIN_LATENCY_FRAMES=3
# Let's try this across the board to see if it breaks anything
# Helps performance in HZD, Cyberpunk, at least
# Expose 8 physical cores, instead of 4c/8t
# export WINE_CPU_TOPOLOGY=8:0,1,2,3,4,5,6,7
# To expose vram info from radv's patch we're including
export WINEDLLOVERRIDES=dxgi=n
# Disable automatic audio device switching in steam, now handled by wireplumber
export STEAM_DISABLE_AUDIO_DEVICE_SWITCHING=1
# Enable support for xwayland isolation per-game in Steam
# export STEAM_MULTIPLE_XWAYLANDS=0
# We have NIS support
export STEAM_GAMESCOPE_NIS_SUPPORTED=1
# Enable tearing controls in steam
export STEAM_GAMESCOPE_TEARING_SUPPORTED=1
# Enable VRR controls in steam
export STEAM_GAMESCOPE_VRR_SUPPORTED=1
# When set to 1, a toggle will show up in the steamui to control whether dynamic refresh rate is applied to the steamui
export STEAM_GAMESCOPE_DYNAMIC_REFRESH_IN_STEAM_SUPPORTED=0
# Don't wait for buffers to idle on the client side before sending them to gamescope
export vk_xwayland_wait_ready=false
# Scaling support
export STEAM_GAMESCOPE_FANCY_SCALING_SUPPORT=1
# Color management support
export STEAM_GAMESCOPE_COLOR_MANAGED=1
export STEAM_GAMESCOPE_VIRTUAL_WHITE=1
# Temporary crutch until dummy plane interactions / etc are figured out
# export GAMESCOPE_DISABLE_ASYNC_FLIPS=1
# Use Vulkan for the Steam Overlay
export STEAM_OVERLAY_RENDERER=vk
# Set input method modules for Qt/GTK that will show the Steam keyboard
export QT_IM_MODULE=steam
export GTK_IM_MODULE=Steam
export XCURSOR_THEME=steam
export XCURSOR_SCALE=256
export GAMESCOPE_WSI=0
export STEAM_USE_PW_CAPTURE=0 # Deck prefers gamescope capture path
export STEAM_DISABLE_GAMESCOPE_LAUNCH=0 # allow gamescope handling
log() { echo "[$(date --iso-8601=seconds)] $*" | tee -a "$LOGFILE"; }
mkdir -p $XDG_RUNTIME_DIR
chmod 700 $XDG_RUNTIME_DIR
if [ -r /proc/sys/user/max_user_namespaces ]; then
max_ns="$(cat /proc/sys/user/max_user_namespaces || echo 0)"
if [ "${max_ns:-0}" -eq 0 ]; then
log "ERROR: user.max_user_namespaces is 0. Enable it with: sudo sysctl -w user.max_user_namespaces=15000"
exit 1
fi
fi
if command -v bwrap >/dev/null 2>&1; then
bwrap_bin="$(command -v bwrap)"
if ! [ -u "$bwrap_bin" ]; then
# Not fatal if user namespaces are enabled, but warn for visibility
log "WARN: $bwrap_bin is not setuid root. If Steam still fails, run: sudo dnf reinstall bubblewrap && sudo chmod 4755 /usr/bin/bwrap"
fi
else
log "WARN: bubblewrap (bwrap) not found. Steam may fail to start."
fi
if findmnt -no OPTIONS / | grep -qw nosuid; then
log "WARN: root filesystem is mounted nosuid. Setuid helpers like bubblewrap will not work."
fi
STEAM_BIN="$(command -v steam || true)"
if [ -z "$STEAM_BIN" ]; then
log "ERROR: steam not found in PATH"
exit 127
fi
log "Updating steam: /opt/steamcmd/steamcmd.sh +quit"
/opt/steamcmd/steamcmd.sh +quit >>"$LOGFILE" 2>&1 || {
log "ERROR: Steam update failed"
exit 1
}
log "Launching Steam"
STEAM_ARGS=("-steamdeck" "-steamos3" "-gamepadui")
systemd-run --user --scope --quiet \
--unit=steam.scope \
--slice=steam.slice \
"$STEAM_BIN" "${STEAM_ARGS[@]}" >>"$LOGFILE" 2>&1 &
steam_scope_pid=$!
log "Steam started with pid $steam_scope_pid"
{
SUNSHINE_BIN="$(command -v sunshine || true)"
if [ -z "$SUNSHINE_BIN" ]; then
log "ERROR: sunshine not found in PATH"
else
SUNSHINE_LOG_FILE="$HOME/.local/logs/sunshine.log"
log "Found Sunshine: $SUNSHINE_BIN, starting after 5s delay"
sleep 5
systemd-run --user --scope --quiet \
--unit=sunshine.scope \
--slice=sunshine.slice \
"$SUNSHINE_BIN" "$HOME/.config/sunshine/sunshine.conf"
fi
} &
# Wait for Steam to exit
wait "$steam_scope_pid"
status=$?
# Stop Sunshine gracefully
if systemctl --user is-active --quiet sunshine.scope; then
log "Stopping Sunshine"
systemctl --user stop sunshine.scope
fi
log "Steam session ended."
exit "$status"
@@ -1,77 +0,0 @@
#!/bin/bash
short_session_tracker_file="/tmp/steamos-short-session-tracker"
short_session_start_file="/tmp/steamos-short-session-start"
short_session_duration=60
short_session_count_before_reset=3
do_repair() {
# This should really be part of the steam launcher as `steam --repair` or similar, but we presently only have
# `steam --reset` which is too heavy of a hammer.
echo >&2 "steam-short-session-tracker: Re-bootstrapping steam from OS copy."
# at this point, might as well make sure we won't relaunch on a sideloaded build
rm -f -- "$HOME/devkit-game/devkit-steam"
mkdir -p ~/.local/share/Steam
# remove some caches and stateful things known to cause Steam to fail to start if corrupt
rm -rf --one-file-system ~/.local/share/Steam/config/widevine
# cleanup the steam config dir, i.e. ~/.steam. We should try to preserve registry.vdf if possible
steam_config_dir="$HOME/.steam"
steam_config_backup_dir="$HOME/dot-steam.bak.$(date +%s)"
registry_vdf="$steam_config_dir/registry.vdf"
registry_backup_vdf="$steam_config_backup_dir/.steam/registry.vdf"
mv "$steam_config_dir" "$steam_config_backup_dir"
mkdir -p "$steam_config_dir"
cp -f "$registry_backup_vdf" "$registry_vdf" || true
# restore clean copy of binaries from RO partition
tar xf /usr/lib/steam/bootstraplinux_ubuntu12_32.tar.xz -C ~/.local/share/Steam
# rearm
rm "$short_session_tracker_file"
}
handle_started() {
short_session_count=$(< "$short_session_tracker_file" wc -l)
touch $short_session_start_file
if [[ "$short_session_count" -ge "$short_session_count_before_reset" ]]; then
echo >&2 "steam-short-session-tracker: Steam failed to start $short_session_count_before_reset times within $short_session_duration seconds"
if [ -f "$HOME/.config/inhibit-short-session-tracker" ]; then
echo >&2 "steam-short-session-tracker: Auto-repair disabled by config. Remove ~/.config/inhibit-short-session-tracker to re-enable"
return
fi
do_repair
fi
}
handle_stopped() {
seconds_since_started=$(($(date +%s) - $(date +%s -r "$short_session_start_file")))
if [[ "seconds_since_started" -lt "$short_session_duration" ]]; then
echo "frog" >> "$short_session_tracker_file"
else
rm "$short_session_tracker_file"
fi
}
case "$1" in
--track-started)
echo "steam-short-session-tracker: Tracking Steam start (unimplemented)"
# handle_started
;;
--track-stopped)
echo "steam-short-session-tracker: Tracking Steam stop (unimplemented)"
# handle_stopped
;;
--repair-now)
echo "steam-short-session-tracker: Performing immediate repair (unimplemented)"
# do_repair
;;
*)
echo "Usage: $0 --track-started | --track-stopped | --repair-now"
exit 1
;;
esac
exit 0
@@ -1,14 +0,0 @@
#!/bin/bash
set -euo pipefail
LOG_FILE="$HOME/.local/logs/session.log"
log() { echo "[$(date --iso-8601=seconds)] $*" | tee -a "$LOG_FILE"; }
if [[ -f "$HOME/.desktop-session-plasma" ]]; then
log "Plasma session selected."
rm "$HOME/.desktop-session-plasma"
exec /usr/bin/startplasma-wayland
else
log "Gamescope session selected."
exec /usr/local/bin/launch-gamescope
fi
@@ -1,30 +0,0 @@
#!/bin/bash
set -euo pipefail
LOGFILE="$HOME/.local/logs/steamos-select-branch.log"
log() { echo "[$(date --iso-8601=seconds)] $*" | tee -a "$LOGFILE"; }
if [[ $# -eq 1 ]]; then
case "$1" in
"-c")
log "Current branch requested"
echo "stable"
exit 0
;;
"-l")
log "Listing available branches"
exit 0
;;
"rel")
log "Legacy branch 'rel' requested"
exit 0
;;
"stable" | "rc" | "beta" | "bc" | "preview" | "pc" | "main" | "staging")
log "Branch '$1' requested"
exit 0
;;
esac
fi
echo "Usage: steamos-select-branch <-c|-l|rel|rc|beta|bc|preview|pc|main>" 1>&2
@@ -1,26 +0,0 @@
# #!/bin/bash
set -euo pipefail
LOG_FILE="$HOME/.local/logs/session.log"
log() { echo "[$(date --iso-8601=seconds)] $*" | tee -a "$LOG_FILE"; }
env > $XDG_RUNTIME_DIR/steamos-environment
log "Starting SteamOS Session"
while true; do
systemctl --user --wait start steamos.service
wait_status=$?
log "Graphical session service exited with status $wait_status"
if [ $wait_status -ne 0 ]; then
log "Graphical session service exited with error; ending loop."
break
fi
if [ -f "$HOME/.steamos-session-switch" ]; then
log "Graphical session service exited; switch file present -> restarting..."
rm "$HOME/.steamos-session-switch"
continue
fi
log "Graphical session service exited; switch file absent -> ending loop."
break
done
sleep 5
log "SteamOS Session Ended"
@@ -1,39 +0,0 @@
#!/bin/bash
set -euo pipefail
LOGFILE="$HOME/.local/logs/session.log"
log() { echo "[$(date --iso-8601=seconds)] $*" | tee -a "$LOGFILE"; }
die() { echo >&2 "!! $*"; exit 1; }
# File this script will modify
CONF_FILE="/etc/sddm.conf.d/zz-steamos-autologin.conf"
session="${1:-gamescope}"
case "$session" in
plasma*)
log "Plasma session selected."
touch "$HOME/.desktop-session-plasma"
;;
gamescope)
log "Gamescope session selected."
if [[ -f "$HOME/.desktop-session-plasma" ]]; then
rm "$HOME/.desktop-session-plasma"
fi
;;
*)
log "!! Unrecognized session '$session'"
exit 1
;;
esac
log "Signaling session switch..."
touch "$HOME/.steamos-session-switch"
# It is possible to get things remaining from the previous session if SDDM is sigkilled by root systemd
# and our units are still running in the user systemd
systemctl --user stop gamescope-session.target
systemctl --user stop plasma-workspace.target
systemctl --user reset-failed
@@ -1,11 +0,0 @@
#!/bin/bash
set -euo pipefail
LOGFILE="$HOME/.local/logs/steamos-update.log"
log() { echo "[$(date --iso-8601=seconds)] $*" | tee -a "$LOGFILE"; }
log "Starting SteamOS update..."
log "Arguments passed: ${@}"
echo "Not applicable for this OS"
# TODO: Implement SteamOS update logic for now inform no update available
exit 7
@@ -1,20 +0,0 @@
#!/usr/bin/env bash
set -euo pipefail
LOGFILE="$HOME/.local/logs/retroarch-genesis.log"
log() { echo "[$(date --iso-8601=seconds)] $*" | tee -a "$LOGFILE"; }
RETROARCH_HOME="$HOME/.local/share/Steam/steamapps/common/RetroArch"
if [ ! -d "$RETROARCH_HOME" ]; then
log "ERROR: RetroArch directory not found at $RETROARCH_HOME, install it via Steam"
exit 127
fi
RETROARCH_CORE=genesis_plus_gx_libretro.so
STEAM_BIN=$(command -v steam || true)
if [ ! -f "$STEAM_BIN" ] || [ ! -f "$RETROARCH_HOME/cores/$RETROARCH_CORE" ]; then
log "ERROR: steam or retroarch core not found, install Steam or RetroArch PPSSPP core via Steam"
exit 127
fi
log "Launching RetroArch: $STEAM_BIN -applaunch 1118310 -L \"$RETROARCH_HOME/cores/$RETROARCH_CORE\" \"${@}\""
"$STEAM_BIN" -applaunch 1118310 -L "$RETROARCH_HOME/cores/$RETROARCH_CORE" "${@}" >>"$LOGFILE"
@@ -1,20 +0,0 @@
#!/usr/bin/env bash
set -euo pipefail
LOGFILE="$HOME/.local/logs/retroarch-ppsspp.log"
log() { echo "[$(date --iso-8601=seconds)] $*" | tee -a "$LOGFILE"; }
RETROARCH_HOME="$HOME/.local/share/Steam/steamapps/common/RetroArch"
if [ ! -d "$RETROARCH_HOME" ]; then
log "ERROR: RetroArch directory not found at $RETROARCH_HOME, install it via Steam"
exit 127
fi
RETROARCH_CORE=ppsspp_libretro.so
STEAM_BIN=$(command -v steam || true)
if [ ! -f "$STEAM_BIN" ] || [ ! -f "$RETROARCH_HOME/cores/$RETROARCH_CORE" ]; then
log "ERROR: steam or retroarch core not found, install Steam or RetroArch PPSSPP core via Steam"
exit 127
fi
log "Launching RetroArch: $STEAM_BIN -applaunch 1118310 -L \"$RETROARCH_HOME/cores/$RETROARCH_CORE\" \"${@}\""
"$STEAM_BIN" -applaunch 1118310 -L "$RETROARCH_HOME/cores/$RETROARCH_CORE" "${@}" >>"$LOGFILE"
@@ -1,15 +0,0 @@
#!/usr/bin/env bash
set -euo pipefail
LOGFILE="$HOME/.local/logs/rpcs3.log"
log() { echo "[$(date --iso-8601=seconds)] $*" | tee -a "$LOGFILE"; }
RPCS_BIN="$(command -v rpcs3 || true)"
if [ -z "$RPCS_BIN" ]; then
log "ERROR: rpcs3 not found in PATH"
exit 127
fi
log "Launching RPCS3: $RPCS_BIN \"${@}\""
"$RPCS_BIN" --no-gui --fullscreen "${@}" >>"$LOGFILE"
@@ -1,20 +0,0 @@
#!/usr/bin/env bash
set -euo pipefail
LOGFILE="$HOME/.local/logs/retroarch-snes.log"
log() { echo "[$(date --iso-8601=seconds)] $*" | tee -a "$LOGFILE"; }
RETROARCH_HOME="$HOME/.local/share/Steam/steamapps/common/RetroArch"
if [ ! -d "$RETROARCH_HOME" ]; then
log "ERROR: RetroArch directory not found at $RETROARCH_HOME, install it via Steam"
exit 127
fi
RETROARCH_CORE=mesen-s_libretro.so
STEAM_BIN=$(command -v steam || true)
if [ ! -f "$STEAM_BIN" ] || [ ! -f "$RETROARCH_HOME/cores/$RETROARCH_CORE" ]; then
log "ERROR: steam or retroarch core not found, install Steam or RetroArch PPSSPP core via Steam"
exit 127
fi
log "Launching RetroArch: $STEAM_BIN -applaunch 1118310 -L \"$RETROARCH_HOME/cores/$RETROARCH_CORE\" \"${@}\""
"$STEAM_BIN" -applaunch 1118310 -L "$RETROARCH_HOME/cores/$RETROARCH_CORE" "${@}" >>"$LOGFILE"
@@ -1,24 +0,0 @@
#!/usr/bin/env bash
set -euo pipefail
LOGFILE="$HOME/.local/logs/update_roms.log"
log() { echo "[$(date --iso-8601=seconds)] $*" | tee -a "$LOGFILE"; }
ROM_MANAGER_BIN="$(command -v srm || true)"
if [ -z "$ROM_MANAGER_BIN" ]; then
log "ERROR: srm (Steam ROM Manager) not found in PATH"
exit 127
fi
log "Updating Steam ROM Manager"
$ROM_MANAGER_BIN add >> "$LOGFILE" 2>&1 || {
log "ERROR: Steam ROM Manager update failed"
exit 1
}
log "Steam ROM Manager update completed successfully, restarting Steam service if running"
if systemctl --user is-active --quiet steam.scope; then
log "Restarting Steam service to apply changes"
systemctl --user restart steam.scope
log "Steam service restarted"
else
log "Steam service is not running, no need to restart"
fi
@@ -1,5 +0,0 @@
AP///////wAhVwkhutc0AQ8iAQOARid4AiiVp1VOoyYPUFQhCADRwIHAAQABAAEAAQABAAEAAjqA
GHE4LUBYLEUAxI4hAAAeAAAA/QAYVR5kHgAKICAgICAgAAAA/ABIRCBUTyBVU0IKICAgAAAAEAAA
AAAAAAAAAAAAAAAAAZYCAzBxThAfIiEgBBM+PTxfZAUUIwkHB4MBAABuAwwAEAAAPCAAgAECAwTl
DmFgZmVqXgCgoKApUDAgJQCwEzIAABgZZACAo6AsULAQNRCwEzIAABgAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAArw==
@@ -1,5 +0,0 @@
OUTPUT_CONNECTOR={{ output_connector }}
FRAMERATE={{ framerate }}
RESOLUTION={{ screen_resolution }}
HDR_ENABLED={{ hdr_enabled }}
DRM_DEVICE={{ drm_device }}
@@ -1,7 +0,0 @@
[General]
DisplayServer=wayland
[Autologin]
Relogin=true
Session=steamos.desktop
User={{ user_name }}
@@ -1,8 +0,0 @@
[Desktop Entry]
Name=ALVR
Comment=Linux Virtual Reality
Exec=/opt/alvr/bin/alvr_dashboard
Icon=alvr.png
Terminal=false
Type=Application
Categories=Game;
Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.6 KiB

@@ -1,8 +0,0 @@
[Desktop Entry]
Name=SteamOS
Comment=Switch to SteamOS
Exec=/usr/local/bin/steamos-session-select
Icon=steamos.png
Terminal=false
Type=Application
Categories=Game;
@@ -1,368 +0,0 @@
[
{
"parserType": "Glob",
"configTitle": "Sony PlayStation 3 - RPCS3 (Extracted ISO)",
"steamDirectory": "${steamdirglobal}",
"romDirectory": "/home/gameuser/.config/rpcs3/games",
"steamCategories": [
"PS3"
],
"executableArgs": "\"${filePath}\"",
"executableModifier": "\"${exePath}\"",
"startInDirectory": "",
"titleModifier": "${fuzzyTitle}",
"steamInputEnabled": "1",
"imageProviders": [
"sgdb"
],
"onlineImageQueries": [
"${fuzzyTitle}"
],
"imagePool": "${fuzzyTitle}",
"drmProtect": false,
"userAccounts": {
"specifiedAccounts": [
"Global"
]
},
"parserInputs": {
"glob": "${title}/PS3_GAME/USRDIR/@(eboot.bin|EBOOT.BIN)"
},
"executable": {
"path": "/home/gameuser/.local/bin/launch_rpcs3.sh",
"shortcutPassthrough": false,
"appendArgsToExecutable": true
},
"titleFromVariable": {
"limitToGroups": [
"PS3"
],
"caseInsensitiveVariables": false,
"skipFileIfVariableWasNotFound": false
},
"fuzzyMatch": {
"replaceDiacritics": true,
"removeCharacters": true,
"removeBrackets": true
},
"controllers": {
"ps4": null,
"ps5": null,
"ps5_edge": null,
"xbox360": null,
"xboxone": null,
"xboxelite": null,
"switch_joycon_left": null,
"switch_joycon_right": null,
"switch_pro": null,
"neptune": null,
"steamcontroller_gordon": null
},
"imageProviderAPIs": {
"sgdb": {
"nsfw": false,
"humor": false,
"styles": [],
"stylesHero": [],
"stylesLogo": [],
"stylesIcon": [],
"imageMotionTypes": [
"static"
],
"sizes": [],
"sizesHero": [],
"sizesIcon": []
}
},
"defaultImage": {
"tall": "",
"long": "",
"hero": "",
"logo": "",
"icon": ""
},
"localImages": {
"tall": "",
"long": "",
"hero": "",
"logo": "",
"icon": ""
},
"parserId": "176098777083347867",
"disabled": false,
"version": 25
},
{
"parserType": "Glob",
"configTitle": "Sega Genesis/Mega Drive - Retroarch - Genesis Plus GX",
"steamDirectory": "${steamdirglobal}",
"romDirectory": "/home/gameuser/Emulation/genesis",
"steamCategories": [
"Genesis/Mega Drive"
],
"executableArgs": "\"${filePath}\"",
"executableModifier": "\"${exePath}\"",
"startInDirectory": "",
"titleModifier": "${fuzzyTitle}",
"steamInputEnabled": "1",
"imageProviders": [
"sgdb"
],
"onlineImageQueries": [
"${fuzzyTitle}"
],
"imagePool": "${fuzzyTitle}",
"drmProtect": false,
"userAccounts": {
"specifiedAccounts": [
"Global"
]
},
"parserInputs": {
"glob": "${title}@(.7z|.7Z|.gen|.GEN|.md|.MD|.smd|.SMD|.zip|.ZIP|.bin|.BIN)"
},
"executable": {
"path": "/home/gameuser/.local/bin/launch_genesis.sh",
"shortcutPassthrough": false,
"appendArgsToExecutable": true
},
"titleFromVariable": {
"limitToGroups": [],
"caseInsensitiveVariables": false,
"skipFileIfVariableWasNotFound": false
},
"fuzzyMatch": {
"replaceDiacritics": true,
"removeCharacters": true,
"removeBrackets": true
},
"controllers": {
"ps4": null,
"ps5": null,
"ps5_edge": null,
"xbox360": null,
"xboxone": null,
"xboxelite": null,
"switch_joycon_left": null,
"switch_joycon_right": null,
"switch_pro": null,
"neptune": null,
"steamcontroller_gordon": null
},
"imageProviderAPIs": {
"sgdb": {
"nsfw": false,
"humor": false,
"styles": [],
"stylesHero": [],
"stylesLogo": [],
"stylesIcon": [],
"imageMotionTypes": [
"static"
],
"sizes": [],
"sizesHero": [],
"sizesIcon": []
}
},
"defaultImage": {
"tall": "",
"long": "",
"hero": "",
"logo": "",
"icon": ""
},
"localImages": {
"tall": "",
"long": "",
"hero": "",
"logo": "",
"icon": ""
},
"parserId": "176029625316137679",
"disabled": false,
"version": 25
},
{
"parserType": "Glob",
"configTitle": "Sony PlayStation Portable - Retroarch - PPSSPP",
"steamDirectory": "${steamdirglobal}",
"romDirectory": "/home/gameuser/Emulation/psp",
"steamCategories": [
"PSP"
],
"executableArgs": "\"${filePath}\"",
"executableModifier": "\"${exePath}\"",
"startInDirectory": "",
"titleModifier": "${fuzzyTitle}",
"steamInputEnabled": "1",
"imageProviders": [
"sgdb"
],
"onlineImageQueries": [
"${fuzzyTitle}"
],
"imagePool": "${fuzzyTitle}",
"drmProtect": false,
"userAccounts": {
"specifiedAccounts": [
"Global"
]
},
"parserInputs": {
"glob": "${title}@(.7z|.7Z|.elf|.ELF|.cso|.CSO|.iso|.ISO|.pbp|.PBP|.prx|.PRX)"
},
"executable": {
"path": "/home/gameuser/.local/bin/launch_ppspp.sh",
"shortcutPassthrough": false,
"appendArgsToExecutable": true
},
"titleFromVariable": {
"limitToGroups": [],
"caseInsensitiveVariables": false,
"skipFileIfVariableWasNotFound": false
},
"fuzzyMatch": {
"replaceDiacritics": true,
"removeCharacters": true,
"removeBrackets": true
},
"controllers": {
"ps4": null,
"ps5": null,
"ps5_edge": null,
"xbox360": null,
"xboxone": null,
"xboxelite": null,
"switch_joycon_left": null,
"switch_joycon_right": null,
"switch_pro": null,
"neptune": null,
"steamcontroller_gordon": null
},
"imageProviderAPIs": {
"sgdb": {
"nsfw": false,
"humor": false,
"styles": [],
"stylesHero": [],
"stylesLogo": [],
"stylesIcon": [],
"imageMotionTypes": [
"static"
],
"sizes": [],
"sizesHero": [],
"sizesIcon": []
}
},
"defaultImage": {
"tall": "",
"long": "",
"hero": "",
"logo": "",
"icon": ""
},
"localImages": {
"tall": "",
"long": "",
"hero": "",
"logo": "",
"icon": ""
},
"parserId": "176029685701892152",
"disabled": false,
"version": 25
},
{
"parserType": "Glob",
"configTitle": "Nintendo SNES - Retroarch - Mesen-S",
"steamDirectory": "${steamdirglobal}",
"romDirectory": "/home/gameuser/Emulation/snes",
"steamCategories": [
"SNES"
],
"executableArgs": "\"${filePath}\"",
"executableModifier": "\"${exePath}\"",
"startInDirectory": "",
"titleModifier": "${fuzzyTitle}",
"steamInputEnabled": "1",
"imageProviders": [
"sgdb"
],
"onlineImageQueries": [
"${fuzzyTitle}"
],
"imagePool": "${fuzzyTitle}",
"drmProtect": false,
"userAccounts": {
"specifiedAccounts": [
"Global"
]
},
"parserInputs": {
"glob": "${title}@(.7z|.7Z|.bml|.BML|.sfc|.SFC|.smc|.SMC|.zip|.ZIP)"
},
"executable": {
"path": "/home/gameuser/.local/bin/launch_snes.sh",
"shortcutPassthrough": false,
"appendArgsToExecutable": true
},
"titleFromVariable": {
"limitToGroups": [],
"caseInsensitiveVariables": false,
"skipFileIfVariableWasNotFound": false
},
"fuzzyMatch": {
"replaceDiacritics": true,
"removeCharacters": true,
"removeBrackets": true
},
"controllers": {
"ps4": null,
"ps5": null,
"ps5_edge": null,
"xbox360": null,
"xboxone": null,
"xboxelite": null,
"switch_joycon_left": null,
"switch_joycon_right": null,
"switch_pro": null,
"neptune": null,
"steamcontroller_gordon": null
},
"imageProviderAPIs": {
"sgdb": {
"nsfw": false,
"humor": false,
"styles": [],
"stylesHero": [],
"stylesLogo": [],
"stylesIcon": [],
"imageMotionTypes": [
"static"
],
"sizes": [],
"sizesHero": [],
"sizesIcon": []
}
},
"defaultImage": {
"tall": "",
"long": "",
"hero": "",
"logo": "",
"icon": ""
},
"localImages": {
"tall": "",
"long": "",
"hero": "",
"logo": "",
"icon": ""
},
"parserId": "176029713768938935",
"disabled": false,
"version": 25
}
]
@@ -1,11 +0,0 @@
{
"env": {
"PATH": "$(PATH)"
},
"apps": [
{
"name": "SteamDeck",
"image-path": "steam.png"
}
]
}
@@ -1,2 +0,0 @@
system_tray=disable
log_path = /home/{{ user_name }}/.local/logs/sunshine.log
@@ -1,11 +0,0 @@
[Unit]
Description=mangoapp
After=graphical-session.target
PartOf=graphical-session.target
[Service]
Type=exec
ExecStart=/usr/bin/mangoapp
Restart=always
EnvironmentFile=%t/gamescope-environment
TimeoutStopSec=5
@@ -1,17 +0,0 @@
[Unit]
Description=Gamescope Session
Before=graphical-session.target
PartOf=graphical-session.target
Wants=graphical-session-pre.target
After=graphical-session-pre.target
RefuseManualStart=yes
[Service]
TimeoutStartSec=5
TimeoutStopSec=10
ExecStart=gamescope-session
Type=notify
NotifyAccess=all
# Make Steam's srt-logger write to the journal with it's own prefixes
Environment=SRT_LOG_TO_JOURNAL=1
Slice=session.slice
@@ -1,13 +0,0 @@
[Unit]
Description=Gamescope Session
Requires=graphical-session.target
BindsTo=graphical-session.target
After=graphical-session.target
Requires=gamescope-session.service
BindsTo=gamescope-session.service
Upholds=steam-launcher.service
Wants=ibus-gamescope.service
Wants=gamescope-mangoapp.service
@@ -1,12 +0,0 @@
[Unit]
Description=Wrapper for Ibus
PartOf=graphical-session.target
After=graphical-session.target
[Service]
Type=dbus
ExecStart=/usr/bin/ibus-daemon -r --panel=disable --emoji-extension=disable
BusName=org.freedesktop.IBus
EnvironmentFile=%t/gamescope-environment
Slice=session.slice
TimeoutStopSec=5
@@ -1,16 +0,0 @@
# SPDX-License-Identifier: LGPL-2.1-or-later
#
# This file is part of systemd.
#
# systemd is free software; you can redistribute it and/or modify it
# under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation; either version 2.1 of the License, or
# (at your option) any later version.
[Unit]
Description=User Core Session Slice
Documentation=man:systemd.special(7)
[Slice]
CPUWeight=100
# AllowedCPUs={{ general_cores }}
@@ -1,22 +0,0 @@
[Unit]
Description=Steam Launcher
After=gamescope-session.target
PartOf=gamescope-session.target
[Service]
ExecStart=steam-launcher
# Track short sessions to trigger a repair after enough failures
ExecStartPre=steam-short-session-tracker --track-started
ExecStopPost=steam-short-session-tracker --track-stopped
# To close properly we need to kill the child of the steam wrapper, as the wrapper itself does not forward signals
ExecStop=/bin/bash -c 'kill -TERM $(pgrep -P $MAINPID || echo $MAINPID)'
# Disable the term signal sent from systemd, as we we handle it manually above
# But still want systemd to kill the entire cgroup on timeout
KillSignal=SIGCONT
KillMode=mixed
TimeoutStopSec=60
Type=exec
EnvironmentFile=%t/gamescope-environment
@@ -1,16 +0,0 @@
# SPDX-License-Identifier: LGPL-2.1-or-later
#
# This file is part of systemd.
#
# systemd is free software; you can redistribute it and/or modify it
# under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation; either version 2.1 of the License, or
# (at your option) any later version.
[Unit]
Description=User Core Session Slice
Documentation=man:systemd.special(7)
[Slice]
CPUWeight=100
# AllowedCPUs={{ general_cores }}
@@ -1,9 +0,0 @@
[Unit]
Description=User graphical session service
After=graphical-session.target
[Service]
ExecStart=/usr/local/bin/steamos
EnvironmentFile=%t/steamos-environment
Restart=on-failure
# AllowedCPUs={{ general_cores }}
@@ -1,16 +0,0 @@
# SPDX-License-Identifier: LGPL-2.1-or-later
#
# This file is part of systemd.
#
# systemd is free software; you can redistribute it and/or modify it
# under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation; either version 2.1 of the License, or
# (at your option) any later version.
[Unit]
Description=User Core Session Slice
Documentation=man:systemd.special(7)
[Slice]
CPUWeight=100
# AllowedCPUs={{ sunshine_cores }}
@@ -1,8 +0,0 @@
[Desktop Entry]
Encoding=UTF-8
Name=SteamOS
Comment=SteamOS graphical session
Exec=/usr/local/bin/steamos-session
Icon=steamicon.png
Type=Application
DesktopNames=gamescope
-1
View File
@@ -1 +0,0 @@
ecdsa-sha2-nistp521 AAAAE2VjZHNhLXNoYTItbmlzdHA1MjEAAAAIbmlzdHA1MjEAAACFBAHHOPBR9p9kq5Cqzpe4cr3jHnweaYrHPXv5sXNzt+sCXP54uc5rWUBhxW2OQVvQzJ47dEVhEKi4WSC7LcuKS2G5AQDzWXNiasHvYIYQU3F/EknVCZnsiXYqXphYkJA6rJCNRnISZCIC1mocq6PB7J08ONdRFCvjfUBuVRT8BNGXNmQ/zQ==
@@ -1,2 +0,0 @@
FROM gemma4:e4b
PARAMETER num_ctx 32359
@@ -1,2 +0,0 @@
FROM gpt-oss:latest
PARAMETER num_ctx 32359
@@ -1,2 +0,0 @@
FROM qwen3.5:9b
PARAMETER num_ctx 32359
@@ -1,2 +0,0 @@
FROM qwen3.5:9b
PARAMETER num_ctx 65359
@@ -1,2 +0,0 @@
FROM qwen3.5:9b
PARAMETER num_ctx 32359
@@ -1,2 +0,0 @@
FROM qwen3.5:9b
PARAMETER num_ctx 65537
-118
View File
@@ -1,118 +0,0 @@
# Ensure running as Administrator
if (-not ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole(`
[Security.Principal.WindowsBuiltInRole] "Administrator")) {
Write-Error "Please run this script as Administrator."
exit 1
}
Write-Output "Applying Windows gaming performance optimizations..."
# 1. Set High Performance Power Plan
Write-Output "Setting High Performance power plan..."
powercfg -setactive SCHEME_MIN
# 2. Disable Windows Defender real-time protection
Write-Output "Disabling Windows Defender Real-Time Protection..."
Set-MpPreference -DisableRealtimeMonitoring $true
# Run as Administrator
Write-Host "Disabling unnecessary startup applications..." -ForegroundColor Cyan
# --- 1. Disable from Registry (HKCU)
$startupRegHKCU = "HKCU:\Software\Microsoft\Windows\CurrentVersion\Run"
$disableListHKCU = @(
"OneDrive",
"EpicGamesLauncher",
"EADM",
"Microsoft.Lists",
"MicrosoftEdgeAutoLaunch_4F5320B4C77A9527F492603BB81F11D5"
)
foreach ($name in $disableListHKCU) {
if (Get-ItemProperty -Path $startupRegHKCU -Name $name -ErrorAction SilentlyContinue) {
Write-Host "Removing HKCU startup item: $name"
Remove-ItemProperty -Path $startupRegHKCU -Name $name
}
}
# --- 2. Disable from Registry (HKLM)
$startupRegHKLM = "HKLM:\Software\Microsoft\Windows\CurrentVersion\Run"
$disableListHKLM = @(
"SecurityHealth" # optional; comment this line to keep Windows Security Tray
)
foreach ($name in $disableListHKLM) {
if (Get-ItemProperty -Path $startupRegHKLM -Name $name -ErrorAction SilentlyContinue) {
Write-Host "Removing HKLM startup item: $name"
Remove-ItemProperty -Path $startupRegHKLM -Name $name
}
}
# --- 3. Disable startup folder entries
$startupFolders = @(
"$env:APPDATA\Microsoft\Windows\Start Menu\Programs\Startup",
"C:\ProgramData\Microsoft\Windows\Start Menu\Programs\Startup"
)
foreach ($folder in $startupFolders) {
Get-ChildItem $folder -Filter *.lnk -ErrorAction SilentlyContinue | ForEach-Object {
Write-Host "Disabling startup shortcut: $($_.Name)"
Remove-Item $_.FullName -Force
}
}
Write-Host "`n✅ Startup applications optimized. Reboot to apply changes." -ForegroundColor Green
# 4. Disable background services
Write-Output "Disabling unnecessary background services..."
$servicesToDisable = @(
"DiagTrack", # Connected User Experience and Telemetry
"SysMain", # Superfetch
"WSearch", # Windows Search
"XblGameSave", # Xbox services
"MapsBroker", # Maps background service
"OneSyncSvc" # OneDrive sync
"AdobeARMservice",
"ClickToRunSvc",
"edgeupdate",
"OneSyncSvc_78aa3",
"CDPUserSvc_78aa3",
"CDPSvc",
"webthreatdefusersvc_78aa3",
"WpnService",
"WpnUserService_78aa3",
"Spooler",
"SCardSvr",
"MicrosoftEdgeAutoLaunch_4F5320B4C77A9527F492603BB81F11D5",
"vorpX Service"
)
foreach ($svc in $servicesToDisable) {
try {
Write-Host "Disabling service: $svc" -ForegroundColor Yellow
Stop-Service -Name $svc -Force -ErrorAction SilentlyContinue
Set-Service -Name $svc -StartupType Disabled
} catch {
Write-Host "Could not disable ${svc}: $_" -ForegroundColor Red }
}
# 5. NVIDIA GPU Tweaks - Launch NVIDIA Control Panel (manual steps)
Write-Output "`n[!] Please open NVIDIA Control Panel and set:"
Write-Output "- Power Management: Prefer Maximum Performance"
Write-Output "- Low Latency Mode: Ultra"
Write-Output "- Vertical Sync: Off or Fast Sync (depending on monitor)"
Start-Process "nvcplui.exe"
# 6. Disable Xbox Game Bar and DVR
Write-Output "Disabling Xbox Game Bar and DVR..."
reg add "HKCU\Software\Microsoft\Windows\CurrentVersion\GameDVR" /v "AppCaptureEnabled" /t REG_DWORD /d 0 /f
reg add "HKCU\System\GameConfigStore" /v "GameDVR_Enabled" /t REG_DWORD /d 0 /f
reg add "HKCU\System\GameConfigStore" /v "GameDVR_FSEBehaviorMode" /t REG_DWORD /d 2 /f
reg add "HKCU\System\GameConfigStore" /v "GameDVR_HonorUserFSEBehaviorMode" /t REG_DWORD /d 1 /f
# 7. Disable Notifications
Write-Output "Disabling Windows notifications..."
reg add "HKCU\Software\Microsoft\Windows\CurrentVersion\PushNotifications" /v "ToastEnabled" /t REG_DWORD /d 0 /f
Write-Output "`n✅ Optimization complete. Please restart your VM for all changes to take effect."
+11
View File
@@ -0,0 +1,11 @@
#!/bin/bash
NAMESPACE=gitea
PREFIX=gitea-app
# Get the list of running pod names in the given namespace and filter by prefix
POD_NAME=$(/usr/local/bin/kubectl get pods -n "$NAMESPACE" -o json | jq -r --arg PREFIX "$PREFIX" '.items[] | select(.metadata.name | startswith($PREFIX)) | select(.status.phase == "Running") | .metadata.name' | head -n 1)
# Check if a running pod matching the prefix is found
if [ -z "$POD_NAME" ]; then
exit 1
else
/usr/local/bin/kubectl exec -n "$NAMESPACE" "$POD_NAME" -i -- su -s /bin/bash git -- /usr/local/bin/gitea keys -c /data/gitea/conf/app.ini -e git "$@"
fi
+10 -1
View File
@@ -1,2 +1,11 @@
#!/bin/bash
exec /usr/local/bin/k8s_gitea_shell -r gitea gitea-app "$@"
NAMESPACE=gitea
PREFIX=gitea-app
# Get the list of running pod names in the given namespace and filter by prefix
POD_NAME=$(/usr/local/bin/kubectl get pods -n "$NAMESPACE" -o json | jq -r --arg PREFIX "$PREFIX" '.items[] | select(.metadata.name | startswith($PREFIX)) | select(.status.phase == "Running") | .metadata.name' | head -n 1)
# Remove the -c flag from the command
COMMAND=$(echo "$@" | sed "s/-c //g")
# Check if a running pod matching the prefix is found
if [ ! -z "$POD_NAME" ]; then
/usr/local/bin/kubectl exec -n "$NAMESPACE" "$POD_NAME" -i -- env SSH_ORIGINAL_COMMAND="$SSH_ORIGINAL_COMMAND" su -s /bin/bash git -- $COMMAND
fi
-5
View File
@@ -1,5 +0,0 @@
@echo off
start "" "C:\Program Files (x86)\Steam\steam.exe"
start "" "C:\Program Files\Sunshine\sunshine.exe"
start "" "C:\Path\To\MetaAirLink\metaairlink.exe"
exit

Some files were not shown because too many files have changed in this diff Show More