name: Update script timestamp on .sh changes on: push: branches: - main paths: - "ct/**/*.sh" - "install/**/*.sh" - "tools/**/*.sh" - "turnkey/**/*.sh" - "vm/**/*.sh" jobs: update-script-timestamp: runs-on: self-hosted steps: - name: Checkout Repository uses: actions/checkout@v4 with: fetch-depth: 0 - name: Get changed .sh files and derive slugs id: slugs run: | changed=$(git diff --name-only "${{ github.event.before }}" "${{ github.event.after }}" -- ct/ install/ tools/ turnkey/ vm/ | grep '\.sh$' || true) if [[ -z "$changed" ]]; then echo "No .sh files changed in ct/, install/, tools/, turnkey/, or vm/." echo "count=0" >> "$GITHUB_OUTPUT" exit 0 fi declare -A seen slugs="" for f in $changed; do [[ -f "$f" ]] || continue base="${f##*/}" base="${base%.sh}" if [[ "$f" == install/* && "$base" == *-install ]]; then slug="${base%-install}" else slug="$base" fi if [[ -z "${seen[$slug]:-}" ]]; then seen[$slug]=1 slugs="$slugs $slug" fi done slugs=$(echo $slugs | xargs -n1 | sort -u) if [[ -z "$slugs" ]]; then echo "No slugs to update." echo "count=0" >> "$GITHUB_OUTPUT" exit 0 fi echo "$slugs" > changed_slugs.txt echo "count=$(echo "$slugs" | wc -w)" >> "$GITHUB_OUTPUT" - name: Parse PR number from merge commit id: pr run: | re='#([0-9]+)' if [[ "$COMMIT_MSG" =~ $re ]]; then echo "number=${BASH_REMATCH[1]}" >> "$GITHUB_OUTPUT" else echo "number=" >> "$GITHUB_OUTPUT" fi env: COMMIT_MSG: ${{ github.event.head_commit.message }} - name: Update script timestamps in PocketBase if: steps.slugs.outputs.count != '0' env: POCKETBASE_URL: ${{ secrets.POCKETBASE_URL }} POCKETBASE_COLLECTION: ${{ secrets.POCKETBASE_COLLECTION }} POCKETBASE_ADMIN_EMAIL: ${{ secrets.POCKETBASE_ADMIN_EMAIL }} POCKETBASE_ADMIN_PASSWORD: ${{ secrets.POCKETBASE_ADMIN_PASSWORD }} COMMIT_URL: ${{ github.server_url }}/${{ github.repository }}/commit/${{ github.sha }} PR_URL: ${{ steps.pr.outputs.number != '' && format('{0}/{1}/pull/{2}', github.server_url, github.repository, steps.pr.outputs.number) || '' }} run: | node << 'ENDSCRIPT' (async function() { const fs = require('fs'); const https = require('https'); const http = require('http'); const url = require('url'); function request(fullUrl, opts) { return new Promise(function(resolve, reject) { const u = url.parse(fullUrl); const isHttps = u.protocol === 'https:'; const body = opts.body; const options = { hostname: u.hostname, port: u.port || (isHttps ? 443 : 80), path: u.path, method: opts.method || 'GET', headers: opts.headers || {} }; if (body) options.headers['Content-Length'] = Buffer.byteLength(body); const lib = isHttps ? https : http; const req = lib.request(options, function(res) { let data = ''; res.on('data', function(chunk) { data += chunk; }); res.on('end', function() { resolve({ ok: res.statusCode >= 200 && res.statusCode < 300, statusCode: res.statusCode, body: data }); }); }); req.on('error', reject); if (body) req.write(body); req.end(); }); } const raw = process.env.POCKETBASE_URL.replace(/\/$/, ''); const apiBase = /\/api$/i.test(raw) ? raw : raw + '/api'; const coll = process.env.POCKETBASE_COLLECTION; const slugsText = fs.readFileSync('changed_slugs.txt', 'utf8').trim(); const slugs = slugsText ? slugsText.split(/\s+/).filter(Boolean) : []; if (slugs.length === 0) { console.log('No slugs to update.'); return; } const authUrl = apiBase + '/collections/users/auth-with-password'; const authBody = JSON.stringify({ identity: process.env.POCKETBASE_ADMIN_EMAIL, password: process.env.POCKETBASE_ADMIN_PASSWORD }); const authRes = await request(authUrl, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: authBody }); if (!authRes.ok) { throw new Error('Auth failed: ' + authRes.body); } const token = JSON.parse(authRes.body).token; const recordsUrl = apiBase + '/collections/' + encodeURIComponent(coll) + '/records'; for (const slug of slugs) { const filter = "(slug='" + slug.replace(/'/g, "''") + "')"; const listRes = await request(recordsUrl + '?filter=' + encodeURIComponent(filter) + '&perPage=1', { headers: { 'Authorization': token } }); const list = JSON.parse(listRes.body); const record = list.items && list.items[0]; if (!record) { console.log('Slug not in DB, skipping: ' + slug); continue; } const patchRes = await request(recordsUrl + '/' + record.id, { method: 'PATCH', headers: { 'Authorization': token, 'Content-Type': 'application/json' }, body: JSON.stringify({ name: record.name || record.slug, last_update_commit: process.env.PR_URL || process.env.COMMIT_URL || '' }) }); if (!patchRes.ok) { console.warn('PATCH failed for slug ' + slug + ': ' + patchRes.body); continue; } console.log('Updated timestamp for slug: ' + slug); } console.log('Done.'); })().catch(e => { console.error(e); process.exit(1); }); ENDSCRIPT shell: bash