From e27ba808b241bdc41b72cf9a81623665e517c036 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Tue, 8 Sep 2026 10:30:32 +0000 Subject: [PATCH 1/2] Update DBLP GQL publications (weekly) --- concepts/graph-query-language.scroll | 17 +---------------- 1 file changed, 1 insertion(+), 16 deletions(-) diff --git a/concepts/graph-query-language.scroll b/concepts/graph-query-language.scroll index afbcaa34e04d..d6979fd02acd 100644 --- a/concepts/graph-query-language.scroll +++ b/concepts/graph-query-language.scroll @@ -54,21 +54,6 @@ wikipedia https://en.wikipedia.org/wiki/Graph_Query_Language appeared 2024 dblp https://dblp.org/search?q=GQL+graph+query+language - hits 15 + hits 0 publications year|title|doi|url - 2026|GQL Rules: Deterministic and Safe Rule-Based Materialization for Property Graphs|10.1109/ACCESS.2026.3686122|https://doi.org/10.1109/ACCESS.2026.3686122 - 2025|GQL and SQL/PGQ: Theoretical Models and Expressive Power|10.14778/3725688.3725707|https://www.vldb.org/pvldb/vol18/p1798-libkin.pdf - 2025|GQL-QL: A Reinforcement Learning Framework for High-Coverage Automated Testing of GraphQL APIs|10.1007/978-3-032-25035-3_6|https://doi.org/10.1007/978-3-032-25035-3_6 - 2025|Mine Graph Rule: A New GQL Operator for Mining Association Rules in Property Graph Databases|10.1007/S00778-025-00934-8|https://doi.org/10.1007/s00778-025-00934-8 - 2025|Parallel and Distributed SQL/PGQ Query Processing for Property Graphs|10.1007/978-3-032-02215-8_12|https://doi.org/10.1007/978-3-032-02215-8_12 - 2025|Towards Cross-Model Efficiency in SQL/PGQ|10.1145/3735546.3735860|https://doi.org/10.1145/3735546.3735860 - 2025|Towards Cross-Model Efficiency in SQL/PGQ|10.48550/ARXIV.2505.07595|https://doi.org/10.48550/arXiv.2505.07595 - 2024|Chimera: A system design of dual storage and traversal-join unified query processing for SQL/PGQ|10.14778/3705829.3705845|https://www.vldb.org/pvldb/vol18/p279-lee.pdf - 2024|GQL and SQL/PGQ: Theoretical Models and Expressive Power|10.48550/ARXIV.2409.01102|https://doi.org/10.48550/arXiv.2409.01102 - 2023|DuckPGQ: Bringing SQL/PGQ to DuckDB|10.14778/3611540.3611614|https://www.vldb.org/pvldb/vol16/p4034-wolde.pdf - 2023|Evaluating Regular Path Queries in GQL and SQL/PGQ: How Far Can The Classical Algorithms Take Us?|10.48550/ARXIV.2306.02194|https://doi.org/10.48550/arXiv.2306.02194 - 2023|SQL/PGQ: A Systems Perspective (Abstract)||https://ceur-ws.org/Vol-3409/keynote04.pdf - 2022|Graph Pattern Matching in GQL and SQL/PGQ|10.1145/3514221.3526057|https://doi.org/10.1145/3514221.3526057 - 2022|Graph Query Language (GQL)-structured Algorithms for Geospatial Intelligence on Public Transportation|10.1109/ICTC55196.2022.9952570|https://doi.org/10.1109/ICTC55196.2022.9952570 - 2021|Graph Pattern Matching in GQL and SQL/PGQ||https://arxiv.org/abs/2112.06217 From ea6667ecfa08f8259f9be0c615489f9a9c543b79 Mon Sep 17 00:00:00 2001 From: Ken Domino Date: Sun, 4 Oct 2026 07:32:15 -0400 Subject: [PATCH 2/2] Deploy upstream release artifacts to production after successful builds --- .gitattributes | 3 +- .github/workflows/build.yml | 33 ++++++++++++ build.js | 3 +- code/deploy-release.sh | 63 ++++++++++++++++++++++ code/test_deploy_release.py | 103 ++++++++++++++++++++++++++++++++++++ readme.md | 37 ++++++++++++- setup-pldb.sh | 73 +------------------------ 7 files changed, 241 insertions(+), 74 deletions(-) create mode 100644 code/deploy-release.sh create mode 100644 code/test_deploy_release.py diff --git a/.gitattributes b/.gitattributes index 80bdca2e1bb1..c423233e5335 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,3 +1,4 @@ *.pldb linguist-language=pldb *.parsers linguist-language=Scroll -*.scroll linguist-language=Scroll eol=lf \ No newline at end of file +*.scroll linguist-language=Scroll eol=lf +*.sh text eol=lf diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index d1b2e7ea8a1f..db7d726de842 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -29,6 +29,12 @@ jobs: - name: Install dependencies run: npm install + - name: Check deployment scripts + run: | + bash -n setup-pldb.sh + bash -n code/deploy-release.sh + python3 code/test_deploy_release.py + - name: Build site run: node --max-old-space-size=4096 build.js @@ -54,3 +60,30 @@ jobs: run: gh release create latest /tmp/site.tar.gz --title "Latest Build" --notes "Auto-built from ${{ github.sha }}" env: GH_TOKEN: ${{ github.token }} + + - name: Deploy published build to production + if: github.repository == 'Programming-Language-DataBase/pldb' && (github.event_name == 'push' || github.event_name == 'schedule') + timeout-minutes: 20 + env: + DEPLOY_HOST: ${{ secrets.PLDB_DEPLOY_HOST }} + DEPLOY_KEY: ${{ secrets.PLDB_DEPLOY_KEY }} + DEPLOY_KNOWN_HOSTS: ${{ secrets.PLDB_DEPLOY_KNOWN_HOSTS }} + shell: bash + run: | + set -euo pipefail + : "${DEPLOY_HOST:?Set PLDB_DEPLOY_HOST to the production server hostname or IP}" + : "${DEPLOY_KEY:?Set PLDB_DEPLOY_KEY to the root SSH private key}" + : "${DEPLOY_KNOWN_HOSTS:?Set PLDB_DEPLOY_KNOWN_HOSTS to the verified server SSH host key entry}" + ssh_dir=$(mktemp -d) + trap 'rm -rf "$ssh_dir"' EXIT + printf '%s\n' "$DEPLOY_KEY" > "$ssh_dir/key" + printf '%s\n' "$DEPLOY_KNOWN_HOSTS" > "$ssh_dir/known_hosts" + chmod 600 "$ssh_dir/key" "$ssh_dir/known_hosts" + ssh_options=(-i "$ssh_dir/key" -o BatchMode=yes -o IdentitiesOnly=yes -o StrictHostKeyChecking=yes -o "UserKnownHostsFile=$ssh_dir/known_hosts" -o ConnectTimeout=30) + artifact="/tmp/pldb-${GITHUB_RUN_ID}-${GITHUB_RUN_ATTEMPT}.tar.gz" + scp "${ssh_options[@]}" /tmp/site.tar.gz "root@${DEPLOY_HOST}:$artifact" + # Deploy this run's exact artifact, even if the latest release changes. + status=0 + ssh "${ssh_options[@]}" "root@${DEPLOY_HOST}" "bash -s -- '$artifact'" < code/deploy-release.sh || status=$? + ssh "${ssh_options[@]}" "root@${DEPLOY_HOST}" "rm -f -- '$artifact'" + exit "$status" diff --git a/build.js b/build.js index 6dd221087f2d..01e59f93f34a 100644 --- a/build.js +++ b/build.js @@ -195,7 +195,8 @@ async function build() { runQuiet(`node "${SCROLL_CLI}" build`, folderPath) console.log(` ✅ ${dir}/ built successfully`) } catch (err) { - console.error(` ⚠️ ${dir}/ build had errors (continuing...)`) + console.error(` ❌ ${dir}/ build failed; refusing to publish an incomplete site`) + throw err } } } diff --git a/code/deploy-release.sh b/code/deploy-release.sh new file mode 100644 index 000000000000..bced6fd35c1e --- /dev/null +++ b/code/deploy-release.sh @@ -0,0 +1,63 @@ +#!/bin/bash +# Deploy a supplied build artifact, or download latest for the weekly fallback. +set -euo pipefail + +exec 9>/root/pldb-deploy.lock +flock -w 600 9 + +INSTALL_DIR=/root/pldb +STAGING=$(mktemp -d /root/pldb-staging.XXXXXX) +BACKUP="$STAGING/previous" +SWAPPED=false +SUCCESS=false + +cleanup() { + local status=$? + trap - EXIT + if [ "$SWAPPED" = true ] && [ "$SUCCESS" = false ]; then + echo 'Deployment failed; restoring the previous site.' >&2 + rm -rf "$INSTALL_DIR" + if ! mv "$BACKUP" "$INSTALL_DIR"; then + echo "Restore failed. Previous site retained at $BACKUP" >&2 + exit 1 + fi + systemctl restart pldb || true + fi + rm -rf "$STAGING" + exit "$status" +} +trap cleanup EXIT + +if [ "$#" -gt 0 ]; then + cp -- "$1" "$STAGING/site.tar.gz" +else + REPO_BASE=$(cat /root/pldb-repo.conf) + curl --fail --show-error --location --retry 3 \ + "$REPO_BASE/releases/download/latest/site.tar.gz" -o "$STAGING/site.tar.gz" +fi + +mkdir "$STAGING/site" +tar xzf "$STAGING/site.tar.gz" -C "$STAGING/site" +test -s "$STAGING/site/index.html" +test -s "$STAGING/site/package.json" +cd "$STAGING/site" +npm install --quiet +cd /root + +# Preserve the running installation until the replacement is ready. +mv "$INSTALL_DIR" "$BACKUP" +SWAPPED=true +mv "$STAGING/site" "$INSTALL_DIR" +systemctl restart pldb + +for attempt in {1..12}; do + if systemctl is-active --quiet pldb && \ + curl --fail --silent --max-time 5 http://localhost:80/ -o /dev/null; then + SUCCESS=true + echo 'PLDB deployment healthy.' + exit 0 + fi + sleep 5 +done +echo 'PLDB failed its HTTP health check.' >&2 +exit 1 diff --git a/code/test_deploy_release.py b/code/test_deploy_release.py new file mode 100644 index 000000000000..d3f31b792e8a --- /dev/null +++ b/code/test_deploy_release.py @@ -0,0 +1,103 @@ +"""Exercise deployment against a temporary filesystem and mocked services. + +Run: python code/test_deploy_release.py (requires Bash). +""" +import io +import os +from pathlib import Path +import shutil +import subprocess +import tarfile +import tempfile +import unittest + + +def shell_path(path): + value = Path(path).resolve().as_posix() + if os.name == "nt": + return "/" + value[0].lower() + value[2:] + return value + + +class DeploymentTests(unittest.TestCase): + def deploy(self, scenario="success", download=False): + with tempfile.TemporaryDirectory() as temp: + root = Path(temp) + site = root / "pldb" + site.mkdir() + (site / "index.html").write_text("old") + (root / "pldb-repo.conf").write_text("https://example.invalid/pldb") + artifact = root / "artifact.tar.gz" + with tarfile.open(artifact, "w:gz") as archive: + files = {"index.html": b"new", "package.json": b"{}"} + if scenario == "invalid": + del files["index.html"] + for name, data in files.items(): + info = tarfile.TarInfo(name) + info.size = len(data) + archive.addfile(info, io.BytesIO(data)) + script = root / "deploy.sh" + source = Path(__file__).with_name("deploy-release.sh").read_text() + script.write_text(source.replace("/root", shell_path(root)), newline="\n") + mocks = root / "bin" + mocks.mkdir() + commands = { + "flock": 'exit 0', + "sleep": 'exit 0', + "npm": '[ "$SCENARIO" != dependencies ]', + "systemctl": '''if [ "$SCENARIO" = restart ] && [ "$1" = restart ] && + [ "$(cat "$TEST_ROOT/pldb/index.html")" = new ]; then exit 1; fi +exit 0''', + "curl": '''if [ "$1" = --fail ] && [ "$2" = --show-error ]; then + [ "$SCENARIO" != download ] || exit 1 + for last; do :; done + cp "$ARTIFACT" "$last" +else + [ "$SCENARIO" != health ] +fi''', + } + for name, body in commands.items(): + target = mocks / name + target.write_text("#!/bin/bash\n" + body + "\n", newline="\n") + target.chmod(0o755) + bash = os.environ.get("TEST_BASH") or shutil.which("bash") + if os.name == "nt" and Path("C:/Program Files/Git/bin/bash.exe").exists(): + bash = "C:/Program Files/Git/bin/bash.exe" + self.assertIsNotNone(bash, "Bash is required") + env = dict(os.environ, SCENARIO=scenario, TEST_ROOT=shell_path(root), + ARTIFACT=shell_path(artifact)) + # Prepend mocks inside Bash, whose PATH uses POSIX separators. + command = 'export PATH="$1:$PATH"; shift; exec bash "$@"' + args = [bash, "-c", command, "test", shell_path(mocks), shell_path(script)] + if not download: + args.append(shell_path(artifact)) + result = subprocess.run(args, env=env, capture_output=True, text=True, timeout=20) + expected_success = scenario == "success" + self.assertEqual(result.returncode == 0, expected_success, result.stdout + result.stderr) + self.assertEqual((site / "index.html").read_text(), "new" if expected_success else "old") + self.assertEqual(list(root.glob("pldb-staging.*")), []) + + def test_success(self): + self.deploy() + + def test_weekly_download(self): + self.deploy(download=True) + + def test_failed_download_keeps_site(self): + self.deploy("download", download=True) + + def test_invalid_artifact_keeps_site(self): + self.deploy("invalid") + + def test_dependency_failure_keeps_site(self): + self.deploy("dependencies") + + def test_restart_failure_rolls_back(self): + self.deploy("restart") + + def test_health_failure_rolls_back(self): + self.deploy("health") + + +if __name__ == "__main__": + unittest.main() diff --git a/readme.md b/readme.md index 592ce2f2698e..e1b2f78230eb 100644 --- a/readme.md +++ b/readme.md @@ -194,7 +194,42 @@ journalctl -u pldb -f # View logs ### Redeploying after changes -After pushing new changes to `main`, wait for the GitHub Actions build to complete, then re-run `setup-pldb.sh` to deploy the updated site. +In `Programming-Language-DataBase/pldb`, pushes and merges to `main` and the Monday +06:00 UTC scheduled build publish the `latest` release, then deploy that run's +exact `site.tar.gz` to production over SSH. Pull request builds and forks do not +deploy. Deployment failures fail the workflow; the release remains available. + +Configure these Actions secrets in the upstream repository: + +| Secret | Value | +|--------|-------| +| `PLDB_DEPLOY_HOST` | Production hostname or IP (SSH port 22) | +| `PLDB_DEPLOY_KEY` | Private key authorized for the server's root user | +| `PLDB_DEPLOY_KNOWN_HOSTS` | Verified OpenSSH known_hosts entry for that hostname/IP | + +The server must already have Node.js, npm, curl, tar, flock, and the `pldb` +systemd service installed by `setup-pldb.sh`. Verify the SSH host key through a +trusted channel before saving it. Missing secrets cause the deployment step to +fail with a configuration message. + +For an existing server, replace `/root/pldb-update.sh` once with this wrapper +after the first successful automatic deployment, so the weekly fallback uses +the same deployment lock and rollback logic: + +```bash +#!/bin/bash +set -euo pipefail +exec bash /root/pldb/code/deploy-release.sh +``` + +New installations create this wrapper automatically. The weekly cron job runs +Monday at 09:00 in the server's timezone and downloads `latest` from the repository +in `/root/pldb-repo.conf`; set that URL to +`https://github.com/Programming-Language-DataBase/pldb` on the production server. +Both update paths stage dependencies before replacing the site, serialize with +`flock`, and restore the previous site if restart or HTTP health checks fail. + +For a manual deployment on the server, run `bash /root/pldb-update.sh`. ## 🌐 Mirrors diff --git a/setup-pldb.sh b/setup-pldb.sh index b1fbc6da429d..5fc3b248a1c0 100644 --- a/setup-pldb.sh +++ b/setup-pldb.sh @@ -214,78 +214,9 @@ echo "$HTTPS_BASE" > /root/pldb-repo.conf echo ">>> Installing weekly update script..." cat > /root/pldb-update.sh << 'UPDATEEOF' #!/bin/bash -# Weekly auto-update script for the PLDB server. -# Downloads the latest release from GitHub and hot-swaps the site directory. -# Called by cron every Monday at 09:00 UTC. - +# Use the deployed updater so improvements arrive with each release. set -euo pipefail - -LOG="/root/pldb-update.log" -INSTALL_DIR="/root/pldb" -CONFIG_FILE="/root/pldb-repo.conf" -WORK_DIR="/root/pldb-new" - -log() { - echo "[$(date -u '+%Y-%m-%d %H:%M:%S UTC')] $*" >> "$LOG" -} - -log "=== Starting PLDB weekly update ===" - -if [ ! -f "$CONFIG_FILE" ]; then - log "ERROR: Config file $CONFIG_FILE not found. Run setup-pldb.sh first." - exit 1 -fi -REPO_BASE=$(cat "$CONFIG_FILE") -RELEASE_URL="${REPO_BASE}/releases/download/latest/site.tar.gz" -log "Release URL: $RELEASE_URL" - -log "Downloading new release..." -curl -fSL "$RELEASE_URL" -o /tmp/site-new.tar.gz -log "Download complete." - -log "Extracting to staging directory..." -rm -rf "$WORK_DIR" -mkdir -p "$WORK_DIR" -tar xzf /tmp/site-new.tar.gz -C "$WORK_DIR" -rm -f /tmp/site-new.tar.gz - -log "Installing node modules..." -cd "$WORK_DIR" -npm install --quiet - -log "Swapping site directory..." -OLD_DIR="/root/pldb-old" -rm -rf "$OLD_DIR" -if ! mv "$INSTALL_DIR" "$OLD_DIR"; then - log "ERROR: Could not back up $INSTALL_DIR to $OLD_DIR. Aborting." - exit 1 -fi -mv "$WORK_DIR" "$INSTALL_DIR" - -log "Restarting pldb service..." -if ! systemctl restart pldb; then - log "ERROR: Service failed to restart. Rolling back..." - rm -rf "$INSTALL_DIR" 2>/dev/null || true - mv "$OLD_DIR" "$INSTALL_DIR" - systemctl restart pldb || true - log "Rollback complete. Check service logs: journalctl -u pldb -n 50" - exit 1 -fi - -sleep 5 -if systemctl is-active --quiet pldb; then - log "Service is running. Update complete." - rm -rf "$OLD_DIR" -else - log "ERROR: Service not active after restart. Rolling back..." - rm -rf "$INSTALL_DIR" 2>/dev/null || true - mv "$OLD_DIR" "$INSTALL_DIR" - systemctl restart pldb || true - log "Rollback complete. Check service logs: journalctl -u pldb -n 50" - exit 1 -fi - -log "=== PLDB weekly update finished successfully ===" +exec bash /root/pldb/code/deploy-release.sh UPDATEEOF chmod +x /root/pldb-update.sh echo " Update script installed at /root/pldb-update.sh"