Compare commits
93 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 33cccdf396 | |||
| 32da5410bc | |||
| 3063561832 | |||
| ebbb1254fb | |||
|
040f7a382f
|
|||
|
dfeaea3283
|
|||
|
9a9b36cac6
|
|||
|
c6f3b0f829
|
|||
|
c4d1db3a91
|
|||
|
e59a832d5d
|
|||
|
df5a98853b
|
|||
|
4825ad1495
|
|||
| 80c154df03 | |||
|
f8a0a8ab1f
|
|||
|
98b0f215aa
|
|||
|
25d3eafa5b
|
|||
|
3775fa4b6e
|
|||
|
bb8b14dcca
|
|||
|
ec740f458f
|
|||
|
350650ecc2
|
|||
|
e9e28a5ca8
|
|||
|
dd943c8963
|
|||
|
255ba6aec9
|
|||
|
829ab48919
|
|||
|
dcf9a5c796
|
|||
|
5acdfd6b23
|
|||
|
2c8b753393
|
|||
|
3c6647c215
|
|||
|
03d5b39a4b
|
|||
|
4781ddbaff
|
|||
|
b4f4fe2f07
|
|||
|
edbe8fbf6f
|
|||
|
b3601e08b1
|
|||
|
0cf0261359
|
|||
|
df2931ca81
|
|||
|
d22ac2d4f4
|
|||
|
d42d717f6c
|
|||
|
7a7f2711fe
|
|||
|
9fa68f3bc5
|
|||
|
49de842b57
|
|||
|
adc893de6e
|
|||
|
161efa5230
|
|||
|
d069fe3585
|
|||
|
84c8288f8a
|
|||
|
7f9a64b0ed
|
|||
|
429a94332e
|
|||
|
0e167d747d
|
|||
|
9b330f84a2
|
|||
|
0d3fb56f78
|
|||
|
7cad175406
|
|||
|
cd69dbc25a
|
|||
|
eb60d37c2d
|
|||
|
0a0d4c9c15
|
|||
|
a412c96c46
|
|||
|
e6a3f3affd
|
|||
|
4d00986273
|
|||
|
1e8526f442
|
|||
| aee57a8b89 | |||
|
ff4197e757
|
|||
|
50758b2bb2
|
|||
| fa2d111bbe | |||
| bef5d94023 | |||
| 1cc8294883 | |||
|
372df1b9e9
|
|||
|
bc85a7bcef
|
|||
|
6579e15394
|
|||
|
dab5520630
|
|||
|
c162944a73
|
|||
|
b813cf8eda
|
|||
|
fc21db9e43
|
|||
|
b130883bc6
|
|||
|
826959c903
|
|||
|
cac269f160
|
|||
|
359b6d47aa
|
|||
|
d969525b58
|
|||
|
01bf71f8d2
|
|||
|
3cda0b5624
|
|||
|
d3b8aff1ae
|
|||
|
3ea65f060f
|
|||
|
a2dc223d1f
|
|||
|
755e14a602
|
|||
|
54f7e5536a
|
|||
|
b0162abb88
|
|||
|
8dd48ec2c7
|
|||
|
b692627317
|
|||
| c1c92a866e | |||
|
071b4eea9e
|
|||
|
f6be1d5dbe
|
|||
|
2ba2df5cf7
|
|||
|
c9def750f6
|
|||
|
fef5c73726
|
|||
|
4e07e062ce
|
|||
|
15b07ea85d
|
@@ -0,0 +1,99 @@
|
||||
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
|
||||
@@ -0,0 +1,24 @@
|
||||
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
|
||||
@@ -0,0 +1,54 @@
|
||||
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
|
||||
@@ -0,0 +1,19 @@
|
||||
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
|
||||
@@ -0,0 +1,15 @@
|
||||
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
|
||||
@@ -0,0 +1,37 @@
|
||||
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
|
||||
@@ -0,0 +1,39 @@
|
||||
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
|
||||
@@ -0,0 +1,39 @@
|
||||
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
|
||||
@@ -0,0 +1,39 @@
|
||||
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
|
||||
@@ -0,0 +1,63 @@
|
||||
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
|
||||
@@ -0,0 +1,39 @@
|
||||
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
|
||||
@@ -0,0 +1,130 @@
|
||||
# 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.
|
||||
@@ -1,53 +0,0 @@
|
||||
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
|
||||
@@ -1,45 +0,0 @@
|
||||
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
|
||||
-1
@@ -29,7 +29,6 @@ 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:
|
||||
-1
@@ -37,7 +37,6 @@ 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
|
||||
+2
-3
@@ -1,13 +1,11 @@
|
||||
name: OpenTofu K8S Apps apply
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: "0 2 * * 0"
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
paths:
|
||||
- "terraform/k8sapps/**"
|
||||
- "terraform/apps/contabo/**"
|
||||
jobs:
|
||||
iac-apply:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -37,6 +35,7 @@ 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 }}
|
||||
+2
-1
@@ -42,4 +42,5 @@ drafts/*
|
||||
.molecule
|
||||
*.auto.tfvars
|
||||
dev/*
|
||||
.ansible
|
||||
.ansible
|
||||
*.x.c
|
||||
|
||||
@@ -0,0 +1,486 @@
|
||||
---
|
||||
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.
|
||||
@@ -0,0 +1,301 @@
|
||||
---
|
||||
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`.
|
||||
@@ -0,0 +1,115 @@
|
||||
---
|
||||
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.
|
||||
@@ -0,0 +1,114 @@
|
||||
# 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`
|
||||
@@ -26,4 +26,127 @@ 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
@@ -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
|
||||
|
||||
@@ -1,55 +0,0 @@
|
||||
#!/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,3 +1,5 @@
|
||||
Match User git
|
||||
Banner none
|
||||
PermitTTY no
|
||||
AuthorizedKeysCommandUser git
|
||||
AuthorizedKeysCommand /usr/local/bin/gitea-auth -u %u -t %t -k %k
|
||||
AuthorizedKeysCommand /usr/local/bin/k8s_gitea_auth -r gitea gitea-app -u %u -t %t -k %k
|
||||
@@ -0,0 +1,35 @@
|
||||
# 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"]
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
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"]
|
||||
@@ -0,0 +1,14 @@
|
||||
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"]
|
||||
@@ -0,0 +1,12 @@
|
||||
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"]
|
||||
@@ -0,0 +1,12 @@
|
||||
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"]
|
||||
@@ -0,0 +1,18 @@
|
||||
FROM local/gitea-runner-base:latest
|
||||
|
||||
# cicd-base runner profile: common CI build and release tooling.
|
||||
USER root
|
||||
RUN apk add --no-cache \
|
||||
bash \
|
||||
ca-certificates \
|
||||
curl \
|
||||
git \
|
||||
jq \
|
||||
make \
|
||||
openssh-client \
|
||||
python3 \
|
||||
py3-pip \
|
||||
rsync \
|
||||
unzip \
|
||||
yq \
|
||||
zip
|
||||
@@ -0,0 +1,20 @@
|
||||
FROM local/gitea-runner-base:latest
|
||||
|
||||
# cpp-build runner profile: dedicated C/C++ build environment.
|
||||
USER root
|
||||
RUN apk add --no-cache \
|
||||
bash \
|
||||
build-base \
|
||||
ca-certificates \
|
||||
cmake \
|
||||
curl \
|
||||
git \
|
||||
jq \
|
||||
make \
|
||||
openssh-client \
|
||||
python3 \
|
||||
py3-pip \
|
||||
rsync \
|
||||
unzip \
|
||||
yq \
|
||||
zip
|
||||
@@ -0,0 +1,13 @@
|
||||
FROM docker.io/gitea/runner:latest
|
||||
|
||||
USER root
|
||||
|
||||
RUN apk add --no-cache \
|
||||
bash \
|
||||
ca-certificates \
|
||||
curl \
|
||||
git \
|
||||
jq \
|
||||
openssh-client \
|
||||
unzip \
|
||||
zip
|
||||
@@ -0,0 +1,19 @@
|
||||
FROM local/gitea-runner-base:latest
|
||||
|
||||
# go-build runner profile: dedicated Go build environment.
|
||||
USER root
|
||||
RUN apk add --no-cache \
|
||||
bash \
|
||||
ca-certificates \
|
||||
curl \
|
||||
git \
|
||||
go \
|
||||
jq \
|
||||
make \
|
||||
openssh-client \
|
||||
python3 \
|
||||
py3-pip \
|
||||
rsync \
|
||||
unzip \
|
||||
yq \
|
||||
zip
|
||||
@@ -0,0 +1,32 @@
|
||||
FROM local/gitea-runner-base:latest
|
||||
|
||||
# image-build runner profile: dedicated environment for container image builds.
|
||||
USER root
|
||||
RUN apk add --no-cache \
|
||||
bash \
|
||||
buildah \
|
||||
ca-certificates \
|
||||
curl \
|
||||
fuse-overlayfs \
|
||||
git \
|
||||
iptables \
|
||||
jq \
|
||||
make \
|
||||
openssh-client \
|
||||
podman \
|
||||
python3 \
|
||||
py3-pip \
|
||||
rsync \
|
||||
shadow-uidmap \
|
||||
skopeo \
|
||||
slirp4netns \
|
||||
unzip \
|
||||
yq \
|
||||
zip
|
||||
|
||||
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
|
||||
|
||||
ENV BUILDAH_ISOLATION=chroot
|
||||
ENV STORAGE_DRIVER=vfs
|
||||
@@ -0,0 +1,28 @@
|
||||
FROM local/gitea-runner-base:latest
|
||||
|
||||
# infra-tools runner profile: Ansible and infrastructure automation tools.
|
||||
USER root
|
||||
RUN apk add --no-cache \
|
||||
bash \
|
||||
ca-certificates \
|
||||
curl \
|
||||
git \
|
||||
jq \
|
||||
make \
|
||||
openssh-client \
|
||||
python3 \
|
||||
py3-pip \
|
||||
rsync \
|
||||
unzip \
|
||||
yq \
|
||||
zip && \
|
||||
python3 -m venv /opt/infra-venv && \
|
||||
/opt/infra-venv/bin/pip install --no-cache-dir \
|
||||
ansible \
|
||||
ansible-lint \
|
||||
bcrypt \
|
||||
fabric \
|
||||
passlib \
|
||||
pywinrm
|
||||
|
||||
ENV PATH="/opt/infra-venv/bin:${PATH}"
|
||||
@@ -0,0 +1,25 @@
|
||||
FROM local/gitea-runner-base:latest
|
||||
|
||||
# security-scan runner profile: image and dependency scanning tooling.
|
||||
USER root
|
||||
RUN apk add --no-cache \
|
||||
bash \
|
||||
ca-certificates \
|
||||
curl \
|
||||
git \
|
||||
jq \
|
||||
make \
|
||||
openssh-client \
|
||||
python3 \
|
||||
py3-pip \
|
||||
rsync \
|
||||
unzip \
|
||||
yq \
|
||||
zip
|
||||
|
||||
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
|
||||
@@ -0,0 +1,22 @@
|
||||
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"]
|
||||
@@ -0,0 +1,8 @@
|
||||
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
|
||||
@@ -0,0 +1,7 @@
|
||||
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
|
||||
@@ -0,0 +1,7 @@
|
||||
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
|
||||
@@ -0,0 +1,9 @@
|
||||
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
|
||||
@@ -0,0 +1,7 @@
|
||||
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
|
||||
@@ -0,0 +1,17 @@
|
||||
$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
|
||||
@@ -0,0 +1,157 @@
|
||||
#!/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
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
#!/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"
|
||||
@@ -0,0 +1,38 @@
|
||||
#!/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"
|
||||
@@ -0,0 +1,64 @@
|
||||
#!/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"
|
||||
@@ -0,0 +1,148 @@
|
||||
#!/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"
|
||||
@@ -0,0 +1,77 @@
|
||||
#!/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
|
||||
@@ -0,0 +1,14 @@
|
||||
#!/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
|
||||
@@ -0,0 +1,30 @@
|
||||
#!/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
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
# #!/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"
|
||||
@@ -0,0 +1,39 @@
|
||||
#!/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
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
#!/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
|
||||
@@ -0,0 +1,20 @@
|
||||
#!/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"
|
||||
@@ -0,0 +1,20 @@
|
||||
#!/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"
|
||||
@@ -0,0 +1,15 @@
|
||||
#!/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"
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
#!/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"
|
||||
@@ -0,0 +1,24 @@
|
||||
#!/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
|
||||
@@ -0,0 +1,5 @@
|
||||
AP///////wAhVwkhutc0AQ8iAQOARid4AiiVp1VOoyYPUFQhCADRwIHAAQABAAEAAQABAAEAAjqA
|
||||
GHE4LUBYLEUAxI4hAAAeAAAA/QAYVR5kHgAKICAgICAgAAAA/ABIRCBUTyBVU0IKICAgAAAAEAAA
|
||||
AAAAAAAAAAAAAAAAAZYCAzBxThAfIiEgBBM+PTxfZAUUIwkHB4MBAABuAwwAEAAAPCAAgAECAwTl
|
||||
DmFgZmVqXgCgoKApUDAgJQCwEzIAABgZZACAo6AsULAQNRCwEzIAABgAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAArw==
|
||||
@@ -0,0 +1,5 @@
|
||||
OUTPUT_CONNECTOR={{ output_connector }}
|
||||
FRAMERATE={{ framerate }}
|
||||
RESOLUTION={{ screen_resolution }}
|
||||
HDR_ENABLED={{ hdr_enabled }}
|
||||
DRM_DEVICE={{ drm_device }}
|
||||
@@ -0,0 +1,7 @@
|
||||
[General]
|
||||
DisplayServer=wayland
|
||||
|
||||
[Autologin]
|
||||
Relogin=true
|
||||
Session=steamos.desktop
|
||||
User={{ user_name }}
|
||||
@@ -0,0 +1,8 @@
|
||||
[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.
|
After Width: | Height: | Size: 24 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 4.6 KiB |
@@ -0,0 +1,8 @@
|
||||
[Desktop Entry]
|
||||
Name=SteamOS
|
||||
Comment=Switch to SteamOS
|
||||
Exec=/usr/local/bin/steamos-session-select
|
||||
Icon=steamos.png
|
||||
Terminal=false
|
||||
Type=Application
|
||||
Categories=Game;
|
||||
@@ -0,0 +1,368 @@
|
||||
[
|
||||
{
|
||||
"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
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"env": {
|
||||
"PATH": "$(PATH)"
|
||||
},
|
||||
"apps": [
|
||||
{
|
||||
"name": "SteamDeck",
|
||||
"image-path": "steam.png"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
system_tray=disable
|
||||
log_path = /home/{{ user_name }}/.local/logs/sunshine.log
|
||||
@@ -0,0 +1,11 @@
|
||||
[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
|
||||
@@ -0,0 +1,17 @@
|
||||
[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
|
||||
@@ -0,0 +1,13 @@
|
||||
[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
|
||||
@@ -0,0 +1,12 @@
|
||||
[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
|
||||
@@ -0,0 +1,16 @@
|
||||
# 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 }}
|
||||
@@ -0,0 +1,22 @@
|
||||
[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
|
||||
@@ -0,0 +1,16 @@
|
||||
# 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 }}
|
||||
@@ -0,0 +1,9 @@
|
||||
[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 }}
|
||||
@@ -0,0 +1,16 @@
|
||||
# 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 }}
|
||||
@@ -0,0 +1,8 @@
|
||||
[Desktop Entry]
|
||||
Encoding=UTF-8
|
||||
Name=SteamOS
|
||||
Comment=SteamOS graphical session
|
||||
Exec=/usr/local/bin/steamos-session
|
||||
Icon=steamicon.png
|
||||
Type=Application
|
||||
DesktopNames=gamescope
|
||||
@@ -0,0 +1 @@
|
||||
ecdsa-sha2-nistp521 AAAAE2VjZHNhLXNoYTItbmlzdHA1MjEAAAAIbmlzdHA1MjEAAACFBAHHOPBR9p9kq5Cqzpe4cr3jHnweaYrHPXv5sXNzt+sCXP54uc5rWUBhxW2OQVvQzJ47dEVhEKi4WSC7LcuKS2G5AQDzWXNiasHvYIYQU3F/EknVCZnsiXYqXphYkJA6rJCNRnISZCIC1mocq6PB7J08ONdRFCvjfUBuVRT8BNGXNmQ/zQ==
|
||||
@@ -0,0 +1,2 @@
|
||||
FROM gemma4:e4b
|
||||
PARAMETER num_ctx 32359
|
||||
@@ -0,0 +1,2 @@
|
||||
FROM gpt-oss:latest
|
||||
PARAMETER num_ctx 32359
|
||||
@@ -0,0 +1,2 @@
|
||||
FROM qwen3.5:9b
|
||||
PARAMETER num_ctx 32359
|
||||
@@ -0,0 +1,2 @@
|
||||
FROM qwen3.5:9b
|
||||
PARAMETER num_ctx 65359
|
||||
@@ -0,0 +1,2 @@
|
||||
FROM qwen3.5:9b
|
||||
PARAMETER num_ctx 32359
|
||||
@@ -0,0 +1,2 @@
|
||||
FROM qwen3.5:9b
|
||||
PARAMETER num_ctx 65537
|
||||
@@ -0,0 +1,118 @@
|
||||
# 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."
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user