commit ac457349afe947c7ceeb89c390f8ebd905b5d393 Author: lyn Date: Sat Mar 21 14:04:00 2026 +0000 Initial commit: git-hangman-lab and keycloak-hangman-lab skills diff --git a/git-hangman-lab/SKILL.md b/git-hangman-lab/SKILL.md new file mode 100644 index 0000000..3eea903 --- /dev/null +++ b/git-hangman-lab/SKILL.md @@ -0,0 +1,80 @@ +--- +name: git-hangman-lab +description: Git operations for hangman-lab.top - manage accounts, tokens, repositories, and Gitea settings. +--- + +> ⚠️ **Note**: All scripts must be executed via the `pcexec` tool. + +## Git Operations + +### Check Git Credentials + +Verify git credentials are configured correctly. + +```bash +{baseDir}/scripts/git/git-ctrl check-git-cred +``` + +### Create Git Account + +Create a new git account and configure access. + +> ⚠️ **Warning**: Do not execute this command unless explicitly requested. If you don't have a git account, contact **agent-resource-director** or **hangman** to guide you through the process. + +```bash +{baseDir}/scripts/git/git-ctrl create-git-account --username --email +``` + +### Generate Access Token + +Generate an access token for the current user. + +```bash +{baseDir}/scripts/git/git-ctrl generate-access-token +``` + +### Link Keycloak Account + +Link Keycloak account with Gitea (for OAuth binding). + +```bash +{baseDir}/scripts/git/git-ctrl link-keycloak +``` + +### Add Repository Collaborator + +Add a collaborator to a repository. + +```bash +# Add to specific repository +{baseDir}/scripts/git/git-ctrl repo-add-collaborators --user --repo + +# Add to roster repository (hzhang/.roster) +# ⚠️ This command can only be executed by agent-resource-director +{baseDir}/scripts/git/git-ctrl repo-add-collaborators --user --roster +``` + +### Repository Config + +When you clone a repository from git.hangman-lab.top and are ready to develop, or after creating a new local repo with git init, run: + +```bash +{baseDir}/scripts/git/git-ctrl repo-config --repo-path --email +``` + +### External Login Control + +Enable or disable local login on Gitea. + +```bash +{baseDir}/scripts/git/git-ctrl external-login-ctrl --enable +{baseDir}/scripts/git/git-ctrl external-login-ctrl --disable +``` + +### Reset Password + +Reset password for the current user (reads username from pass_mgr). + +```bash +{baseDir}/scripts/git/git-ctrl reset-password +``` diff --git a/git-hangman-lab/scripts/git/check-git-cred b/git-hangman-lab/scripts/git/check-git-cred new file mode 100755 index 0000000..49946ff --- /dev/null +++ b/git-hangman-lab/scripts/git/check-git-cred @@ -0,0 +1,28 @@ +#!/usr/bin/env bash +set -euo pipefail + +GIT_URL="https://git.hangman-lab.top" + +USER="$(pass_mgr get-username --key git)" +PASS="$(pass_mgr get-secret --key git)" + +if [[ -z "$USER" || -z "$PASS" ]]; then + echo "Missing credentials from pass_mgr (key: git)" + exit 2 +fi + +response=$(curl -s -w "%{http_code}" -u "$USER:$PASS" "$GIT_URL/api/v1/user") +http_code="${response: -3}" +body="${response:0:-3}" + +if [[ "$http_code" == "200" ]]; then + echo "OK" + exit 0 +elif [[ "$http_code" == "401" ]]; then + echo "AUTH FAILED" + exit 1 +else + echo "ERROR: HTTP $http_code" + echo "$body" + exit 1 +fi diff --git a/git-hangman-lab/scripts/git/create-git-account b/git-hangman-lab/scripts/git/create-git-account new file mode 100755 index 0000000..1b43a08 --- /dev/null +++ b/git-hangman-lab/scripts/git/create-git-account @@ -0,0 +1,38 @@ +#!/bin/bash + +# Get the directory where this script is located +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" + +# Parse arguments +while [[ $# -gt 0 ]]; do + case $1 in + --username) + username="$2" + shift 2 + ;; + --email) + email="$2" + shift 2 + ;; + *) + echo "Unknown option: $1" + exit 1 + ;; + esac +done + +# Check if username and email are provided +if [[ -z "$username" || -z "$email" ]]; then + echo "Usage: $0 --username --email " + exit 1 +fi + +# Generate git credentials (do not print secret) +pass_mgr generate --username "$username" --key git >/dev/null + +# Create gitea user +"$SCRIPT_DIR/gitea" admin user create \ + --username "$(pass_mgr get-username --key git)" \ + --password "$(pass_mgr get-secret --key git)" \ + --email "$email" \ + --must-change-password=false diff --git a/git-hangman-lab/scripts/git/external-login-ctrl b/git-hangman-lab/scripts/git/external-login-ctrl new file mode 100755 index 0000000..6fb69a3 --- /dev/null +++ b/git-hangman-lab/scripts/git/external-login-ctrl @@ -0,0 +1,100 @@ +#!/bin/bash +set -euo pipefail + +# Parse arguments +enable=false +disable=false + +while [[ $# -gt 0 ]]; do + case $1 in + --enable) + enable=true + shift + ;; + --disable) + disable=true + shift + ;; + *) + echo "Unknown option: $1" + echo "Usage: $0 [--enable | --disable]" + exit 1 + ;; + esac +done + +if [[ "$enable" == "false" && "$disable" == "false" ]]; then + echo "Usage: $0 [--enable | --disable]" + exit 1 +fi + +if [[ "$enable" == "true" && "$disable" == "true" ]]; then + echo "Error: Cannot use both --enable and --disable" + exit 1 +fi + +REMOTE_HOST="vps.git" +REMOTE_USER="root" +GITEA_URL="https://git.hangman-lab.top/user/login" +MAX_RETRIES=10 +RETRY_INTERVAL=3 +TARGET_VALUE="true" +ACTION_WORD="disabled" + +if [[ "$enable" == "true" ]]; then + TARGET_VALUE="false" + ACTION_WORD="enabled" +fi + +wait_for_gitea() { + echo "[INFO] Waiting for Gitea to be ready..." + for i in $(seq 1 $MAX_RETRIES); do + STATUS=$(curl -s -o /dev/null -w '%{http_code}' "$GITEA_URL" 2>/dev/null || echo "000") + if [[ "$STATUS" == "200" ]]; then + echo "[INFO] Gitea is ready (HTTP 200)." + return 0 + fi + echo "[INFO] Waiting for Gitea ($i/$MAX_RETRIES), current status: $STATUS..." + sleep $RETRY_INTERVAL + done + echo "[WARN] Gitea did not return 200 within expected time." + return 0 +} + +echo "[INFO] Setting REQUIRE_EXTERNAL_LOGIN = $TARGET_VALUE on Dockerized Gitea..." +ssh "$REMOTE_USER@$REMOTE_HOST" " + set -euo pipefail + python3 - <<'PY' +from pathlib import Path +p = Path('/root/git-kc/gitea/app.ini') +lines = p.read_text().splitlines() +out = [] +in_auth = False +updated_in_auth = False +for line in lines: + stripped = line.strip() + if stripped.startswith('['): + if in_auth and not updated_in_auth: + out.append('REQUIRE_EXTERNAL_LOGIN = $TARGET_VALUE') + updated_in_auth = True + in_auth = (stripped == '[auth]') + out.append(line) + continue + if stripped.startswith('REQUIRE_EXTERNAL_LOGIN = '): + if in_auth and not updated_in_auth: + out.append('REQUIRE_EXTERNAL_LOGIN = $TARGET_VALUE') + updated_in_auth = True + # drop all duplicates outside [auth], and extra duplicates inside [auth] + continue + out.append(line) +if in_auth and not updated_in_auth: + out.append('REQUIRE_EXTERNAL_LOGIN = $TARGET_VALUE') +if not any(l.strip() == '[auth]' for l in lines): + out.extend(['', '[auth]', 'REQUIRE_EXTERNAL_LOGIN = $TARGET_VALUE']) +p.write_text('\n'.join(out) + '\n') +PY + docker restart git-kc-gitea >/dev/null +" <&0 + +wait_for_gitea +echo "[DONE] Local login $ACTION_WORD." diff --git a/git-hangman-lab/scripts/git/generate-access-token b/git-hangman-lab/scripts/git/generate-access-token new file mode 100755 index 0000000..8e5ded1 --- /dev/null +++ b/git-hangman-lab/scripts/git/generate-access-token @@ -0,0 +1,14 @@ +#!/bin/bash + +# Get the directory where this script is located +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" + +# Verify git credentials first +"$SCRIPT_DIR/check-git-cred" + +username=$(pass_mgr get-username --key git) +token=$("$SCRIPT_DIR/gitea" admin user generate-access-token --username "$username" --token-name "$username") + +pass_mgr set --key git-access-token --username "$username" --secret "$token" + +echo "Access token generated and stored successfully" diff --git a/git-hangman-lab/scripts/git/git-ctrl b/git-hangman-lab/scripts/git/git-ctrl new file mode 100755 index 0000000..44082e5 --- /dev/null +++ b/git-hangman-lab/scripts/git/git-ctrl @@ -0,0 +1,57 @@ +#!/bin/bash + +# Get the directory where this script is located +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" + +# Check if subcommand is provided +if [[ $# -eq 0 ]]; then + echo "Usage: $0 [options]" + echo "" + echo "Commands:" + echo " check-git-cred Verify git credentials" + echo " create-git-account Create a new git account" + echo " generate-access-token Generate access token for current user" + echo " link-keycloak Link Keycloak account with Gitea" + echo " repo-add-collaborators Add collaborator to repository" + echo " repo-config Configure repository" + echo " external-login-ctrl Enable/disable local login" + echo " reset-password Reset user password" + exit 1 +fi + +# Get subcommand +subcommand="$1" +shift + +# Route to appropriate script +case "$subcommand" in + check-git-cred) + "$SCRIPT_DIR/check-git-cred" "$@" + ;; + create-git-account) + "$SCRIPT_DIR/create-git-account" "$@" + ;; + generate-access-token) + "$SCRIPT_DIR/generate-access-token" "$@" + ;; + link-keycloak) + "$SCRIPT_DIR/link-keycloak" "$@" + ;; + repo-add-collaborators) + "$SCRIPT_DIR/repo-add-collaborators" "$@" + ;; + repo-config) + "$SCRIPT_DIR/repo-config" "$@" + ;; + external-login-ctrl) + "$SCRIPT_DIR/external-login-ctrl" "$@" + ;; + reset-password) + "$SCRIPT_DIR/reset-password" "$@" + ;; + *) + echo "Unknown command: $subcommand" + echo "Run '$0' for usage information" + exit 1 + ;; +esac diff --git a/git-hangman-lab/scripts/git/gitea b/git-hangman-lab/scripts/git/gitea new file mode 100755 index 0000000..dd583a7 --- /dev/null +++ b/git-hangman-lab/scripts/git/gitea @@ -0,0 +1,9 @@ +#!/bin/bash +set -euo pipefail + +REMOTE_HOST="vps.git" +REMOTE_USER="root" +CONTAINER_NAME="git-kc-gitea" + +ssh "$REMOTE_USER@$REMOTE_HOST" \ + "docker exec -i $CONTAINER_NAME gitea --config /etc/gitea/app.ini $(printf '%q ' "$@")" <&0 diff --git a/git-hangman-lab/scripts/git/link-keycloak b/git-hangman-lab/scripts/git/link-keycloak new file mode 100755 index 0000000..f635e42 --- /dev/null +++ b/git-hangman-lab/scripts/git/link-keycloak @@ -0,0 +1,140 @@ +#!/bin/bash +set -euo pipefail + +# Get the directory where this script is located +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" + +USERNAME=$(pass_mgr get-username --key git) +KC_PASS=$(pass_mgr get-secret --key keycloak) +GITEA_PASS=$(pass_mgr get-secret --key git) + +if [[ -z "$USERNAME" || -z "$KC_PASS" || -z "$GITEA_PASS" ]]; then + echo "[ERROR] Missing required credentials in pass_mgr" >&2 + exit 1 +fi + +sql_escape() { + printf '%s' "$1" | sed "s/'/''/g" +} + +WORKDIR="/tmp" +COOKIES_FILE="$WORKDIR/${USERNAME}_oidc_cookies.txt" +KC_LOGIN_HTML="$WORKDIR/${USERNAME}_kc_login.html" +KC_POST_LOGIN_HTML="$WORKDIR/${USERNAME}_kc_post_login.html" +KC_POST_LOGIN_LOG="$WORKDIR/${USERNAME}_kc_post_login.log" +GITEA_CALLBACK_HTML="$WORKDIR/${USERNAME}_gitea_after_callback.html" +GITEA_LINK_HTML="$WORKDIR/${USERNAME}_gitea_link_account.html" +GITEA_LINK_RESP_HTML="$WORKDIR/${USERNAME}_gitea_link_signin.html" +GITEA_LINK_RESP_LOG="$WORKDIR/${USERNAME}_gitea_link_signin.log" + +OIDC_URL="https://git.hangman-lab.top/user/oauth2/hangman-lab" +ESCAPED_USERNAME=$(sql_escape "$USERNAME") + +cleanup() { + "$SCRIPT_DIR/external-login-ctrl" --disable >/dev/null 2>&1 || true + if [[ -n "${ORIG_LOGIN_TYPE:-}" && -n "${ORIG_LOGIN_SOURCE:-}" ]]; then + ssh root@vps.git " + set -euo pipefail + . /root/git-kc/.env + docker exec -i git-kc-mysql mysql -uroot -p\"\$MYSQL_ROOT_PASSWORD\" giteadb -e \"UPDATE user SET login_type=${ORIG_LOGIN_TYPE}, login_source=${ORIG_LOGIN_SOURCE}, login_name=${ORIG_LOGIN_NAME_SQL:-NULL} WHERE name='${ESCAPED_USERNAME}';\" + " >/dev/null 2>&1 || true + fi + rm -f "$COOKIES_FILE" "$KC_LOGIN_HTML" "$KC_POST_LOGIN_HTML" "$KC_POST_LOGIN_LOG" \ + "$GITEA_CALLBACK_HTML" "$GITEA_LINK_HTML" "$GITEA_LINK_RESP_HTML" "$GITEA_LINK_RESP_LOG" +} +trap cleanup EXIT + +rm -f "$COOKIES_FILE" "$KC_LOGIN_HTML" "$KC_POST_LOGIN_HTML" "$KC_POST_LOGIN_LOG" \ + "$GITEA_CALLBACK_HTML" "$GITEA_LINK_HTML" "$GITEA_LINK_RESP_HTML" "$GITEA_LINK_RESP_LOG" + +# Capture original login fields so we can restore them exactly. +ORIG_STATE=$(ssh root@vps.git " + set -euo pipefail + . /root/git-kc/.env + docker exec -i git-kc-mysql mysql -N -B -uroot -p\"\$MYSQL_ROOT_PASSWORD\" giteadb -e \"SELECT login_type, login_source, COALESCE(login_name, '__NULL__') FROM user WHERE name='${ESCAPED_USERNAME}' LIMIT 1;\" +") + +if [[ -z "$ORIG_STATE" ]]; then + echo "[ERROR] User not found in Gitea DB: $USERNAME" >&2 + exit 1 +fi + +IFS=$'\t' read -r ORIG_LOGIN_TYPE ORIG_LOGIN_SOURCE ORIG_LOGIN_NAME <<< "$ORIG_STATE" +if [[ "$ORIG_LOGIN_NAME" == "__NULL__" ]]; then + ORIG_LOGIN_NAME_SQL="NULL" +else + ORIG_LOGIN_NAME_SQL="'$(sql_escape "$ORIG_LOGIN_NAME")'" +fi + +"$SCRIPT_DIR/external-login-ctrl" --enable + +echo "[INFO] 通过 OIDC 入口触发跳转,获取 Keycloak 登录页..." +curl -s -L -c "$COOKIES_FILE" "$OIDC_URL" -o "$KC_LOGIN_HTML" + +KC_LOGIN_URL=$(perl -ne ' + if(/
/){ + if(/action="([^"]+)"/){ + my $u=$1; $u=~s/&/&/g; print $u; exit + } + } +' "$KC_LOGIN_HTML") + +if [[ -z "${KC_LOGIN_URL:-}" ]]; then + echo "[ERROR] 未找到 kc-form-login 表单的 action URL" >&2 + exit 1 +fi + +echo "[INFO] 登录 Keycloak..." +curl -v "$KC_LOGIN_URL" \ + -b "$COOKIES_FILE" -c "$COOKIES_FILE" \ + --data-urlencode "username=$USERNAME" \ + --data-urlencode "password=$KC_PASS" \ + -d "credentialId=" \ + -o "$KC_POST_LOGIN_HTML" \ + 2>"$KC_POST_LOGIN_LOG" || true + +GITEA_CALLBACK_URL=$(grep -i "location: https://git.hangman-lab.top" "$KC_POST_LOGIN_LOG" | sed -E 's/.*location: (https:[^\r]+).*/\1/i' | tail -n1) + +if [[ -z "${GITEA_CALLBACK_URL:-}" ]]; then + echo "[ERROR] 登录后未发现返回 Gitea 的 callback URL" >&2 + exit 1 +fi + +echo "[INFO] 调用 Gitea 回调..." +curl -s -L -b "$COOKIES_FILE" -c "$COOKIES_FILE" "$GITEA_CALLBACK_URL" -o "$GITEA_CALLBACK_HTML" + +echo "[INFO] 访问 Gitea Link Account 页面..." +curl -s -b "$COOKIES_FILE" "https://git.hangman-lab.top/user/link_account" -o "$GITEA_LINK_HTML" + +CSRF_TOKEN=$(perl -ne 'if(/name="_csrf" value="([^"]+)"/){print $1; exit}' "$GITEA_LINK_HTML" || true) + +if [[ -z "${CSRF_TOKEN:-}" ]]; then + echo "[ERROR] 未能从 Link Account 页面解析出 _csrf" >&2 + exit 1 +fi + +echo "[INFO] 临时将 login_type 改为本地登录..." +ssh root@vps.git " + set -euo pipefail + . /root/git-kc/.env + docker exec -i git-kc-mysql mysql -uroot -p\"\$MYSQL_ROOT_PASSWORD\" giteadb -e \"UPDATE user SET login_type=0, login_source=0, login_name=NULL WHERE name='${ESCAPED_USERNAME}';\" +" + +echo "[INFO] 提交 link_account_signin..." +curl -v "https://git.hangman-lab.top/user/link_account_signin" \ + -b "$COOKIES_FILE" -c "$COOKIES_FILE" \ + --data-urlencode "_csrf=$CSRF_TOKEN" \ + --data-urlencode "user_name=$USERNAME" \ + --data-urlencode "password=$GITEA_PASS" \ + -o "$GITEA_LINK_RESP_HTML" \ + 2>"$GITEA_LINK_RESP_LOG" || true + +echo "[INFO] 恢复原始登录方式..." +ssh root@vps.git " + set -euo pipefail + . /root/git-kc/.env + docker exec -i git-kc-mysql mysql -uroot -p\"\$MYSQL_ROOT_PASSWORD\" giteadb -e \"UPDATE user SET login_type=${ORIG_LOGIN_TYPE}, login_source=${ORIG_LOGIN_SOURCE}, login_name=${ORIG_LOGIN_NAME_SQL} WHERE name='${ESCAPED_USERNAME}';\" +" +unset ORIG_LOGIN_TYPE ORIG_LOGIN_SOURCE ORIG_LOGIN_NAME ORIG_LOGIN_NAME_SQL + +echo "[DONE] Keycloak 账号关联完成" diff --git a/git-hangman-lab/scripts/git/repo-add-collaborators b/git-hangman-lab/scripts/git/repo-add-collaborators new file mode 100755 index 0000000..00e5081 --- /dev/null +++ b/git-hangman-lab/scripts/git/repo-add-collaborators @@ -0,0 +1,65 @@ +#!/bin/bash + +# Get the directory where this script is located +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" + +roster=false + +# Parse arguments +while [[ $# -gt 0 ]]; do + case $1 in + --repo) + repo="$2" + shift 2 + ;; + --user) + user="$2" + shift 2 + ;; + --roster) + roster=true + shift + ;; + *) + echo "Unknown option: $1" + exit 1 + ;; + esac +done + +# Check if user is provided +if [[ -z "$user" ]]; then + echo "Usage: $0 --user [--repo ] [--roster]" + exit 1 +fi + +# Handle roster mode +if [[ "$roster" == "true" ]]; then + owner="hzhang" + repo=".roster" + + # Check if git-adm key exists + if ! pass_mgr list | grep -q "git-adm"; then + echo "you dont have permission to run this script" + exit 1 + fi + + token=$(pass_mgr get-secret --key git-adm) +else + # Check if repo and git-access-token are provided + if [[ -z "$repo" ]]; then + echo "Usage: $0 --user --repo " + exit 1 + fi + + if ! pass_mgr list | grep -q "git-access-token"; then + echo "generate your access token first" + exit 1 + fi + + owner=$(pass_mgr get-username --key git) + token=$(pass_mgr get-secret --key git-access-token) +fi + +# Execute +curl -X PUT -H "Authorization: token $token" -H "Content-Type: application/json" -d '{"permission":"write"}' "https://git.hangman-lab.top/api/v1/repos/$owner/$repo/collaborators/$user" diff --git a/git-hangman-lab/scripts/git/repo-config b/git-hangman-lab/scripts/git/repo-config new file mode 100755 index 0000000..d6f8eeb --- /dev/null +++ b/git-hangman-lab/scripts/git/repo-config @@ -0,0 +1,132 @@ +#!/usr/bin/env bash +set -euo pipefail + +usage() { + echo "Usage: $0 --repo-path --email [--recursive]" + echo " --repo-path: Path to the git repository" + echo " --email: Email address to configure" + echo " --recursive: Also configure all submodules (recursive)" + exit 2 +} + +REPO_PATH="" +EMAIL="" +RECURSIVE=false + +while [[ $# -gt 0 ]]; do + case "$1" in + --repo-path) + REPO_PATH="${2:-}" + shift 2 + ;; + --email) + EMAIL="${2:-}" + shift 2 + ;; + --recursive) + RECURSIVE=true + shift + ;; + *) + usage + ;; + esac +done + +if [[ -z "$REPO_PATH" || -z "$EMAIL" ]]; then + usage +fi + +# Check if it's a git repo (either .git is a directory or a file with gitdir: reference) +is_git_repo() { + local repo="$1" + if [[ -d "$repo/.git" ]]; then + return 0 + elif [[ -f "$repo/.git" ]]; then + local gitdir + gitdir=$(grep -m1 "gitdir:" "$repo/.git" | cut -d' ' -f2 | tr -d ' ') + if [[ -n "$gitdir" ]]; then + return 0 + fi + fi + return 1 +} + +if ! is_git_repo "$REPO_PATH"; then + echo "Not a git repo: $REPO_PATH" + exit 1 +fi + +USER="$(pass_mgr get-username --key git)" +PASS="$(pass_mgr get-secret --key git)" + +if [[ -z "$USER" || -z "$PASS" ]]; then + echo "Missing credentials from pass_mgr (key: git)" + exit 2 +fi + +# URL-encode username for credential URL +ENC_USER="$(U="$USER" python3 - <<'PY' +import os, urllib.parse +print(urllib.parse.quote(os.environ['U'], safe='')) +PY +)" + +# Function to configure a single repo +configure_repo() { + local repo="$1" + local relative="${2:-}" + + # Get relative path name for display + local name="${relative:-$repo}" + + echo "Configuring: $name" + + # Set local user.name / user.email + ( cd "$repo" && git config user.name "$USER" ) + ( cd "$repo" && git config user.email "$EMAIL" ) + + # Resolve the real git dir (works for normal repos and submodules) + local git_dir + git_dir="$(cd "$repo" && git rev-parse --absolute-git-dir)" + local cred_file="${git_dir}/credentials" + + ( cd "$repo" && git config credential.helper "store --file ${cred_file}" ) + ( cd "$repo" && GIT_ASKPASS=true git credential-store --file "${cred_file}" store </dev/null | awk '{print $2}' || true) + + if [[ -z "$submodules" ]]; then + echo "No submodules found" + else + for sm in $submodules; do + sm_path="$REPO_PATH/$sm" + if is_git_repo "$sm_path"; then + configure_repo "$sm_path" "$sm" + fi + done + fi +fi + +echo "" +echo "OK" diff --git a/git-hangman-lab/scripts/git/reset-password b/git-hangman-lab/scripts/git/reset-password new file mode 100755 index 0000000..355cb8d --- /dev/null +++ b/git-hangman-lab/scripts/git/reset-password @@ -0,0 +1,19 @@ +#!/bin/bash + +# Get the directory where this script is located +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" + +# Get username from pass_mgr +username=$(pass_mgr get-username --key git) +if [[ -z "$username" ]]; then + echo "Error: No username found in pass_mgr for key 'git'" + exit 1 +fi + +# Generate new password +password=$(pass_mgr generate --key git --username "$username") + +# Update password via gitea admin +"$SCRIPT_DIR/gitea" admin user change-password --username "$username" --password "$password" --must-change-password=false + +echo "Password updated for user: $username" diff --git a/keycloak-hangman-lab/SKILL.md b/keycloak-hangman-lab/SKILL.md new file mode 100644 index 0000000..372b564 --- /dev/null +++ b/keycloak-hangman-lab/SKILL.md @@ -0,0 +1,42 @@ +--- +name: keycloak-hangman-lab +description: Keycloak operations for hangman-lab.top - manage accounts, email verification, and passwords. +--- + +> ⚠️ **Note**: All scripts must be executed via the `pcexec` tool. + +## Keycloak Operations + +### Create Keycloak Account + +Create a new Keycloak account. + +> ⚠️ **Warning**: Do not execute this command unless explicitly requested. If you don't have a Keycloak account, contact **agent-resource-director** or **hangman** to guide you through the process. + +```bash +{baseDir}/scripts/keycloak/kc-ctrl create-keycloak-account --username --email +``` + +### Verify Email + +Verify user email in Keycloak. + +```bash +{baseDir}/scripts/keycloak/kc-ctrl verify-email +``` + +### Set User Name + +Set user firstName and lastName in Keycloak. + +```bash +{baseDir}/scripts/keycloak/kc-ctrl set-name +``` + +### Reset Password + +Reset password for the current user (reads username from pass_mgr). + +```bash +{baseDir}/scripts/keycloak/kc-ctrl reset-password +``` diff --git a/keycloak-hangman-lab/scripts/keycloak/create-keycloak-account b/keycloak-hangman-lab/scripts/keycloak/create-keycloak-account new file mode 100755 index 0000000..b54c53d --- /dev/null +++ b/keycloak-hangman-lab/scripts/keycloak/create-keycloak-account @@ -0,0 +1,49 @@ +#!/bin/bash + +# Get the directory where this script is located +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" + +# Parse arguments +while [[ $# -gt 0 ]]; do + case $1 in + --username) + username="$2" + shift 2 + ;; + --email) + email="$2" + shift 2 + ;; + *) + echo "Unknown option: $1" + exit 1 + ;; + esac +done + +# Check if username and email are provided +if [[ -z "$username" || -z "$email" ]]; then + echo "Usage: $0 --username --email " + exit 1 +fi + +# Generate keycloak credentials (do not print secret) +pass_mgr generate --username "$username" --key keycloak >/dev/null + +# Get the generated username and password +user=$(pass_mgr get-username --key keycloak) +pass=$(pass_mgr get-secret --key keycloak) + +realm="Hangman-Lab" + +# Create keycloak user +"$SCRIPT_DIR/kcadm" create users -r "$realm" -s "username=$user" -s "enabled=true" -s "email=$email" || true + +# Set password for the user +"$SCRIPT_DIR/kcadm" set-password -r "$realm" --username "$user" --new-password "$pass" + +# Verify email and set profile fields to avoid VERIFY_PROFILE during first OIDC login +"$SCRIPT_DIR/verify-email" --username "$user" +"$SCRIPT_DIR/set-name" --username "$user" >/dev/null 2>&1 || "$SCRIPT_DIR/set-name" + +echo "Keycloak account created for: $user (realm: $realm)" diff --git a/keycloak-hangman-lab/scripts/keycloak/kc-ctrl b/keycloak-hangman-lab/scripts/keycloak/kc-ctrl new file mode 100755 index 0000000..376a37d --- /dev/null +++ b/keycloak-hangman-lab/scripts/keycloak/kc-ctrl @@ -0,0 +1,41 @@ +#!/bin/bash + +# Get the directory where this script is located +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" + +# Check if subcommand is provided +if [[ $# -eq 0 ]]; then + echo "Usage: $0 [options]" + echo "" + echo "Commands:" + echo " create-keycloak-account Create a new Keycloak account" + echo " set-name Set user firstName and lastName" + echo " verify-email Verify user email" + echo " reset-password Reset user password" + exit 1 +fi + +# Get subcommand +subcommand="$1" +shift + +# Route to appropriate script +case "$subcommand" in + create-keycloak-account) + "$SCRIPT_DIR/create-keycloak-account" "$@" + ;; + set-name) + "$SCRIPT_DIR/set-name" "$@" + ;; + verify-email) + "$SCRIPT_DIR/verify-email" "$@" + ;; + reset-password) + "$SCRIPT_DIR/reset-password" "$@" + ;; + *) + echo "Unknown command: $subcommand" + echo "Run '$0' for usage information" + exit 1 + ;; +esac diff --git a/keycloak-hangman-lab/scripts/keycloak/kcadm b/keycloak-hangman-lab/scripts/keycloak/kcadm new file mode 100755 index 0000000..66f5d94 --- /dev/null +++ b/keycloak-hangman-lab/scripts/keycloak/kcadm @@ -0,0 +1,47 @@ +#!/bin/bash +set -euo pipefail +# pcguard || exit 1 + +REMOTE_HOST="vps.git" +REMOTE_USER="root" +CONTAINER_NAME="git-kc-keycloak" +HOST_CONFIG="/root/.keycloak/kcadm.config" +CONTAINER_CONFIG="/tmp/kcadm.config" +ENV_FILE="/root/git-kc/.env" + +if [[ $# -eq 0 ]]; then + ssh "$REMOTE_USER@$REMOTE_HOST" \ + "docker exec -i $CONTAINER_NAME /opt/keycloak/bin/kcadm.sh --help" <&0 + exit $? +fi + +SUBCOMMAND="$1" +shift + +ssh "$REMOTE_USER@$REMOTE_HOST" " + set -euo pipefail + mkdir -p /root/.keycloak + if [ -f $HOST_CONFIG ]; then + docker cp $HOST_CONFIG $CONTAINER_NAME:$CONTAINER_CONFIG >/dev/null 2>&1 || true + docker exec --user 0:0 $CONTAINER_NAME /bin/chmod 666 $CONTAINER_CONFIG >/dev/null 2>&1 || true + fi + + if [ $(printf '%q' "$SUBCOMMAND") != config ] && [ -f $ENV_FILE ]; then + set -a + . $ENV_FILE + set +a + docker exec -i $CONTAINER_NAME /opt/keycloak/bin/kcadm.sh config credentials \ + --config $CONTAINER_CONFIG \ + --server http://127.0.0.1:8080 \ + --realm master \ + --user \"\$KC_BOOTSTRAP_ADMIN_USERNAME\" \ + --password \"\$KC_BOOTSTRAP_ADMIN_PASSWORD\" >/dev/null + fi + + set +e + docker exec -i $CONTAINER_NAME /opt/keycloak/bin/kcadm.sh $(printf '%q ' "$SUBCOMMAND") --config $CONTAINER_CONFIG $(printf '%q ' "$@") + status=\$? + set -e + docker cp $CONTAINER_NAME:$CONTAINER_CONFIG $HOST_CONFIG >/dev/null 2>&1 || true + exit \$status +" <&0 diff --git a/keycloak-hangman-lab/scripts/keycloak/reset-password b/keycloak-hangman-lab/scripts/keycloak/reset-password new file mode 100755 index 0000000..2360eb0 --- /dev/null +++ b/keycloak-hangman-lab/scripts/keycloak/reset-password @@ -0,0 +1,21 @@ +#!/bin/bash + +# Get the directory where this script is located +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" + +# Get username from pass_mgr +username=$(pass_mgr get-username --key keycloak) +if [[ -z "$username" ]]; then + echo "Error: No username found in pass_mgr for key 'keycloak'" + exit 1 +fi + +realm="Hangman-Lab" + +# Generate new password +password=$(pass_mgr generate --key keycloak --username "$username") + +# Update password via kcadm +"$SCRIPT_DIR/kcadm" set-password -r "$realm" --username "$username" --new-password "$password" + +echo "Password updated for user: $username (realm: $realm)" diff --git a/keycloak-hangman-lab/scripts/keycloak/set-name b/keycloak-hangman-lab/scripts/keycloak/set-name new file mode 100755 index 0000000..94386cb --- /dev/null +++ b/keycloak-hangman-lab/scripts/keycloak/set-name @@ -0,0 +1,51 @@ +#!/bin/bash + +# Get the directory where this script is located +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" + +# Optional explicit username +username="" +while [[ $# -gt 0 ]]; do + case $1 in + --username) + username="$2" + shift 2 + ;; + *) + echo "Unknown option: $1" + echo "Usage: $0 [--username ]" + exit 1 + ;; + esac +done + +# Default to pass_mgr if not provided +if [[ -z "$username" ]]; then + username=$(pass_mgr get-username --key keycloak) +fi + +if [[ -z "$username" ]]; then + echo "Error: No keycloak username found in pass_mgr" + exit 1 +fi + +realm="Hangman-Lab" + +# Check if user exists +result=$("$SCRIPT_DIR/kcadm" get users -r "$realm" -q "username=$username") +user_count=$(echo "$result" | jq 'length') + +if [[ "$user_count" -eq 0 ]]; then + echo "Error: User $username not found in Keycloak" + exit 1 +fi + +# Get user ID +userid=$(echo "$result" | jq -r '.[0].id') + +# Set firstName and lastName +"$SCRIPT_DIR/kcadm" update users/"$userid" -r "$realm" \ + --set "firstName=$username" \ + --set "lastName=$username" + +echo "Name set for user: $username (firstName=$username, lastName=$username)" diff --git a/keycloak-hangman-lab/scripts/keycloak/verify-email b/keycloak-hangman-lab/scripts/keycloak/verify-email new file mode 100755 index 0000000..e329348 --- /dev/null +++ b/keycloak-hangman-lab/scripts/keycloak/verify-email @@ -0,0 +1,31 @@ +#!/bin/bash + +# Get the directory where this script is located +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" + +# Get username from pass_mgr +username=$(pass_mgr get-username --key keycloak) + +if [[ -z "$username" ]]; then + echo "Error: No keycloak username found in pass_mgr" + exit 1 +fi + +realm="Hangman-Lab" + +# Check if user exists +result=$("$SCRIPT_DIR/kcadm" get users -r "$realm" -q "username=$username") +user_count=$(echo "$result" | jq 'length') + +if [[ "$user_count" -eq 0 ]]; then + echo "Error: User $username not found in Keycloak" + exit 1 +fi + +# Get user ID +userid=$(echo "$result" | jq -r '.[0].id') + +# Set email verified +"$SCRIPT_DIR/kcadm" update users/"$userid" -r "$realm" -s "emailVerified=true" + +echo "Email verified for user: $username"