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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
211 changes: 211 additions & 0 deletions .github/setup-rulesets.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,211 @@
#!/usr/bin/env bash
# =============================================================================
# GitHub Rulesets Setup Script
# =============================================================================
# Configures branch protection and rulesets for the main repository via the
# GitHub REST API. Run this once after cloning, or whenever rules need reset.
#
# USAGE:
# export GITHUB_TOKEN="ghp_yourPersonalAccessToken"
# ./setup-rulesets.sh
#
# REQUIRED TOKEN SCOPES:
# - repo (full) — needed to write branch protection and rulesets
#
# WHAT THIS SETS UP:
# 1. Main branch protection (classic) — require PR, 1 approval, no force-push
# 2. Ruleset: branch naming — enforce feat/fix/chore/docs/refactor/claude/ prefixes
# 3. Ruleset: commit messages — conventional commit format
# =============================================================================

set -euo pipefail

OWNER="aradanmn"
REPO="minecraftsplitscreensteamdeck"
API="https://api.github.com"

# ---------------------------------------------------------------------------
# Preflight checks
# ---------------------------------------------------------------------------

if [[ -z "${GITHUB_TOKEN:-}" ]]; then
echo "[ERROR] GITHUB_TOKEN is not set."
echo " Export a PAT with 'repo' scope:"
echo " export GITHUB_TOKEN=ghp_yourToken"
exit 1
fi

if ! command -v curl >/dev/null 2>&1; then
echo "[ERROR] curl is required but not installed."
exit 1
fi

if ! command -v jq >/dev/null 2>&1; then
echo "[ERROR] jq is required but not installed."
exit 1
fi

AUTH_HEADER="Authorization: Bearer $GITHUB_TOKEN"
CONTENT="Content-Type: application/json"

gh_api() {
local method="$1"
local endpoint="$2"
local body="${3:-}"
if [[ -n "$body" ]]; then
curl -fsSL -X "$method" \
-H "$AUTH_HEADER" \
-H "$CONTENT" \
-H "Accept: application/vnd.github+json" \
-H "X-GitHub-Api-Version: 2022-11-28" \
--data "$body" \
"$API$endpoint"
else
curl -fsSL -X "$method" \
-H "$AUTH_HEADER" \
-H "Accept: application/vnd.github+json" \
-H "X-GitHub-Api-Version: 2022-11-28" \
"$API$endpoint"
fi
}

echo ""
echo "=== GitHub Rulesets Setup: $OWNER/$REPO ==="
echo ""

# ---------------------------------------------------------------------------
# 1. Classic branch protection on main
# - Require PRs with at least 1 approval
# - Dismiss stale reviews when new commits are pushed
# - Require conversation resolution before merge
# - Require ShellCheck status check to pass
# - No force-pushes, no branch deletion
# - Linear history (squash/rebase only — no merge commits)
# ---------------------------------------------------------------------------

echo "[1/3] Applying classic branch protection to 'main'..."

gh_api PUT "/repos/$OWNER/$REPO/branches/main/protection" "$(jq -n '{
required_status_checks: {
strict: true,
contexts: ["Lint shell scripts"]
},
enforce_admins: true,
required_pull_request_reviews: {
dismiss_stale_reviews: true,
require_code_owner_reviews: false,
required_approving_review_count: 1,
require_last_push_approval: false
},
restrictions: null,
required_linear_history: true,
allow_force_pushes: false,
allow_deletions: false,
block_creations: false,
required_conversation_resolution: true
}')" | jq '.url' -r

echo "[OK] Main branch protection applied."
echo ""

# ---------------------------------------------------------------------------
# 2. Ruleset: Branch naming convention
# Applies to all branches EXCEPT main.
# Allowed prefixes: feat/ fix/ chore/ docs/ refactor/ claude/ hotfix/ test/
# ---------------------------------------------------------------------------

echo "[2/3] Creating ruleset: branch naming convention..."

# Delete existing ruleset with same name if present (idempotent re-run)
existing=$(gh_api GET "/repos/$OWNER/$REPO/rulesets" | jq -r '.[] | select(.name == "Branch naming convention") | .id')
if [[ -n "$existing" ]]; then
echo " Deleting existing ruleset (id=$existing)..."
gh_api DELETE "/repos/$OWNER/$REPO/rulesets/$existing" >/dev/null
fi

gh_api POST "/repos/$OWNER/$REPO/rulesets" "$(jq -n '{
name: "Branch naming convention",
target: "branch",
enforcement: "active",
conditions: {
ref_name: {
include: ["~ALL"],
exclude: ["refs/heads/main"]
}
},
rules: [
{
type: "ref_name",
parameters: {
patterns: [
"feat/*",
"fix/*",
"chore/*",
"docs/*",
"refactor/*",
"claude/*",
"hotfix/*",
"test/*"
]
}
}
]
}')" | jq '.id' -r | xargs -I{} echo " Created ruleset id={}"

echo "[OK] Branch naming ruleset applied."
echo ""

# ---------------------------------------------------------------------------
# 3. Ruleset: Conventional commit messages
# All commits to main (via PR squash/merge) must start with a conventional
# commit type: feat|fix|chore|docs|refactor|perf|test|ci|build|revert
# ---------------------------------------------------------------------------

echo "[3/3] Creating ruleset: conventional commit messages..."

existing=$(gh_api GET "/repos/$OWNER/$REPO/rulesets" | jq -r '.[] | select(.name == "Conventional commits") | .id')
if [[ -n "$existing" ]]; then
echo " Deleting existing ruleset (id=$existing)..."
gh_api DELETE "/repos/$OWNER/$REPO/rulesets/$existing" >/dev/null
fi

gh_api POST "/repos/$OWNER/$REPO/rulesets" "$(jq -n '{
name: "Conventional commits",
target: "branch",
enforcement: "active",
conditions: {
ref_name: {
include: ["refs/heads/main"],
exclude: []
}
},
rules: [
{
type: "commit_message_pattern",
parameters: {
name: "Conventional commit format",
negate: false,
operator: "regex",
pattern: "^(feat|fix|chore|docs|refactor|perf|test|ci|build|revert)(\\([^)]+\\))?: .+"
}
}
]
}')" | jq '.id' -r | xargs -I{} echo " Created ruleset id={}"

echo "[OK] Conventional commit ruleset applied."
echo ""
echo "=== Setup complete. ==="
echo ""
echo "Summary of rules now active on $OWNER/$REPO:"
echo " main branch:"
echo " - PRs required (1 approval, stale reviews dismissed)"
echo " - ShellCheck CI must pass before merge"
echo " - Conversation resolution required"
echo " - Linear history enforced (squash/rebase only)"
echo " - Force-push and deletion blocked"
echo " - Admins are not exempt"
echo " All other branches:"
echo " - Must be named: feat/* fix/* chore/* docs/* refactor/* claude/* hotfix/* test/*"
echo " Commits landing on main:"
echo " - Must follow conventional commit format"
echo ""
24 changes: 24 additions & 0 deletions .github/workflows/shellcheck.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
name: ShellCheck

on:
pull_request:
branches: [main]
push:
branches: [main]

jobs:
shellcheck:
name: Lint shell scripts
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Install ShellCheck
run: sudo apt-get install -y shellcheck

- name: Run ShellCheck on all scripts
run: |
find . -name "*.sh" -not -path "./.git/*" | sort | while read -r f; do
echo "Checking: $f"
shellcheck --severity=error --exclude=SC2034,SC2155,SC2046 "$f"
done
9 changes: 5 additions & 4 deletions modules/launcher_script_generator.sh
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
#!/bin/bash
# =============================================================================
# @file launcher_script_generator.sh
# @version 3.2.12
# @version 3.2.13
# @date 2026-04-18
# @author Minecraft Splitscreen Steam Deck Project
# @license MIT
Expand Down Expand Up @@ -30,6 +30,7 @@
# - verify_generated_script : Validates generated script (executable, no placeholders, syntax)
#
# @changelog
# 3.2.13 (2026-04-18) - Fix: killall plasmashell scoped to current user (-u $USER); numeric comparisons for controller count and player count
# 3.2.12 (2026-04-18) - Fix: move CURRENT_PLAYER_COUNT update before updatePlaceholderWindow in scale-up path so placeholder shows correctly when 3rd player joins
# 3.2.11 (2026-04-18) - Fix: add quick 1s second reposition pass so KWin processes fullscreen clearance before geometry is re-applied; keeps 7s third pass for stragglers
# 3.2.10 (2026-04-18) - Fix: second reposition pass 8s after first to catch late-opening windows (P3/P4 under load)
Expand Down Expand Up @@ -724,7 +725,7 @@ hidePanels() {
pkill plasmashell
sleep 1
if pgrep -u "$USER" plasmashell >/dev/null; then
killall plasmashell
killall -u "$USER" plasmashell
sleep 1
fi
if pgrep -u "$USER" plasmashell >/dev/null; then
Expand Down Expand Up @@ -1045,7 +1046,7 @@ monitorControllers() {
sleep 0.5 # Debounce rapid events
local new_count
new_count=$(getControllerCount)
if [ "$new_count" != "$last_count" ]; then
if [ "$new_count" -ne "$last_count" ]; then
echo "CONTROLLER_CHANGE:$new_count"
last_count=$new_count
fi
Expand Down Expand Up @@ -2086,7 +2087,7 @@ hidePlaceholderWindow() {
updatePlaceholderWindow() {
# CURRENT_PLAYER_COUNT is maintained by handleControllerChange and
# checkForExitedInstances — read it directly instead of spawning a subshell.
if [ "$CURRENT_PLAYER_COUNT" = "3" ]; then
if [ "$CURRENT_PLAYER_COUNT" -eq 3 ]; then
# Skip re-spawn if the window is already live — avoids a brief flicker
# and the cost of a redundant tkinter availability check + process fork.
if [ -n "$PLACEHOLDER_PID" ] && kill -0 "$PLACEHOLDER_PID" 2>/dev/null; then
Expand Down
Loading