|
30
|
29
|
2
|
2
|
6ccdfdf78088ebb3a5e0bd64de10053b9c881f60
|
0
|
deploy
|
1
|
name: Smart Deploy via Rsync Daemon
"on": name: Smart Deploy via Rsync Daemon
"on": [push]
jobs:
deploy:
name: deploy
runs-on: ubuntu-latest
steps:
- name: Checkout code manually
run: |
# 调试输出
echo "GITEA_HOST: $GITEA_HOST"
echo "GITHUB_REPOSITORY: ${{ github.repository }}"
echo "GITHUB_SHA: ${{ github.sha }}"
if [ -z "$GITEA_HOST" ]; then
echo "❌ GITEA_HOST secret is missing!"
exit 1
fi
# 使用 github 上下文变量
REPO="${{ github.repository }}"
SHA="${{ github.sha }}"
if [ -z "$REPO" ] || [ -z "$SHA" ]; then
echo "❌ Missing GITHUB_REPOSITORY or GITHUB_SHA"
exit 1
fi
REPO_URL="https://${GITEA_TOKEN}@${GITEA_HOST}/${REPO}.git"
echo "Cloning from: ${REPO_URL//${GITEA_TOKEN}/***REDACTED***} (SHA: $SHA)"
git init
git remote add origin "$REPO_URL"
git fetch --depth=1 origin "$SHA"
git checkout "$SHA"
env:
GITEA_HOST: ${{ secrets.GITEAS_HOST }}
GITEA_TOKEN: ${{ secrets.GITEAS_TOKEN }}
- name: Prepare rsync password file
run: |
if [ -z "$RSYNC_PASSWORD" ]; then
echo "❌ TEST_RSYNC_SECRETS is empty or not set!"
ls -la /tmp/
exit 1
fi
echo "$RSYNC_PASSWORD" > /tmp/rsync.pass
chmod 600 /tmp/rsync.pass
echo "✅ /tmp/rsync.pass created (size: $(wc -c < /tmp/rsync.pass) bytes)"
env:
RSYNC_PASSWORD: ${{ secrets.TEST_RSYNC_SECRETS }}
- name: Get commit message and decide sync mode
run: "COMMIT_MSG=$(git log -1 --pretty=%B | head -n1)\necho \"Commit message: $COMMIT_MSG\"\n\nRSYNC_TARGET=\"rsync://${RSYNC_USER}@${RSYNC_HOST}/${RSYNC_MODULE}/\"\n\nif [[ \"$COMMIT_MSG\" == *\"全量同步\"* ]] || [[ \"$COMMIT_MSG\" == *\"full sync\"* ]]; then\n echo \"\U0001F50D Full sync requested...\"\n /usr/bin/rsync -avz --delete \\\n --exclude='.git' \\\n --exclude='.gitea' \\\n --exclude='node_modules/' \\\n --password-file=/tmp/rsync.pass \\\n ./ \\\n \"$RSYNC_TARGET\"\nelse\n echo \"\U0001F504 Incremental sync...\"\n\n if git rev-parse HEAD~1 >/dev/null 2>&1; then\n git diff --name-only HEAD~1 HEAD > /tmp/changed.txt\n else\n find . -type f -not -path './.git/*' -not -path './.gitea/*' | sed 's|^\\./||' > /tmp/changed.txt\n fi\n\n if [ -s /tmp/changed.txt ]; then\n echo \"Files to sync:\"\n cat /tmp/changed.txt\n \n /usr/bin/rsync -avz --relative \\\n --files-from=/tmp/changed.txt \\\n --password-file=/tmp/rsync.pass \\\n ./ \\\n \"$RSYNC_TARGET\"\n else\n echo \"✅ No files changed.\"\n fi\nfi\n"
env:
RSYNC_HOST: ${{ secrets.TEST_RSYNC_HOST }}
RSYNC_USER: ${{ secrets.TEST_RSYNC_USER }}
RSYNC_MODULE: ftp
- if: always()
name: Clean up
run: rm -f /tmp/rsync.pass
...
|
deploy
|
null
|
["ubuntu-latest"]
|
22
|
2
|
1770886646
|
1770886649
|
1770886646
|
1770886649
|
NULL
|
NULL
|
|
0
|
Edit
Delete
|
|
15
|
14
|
2
|
2
|
b895cbd8b188037f0f5dbfb0fcfef056dfc36ebc
|
0
|
deploy
|
1
|
name: Smart Deploy via Rsync Daemon
"on": name: Smart Deploy via Rsync Daemon
"on": [push]
jobs:
deploy:
name: deploy
runs-on: ubuntu-latest
steps:
- name: Checkout code manually
run: |
# 调试输出
echo "GITEA_HOST: $GITEA_HOST"
echo "GITHUB_REPOSITORY: ${{ github.repository }}"
echo "GITHUB_SHA: ${{ github.sha }}"
if [ -z "$GITEA_HOST" ]; then
echo "❌ GITEA_HOST secret is missing!"
exit 1
fi
# 使用 github 上下文变量
REPO="${{ github.repository }}"
SHA="${{ github.sha }}"
if [ -z "$REPO" ] || [ -z "$SHA" ]; then
echo "❌ Missing GITHUB_REPOSITORY or GITHUB_SHA"
exit 1
fi
REPO_URL="https://${GITEA_TOKEN}@${GITEA_HOST}/${REPO}.git"
echo "Cloning from: ${REPO_URL//${GITEA_TOKEN}/***REDACTED***} (SHA: $SHA)"
git init
git remote add origin "$REPO_URL"
git fetch --depth=1 origin "$SHA"
git checkout "$SHA"
env:
GITEA_HOST: ${{ secrets.GITEAS_HOST }}
GITEA_TOKEN: ${{ secrets.GITEAS_TOKEN }}
- name: Prepare rsync password file
run: |
echo "$RSYNC_PASSWORD" > /tmp/rsync.pass
chmod 600 /tmp/rsync.pass
env:
RSYNC_PASSWORD: ${{ secrets.TEST_RSYNC_SECRETS }}
- name: Get commit message and decide sync mode
run: "COMMIT_MSG=$(git log -1 --pretty=%B | head -n1)\necho \"Commit message: $COMMIT_MSG\"\n\nRSYNC_TARGET=\"rsync://${RSYNC_USER}@${RSYNC_HOST}/${RSYNC_MODULE}/\"\n\nif [[ \"$COMMIT_MSG\" == *\"全量同步\"* ]] || [[ \"$COMMIT_MSG\" == *\"full sync\"* ]]; then\n echo \"\U0001F50D Full sync requested...\"\n rsync -avz --delete \\\n --exclude='.git' \\\n --exclude='.gitea' \\\n --exclude='node_modules/' \\\n --password-file=/tmp/rsync.pass \\\n ./ \\\n \"$RSYNC_TARGET\"\nelse\n echo \"\U0001F504 Incremental sync...\"\n\n if git rev-parse HEAD~1 >/dev/null 2>&1; then\n git diff --name-only HEAD~1 HEAD > /tmp/changed.txt\n else\n find . -type f -not -path './.git/*' -not -path './.gitea/*' | sed 's|^\\./||' > /tmp/changed.txt\n fi\n\n if [ -s /tmp/changed.txt ]; then\n echo \"Files to sync:\"\n cat /tmp/changed.txt\n \n rsync -avz --relative \\\n --files-from=/tmp/changed.txt \\\n --password-file=/tmp/rsync.pass \\\n ./ \\\n \"$RSYNC_TARGET\"\n else\n echo \"✅ No files changed.\"\n fi\nfi\n"
env:
RSYNC_HOST: ${{ secrets.TEST_RSYNC_HOST }}
RSYNC_USER: ${{ secrets.TEST_RSYNC_USER }}
RSYNC_MODULE: ftp
- if: always()
name: Clean up
run: rm -f /tmp/rsync.pass
...
|
deploy
|
null
|
["ubuntu-latest"]
|
14
|
2
|
1770802531
|
1770802550
|
1770802529
|
1770802550
|
NULL
|
NULL
|
|
0
|
Edit
Delete
|
|
22
|
21
|
2
|
2
|
ac9571bc40f603c832247b982b1f4181e6a3e297
|
0
|
deploy
|
1
|
name: Smart Deploy via Rsync Daemon
"on": name: Smart Deploy via Rsync Daemon
"on": [push]
jobs:
deploy:
name: deploy
runs-on: ubuntu-latest
steps:
- name: Checkout code manually
run: |
# 调试输出
echo "GITEA_HOST: $GITEA_HOST"
echo "GITHUB_REPOSITORY: ${{ github.repository }}"
echo "GITHUB_SHA: ${{ github.sha }}"
if [ -z "$GITEA_HOST" ]; then
echo "❌ GITEA_HOST secret is missing!"
exit 1
fi
# 使用 github 上下文变量
REPO="${{ github.repository }}"
SHA="${{ github.sha }}"
if [ -z "$REPO" ] || [ -z "$SHA" ]; then
echo "❌ Missing GITHUB_REPOSITORY or GITHUB_SHA"
exit 1
fi
REPO_URL="https://${GITEA_TOKEN}@${GITEA_HOST}/${REPO}.git"
echo "Cloning from: ${REPO_URL//${GITEA_TOKEN}/***REDACTED***} (SHA: $SHA)"
git init
git remote add origin "$REPO_URL"
git fetch --depth=1 origin "$SHA"
git checkout "$SHA"
env:
GITEA_HOST: ${{ secrets.GITEAS_HOST }}
GITEA_TOKEN: ${{ secrets.GITEAS_TOKEN }}
- name: Prepare rsync password file
run: |
echo "$RSYNC_PASSWORD" > /tmp/rsync.pass
chmod 600 /tmp/rsync.pass
env:
RSYNC_PASSWORD: ${{ secrets.TEST_RSYNC_SECRETS }}
- name: Get commit message and decide sync mode
run: "COMMIT_MSG=$(git log -1 --pretty=%B | head -n1)\necho \"Commit message: $COMMIT_MSG\"\n\nRSYNC_TARGET=\"rsync://${RSYNC_USER}@${RSYNC_HOST}/${RSYNC_MODULE}/\"\n\nif [[ \"$COMMIT_MSG\" == *\"全量同步\"* ]] || [[ \"$COMMIT_MSG\" == *\"full sync\"* ]]; then\n echo \"\U0001F50D Full sync requested...\"\n rsync -avz --delete \\\n --exclude='.git' \\\n --exclude='.gitea' \\\n --exclude='node_modules/' \\\n --password-file=/tmp/rsync.pass \\\n ./ \\\n \"$RSYNC_TARGET\"\nelse\n echo \"\U0001F504 Incremental sync...\"\n\n if git rev-parse HEAD~1 >/dev/null 2>&1; then\n git diff --name-only HEAD~1 HEAD > /tmp/changed.txt\n else\n find . -type f -not -path './.git/*' -not -path './.gitea/*' | sed 's|^\\./||' > /tmp/changed.txt\n fi\n\n if [ -s /tmp/changed.txt ]; then\n echo \"Files to sync:\"\n cat /tmp/changed.txt\n \n rsync -avz --relative \\\n --files-from=/tmp/changed.txt \\\n --password-file=/tmp/rsync.pass \\\n ./ \\\n \"$RSYNC_TARGET\"\n else\n echo \"✅ No files changed.\"\n fi\nfi\n"
env:
RSYNC_HOST: ${{ secrets.TEST_RSYNC_HOST }}
RSYNC_USER: ${{ secrets.TEST_RSYNC_USER }}
RSYNC_MODULE: ftp
- if: always()
name: Clean up
run: rm -f /tmp/rsync.pass
...
|
deploy
|
null
|
["ubuntu-latest"]
|
15
|
2
|
1770878387
|
1770878407
|
1770878387
|
1770878407
|
NULL
|
NULL
|
|
0
|
Edit
Delete
|
|
23
|
22
|
2
|
2
|
9bbc92b4f9f21e5efef46a53f1d89fd7e752d327
|
0
|
deploy
|
1
|
name: Smart Deploy via Rsync Daemon
"on": name: Smart Deploy via Rsync Daemon
"on": [push]
jobs:
deploy:
name: deploy
runs-on: ubuntu-latest
steps:
- name: Checkout code manually
run: |
# 调试输出
echo "GITEA_HOST: $GITEA_HOST"
echo "GITHUB_REPOSITORY: ${{ github.repository }}"
echo "GITHUB_SHA: ${{ github.sha }}"
if [ -z "$GITEA_HOST" ]; then
echo "❌ GITEA_HOST secret is missing!"
exit 1
fi
# 使用 github 上下文变量
REPO="${{ github.repository }}"
SHA="${{ github.sha }}"
if [ -z "$REPO" ] || [ -z "$SHA" ]; then
echo "❌ Missing GITHUB_REPOSITORY or GITHUB_SHA"
exit 1
fi
REPO_URL="https://${GITEA_TOKEN}@${GITEA_HOST}/${REPO}.git"
echo "Cloning from: ${REPO_URL//${GITEA_TOKEN}/***REDACTED***} (SHA: $SHA)"
git init
git remote add origin "$REPO_URL"
git fetch --depth=1 origin "$SHA"
git checkout "$SHA"
env:
GITEA_HOST: ${{ secrets.GITEAS_HOST }}
GITEA_TOKEN: ${{ secrets.GITEAS_TOKEN }}
- name: Prepare rsync password file
run: |
echo "$RSYNC_PASSWORD" > /tmp/rsync.pass
chmod 600 /tmp/rsync.pass
env:
RSYNC_PASSWORD: ${{ secrets.TEST_RSYNC_SECRETS }}
- name: Get commit message and decide sync mode
run: "COMMIT_MSG=$(git log -1 --pretty=%B | head -n1)\necho \"Commit message: $COMMIT_MSG\"\n\nRSYNC_TARGET=\"rsync://${RSYNC_USER}@${RSYNC_HOST}/${RSYNC_MODULE}/\"\n\nif [[ \"$COMMIT_MSG\" == *\"全量同步\"* ]] || [[ \"$COMMIT_MSG\" == *\"full sync\"* ]]; then\n echo \"\U0001F50D Full sync requested...\"\n rsync -avz --delete \\\n --exclude='.git' \\\n --exclude='.gitea' \\\n --exclude='node_modules/' \\\n --password-file=/tmp/rsync.pass \\\n ./ \\\n \"$RSYNC_TARGET\"\nelse\n echo \"\U0001F504 Incremental sync...\"\n\n if git rev-parse HEAD~1 >/dev/null 2>&1; then\n git diff --name-only HEAD~1 HEAD > /tmp/changed.txt\n else\n find . -type f -not -path './.git/*' -not -path './.gitea/*' | sed 's|^\\./||' > /tmp/changed.txt\n fi\n\n if [ -s /tmp/changed.txt ]; then\n echo \"Files to sync:\"\n cat /tmp/changed.txt\n \n rsync -avz --relative \\\n --files-from=/tmp/changed.txt \\\n --password-file=/tmp/rsync.pass \\\n ./ \\\n \"$RSYNC_TARGET\"\n else\n echo \"✅ No files changed.\"\n fi\nfi\n"
env:
RSYNC_HOST: ${{ secrets.TEST_RSYNC_HOST }}
RSYNC_USER: ${{ secrets.TEST_RSYNC_USER }}
RSYNC_MODULE: ftp
- if: always()
name: Clean up
run: rm -f /tmp/rsync.pass
...
|
deploy
|
null
|
["ubuntu-latest"]
|
16
|
2
|
1770882286
|
1770882302
|
1770882162
|
1770882303
|
NULL
|
NULL
|
|
0
|
Edit
Delete
|
|
24
|
23
|
2
|
2
|
2ac24ff0484bc4a60c5bfed9495a83a4e337f27b
|
0
|
deploy
|
0
|
name: Smart Deploy via Rsync Daemon
"on": name: Smart Deploy via Rsync Daemon
"on": [push]
jobs:
deploy:
name: deploy
runs-on: ubuntu-latest
steps:
- name: Checkout code manually
run: |
# 调试输出
echo "GITEA_HOST: $GITEA_HOST"
echo "GITHUB_REPOSITORY: ${{ github.repository }}"
echo "GITHUB_SHA: ${{ github.sha }}"
if [ -z "$GITEA_HOST" ]; then
echo "❌ GITEA_HOST secret is missing!"
exit 1
fi
# 使用 github 上下文变量
REPO="${{ github.repository }}"
SHA="${{ github.sha }}"
if [ -z "$REPO" ] || [ -z "$SHA" ]; then
echo "❌ Missing GITHUB_REPOSITORY or GITHUB_SHA"
exit 1
fi
REPO_URL="https://${GITEA_TOKEN}@${GITEA_HOST}/${REPO}.git"
echo "Cloning from: ${REPO_URL//${GITEA_TOKEN}/***REDACTED***} (SHA: $SHA)"
git init
git remote add origin "$REPO_URL"
git fetch --depth=1 origin "$SHA"
git checkout "$SHA"
env:
GITEA_HOST: ${{ secrets.GITEAS_HOST }}
GITEA_TOKEN: ${{ secrets.GITEAS_TOKEN }}
- name: Prepare rsync password file
run: |
echo "$RSYNC_PASSWORD" > /tmp/rsync.pass
chmod 600 /tmp/rsync.pass
env:
RSYNC_PASSWORD: ${{ secrets.TEST_RSYNC_SECRETS }}
- name: Get commit message and decide sync mode
run: "COMMIT_MSG=$(git log -1 --pretty=%B | head -n1)\necho \"Commit message: $COMMIT_MSG\"\n\nRSYNC_TARGET=\"rsync://${RSYNC_USER}@${RSYNC_HOST}/${RSYNC_MODULE}/\"\n\nif [[ \"$COMMIT_MSG\" == *\"全量同步\"* ]] || [[ \"$COMMIT_MSG\" == *\"full sync\"* ]]; then\n echo \"\U0001F50D Full sync requested...\"\n /usr/bin/rsync -avz --delete \\\n --exclude='.git' \\\n --exclude='.gitea' \\\n --exclude='node_modules/' \\\n --password-file=/tmp/rsync.pass \\\n ./ \\\n \"$RSYNC_TARGET\"\nelse\n echo \"\U0001F504 Incremental sync...\"\n\n if git rev-parse HEAD~1 >/dev/null 2>&1; then\n git diff --name-only HEAD~1 HEAD > /tmp/changed.txt\n else\n find . -type f -not -path './.git/*' -not -path './.gitea/*' | sed 's|^\\./||' > /tmp/changed.txt\n fi\n\n if [ -s /tmp/changed.txt ]; then\n echo \"Files to sync:\"\n cat /tmp/changed.txt\n \n /usr/bin/rsync -avz --relative \\\n --files-from=/tmp/changed.txt \\\n --password-file=/tmp/rsync.pass \\\n ./ \\\n \"$RSYNC_TARGET\"\n else\n echo \"✅ No files changed.\"\n fi\nfi\n"
env:
RSYNC_HOST: ${{ secrets.TEST_RSYNC_HOST }}
RSYNC_USER: ${{ secrets.TEST_RSYNC_USER }}
RSYNC_MODULE: ftp
- if: always()
name: Clean up
run: rm -f /tmp/rsync.pass
...
|
deploy
|
null
|
["ubuntu-latest"]
|
0
|
3
|
0
|
1770884630
|
1770882563
|
1770884630
|
NULL
|
NULL
|
|
0
|
Edit
Delete
|
|
25
|
24
|
2
|
2
|
f98b8cb65887cd9ea97ec74d24dca6f43e8f15ff
|
0
|
deploy
|
1
|
name: Smart Deploy via Rsync Daemon
"on": name: Smart Deploy via Rsync Daemon
"on": [push]
jobs:
deploy:
name: deploy
runs-on: ubuntu-latest
steps:
- name: Checkout code manually
run: |
# 调试输出
echo "GITEA_HOST: $GITEA_HOST"
echo "GITHUB_REPOSITORY: ${{ github.repository }}"
echo "GITHUB_SHA: ${{ github.sha }}"
if [ -z "$GITEA_HOST" ]; then
echo "❌ GITEA_HOST secret is missing!"
exit 1
fi
# 使用 github 上下文变量
REPO="${{ github.repository }}"
SHA="${{ github.sha }}"
if [ -z "$REPO" ] || [ -z "$SHA" ]; then
echo "❌ Missing GITHUB_REPOSITORY or GITHUB_SHA"
exit 1
fi
REPO_URL="https://${GITEA_TOKEN}@${GITEA_HOST}/${REPO}.git"
echo "Cloning from: ${REPO_URL//${GITEA_TOKEN}/***REDACTED***} (SHA: $SHA)"
git init
git remote add origin "$REPO_URL"
git fetch --depth=1 origin "$SHA"
git checkout "$SHA"
env:
GITEA_HOST: ${{ secrets.GITEAS_HOST }}
GITEA_TOKEN: ${{ secrets.GITEAS_TOKEN }}
- name: Prepare rsync password file
run: |
echo "$RSYNC_PASSWORD" > /tmp/rsync.pass
chmod 600 /tmp/rsync.pass
env:
RSYNC_PASSWORD: ${{ secrets.TEST_RSYNC_SECRETS }}
- name: Get commit message and decide sync mode
run: "COMMIT_MSG=$(git log -1 --pretty=%B | head -n1)\necho \"Commit message: $COMMIT_MSG\"\n\nRSYNC_TARGET=\"rsync://${RSYNC_USER}@${RSYNC_HOST}/${RSYNC_MODULE}/\"\n\nif [[ \"$COMMIT_MSG\" == *\"全量同步\"* ]] || [[ \"$COMMIT_MSG\" == *\"full sync\"* ]]; then\n echo \"\U0001F50D Full sync requested...\"\n /usr/bin/rsync -avz --delete \\\n --exclude='.git' \\\n --exclude='.gitea' \\\n --exclude='node_modules/' \\\n --password-file=/tmp/rsync.pass \\\n ./ \\\n \"$RSYNC_TARGET\"\nelse\n echo \"\U0001F504 Incremental sync...\"\n\n if git rev-parse HEAD~1 >/dev/null 2>&1; then\n git diff --name-only HEAD~1 HEAD > /tmp/changed.txt\n else\n find . -type f -not -path './.git/*' -not -path './.gitea/*' | sed 's|^\\./||' > /tmp/changed.txt\n fi\n\n if [ -s /tmp/changed.txt ]; then\n echo \"Files to sync:\"\n cat /tmp/changed.txt\n \n /usr/bin/rsync -avz --relative \\\n --files-from=/tmp/changed.txt \\\n --password-file=/tmp/rsync.pass \\\n ./ \\\n \"$RSYNC_TARGET\"\n else\n echo \"✅ No files changed.\"\n fi\nfi\n"
env:
RSYNC_HOST: ${{ secrets.TEST_RSYNC_HOST }}
RSYNC_USER: ${{ secrets.TEST_RSYNC_USER }}
RSYNC_MODULE: ftp
- if: always()
name: Clean up
run: rm -f /tmp/rsync.pass
...
|
deploy
|
null
|
["ubuntu-latest"]
|
17
|
2
|
1770885475
|
1770885478
|
1770885473
|
1770885478
|
NULL
|
NULL
|
|
0
|
Edit
Delete
|
|
14
|
13
|
2
|
2
|
646aa1dc79636bb90dbfec0b6b9fb9aef11c7556
|
0
|
deploy
|
1
|
name: Smart Deploy via Rsync Daemon
"on": name: Smart Deploy via Rsync Daemon
"on": [push]
jobs:
deploy:
name: deploy
runs-on: ubuntu-latest
steps:
- name: Checkout code manually
run: |
# 构建完整 URL
if [ -z "$GITEA_HOST" ]; then
echo "❌ GITEA_HOST secret is missing!"
exit 1
fi
if [ -z "$GITHUB_REPOSITORY" ] || [ -z "$GITHUB_SHA" ]; then
echo "❌ Missing GITHUB_REPOSITORY or GITHUB_SHA"
exit 1
fi
REPO_URL="https://${GITEA_TOKEN}@${GITEA_HOST}/${GITHUB_REPOSITORY}.git"
echo "Cloning from: $REPO_URL (SHA: $GITHUB_SHA)"
git init
git remote add origin "$REPO_URL"
git fetch --depth=1 origin "$GITHUB_SHA"
git checkout "$GITHUB_SHA"
env:
# 从 Secrets 获取你的 Gitea 地址(避免依赖 env.GITEA_SERVER_URL)
GITEA_HOST: ${{ secrets.GITEAS_HOST }} # 例如: gitea.g-hi.com
GITHUB_REPOSITORY: ${{ env.GITHUB_REPOSITORY }}
GITHUB_SHA: ${{ env.GITHUB_SHA }}
GITEA_TOKEN: ${{ secrets.GITEAS_TOKEN }}
- name: Prepare rsync password file
run: |
echo "$RSYNC_PASSWORD" > /tmp/rsync.pass
chmod 600 /tmp/rsync.pass
env:
RSYNC_PASSWORD: ${{ secrets.TEST_RSYNC_SECRETS }}
- name: Get commit message and decide sync mode
run: "COMMIT_MSG=$(git log -1 --pretty=%B | head -n1)\necho \"Commit message: $COMMIT_MSG\"\n\nRSYNC_TARGET=\"rsync://${RSYNC_USER}@${RSYNC_HOST}/${RSYNC_MODULE}/\"\n\nif [[ \"$COMMIT_MSG\" == *\"全量同步\"* ]] || [[ \"$COMMIT_MSG\" == *\"full sync\"* ]]; then\n echo \"\U0001F50D Full sync requested...\"\n rsync -avz --delete \\\n --exclude='.git' \\\n --exclude='.gitea' \\\n --exclude='node_modules/' \\\n --password-file=/tmp/rsync.pass \\\n ./ \\\n \"$RSYNC_TARGET\"\nelse\n echo \"\U0001F504 Incremental sync...\"\n\n if git rev-parse HEAD~1 >/dev/null 2>&1; then\n git diff --name-only HEAD~1 HEAD > /tmp/changed.txt\n else\n find . -type f | sed 's|^\\./||' > /tmp/changed.txt\n fi\n\n if [ -s /tmp/changed.txt ]; then\n rsync -avz --relative \\\n --files-from=/tmp/changed.txt \\\n --password-file=/tmp/rsync.pass \\\n ./ \\\n \"$RSYNC_TARGET\"\n else\n echo \"✅ No files changed.\"\n fi\nfi\n"
env:
RSYNC_HOST: ${{ secrets.TEST_RSYNC_HOST }}
RSYNC_USER: ${{ secrets.TEST_RSYNC_USER }}
RSYNC_MODULE: ftp
- name: Clean up
run: rm -f /tmp/rsync.pass
...
|
deploy
|
null
|
["ubuntu-latest"]
|
13
|
2
|
1770802313
|
1770802318
|
1770802312
|
1770802318
|
NULL
|
NULL
|
|
0
|
Edit
Delete
|
|
13
|
12
|
2
|
2
|
4dfa289ffffceaeca9d550f8e594eb56a3c414be
|
0
|
deploy
|
1
|
name: Smart Deploy via Rsync Daemon
"on": name: Smart Deploy via Rsync Daemon
"on": [push]
jobs:
deploy:
name: deploy
runs-on: ubuntu-latest
steps:
- name: Checkout code manually
run: "echo \"\U0001F50D Cloning repository from: ${GITEA_SERVER_URL}/${GITEA_REPOSITORY}\"\n\n# 检查关键变量是否存在\nif [ -z \"$GITEA_SERVER_URL\" ] || [ -z \"$GITEA_REPOSITORY\" ] || [ -z \"$GITEA_SHA\" ]; then\n echo \"❌ Error: Missing required Gitea environment variables.\"\n exit 1\nfi\n\n# 移除 URL 中的 https:// 前缀,以便拼接 token\nHOST_AND_PATH=\"${GITEA_SERVER_URL#https://}\"\nREPO_URL=\"https://${GITEA_TOKEN}@${HOST_AND_PATH}/${GITEA_REPOSITORY}.git\"\n\ngit init\ngit remote add origin \"$REPO_URL\"\ngit fetch --depth=1 origin \"$GITEA_SHA\"\ngit checkout \"$GITEA_SHA\"\n"
env:
GITEA_SERVER_URL: ${{ env.GITEA_SERVER_URL }}
GITEA_REPOSITORY: ${{ env.GITEA_REPOSITORY }}
GITEA_SHA: ${{ env.GITEA_SHA }}
GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }} # ← 必须在 Secrets 中配置
- name: Prepare rsync password file
run: |
echo "$RSYNC_PASSWORD" > /tmp/rsync.pass
chmod 600 /tmp/rsync.pass
env:
RSYNC_PASSWORD: ${{ secrets.TEST_RSYNC_SECRETS }}
- name: Get commit message and decide sync mode
run: "COMMIT_MSG=$(git log -1 --pretty=%B | head -n1)\necho \"Commit message: $COMMIT_MSG\"\n\nRSYNC_TARGET=\"rsync://${RSYNC_USER}@${RSYNC_HOST}/${RSYNC_MODULE}/\"\n\nif [[ \"$COMMIT_MSG\" == *\"全量同步\"* ]] || [[ \"$COMMIT_MSG\" == *\"full sync\"* ]]; then\n echo \"\U0001F50D Full sync requested. Syncing all files...\"\n rsync -avz --delete \\\n --exclude='.git' \\\n --exclude='.gitea' \\\n --exclude='node_modules/' \\\n --password-file=/tmp/rsync.pass \\\n ./ \\\n \"$RSYNC_TARGET\"\nelse\n echo \"\U0001F504 Incremental sync (changed files only)...\"\n\n if git rev-parse HEAD~1 >/dev/null 2>&1; then\n git diff --name-only HEAD~1 HEAD > /tmp/changed.txt\n else\n find . -type f | sed 's|^\\./||' > /tmp/changed.txt\n fi\n\n if [ -s /tmp/changed.txt ]; then\n rsync -avz --relative \\\n --files-from=/tmp/changed.txt \\\n --password-file=/tmp/rsync.pass \\\n ./ \\\n \"$RSYNC_TARGET\"\n else\n echo \"✅ No files changed.\"\n fi\nfi\n"
env:
RSYNC_HOST: ${{ secrets.TEST_RSYNC_HOST }}
RSYNC_USER: ${{ secrets.TEST_RSYNC_USER }}
RSYNC_MODULE: ftp
- name: Clean up password file
run: rm -f /tmp/rsync.pass
...
|
deploy
|
null
|
["ubuntu-latest"]
|
12
|
2
|
1770801845
|
1770801850
|
1770801845
|
1770801850
|
NULL
|
NULL
|
|
0
|
Edit
Delete
|
|
16
|
15
|
2
|
2
|
061edb11a5fd660934c3b57491d585077b3805cf
|
0
|
deploy
|
0
|
name: Smart Deploy via Rsync Daemon
"on": name: Smart Deploy via Rsync Daemon
"on": [push]
jobs:
deploy:
name: deploy
runs-on: self-hosted
steps:
- name: Checkout code manually
run: |
# 调试输出
echo "GITEA_HOST: $GITEA_HOST"
echo "GITHUB_REPOSITORY: ${{ github.repository }}"
echo "GITHUB_SHA: ${{ github.sha }}"
if [ -z "$GITEA_HOST" ]; then
echo "❌ GITEA_HOST secret is missing!"
exit 1
fi
# 使用 github 上下文变量
REPO="${{ github.repository }}"
SHA="${{ github.sha }}"
if [ -z "$REPO" ] || [ -z "$SHA" ]; then
echo "❌ Missing GITHUB_REPOSITORY or GITHUB_SHA"
exit 1
fi
REPO_URL="https://${GITEA_TOKEN}@${GITEA_HOST}/${REPO}.git"
echo "Cloning from: ${REPO_URL//${GITEA_TOKEN}/***REDACTED***} (SHA: $SHA)"
git init
git remote add origin "$REPO_URL"
git fetch --depth=1 origin "$SHA"
git checkout "$SHA"
env:
GITEA_HOST: ${{ secrets.GITEAS_HOST }}
GITEA_TOKEN: ${{ secrets.GITEAS_TOKEN }}
- name: Prepare rsync password file
run: |
echo "$RSYNC_PASSWORD" > /tmp/rsync.pass
chmod 600 /tmp/rsync.pass
env:
RSYNC_PASSWORD: ${{ secrets.TEST_RSYNC_SECRETS }}
- name: Get commit message and decide sync mode
run: "COMMIT_MSG=$(git log -1 --pretty=%B | head -n1)\necho \"Commit message: $COMMIT_MSG\"\n\nRSYNC_TARGET=\"rsync://${RSYNC_USER}@${RSYNC_HOST}/${RSYNC_MODULE}/\"\n\nif [[ \"$COMMIT_MSG\" == *\"全量同步\"* ]] || [[ \"$COMMIT_MSG\" == *\"full sync\"* ]]; then\n echo \"\U0001F50D Full sync requested...\"\n rsync -avz --delete \\\n --exclude='.git' \\\n --exclude='.gitea' \\\n --exclude='node_modules/' \\\n --password-file=/tmp/rsync.pass \\\n ./ \\\n \"$RSYNC_TARGET\"\nelse\n echo \"\U0001F504 Incremental sync...\"\n\n if git rev-parse HEAD~1 >/dev/null 2>&1; then\n git diff --name-only HEAD~1 HEAD > /tmp/changed.txt\n else\n find . -type f -not -path './.git/*' -not -path './.gitea/*' | sed 's|^\\./||' > /tmp/changed.txt\n fi\n\n if [ -s /tmp/changed.txt ]; then\n echo \"Files to sync:\"\n cat /tmp/changed.txt\n \n rsync -avz --relative \\\n --files-from=/tmp/changed.txt \\\n --password-file=/tmp/rsync.pass \\\n ./ \\\n \"$RSYNC_TARGET\"\n else\n echo \"✅ No files changed.\"\n fi\nfi\n"
env:
RSYNC_HOST: ${{ secrets.TEST_RSYNC_HOST }}
RSYNC_USER: ${{ secrets.TEST_RSYNC_USER }}
RSYNC_MODULE: ftp
- if: always()
name: Clean up
run: rm -f /tmp/rsync.pass
...
|
deploy
|
null
|
["self-hosted"]
|
0
|
3
|
0
|
1770875534
|
1770874771
|
1770875534
|
NULL
|
NULL
|
|
0
|
Edit
Delete
|
|
17
|
16
|
2
|
2
|
062acdd73d520de0360ff9d9158e60f202a02578
|
0
|
deploy
|
0
|
name: Smart Deploy via Rsync Daemon
"on": name: Smart Deploy via Rsync Daemon
"on": [push]
jobs:
deploy:
name: deploy
runs-on: gitea.g-hi.com/zhangjunnan/gitea-runner:node20-rsync-v1
steps:
- name: Checkout code manually
run: |
# 调试输出
echo "GITEA_HOST: $GITEA_HOST"
echo "GITHUB_REPOSITORY: ${{ github.repository }}"
echo "GITHUB_SHA: ${{ github.sha }}"
if [ -z "$GITEA_HOST" ]; then
echo "❌ GITEA_HOST secret is missing!"
exit 1
fi
# 使用 github 上下文变量
REPO="${{ github.repository }}"
SHA="${{ github.sha }}"
if [ -z "$REPO" ] || [ -z "$SHA" ]; then
echo "❌ Missing GITHUB_REPOSITORY or GITHUB_SHA"
exit 1
fi
REPO_URL="https://${GITEA_TOKEN}@${GITEA_HOST}/${REPO}.git"
echo "Cloning from: ${REPO_URL//${GITEA_TOKEN}/***REDACTED***} (SHA: $SHA)"
git init
git remote add origin "$REPO_URL"
git fetch --depth=1 origin "$SHA"
git checkout "$SHA"
env:
GITEA_HOST: ${{ secrets.GITEAS_HOST }}
GITEA_TOKEN: ${{ secrets.GITEAS_TOKEN }}
- name: Prepare rsync password file
run: |
echo "$RSYNC_PASSWORD" > /tmp/rsync.pass
chmod 600 /tmp/rsync.pass
env:
RSYNC_PASSWORD: ${{ secrets.TEST_RSYNC_SECRETS }}
- name: Get commit message and decide sync mode
run: "COMMIT_MSG=$(git log -1 --pretty=%B | head -n1)\necho \"Commit message: $COMMIT_MSG\"\n\nRSYNC_TARGET=\"rsync://${RSYNC_USER}@${RSYNC_HOST}/${RSYNC_MODULE}/\"\n\nif [[ \"$COMMIT_MSG\" == *\"全量同步\"* ]] || [[ \"$COMMIT_MSG\" == *\"full sync\"* ]]; then\n echo \"\U0001F50D Full sync requested...\"\n rsync -avz --delete \\\n --exclude='.git' \\\n --exclude='.gitea' \\\n --exclude='node_modules/' \\\n --password-file=/tmp/rsync.pass \\\n ./ \\\n \"$RSYNC_TARGET\"\nelse\n echo \"\U0001F504 Incremental sync...\"\n\n if git rev-parse HEAD~1 >/dev/null 2>&1; then\n git diff --name-only HEAD~1 HEAD > /tmp/changed.txt\n else\n find . -type f -not -path './.git/*' -not -path './.gitea/*' | sed 's|^\\./||' > /tmp/changed.txt\n fi\n\n if [ -s /tmp/changed.txt ]; then\n echo \"Files to sync:\"\n cat /tmp/changed.txt\n \n rsync -avz --relative \\\n --files-from=/tmp/changed.txt \\\n --password-file=/tmp/rsync.pass \\\n ./ \\\n \"$RSYNC_TARGET\"\n else\n echo \"✅ No files changed.\"\n fi\nfi\n"
env:
RSYNC_HOST: ${{ secrets.TEST_RSYNC_HOST }}
RSYNC_USER: ${{ secrets.TEST_RSYNC_USER }}
RSYNC_MODULE: ftp
- if: always()
name: Clean up
run: rm -f /tmp/rsync.pass
...
|
deploy
|
null
|
["gitea.g-hi.com/zhangjunnan/gitea-runner:node ["gitea.g-hi.com/zhangjunnan/gitea-runner:node20-rsync-v1"]...
|
0
|
3
|
0
|
1770877321
|
1770877295
|
1770877321
|
NULL
|
NULL
|
|
0
|
Edit
Delete
|
|
18
|
17
|
2
|
2
|
5bb99418afc48f8e77d3cdb4edd9fa1390476c57
|
0
|
deploy
|
0
|
name: Smart Deploy via Rsync Daemon
"on": name: Smart Deploy via Rsync Daemon
"on": [push]
jobs:
deploy:
name: deploy
runs-on: gitea.g-hi.com/zhangjunnan/gitea-runner:node20-rsync-v1
steps:
- name: Checkout code manually
run: |
# 调试输出
echo "GITEA_HOST: $GITEA_HOST"
echo "GITHUB_REPOSITORY: ${{ github.repository }}"
echo "GITHUB_SHA: ${{ github.sha }}"
if [ -z "$GITEA_HOST" ]; then
echo "❌ GITEA_HOST secret is missing!"
exit 1
fi
# 使用 github 上下文变量
REPO="${{ github.repository }}"
SHA="${{ github.sha }}"
if [ -z "$REPO" ] || [ -z "$SHA" ]; then
echo "❌ Missing GITHUB_REPOSITORY or GITHUB_SHA"
exit 1
fi
REPO_URL="https://${GITEA_TOKEN}@${GITEA_HOST}/${REPO}.git"
echo "Cloning from: ${REPO_URL//${GITEA_TOKEN}/***REDACTED***} (SHA: $SHA)"
git init
git remote add origin "$REPO_URL"
git fetch --depth=1 origin "$SHA"
git checkout "$SHA"
env:
GITEA_HOST: ${{ secrets.GITEAS_HOST }}
GITEA_TOKEN: ${{ secrets.GITEAS_TOKEN }}
- name: Prepare rsync password file
run: |
echo "$RSYNC_PASSWORD" > /tmp/rsync.pass
chmod 600 /tmp/rsync.pass
env:
RSYNC_PASSWORD: ${{ secrets.TEST_RSYNC_SECRETS }}
- name: Get commit message and decide sync mode
run: "COMMIT_MSG=$(git log -1 --pretty=%B | head -n1)\necho \"Commit message: $COMMIT_MSG\"\n\nRSYNC_TARGET=\"rsync://${RSYNC_USER}@${RSYNC_HOST}/${RSYNC_MODULE}/\"\n\nif [[ \"$COMMIT_MSG\" == *\"全量同步\"* ]] || [[ \"$COMMIT_MSG\" == *\"full sync\"* ]]; then\n echo \"\U0001F50D Full sync requested...\"\n rsync -avz --delete \\\n --exclude='.git' \\\n --exclude='.gitea' \\\n --exclude='node_modules/' \\\n --password-file=/tmp/rsync.pass \\\n ./ \\\n \"$RSYNC_TARGET\"\nelse\n echo \"\U0001F504 Incremental sync...\"\n\n if git rev-parse HEAD~1 >/dev/null 2>&1; then\n git diff --name-only HEAD~1 HEAD > /tmp/changed.txt\n else\n find . -type f -not -path './.git/*' -not -path './.gitea/*' | sed 's|^\\./||' > /tmp/changed.txt\n fi\n\n if [ -s /tmp/changed.txt ]; then\n echo \"Files to sync:\"\n cat /tmp/changed.txt\n \n rsync -avz --relative \\\n --files-from=/tmp/changed.txt \\\n --password-file=/tmp/rsync.pass \\\n ./ \\\n \"$RSYNC_TARGET\"\n else\n echo \"✅ No files changed.\"\n fi\nfi\n"
env:
RSYNC_HOST: ${{ secrets.TEST_RSYNC_HOST }}
RSYNC_USER: ${{ secrets.TEST_RSYNC_USER }}
RSYNC_MODULE: ftp
- if: always()
name: Clean up
run: rm -f /tmp/rsync.pass
...
|
deploy
|
null
|
["gitea.g-hi.com/zhangjunnan/gitea-runner:node ["gitea.g-hi.com/zhangjunnan/gitea-runner:node20-rsync-v1"]...
|
0
|
3
|
0
|
1770877939
|
1770877350
|
1770877939
|
NULL
|
NULL
|
|
0
|
Edit
Delete
|
|
19
|
18
|
2
|
2
|
fbac0054c597227db514fa1c6581f5e9002a4c84
|
0
|
deploy
|
0
|
name: Smart Deploy via Rsync Daemon
"on": name: Smart Deploy via Rsync Daemon
"on": [push]
jobs:
deploy:
name: deploy
runs-on: gitea.g-hi.com/zhangjunnan/gitea-runner:node20-rsync-v1
steps:
- name: Checkout code manually
run: |
# 调试输出
echo "GITEA_HOST: $GITEA_HOST"
echo "GITHUB_REPOSITORY: ${{ github.repository }}"
echo "GITHUB_SHA: ${{ github.sha }}"
if [ -z "$GITEA_HOST" ]; then
echo "❌ GITEA_HOST secret is missing!"
exit 1
fi
# 使用 github 上下文变量
REPO="${{ github.repository }}"
SHA="${{ github.sha }}"
if [ -z "$REPO" ] || [ -z "$SHA" ]; then
echo "❌ Missing GITHUB_REPOSITORY or GITHUB_SHA"
exit 1
fi
REPO_URL="https://${GITEA_TOKEN}@${GITEA_HOST}/${REPO}.git"
echo "Cloning from: ${REPO_URL//${GITEA_TOKEN}/***REDACTED***} (SHA: $SHA)"
git init
git remote add origin "$REPO_URL"
git fetch --depth=1 origin "$SHA"
git checkout "$SHA"
env:
GITEA_HOST: ${{ secrets.GITEAS_HOST }}
GITEA_TOKEN: ${{ secrets.GITEAS_TOKEN }}
- name: Prepare rsync password file
run: |
echo "$RSYNC_PASSWORD" > /tmp/rsync.pass
chmod 600 /tmp/rsync.pass
env:
RSYNC_PASSWORD: ${{ secrets.TEST_RSYNC_SECRETS }}
- name: Get commit message and decide sync mode
run: "COMMIT_MSG=$(git log -1 --pretty=%B | head -n1)\necho \"Commit message: $COMMIT_MSG\"\n\nRSYNC_TARGET=\"rsync://${RSYNC_USER}@${RSYNC_HOST}/${RSYNC_MODULE}/\"\n\nif [[ \"$COMMIT_MSG\" == *\"全量同步\"* ]] || [[ \"$COMMIT_MSG\" == *\"full sync\"* ]]; then\n echo \"\U0001F50D Full sync requested...\"\n rsync -avz --delete \\\n --exclude='.git' \\\n --exclude='.gitea' \\\n --exclude='node_modules/' \\\n --password-file=/tmp/rsync.pass \\\n ./ \\\n \"$RSYNC_TARGET\"\nelse\n echo \"\U0001F504 Incremental sync...\"\n\n if git rev-parse HEAD~1 >/dev/null 2>&1; then\n git diff --name-only HEAD~1 HEAD > /tmp/changed.txt\n else\n find . -type f -not -path './.git/*' -not -path './.gitea/*' | sed 's|^\\./||' > /tmp/changed.txt\n fi\n\n if [ -s /tmp/changed.txt ]; then\n echo \"Files to sync:\"\n cat /tmp/changed.txt\n \n rsync -avz --relative \\\n --files-from=/tmp/changed.txt \\\n --password-file=/tmp/rsync.pass \\\n ./ \\\n \"$RSYNC_TARGET\"\n else\n echo \"✅ No files changed.\"\n fi\nfi\n"
env:
RSYNC_HOST: ${{ secrets.TEST_RSYNC_HOST }}
RSYNC_USER: ${{ secrets.TEST_RSYNC_USER }}
RSYNC_MODULE: ftp
- if: always()
name: Clean up
run: rm -f /tmp/rsync.pass
...
|
deploy
|
null
|
["gitea.g-hi.com/zhangjunnan/gitea-runner:node ["gitea.g-hi.com/zhangjunnan/gitea-runner:node20-rsync-v1"]...
|
0
|
3
|
0
|
1770878173
|
1770878076
|
1770878173
|
NULL
|
NULL
|
|
0
|
Edit
Delete
|
|
20
|
19
|
2
|
2
|
b277cb95b0e9e11aded5a72555e5bfe6066bd7b5
|
0
|
deploy
|
0
|
name: Smart Deploy via Rsync Daemon
"on": name: Smart Deploy via Rsync Daemon
"on": [push]
jobs:
deploy:
name: deploy
runs-on: gitea.g-hi.com/zhangjunnan/gitea-runner:node20-rsync-v1
steps:
- name: Checkout code manually
run: |
# 调试输出
echo "GITEA_HOST: $GITEA_HOST"
echo "GITHUB_REPOSITORY: ${{ github.repository }}"
echo "GITHUB_SHA: ${{ github.sha }}"
if [ -z "$GITEA_HOST" ]; then
echo "❌ GITEA_HOST secret is missing!"
exit 1
fi
# 使用 github 上下文变量
REPO="${{ github.repository }}"
SHA="${{ github.sha }}"
if [ -z "$REPO" ] || [ -z "$SHA" ]; then
echo "❌ Missing GITHUB_REPOSITORY or GITHUB_SHA"
exit 1
fi
REPO_URL="https://${GITEA_TOKEN}@${GITEA_HOST}/${REPO}.git"
echo "Cloning from: ${REPO_URL//${GITEA_TOKEN}/***REDACTED***} (SHA: $SHA)"
git init
git remote add origin "$REPO_URL"
git fetch --depth=1 origin "$SHA"
git checkout "$SHA"
env:
GITEA_HOST: ${{ secrets.GITEAS_HOST }}
GITEA_TOKEN: ${{ secrets.GITEAS_TOKEN }}
- name: Prepare rsync password file
run: |
echo "$RSYNC_PASSWORD" > /tmp/rsync.pass
chmod 600 /tmp/rsync.pass
env:
RSYNC_PASSWORD: ${{ secrets.TEST_RSYNC_SECRETS }}
- name: Get commit message and decide sync mode
run: "COMMIT_MSG=$(git log -1 --pretty=%B | head -n1)\necho \"Commit message: $COMMIT_MSG\"\n\nRSYNC_TARGET=\"rsync://${RSYNC_USER}@${RSYNC_HOST}/${RSYNC_MODULE}/\"\n\nif [[ \"$COMMIT_MSG\" == *\"全量同步\"* ]] || [[ \"$COMMIT_MSG\" == *\"full sync\"* ]]; then\n echo \"\U0001F50D Full sync requested...\"\n rsync -avz --delete \\\n --exclude='.git' \\\n --exclude='.gitea' \\\n --exclude='node_modules/' \\\n --password-file=/tmp/rsync.pass \\\n ./ \\\n \"$RSYNC_TARGET\"\nelse\n echo \"\U0001F504 Incremental sync...\"\n\n if git rev-parse HEAD~1 >/dev/null 2>&1; then\n git diff --name-only HEAD~1 HEAD > /tmp/changed.txt\n else\n find . -type f -not -path './.git/*' -not -path './.gitea/*' | sed 's|^\\./||' > /tmp/changed.txt\n fi\n\n if [ -s /tmp/changed.txt ]; then\n echo \"Files to sync:\"\n cat /tmp/changed.txt\n \n rsync -avz --relative \\\n --files-from=/tmp/changed.txt \\\n --password-file=/tmp/rsync.pass \\\n ./ \\\n \"$RSYNC_TARGET\"\n else\n echo \"✅ No files changed.\"\n fi\nfi\n"
env:
RSYNC_HOST: ${{ secrets.TEST_RSYNC_HOST }}
RSYNC_USER: ${{ secrets.TEST_RSYNC_USER }}
RSYNC_MODULE: ftp
- if: always()
name: Clean up
run: rm -f /tmp/rsync.pass
...
|
deploy
|
null
|
["gitea.g-hi.com/zhangjunnan/gitea-runner:node ["gitea.g-hi.com/zhangjunnan/gitea-runner:node20-rsync-v1"]...
|
0
|
3
|
0
|
1770878314
|
1770878195
|
1770878314
|
NULL
|
NULL
|
|
0
|
Edit
Delete
|
|
21
|
20
|
2
|
2
|
22ffb09cf279f85db485e386b24c7d3a1cdda4e6
|
0
|
deploy
|
0
|
name: Smart Deploy via Rsync Daemon
"on": name: Smart Deploy via Rsync Daemon
"on": [push]
jobs:
deploy:
name: deploy
runs-on: docker://gitea.g-hi.com/zhangjunnan/gitea-runner:node20-rsync-v1
steps:
- name: Checkout code manually
run: |
# 调试输出
echo "GITEA_HOST: $GITEA_HOST"
echo "GITHUB_REPOSITORY: ${{ github.repository }}"
echo "GITHUB_SHA: ${{ github.sha }}"
if [ -z "$GITEA_HOST" ]; then
echo "❌ GITEA_HOST secret is missing!"
exit 1
fi
# 使用 github 上下文变量
REPO="${{ github.repository }}"
SHA="${{ github.sha }}"
if [ -z "$REPO" ] || [ -z "$SHA" ]; then
echo "❌ Missing GITHUB_REPOSITORY or GITHUB_SHA"
exit 1
fi
REPO_URL="https://${GITEA_TOKEN}@${GITEA_HOST}/${REPO}.git"
echo "Cloning from: ${REPO_URL//${GITEA_TOKEN}/***REDACTED***} (SHA: $SHA)"
git init
git remote add origin "$REPO_URL"
git fetch --depth=1 origin "$SHA"
git checkout "$SHA"
env:
GITEA_HOST: ${{ secrets.GITEAS_HOST }}
GITEA_TOKEN: ${{ secrets.GITEAS_TOKEN }}
- name: Prepare rsync password file
run: |
echo "$RSYNC_PASSWORD" > /tmp/rsync.pass
chmod 600 /tmp/rsync.pass
env:
RSYNC_PASSWORD: ${{ secrets.TEST_RSYNC_SECRETS }}
- name: Get commit message and decide sync mode
run: "COMMIT_MSG=$(git log -1 --pretty=%B | head -n1)\necho \"Commit message: $COMMIT_MSG\"\n\nRSYNC_TARGET=\"rsync://${RSYNC_USER}@${RSYNC_HOST}/${RSYNC_MODULE}/\"\n\nif [[ \"$COMMIT_MSG\" == *\"全量同步\"* ]] || [[ \"$COMMIT_MSG\" == *\"full sync\"* ]]; then\n echo \"\U0001F50D Full sync requested...\"\n rsync -avz --delete \\\n --exclude='.git' \\\n --exclude='.gitea' \\\n --exclude='node_modules/' \\\n --password-file=/tmp/rsync.pass \\\n ./ \\\n \"$RSYNC_TARGET\"\nelse\n echo \"\U0001F504 Incremental sync...\"\n\n if git rev-parse HEAD~1 >/dev/null 2>&1; then\n git diff --name-only HEAD~1 HEAD > /tmp/changed.txt\n else\n find . -type f -not -path './.git/*' -not -path './.gitea/*' | sed 's|^\\./||' > /tmp/changed.txt\n fi\n\n if [ -s /tmp/changed.txt ]; then\n echo \"Files to sync:\"\n cat /tmp/changed.txt\n \n rsync -avz --relative \\\n --files-from=/tmp/changed.txt \\\n --password-file=/tmp/rsync.pass \\\n ./ \\\n \"$RSYNC_TARGET\"\n else\n echo \"✅ No files changed.\"\n fi\nfi\n"
env:
RSYNC_HOST: ${{ secrets.TEST_RSYNC_HOST }}
RSYNC_USER: ${{ secrets.TEST_RSYNC_USER }}
RSYNC_MODULE: ftp
- if: always()
name: Clean up
run: rm -f /tmp/rsync.pass
...
|
deploy
|
null
|
["docker://gitea.g-hi.com/zhangjunnan/gitea-ru ["docker://gitea.g-hi.com/zhangjunnan/gitea-runner:node20-rsync-v1"]...
|
0
|
3
|
0
|
1770878361
|
1770878332
|
1770878361
|
NULL
|
NULL
|
|
0
|
Edit
Delete
|
|
46281
|
26862
|
116
|
5
|
176ef800c75586d22886a922e7610e7f18591156
|
0
|
validate
|
1
|
name: Separate runner registration custody from ro name: Separate runner registration custody from rootless Docker UID
"on":
push:
branches: [codex/runner-maintenance-diagnostic-20261101]
paths: [.gitea/workflows/gitea-runner-custody.yml]
workflow_dispatch:
jobs:
validate:
name: validate
runs-on: ubuntu-latest
if: github.repository == 'luoanwu/enterprise-platform'
steps:
- name: Validate fixed reviewed inputs in private resource limited units
run: |
set -eu
python3 <<'PY'
import os,re,subprocess,json
maintenanceLoaderSha=os.environ.get('GITHUB_SHA','')
if not re.fullmatch('[0-9a-f]{40}',maintenanceLoaderSha):raise RuntimeError('FIXED_SHA_REQUIRED')
command=['git','-c','safe.directory=/home/git/repositories/luoanwu/enterprise-platform.git','-c','core.hooksPath=/dev/null','-C','/home/git/repositories/luoanwu/enterprise-platform.git','show',maintenanceLoaderSha+':ops/gitea-r1/prepare-runner-custody.py']
result=subprocess.run(command,capture_output=True,timeout=30)
if result.returncode:raise RuntimeError('FIXED_SCRIPT_UNAVAILABLE')
try:exec(compile(result.stdout,'prepare-runner-custody.py','exec'))
except Exception as error:
message=str(error)
safe=message if isinstance(error,RuntimeError) and re.fullmatch(r'[A-Z_]+(?::[A-Za-z0-9_./,:]+)?',message) else type(error).__name__
print('GITEA_RUNNER_CUSTODY_JSON '+json.dumps({'kind':'GiteaRunnerSeparateCredentialCustody','ci':{'sourceSha':maintenanceLoaderSha,'runId':os.environ.get('GITHUB_RUN_ID')},'status':'FAILED_PREPARATION','error':safe,'releaseEligible':False,'liveServicesChanged':False,'liveDatabaseChanged':False}),flush=True)
raise SystemExit(1)
PY
shell: bash
timeout-minutes: "95"
permissions:
contents: read
...
|
validate
|
null
|
["ubuntu-latest"]
|
37686
|
2
|
1793542883
|
1793542883
|
1793542882
|
1793542884
|
|
0
|
|
0
|
Edit
Delete
|
|
46283
|
26864
|
116
|
5
|
55d905e88a8db82e0505715319c0acc808b15c46
|
0
|
validate
|
1
|
name: Separate runner registration custody from ro name: Separate runner registration custody from rootless Docker UID
"on":
push:
branches: [codex/runner-maintenance-diagnostic-20261101]
paths: [.gitea/workflows/gitea-runner-custody.yml]
workflow_dispatch:
jobs:
validate:
name: validate
runs-on: ubuntu-latest
if: github.repository == 'luoanwu/enterprise-platform'
steps:
- name: Validate fixed reviewed inputs in private resource limited units
run: |
set -eu
python3 <<'PY'
import os,re,subprocess,json
maintenanceLoaderSha=os.environ.get('GITHUB_SHA','')
if not re.fullmatch('[0-9a-f]{40}',maintenanceLoaderSha):raise RuntimeError('FIXED_SHA_REQUIRED')
command=['git','-c','safe.directory=/home/git/repositories/luoanwu/enterprise-platform.git','-c','core.hooksPath=/dev/null','-C','/home/git/repositories/luoanwu/enterprise-platform.git','show',maintenanceLoaderSha+':ops/gitea-r1/prepare-runner-custody.py']
result=subprocess.run(command,capture_output=True,timeout=30)
if result.returncode:raise RuntimeError('FIXED_SCRIPT_UNAVAILABLE')
try:exec(compile(result.stdout,'prepare-runner-custody.py','exec'))
except Exception as error:
message=str(error)
safe=message if isinstance(error,RuntimeError) and re.fullmatch(r'[A-Z_]+(?::[A-Za-z0-9_./,:]+)?',message) else type(error).__name__
print('GITEA_RUNNER_CUSTODY_JSON '+json.dumps({'kind':'GiteaRunnerSeparateCredentialCustody','ci':{'sourceSha':maintenanceLoaderSha,'runId':os.environ.get('GITHUB_RUN_ID')},'status':'FAILED_PREPARATION','error':safe,'releaseEligible':False,'liveServicesChanged':False,'liveDatabaseChanged':False}),flush=True)
raise SystemExit(1)
PY
shell: bash
timeout-minutes: "95"
permissions:
contents: read
...
|
validate
|
null
|
["ubuntu-latest"]
|
37688
|
1
|
1793542949
|
1793542951
|
1793542948
|
1793542951
|
|
0
|
|
0
|
Edit
Delete
|
|
46009
|
26751
|
116
|
5
|
aa84ee9822653378826fda649c445d2f0b5e462e
|
0
|
Verify actual storage capacity and schema metadata
|
1
|
name: Runner maintenance storage preflight (read o name: Runner maintenance storage preflight (read only)
"on":
push:
branches: [codex/runner-maintenance-diagnostic-20261101]
paths: [.gitea/workflows/runner-maintenance-preflight.yml]
workflow_dispatch:
jobs:
preflight:
name: Verify actual storage capacity and schema metadata
runs-on: ubuntu-latest
if: github.repository == 'luoanwu/enterprise-platform'
steps:
- name: Read storage sizes and public build metadata
run: |
set -eu
python3 <<'PY'
import configparser, datetime, hashlib, json, mmap, os, pathlib, socket, sqlite3, struct, subprocess
if os.geteuid()!=0 or socket.gethostname()!='iZ2zed3pt2nygf9dy1a9d2Z': raise RuntimeError('EXPECTED_HOST_REQUIRED')
config_path=pathlib.Path('/home/git/gitea/config/app.ini')
if hashlib.sha256(config_path.read_bytes()).hexdigest()!='8923b93032ec5c303317b02a7011f756c0ee8e6c2756d4cd4286dd9b5c54d072': raise RuntimeError('CONFIG_CHANGED_REINVENTORY_REQUIRED')
report={'kind':'RunnerMaintenanceStoragePreflight','observedAtUTC':datetime.datetime.now(datetime.timezone.utc).isoformat(),'ci':{'sourceSha':os.environ.get('GITHUB_SHA'),'runId':os.environ.get('GITHUB_RUN_ID')},'releaseEligible':False,'sizes':[],'storageSections':[]}
def command(args,timeout=40):
r=subprocess.run(args,stdin=subprocess.DEVNULL,capture_output=True,text=True,timeout=timeout)
return r.returncode,r.stdout
for p in ['/home/git/gitea','/home/git/repositories','/usr/local/bin/data','/opt/gitea-runner','/root/gitea-ext','/root/gitea-code-review']:
if not pathlib.Path(p).exists(): continue
code,out=command(['du','-sx','--block-size=1',p])
report['sizes'].append({'path':p,'bytes':int(out.split()[0]) if code==0 else None,'exitCode':code})
st=os.statvfs('/')
report['rootFilesystem']={'availableBytes':st.f_bavail*st.f_frsize,'totalBytes':st.f_blocks*st.f_frsize}
cfg=configparser.RawConfigParser(strict=False);cfg.read(config_path)
for section in cfg.sections():
if section.lower().startswith(('storage','lfs','attachment','packages','actions')):
report['storageSections'].append({'section':section,'keys':[k for k in cfg[section] if not any(x in k.lower() for x in ['secret','password','token','access_key'])]})
db='/home/git/gitea/data/gitea.db'
connection=sqlite3.connect('file:'+db+'?mode=ro',uri=True,timeout=10)
connection.execute('PRAGMA query_only=ON')
tables={r[0] for r in connection.execute("SELECT name FROM sqlite_master WHERE type='table'")}
report['database']={'path':db,'sqliteVersion':sqlite3.sqlite_version,'tableCount':len(tables),'hasAttemptTable':'action_run_attempt' in tables}
if 'version' in tables:
columns=[r[1] for r in connection.execute('PRAGMA table_info("version")')]
col=next((x for x in columns if x.lower()=='version'),None)
if col: report['database']['schemaVersions']=[r[0] for r in connection.execute('SELECT "'+col.replace('"','""')+'" FROM "version" LIMIT 5')]
report['database']['journalMode']=connection.execute('PRAGMA journal_mode').fetchone()[0]
connection.close()
def go_build(path):
try:
with open(path,'rb') as f, mmap.mmap(f.fileno(),0,access=mmap.ACCESS_READ) as data:
at=data.find(b'\xff Go buildinf:')
if at<0:return {'format':'not-found'}
ptr_size=data[at+14];flags=data[at+15]
if not flags&2:return {'format':'pointer-layout','pointerSize':ptr_size,'flags':flags}
pos=at+32
def readvar(pos):
value=0;shift=0
for i in range(10):
b=data[pos];pos+=1;value|=(b&127)<<shift
if b<128:return value,pos
shift+=7
raise ValueError('invalid-varint')
size,pos=readvar(pos);version=bytes(data[pos:pos+size]).decode(errors='replace');pos+=size
size,pos=readvar(pos);mod=bytes(data[pos:pos+size]).decode(errors='replace')
selected=[]
for line in mod.splitlines():
if line.startswith('path\t') and ('gitea' in line or 'runner' in line): selected.append(line[:300])
elif line.startswith('mod\t') and ('gitea' in line or 'runner' in line): selected.append(line[:300])
elif line.startswith('build\tvcs'): selected.append(line[:200])
return {'format':'inline','goVersion':version,'selectedModuleFields':selected}
except Exception as e:return {'errorClass':type(e).__name__}
report['giteaGoBuild']=go_build('/usr/local/bin/gitea')
report['runnerGoBuild']=go_build('/opt/gitea-runner/act-runner')
for p in ['/home/git/gitea/custom','/home/git/gitea/config','/usr/local/bin/data','/opt/gitea-runner']:
root=pathlib.Path(p)
if not root.is_dir():continue
children=[]
for child in sorted(root.iterdir()):
if child.name.startswith('.') or any(x in child.name.lower() for x in ['secret','token','password','credential','private']):continue
if p=='/opt/gitea-runner' and child.name not in ['config.yaml','config.yml','act-runner','act_runner']:continue
children.append({'name':child.name,'directory':child.is_dir(),'bytes':child.stat().st_size})
report.setdefault('layoutMetadata',[]).append({'path':p,'children':children[:30]})
report['boundaries']={'serviceChanges':False,'dataWrites':False,'secretValuesPrinted':False,'backupsOrUpgrades':False}
print('RUNNER_MAINTENANCE_PREFLIGHT_JSON '+json.dumps(report,separators=(',',':')))
PY
shell: bash
timeout-minutes: "5"
permissions:
contents: read
...
|
preflight
|
null
|
["ubuntu-latest"]
|
37479
|
2
|
1793520494
|
1793520533
|
1793520493
|
1793520534
|
|
0
|
|
0
|
Edit
Delete
|
|
46011
|
26753
|
116
|
5
|
43749e863bb29a4099fef7730f962c9bde7ba4ba
|
0
|
Verify actual storage capacity and schema metadata
|
1
|
name: Runner maintenance storage preflight (read o name: Runner maintenance storage preflight (read only)
"on":
push:
branches: [codex/runner-maintenance-diagnostic-20261101]
paths: [.gitea/workflows/runner-maintenance-preflight.yml]
workflow_dispatch:
jobs:
preflight:
name: Verify actual storage capacity and schema metadata
runs-on: ubuntu-latest
if: github.repository == 'luoanwu/enterprise-platform'
steps:
- name: Read storage sizes and public build metadata
run: |
set -eu
python3 <<'PY'
import configparser, datetime, hashlib, json, mmap, os, pathlib, socket, sqlite3, struct, subprocess
if os.geteuid()!=0 or socket.gethostname()!='iZ2zed3pt2nygf9dy1a9d2Z': raise RuntimeError('EXPECTED_HOST_REQUIRED')
config_path=pathlib.Path('/home/git/gitea/config/app.ini')
if hashlib.sha256(config_path.read_bytes()).hexdigest()!='8923b93032ec5c303317b02a7011f756c0ee8e6c2756d4cd4286dd9b5c54d072': raise RuntimeError('CONFIG_CHANGED_REINVENTORY_REQUIRED')
report={'kind':'RunnerMaintenanceStoragePreflight','observedAtUTC':datetime.datetime.now(datetime.timezone.utc).isoformat(),'ci':{'sourceSha':os.environ.get('GITHUB_SHA'),'runId':os.environ.get('GITHUB_RUN_ID')},'releaseEligible':False,'sizes':[],'storageSections':[]}
def command(args,timeout=40):
r=subprocess.run(args,stdin=subprocess.DEVNULL,capture_output=True,text=True,timeout=timeout)
return r.returncode,r.stdout
for p in ['/home/git/gitea','/home/git/repositories','/usr/local/bin/data','/opt/gitea-runner','/root/gitea-ext','/root/gitea-code-review']:
if not pathlib.Path(p).exists(): continue
code,out=command(['du','-sx','--block-size=1',p])
report['sizes'].append({'path':p,'bytes':int(out.split()[0]) if code==0 else None,'exitCode':code})
st=os.statvfs('/')
report['rootFilesystem']={'availableBytes':st.f_bavail*st.f_frsize,'totalBytes':st.f_blocks*st.f_frsize}
# Gitea INI permits top-level APP_NAME/RUN_USER before its first section.
cfg=configparser.RawConfigParser(strict=False);cfg.read_string('[DEFAULT]\n'+config_path.read_text())
for section in cfg.sections():
if section.lower().startswith(('storage','lfs','attachment','packages','actions')):
report['storageSections'].append({'section':section,'keys':[k for k in cfg[section] if not any(x in k.lower() for x in ['secret','password','token','access_key'])]})
db='/home/git/gitea/data/gitea.db'
connection=sqlite3.connect('file:'+db+'?mode=ro',uri=True,timeout=10)
connection.execute('PRAGMA query_only=ON')
tables={r[0] for r in connection.execute("SELECT name FROM sqlite_master WHERE type='table'")}
report['database']={'path':db,'sqliteVersion':sqlite3.sqlite_version,'tableCount':len(tables),'hasAttemptTable':'action_run_attempt' in tables}
if 'version' in tables:
columns=[r[1] for r in connection.execute('PRAGMA table_info("version")')]
col=next((x for x in columns if x.lower()=='version'),None)
if col: report['database']['schemaVersions']=[r[0] for r in connection.execute('SELECT "'+col.replace('"','""')+'" FROM "version" LIMIT 5')]
report['database']['journalMode']=connection.execute('PRAGMA journal_mode').fetchone()[0]
connection.close()
def go_build(path):
try:
with open(path,'rb') as f, mmap.mmap(f.fileno(),0,access=mmap.ACCESS_READ) as data:
at=data.find(b'\xff Go buildinf:')
if at<0:return {'format':'not-found'}
ptr_size=data[at+14];flags=data[at+15]
if not flags&2:return {'format':'pointer-layout','pointerSize':ptr_size,'flags':flags}
pos=at+32
def readvar(pos):
value=0;shift=0
for i in range(10):
b=data[pos];pos+=1;value|=(b&127)<<shift
if b<128:return value,pos
shift+=7
raise ValueError('invalid-varint')
size,pos=readvar(pos);version=bytes(data[pos:pos+size]).decode(errors='replace');pos+=size
size,pos=readvar(pos);mod=bytes(data[pos:pos+size]).decode(errors='replace')
selected=[]
for line in mod.splitlines():
if line.startswith('path\t') and ('gitea' in line or 'runner' in line): selected.append(line[:300])
elif line.startswith('mod\t') and ('gitea' in line or 'runner' in line): selected.append(line[:300])
elif line.startswith('build\tvcs'): selected.append(line[:200])
return {'format':'inline','goVersion':version,'selectedModuleFields':selected}
except Exception as e:return {'errorClass':type(e).__name__}
report['giteaGoBuild']=go_build('/usr/local/bin/gitea')
report['runnerGoBuild']=go_build('/opt/gitea-runner/act-runner')
for p in ['/home/git/gitea/custom','/home/git/gitea/config','/usr/local/bin/data','/opt/gitea-runner']:
root=pathlib.Path(p)
if not root.is_dir():continue
children=[]
for child in sorted(root.iterdir()):
if child.name.startswith('.') or any(x in child.name.lower() for x in ['secret','token','password','credential','private']):continue
if p=='/opt/gitea-runner' and child.name not in ['config.yaml','config.yml','act-runner','act_runner']:continue
children.append({'name':child.name,'directory':child.is_dir(),'bytes':child.stat().st_size})
report.setdefault('layoutMetadata',[]).append({'path':p,'children':children[:30]})
report['sourceCandidates']=[]
for base in ['/root','/opt','/home/git','/usr/local/src','/srv']:
root=pathlib.Path(base)
if not root.is_dir():continue
for candidate in root.iterdir():
if not candidate.is_dir() or 'gitea' not in candidate.name.lower() or candidate.name.startswith('.'):continue
for project in [candidate,candidate/'gitea',candidate/'src']:
if not (project/'.git').exists():continue
code,head=command(['git','--no-optional-locks','-C',str(project),'rev-parse','HEAD'])
sc,dirty=command(['git','--no-optional-locks','-C',str(project),'status','--porcelain','--untracked-files=no'])
rc,origin=command(['git','--no-optional-locks','-C',str(project),'remote','get-url','origin'])
import re
safe_origin=re.sub(r'://[^/@]+@','://REDACTED@',origin.strip()).split('?',1)[0]
report['sourceCandidates'].append({'path':str(project),'head':head.strip() if code==0 else None,'trackedDirty':bool(dirty.strip()) if sc==0 else None,'origin':safe_origin if rc==0 else None})
report['sizeBreakdown']=[]
for parent in ['/home/git/gitea','/root/gitea-code-review']:
for child in pathlib.Path(parent).iterdir():
if child.name.startswith('.') or any(x in child.name.lower() for x in ['secret','credential','token','password','private']):continue
if not child.is_dir():continue
code,out=command(['du','-sx','--block-size=1',str(child)])
report['sizeBreakdown'].append({'path':str(child),'bytes':int(out.split()[0]) if code==0 else None})
cfgfile=pathlib.Path('/opt/gitea-runner/config.yaml')
if cfgfile.is_file():
selected={};parent=None
allowed={'log':{'level'},'runner':{'capacity','timeout','shutdown_timeout','fetch_timeout','fetch_interval','file'},'container':{'network','privileged','docker_host'},'cache':{'enabled','dir','host','port'}}
for line in cfgfile.read_text().splitlines():
if not line.strip() or line.lstrip().startswith('#'):continue
if not line.startswith((' ','\t')):parent=line.split(':',1)[0].strip();continue
if ':' not in line:continue
key,value=line.strip().split(':',1)
if key in allowed.get(parent,set()):selected[parent+'.'+key]=value.strip()[:200]
report['runnerConfigWhitelist']=selected
report['boundaries']={'serviceChanges':False,'dataWrites':False,'secretValuesPrinted':False,'backupsOrUpgrades':False}
print('RUNNER_MAINTENANCE_PREFLIGHT_JSON '+json.dumps(report,separators=(',',':')))
PY
shell: bash
timeout-minutes: "5"
permissions:
contents: read
...
|
preflight
|
null
|
["ubuntu-latest"]
|
37481
|
1
|
1793520767
|
1793520770
|
1793520766
|
1793520770
|
|
0
|
|
0
|
Edit
Delete
|
|
46020
|
26755
|
116
|
5
|
f24a6b3ed9c82f3ee43458ef7d9f61ba6eab585b
|
0
|
Verify actual storage capacity and schema metadata
|
1
|
name: Runner maintenance storage preflight (read o name: Runner maintenance storage preflight (read only)
"on":
push:
branches: [codex/runner-maintenance-diagnostic-20261101]
paths: [.gitea/workflows/runner-maintenance-preflight.yml]
workflow_dispatch:
jobs:
preflight:
name: Verify actual storage capacity and schema metadata
runs-on: ubuntu-latest
if: github.repository == 'luoanwu/enterprise-platform'
steps:
- name: Read storage sizes and public build metadata
run: |
set -eu
python3 <<'PY'
import configparser, datetime, hashlib, json, mmap, os, pathlib, socket, sqlite3, struct, subprocess
if os.geteuid()!=0 or socket.gethostname()!='iZ2zed3pt2nygf9dy1a9d2Z': raise RuntimeError('EXPECTED_HOST_REQUIRED')
config_path=pathlib.Path('/home/git/gitea/config/app.ini')
if hashlib.sha256(config_path.read_bytes()).hexdigest()!='8923b93032ec5c303317b02a7011f756c0ee8e6c2756d4cd4286dd9b5c54d072': raise RuntimeError('CONFIG_CHANGED_REINVENTORY_REQUIRED')
report={'kind':'RunnerMaintenanceStoragePreflight','observedAtUTC':datetime.datetime.now(datetime.timezone.utc).isoformat(),'ci':{'sourceSha':os.environ.get('GITHUB_SHA'),'runId':os.environ.get('GITHUB_RUN_ID')},'releaseEligible':False,'sizes':[],'storageSections':[]}
def command(args,timeout=40):
r=subprocess.run(args,stdin=subprocess.DEVNULL,capture_output=True,text=True,timeout=timeout)
return r.returncode,r.stdout
for p in ['/home/git/gitea','/home/git/repositories','/usr/local/bin/data','/opt/gitea-runner','/root/gitea-ext','/root/gitea-code-review']:
if not pathlib.Path(p).exists(): continue
code,out=command(['du','-sx','--block-size=1',p])
report['sizes'].append({'path':p,'bytes':int(out.split()[0]) if code==0 else None,'exitCode':code})
st=os.statvfs('/')
report['rootFilesystem']={'availableBytes':st.f_bavail*st.f_frsize,'totalBytes':st.f_blocks*st.f_frsize}
# Gitea INI permits top-level APP_NAME/RUN_USER before its first section.
cfg=configparser.RawConfigParser(strict=False);cfg.read_string('[DEFAULT]\n'+config_path.read_text())
for section in cfg.sections():
if section.lower().startswith(('storage','lfs','attachment','packages','actions')):
report['storageSections'].append({'section':section,'keys':[k for k in cfg[section] if not any(x in k.lower() for x in ['secret','password','token','access_key'])]})
db='/home/git/gitea/data/gitea.db'
connection=sqlite3.connect('file:'+db+'?mode=ro',uri=True,timeout=10)
connection.execute('PRAGMA query_only=ON')
tables={r[0] for r in connection.execute("SELECT name FROM sqlite_master WHERE type='table'")}
report['database']={'path':db,'sqliteVersion':sqlite3.sqlite_version,'tableCount':len(tables),'hasAttemptTable':'action_run_attempt' in tables}
if 'version' in tables:
columns=[r[1] for r in connection.execute('PRAGMA table_info("version")')]
col=next((x for x in columns if x.lower()=='version'),None)
if col: report['database']['schemaVersions']=[r[0] for r in connection.execute('SELECT "'+col.replace('"','""')+'" FROM "version" LIMIT 5')]
report['database']['journalMode']=connection.execute('PRAGMA journal_mode').fetchone()[0]
connection.close()
def go_build(path):
try:
with open(path,'rb') as f, mmap.mmap(f.fileno(),0,access=mmap.ACCESS_READ) as data:
at=data.find(b'\xff Go buildinf:')
if at<0:return {'format':'not-found'}
ptr_size=data[at+14];flags=data[at+15]
if not flags&2:return {'format':'pointer-layout','pointerSize':ptr_size,'flags':flags}
pos=at+32
def readvar(pos):
value=0;shift=0
for i in range(10):
b=data[pos];pos+=1;value|=(b&127)<<shift
if b<128:return value,pos
shift+=7
raise ValueError('invalid-varint')
size,pos=readvar(pos);version=bytes(data[pos:pos+size]).decode(errors='replace');pos+=size
size,pos=readvar(pos);mod=bytes(data[pos:pos+size]).decode(errors='replace')
selected=[]
for line in mod.splitlines():
if line.startswith('path\t') and ('gitea' in line or 'runner' in line): selected.append(line[:300])
elif line.startswith('mod\t') and ('gitea' in line or 'runner' in line): selected.append(line[:300])
elif line.startswith('build\tvcs'): selected.append(line[:200])
return {'format':'inline','goVersion':version,'selectedModuleFields':selected}
except Exception as e:return {'errorClass':type(e).__name__}
report['giteaGoBuild']=go_build('/usr/local/bin/gitea')
report['runnerGoBuild']=go_build('/opt/gitea-runner/act-runner')
for p in ['/home/git/gitea/custom','/home/git/gitea/config','/usr/local/bin/data','/opt/gitea-runner']:
root=pathlib.Path(p)
if not root.is_dir():continue
children=[]
for child in sorted(root.iterdir()):
if child.name.startswith('.') or any(x in child.name.lower() for x in ['secret','token','password','credential','private']):continue
if p=='/opt/gitea-runner' and child.name not in ['config.yaml','config.yml','act-runner','act_runner']:continue
children.append({'name':child.name,'directory':child.is_dir(),'bytes':child.stat().st_size})
report.setdefault('layoutMetadata',[]).append({'path':p,'children':children[:30]})
report['sourceCandidates']=[]
for base in ['/root','/opt','/home/git','/usr/local/src','/srv']:
root=pathlib.Path(base)
if not root.is_dir():continue
for candidate in root.iterdir():
if not candidate.is_dir() or 'gitea' not in candidate.name.lower() or candidate.name.startswith('.'):continue
for project in [candidate,candidate/'gitea',candidate/'src']:
if not (project/'.git').exists():continue
code,head=command(['git','--no-optional-locks','-C',str(project),'rev-parse','HEAD'])
sc,dirty=command(['git','--no-optional-locks','-C',str(project),'status','--porcelain','--untracked-files=no'])
rc,origin=command(['git','--no-optional-locks','-C',str(project),'remote','get-url','origin'])
import re
safe_origin=re.sub(r'://[^/@]+@','://REDACTED@',origin.strip()).split('?',1)[0]
dc,delta=command(['git','--no-optional-locks','-C',str(project),'diff','--numstat'])
changed=[]
if dc==0:
for row in delta.splitlines():
fields=row.split('\t',2)
if len(fields)!=3:continue
name=fields[2]
if any(x in name.lower() for x in ['secret','credential','token','password','private','.env']):name='REDACTED_SENSITIVE_PATH'
changed.append({'added':fields[0],'removed':fields[1],'path':name})
report['sourceCandidates'].append({'path':str(project),'head':head.strip() if code==0 else None,'trackedDirty':bool(dirty.strip()) if sc==0 else None,'origin':safe_origin if rc==0 else None,'changedFiles':changed[:60]})
report['sizeBreakdown']=[]
for parent in ['/home/git/gitea','/root/gitea-code-review']:
for child in pathlib.Path(parent).iterdir():
if child.name.startswith('.') or any(x in child.name.lower() for x in ['secret','credential','token','password','private']):continue
if not child.is_dir():continue
code,out=command(['du','-sx','--block-size=1',str(child)])
report['sizeBreakdown'].append({'path':str(child),'bytes':int(out.split()[0]) if code==0 else None})
cfgfile=pathlib.Path('/opt/gitea-runner/config.yaml')
if cfgfile.is_file():
selected={};parent=None
allowed={'log':{'level'},'runner':{'capacity','timeout','shutdown_timeout','fetch_timeout','fetch_interval','file'},'container':{'network','privileged','docker_host'},'cache':{'enabled','dir','host','port'}}
for line in cfgfile.read_text().splitlines():
if not line.strip() or line.lstrip().startswith('#'):continue
if not line.startswith((' ','\t')):parent=line.split(':',1)[0].strip();continue
if ':' not in line:continue
key,value=line.strip().split(':',1)
if key in allowed.get(parent,set()):selected[parent+'.'+key]=value.strip()[:200]
report['runnerConfigWhitelist']=selected
report['extraTools']={}
for executable in ['/usr/local/go/bin/go','/usr/bin/node','/usr/bin/gpg','/usr/bin/zstd','/usr/bin/nsenter','/usr/bin/systemd-run']:
if not pathlib.Path(executable).is_file():continue
args=[executable,'version'] if executable.endswith('/go') else [executable,'--version']
code,version=command(args,timeout=10)
report['extraTools'][executable]={'exists':True,'versionFirstLine':version.splitlines()[0][:200] if code==0 and version.splitlines() else None}
import urllib.request,urllib.error,re
report['officialChecksumReachability']=[]
for url in ['https://dl.gitea.com/gitea/28.2.0/gitea-28.2.0-linux-amd64.sha256','https://dl.gitea.com/gitea-runner/5.0.0/gitea-runner-5.0.0-linux-amd64.sha256']:
item={'url':url}
try:
with urllib.request.urlopen(url,timeout=15) as response:
body=response.read(4096).decode();item['httpStatus']=response.status
match=re.search(r'\b[a-f0-9]{64}\b',body);item['sha256']=match.group(0) if match else None
except Exception as error:item.update({'errorClass':type(error).__name__,'httpStatus':getattr(error,'code',None)})
report['officialChecksumReachability'].append(item)
report['boundaries']={'serviceChanges':False,'dataWrites':False,'secretValuesPrinted':False,'backupsOrUpgrades':False}
print('RUNNER_MAINTENANCE_PREFLIGHT_JSON '+json.dumps(report,separators=(',',':')))
PY
shell: bash
timeout-minutes: "5"
permissions:
contents: read
...
|
preflight
|
null
|
["ubuntu-latest"]
|
37490
|
1
|
1793520919
|
1793520926
|
1793520918
|
1793520927
|
|
0
|
|
0
|
Edit
Delete
|
|
46010
|
26752
|
116
|
5
|
d55b0f60f2be02e655419627c2cdbc4a428a721a
|
0
|
Verify actual storage capacity and schema metadata
|
1
|
name: Runner maintenance storage preflight (read o name: Runner maintenance storage preflight (read only)
"on":
push:
branches: [codex/runner-maintenance-diagnostic-20261101]
paths: [.gitea/workflows/runner-maintenance-preflight.yml]
workflow_dispatch:
jobs:
preflight:
name: Verify actual storage capacity and schema metadata
runs-on: ubuntu-latest
if: github.repository == 'luoanwu/enterprise-platform'
steps:
- name: Read storage sizes and public build metadata
run: |
set -eu
python3 <<'PY'
import configparser, datetime, hashlib, json, mmap, os, pathlib, socket, sqlite3, struct, subprocess
if os.geteuid()!=0 or socket.gethostname()!='iZ2zed3pt2nygf9dy1a9d2Z': raise RuntimeError('EXPECTED_HOST_REQUIRED')
config_path=pathlib.Path('/home/git/gitea/config/app.ini')
if hashlib.sha256(config_path.read_bytes()).hexdigest()!='8923b93032ec5c303317b02a7011f756c0ee8e6c2756d4cd4286dd9b5c54d072': raise RuntimeError('CONFIG_CHANGED_REINVENTORY_REQUIRED')
report={'kind':'RunnerMaintenanceStoragePreflight','observedAtUTC':datetime.datetime.now(datetime.timezone.utc).isoformat(),'ci':{'sourceSha':os.environ.get('GITHUB_SHA'),'runId':os.environ.get('GITHUB_RUN_ID')},'releaseEligible':False,'sizes':[],'storageSections':[]}
def command(args,timeout=40):
r=subprocess.run(args,stdin=subprocess.DEVNULL,capture_output=True,text=True,timeout=timeout)
return r.returncode,r.stdout
for p in ['/home/git/gitea','/home/git/repositories','/usr/local/bin/data','/opt/gitea-runner','/root/gitea-ext','/root/gitea-code-review']:
if not pathlib.Path(p).exists(): continue
code,out=command(['du','-sx','--block-size=1',p])
report['sizes'].append({'path':p,'bytes':int(out.split()[0]) if code==0 else None,'exitCode':code})
st=os.statvfs('/')
report['rootFilesystem']={'availableBytes':st.f_bavail*st.f_frsize,'totalBytes':st.f_blocks*st.f_frsize}
# Gitea INI permits top-level APP_NAME/RUN_USER before its first section.
cfg=configparser.RawConfigParser(strict=False);cfg.read_string('[DEFAULT]\n'+config_path.read_text())
for section in cfg.sections():
if section.lower().startswith(('storage','lfs','attachment','packages','actions')):
report['storageSections'].append({'section':section,'keys':[k for k in cfg[section] if not any(x in k.lower() for x in ['secret','password','token','access_key'])]})
db='/home/git/gitea/data/gitea.db'
connection=sqlite3.connect('file:'+db+'?mode=ro',uri=True,timeout=10)
connection.execute('PRAGMA query_only=ON')
tables={r[0] for r in connection.execute("SELECT name FROM sqlite_master WHERE type='table'")}
report['database']={'path':db,'sqliteVersion':sqlite3.sqlite_version,'tableCount':len(tables),'hasAttemptTable':'action_run_attempt' in tables}
if 'version' in tables:
columns=[r[1] for r in connection.execute('PRAGMA table_info("version")')]
col=next((x for x in columns if x.lower()=='version'),None)
if col: report['database']['schemaVersions']=[r[0] for r in connection.execute('SELECT "'+col.replace('"','""')+'" FROM "version" LIMIT 5')]
report['database']['journalMode']=connection.execute('PRAGMA journal_mode').fetchone()[0]
connection.close()
def go_build(path):
try:
with open(path,'rb') as f, mmap.mmap(f.fileno(),0,access=mmap.ACCESS_READ) as data:
at=data.find(b'\xff Go buildinf:')
if at<0:return {'format':'not-found'}
ptr_size=data[at+14];flags=data[at+15]
if not flags&2:return {'format':'pointer-layout','pointerSize':ptr_size,'flags':flags}
pos=at+32
def readvar(pos):
value=0;shift=0
for i in range(10):
b=data[pos];pos+=1;value|=(b&127)<<shift
if b<128:return value,pos
shift+=7
raise ValueError('invalid-varint')
size,pos=readvar(pos);version=bytes(data[pos:pos+size]).decode(errors='replace');pos+=size
size,pos=readvar(pos);mod=bytes(data[pos:pos+size]).decode(errors='replace')
selected=[]
for line in mod.splitlines():
if line.startswith('path\t') and ('gitea' in line or 'runner' in line): selected.append(line[:300])
elif line.startswith('mod\t') and ('gitea' in line or 'runner' in line): selected.append(line[:300])
elif line.startswith('build\tvcs'): selected.append(line[:200])
return {'format':'inline','goVersion':version,'selectedModuleFields':selected}
except Exception as e:return {'errorClass':type(e).__name__}
report['giteaGoBuild']=go_build('/usr/local/bin/gitea')
report['runnerGoBuild']=go_build('/opt/gitea-runner/act-runner')
for p in ['/home/git/gitea/custom','/home/git/gitea/config','/usr/local/bin/data','/opt/gitea-runner']:
root=pathlib.Path(p)
if not root.is_dir():continue
children=[]
for child in sorted(root.iterdir()):
if child.name.startswith('.') or any(x in child.name.lower() for x in ['secret','token','password','credential','private']):continue
if p=='/opt/gitea-runner' and child.name not in ['config.yaml','config.yml','act-runner','act_runner']:continue
children.append({'name':child.name,'directory':child.is_dir(),'bytes':child.stat().st_size})
report.setdefault('layoutMetadata',[]).append({'path':p,'children':children[:30]})
report['boundaries']={'serviceChanges':False,'dataWrites':False,'secretValuesPrinted':False,'backupsOrUpgrades':False}
print('RUNNER_MAINTENANCE_PREFLIGHT_JSON '+json.dumps(report,separators=(',',':')))
PY
shell: bash
timeout-minutes: "5"
permissions:
contents: read
...
|
preflight
|
null
|
["ubuntu-latest"]
|
37480
|
1
|
1793520610
|
1793520612
|
1793520609
|
1793520612
|
|
0
|
|
0
|
Edit
Delete
|
|
46008
|
26750
|
116
|
5
|
b2cff86c1cba9e7f9bc40991ed1abc1e73cc281c
|
0
|
Read existing Gitea and runner service layout
|
1
|
name: Runner maintenance inventory (read only)
name: Runner maintenance inventory (read only)
"on":
push:
branches: [codex/runner-maintenance-diagnostic-20261101]
paths: [.gitea/workflows/runner-maintenance-inventory.yml]
workflow_dispatch:
jobs:
inventory:
name: Read existing Gitea and runner service layout
runs-on: ubuntu-latest
if: github.repository == 'luoanwu/enterprise-platform'
steps:
- name: Read whitelisted deployment metadata without secret output
run: |
set -eu
node <<'NODE'
const fs = require('node:fs');
const path = require('node:path');
const os = require('node:os');
const { spawnSync } = require('node:child_process');
if (process.getuid?.() !== 0 || os.hostname() !== 'iZ2zed3pt2nygf9dy1a9d2Z') throw new Error('EXPECTED_RUNNER_HOST_IDENTITY_REQUIRED');
const commands = [];
function run(command, args) {
const allowed = ['systemctl','sha256sum','df','ps','go','git','getent'];
const versionOnly = path.isAbsolute(command) && args.length === 1 && args[0] === '--version';
if (!allowed.includes(command) && !versionOnly) throw new Error('READ_ONLY_COMMAND_NOT_ALLOWED');
const r = spawnSync(command, args, { encoding:'utf8', timeout:8000, maxBuffer:256*1024, input:'' });
// Never log argv/stdout: startup definitions may contain sensitive values.
commands.push({ command: path.isAbsolute(command) ? path.basename(command) : command, exitCode:r.status, errorCode:r.error?.code ?? null });
return { ok:r.status === 0, stdout:r.stdout ?? '' };
}
function publicPath(p) {
return typeof p === 'string' && p.length <= 400 && !/[\r\n]/.test(p) && !/secret|credential|token|password|private/i.test(p) ? p : 'REDACTED_OR_INVALID_PATH';
}
function meta(p) {
try { const s=fs.statSync(p);return {path:publicPath(p),exists:true,uid:s.uid,gid:s.gid,mode:(s.mode&0o777).toString(8),bytes:s.size,directory:s.isDirectory()}; }
catch { return {path:publicPath(p),exists:false}; }
}
function link(p) { try{return fs.readlinkSync(p);}catch{return null;} }
function splitArgs(s) { return (s.match(/"[^"]*"|'[^']*'|[^\s]+/g) ?? []).map(x=>x.replace(/^(['"])(.*)\1$/,'$2')); }
function flag(args,names) {
for(let i=0;i<args.length;i++) for(const n of names) {
if(args[i]===n) return args[i+1] ?? null;
if(args[i].startsWith(n+'=')) return args[i].slice(n.length+1);
}
return null;
}
const report={kind:'RunnerMaintenanceServiceInventory',schemaVersion:1,observedAtUTC:new Date().toISOString(),
releaseEligible:false,ci:{sourceSha:process.env.GITHUB_SHA??null,runId:process.env.GITHUB_RUN_ID??null},
hostname:os.hostname(),services:[],configs:[],paths:[],sourceTrees:[],tools:{},commands};
const units=['gitea.service','gitea-runner.service','gitea-ext.service','gitea-webapp.service','gitea-webhook.service'];
let giteaArgs=[],giteaBase=null,giteaConfig=null,runnerDir=null;
for(const unit of units) {
const r=run('systemctl',['show',unit,'--no-pager','--property=User,WorkingDirectory,ExecStart,MainPID,FragmentPath,RootDirectory,RootImage']);
if(!r.ok){report.services.push({unit,querySucceeded:false});continue;}
const p={};
for(const l of r.stdout.split('\n')) {const i=l.indexOf('=');if(i>=0)p[l.slice(0,i)]=l.slice(i+1);}
const start=p.ExecStart??'';
const executable=/\bpath=([^ ;]+)\s*;/.exec(start)?.[1] ?? null;
const argv=/argv\[\]=(.*?)\s*;\s*ignore_errors=/s.exec(start)?.[1] ?? '';
const args=splitArgs(argv);
const pid=/^[1-9][0-9]*$/.test(p.MainPID??'')?p.MainPID:null;
const actualExe=pid?link('/proc/'+pid+'/exe'):null;
const actualCwd=pid?link('/proc/'+pid+'/cwd'):null;
const item={unit,user:p.User??null,workingDirectory:publicPath(p.WorkingDirectory??''),
unitFile:publicPath(p.FragmentPath??''),rootDirectory:publicPath(p.RootDirectory??''),
rootImage:publicPath(p.RootImage??''),executable:executable?publicPath(executable):null,
processExecutable:actualExe?publicPath(actualExe):null,processWorkingDirectory:actualCwd?publicPath(actualCwd):null,
scripts:args.filter(x=>path.isAbsolute(x)&&/\.(?:py|js|mjs|cjs|sh)$/.test(x)).map(publicPath),
configFlag:flag(args,['--config','-c'])?publicPath(flag(args,['--config','-c'])):null};
if(unit==='gitea.service'||unit==='gitea-runner.service') {
const binary=actualExe && !actualExe.endsWith(' (deleted)')?actualExe:executable;
if(binary&&path.isAbsolute(binary)&&fs.existsSync(binary)){
const sum=run('sha256sum',[binary]);item.binarySha256=sum.ok?sum.stdout.match(/^[a-f0-9]{64}/)?.[0]??null:null;
const version=run(binary,['--version']);item.version=version.ok?version.stdout.trim().slice(0,400):null;
const go=run('go',['version','-m',binary]);
item.goBuild=go.ok?go.stdout.split('\n').map(l=>l.trim()).filter(l=>/^(?:path|mod)\s+(?:code\.gitea\.io|github\.com\/go-gitea|gitea\.com\/gitea)/.test(l)||/^build\s+vcs(?:\.revision|\.time|\.modified)?=/.test(l)):null;
}
}
report.services.push(item);
if(unit==='gitea.service'){
giteaArgs=args;giteaBase=flag(args,['--work-path','-w'])||actualCwd||p.WorkingDirectory||path.dirname(executable??'/');
giteaConfig=flag(args,['--config','-c']);
}
if(unit==='gitea-runner.service')runnerDir=actualCwd||p.WorkingDirectory;
}
if(giteaConfig && !path.isAbsolute(giteaConfig))giteaConfig=path.resolve(giteaBase,giteaConfig);
const configCandidates=[giteaConfig,path.join(giteaBase||'/nonexistent','custom/conf/app.ini'),
'/etc/gitea/app.ini','/home/git/gitea/custom/conf/app.ini','/home/git/gitea/conf/app.ini'].filter(Boolean);
const safeKeys={
database:['DB_TYPE','HOST','NAME','USER','PATH','SCHEMA','SSL_MODE','CHARSET'],
server:['APP_DATA_PATH','ROOT_URL','DOMAIN','HTTP_ADDR','HTTP_PORT','SSH_DOMAIN','SSH_PORT','START_SSH_SERVER','LOCAL_ROOT_URL'],
repository:['ROOT'],actions:['ENABLED','DEFAULT_ACTIONS_URL','MAX_RERUN_ATTEMPTS','ARTIFACT_RETENTION_DAYS'],
log:['MODE','ROOT_PATH'],
};
const storageKeys=['STORAGE_TYPE','PATH','MINIO_ENDPOINT','MINIO_BUCKET','MINIO_BASE_PATH','MINIO_LOCATION','MINIO_USE_SSL','SERVE_DIRECT'];
const configPaths=new Set();
for(const p of [...new Set(configCandidates)]) {
report.paths.push(meta(p));
if(!fs.existsSync(p)||!fs.statSync(p).isFile()||fs.statSync(p).size>1024*1024)continue;
if(/\/(?:\.secrets|\.evidence-private)\//.test(p))throw new Error('RESTRICTED_PATH_REFUSED');
const ini=fs.readFileSync(p,'utf8');let section='DEFAULT';const selected={};
for(const line of ini.split('\n')) {
const s=line.trim();if(!s||s.startsWith(';')||s.startsWith('#'))continue;
const h=/^\[([^\]]+)\]$/.exec(s);if(h){section=h[1].toLowerCase();continue;}
const m=/^([A-Za-z0-9_]+)\s*=\s*(.*)$/.exec(s);if(!m)continue;
const key=m[1].toUpperCase();
const storage=/^(?:storage(?:\.|$)|lfs$|attachment$|packages$)/.test(section);
if(!(safeKeys[section]??[]).includes(key)&&!(storage&&storageKeys.includes(key)))continue;
let value=m[2].trim().replace(/^(['"])(.*)\1$/,'$2');
if(/PASSW|TOKEN|SECRET|CREDENTIAL|PRIVATE_KEY/i.test(value)){value='REDACTED_SENSITIVE_REFERENCE';}
else if(value.includes('@')){value=value.replace(/[^/@\s]+@/g,'REDACTED@');}
(selected[section]??={})[key]=value.slice(0,500);
if((key==='PATH'||key==='ROOT'||key==='APP_DATA_PATH'||key==='ROOT_PATH')&&path.isAbsolute(value))configPaths.add(value);
}
const sum=run('sha256sum',[p]);
report.configs.push({path:publicPath(p),sha256:sum.ok?sum.stdout.match(/^[a-f0-9]{64}/)?.[0]??null:null,selectedNonSecretFields:selected});
}
for(const p of configPaths)report.paths.push(meta(p));
if(runnerDir){report.runnerWorkingDirectory=publicPath(runnerDir);report.runnerRegistrationMetadataOnly=meta(path.join(runnerDir,'.runner'));}
for(const dir of [...new Set([giteaBase,runnerDir].filter(Boolean))]){
const git=run('git',['-C',dir,'rev-parse','--show-toplevel']);
if(!git.ok)continue;
const root=git.stdout.trim();const head=run('git',['-C',root,'rev-parse','HEAD']);
const status=run('git',['--no-optional-locks','-C',root,'status','--porcelain','--untracked-files=no']);
report.sourceTrees.push({root:publicPath(root),head:head.ok?head.stdout.trim():null,trackedDirty:status.ok?status.stdout.trim().length>0:null});
}
const processes=run('ps',['-eo','pid=,comm=']);
report.relevantProcessNames=processes.ok?processes.stdout.split('\n').map(l=>l.trim().split(/\s+/)).filter(a=>/^(?:postgres|mysqld|mariadbd|sqlite3|gitea|act_runner|gitea-runner|nginx)$/.test(a[1]??'')).map(a=>({pid:Number(a[0]),command:a[1]})).slice(0,30):[];
const disk=run('df',['-Pk','/']);
report.rootFilesystem=disk.ok?disk.stdout.trim().split('\n').slice(-1)[0].trim().split(/\s+/).slice(1,6):null;
for(const tool of ['pg_dump','pg_restore','mysqldump','mariadb-dump','sqlite3','python3','tar','unzip','curl','git','go']){
report.tools[tool]=['/usr/bin/','/usr/local/bin/','/bin/'].map(p=>p+tool).find(p=>fs.existsSync(p))??null;
}
report.boundaries={configBodyPrinted:false,secretValuesPrinted:false,environmentRead:false,registrationBodyRead:false,serviceChanges:false,backupsOrUpgrades:false};
report.conclusion='READ_ONLY_INVENTORY_ONLY: target/storage/backup-restore and fixed-version compatibility still require verification before maintenance';
console.log('RUNNER_MAINTENANCE_INVENTORY_JSON '+JSON.stringify(report));
NODE
shell: bash
timeout-minutes: "4"
permissions:
contents: read
...
|
inventory
|
null
|
["ubuntu-latest"]
|
37478
|
1
|
1793520334
|
1793520336
|
1793520334
|
1793520336
|
|
0
|
|
0
|
Edit
Delete
|
|
46007
|
26749
|
116
|
5
|
1a6575250e19bdc11d9cb8e08539b1dd7fd86a9b
|
0
|
Observe existing runner maintenance boundary
|
1
|
name: Runner maintenance access diagnostic (read o name: Runner maintenance access diagnostic (read only)
"on":
push:
branches: [codex/runner-maintenance-diagnostic-20261101]
paths: [.gitea/workflows/runner-maintenance-diagnostic.yml]
workflow_dispatch:
jobs:
observe:
name: Observe existing runner maintenance boundary
runs-on: ubuntu-latest
if: github.repository == 'luoanwu/enterprise-platform'
steps:
- name: Read execution identity and existing service metadata
run: |
set -eu
if ! command -v node >/dev/null 2>&1; then
echo 'RUNNER_MAINTENANCE_READONLY_UNAVAILABLE node-not-installed'
exit 2
fi
node <<'NODE'
const fs = require('node:fs');
const os = require('node:os');
const { spawnSync } = require('node:child_process');
const commands = [];
function run(command, args) {
const allowed = { systemctl: ['list-unit-files', 'show'], docker: ['version', 'info', 'ps', 'inspect'] };
const sudoListOnly = command === 'sudo' && JSON.stringify(args) === '["-n","-l"]';
if (!sudoListOnly && !allowed[command]?.includes(args[0])) throw new Error('READ_ONLY_OPERATION_NOT_ALLOWED');
const r = spawnSync(command, args, { encoding: 'utf8', timeout: 5000, maxBuffer: 256 * 1024, input: '' });
commands.push({ command, args, exitCode: r.status, errorCode: r.error?.code ?? null });
return { ok: r.status === 0, stdout: r.stdout ?? '' };
}
function safePath(value) {
if (typeof value !== 'string' || value.length > 300 || /[\r\n]/.test(value)) return 'UNAVAILABLE';
return /secret|credential|token|password|private/i.test(value) ? 'REDACTED_PRIVATE_PATH' : value;
}
function metadata(path) {
try {
const stat = fs.statSync(path);
const access = mode => { try { fs.accessSync(path, mode); return true; } catch { return false; } };
return { path, exists: true, uid: stat.uid, gid: stat.gid, mode: (stat.mode & 0o777).toString(8),
readable: access(fs.constants.R_OK), writable: access(fs.constants.W_OK),
socket: stat.isSocket(), directory: stat.isDirectory() };
} catch { return { path, exists: false }; }
}
function publicFile(path) {
try { return fs.readFileSync(path, 'utf8').slice(0, 8192); } catch { return null; }
}
const report = {
kind: 'RunnerMaintenanceReadOnlyDiagnostic', schemaVersion: 1, observedAtUTC: new Date().toISOString(),
releaseEligible: false, purpose: 'Execution-boundary and existing maintenance-capability observation only',
ci: { repository: process.env.GITHUB_REPOSITORY ?? null, sourceSha: process.env.GITHUB_SHA ?? null,
runId: process.env.GITHUB_RUN_ID ?? null, rawGithubRunAttempt: process.env.GITHUB_RUN_ATTEMPT ?? null,
rawGiteaRunAttempt: process.env.GITEA_RUN_ATTEMPT ?? null, runnerName: process.env.RUNNER_NAME ?? null },
execution: { hostname: os.hostname(), platform: os.platform(), arch: os.arch(),
uid: process.getuid?.(), gid: process.getgid?.(), groups: process.getgroups?.(),
rootInExecutionContext: process.getuid?.() === 0,
dockerMarker: fs.existsSync('/.dockerenv'), containerEnvMarker: fs.existsSync('/run/.containerenv'),
pid1Command: publicFile('/proc/1/comm')?.trim() ?? null,
uidMap: publicFile('/proc/self/uid_map')?.trim() ?? null },
commonPathMetadataOnly: [
'/var/run/docker.sock', '/run/docker.sock', '/etc/gitea/app.ini',
'/data/gitea/conf/app.ini', '/var/lib/gitea', '/data/gitea', '/data/git/repositories',
'/etc/act_runner/config.yaml', '/etc/gitea-runner/config.yaml',
].map(metadata),
services: [], docker: { accessible: false, relatedContainers: [] },
nonInteractiveSudo: { listingAvailable: false, unrestrictedRuleObserved: false, relevantCommandNames: [] },
};
const osRelease = publicFile('/etc/os-release');
report.execution.osName = osRelease?.match(/^PRETTY_NAME="?([^"\n]+)"?$/m)?.[1] ?? null;
const units = run('systemctl', ['list-unit-files', '--type=service', '--no-legend', '--no-pager']);
if (units.ok) {
const names = units.stdout.split('\n').map(l => l.trim().split(/\s+/)[0])
.filter(n => /^(?:gitea|act[-_]?runner|gitea[-_]?runner)[\w@.-]*\.service$/i.test(n)).slice(0, 20);
for (const name of names) {
const r = run('systemctl', ['show', name, '--no-pager', '--property=LoadState,ActiveState,FragmentPath,User']);
const properties = {};
if (r.ok) for (const line of r.stdout.split('\n')) {
const m = /^(LoadState|ActiveState|FragmentPath|User)=(.*)$/.exec(line);
if (m) properties[m[1]] = m[1] === 'FragmentPath' ? safePath(m[2]) : m[2].slice(0, 150);
}
report.services.push({ name, querySucceeded: r.ok, properties });
}
}
const sudo = run('sudo', ['-n', '-l']);
report.nonInteractiveSudo.listingAvailable = sudo.ok;
if (sudo.ok) {
report.nonInteractiveSudo.unrestrictedRuleObserved = /^\s*\([^)\n]+\)\s*(?:NOPASSWD:\s*)?ALL\s*$/m.test(sudo.stdout);
report.nonInteractiveSudo.relevantCommandNames = ['systemctl','docker','podman','pg_dump','pg_restore','mysqldump','gitea','act_runner','gitea-runner']
.filter(n => sudo.stdout.includes(n));
}
const version = run('docker', ['version', '--format', '{{.Server.Version}}']);
report.docker.accessible = version.ok;
report.docker.serverVersion = version.ok ? version.stdout.trim().slice(0, 80) : null;
if (version.ok) {
const info = run('docker', ['info', '--format', '{{json .Name}}']);
if (info.ok) { try { report.docker.daemonName = JSON.parse(info.stdout); } catch {} }
const list = run('docker', ['ps', '-a', '--format', '{{.ID}}\t{{.Names}}\t{{.Image}}\t{{.Status}}']);
if (list.ok) for (const line of list.stdout.split('\n')) {
const [id, name, image, status] = line.split('\t');
if (!/^[a-f0-9]{12,64}$/.test(id ?? '') || !/gitea|act[-_]?runner|gitea[-_]?runner/i.test((name ?? '') + ' ' + (image ?? ''))) continue;
if (report.docker.relatedContainers.length >= 15) break;
const container = { id, name, image, status };
const mounts = run('docker', ['inspect', '--format', '{{json .Mounts}}', id]);
if (mounts.ok) {
try { container.mounts = JSON.parse(mounts.stdout).map(m => ({
type: m.Type, source: safePath(m.Source), destination: safePath(m.Destination), rw: m.RW,
})); } catch { container.mounts = 'UNPARSEABLE'; }
}
const workingDir = run('docker', ['inspect', '--format', '{{index .Config.Labels "com.docker.compose.project.working_dir"}}', id]);
if (workingDir.ok) container.composeWorkingDirectory = safePath(workingDir.stdout.trim());
const configFiles = run('docker', ['inspect', '--format', '{{index .Config.Labels "com.docker.compose.project.config_files"}}', id]);
if (configFiles.ok) container.composeConfigFiles = safePath(configFiles.stdout.trim());
report.docker.relatedContainers.push(container);
}
}
report.commands = commands;
report.boundaries = {
repositorySecretsRequested: false, environmentDumped: false, secretConfigContentsRead: false,
dockerEnvironmentInspected: false, privilegedServiceCommandExecuted: false, dockerMutationExecuted: false,
credentialOrPermissionChanges: false, arbitraryNetworkProbes: false, backupsOrServiceChanges: false,
contextRootOrDockerAccessIsNotProofOfGiteaHostOwnership: true,
};
report.conclusion = 'DIAGNOSTIC_ONLY: maintenance admission requires verified target, authorized service control, complete storage inventory and tested backup/restore; unknowns are not passes';
console.log('RUNNER_MAINTENANCE_READONLY_JSON ' + JSON.stringify(report));
NODE
shell: bash
timeout-minutes: "4"
permissions:
contents: read
...
|
observe
|
null
|
["ubuntu-latest"]
|
37471
|
1
|
1793520113
|
1793520116
|
1793519845
|
1793520116
|
|
0
|
|
0
|
Edit
Delete
|
|
46254
|
26842
|
116
|
5
|
1e93ce65273c624562e316cf3d2cde5750f502cc
|
0
|
export
|
1
|
name: Review HTTP custom helpers with opaque liter name: Review HTTP custom helpers with opaque literals redacted
"on":
push:
branches: [codex/runner-maintenance-diagnostic-20261101]
paths: [.gitea/workflows/gitea-http-lint-review.yml]
workflow_dispatch:
jobs:
export:
name: export
runs-on: ubuntu-latest
if: github.repository == 'luoanwu/enterprise-platform'
steps:
- name: Screen only known source changes and produce a review archive
run: |
set -eu
python3 <<'PY'
import base64, datetime, hashlib, io, json, os, pathlib, re, socket, subprocess, tarfile
if os.geteuid()!=0 or socket.gethostname()!='iZ2zed3pt2nygf9dy1a9d2Z':raise RuntimeError('EXPECTED_HOST_REQUIRED')
os.umask(0o077)
runid=os.environ.get('GITHUB_RUN_ID')
if not runid or not runid.isdigit():raise RuntimeError('REAL_RUN_ID_REQUIRED')
base=pathlib.Path('/var/lib/eios-r1-maintenance')
if base.is_symlink() or not base.is_dir() or base.stat().st_uid!=0 or base.stat().st_mode&0o077:raise RuntimeError('PRIVATE_BASE_REQUIRED')
analysis=base/'analysis-26765'
receipt=json.loads((analysis/'receipt.json').read_text())
if receipt.get('status')!='PRIVATE_BASELINE_VERIFIED_AND_COMPATIBILITY_ANALYZED':raise RuntimeError('VERIFIED_BASELINE_REQUIRED')
source=base/'reviewed-26829'/'candidate-source'
live=pathlib.Path('/root/gitea-src')
upstream=analysis/'candidate-source'
env={'PATH':'/usr/local/bin:/usr/bin:/bin','HOME':str(base),'LANG':'C.UTF-8','GIT_CONFIG_NOSYSTEM':'1','GIT_CONFIG_GLOBAL':'/dev/null','GIT_OPTIONAL_LOCKS':'0','GIT_TERMINAL_PROMPT':'0'}
def git(path,*args):
r=subprocess.run(['git','--no-optional-locks','-c','core.hooksPath=/dev/null','-C',str(path),*args],capture_output=True,timeout=30,env=env)
return r.returncode,r.stdout
code,head=git(upstream,'rev-parse','HEAD')
if code or head.decode().strip()!='146cc3eec57174711eac0e0a0c7b38670c6e3922':raise RuntimeError('FIXED_OFFICIAL_SOURCE_REQUIRED')
# Export only files that require manual conflict or migration review.
# Already textually merged files and additive files stay on the server for native testing.
paths=['routers/web/repo/githttp.go']
def public_literals(text):
return set(re.findall(r'["\']([^"\'\n]{8,})["\']',text))
def suspicious(text,public,is_git_test=False):
issues=[]
for m in re.finditer(r'-----BEGIN (?:RSA |EC |OPENSSH )?PRIVATE KEY-----|(?:ghp_|github_pat_|AKIA[0-9A-Z]{16}|sk-(?:proj-)?)[A-Za-z0-9_/-]{12,}',text):
public_ssh=bool(re.match(r'sk-(?:ecdsa-sha2-nistp256|ssh-ed25519)@openssh\.com',text[m.start():])) and any(m.group(0) in v for v in public)
if not public_ssh:issues.append('credential-prefix-or-private-key')
if re.search(r'https?://[^/\s:@]+:[^/\s@]+@',text):issues.append('credential-in-url')
for match in re.finditer(r'(?i)(?:[A-Za-z0-9_]*(?:password|passwd|secret|token|api_?key|access_?key|private_?key)[A-Za-z0-9_]*)\s*(?::=|=|:)\s*["\']([^"\'\n]{8,})["\']',text):
value=match.group(1)
if value not in public:issues.append('nonpublic-credential-literal')
for match in re.finditer(r'(?i)(?:Bearer|Basic)\s+[A-Za-z0-9_+/=.-]{12,}',text):
if not any(match.group(0) in literal for literal in public):issues.append('nonpublic-authorization-literal')
for value in re.findall(r'["\']([A-Za-z0-9+/=_-]{32,})["\']',text):
if value not in public and len(set(value))>8 and not is_git_test:issues.append('nonpublic-long-opaque-literal')
if not is_git_test:
for value in re.findall(r'["\']([A-Fa-f0-9]{32,128})["\']',text):
if value not in public and len(set(value))>3:issues.append('nonpublic-long-hex-literal')
return sorted(set(issues))
payload={};manifest=[];refused=[]
for name in paths:
rel=pathlib.PurePosixPath(name)
if rel.is_absolute() or '..' in rel.parts or not rel.parts or rel.parts[0] not in ['models','routers','services','modules','templates','options'] or rel.suffix not in ['.go','.tmpl','.json']:raise RuntimeError('SOURCE_EXPORT_PATH_REFUSED')
p=source/name
if p.is_symlink() or not p.is_file():raise RuntimeError('SOURCE_EXPORT_NONREGULAR_FILE')
data=p.read_bytes()
if len(data)>2*1024*1024:raise RuntimeError('SOURCE_FILE_SIZE_LIMIT')
rc,public=git(upstream,'show','HEAD:'+name)
known=public_literals(public.decode(errors='replace')) if rc==0 else set()
# Never export authorization-looking comment text, even when it is likely prose.
text=data.decode('utf-8')
text=re.sub(r'(?m)^(\s*//[^\n]*?)(?i:Basic|Bearer)\s+[A-Za-z0-9_+/=.-]{12,}[^\n]*$',r'\1[REDACTED_AUTHORIZATION_LOOKING_COMMENT]',text)
# Review copy only: every nonpublic opaque literal is removed before unchanged screening.
original_sha=hashlib.sha256(data).hexdigest()
redactions=[]
def redact(match):
value=match.group(1)
if value in known or len(set(value))<=8:return match.group(0)
redactions.append({'line':text.count('\n',0,match.start())+1,'length':len(value)})
return '"[REDACTED_NONPUBLIC_LITERAL]"'
text=re.sub(r'["\']([A-Za-z0-9+/=_-]{32,})["\']',redact,text)
print('GITEA_HTTP_REDACTIONS_JSON '+json.dumps({'path':name,'originalSha256':original_sha,'reviewCopyOnly':True,'redactions':redactions}),flush=True)
data=text.encode('utf-8')
candidates=[(name,data)]
for archive_name,body in candidates:
problems=suspicious(body.decode('utf-8'),known,name.startswith('modules/git/') and name.endswith('_test.go'))
if problems:
text=body.decode('utf-8');classification=[]
for m in re.finditer(r'-----BEGIN (?:RSA |EC |OPENSSH )?PRIVATE KEY-----|(?:ghp_|github_pat_|AKIA[0-9A-Z]{16}|sk-(?:proj-)?)[A-Za-z0-9_/-]{12,}',text):
classification.append({'category':'prefix','publicSshAlgorithm':bool(re.match(r'sk-(?:ecdsa-sha2-nistp256|ssh-ed25519)@openssh\.com',text[m.start():])),'presentInOfficialLiteral':any(m.group(0) in v for v in known)})
for m in re.finditer(r'(?i)(Bearer|Basic)\s+([A-Za-z0-9_+/=.-]{12,})',text):
line=text[text.rfind('\n',0,m.start())+1:m.start()]
valid=False
if m.group(1).lower()=='basic':
try:
decoded=base64.b64decode(m.group(2)+'='*((-len(m.group(2)))%4),validate=True).decode('utf-8')
valid=':' in decoded and decoded.isprintable()
except Exception:pass
classification.append({'category':'authorization','scheme':m.group(1).lower(),'commentLine':line.lstrip().startswith('//'),'validPrintableBasicCredential':valid,'presentInOfficialLiteral':any(m.group(0) in v for v in known)})
for value in re.findall(r'["\']([A-Za-z0-9+/=_-]{32,})["\']',text):
if value not in known and len(set(value))>8 and not (name.startswith('modules/git/') and name.endswith('_test.go')):
classification.append({'category':'opaque','publicGitSmartHttpMime':value in ['application/x-git-upload-pack-advertisement','application/x-git-receive-pack-advertisement','application/x-git-upload-pack-result','application/x-git-receive-pack-result']})
refused.append({'path':archive_name,'reasons':problems,'classification':classification});continue
payload[archive_name]=body
manifest.append({'path':archive_name,'bytes':len(body),'sha256':hashlib.sha256(body).hexdigest()})
report={'kind':'GiteaCustomSourceReviewExport','observedAtUTC':datetime.datetime.now(datetime.timezone.utc).isoformat(),'ci':{'sourceSha':os.environ.get('GITHUB_SHA'),'runId':runid},'releaseEligible':False,'sourceBaselineFingerprint':receipt['sourceFingerprint'],'upstreamCommit':head.decode().strip(),'serviceChanges':False,'databaseOrConfigurationExported':False,'registrationOrCredentialFilesRead':False,'refusedFiles':refused}
if refused:
report['status']='BLOCKED_CREDENTIAL_SCREENING';print('GITEA_CUSTOM_SOURCE_EXPORT_JSON '+json.dumps(report,separators=(',',':')));raise SystemExit(1)
if sum(len(v) for v in payload.values())>8*1024*1024:raise RuntimeError('SOURCE_EXPORT_SIZE_LIMIT')
work=base/('export-'+runid);work.mkdir(mode=0o700)
index={'kind':'GiteaCustomSourceReviewManifest','sourceBaselineFingerprint':receipt['sourceFingerprint'],'originalSourceHead':'287bf811487c04cd90bcf07aa04905cbaf2e4c2d','upstreamCommit':head.decode().strip(),'files':manifest,'scope':'Selected code only, not a system backup; no database, config, registration, environment, key or dependency cache files'}
payload['manifest.json']=(json.dumps(index,ensure_ascii=False,indent=2)+'\n').encode()
target=work/'gitea-custom-source.tar.gz'
with tarfile.open(target,'w:gz') as archive:
for name,body in sorted(payload.items()):
entry=tarfile.TarInfo(name);entry.size=len(body);entry.mode=0o600;entry.mtime=0;archive.addfile(entry,io.BytesIO(body))
report.update({'status':'SOURCE_CODE_SCREENED_AND_PACKED','fileCount':len(manifest),'archiveBytes':target.stat().st_size,'archiveSha256':hashlib.sha256(target.read_bytes()).hexdigest(),'privateArchive':str(target)})
(work/'receipt.json').write_text(json.dumps(report,indent=2)+'\n')
print('GITEA_CUSTOM_SOURCE_EXPORT_JSON '+json.dumps(report,separators=(',',':')))
for name,body in sorted(payload.items()):
if name=='manifest.json':continue
encoded=base64.b64encode(body).decode()
chunks=[encoded[i:i+8000] for i in range(0,len(encoded),8000)]
for i,chunk in enumerate(chunks):
print('GITEA_GIT_SOURCE_FILE_JSON '+json.dumps({'path':name,'sha256':hashlib.sha256(body).hexdigest(),'index':i,'chunks':len(chunks),'data':chunk},separators=(',',':')),flush=True)
PY
shell: bash
timeout-minutes: "15"
permissions:
contents: read
...
|
export
|
null
|
["ubuntu-latest"]
|
37658
|
1
|
1793533951
|
1793533951
|
1793533943
|
1793533951
|
|
0
|
|
0
|
Edit
Delete
|
|
46270
|
26851
|
116
|
5
|
1a992ce3f8423df3d2ddfffd0d3a7527cb027806
|
0
|
validate
|
1
|
name: Revalidate same fixed checkout after native name: Revalidate same fixed checkout after native lint timeout
"on":
push:
branches: [codex/runner-maintenance-diagnostic-20261101]
paths: [.gitea/workflows/gitea-reviewed-revalidation.yml]
workflow_dispatch:
jobs:
validate:
name: validate
runs-on: ubuntu-latest
if: github.repository == 'luoanwu/enterprise-platform'
steps:
- name: Validate fixed reviewed inputs in private resource limited units
run: |
set -eu
python3 <<'PY'
import os,re,subprocess,json
maintenanceLoaderSha=os.environ.get('GITHUB_SHA','')
if not re.fullmatch('[0-9a-f]{40}',maintenanceLoaderSha):raise RuntimeError('FIXED_SHA_REQUIRED')
command=['git','-c','safe.directory=/home/git/repositories/luoanwu/enterprise-platform.git','-c','core.hooksPath=/dev/null','-C','/home/git/repositories/luoanwu/enterprise-platform.git','show',maintenanceLoaderSha+':ops/gitea-r1/revalidate-reviewed-checkout.py']
result=subprocess.run(command,capture_output=True,timeout=30)
if result.returncode:raise RuntimeError('FIXED_SCRIPT_UNAVAILABLE')
try:exec(compile(result.stdout,'revalidate-reviewed-checkout.py','exec'))
except Exception as error:
message=str(error)
safe=message if isinstance(error,RuntimeError) and re.fullmatch(r'[A-Z_]+(?::[A-Za-z0-9_./,:]+)?',message) else type(error).__name__
print('GITEA_REVIEWED_VALIDATION_JSON '+json.dumps({'kind':'GiteaReviewedCandidateValidation','ci':{'sourceSha':maintenanceLoaderSha,'runId':os.environ.get('GITHUB_RUN_ID')},'status':'FAILED_PREPARATION','error':safe,'releaseEligible':False,'liveServicesChanged':False,'liveDatabaseChanged':False}),flush=True)
raise SystemExit(1)
PY
shell: bash
timeout-minutes: "95"
permissions:
contents: read
...
|
validate
|
null
|
["ubuntu-latest"]
|
37675
|
1
|
1793536584
|
1793536760
|
1793536584
|
1793536760
|
|
0
|
|
0
|
Edit
Delete
|
|
46317
|
26877
|
116
|
5
|
f3e9ea997fb1a6076a27af3f36ce7af1407d8c83
|
0
|
validate
|
1
|
name: Retain source history and reclaim named rebu name: Retain source history and reclaim named rebuildable caches for full recovery
"on":
push:
branches: [codex/runner-maintenance-diagnostic-20261101]
paths: [.gitea/workflows/gitea-window-capacity-reclaim.yml]
workflow_dispatch:
jobs:
validate:
name: validate
runs-on: ubuntu-latest
if: github.repository == 'luoanwu/enterprise-platform'
steps:
- name: Validate fixed reviewed inputs in private resource limited units
run: |
set -eu
python3 <<'PY'
import os,re,subprocess,json
maintenanceLoaderSha=os.environ.get('GITHUB_SHA','')
if not re.fullmatch('[0-9a-f]{40}',maintenanceLoaderSha):raise RuntimeError('FIXED_SHA_REQUIRED')
command=['git','-c','safe.directory=/home/git/repositories/luoanwu/enterprise-platform.git','-c','core.hooksPath=/dev/null','-C','/home/git/repositories/luoanwu/enterprise-platform.git','show',maintenanceLoaderSha+':ops/gitea-r1/reclaim-window-capacity.py']
result=subprocess.run(command,capture_output=True,timeout=30)
if result.returncode:raise RuntimeError('FIXED_SCRIPT_UNAVAILABLE')
try:exec(compile(result.stdout,'reclaim-window-capacity.py','exec'))
except Exception as error:
message=str(error)
safe=message if isinstance(error,RuntimeError) and re.fullmatch(r'[A-Z_]+(?::[A-Za-z0-9_./,:]+)?',message) else type(error).__name__
print('GITEA_WINDOW_CAPACITY_RECLAIM_JSON '+json.dumps({'kind':'GiteaWindowCapacityReclaimed','ci':{'sourceSha':maintenanceLoaderSha,'runId':os.environ.get('GITHUB_RUN_ID')},'status':'FAILED_PREPARATION','error':safe,'releaseEligible':False,'liveServicesChanged':False,'liveDatabaseChanged':False}),flush=True)
raise SystemExit(1)
PY
shell: bash
timeout-minutes: "95"
permissions:
contents: read
...
|
validate
|
null
|
["ubuntu-latest"]
|
37714
|
2
|
1793544912
|
1793544930
|
1793544750
|
1793544931
|
|
0
|
|
0
|
Edit
Delete
|
|
46319
|
26879
|
116
|
5
|
0cbe544a1aa3cce37509daf46abc260806375a77
|
0
|
validate
|
1
|
name: Retain source history and reclaim named rebu name: Retain source history and reclaim named rebuildable caches for full recovery
"on":
push:
branches: [codex/runner-maintenance-diagnostic-20261101]
paths: [.gitea/workflows/gitea-window-capacity-reclaim.yml]
workflow_dispatch:
jobs:
validate:
name: validate
runs-on: ubuntu-latest
if: github.repository == 'luoanwu/enterprise-platform'
steps:
- name: Validate fixed reviewed inputs in private resource limited units
run: |
set -eu
python3 <<'PY'
import os,re,subprocess,json
maintenanceLoaderSha=os.environ.get('GITHUB_SHA','')
if not re.fullmatch('[0-9a-f]{40}',maintenanceLoaderSha):raise RuntimeError('FIXED_SHA_REQUIRED')
command=['git','-c','safe.directory=/home/git/repositories/luoanwu/enterprise-platform.git','-c','core.hooksPath=/dev/null','-C','/home/git/repositories/luoanwu/enterprise-platform.git','show',maintenanceLoaderSha+':ops/gitea-r1/reclaim-window-capacity.py']
result=subprocess.run(command,capture_output=True,timeout=30)
if result.returncode:raise RuntimeError('FIXED_SCRIPT_UNAVAILABLE')
try:exec(compile(result.stdout,'reclaim-window-capacity.py','exec'))
except Exception as error:
message=str(error)
safe=message if isinstance(error,RuntimeError) and re.fullmatch(r'[A-Z_]+(?::[A-Za-z0-9_./,:]+)?',message) else type(error).__name__
print('GITEA_WINDOW_CAPACITY_RECLAIM_JSON '+json.dumps({'kind':'GiteaWindowCapacityReclaimed','ci':{'sourceSha':maintenanceLoaderSha,'runId':os.environ.get('GITHUB_RUN_ID')},'status':'FAILED_PREPARATION','error':safe,'releaseEligible':False,'liveServicesChanged':False,'liveDatabaseChanged':False}),flush=True)
raise SystemExit(1)
PY
shell: bash
timeout-minutes: "95"
permissions:
contents: read
...
|
validate
|
null
|
["ubuntu-latest"]
|
37716
|
1
|
1793544933
|
1793545202
|
1793544817
|
1793545202
|
|
0
|
|
0
|
Edit
Delete
|
|
46153
|
26818
|
116
|
5
|
420f7580fc8a4f75162d3886f981a9e8201bc900
|
0
|
validate
|
1
|
name: Resolve official job image and test rootless name: Resolve official job image and test rootless canary
"on":
push:
branches: [codex/runner-maintenance-diagnostic-20261101]
paths: [.gitea/workflows/gitea-rootless-job-image.yml]
workflow_dispatch:
jobs:
validate:
name: validate
runs-on: ubuntu-latest
if: github.repository == 'luoanwu/enterprise-platform'
steps:
- name: Validate fixed reviewed inputs in private resource limited units
run: |
set -eu
python3 <<'PY'
import os,re,subprocess,json
maintenanceLoaderSha=os.environ.get('GITHUB_SHA','')
if not re.fullmatch('[0-9a-f]{40}',maintenanceLoaderSha):raise RuntimeError('FIXED_SHA_REQUIRED')
command=['git','-c','safe.directory=/home/git/repositories/luoanwu/enterprise-platform.git','-c','core.hooksPath=/dev/null','-C','/home/git/repositories/luoanwu/enterprise-platform.git','show',maintenanceLoaderSha+':ops/gitea-r1/prepare-job-image.py']
result=subprocess.run(command,capture_output=True,timeout=30)
if result.returncode:raise RuntimeError('FIXED_SCRIPT_UNAVAILABLE')
try:exec(compile(result.stdout,'prepare-job-image.py','exec'))
except Exception as error:
message=str(error)
safe=message if isinstance(error,RuntimeError) and re.fullmatch(r'[A-Z_]+(?::[A-Za-z0-9_./,:]+)?',message) else type(error).__name__
print('GITEA_ROOTLESS_JOB_IMAGE_JSON '+json.dumps({'kind':'GiteaRootlessJobImage','ci':{'sourceSha':maintenanceLoaderSha,'runId':os.environ.get('GITHUB_RUN_ID')},'status':'FAILED_PREPARATION','error':safe,'releaseEligible':False,'liveServicesChanged':False,'liveDatabaseChanged':False}),flush=True)
raise SystemExit(1)
PY
shell: bash
timeout-minutes: "95"
permissions:
contents: read
...
|
validate
|
null
|
["ubuntu-latest"]
|
37606
|
1
|
1793530667
|
1793530771
|
1793530666
|
1793530771
|
|
0
|
|
0
|
Edit
Delete
|
|
46068
|
26768
|
116
|
5
|
40769bdad3adf44ff656aa94a6c331826295a12b
|
0
|
repair
|
1
|
name: Repair runner registration file permissions
name: Repair runner registration file permissions
"on":
push:
branches: [codex/runner-maintenance-diagnostic-20261101]
paths: [.gitea/workflows/gitea-runner-permissions.yml]
workflow_dispatch:
jobs:
repair:
name: repair
runs-on: ubuntu-latest
if: github.repository == 'luoanwu/enterprise-platform'
steps:
- name: Limit existing root-owned registration to owner read-write
run: |
set -eu
python3 <<'PY'
import datetime, json, os, pathlib, socket, stat, subprocess
if os.geteuid()!=0 or socket.gethostname()!='iZ2zed3pt2nygf9dy1a9d2Z':raise RuntimeError('EXPECTED_HOST_REQUIRED')
os.umask(0o077)
base=pathlib.Path('/var/lib/eios-r1-maintenance')
if base.is_symlink() or not base.is_dir() or base.stat().st_uid!=0 or base.stat().st_mode&0o077:raise RuntimeError('PRIVATE_RECEIPT_BASE_REQUIRED')
runid=os.environ.get('GITHUB_RUN_ID')
if not runid or not runid.isdigit():raise RuntimeError('REAL_RUN_ID_REQUIRED')
stamp=datetime.datetime.now(datetime.timezone.utc).strftime('%Y%m%dT%H%M%S%fZ')
target=base/('registration-permissions-'+runid+'-'+stamp+'.json')
def unit():
r=subprocess.run(['systemctl','show','gitea-runner.service','-p','User','-p','ActiveState','-p','MainPID'],capture_output=True,text=True,check=True,timeout=10)
return dict(row.split('=',1) for row in r.stdout.splitlines() if '=' in row)
before_unit=unit()
if before_unit.get('User') not in ['','root'] or before_unit.get('ActiveState')!='active' or not before_unit.get('MainPID','').isdigit():raise RuntimeError('ACTIVE_ROOT_RUNNER_REQUIRED')
path=pathlib.Path('/opt/gitea-runner/.runner')
parent=path.parent.lstat()
if not stat.S_ISDIR(parent.st_mode) or parent.st_uid!=0 or stat.S_IMODE(parent.st_mode)&0o022:raise RuntimeError('PROTECTED_REGISTRATION_DIRECTORY_REQUIRED')
fd=os.open(path,os.O_RDONLY|os.O_NOFOLLOW)
try:
before=os.fstat(fd)
if not stat.S_ISREG(before.st_mode) or before.st_uid!=0 or before.st_gid!=0 or before.st_nlink!=1:raise RuntimeError('EXPECTED_ROOT_REGISTRATION_FILE_REQUIRED')
mode=stat.S_IMODE(before.st_mode)
if mode not in [0o644,0o600]:raise RuntimeError('UNEXPECTED_REGISTRATION_PERMISSIONS')
if mode!=0o600:os.fchmod(fd,0o600)
after=os.fstat(fd);current=path.lstat()
if (after.st_dev,after.st_ino)!=(current.st_dev,current.st_ino):raise RuntimeError('REGISTRATION_PATH_CHANGED')
if (after.st_dev,after.st_ino,after.st_size,after.st_mtime_ns)!=(before.st_dev,before.st_ino,before.st_size,before.st_mtime_ns):raise RuntimeError('REGISTRATION_CONTENT_METADATA_CHANGED')
if stat.S_IMODE(after.st_mode)!=0o600:raise RuntimeError('PERMISSION_REPAIR_FAILED')
finally:os.close(fd)
after_unit=unit()
if after_unit!=before_unit:raise RuntimeError('RUNNER_SERVICE_STATE_CHANGED')
report={'kind':'GiteaRunnerRegistrationPermissionRepair','observedAtUTC':datetime.datetime.now(datetime.timezone.utc).isoformat(),'ci':{'sourceSha':os.environ.get('GITHUB_SHA'),'runId':os.environ.get('GITHUB_RUN_ID')},'releaseEligible':False,'path':str(path),'beforeMode':oct(mode),'afterMode':'0o600','ownerUid':0,'ownerGid':0,'changed':mode!=0o600,'contentRead':False,'contentWritten':False,'contentMetadataUnchanged':True,'runnerServiceBefore':before_unit,'runnerServiceAfter':after_unit,'serviceRestarted':False,'giteaBinaryOrDatabaseChanged':False,'fullExecutorIsolationAccepted':False}
with open(target,'x') as f:json.dump(report,f,indent=2);f.write('\n')
print('GITEA_RUNNER_PERMISSION_REPAIR_JSON '+json.dumps(report,separators=(',',':')))
PY
shell: bash
timeout-minutes: "5"
permissions:
contents: read
...
|
repair
|
null
|
["ubuntu-latest"]
|
37529
|
1
|
1793523139
|
1793523139
|
1793523138
|
1793523139
|
|
0
|
|
0
|
Edit
Delete
|
|
46422
|
26913
|
116
|
5
|
8bc522dc372989a4082475b1aae88ee1924811e3
|
0
|
cleanup
|
1
|
name: Remove only verified idle duplicate tools fr name: Remove only verified idle duplicate tools from failed maintenance run
"on":
workflow_dispatch:
jobs:
cleanup:
name: cleanup
runs-on: ubuntu-latest
if: github.repository == 'luoanwu/enterprise-platform' && github.ref == 'refs/heads/codex/runner-maintenance-diagnostic-20261101'
steps:
- name: Recheck two exact copies and preserve every other task file
run: |
set -eu
python3 <<'PY'
import os,re,subprocess
sha=os.environ['GITHUB_SHA'];repo='/home/git/repositories/luoanwu/enterprise-platform.git'
if not re.fullmatch('[a-f0-9]{40}',sha):raise RuntimeError('FIXED_SOURCE_REQUIRED')
r=subprocess.run(['git','-c','safe.directory='+repo,'-C',repo,'show',sha+':ops/gitea-r1/cleanup-obsolete-tools.py'],capture_output=True,timeout=20)
if r.returncode:raise RuntimeError('FIXED_CLEANUP_SOURCE_REQUIRED')
exec(compile(r.stdout,'cleanup-obsolete-tools.py','exec'),{'__name__':'__main__'})
PY
shell: bash
timeout-minutes: "10"
permissions:
contents: read
...
|
cleanup
|
null
|
["ubuntu-latest"]
|
37795
|
2
|
1793580016
|
1793580024
|
1793580009
|
1793580024
|
|
0
|
|
0
|
Edit
Delete
|
|
46436
|
26917
|
116
|
5
|
03f3fff139ed461e4d27a1b738dc4fe339454b06
|
0
|
cleanup
|
1
|
name: Remove only verified idle duplicate tools fr name: Remove only verified idle duplicate tools from failed maintenance run
"on":
workflow_dispatch:
jobs:
cleanup:
name: cleanup
runs-on: ubuntu-latest
if: github.repository == 'luoanwu/enterprise-platform' && github.ref == 'refs/heads/codex/runner-maintenance-diagnostic-20261101'
steps:
- name: Recheck two exact copies and preserve every other task file
run: |
set -eu
python3 <<'PY'
import os,re,subprocess
sha=os.environ['GITHUB_SHA'];repo='/home/git/repositories/luoanwu/enterprise-platform.git'
if not re.fullmatch('[a-f0-9]{40}',sha):raise RuntimeError('FIXED_SOURCE_REQUIRED')
r=subprocess.run(['git','-c','safe.directory='+repo,'-C',repo,'show',sha+':ops/gitea-r1/cleanup-obsolete-tools.py'],capture_output=True,timeout=20)
if r.returncode:raise RuntimeError('FIXED_CLEANUP_SOURCE_REQUIRED')
exec(compile(r.stdout,'cleanup-obsolete-tools.py','exec'),{'__name__':'__main__'})
PY
shell: bash
timeout-minutes: "10"
permissions:
contents: read
...
|
cleanup
|
null
|
["ubuntu-latest"]
|
37804
|
1
|
1793582118
|
1793582151
|
1793581650
|
1793582151
|
|
0
|
|
0
|
Edit
Delete
|
|
46151
|
26816
|
116
|
5
|
62c9546ddd89b6b42d6102c67af41ca314f3c9ea
|
0
|
validate
|
1
|
name: Rehearse old restore keeping authenticated v name: Rehearse old restore keeping authenticated version API policy
"on":
push:
branches: [codex/runner-maintenance-diagnostic-20261101]
paths: [.gitea/workflows/gitea-old-database-restore.yml]
workflow_dispatch:
jobs:
validate:
name: validate
runs-on: ubuntu-latest
if: github.repository == 'luoanwu/enterprise-platform'
steps:
- name: Validate fixed reviewed inputs in private resource limited units
run: |
set -eu
python3 <<'PY'
import os,re,subprocess,json
maintenanceLoaderSha=os.environ.get('GITHUB_SHA','')
if not re.fullmatch('[0-9a-f]{40}',maintenanceLoaderSha):raise RuntimeError('FIXED_SHA_REQUIRED')
command=['git','-c','safe.directory=/home/git/repositories/luoanwu/enterprise-platform.git','-c','core.hooksPath=/dev/null','-C','/home/git/repositories/luoanwu/enterprise-platform.git','show',maintenanceLoaderSha+':ops/gitea-r1/rehearse-old-database.py']
result=subprocess.run(command,capture_output=True,timeout=30)
if result.returncode:raise RuntimeError('FIXED_SCRIPT_UNAVAILABLE')
try:exec(compile(result.stdout,'rehearse-old-database.py','exec'))
except Exception as error:
message=str(error)
safe=message if isinstance(error,RuntimeError) and re.fullmatch(r'[A-Z_]+(?::[A-Za-z0-9_./,:]+)?',message) else type(error).__name__
print('GITEA_OLD_DATABASE_RESTORE_JSON '+json.dumps({'kind':'GiteaOldDatabaseRestoreRehearsal','ci':{'sourceSha':maintenanceLoaderSha,'runId':os.environ.get('GITHUB_RUN_ID')},'status':'FAILED_PREPARATION','error':safe,'releaseEligible':False,'liveServicesChanged':False,'liveDatabaseChanged':False}),flush=True)
raise SystemExit(1)
PY
shell: bash
timeout-minutes: "95"
permissions:
contents: read
...
|
validate
|
null
|
["ubuntu-latest"]
|
37604
|
1
|
1793530564
|
1793530625
|
1793530562
|
1793530625
|
|
0
|
|
0
|
Edit
Delete
|
|
46145
|
26810
|
116
|
5
|
12b1b68448b3bfa1b954bf21b17084eccc99bc54
|
0
|
validate
|
1
|
name: Rehearse old database restore in isolated ne name: Rehearse old database restore in isolated network
"on":
push:
branches: [codex/runner-maintenance-diagnostic-20261101]
paths: [.gitea/workflows/gitea-old-database-restore.yml]
workflow_dispatch:
jobs:
validate:
name: validate
runs-on: ubuntu-latest
if: github.repository == 'luoanwu/enterprise-platform'
steps:
- name: Validate fixed reviewed inputs in private resource limited units
run: |
set -eu
python3 <<'PY'
import os,re,subprocess,json
maintenanceLoaderSha=os.environ.get('GITHUB_SHA','')
if not re.fullmatch('[0-9a-f]{40}',maintenanceLoaderSha):raise RuntimeError('FIXED_SHA_REQUIRED')
command=['git','-c','safe.directory=/home/git/repositories/luoanwu/enterprise-platform.git','-c','core.hooksPath=/dev/null','-C','/home/git/repositories/luoanwu/enterprise-platform.git','show',maintenanceLoaderSha+':ops/gitea-r1/rehearse-old-database.py']
result=subprocess.run(command,capture_output=True,timeout=30)
if result.returncode:raise RuntimeError('FIXED_SCRIPT_UNAVAILABLE')
try:exec(compile(result.stdout,'rehearse-old-database.py','exec'))
except Exception as error:
message=str(error)
safe=message if isinstance(error,RuntimeError) and re.fullmatch(r'[A-Z_]+(?::[A-Za-z0-9_./,:]+)?',message) else type(error).__name__
print('GITEA_OLD_DATABASE_RESTORE_JSON '+json.dumps({'kind':'GiteaOldDatabaseRestoreRehearsal','ci':{'sourceSha':maintenanceLoaderSha,'runId':os.environ.get('GITHUB_RUN_ID')},'status':'FAILED_PREPARATION','error':safe,'releaseEligible':False,'liveServicesChanged':False,'liveDatabaseChanged':False}),flush=True)
raise SystemExit(1)
PY
shell: bash
timeout-minutes: "95"
permissions:
contents: read
...
|
validate
|
null
|
["ubuntu-latest"]
|
37598
|
2
|
1793530286
|
1793530418
|
1793530235
|
1793530418
|
|
0
|
|
0
|
Edit
Delete
|
|
46277
|
26858
|
116
|
5
|
92a63617fc34559094d2e39f67cfd12131bbcaee
|
0
|
validate
|
1
|
name: Rehearse copied database only after fixed bu name: Rehearse copied database only after fixed build 26857 passes
"on":
push:
branches: [codex/runner-maintenance-diagnostic-20261101]
paths: [.gitea/workflows/gitea-candidate-database-rehearsal.yml]
workflow_dispatch:
jobs:
validate:
name: validate
runs-on: ubuntu-latest
if: github.repository == 'luoanwu/enterprise-platform'
steps:
- name: Validate fixed reviewed inputs in private resource limited units
run: |
set -eu
python3 <<'PY'
import os,re,subprocess,json
maintenanceLoaderSha=os.environ.get('GITHUB_SHA','')
if not re.fullmatch('[0-9a-f]{40}',maintenanceLoaderSha):raise RuntimeError('FIXED_SHA_REQUIRED')
command=['git','-c','safe.directory=/home/git/repositories/luoanwu/enterprise-platform.git','-c','core.hooksPath=/dev/null','-C','/home/git/repositories/luoanwu/enterprise-platform.git','show',maintenanceLoaderSha+':ops/gitea-r1/rehearse-candidate-database.py']
result=subprocess.run(command,capture_output=True,timeout=30)
if result.returncode:raise RuntimeError('FIXED_SCRIPT_UNAVAILABLE')
try:exec(compile(result.stdout,'rehearse-candidate-database.py','exec'))
except Exception as error:
message=str(error)
safe=message if isinstance(error,RuntimeError) and re.fullmatch(r'[A-Z_]+(?::[A-Za-z0-9_./,:]+)?',message) else type(error).__name__
print('GITEA_CANDIDATE_DATABASE_REHEARSAL_JSON '+json.dumps({'kind':'GiteaCandidateDatabaseMigrationRehearsal','ci':{'sourceSha':maintenanceLoaderSha,'runId':os.environ.get('GITHUB_RUN_ID')},'status':'FAILED_PREPARATION','error':safe,'releaseEligible':False,'liveServicesChanged':False,'liveDatabaseChanged':False}),flush=True)
raise SystemExit(1)
PY
shell: bash
timeout-minutes: "95"
permissions:
contents: read
...
|
validate
|
null
|
["ubuntu-latest"]
|
37682
|
2
|
1793537396
|
1793537442
|
1793537282
|
1793537443
|
|
0
|
|
0
|
Edit
Delete
|
|
46272
|
26853
|
116
|
5
|
e1c6f2edb85e7a266adb72402f75650a1a314e4d
|
0
|
validate
|
1
|
name: Rehearse copied database only after fixed bu name: Rehearse copied database only after fixed build 26852 passes
"on":
push:
branches: [codex/runner-maintenance-diagnostic-20261101]
paths: [.gitea/workflows/gitea-candidate-database-rehearsal.yml]
workflow_dispatch:
jobs:
validate:
name: validate
runs-on: ubuntu-latest
if: github.repository == 'luoanwu/enterprise-platform'
steps:
- name: Validate fixed reviewed inputs in private resource limited units
run: |
set -eu
python3 <<'PY'
import os,re,subprocess,json
maintenanceLoaderSha=os.environ.get('GITHUB_SHA','')
if not re.fullmatch('[0-9a-f]{40}',maintenanceLoaderSha):raise RuntimeError('FIXED_SHA_REQUIRED')
command=['git','-c','safe.directory=/home/git/repositories/luoanwu/enterprise-platform.git','-c','core.hooksPath=/dev/null','-C','/home/git/repositories/luoanwu/enterprise-platform.git','show',maintenanceLoaderSha+':ops/gitea-r1/rehearse-candidate-database.py']
result=subprocess.run(command,capture_output=True,timeout=30)
if result.returncode:raise RuntimeError('FIXED_SCRIPT_UNAVAILABLE')
try:exec(compile(result.stdout,'rehearse-candidate-database.py','exec'))
except Exception as error:
message=str(error)
safe=message if isinstance(error,RuntimeError) and re.fullmatch(r'[A-Z_]+(?::[A-Za-z0-9_./,:]+)?',message) else type(error).__name__
print('GITEA_CANDIDATE_DATABASE_REHEARSAL_JSON '+json.dumps({'kind':'GiteaCandidateDatabaseMigrationRehearsal','ci':{'sourceSha':maintenanceLoaderSha,'runId':os.environ.get('GITHUB_RUN_ID')},'status':'FAILED_PREPARATION','error':safe,'releaseEligible':False,'liveServicesChanged':False,'liveDatabaseChanged':False}),flush=True)
raise SystemExit(1)
PY
shell: bash
timeout-minutes: "95"
permissions:
contents: read
...
|
validate
|
null
|
["ubuntu-latest"]
|
37677
|
2
|
1793536763
|
1793536769
|
1793536744
|
1793536769
|
|
0
|
|
0
|
Edit
Delete
|
|
46269
|
26850
|
116
|
5
|
f05a642ae8252c6f18435f5bf929515f3cf962d7
|
0
|
validate
|
1
|
name: Rehearse copied database only after fixed bu name: Rehearse copied database only after fixed build 26849 passes
"on":
push:
branches: [codex/runner-maintenance-diagnostic-20261101]
paths: [.gitea/workflows/gitea-candidate-database-rehearsal.yml]
workflow_dispatch:
jobs:
validate:
name: validate
runs-on: ubuntu-latest
if: github.repository == 'luoanwu/enterprise-platform'
steps:
- name: Validate fixed reviewed inputs in private resource limited units
run: |
set -eu
python3 <<'PY'
import os,re,subprocess,json
maintenanceLoaderSha=os.environ.get('GITHUB_SHA','')
if not re.fullmatch('[0-9a-f]{40}',maintenanceLoaderSha):raise RuntimeError('FIXED_SHA_REQUIRED')
command=['git','-c','safe.directory=/home/git/repositories/luoanwu/enterprise-platform.git','-c','core.hooksPath=/dev/null','-C','/home/git/repositories/luoanwu/enterprise-platform.git','show',maintenanceLoaderSha+':ops/gitea-r1/rehearse-candidate-database.py']
result=subprocess.run(command,capture_output=True,timeout=30)
if result.returncode:raise RuntimeError('FIXED_SCRIPT_UNAVAILABLE')
try:exec(compile(result.stdout,'rehearse-candidate-database.py','exec'))
except Exception as error:
message=str(error)
safe=message if isinstance(error,RuntimeError) and re.fullmatch(r'[A-Z_]+(?::[A-Za-z0-9_./,:]+)?',message) else type(error).__name__
print('GITEA_CANDIDATE_DATABASE_REHEARSAL_JSON '+json.dumps({'kind':'GiteaCandidateDatabaseMigrationRehearsal','ci':{'sourceSha':maintenanceLoaderSha,'runId':os.environ.get('GITHUB_RUN_ID')},'status':'FAILED_PREPARATION','error':safe,'releaseEligible':False,'liveServicesChanged':False,'liveDatabaseChanged':False}),flush=True)
raise SystemExit(1)
PY
shell: bash
timeout-minutes: "95"
permissions:
contents: read
...
|
validate
|
null
|
["ubuntu-latest"]
|
37674
|
2
|
1793536452
|
1793536464
|
1793535198
|
1793536464
|
|
0
|
|
0
|
Edit
Delete
|
|
46286
|
26867
|
116
|
5
|
e35d033f6c9ba3a85010324e5bb7142aaef12cdf
|
0
|
validate
|
1
|
name: Reclaim only maintenance task compile cache
name: Reclaim only maintenance task compile cache
"on":
push:
branches: [codex/runner-maintenance-diagnostic-20261101]
paths: [.gitea/workflows/gitea-task-cache-reclaim.yml]
workflow_dispatch:
jobs:
validate:
name: validate
runs-on: ubuntu-latest
if: github.repository == 'luoanwu/enterprise-platform'
steps:
- name: Validate fixed reviewed inputs in private resource limited units
run: |
set -eu
python3 <<'PY'
import os,re,subprocess,json
maintenanceLoaderSha=os.environ.get('GITHUB_SHA','')
if not re.fullmatch('[0-9a-f]{40}',maintenanceLoaderSha):raise RuntimeError('FIXED_SHA_REQUIRED')
command=['git','-c','safe.directory=/home/git/repositories/luoanwu/enterprise-platform.git','-c','core.hooksPath=/dev/null','-C','/home/git/repositories/luoanwu/enterprise-platform.git','show',maintenanceLoaderSha+':ops/gitea-r1/reclaim-task-compile-cache.py']
result=subprocess.run(command,capture_output=True,timeout=30)
if result.returncode:raise RuntimeError('FIXED_SCRIPT_UNAVAILABLE')
try:exec(compile(result.stdout,'reclaim-task-compile-cache.py','exec'))
except Exception as error:
message=str(error)
safe=message if isinstance(error,RuntimeError) and re.fullmatch(r'[A-Z_]+(?::[A-Za-z0-9_./,:]+)?',message) else type(error).__name__
print('GITEA_CACHE_RECLAIM_JSON '+json.dumps({'kind':'GiteaTaskCompileCacheReclaimed','ci':{'sourceSha':maintenanceLoaderSha,'runId':os.environ.get('GITHUB_RUN_ID')},'status':'FAILED_PREPARATION','error':safe,'releaseEligible':False,'liveServicesChanged':False,'liveDatabaseChanged':False}),flush=True)
raise SystemExit(1)
PY
shell: bash
timeout-minutes: "95"
permissions:
contents: read
...
|
validate
|
null
|
["ubuntu-latest"]
|
37691
|
1
|
1793543272
|
1793543284
|
1793543272
|
1793543284
|
|
0
|
|
0
|
Edit
Delete
|
|
46177
|
26828
|
116
|
5
|
a451536e0fe212b0a5d07700bcf990d57356ebe1
|
0
|
inspect
|
1
|
name: Read terminated lint unit result and memory name: Read terminated lint unit result and memory metadata only
"on":
push:
branches: [codex/runner-maintenance-diagnostic-20261101]
paths: [.gitea/workflows/gitea-lint-resource-diagnostic.yml]
jobs:
inspect:
name: inspect
runs-on: ubuntu-latest
if: github.repository == 'luoanwu/enterprise-platform'
steps:
- name: Read only fixed private synthetic test failure
run: |
set -eu
python3 <<'PYCODE'
import pathlib,re,json,os,subprocess
unit='eios-r1-26822-lint_go.service'
r=subprocess.run(['journalctl','-u',unit,'--no-pager','-o','json'],capture_output=True,timeout=30)
messages=[]
for line in r.stdout.decode(errors='replace').splitlines():
try:message=json.loads(line).get('MESSAGE','')
except ValueError:continue
if not isinstance(message,str) or re.search(r'(?i)password|secret|authorization|://[^ /]+@',message):continue
messages.append(message[-800:])
mem={}
for line in pathlib.Path('/proc/meminfo').read_text().splitlines():
key,value=line.split(':',1)
if key in ['MemTotal','MemAvailable','SwapTotal','SwapFree']:mem[key]=value.strip()
print('GITEA_LINT_RESOURCE_DIAG_JSON '+json.dumps({'kind':'CandidateLintResourceReadOnlyDiagnosis','ci':{'sourceSha':os.environ.get('GITHUB_SHA'),'runId':os.environ.get('GITHUB_RUN_ID')},'unit':unit,'screenedUnitMessages':messages[-25:],'memory':mem,'oomMentioned':any('oom' in m.lower() for m in messages),'mutations':False,'releaseEligible':False},separators=(',',':')),flush=True)
PYCODE
shell: bash
timeout-minutes: "3"
permissions:
contents: read
...
|
inspect
|
null
|
["ubuntu-latest"]
|
37622
|
1
|
1793532963
|
1793532967
|
1793532963
|
1793532968
|
|
0
|
|
0
|
Edit
Delete
|
|
46253
|
26841
|
116
|
5
|
093e09c0a46348fd3cdc27d7cf7dde53c7b080dd
|
0
|
export
|
1
|
name: Read screened lint files excluding separatel name: Read screened lint files excluding separately reviewed HTTP handler
"on":
push:
branches: [codex/runner-maintenance-diagnostic-20261101]
paths: [.gitea/workflows/gitea-lint-source-review.yml]
workflow_dispatch:
jobs:
export:
name: export
runs-on: ubuntu-latest
if: github.repository == 'luoanwu/enterprise-platform'
steps:
- name: Screen only known source changes and produce a review archive
run: |
set -eu
python3 <<'PY'
import base64, datetime, hashlib, io, json, os, pathlib, re, socket, subprocess, tarfile
if os.geteuid()!=0 or socket.gethostname()!='iZ2zed3pt2nygf9dy1a9d2Z':raise RuntimeError('EXPECTED_HOST_REQUIRED')
os.umask(0o077)
runid=os.environ.get('GITHUB_RUN_ID')
if not runid or not runid.isdigit():raise RuntimeError('REAL_RUN_ID_REQUIRED')
base=pathlib.Path('/var/lib/eios-r1-maintenance')
if base.is_symlink() or not base.is_dir() or base.stat().st_uid!=0 or base.stat().st_mode&0o077:raise RuntimeError('PRIVATE_BASE_REQUIRED')
analysis=base/'analysis-26765'
receipt=json.loads((analysis/'receipt.json').read_text())
if receipt.get('status')!='PRIVATE_BASELINE_VERIFIED_AND_COMPATIBILITY_ANALYZED':raise RuntimeError('VERIFIED_BASELINE_REQUIRED')
source=base/'reviewed-26829'/'candidate-source'
live=pathlib.Path('/root/gitea-src')
upstream=analysis/'candidate-source'
env={'PATH':'/usr/local/bin:/usr/bin:/bin','HOME':str(base),'LANG':'C.UTF-8','GIT_CONFIG_NOSYSTEM':'1','GIT_CONFIG_GLOBAL':'/dev/null','GIT_OPTIONAL_LOCKS':'0','GIT_TERMINAL_PROMPT':'0'}
def git(path,*args):
r=subprocess.run(['git','--no-optional-locks','-c','core.hooksPath=/dev/null','-C',str(path),*args],capture_output=True,timeout=30,env=env)
return r.returncode,r.stdout
code,head=git(upstream,'rev-parse','HEAD')
if code or head.decode().strip()!='146cc3eec57174711eac0e0a0c7b38670c6e3922':raise RuntimeError('FIXED_OFFICIAL_SOURCE_REQUIRED')
# Export only files that require manual conflict or migration review.
# Already textually merged files and additive files stay on the server for native testing.
paths=['models/repo/commit_sync_log.go', 'models/repo/commit_sync_status.go', 'models/repo/hidden_file.go', 'modules/git/pktline.go', 'routers/web/repo/commit.go', 'routers/web/repo/hidden_file.go', 'routers/web/repo/setting/setting.go', 'routers/web/repo/sync_records.go', 'services/gitpull/filter.go', 'services/gitpull/matcher.go', 'services/repository/auto_sync_deploy.go']
def public_literals(text):
return set(re.findall(r'["\']([^"\'\n]{8,})["\']',text))
def suspicious(text,public,is_git_test=False):
issues=[]
for m in re.finditer(r'-----BEGIN (?:RSA |EC |OPENSSH )?PRIVATE KEY-----|(?:ghp_|github_pat_|AKIA[0-9A-Z]{16}|sk-(?:proj-)?)[A-Za-z0-9_/-]{12,}',text):
public_ssh=bool(re.match(r'sk-(?:ecdsa-sha2-nistp256|ssh-ed25519)@openssh\.com',text[m.start():])) and any(m.group(0) in v for v in public)
if not public_ssh:issues.append('credential-prefix-or-private-key')
if re.search(r'https?://[^/\s:@]+:[^/\s@]+@',text):issues.append('credential-in-url')
for match in re.finditer(r'(?i)(?:[A-Za-z0-9_]*(?:password|passwd|secret|token|api_?key|access_?key|private_?key)[A-Za-z0-9_]*)\s*(?::=|=|:)\s*["\']([^"\'\n]{8,})["\']',text):
value=match.group(1)
if value not in public:issues.append('nonpublic-credential-literal')
for match in re.finditer(r'(?i)(?:Bearer|Basic)\s+[A-Za-z0-9_+/=.-]{12,}',text):
if not any(match.group(0) in literal for literal in public):issues.append('nonpublic-authorization-literal')
for value in re.findall(r'["\']([A-Za-z0-9+/=_-]{32,})["\']',text):
if value not in public and len(set(value))>8 and not is_git_test:issues.append('nonpublic-long-opaque-literal')
if not is_git_test:
for value in re.findall(r'["\']([A-Fa-f0-9]{32,128})["\']',text):
if value not in public and len(set(value))>3:issues.append('nonpublic-long-hex-literal')
return sorted(set(issues))
payload={};manifest=[];refused=[]
for name in paths:
rel=pathlib.PurePosixPath(name)
if rel.is_absolute() or '..' in rel.parts or not rel.parts or rel.parts[0] not in ['models','routers','services','modules','templates','options'] or rel.suffix not in ['.go','.tmpl','.json']:raise RuntimeError('SOURCE_EXPORT_PATH_REFUSED')
p=source/name
if p.is_symlink() or not p.is_file():raise RuntimeError('SOURCE_EXPORT_NONREGULAR_FILE')
data=p.read_bytes()
if len(data)>2*1024*1024:raise RuntimeError('SOURCE_FILE_SIZE_LIMIT')
rc,public=git(upstream,'show','HEAD:'+name)
known=public_literals(public.decode(errors='replace')) if rc==0 else set()
# Never export authorization-looking comment text, even when it is likely prose.
text=data.decode('utf-8')
text=re.sub(r'(?m)^(\s*//[^\n]*?)(?i:Basic|Bearer)\s+[A-Za-z0-9_+/=.-]{12,}[^\n]*$',r'\1[REDACTED_AUTHORIZATION_LOOKING_COMMENT]',text)
data=text.encode('utf-8')
candidates=[(name,data)]
for archive_name,body in candidates:
problems=suspicious(body.decode('utf-8'),known,name.startswith('modules/git/') and name.endswith('_test.go'))
if problems:
text=body.decode('utf-8');classification=[]
for m in re.finditer(r'-----BEGIN (?:RSA |EC |OPENSSH )?PRIVATE KEY-----|(?:ghp_|github_pat_|AKIA[0-9A-Z]{16}|sk-(?:proj-)?)[A-Za-z0-9_/-]{12,}',text):
classification.append({'category':'prefix','publicSshAlgorithm':bool(re.match(r'sk-(?:ecdsa-sha2-nistp256|ssh-ed25519)@openssh\.com',text[m.start():])),'presentInOfficialLiteral':any(m.group(0) in v for v in known)})
for m in re.finditer(r'(?i)(Bearer|Basic)\s+([A-Za-z0-9_+/=.-]{12,})',text):
line=text[text.rfind('\n',0,m.start())+1:m.start()]
valid=False
if m.group(1).lower()=='basic':
try:
decoded=base64.b64decode(m.group(2)+'='*((-len(m.group(2)))%4),validate=True).decode('utf-8')
valid=':' in decoded and decoded.isprintable()
except Exception:pass
classification.append({'category':'authorization','scheme':m.group(1).lower(),'commentLine':line.lstrip().startswith('//'),'validPrintableBasicCredential':valid,'presentInOfficialLiteral':any(m.group(0) in v for v in known)})
for value in re.findall(r'["\']([A-Za-z0-9+/=_-]{32,})["\']',text):
if value not in known and len(set(value))>8 and not (name.startswith('modules/git/') and name.endswith('_test.go')):
classification.append({'category':'opaque','publicGitSmartHttpMime':value in ['application/x-git-upload-pack-advertisement','application/x-git-receive-pack-advertisement','application/x-git-upload-pack-result','application/x-git-receive-pack-result']})
refused.append({'path':archive_name,'reasons':problems,'classification':classification});continue
payload[archive_name]=body
manifest.append({'path':archive_name,'bytes':len(body),'sha256':hashlib.sha256(body).hexdigest()})
report={'kind':'GiteaCustomSourceReviewExport','observedAtUTC':datetime.datetime.now(datetime.timezone.utc).isoformat(),'ci':{'sourceSha':os.environ.get('GITHUB_SHA'),'runId':runid},'releaseEligible':False,'sourceBaselineFingerprint':receipt['sourceFingerprint'],'upstreamCommit':head.decode().strip(),'serviceChanges':False,'databaseOrConfigurationExported':False,'registrationOrCredentialFilesRead':False,'refusedFiles':refused}
if refused:
report['status']='BLOCKED_CREDENTIAL_SCREENING';print('GITEA_CUSTOM_SOURCE_EXPORT_JSON '+json.dumps(report,separators=(',',':')));raise SystemExit(1)
if sum(len(v) for v in payload.values())>8*1024*1024:raise RuntimeError('SOURCE_EXPORT_SIZE_LIMIT')
work=base/('export-'+runid);work.mkdir(mode=0o700)
index={'kind':'GiteaCustomSourceReviewManifest','sourceBaselineFingerprint':receipt['sourceFingerprint'],'originalSourceHead':'287bf811487c04cd90bcf07aa04905cbaf2e4c2d','upstreamCommit':head.decode().strip(),'files':manifest,'scope':'Selected code only, not a system backup; no database, config, registration, environment, key or dependency cache files'}
payload['manifest.json']=(json.dumps(index,ensure_ascii=False,indent=2)+'\n').encode()
target=work/'gitea-custom-source.tar.gz'
with tarfile.open(target,'w:gz') as archive:
for name,body in sorted(payload.items()):
entry=tarfile.TarInfo(name);entry.size=len(body);entry.mode=0o600;entry.mtime=0;archive.addfile(entry,io.BytesIO(body))
report.update({'status':'SOURCE_CODE_SCREENED_AND_PACKED','fileCount':len(manifest),'archiveBytes':target.stat().st_size,'archiveSha256':hashlib.sha256(target.read_bytes()).hexdigest(),'privateArchive':str(target)})
(work/'receipt.json').write_text(json.dumps(report,indent=2)+'\n')
print('GITEA_CUSTOM_SOURCE_EXPORT_JSON '+json.dumps(report,separators=(',',':')))
for name,body in sorted(payload.items()):
if name=='manifest.json':continue
encoded=base64.b64encode(body).decode()
chunks=[encoded[i:i+8000] for i in range(0,len(encoded),8000)]
for i,chunk in enumerate(chunks):
print('GITEA_GIT_SOURCE_FILE_JSON '+json.dumps({'path':name,'sha256':hashlib.sha256(body).hexdigest(),'index':i,'chunks':len(chunks),'data':chunk},separators=(',',':')),flush=True)
PY
shell: bash
timeout-minutes: "15"
permissions:
contents: read
...
|
export
|
null
|
["ubuntu-latest"]
|
37652
|
1
|
1793533924
|
1793533924
|
1793533920
|
1793533925
|
|
0
|
|
0
|
Edit
Delete
|
|
46244
|
26839
|
116
|
5
|
126b2e92b788caa79f2cfc6cc6f14c736ee54fac
|
0
|
export
|
1
|
name: Read screened custom files for complete lint name: Read screened custom files for complete lint repair
"on":
push:
branches: [codex/runner-maintenance-diagnostic-20261101]
paths: [.gitea/workflows/gitea-lint-source-review.yml]
workflow_dispatch:
jobs:
export:
name: export
runs-on: ubuntu-latest
if: github.repository == 'luoanwu/enterprise-platform'
steps:
- name: Screen only known source changes and produce a review archive
run: |
set -eu
python3 <<'PY'
import base64, datetime, hashlib, io, json, os, pathlib, re, socket, subprocess, tarfile
if os.geteuid()!=0 or socket.gethostname()!='iZ2zed3pt2nygf9dy1a9d2Z':raise RuntimeError('EXPECTED_HOST_REQUIRED')
os.umask(0o077)
runid=os.environ.get('GITHUB_RUN_ID')
if not runid or not runid.isdigit():raise RuntimeError('REAL_RUN_ID_REQUIRED')
base=pathlib.Path('/var/lib/eios-r1-maintenance')
if base.is_symlink() or not base.is_dir() or base.stat().st_uid!=0 or base.stat().st_mode&0o077:raise RuntimeError('PRIVATE_BASE_REQUIRED')
analysis=base/'analysis-26765'
receipt=json.loads((analysis/'receipt.json').read_text())
if receipt.get('status')!='PRIVATE_BASELINE_VERIFIED_AND_COMPATIBILITY_ANALYZED':raise RuntimeError('VERIFIED_BASELINE_REQUIRED')
source=base/'reviewed-26829'/'candidate-source'
live=pathlib.Path('/root/gitea-src')
upstream=analysis/'candidate-source'
env={'PATH':'/usr/local/bin:/usr/bin:/bin','HOME':str(base),'LANG':'C.UTF-8','GIT_CONFIG_NOSYSTEM':'1','GIT_CONFIG_GLOBAL':'/dev/null','GIT_OPTIONAL_LOCKS':'0','GIT_TERMINAL_PROMPT':'0'}
def git(path,*args):
r=subprocess.run(['git','--no-optional-locks','-c','core.hooksPath=/dev/null','-C',str(path),*args],capture_output=True,timeout=30,env=env)
return r.returncode,r.stdout
code,head=git(upstream,'rev-parse','HEAD')
if code or head.decode().strip()!='146cc3eec57174711eac0e0a0c7b38670c6e3922':raise RuntimeError('FIXED_OFFICIAL_SOURCE_REQUIRED')
# Export only files that require manual conflict or migration review.
# Already textually merged files and additive files stay on the server for native testing.
paths=['models/repo/commit_sync_log.go', 'models/repo/commit_sync_status.go', 'models/repo/hidden_file.go', 'modules/git/pktline.go', 'routers/web/repo/commit.go', 'routers/web/repo/githttp.go', 'routers/web/repo/hidden_file.go', 'routers/web/repo/setting/setting.go', 'routers/web/repo/sync_records.go', 'services/gitpull/filter.go', 'services/gitpull/matcher.go', 'services/repository/auto_sync_deploy.go']
def public_literals(text):
return set(re.findall(r'["\']([^"\'\n]{8,})["\']',text))
def suspicious(text,public,is_git_test=False):
issues=[]
for m in re.finditer(r'-----BEGIN (?:RSA |EC |OPENSSH )?PRIVATE KEY-----|(?:ghp_|github_pat_|AKIA[0-9A-Z]{16}|sk-(?:proj-)?)[A-Za-z0-9_/-]{12,}',text):
public_ssh=bool(re.match(r'sk-(?:ecdsa-sha2-nistp256|ssh-ed25519)@openssh\.com',text[m.start():])) and any(m.group(0) in v for v in public)
if not public_ssh:issues.append('credential-prefix-or-private-key')
if re.search(r'https?://[^/\s:@]+:[^/\s@]+@',text):issues.append('credential-in-url')
for match in re.finditer(r'(?i)(?:[A-Za-z0-9_]*(?:password|passwd|secret|token|api_?key|access_?key|private_?key)[A-Za-z0-9_]*)\s*(?::=|=|:)\s*["\']([^"\'\n]{8,})["\']',text):
value=match.group(1)
if value not in public:issues.append('nonpublic-credential-literal')
for match in re.finditer(r'(?i)(?:Bearer|Basic)\s+[A-Za-z0-9_+/=.-]{12,}',text):
if not any(match.group(0) in literal for literal in public):issues.append('nonpublic-authorization-literal')
for value in re.findall(r'["\']([A-Za-z0-9+/=_-]{32,})["\']',text):
if value not in public and len(set(value))>8 and not is_git_test:issues.append('nonpublic-long-opaque-literal')
if not is_git_test:
for value in re.findall(r'["\']([A-Fa-f0-9]{32,128})["\']',text):
if value not in public and len(set(value))>3:issues.append('nonpublic-long-hex-literal')
return sorted(set(issues))
payload={};manifest=[];refused=[]
for name in paths:
rel=pathlib.PurePosixPath(name)
if rel.is_absolute() or '..' in rel.parts or not rel.parts or rel.parts[0] not in ['models','routers','services','modules','templates','options'] or rel.suffix not in ['.go','.tmpl','.json']:raise RuntimeError('SOURCE_EXPORT_PATH_REFUSED')
p=source/name
if p.is_symlink() or not p.is_file():raise RuntimeError('SOURCE_EXPORT_NONREGULAR_FILE')
data=p.read_bytes()
if len(data)>2*1024*1024:raise RuntimeError('SOURCE_FILE_SIZE_LIMIT')
rc,public=git(upstream,'show','HEAD:'+name)
known=public_literals(public.decode(errors='replace')) if rc==0 else set()
# Never export authorization-looking comment text, even when it is likely prose.
text=data.decode('utf-8')
text=re.sub(r'(?m)^(\s*//[^\n]*?)(?i:Basic|Bearer)\s+[A-Za-z0-9_+/=.-]{12,}[^\n]*$',r'\1[REDACTED_AUTHORIZATION_LOOKING_COMMENT]',text)
data=text.encode('utf-8')
candidates=[(name,data)]
for archive_name,body in candidates:
problems=suspicious(body.decode('utf-8'),known,name.startswith('modules/git/') and name.endswith('_test.go'))
if problems:
text=body.decode('utf-8');classification=[]
for m in re.finditer(r'-----BEGIN (?:RSA |EC |OPENSSH )?PRIVATE KEY-----|(?:ghp_|github_pat_|AKIA[0-9A-Z]{16}|sk-(?:proj-)?)[A-Za-z0-9_/-]{12,}',text):
classification.append({'category':'prefix','publicSshAlgorithm':bool(re.match(r'sk-(?:ecdsa-sha2-nistp256|ssh-ed25519)@openssh\.com',text[m.start():])),'presentInOfficialLiteral':any(m.group(0) in v for v in known)})
for m in re.finditer(r'(?i)(Bearer|Basic)\s+([A-Za-z0-9_+/=.-]{12,})',text):
line=text[text.rfind('\n',0,m.start())+1:m.start()]
valid=False
if m.group(1).lower()=='basic':
try:
decoded=base64.b64decode(m.group(2)+'='*((-len(m.group(2)))%4),validate=True).decode('utf-8')
valid=':' in decoded and decoded.isprintable()
except Exception:pass
classification.append({'category':'authorization','scheme':m.group(1).lower(),'commentLine':line.lstrip().startswith('//'),'validPrintableBasicCredential':valid,'presentInOfficialLiteral':any(m.group(0) in v for v in known)})
for value in re.findall(r'["\']([A-Za-z0-9+/=_-]{32,})["\']',text):
if value not in known and len(set(value))>8 and not (name.startswith('modules/git/') and name.endswith('_test.go')):
classification.append({'category':'opaque','publicGitSmartHttpMime':value in ['application/x-git-upload-pack-advertisement','application/x-git-receive-pack-advertisement','application/x-git-upload-pack-result','application/x-git-receive-pack-result']})
refused.append({'path':archive_name,'reasons':problems,'classification':classification});continue
payload[archive_name]=body
manifest.append({'path':archive_name,'bytes':len(body),'sha256':hashlib.sha256(body).hexdigest()})
report={'kind':'GiteaCustomSourceReviewExport','observedAtUTC':datetime.datetime.now(datetime.timezone.utc).isoformat(),'ci':{'sourceSha':os.environ.get('GITHUB_SHA'),'runId':runid},'releaseEligible':False,'sourceBaselineFingerprint':receipt['sourceFingerprint'],'upstreamCommit':head.decode().strip(),'serviceChanges':False,'databaseOrConfigurationExported':False,'registrationOrCredentialFilesRead':False,'refusedFiles':refused}
if refused:
report['status']='BLOCKED_CREDENTIAL_SCREENING';print('GITEA_CUSTOM_SOURCE_EXPORT_JSON '+json.dumps(report,separators=(',',':')));raise SystemExit(1)
if sum(len(v) for v in payload.values())>8*1024*1024:raise RuntimeError('SOURCE_EXPORT_SIZE_LIMIT')
work=base/('export-'+runid);work.mkdir(mode=0o700)
index={'kind':'GiteaCustomSourceReviewManifest','sourceBaselineFingerprint':receipt['sourceFingerprint'],'originalSourceHead':'287bf811487c04cd90bcf07aa04905cbaf2e4c2d','upstreamCommit':head.decode().strip(),'files':manifest,'scope':'Selected code only, not a system backup; no database, config, registration, environment, key or dependency cache files'}
payload['manifest.json']=(json.dumps(index,ensure_ascii=False,indent=2)+'\n').encode()
target=work/'gitea-custom-source.tar.gz'
with tarfile.open(target,'w:gz') as archive:
for name,body in sorted(payload.items()):
entry=tarfile.TarInfo(name);entry.size=len(body);entry.mode=0o600;entry.mtime=0;archive.addfile(entry,io.BytesIO(body))
report.update({'status':'SOURCE_CODE_SCREENED_AND_PACKED','fileCount':len(manifest),'archiveBytes':target.stat().st_size,'archiveSha256':hashlib.sha256(target.read_bytes()).hexdigest(),'privateArchive':str(target)})
(work/'receipt.json').write_text(json.dumps(report,indent=2)+'\n')
print('GITEA_CUSTOM_SOURCE_EXPORT_JSON '+json.dumps(report,separators=(',',':')))
for name,body in sorted(payload.items()):
if name=='manifest.json':continue
encoded=base64.b64encode(body).decode()
chunks=[encoded[i:i+8000] for i in range(0,len(encoded),8000)]
for i,chunk in enumerate(chunks):
print('GITEA_GIT_SOURCE_FILE_JSON '+json.dumps({'path':name,'sha256':hashlib.sha256(body).hexdigest(),'index':i,'chunks':len(chunks),'data':chunk},separators=(',',':')),flush=True)
PY
shell: bash
timeout-minutes: "15"
permissions:
contents: read
...
|
export
|
null
|
["ubuntu-latest"]
|
37649
|
2
|
1793533896
|
1793533896
|
1793533895
|
1793533896
|
|
0
|
|
0
|
Edit
Delete
|
|
46139
|
26804
|
116
|
5
|
31434941cf8cd89eb537ab7b217934a085b9544b
|
0
|
export
|
1
|
name: Read screened Git pack protocol source for n name: Read screened Git pack protocol source for native failures
"on":
push:
branches: [codex/runner-maintenance-diagnostic-20261101]
paths: [.gitea/workflows/gitea-git-protocol-review.yml]
workflow_dispatch:
jobs:
export:
name: export
runs-on: ubuntu-latest
if: github.repository == 'luoanwu/enterprise-platform'
steps:
- name: Screen only known source changes and produce a review archive
run: |
set -eu
python3 <<'PY'
import base64, datetime, hashlib, io, json, os, pathlib, re, socket, subprocess, tarfile
if os.geteuid()!=0 or socket.gethostname()!='iZ2zed3pt2nygf9dy1a9d2Z':raise RuntimeError('EXPECTED_HOST_REQUIRED')
os.umask(0o077)
runid=os.environ.get('GITHUB_RUN_ID')
if not runid or not runid.isdigit():raise RuntimeError('REAL_RUN_ID_REQUIRED')
base=pathlib.Path('/var/lib/eios-r1-maintenance')
if base.is_symlink() or not base.is_dir() or base.stat().st_uid!=0 or base.stat().st_mode&0o077:raise RuntimeError('PRIVATE_BASE_REQUIRED')
analysis=base/'analysis-26765'
receipt=json.loads((analysis/'receipt.json').read_text())
if receipt.get('status')!='PRIVATE_BASELINE_VERIFIED_AND_COMPATIBILITY_ANALYZED':raise RuntimeError('VERIFIED_BASELINE_REQUIRED')
source=base/'reviewed-26801'/'candidate-source'
live=pathlib.Path('/root/gitea-src')
upstream=analysis/'candidate-source'
env={'PATH':'/usr/local/bin:/usr/bin:/bin','HOME':str(base),'LANG':'C.UTF-8','GIT_CONFIG_NOSYSTEM':'1','GIT_CONFIG_GLOBAL':'/dev/null','GIT_OPTIONAL_LOCKS':'0','GIT_TERMINAL_PROMPT':'0'}
def git(path,*args):
r=subprocess.run(['git','--no-optional-locks','-c','core.hooksPath=/dev/null','-C',str(path),*args],capture_output=True,timeout=30,env=env)
return r.returncode,r.stdout
code,head=git(upstream,'rev-parse','HEAD')
if code or head.decode().strip()!='146cc3eec57174711eac0e0a0c7b38670c6e3922':raise RuntimeError('FIXED_OFFICIAL_SOURCE_REQUIRED')
# Export only files that require manual conflict or migration review.
# Already textually merged files and additive files stay on the server for native testing.
paths=['modules/git/'+name for name in ['delta.go','delta_test.go','packfile.go','packfile_test.go','packwriter.go','packwriter_test.go','packreader.go'] if (source/'modules/git'/name).is_file()]
def public_literals(text):
return set(re.findall(r'["\']([^"\'\n]{8,})["\']',text))
def suspicious(text,public,is_git_test=False):
issues=[]
for m in re.finditer(r'-----BEGIN (?:RSA |EC |OPENSSH )?PRIVATE KEY-----|(?:ghp_|github_pat_|AKIA[0-9A-Z]{16}|sk-(?:proj-)?)[A-Za-z0-9_/-]{12,}',text):
public_ssh=bool(re.match(r'sk-(?:ecdsa-sha2-nistp256|ssh-ed25519)@openssh\.com',text[m.start():])) and any(m.group(0) in v for v in public)
if not public_ssh:issues.append('credential-prefix-or-private-key')
if re.search(r'https?://[^/\s:@]+:[^/\s@]+@',text):issues.append('credential-in-url')
for match in re.finditer(r'(?i)(?:[A-Za-z0-9_]*(?:password|passwd|secret|token|api_?key|access_?key|private_?key)[A-Za-z0-9_]*)\s*(?::=|=|:)\s*["\']([^"\'\n]{8,})["\']',text):
value=match.group(1)
if value not in public:issues.append('nonpublic-credential-literal')
for match in re.finditer(r'(?i)(?:Bearer|Basic)\s+[A-Za-z0-9_+/=.-]{12,}',text):
if not any(match.group(0) in literal for literal in public):issues.append('nonpublic-authorization-literal')
for value in re.findall(r'["\']([A-Za-z0-9+/=_-]{32,})["\']',text):
if value not in public and len(set(value))>8 and not is_git_test:issues.append('nonpublic-long-opaque-literal')
if not is_git_test:
for value in re.findall(r'["\']([A-Fa-f0-9]{32,128})["\']',text):
if value not in public and len(set(value))>3:issues.append('nonpublic-long-hex-literal')
return sorted(set(issues))
payload={};manifest=[];refused=[]
for name in paths:
rel=pathlib.PurePosixPath(name)
if rel.is_absolute() or '..' in rel.parts or not rel.parts or rel.parts[0] not in ['models','routers','services','modules','templates','options'] or rel.suffix not in ['.go','.tmpl','.json']:raise RuntimeError('SOURCE_EXPORT_PATH_REFUSED')
p=source/name
if p.is_symlink() or not p.is_file():raise RuntimeError('SOURCE_EXPORT_NONREGULAR_FILE')
data=p.read_bytes()
if len(data)>2*1024*1024:raise RuntimeError('SOURCE_FILE_SIZE_LIMIT')
rc,public=git(upstream,'show','HEAD:'+name)
known=public_literals(public.decode(errors='replace')) if rc==0 else set()
candidates=[(name,data)]
for archive_name,body in candidates:
problems=suspicious(body.decode('utf-8'),known,name.startswith('modules/git/') and name.endswith('_test.go'))
if problems:
text=body.decode('utf-8');classification=[]
for m in re.finditer(r'-----BEGIN (?:RSA |EC |OPENSSH )?PRIVATE KEY-----|(?:ghp_|github_pat_|AKIA[0-9A-Z]{16}|sk-(?:proj-)?)[A-Za-z0-9_/-]{12,}',text):
classification.append({'category':'prefix','publicSshAlgorithm':bool(re.match(r'sk-(?:ecdsa-sha2-nistp256|ssh-ed25519)@openssh\.com',text[m.start():])),'presentInOfficialLiteral':any(m.group(0) in v for v in known)})
for m in re.finditer(r'(?i)(Bearer|Basic)\s+([A-Za-z0-9_+/=.-]{12,})',text):
line=text[text.rfind('\n',0,m.start())+1:m.start()]
valid=False
if m.group(1).lower()=='basic':
try:
decoded=base64.b64decode(m.group(2)+'='*((-len(m.group(2)))%4),validate=True).decode('utf-8')
valid=':' in decoded and decoded.isprintable()
except Exception:pass
classification.append({'category':'authorization','scheme':m.group(1).lower(),'commentLine':line.lstrip().startswith('//'),'validPrintableBasicCredential':valid,'presentInOfficialLiteral':any(m.group(0) in v for v in known)})
for value in re.findall(r'["\']([A-Za-z0-9+/=_-]{32,})["\']',text):
if value not in known and len(set(value))>8 and not (name.startswith('modules/git/') and name.endswith('_test.go')):
classification.append({'category':'opaque','publicGitSmartHttpMime':value in ['application/x-git-upload-pack-advertisement','application/x-git-receive-pack-advertisement','application/x-git-upload-pack-result','application/x-git-receive-pack-result']})
refused.append({'path':archive_name,'reasons':problems,'classification':classification});continue
payload[archive_name]=body
manifest.append({'path':archive_name,'bytes':len(body),'sha256':hashlib.sha256(body).hexdigest()})
report={'kind':'GiteaCustomSourceReviewExport','observedAtUTC':datetime.datetime.now(datetime.timezone.utc).isoformat(),'ci':{'sourceSha':os.environ.get('GITHUB_SHA'),'runId':runid},'releaseEligible':False,'sourceBaselineFingerprint':receipt['sourceFingerprint'],'upstreamCommit':head.decode().strip(),'serviceChanges':False,'databaseOrConfigurationExported':False,'registrationOrCredentialFilesRead':False,'refusedFiles':refused}
if refused:
report['status']='BLOCKED_CREDENTIAL_SCREENING';print('GITEA_CUSTOM_SOURCE_EXPORT_JSON '+json.dumps(report,separators=(',',':')));raise SystemExit(1)
if sum(len(v) for v in payload.values())>8*1024*1024:raise RuntimeError('SOURCE_EXPORT_SIZE_LIMIT')
work=base/('export-'+runid);work.mkdir(mode=0o700)
index={'kind':'GiteaCustomSourceReviewManifest','sourceBaselineFingerprint':receipt['sourceFingerprint'],'originalSourceHead':'287bf811487c04cd90bcf07aa04905cbaf2e4c2d','upstreamCommit':head.decode().strip(),'files':manifest,'scope':'Selected code only, not a system backup; no database, config, registration, environment, key or dependency cache files'}
payload['manifest.json']=(json.dumps(index,ensure_ascii=False,indent=2)+'\n').encode()
target=work/'gitea-custom-source.tar.gz'
with tarfile.open(target,'w:gz') as archive:
for name,body in sorted(payload.items()):
entry=tarfile.TarInfo(name);entry.size=len(body);entry.mode=0o600;entry.mtime=0;archive.addfile(entry,io.BytesIO(body))
report.update({'status':'SOURCE_CODE_SCREENED_AND_PACKED','fileCount':len(manifest),'archiveBytes':target.stat().st_size,'archiveSha256':hashlib.sha256(target.read_bytes()).hexdigest(),'privateArchive':str(target)})
(work/'receipt.json').write_text(json.dumps(report,indent=2)+'\n')
print('GITEA_CUSTOM_SOURCE_EXPORT_JSON '+json.dumps(report,separators=(',',':')))
PY
for name,body in sorted(payload.items()):
if name=='manifest.json':continue
encoded=base64.b64encode(body).decode()
chunks=[encoded[i:i+8000] for i in range(0,len(encoded),8000)]
for i,chunk in enumerate(chunks):
print('GITEA_GIT_SOURCE_FILE_JSON '+json.dumps({'path':name,'sha256':hashlib.sha256(body).hexdigest(),'index':i,'chunks':len(chunks),'data':chunk},separators=(',',':')),flush=True)
shell: bash
timeout-minutes: "15"
permissions:
contents: read
...
|
export
|
null
|
["ubuntu-latest"]
|
37592
|
2
|
1793529870
|
1793529870
|
1793529868
|
1793529870
|
|
0
|
|
0
|
Edit
Delete
|
|
46141
|
26806
|
116
|
5
|
a7f71fed666a2cbc34f044ddd15b27103eeb9605
|
0
|
export
|
1
|
name: Read screened Git pack protocol source for n name: Read screened Git pack protocol source for native failures
"on":
push:
branches: [codex/runner-maintenance-diagnostic-20261101]
paths: [.gitea/workflows/gitea-git-protocol-review.yml]
workflow_dispatch:
jobs:
export:
name: export
runs-on: ubuntu-latest
if: github.repository == 'luoanwu/enterprise-platform'
steps:
- name: Screen only known source changes and produce a review archive
run: |
set -eu
python3 <<'PY'
import base64, datetime, hashlib, io, json, os, pathlib, re, socket, subprocess, tarfile
if os.geteuid()!=0 or socket.gethostname()!='iZ2zed3pt2nygf9dy1a9d2Z':raise RuntimeError('EXPECTED_HOST_REQUIRED')
os.umask(0o077)
runid=os.environ.get('GITHUB_RUN_ID')
if not runid or not runid.isdigit():raise RuntimeError('REAL_RUN_ID_REQUIRED')
base=pathlib.Path('/var/lib/eios-r1-maintenance')
if base.is_symlink() or not base.is_dir() or base.stat().st_uid!=0 or base.stat().st_mode&0o077:raise RuntimeError('PRIVATE_BASE_REQUIRED')
analysis=base/'analysis-26765'
receipt=json.loads((analysis/'receipt.json').read_text())
if receipt.get('status')!='PRIVATE_BASELINE_VERIFIED_AND_COMPATIBILITY_ANALYZED':raise RuntimeError('VERIFIED_BASELINE_REQUIRED')
source=base/'reviewed-26801'/'candidate-source'
live=pathlib.Path('/root/gitea-src')
upstream=analysis/'candidate-source'
env={'PATH':'/usr/local/bin:/usr/bin:/bin','HOME':str(base),'LANG':'C.UTF-8','GIT_CONFIG_NOSYSTEM':'1','GIT_CONFIG_GLOBAL':'/dev/null','GIT_OPTIONAL_LOCKS':'0','GIT_TERMINAL_PROMPT':'0'}
def git(path,*args):
r=subprocess.run(['git','--no-optional-locks','-c','core.hooksPath=/dev/null','-C',str(path),*args],capture_output=True,timeout=30,env=env)
return r.returncode,r.stdout
code,head=git(upstream,'rev-parse','HEAD')
if code or head.decode().strip()!='146cc3eec57174711eac0e0a0c7b38670c6e3922':raise RuntimeError('FIXED_OFFICIAL_SOURCE_REQUIRED')
# Export only files that require manual conflict or migration review.
# Already textually merged files and additive files stay on the server for native testing.
paths=['modules/git/'+name for name in ['delta.go','delta_test.go','packfile.go','packfile_test.go','packwriter.go','packwriter_test.go','packreader.go'] if (source/'modules/git'/name).is_file()]
def public_literals(text):
return set(re.findall(r'["\']([^"\'\n]{8,})["\']',text))
def suspicious(text,public,is_git_test=False):
issues=[]
for m in re.finditer(r'-----BEGIN (?:RSA |EC |OPENSSH )?PRIVATE KEY-----|(?:ghp_|github_pat_|AKIA[0-9A-Z]{16}|sk-(?:proj-)?)[A-Za-z0-9_/-]{12,}',text):
public_ssh=bool(re.match(r'sk-(?:ecdsa-sha2-nistp256|ssh-ed25519)@openssh\.com',text[m.start():])) and any(m.group(0) in v for v in public)
if not public_ssh:issues.append('credential-prefix-or-private-key')
if re.search(r'https?://[^/\s:@]+:[^/\s@]+@',text):issues.append('credential-in-url')
for match in re.finditer(r'(?i)(?:[A-Za-z0-9_]*(?:password|passwd|secret|token|api_?key|access_?key|private_?key)[A-Za-z0-9_]*)\s*(?::=|=|:)\s*["\']([^"\'\n]{8,})["\']',text):
value=match.group(1)
if value not in public:issues.append('nonpublic-credential-literal')
for match in re.finditer(r'(?i)(?:Bearer|Basic)\s+[A-Za-z0-9_+/=.-]{12,}',text):
if not any(match.group(0) in literal for literal in public):issues.append('nonpublic-authorization-literal')
for value in re.findall(r'["\']([A-Za-z0-9+/=_-]{32,})["\']',text):
if value not in public and len(set(value))>8 and not is_git_test:issues.append('nonpublic-long-opaque-literal')
if not is_git_test:
for value in re.findall(r'["\']([A-Fa-f0-9]{32,128})["\']',text):
if value not in public and len(set(value))>3:issues.append('nonpublic-long-hex-literal')
return sorted(set(issues))
payload={};manifest=[];refused=[]
for name in paths:
rel=pathlib.PurePosixPath(name)
if rel.is_absolute() or '..' in rel.parts or not rel.parts or rel.parts[0] not in ['models','routers','services','modules','templates','options'] or rel.suffix not in ['.go','.tmpl','.json']:raise RuntimeError('SOURCE_EXPORT_PATH_REFUSED')
p=source/name
if p.is_symlink() or not p.is_file():raise RuntimeError('SOURCE_EXPORT_NONREGULAR_FILE')
data=p.read_bytes()
if len(data)>2*1024*1024:raise RuntimeError('SOURCE_FILE_SIZE_LIMIT')
rc,public=git(upstream,'show','HEAD:'+name)
known=public_literals(public.decode(errors='replace')) if rc==0 else set()
# Never export authorization-looking comment text, even when it is likely prose.
text=data.decode('utf-8')
text=re.sub(r'(?m)^(\s*//[^\n]*?)(?i:Basic|Bearer)\s+[A-Za-z0-9_+/=.-]{12,}[^\n]*$',r'\1[REDACTED_AUTHORIZATION_LOOKING_COMMENT]',text)
data=text.encode('utf-8')
candidates=[(name,data)]
for archive_name,body in candidates:
problems=suspicious(body.decode('utf-8'),known,name.startswith('modules/git/') and name.endswith('_test.go'))
if problems:
text=body.decode('utf-8');classification=[]
for m in re.finditer(r'-----BEGIN (?:RSA |EC |OPENSSH )?PRIVATE KEY-----|(?:ghp_|github_pat_|AKIA[0-9A-Z]{16}|sk-(?:proj-)?)[A-Za-z0-9_/-]{12,}',text):
classification.append({'category':'prefix','publicSshAlgorithm':bool(re.match(r'sk-(?:ecdsa-sha2-nistp256|ssh-ed25519)@openssh\.com',text[m.start():])),'presentInOfficialLiteral':any(m.group(0) in v for v in known)})
for m in re.finditer(r'(?i)(Bearer|Basic)\s+([A-Za-z0-9_+/=.-]{12,})',text):
line=text[text.rfind('\n',0,m.start())+1:m.start()]
valid=False
if m.group(1).lower()=='basic':
try:
decoded=base64.b64decode(m.group(2)+'='*((-len(m.group(2)))%4),validate=True).decode('utf-8')
valid=':' in decoded and decoded.isprintable()
except Exception:pass
classification.append({'category':'authorization','scheme':m.group(1).lower(),'commentLine':line.lstrip().startswith('//'),'validPrintableBasicCredential':valid,'presentInOfficialLiteral':any(m.group(0) in v for v in known)})
for value in re.findall(r'["\']([A-Za-z0-9+/=_-]{32,})["\']',text):
if value not in known and len(set(value))>8 and not (name.startswith('modules/git/') and name.endswith('_test.go')):
classification.append({'category':'opaque','publicGitSmartHttpMime':value in ['application/x-git-upload-pack-advertisement','application/x-git-receive-pack-advertisement','application/x-git-upload-pack-result','application/x-git-receive-pack-result']})
refused.append({'path':archive_name,'reasons':problems,'classification':classification});continue
payload[archive_name]=body
manifest.append({'path':archive_name,'bytes':len(body),'sha256':hashlib.sha256(body).hexdigest()})
report={'kind':'GiteaCustomSourceReviewExport','observedAtUTC':datetime.datetime.now(datetime.timezone.utc).isoformat(),'ci':{'sourceSha':os.environ.get('GITHUB_SHA'),'runId':runid},'releaseEligible':False,'sourceBaselineFingerprint':receipt['sourceFingerprint'],'upstreamCommit':head.decode().strip(),'serviceChanges':False,'databaseOrConfigurationExported':False,'registrationOrCredentialFilesRead':False,'refusedFiles':refused}
if refused:
report['status']='BLOCKED_CREDENTIAL_SCREENING';print('GITEA_CUSTOM_SOURCE_EXPORT_JSON '+json.dumps(report,separators=(',',':')));raise SystemExit(1)
if sum(len(v) for v in payload.values())>8*1024*1024:raise RuntimeError('SOURCE_EXPORT_SIZE_LIMIT')
work=base/('export-'+runid);work.mkdir(mode=0o700)
index={'kind':'GiteaCustomSourceReviewManifest','sourceBaselineFingerprint':receipt['sourceFingerprint'],'originalSourceHead':'287bf811487c04cd90bcf07aa04905cbaf2e4c2d','upstreamCommit':head.decode().strip(),'files':manifest,'scope':'Selected code only, not a system backup; no database, config, registration, environment, key or dependency cache files'}
payload['manifest.json']=(json.dumps(index,ensure_ascii=False,indent=2)+'\n').encode()
target=work/'gitea-custom-source.tar.gz'
with tarfile.open(target,'w:gz') as archive:
for name,body in sorted(payload.items()):
entry=tarfile.TarInfo(name);entry.size=len(body);entry.mode=0o600;entry.mtime=0;archive.addfile(entry,io.BytesIO(body))
report.update({'status':'SOURCE_CODE_SCREENED_AND_PACKED','fileCount':len(manifest),'archiveBytes':target.stat().st_size,'archiveSha256':hashlib.sha256(target.read_bytes()).hexdigest(),'privateArchive':str(target)})
(work/'receipt.json').write_text(json.dumps(report,indent=2)+'\n')
print('GITEA_CUSTOM_SOURCE_EXPORT_JSON '+json.dumps(report,separators=(',',':')))
for name,body in sorted(payload.items()):
if name=='manifest.json':continue
encoded=base64.b64encode(body).decode()
chunks=[encoded[i:i+8000] for i in range(0,len(encoded),8000)]
for i,chunk in enumerate(chunks):
print('GITEA_GIT_SOURCE_FILE_JSON '+json.dumps({'path':name,'sha256':hashlib.sha256(body).hexdigest(),'index':i,'chunks':len(chunks),'data':chunk},separators=(',',':')),flush=True)
PY
shell: bash
timeout-minutes: "15"
permissions:
contents: read
...
|
export
|
null
|
["ubuntu-latest"]
|
37594
|
1
|
1793529994
|
1793529994
|
1793529992
|
1793529994
|
|
0
|
|
0
|
Edit
Delete
|
|
46140
|
26805
|
116
|
5
|
391e7f1701d2ce7fb15776b456aff8c605f86d18
|
0
|
export
|
1
|
name: Read screened Git pack protocol source for n name: Read screened Git pack protocol source for native failures
"on":
push:
branches: [codex/runner-maintenance-diagnostic-20261101]
paths: [.gitea/workflows/gitea-git-protocol-review.yml]
workflow_dispatch:
jobs:
export:
name: export
runs-on: ubuntu-latest
if: github.repository == 'luoanwu/enterprise-platform'
steps:
- name: Screen only known source changes and produce a review archive
run: |
set -eu
python3 <<'PY'
import base64, datetime, hashlib, io, json, os, pathlib, re, socket, subprocess, tarfile
if os.geteuid()!=0 or socket.gethostname()!='iZ2zed3pt2nygf9dy1a9d2Z':raise RuntimeError('EXPECTED_HOST_REQUIRED')
os.umask(0o077)
runid=os.environ.get('GITHUB_RUN_ID')
if not runid or not runid.isdigit():raise RuntimeError('REAL_RUN_ID_REQUIRED')
base=pathlib.Path('/var/lib/eios-r1-maintenance')
if base.is_symlink() or not base.is_dir() or base.stat().st_uid!=0 or base.stat().st_mode&0o077:raise RuntimeError('PRIVATE_BASE_REQUIRED')
analysis=base/'analysis-26765'
receipt=json.loads((analysis/'receipt.json').read_text())
if receipt.get('status')!='PRIVATE_BASELINE_VERIFIED_AND_COMPATIBILITY_ANALYZED':raise RuntimeError('VERIFIED_BASELINE_REQUIRED')
source=base/'reviewed-26801'/'candidate-source'
live=pathlib.Path('/root/gitea-src')
upstream=analysis/'candidate-source'
env={'PATH':'/usr/local/bin:/usr/bin:/bin','HOME':str(base),'LANG':'C.UTF-8','GIT_CONFIG_NOSYSTEM':'1','GIT_CONFIG_GLOBAL':'/dev/null','GIT_OPTIONAL_LOCKS':'0','GIT_TERMINAL_PROMPT':'0'}
def git(path,*args):
r=subprocess.run(['git','--no-optional-locks','-c','core.hooksPath=/dev/null','-C',str(path),*args],capture_output=True,timeout=30,env=env)
return r.returncode,r.stdout
code,head=git(upstream,'rev-parse','HEAD')
if code or head.decode().strip()!='146cc3eec57174711eac0e0a0c7b38670c6e3922':raise RuntimeError('FIXED_OFFICIAL_SOURCE_REQUIRED')
# Export only files that require manual conflict or migration review.
# Already textually merged files and additive files stay on the server for native testing.
paths=['modules/git/'+name for name in ['delta.go','delta_test.go','packfile.go','packfile_test.go','packwriter.go','packwriter_test.go','packreader.go'] if (source/'modules/git'/name).is_file()]
def public_literals(text):
return set(re.findall(r'["\']([^"\'\n]{8,})["\']',text))
def suspicious(text,public,is_git_test=False):
issues=[]
for m in re.finditer(r'-----BEGIN (?:RSA |EC |OPENSSH )?PRIVATE KEY-----|(?:ghp_|github_pat_|AKIA[0-9A-Z]{16}|sk-(?:proj-)?)[A-Za-z0-9_/-]{12,}',text):
public_ssh=bool(re.match(r'sk-(?:ecdsa-sha2-nistp256|ssh-ed25519)@openssh\.com',text[m.start():])) and any(m.group(0) in v for v in public)
if not public_ssh:issues.append('credential-prefix-or-private-key')
if re.search(r'https?://[^/\s:@]+:[^/\s@]+@',text):issues.append('credential-in-url')
for match in re.finditer(r'(?i)(?:[A-Za-z0-9_]*(?:password|passwd|secret|token|api_?key|access_?key|private_?key)[A-Za-z0-9_]*)\s*(?::=|=|:)\s*["\']([^"\'\n]{8,})["\']',text):
value=match.group(1)
if value not in public:issues.append('nonpublic-credential-literal')
for match in re.finditer(r'(?i)(?:Bearer|Basic)\s+[A-Za-z0-9_+/=.-]{12,}',text):
if not any(match.group(0) in literal for literal in public):issues.append('nonpublic-authorization-literal')
for value in re.findall(r'["\']([A-Za-z0-9+/=_-]{32,})["\']',text):
if value not in public and len(set(value))>8 and not is_git_test:issues.append('nonpublic-long-opaque-literal')
if not is_git_test:
for value in re.findall(r'["\']([A-Fa-f0-9]{32,128})["\']',text):
if value not in public and len(set(value))>3:issues.append('nonpublic-long-hex-literal')
return sorted(set(issues))
payload={};manifest=[];refused=[]
for name in paths:
rel=pathlib.PurePosixPath(name)
if rel.is_absolute() or '..' in rel.parts or not rel.parts or rel.parts[0] not in ['models','routers','services','modules','templates','options'] or rel.suffix not in ['.go','.tmpl','.json']:raise RuntimeError('SOURCE_EXPORT_PATH_REFUSED')
p=source/name
if p.is_symlink() or not p.is_file():raise RuntimeError('SOURCE_EXPORT_NONREGULAR_FILE')
data=p.read_bytes()
if len(data)>2*1024*1024:raise RuntimeError('SOURCE_FILE_SIZE_LIMIT')
rc,public=git(upstream,'show','HEAD:'+name)
known=public_literals(public.decode(errors='replace')) if rc==0 else set()
# Never export authorization-looking comment text, even when it is likely prose.
text=data.decode('utf-8')
text=re.sub(r'(?m)^(\s*//[^\n]*?)(?i:Basic|Bearer)\s+[A-Za-z0-9_+/=.-]{12,}[^\n]*$',r'\1[REDACTED_AUTHORIZATION_LOOKING_COMMENT]',text)
data=text.encode('utf-8')
candidates=[(name,data)]
for archive_name,body in candidates:
problems=suspicious(body.decode('utf-8'),known,name.startswith('modules/git/') and name.endswith('_test.go'))
if problems:
text=body.decode('utf-8');classification=[]
for m in re.finditer(r'-----BEGIN (?:RSA |EC |OPENSSH )?PRIVATE KEY-----|(?:ghp_|github_pat_|AKIA[0-9A-Z]{16}|sk-(?:proj-)?)[A-Za-z0-9_/-]{12,}',text):
classification.append({'category':'prefix','publicSshAlgorithm':bool(re.match(r'sk-(?:ecdsa-sha2-nistp256|ssh-ed25519)@openssh\.com',text[m.start():])),'presentInOfficialLiteral':any(m.group(0) in v for v in known)})
for m in re.finditer(r'(?i)(Bearer|Basic)\s+([A-Za-z0-9_+/=.-]{12,})',text):
line=text[text.rfind('\n',0,m.start())+1:m.start()]
valid=False
if m.group(1).lower()=='basic':
try:
decoded=base64.b64decode(m.group(2)+'='*((-len(m.group(2)))%4),validate=True).decode('utf-8')
valid=':' in decoded and decoded.isprintable()
except Exception:pass
classification.append({'category':'authorization','scheme':m.group(1).lower(),'commentLine':line.lstrip().startswith('//'),'validPrintableBasicCredential':valid,'presentInOfficialLiteral':any(m.group(0) in v for v in known)})
for value in re.findall(r'["\']([A-Za-z0-9+/=_-]{32,})["\']',text):
if value not in known and len(set(value))>8 and not (name.startswith('modules/git/') and name.endswith('_test.go')):
classification.append({'category':'opaque','publicGitSmartHttpMime':value in ['application/x-git-upload-pack-advertisement','application/x-git-receive-pack-advertisement','application/x-git-upload-pack-result','application/x-git-receive-pack-result']})
refused.append({'path':archive_name,'reasons':problems,'classification':classification});continue
payload[archive_name]=body
manifest.append({'path':archive_name,'bytes':len(body),'sha256':hashlib.sha256(body).hexdigest()})
report={'kind':'GiteaCustomSourceReviewExport','observedAtUTC':datetime.datetime.now(datetime.timezone.utc).isoformat(),'ci':{'sourceSha':os.environ.get('GITHUB_SHA'),'runId':runid},'releaseEligible':False,'sourceBaselineFingerprint':receipt['sourceFingerprint'],'upstreamCommit':head.decode().strip(),'serviceChanges':False,'databaseOrConfigurationExported':False,'registrationOrCredentialFilesRead':False,'refusedFiles':refused}
if refused:
report['status']='BLOCKED_CREDENTIAL_SCREENING';print('GITEA_CUSTOM_SOURCE_EXPORT_JSON '+json.dumps(report,separators=(',',':')));raise SystemExit(1)
if sum(len(v) for v in payload.values())>8*1024*1024:raise RuntimeError('SOURCE_EXPORT_SIZE_LIMIT')
work=base/('export-'+runid);work.mkdir(mode=0o700)
index={'kind':'GiteaCustomSourceReviewManifest','sourceBaselineFingerprint':receipt['sourceFingerprint'],'originalSourceHead':'287bf811487c04cd90bcf07aa04905cbaf2e4c2d','upstreamCommit':head.decode().strip(),'files':manifest,'scope':'Selected code only, not a system backup; no database, config, registration, environment, key or dependency cache files'}
payload['manifest.json']=(json.dumps(index,ensure_ascii=False,indent=2)+'\n').encode()
target=work/'gitea-custom-source.tar.gz'
with tarfile.open(target,'w:gz') as archive:
for name,body in sorted(payload.items()):
entry=tarfile.TarInfo(name);entry.size=len(body);entry.mode=0o600;entry.mtime=0;archive.addfile(entry,io.BytesIO(body))
report.update({'status':'SOURCE_CODE_SCREENED_AND_PACKED','fileCount':len(manifest),'archiveBytes':target.stat().st_size,'archiveSha256':hashlib.sha256(target.read_bytes()).hexdigest(),'privateArchive':str(target)})
(work/'receipt.json').write_text(json.dumps(report,indent=2)+'\n')
print('GITEA_CUSTOM_SOURCE_EXPORT_JSON '+json.dumps(report,separators=(',',':')))
PY
for name,body in sorted(payload.items()):
if name=='manifest.json':continue
encoded=base64.b64encode(body).decode()
chunks=[encoded[i:i+8000] for i in range(0,len(encoded),8000)]
for i,chunk in enumerate(chunks):
print('GITEA_GIT_SOURCE_FILE_JSON '+json.dumps({'path':name,'sha256':hashlib.sha256(body).hexdigest(),'index':i,'chunks':len(chunks),'data':chunk},separators=(',',':')),flush=True)
shell: bash
timeout-minutes: "15"
permissions:
contents: read
...
|
export
|
null
|
["ubuntu-latest"]
|
37593
|
2
|
1793529920
|
1793529920
|
1793529919
|
1793529920
|
|
0
|
|
0
|
Edit
Delete
|
|
46123
|
26795
|
116
|
5
|
56fb5db0157e53e9781664f499e19dd85b037d3b
|
0
|
inspect
|
1
|
name: Read private candidate failed test details
& name: Read private candidate failed test details
"on":
push:
branches: [codex/runner-maintenance-diagnostic-20261101]
paths: [.gitea/workflows/gitea-failed-test-detail.yml]
jobs:
inspect:
name: inspect
runs-on: ubuntu-latest
if: github.repository == 'luoanwu/enterprise-platform'
steps:
- name: Read only fixed private synthetic test failure
run: |
set -eu
python3 <<'PYCODE'
import pathlib,re,json
root=pathlib.Path('/var/lib/eios-r1-maintenance/reviewed-26793')
text=(root/'TARGETED_TESTS.stdout').read_text(errors='replace').splitlines()
for i,line in enumerate(text):
if '--- FAIL: TestToActionArtifact' in line:
lines=text[i:i+28]
assert not any(re.search(r'(?i)password|secret|authorization|BEGIN .*PRIVATE KEY|://[^ /]+@',s) for s in lines)
print('SYNTHETIC_TEST_FAILURE '+json.dumps(lines))
p=root/'candidate-source/modules/git/packfile_test.go'
lines=p.read_text().splitlines()
print('PACKFILE_TEST_TYPE '+json.dumps(lines[220:260]))
PYCODE
shell: bash
timeout-minutes: "3"
permissions:
contents: read
...
|
inspect
|
null
|
["ubuntu-latest"]
|
37576
|
1
|
1793529187
|
1793529187
|
1793529186
|
1793529187
|
|
0
|
|
0
|
Edit
Delete
|
|
46402
|
26905
|
116
|
5
|
fa1f638223ee62ac0fae19b177087232f58f3004
|
0
|
inventory
|
1
|
name: Read owned maintenance directory sizes witho name: Read owned maintenance directory sizes without cleanup
"on":
workflow_dispatch:
jobs:
inventory:
name: inventory
runs-on: ubuntu-latest
if: github.repository == 'luoanwu/enterprise-platform'
steps:
- name: Inspect fixed task directory metadata only
run: |
set -eu
python3 <<'PY'
import os,re,subprocess
sha=os.environ['GITHUB_SHA'];repo='/home/git/repositories/luoanwu/enterprise-platform.git'
if not re.fullmatch('[a-f0-9]{40}',sha):raise RuntimeError('FIXED_SOURCE_REQUIRED')
r=subprocess.run(['git','-c','safe.directory='+repo,'-C',repo,'show',sha+':ops/gitea-r1/inventory-window-capacity.py'],capture_output=True,timeout=20)
if r.returncode:raise RuntimeError('FIXED_INVENTORY_SOURCE_REQUIRED')
exec(compile(r.stdout,'inventory-window-capacity.py','exec'))
PY
shell: bash
timeout-minutes: "10"
permissions:
contents: read
...
|
inventory
|
null
|
["ubuntu-latest"]
|
37779
|
1
|
1793578244
|
1793578253
|
1793578243
|
1793578254
|
|
0
|
|
0
|
Edit
Delete
|
|
46403
|
26906
|
116
|
5
|
37718a544cac68c68a783bb29e612c72d478acd0
|
0
|
inventory
|
1
|
name: Read owned maintenance directory sizes witho name: Read owned maintenance directory sizes without cleanup
"on":
workflow_dispatch:
jobs:
inventory:
name: inventory
runs-on: ubuntu-latest
if: github.repository == 'luoanwu/enterprise-platform'
steps:
- name: Inspect fixed task directory metadata only
run: |
set -eu
python3 <<'PY'
import os,re,subprocess
sha=os.environ['GITHUB_SHA'];repo='/home/git/repositories/luoanwu/enterprise-platform.git'
if not re.fullmatch('[a-f0-9]{40}',sha):raise RuntimeError('FIXED_SOURCE_REQUIRED')
r=subprocess.run(['git','-c','safe.directory='+repo,'-C',repo,'show',sha+':ops/gitea-r1/inventory-window-capacity.py'],capture_output=True,timeout=20)
if r.returncode:raise RuntimeError('FIXED_INVENTORY_SOURCE_REQUIRED')
exec(compile(r.stdout,'inventory-window-capacity.py','exec'))
PY
shell: bash
timeout-minutes: "10"
permissions:
contents: read
...
|
inventory
|
null
|
["ubuntu-latest"]
|
37780
|
2
|
1793578334
|
1793578335
|
1793578332
|
1793578335
|
|
0
|
|
0
|
Edit
Delete
|
|
46405
|
26908
|
116
|
5
|
11ac9dc1eaad0663c9b25cb3fcfd175f18e7d444
|
0
|
inventory
|
1
|
name: Read owned maintenance directory sizes witho name: Read owned maintenance directory sizes without cleanup
"on":
workflow_dispatch:
jobs:
inventory:
name: inventory
runs-on: ubuntu-latest
if: github.repository == 'luoanwu/enterprise-platform'
steps:
- name: Inspect fixed task directory metadata only
run: |
set -eu
python3 <<'PY'
import os,re,subprocess
sha=os.environ['GITHUB_SHA'];repo='/home/git/repositories/luoanwu/enterprise-platform.git'
if not re.fullmatch('[a-f0-9]{40}',sha):raise RuntimeError('FIXED_SOURCE_REQUIRED')
r=subprocess.run(['git','-c','safe.directory='+repo,'-C',repo,'show',sha+':ops/gitea-r1/inventory-window-capacity.py'],capture_output=True,timeout=20)
if r.returncode:raise RuntimeError('FIXED_INVENTORY_SOURCE_REQUIRED')
exec(compile(r.stdout,'inventory-window-capacity.py','exec'))
PY
shell: bash
timeout-minutes: "10"
permissions:
contents: read
...
|
inventory
|
null
|
["ubuntu-latest"]
|
37782
|
1
|
1793578414
|
1793578415
|
1793578411
|
1793578415
|
|
0
|
|
0
|
Edit
Delete
|
|
46406
|
26909
|
116
|
5
|
ccd4e8a1af1b63779fb6694497e0b6846b8374c7
|
0
|
inventory
|
1
|
name: Read owned maintenance directory sizes witho name: Read owned maintenance directory sizes without cleanup
"on":
workflow_dispatch:
jobs:
inventory:
name: inventory
runs-on: ubuntu-latest
if: github.repository == 'luoanwu/enterprise-platform'
steps:
- name: Inspect fixed task directory metadata only
run: |
set -eu
python3 <<'PY'
import os,re,subprocess
sha=os.environ['GITHUB_SHA'];repo='/home/git/repositories/luoanwu/enterprise-platform.git'
if not re.fullmatch('[a-f0-9]{40}',sha):raise RuntimeError('FIXED_SOURCE_REQUIRED')
r=subprocess.run(['git','-c','safe.directory='+repo,'-C',repo,'show',sha+':ops/gitea-r1/inventory-window-capacity.py'],capture_output=True,timeout=20)
if r.returncode:raise RuntimeError('FIXED_INVENTORY_SOURCE_REQUIRED')
exec(compile(r.stdout,'inventory-window-capacity.py','exec'))
PY
shell: bash
timeout-minutes: "10"
permissions:
contents: read
...
|
inventory
|
null
|
["ubuntu-latest"]
|
37783
|
1
|
1793578552
|
1793578555
|
1793578551
|
1793578556
|
|
0
|
|
0
|
Edit
Delete
|
|
46407
|
26910
|
116
|
5
|
63e4dc2d4fee49fe7bab80b0d076349ea6ebd94e
|
0
|
inventory
|
1
|
name: Read owned maintenance directory sizes witho name: Read owned maintenance directory sizes without cleanup
"on":
workflow_dispatch:
jobs:
inventory:
name: inventory
runs-on: ubuntu-latest
if: github.repository == 'luoanwu/enterprise-platform'
steps:
- name: Inspect fixed task directory metadata only
run: |
set -eu
python3 <<'PY'
import os,re,subprocess
sha=os.environ['GITHUB_SHA'];repo='/home/git/repositories/luoanwu/enterprise-platform.git'
if not re.fullmatch('[a-f0-9]{40}',sha):raise RuntimeError('FIXED_SOURCE_REQUIRED')
r=subprocess.run(['git','-c','safe.directory='+repo,'-C',repo,'show',sha+':ops/gitea-r1/inventory-window-capacity.py'],capture_output=True,timeout=20)
if r.returncode:raise RuntimeError('FIXED_INVENTORY_SOURCE_REQUIRED')
exec(compile(r.stdout,'inventory-window-capacity.py','exec'))
PY
shell: bash
timeout-minutes: "10"
permissions:
contents: read
...
|
inventory
|
null
|
["ubuntu-latest"]
|
37784
|
1
|
1793579034
|
1793579036
|
1793579033
|
1793579037
|
|
0
|
|
0
|
Edit
Delete
|
|
46429
|
26915
|
116
|
5
|
135dcf751613b903b449be1852b697472becb3ef
|
0
|
inventory
|
1
|
name: Read owned maintenance directory sizes witho name: Read owned maintenance directory sizes without cleanup
"on":
workflow_dispatch:
jobs:
inventory:
name: inventory
runs-on: ubuntu-latest
if: github.repository == 'luoanwu/enterprise-platform'
steps:
- name: Inspect fixed task directory metadata only
run: |
set -eu
python3 <<'PY'
import os,re,subprocess
sha=os.environ['GITHUB_SHA'];repo='/home/git/repositories/luoanwu/enterprise-platform.git'
if not re.fullmatch('[a-f0-9]{40}',sha):raise RuntimeError('FIXED_SOURCE_REQUIRED')
r=subprocess.run(['git','-c','safe.directory='+repo,'-C',repo,'show',sha+':ops/gitea-r1/inventory-window-capacity.py'],capture_output=True,timeout=20)
if r.returncode:raise RuntimeError('FIXED_INVENTORY_SOURCE_REQUIRED')
exec(compile(r.stdout,'inventory-window-capacity.py','exec'))
PY
shell: bash
timeout-minutes: "10"
permissions:
contents: read
...
|
inventory
|
null
|
["ubuntu-latest"]
|
37798
|
1
|
1793580544
|
1793580556
|
1793580291
|
1793580557
|
|
0
|
|
0
|
Edit
Delete
|
|
46155
|
26820
|
116
|
5
|
99377766818e8de375eb4b5d5e14b24b83a08fc2
|
0
|
inspect
|
1
|
name: Read only obsolete owner guard source window name: Read only obsolete owner guard source windows
"on":
push:
branches: [codex/runner-maintenance-diagnostic-20261101]
paths: [.gitea/workflows/gitea-owner-guard-diagnostic.yml]
jobs:
inspect:
name: inspect
runs-on: ubuntu-latest
if: github.repository == 'luoanwu/enterprise-platform'
steps:
- name: Read only fixed private synthetic test failure
run: |
set -eu
python3 <<'PYCODE'
import pathlib,json,os
p=pathlib.Path('/var/lib/eios-r1-maintenance/reviewed-26819/candidate-source/routers/web/repo/setting/setting.go')
text=p.read_text().splitlines()
windows=[]
for i,line in enumerate(text):
if 'ctx.Repo.IsOwner' in line:windows.append({'line':i+1,'source':text[max(0,i-12):i+17]})
print('GITEA_PERMISSION_SOURCE_DIAG_JSON '+json.dumps({'kind':'PermissionSourceReadOnlyDiagnosis','ci':{'sourceSha':os.environ.get('GITHUB_SHA'),'runId':os.environ.get('GITHUB_RUN_ID')},'windows':windows,'mutations':False,'releaseEligible':False},separators=(',',':')),flush=True)
PYCODE
shell: bash
timeout-minutes: "3"
permissions:
contents: read
...
|
inspect
|
null
|
["ubuntu-latest"]
|
37608
|
1
|
1793531041
|
1793531041
|
1793531040
|
1793531041
|
|
0
|
|
0
|
Edit
Delete
|