Initial Commit - including auto-update automation scripts from build to publish
This commit is contained in:
commit
1a67fab14a
130
README.md
Normal file
130
README.md
Normal file
@ -0,0 +1,130 @@
|
||||
# Qortal Auto-Update Publisher Scripts
|
||||
|
||||
This toolkit modernizes and automates the Qortal auto-update process. It includes:
|
||||
|
||||
- A Bash script (`build-auto-update.sh`) to build and push the update
|
||||
- A Python script (`publish-auto-update.py`) to publish the auto-update transaction
|
||||
- Full support for dry-run mode, interactive or scripted use, and secure key input
|
||||
|
||||
---
|
||||
|
||||
## 🧰 Prerequisites
|
||||
|
||||
- You must be a **non-admin member** of the Qortal `dev` group
|
||||
- A Qortal core node must be running locally (default API port: `12391`)
|
||||
- You need the latest version of the `qortal` repo cloned locally
|
||||
|
||||
---
|
||||
|
||||
## 🚀 Workflow Overview
|
||||
|
||||
### 1. Run the Build Script
|
||||
|
||||
This script:
|
||||
- Auto-increments the version in `pom.xml`
|
||||
- Rebuilds the JAR file
|
||||
- XORs it into a `.update` file
|
||||
- Creates a new `auto-update-<hash>` branch with only the update
|
||||
- Pushes it to the repo
|
||||
|
||||
```bash
|
||||
./tools/auto-update-scripts/build-auto-update.sh
|
||||
```
|
||||
|
||||
You'll be prompted to:
|
||||
- Confirm or modify the version number
|
||||
- Push the version tag and update branch, and final commit.
|
||||
- Optionally run the publisher script at the end
|
||||
|
||||
> ✅ Dry-run mode is supported to preview the full process.
|
||||
|
||||
---
|
||||
|
||||
### 2. Publish the Auto-Update
|
||||
|
||||
You can either:
|
||||
- Let the build script call it for you
|
||||
- Or run it manually:
|
||||
|
||||
```bash
|
||||
# Run manually with interactive key prompt and auto-detected latest update:
|
||||
python3 tools/auto-update-scripts/publish-auto-update.py
|
||||
|
||||
# Or specify a commit hash:
|
||||
python3 tools/auto-update-scripts/publish-auto-update.py 0b37666d
|
||||
|
||||
# Or pass both from another script:
|
||||
python3 tools/auto-update-scripts/publish-auto-update.py <privkey> <commit_hash>
|
||||
```
|
||||
|
||||
> 🔐 Private key is always prompted securely unless passed explicitly (e.g. from automation).
|
||||
|
||||
This script will:
|
||||
- Detect the latest `auto-update-<hash>` branch (or use the one you specify)
|
||||
- Validate that the commit exists
|
||||
- Restore the `.update` file if missing
|
||||
- Compute its SHA256 hash
|
||||
- Build and sign the transaction
|
||||
- Submit it to your local node
|
||||
|
||||
> ✅ `--dry-run` is supported to show what would happen without sending anything.
|
||||
|
||||
---
|
||||
|
||||
## 🛠 Advanced Options
|
||||
|
||||
- Log files are created in `~/qortal-auto-update-logs` by default
|
||||
- You can override the log directory interactively
|
||||
- Branch naming is standardized: `auto-update-<short-commit-hash>`
|
||||
- The `.update` file is XOR-obfuscated using Qortal’s built-in logic
|
||||
- Your commit must already exist on the main repo (e.g. via push or PR merge)
|
||||
|
||||
---
|
||||
|
||||
## 📌 Notes
|
||||
|
||||
- **Do not use Git LFS** — Qortal nodes download `.update` files using raw HTTP from GitHub
|
||||
We may build LFS support in the future, but for now it is NOT utilized, and will NOT work.
|
||||
(Other locations for the publish of the .update file will be utilized in the future,
|
||||
preferably utilizing QDN via gateway nodes, until auto-update setup can be re-written to
|
||||
leverage QDN directly.)
|
||||
- GitHub will warn if `.update` files exceed 50MB, but auto-update still works.
|
||||
(In the past there HAVE been issues with accounts getting banned due to publish of .update file,
|
||||
however, as of recently (April 2025) it seems they are only warning, and not banning. But we
|
||||
will be modifying the need for this in the future anyway.)
|
||||
- Update mirrors will be added in the future, and others can be added in settings as well.
|
||||
|
||||
---
|
||||
|
||||
## ✅ Example End-to-End (Manual)
|
||||
|
||||
```bash
|
||||
cd ~/git-repos/qortal
|
||||
./tools/auto-update-scripts/build-auto-update.sh
|
||||
# follow prompts...
|
||||
|
||||
# then manually publish:
|
||||
python3 tools/auto-update-scripts/publish-auto-update.py
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🧪 Test Without Sending
|
||||
|
||||
```bash
|
||||
./build-auto-update.sh # enable dry-run when prompted
|
||||
# OR
|
||||
python3 publish-auto-update.py 0b37666d --dry-run
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🙌 Contributors
|
||||
|
||||
Modernization by [@crowetic](https://github.com/crowetic)
|
||||
Based on original Perl scripts by Qortal core devs, specifically @catbref.
|
||||
|
||||
---
|
||||
|
||||
Questions or issues? Drop into the Qortal Dev group on Discord, Q-Chat, or reach out directly via Q-Mail to 'crowetic'.
|
||||
|
76
backups/README.md
Normal file
76
backups/README.md
Normal file
@ -0,0 +1,76 @@
|
||||
# Qortal Auto-Update Publisher Script
|
||||
|
||||
This script provides a modern, simplified, and testable way to publish an auto-update transaction for Qortal. It replaces the legacy Perl-based script with a more maintainable Python version.
|
||||
|
||||
## 🔧 Requirements (Before Running)
|
||||
|
||||
Ensure the following steps and conditions are met:
|
||||
|
||||
1. **Node Environment**
|
||||
- A local Qortal node must be running and fully synced.
|
||||
- The node must expose its API (default port: `12391`).
|
||||
- Ensure your node's `settings.json` includes access to the relevant endpoints (i.e., it's not locked down).
|
||||
|
||||
2. **Qortal Update Prepared**
|
||||
- You have run the `tools/build-auto-update.sh` script (or the improved `build-auto-update.sh` version).
|
||||
- This should generate a `.update` file (e.g. `qortal.update`) and commit it to a new branch named: `auto-update-<commit-hash>`.
|
||||
|
||||
3. **Git Repository Setup**
|
||||
- You must be inside the root of the Qortal Git repository.
|
||||
- The `pom.xml` file should contain the correct `<artifactId>`.
|
||||
- You have pushed your commit + branch to a public GitHub repository, preferably a fork (for testing).
|
||||
|
||||
4. **Authentication**
|
||||
- You possess the **Base58-encoded private key** for a non-admin member of the `dev` group.
|
||||
- The key must correspond to a Qortal account that can submit `ARBITRARY` transactions to the group (group ID 1).
|
||||
|
||||
5. **Python Requirements**
|
||||
- Python 3.6+
|
||||
- `requests` package (`pip install requests`)
|
||||
|
||||
## 🚀 Full Auto-Update Workflow
|
||||
|
||||
### Step 1: Prepare Your Code
|
||||
- Ensure your latest code changes are committed and pushed.
|
||||
- Bump the version in `pom.xml` if needed.
|
||||
- Tag the commit with the version number (e.g. `v1.4.2`) and push the tag.
|
||||
|
||||
```bash
|
||||
git commit -m "Bump version to 1.4.2" pom.xml
|
||||
git tag v1.4.2
|
||||
git push origin v1.4.2
|
||||
```
|
||||
|
||||
### Step 2: Build the XOR-Obfuscated Update
|
||||
Use the improved `build-auto-update.sh`:
|
||||
```bash
|
||||
./build-auto-update.sh
|
||||
```
|
||||
- This builds the JAR.
|
||||
- XORs it into `qortal.update`.
|
||||
- Creates a new branch named `auto-update-<commit>` and pushes it.
|
||||
|
||||
### Step 3: Test the Update Transaction (Dry Run)
|
||||
```bash
|
||||
python3 publish_auto_update.py <Base58PrivateKey> <CommitHash> --repo YourUser/qortal-test --dry-run
|
||||
```
|
||||
- This will validate the timestamp, hash, and download URL, without submitting anything to the chain.
|
||||
|
||||
### Step 4: Publish the Auto-Update
|
||||
```bash
|
||||
python3 publish_auto_update.py <Base58PrivateKey> <CommitHash> --repo YourUser/qortal-test
|
||||
```
|
||||
- This will sign and submit the auto-update transaction.
|
||||
|
||||
### Step 5: Approve the Update (Admin Only)
|
||||
- Use `tools/approve-auto-update.sh` from a dev group admin account to approve the update.
|
||||
- A minimum number of approvals + block confirmations will trigger update rollout.
|
||||
|
||||
### Step 6: Monitor and Confirm
|
||||
- Nodes will download the update over the next ~20 minutes (per `CHECK_INTERVAL`).
|
||||
- Confirm via logs or block explorer that the update was received and applied.
|
||||
|
||||
---
|
||||
|
||||
By following this workflow, new contributors and developers can easily participate in the Qortal auto-update process with minimal risk and clear validation checkpoints.
|
||||
|
148
backups/build-auto-update.sh-(Tue Apr 8 02:57:31 PM PDT 2025)
Executable file
148
backups/build-auto-update.sh-(Tue Apr 8 02:57:31 PM PDT 2025)
Executable file
@ -0,0 +1,148 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# === Configurable Defaults ===
|
||||
BASE_BRANCH="master"
|
||||
DEFAULT_LOG_DIR="${HOME}/qortal-auto-update-logs"
|
||||
LOG_FILE=""
|
||||
DRY_RUN=false
|
||||
|
||||
# === Helper Functions ===
|
||||
function abort() {
|
||||
echo -e "\nERROR: $1" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
function confirm_or_exit() {
|
||||
echo "$1"
|
||||
read -rp "Continue? (y/N): " confirm
|
||||
[[ "${confirm}" =~ ^[Yy]$ ]] || exit 1
|
||||
}
|
||||
|
||||
function run_git() {
|
||||
echo "Running: git $*"
|
||||
$DRY_RUN || git "$@"
|
||||
}
|
||||
|
||||
function increment_version() {
|
||||
local version=$1
|
||||
local major minor patch
|
||||
IFS='.' read -r major minor patch <<< "$version"
|
||||
((patch++))
|
||||
echo "$major.$minor.$patch"
|
||||
}
|
||||
|
||||
# === Prompt for Logging Directory ===
|
||||
echo "Default log directory: ${DEFAULT_LOG_DIR}"
|
||||
read -rp "Use this log directory? (Y/n): " log_choice
|
||||
if [[ "${log_choice}" =~ ^[Nn]$ ]]; then
|
||||
read -rp "Enter desired log directory path: " CUSTOM_LOG_DIR
|
||||
LOG_DIR="${CUSTOM_LOG_DIR}"
|
||||
else
|
||||
LOG_DIR="${DEFAULT_LOG_DIR}"
|
||||
fi
|
||||
|
||||
mkdir -p "${LOG_DIR}" || abort "Unable to create log directory: ${LOG_DIR}"
|
||||
LOG_FILE="${LOG_DIR}/qortal-mvn-build-$(date +%Y%m%d-%H%M%S).log"
|
||||
echo "Logging to: ${LOG_FILE}"
|
||||
|
||||
# === Dry Run Mode Option ===
|
||||
read -rp "Enable dry-run mode? (y/N): " dry_choice
|
||||
if [[ "${dry_choice}" =~ ^[Yy]$ ]]; then
|
||||
DRY_RUN=true
|
||||
echo "Dry-run mode ENABLED. Commands will be shown but not executed."
|
||||
else
|
||||
echo "Dry-run mode DISABLED. Real commands will be executed."
|
||||
fi
|
||||
|
||||
# === Detect Git Root ===
|
||||
git_dir=$(git rev-parse --show-toplevel 2>/dev/null || true)
|
||||
[[ -z "${git_dir}" ]] && abort "Not inside a git repository."
|
||||
cd "${git_dir}"
|
||||
|
||||
# === Confirm Git Origin URL ===
|
||||
git_origin=$(git config --get remote.origin.url)
|
||||
echo "Git origin URL: ${git_origin}"
|
||||
confirm_or_exit "Is this the correct repository?"
|
||||
|
||||
# === Verify Current Branch ===
|
||||
current_branch=$(git rev-parse --abbrev-ref HEAD)
|
||||
echo "Current git branch: ${current_branch}"
|
||||
if [[ "${current_branch}" != "${BASE_BRANCH}" ]]; then
|
||||
echo "Expected to be on '${BASE_BRANCH}' branch, but found '${current_branch}'"
|
||||
confirm_or_exit "Proceed anyway in 5 seconds or abort with CTRL+C."
|
||||
sleep 5
|
||||
fi
|
||||
|
||||
# === Check for Uncommitted Changes ===
|
||||
uncommitted=$(git status --short --untracked-files=no)
|
||||
if [[ -n "${uncommitted}" ]]; then
|
||||
echo "Uncommitted changes detected:"
|
||||
echo "${uncommitted}"
|
||||
abort "Please commit or stash changes first."
|
||||
fi
|
||||
|
||||
# === Extract Info ===
|
||||
short_hash=$(git rev-parse --short HEAD)
|
||||
[[ -z "${short_hash}" ]] && abort "Unable to extract commit hash."
|
||||
echo "Using commit hash: ${short_hash}"
|
||||
|
||||
project=$(grep -oPm1 "(?<=<artifactId>)[^<]+" pom.xml)
|
||||
[[ -z "${project}" ]] && abort "Unable to determine project name from pom.xml."
|
||||
echo "Detected project: ${project}"
|
||||
|
||||
# === Auto-Increment Version in pom.xml ===
|
||||
current_version=$(grep -oPm1 "(?<=<version>)[^<]+" pom.xml)
|
||||
new_version=$(increment_version "$current_version")
|
||||
|
||||
$DRY_RUN || sed -i "s|<version>${current_version}</version>|<version>${new_version}</version>|" pom.xml
|
||||
|
||||
echo "Updated version from ${current_version} to ${new_version} in pom.xml"
|
||||
git diff pom.xml
|
||||
confirm_or_exit "Is the updated version correct?"
|
||||
|
||||
run_git add pom.xml
|
||||
run_git commit -m "Bump version to ${new_version}"
|
||||
run_git tag "v${new_version}"
|
||||
confirm_or_exit "About to push version tag 'v${new_version}' to origin."
|
||||
run_git push origin "v${new_version}"
|
||||
|
||||
# === Build JAR ===
|
||||
echo "Building JAR for ${project}..."
|
||||
if ! $DRY_RUN; then
|
||||
mvn clean package &> "${LOG_FILE}" || {
|
||||
tail -n 20 "${LOG_FILE}"
|
||||
abort "Maven build failed. See full log: ${LOG_FILE}"
|
||||
}
|
||||
fi
|
||||
|
||||
jar_file=$(ls target/${project}*.jar | head -n1)
|
||||
[[ ! -f "${jar_file}" ]] && abort "Built JAR file not found."
|
||||
|
||||
# === XOR Obfuscation ===
|
||||
echo "Creating ${project}.update..."
|
||||
$DRY_RUN || java -cp "${jar_file}" org.qortal.XorUpdate "${jar_file}" "${project}.update"
|
||||
|
||||
# === Create Auto-Update Branch ===
|
||||
update_branch="auto-update-${short_hash}"
|
||||
|
||||
echo "Creating update branch: ${update_branch}"
|
||||
if git show-ref --verify --quiet refs/heads/${update_branch}; then
|
||||
run_git branch -D "${update_branch}"
|
||||
fi
|
||||
|
||||
run_git checkout --orphan "${update_branch}"
|
||||
$DRY_RUN || git rm -rf . > /dev/null 2>&1 || true
|
||||
|
||||
run_git add "${project}.update"
|
||||
run_git commit -m "XORed auto-update JAR for commit ${short_hash}"
|
||||
|
||||
confirm_or_exit "About to push auto-update branch '${update_branch}' to origin."
|
||||
run_git push --set-upstream origin "${update_branch}"
|
||||
|
||||
# === Return to Original Branch ===
|
||||
echo "Switching back to original branch: ${current_branch}"
|
||||
run_git checkout --force "${current_branch}"
|
||||
echo "Done. ${project}.update is committed to ${update_branch}."
|
||||
|
182
backups/build-auto-update.sh-(Tue Apr 8 08:33:23 PM PDT 2025)
Executable file
182
backups/build-auto-update.sh-(Tue Apr 8 08:33:23 PM PDT 2025)
Executable file
@ -0,0 +1,182 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# === Configurable Defaults ===
|
||||
BASE_BRANCH="master"
|
||||
DEFAULT_LOG_DIR="${HOME}/qortal-auto-update-logs"
|
||||
LOG_FILE=""
|
||||
DRY_RUN=false
|
||||
RUN_PUBLISH=false
|
||||
PUBLISH_SCRIPT="tools/auto-update-scripts/publish-auto-update.py"
|
||||
|
||||
# === Helper Functions ===
|
||||
function abort() {
|
||||
echo -e "\nERROR: $1" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
function confirm_or_exit() {
|
||||
echo "$1"
|
||||
read -rp "Continue? (y/N): " confirm
|
||||
[[ "${confirm}" =~ ^[Yy]$ ]] || exit 1
|
||||
}
|
||||
|
||||
function run_git() {
|
||||
echo "Running: git $*"
|
||||
$DRY_RUN || git "$@"
|
||||
}
|
||||
|
||||
function increment_version() {
|
||||
local version=$1
|
||||
local major minor patch
|
||||
IFS='.' read -r major minor patch <<< "$version"
|
||||
((patch++))
|
||||
echo "$major.$minor.$patch"
|
||||
}
|
||||
|
||||
# === Prompt for Logging Directory ===
|
||||
echo "Default log directory: ${DEFAULT_LOG_DIR}"
|
||||
read -rp "Use this log directory? (Y/n): " log_choice
|
||||
if [[ "${log_choice}" =~ ^[Nn]$ ]]; then
|
||||
read -rp "Enter desired log directory path: " CUSTOM_LOG_DIR
|
||||
LOG_DIR="${CUSTOM_LOG_DIR}"
|
||||
else
|
||||
LOG_DIR="${DEFAULT_LOG_DIR}"
|
||||
fi
|
||||
|
||||
mkdir -p "${LOG_DIR}" || abort "Unable to create log directory: ${LOG_DIR}"
|
||||
LOG_FILE="${LOG_DIR}/qortal-mvn-build-$(date +%Y%m%d-%H%M%S).log"
|
||||
echo "Logging to: ${LOG_FILE}"
|
||||
|
||||
# === Dry Run Mode Option ===
|
||||
read -rp "Enable dry-run mode? (y/N): " dry_choice
|
||||
if [[ "${dry_choice}" =~ ^[Yy]$ ]]; then
|
||||
DRY_RUN=true
|
||||
echo "Dry-run mode ENABLED. Commands will be shown but not executed."
|
||||
else
|
||||
echo "Dry-run mode DISABLED. Real commands will be executed."
|
||||
fi
|
||||
|
||||
# === Run Python Publisher Option ===
|
||||
read -rp "Run the Python publish_auto_update script at the end? (y/N): " pub_choice
|
||||
if [[ "${pub_choice}" =~ ^[Yy]$ ]]; then
|
||||
RUN_PUBLISH=true
|
||||
read -rp "Run Python script in dry-run mode? (y/N): " pub_dry
|
||||
if [[ "${pub_dry}" =~ ^[Yy]$ ]]; then
|
||||
PUBLISH_DRY_FLAG="--dry-run"
|
||||
else
|
||||
PUBLISH_DRY_FLAG=""
|
||||
fi
|
||||
else
|
||||
RUN_PUBLISH=false
|
||||
fi
|
||||
|
||||
# === Detect Git Root ===
|
||||
git_dir=$(git rev-parse --show-toplevel 2>/dev/null || true)
|
||||
[[ -z "${git_dir}" ]] && abort "Not inside a git repository."
|
||||
cd "${git_dir}"
|
||||
|
||||
# === Confirm Git Origin URL ===
|
||||
git_origin=$(git config --get remote.origin.url)
|
||||
echo "Git origin URL: ${git_origin}"
|
||||
confirm_or_exit "Is this the correct repository?"
|
||||
|
||||
# === Verify Current Branch ===
|
||||
current_branch=$(git rev-parse --abbrev-ref HEAD)
|
||||
echo "Current git branch: ${current_branch}"
|
||||
if [[ "${current_branch}" != "${BASE_BRANCH}" ]]; then
|
||||
echo "Expected to be on '${BASE_BRANCH}' branch, but found '${current_branch}'"
|
||||
confirm_or_exit "Proceed anyway in 5 seconds or abort with CTRL+C."
|
||||
sleep 5
|
||||
fi
|
||||
|
||||
# === Check for Uncommitted Changes ===
|
||||
uncommitted=$(git status --short --untracked-files=no)
|
||||
if [[ -n "${uncommitted}" ]]; then
|
||||
echo "Uncommitted changes detected:"
|
||||
echo "${uncommitted}"
|
||||
abort "Please commit or stash changes first."
|
||||
fi
|
||||
|
||||
# === Extract Info ===
|
||||
short_hash=$(git rev-parse --short HEAD)
|
||||
[[ -z "${short_hash}" ]] && abort "Unable to extract commit hash."
|
||||
echo "Using commit hash: ${short_hash}"
|
||||
|
||||
project=$(grep -oPm1 "(?<=<artifactId>)[^<]+" pom.xml)
|
||||
[[ -z "${project}" ]] && abort "Unable to determine project name from pom.xml."
|
||||
echo "Detected project: ${project}"
|
||||
|
||||
# === Auto-Increment Version in pom.xml ===
|
||||
current_version=$(grep -oPm1 "(?<=<version>)[^<]+" pom.xml)
|
||||
new_version=$(increment_version "$current_version")
|
||||
|
||||
$DRY_RUN || sed -i "s|<version>${current_version}</version>|<version>${new_version}</version>|" pom.xml
|
||||
|
||||
echo "Updated version from ${current_version} to ${new_version} in pom.xml"
|
||||
git diff pom.xml
|
||||
confirm_or_exit "Is the updated version correct?"
|
||||
|
||||
run_git add pom.xml
|
||||
run_git commit -m "Bump version to ${new_version}"
|
||||
run_git tag "v${new_version}"
|
||||
confirm_or_exit "About to push version tag 'v${new_version}' to origin."
|
||||
run_git push origin "v${new_version}"
|
||||
|
||||
# === Build JAR ===
|
||||
echo "Building JAR for ${project}..."
|
||||
if ! $DRY_RUN; then
|
||||
mvn clean package &> "${LOG_FILE}" || {
|
||||
tail -n 20 "${LOG_FILE}"
|
||||
abort "Maven build failed. See full log: ${LOG_FILE}"
|
||||
}
|
||||
fi
|
||||
|
||||
jar_file=$(ls target/${project}*.jar | head -n1)
|
||||
[[ ! -f "${jar_file}" ]] && abort "Built JAR file not found."
|
||||
|
||||
# === XOR Obfuscation ===
|
||||
echo "Creating ${project}.update..."
|
||||
$DRY_RUN || java -cp "${jar_file}" org.qortal.XorUpdate "${jar_file}" "${project}.update"
|
||||
|
||||
# === Create Auto-Update Branch ===
|
||||
update_branch="auto-update-${short_hash}"
|
||||
|
||||
echo "Creating update branch: ${update_branch}"
|
||||
if git show-ref --verify --quiet refs/heads/${update_branch}; then
|
||||
run_git branch -D "${update_branch}"
|
||||
fi
|
||||
|
||||
run_git checkout --orphan "${update_branch}"
|
||||
$DRY_RUN || git rm -rf . > /dev/null 2>&1 || true
|
||||
|
||||
run_git add "${project}.update"
|
||||
run_git commit -m "XORed auto-update JAR for commit ${short_hash}"
|
||||
|
||||
confirm_or_exit "About to push auto-update branch '${update_branch}' to origin."
|
||||
run_git push --set-upstream origin "${update_branch}"
|
||||
|
||||
# === Return to Original Branch ===
|
||||
echo "Switching back to original branch: ${current_branch}"
|
||||
run_git checkout --force "${current_branch}"
|
||||
echo "Done. ${project}.update is committed to ${update_branch}."
|
||||
|
||||
# === Optionally Run Python Publisher ===
|
||||
if $RUN_PUBLISH; then
|
||||
echo "Running Python publish_auto_update script..."
|
||||
if [[ -f "${PUBLISH_SCRIPT}" ]]; then
|
||||
read -rsp "Enter your Base58 private key: " PRIVATE_KEY
|
||||
if [[ "${PUBLISH_DRY_FLAG}" == "--dry-run" ]]; then
|
||||
echo "Dry-run mode active for Python script."
|
||||
python3 "${PUBLISH_SCRIPT}" "${PRIVATE_KEY}" "${short_hash}" --dry-run
|
||||
else
|
||||
echo "Publishing auto-update for real..."
|
||||
python3 "${PUBLISH_SCRIPT}" "${PRIVATE_KEY}" "${short_hash}"
|
||||
fi
|
||||
else
|
||||
echo "WARNING: Python script not found at ${PUBLISH_SCRIPT}. Skipping."
|
||||
fi
|
||||
fi
|
||||
|
||||
|
264
build-auto-update.sh
Executable file
264
build-auto-update.sh
Executable file
@ -0,0 +1,264 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# === Configurable Defaults ===
|
||||
BASE_BRANCH="master"
|
||||
DEFAULT_LOG_DIR="${HOME}/qortal-auto-update-logs"
|
||||
LOG_FILE=""
|
||||
DRY_RUN=false
|
||||
RUN_PUBLISH=false
|
||||
PUBLISH_SCRIPT="tools/auto-update-scripts/publish-auto-update.py"
|
||||
|
||||
# === Helper Functions ===
|
||||
function abort() {
|
||||
echo -e "\nERROR: $1" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
function confirm_or_exit() {
|
||||
echo "$1"
|
||||
read -rp "Continue? (y/N): " confirm
|
||||
[[ "${confirm}" =~ ^[Yy]$ ]] || exit 1
|
||||
}
|
||||
|
||||
function run_git() {
|
||||
echo "Running: git $*" | tee -a "$LOG_FILE"
|
||||
$DRY_RUN || git "$@"
|
||||
}
|
||||
|
||||
function increment_version() {
|
||||
local version=$1
|
||||
local major minor patch
|
||||
IFS='.' read -r major minor patch <<< "$version"
|
||||
((patch++))
|
||||
echo "$major.$minor.$patch"
|
||||
}
|
||||
|
||||
# === Prompt for Logging Directory ===
|
||||
echo "Default log directory: ${DEFAULT_LOG_DIR}"
|
||||
read -rp "Use this log directory? (Y/n): " log_choice
|
||||
if [[ "${log_choice}" =~ ^[Nn]$ ]]; then
|
||||
read -rp "Enter desired log directory path: " CUSTOM_LOG_DIR
|
||||
LOG_DIR="${CUSTOM_LOG_DIR}"
|
||||
else
|
||||
LOG_DIR="${DEFAULT_LOG_DIR}"
|
||||
fi
|
||||
|
||||
mkdir -p "${LOG_DIR}" || abort "Unable to create log directory: ${LOG_DIR}"
|
||||
LOG_FILE="${LOG_DIR}/qortal-auto-update-log-$(date +%Y%m%d-%H%M%S).log"
|
||||
echo "Logging to: ${LOG_FILE}"
|
||||
|
||||
# Log everything to file as well as terminal
|
||||
exec > >(tee -a "$LOG_FILE") 2>&1
|
||||
|
||||
# === Dry Run Mode Option ===
|
||||
read -rp "Enable dry-run mode? (y/N): " dry_choice
|
||||
if [[ "${dry_choice}" =~ ^[Yy]$ ]]; then
|
||||
DRY_RUN=true
|
||||
echo "Dry-run mode ENABLED. Commands will be shown but not executed."
|
||||
else
|
||||
echo "Dry-run mode DISABLED. Real commands will be executed."
|
||||
fi
|
||||
|
||||
# === Run Python Publisher Option ===
|
||||
read -rp "Run the Python publish_auto_update script at the end? (y/N): " pub_choice
|
||||
if [[ "${pub_choice}" =~ ^[Yy]$ ]]; then
|
||||
RUN_PUBLISH=true
|
||||
read -rp "Run Python script in dry-run mode? (y/N): " pub_dry
|
||||
if [[ "${pub_dry}" =~ ^[Yy]$ ]]; then
|
||||
PUBLISH_DRY_FLAG="--dry-run"
|
||||
else
|
||||
PUBLISH_DRY_FLAG=""
|
||||
fi
|
||||
else
|
||||
RUN_PUBLISH=false
|
||||
fi
|
||||
|
||||
# === Detect Git Root ===
|
||||
git_dir=$(git rev-parse --show-toplevel 2>/dev/null || true)
|
||||
[[ -z "${git_dir}" ]] && abort "Not inside a git repository."
|
||||
cd "${git_dir}"
|
||||
|
||||
echo
|
||||
echo "Current Git identity:"
|
||||
git config user.name || echo "(not set)"
|
||||
git config user.email || echo "(not set)"
|
||||
|
||||
read -rp "Would you like to set/override the Git username and email for this repo? (y/N): " git_id_choice
|
||||
if [[ "${git_id_choice}" =~ ^[Yy]$ ]]; then
|
||||
read -rp "Enter Git username (e.g. Qortal-Auto-Update): " git_user
|
||||
read -rp "Enter Git email (e.g. qortal-auto-update@example.com): " git_email
|
||||
|
||||
run_git config user.name "${git_user}"
|
||||
run_git config user.email "${git_email}"
|
||||
echo "Git identity set to: ${git_user} <${git_email}>"
|
||||
fi
|
||||
|
||||
# === Confirm Git Origin URL ===
|
||||
git_origin=$(git config --get remote.origin.url)
|
||||
echo "Git origin URL: ${git_origin}"
|
||||
confirm_or_exit "Is this the correct repository?"
|
||||
|
||||
# === Verify Current Branch ===
|
||||
current_branch=$(git rev-parse --abbrev-ref HEAD)
|
||||
echo "Current git branch: ${current_branch}"
|
||||
if [[ "${current_branch}" != "${BASE_BRANCH}" ]]; then
|
||||
echo "Expected to be on '${BASE_BRANCH}' branch, but found '${current_branch}'"
|
||||
confirm_or_exit "Proceed anyway in 5 seconds or abort with CTRL+C."
|
||||
sleep 5
|
||||
fi
|
||||
|
||||
# === Check for Uncommitted Changes ===
|
||||
uncommitted=$(git status --short --untracked-files=no)
|
||||
if [[ -n "${uncommitted}" ]]; then
|
||||
echo "Uncommitted changes detected:"
|
||||
echo "${uncommitted}"
|
||||
abort "Please commit or stash changes first."
|
||||
fi
|
||||
|
||||
project=$(grep -oPm1 "(?<=<artifactId>)[^<]+" pom.xml)
|
||||
[[ -z "${project}" ]] && abort "Unable to determine project name from pom.xml."
|
||||
echo "Detected project: ${project}"
|
||||
|
||||
# === Auto-Increment Version in pom.xml ===
|
||||
current_version=$(grep -oPm1 "(?<=<version>)[^<]+" pom.xml)
|
||||
new_version=$(increment_version "$current_version")
|
||||
|
||||
$DRY_RUN || sed -i "s|<version>${current_version}</version>|<version>${new_version}</version>|" pom.xml
|
||||
|
||||
echo "Updated version from ${current_version} to ${new_version} in pom.xml"
|
||||
git diff pom.xml
|
||||
|
||||
while true; do
|
||||
read -rp "Is the updated version correct? (y/N): " version_ok
|
||||
if [[ "${version_ok}" =~ ^[Yy]$ ]]; then
|
||||
break
|
||||
fi
|
||||
|
||||
read -rp "Enter the correct version number (e.g., 4.7.2): " user_version
|
||||
|
||||
# Validate format x.y.z and version > current_version
|
||||
if [[ ! "${user_version}" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
|
||||
echo "Invalid format. Use x.y.z (e.g., 4.7.2)."
|
||||
continue
|
||||
fi
|
||||
|
||||
IFS='.' read -r curr_major curr_minor curr_patch <<< "${current_version}"
|
||||
IFS='.' read -r new_major new_minor new_patch <<< "${user_version}"
|
||||
|
||||
if (( new_major < curr_major )) || \
|
||||
(( new_major == curr_major && new_minor < curr_minor )) || \
|
||||
(( new_major == curr_major && new_minor == curr_minor && new_patch <= curr_patch )); then
|
||||
echo "Version must be greater than current version (${current_version})."
|
||||
continue
|
||||
fi
|
||||
|
||||
$DRY_RUN || sed -i "s|<version>${new_version}</version>|<version>${user_version}</version>|" pom.xml
|
||||
echo "Updated version to user-provided version: ${user_version}"
|
||||
git diff pom.xml
|
||||
new_version="${user_version}"
|
||||
echo
|
||||
echo "Rechecking updated version..."
|
||||
done
|
||||
|
||||
run_git add pom.xml
|
||||
run_git commit -m "Bump version to ${new_version}"
|
||||
run_git tag "v${new_version}"
|
||||
confirm_or_exit "About to push version tag 'v${new_version}' to origin."
|
||||
run_git push origin "v${new_version}"
|
||||
confirm_or_exit "Also push the ${current_branch} branch to origin?"
|
||||
run_git push origin "${current_branch}"
|
||||
|
||||
# === Extract Info ===
|
||||
short_hash=$(git rev-parse --short HEAD)
|
||||
[[ -z "${short_hash}" ]] && abort "Unable to extract commit hash."
|
||||
echo "Using commit hash: ${short_hash}"
|
||||
|
||||
|
||||
# === Build JAR ===
|
||||
echo "Building JAR for ${project}..."
|
||||
if ! $DRY_RUN; then
|
||||
mvn clean package > /dev/null 2>&1 || {
|
||||
echo "Build failed. Check logs in ${LOG_FILE}" >&2
|
||||
abort "Maven build failed."
|
||||
}
|
||||
fi
|
||||
|
||||
jar_file=$(ls target/${project}*.jar | head -n1)
|
||||
[[ ! -f "${jar_file}" ]] && abort "Built JAR file not found."
|
||||
|
||||
# === XOR Obfuscation ===
|
||||
echo "Creating ${project}.update..."
|
||||
$DRY_RUN || java -cp "${jar_file}" org.qortal.XorUpdate "${jar_file}" "${project}.update"
|
||||
|
||||
# === Create Auto-Update Branch ===
|
||||
update_branch="auto-update-${short_hash}"
|
||||
|
||||
echo "Creating update branch: ${update_branch}"
|
||||
if git show-ref --verify --quiet refs/heads/${update_branch}; then
|
||||
run_git branch -D "${update_branch}"
|
||||
fi
|
||||
|
||||
run_git checkout --orphan "${update_branch}"
|
||||
$DRY_RUN || git rm -rf . > /dev/null 2>&1 || true
|
||||
|
||||
run_git add "${project}.update"
|
||||
run_git commit -m "XORed auto-update JAR for commit ${short_hash}"
|
||||
|
||||
confirm_or_exit "About to push auto-update branch '${update_branch}' to origin."
|
||||
run_git push --set-upstream origin "${update_branch}"
|
||||
|
||||
# === Return to Original Branch ===
|
||||
echo "Switching back to original branch: ${current_branch}"
|
||||
run_git checkout --force "${current_branch}"
|
||||
echo "Done. ${project}.update is committed to ${update_branch}."
|
||||
|
||||
# === Summary Output ===
|
||||
echo
|
||||
echo "======================================"
|
||||
echo "✅ Auto-Update Build Complete!"
|
||||
echo "--------------------------------------"
|
||||
echo "Project: ${project}"
|
||||
echo "Version: ${new_version}"
|
||||
echo "Tag: v${new_version}"
|
||||
echo "Commit Hash: ${short_hash}"
|
||||
echo "Auto-Update Branch: auto-update-${short_hash}"
|
||||
echo
|
||||
echo "Pushed to: ${git_origin}"
|
||||
echo "Logs saved to: ${LOG_FILE}"
|
||||
echo "======================================"
|
||||
echo
|
||||
# === Provide additional information regarding publish script, and private key. ===
|
||||
if $RUN_PUBLISH; then
|
||||
echo "...===...===...===...===...===..."
|
||||
echo
|
||||
echo "CONTINUING TO EXECUTE PUBLISH SCRIPT AS SELECTED"
|
||||
echo
|
||||
echo "This will publish the AUTO-UPDATE TRANSACTION for signing by the DEVELOPER GROUP ADMINS"
|
||||
echo
|
||||
echo "NOTICE: For security, when prompted for PRIVATE KEY, you will NOT see the input, SIMPLY PASTE/TYPE KEY AND PUSH ENTER."
|
||||
echo
|
||||
echo "...===...===...===...===...===..."
|
||||
fi
|
||||
|
||||
# === Optionally Run Python Publisher ===
|
||||
if $RUN_PUBLISH; then
|
||||
echo "Running Python publish_auto_update script..."
|
||||
if [[ -f "${PUBLISH_SCRIPT}" ]]; then
|
||||
read -rsp "Enter your Base58 private key: " PRIVATE_KEY
|
||||
echo
|
||||
|
||||
if [[ "${PUBLISH_DRY_FLAG}" == "--dry-run" ]]; then
|
||||
echo "Dry-run mode active for Python script."
|
||||
python3 "${PUBLISH_SCRIPT}" "${PRIVATE_KEY}" "${short_hash}" --dry-run
|
||||
else
|
||||
echo "Publishing auto-update for real..."
|
||||
python3 "${PUBLISH_SCRIPT}" "${PRIVATE_KEY}" "${short_hash}"
|
||||
fi
|
||||
else
|
||||
echo "WARNING: Python script not found at ${PUBLISH_SCRIPT}. Skipping."
|
||||
fi
|
||||
fi
|
||||
|
||||
|
206
original/publish-auto-update.pl
Normal file
206
original/publish-auto-update.pl
Normal file
@ -0,0 +1,206 @@
|
||||
#!/usr/bin/env perl
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
use POSIX;
|
||||
use Getopt::Std;
|
||||
use File::Slurp;
|
||||
|
||||
sub usage() {
|
||||
die("usage: $0 [-p api-port] dev-private-key [short-commit-hash]\n");
|
||||
}
|
||||
|
||||
my %opt;
|
||||
getopts('p:', \%opt);
|
||||
|
||||
usage() if @ARGV < 1 || @ARGV > 2;
|
||||
|
||||
my $port = $opt{p} || 12391;
|
||||
my $privkey = shift @ARGV;
|
||||
my $commit_hash = shift @ARGV;
|
||||
|
||||
my $git_dir = `git rev-parse --show-toplevel`;
|
||||
die("Cannot determine git top level dir\n") unless $git_dir;
|
||||
|
||||
chomp $git_dir;
|
||||
chdir($git_dir) || die("Can't change directory to $git_dir: $!\n");
|
||||
|
||||
open(POM, '<', 'pom.xml') || die ("Can't open 'pom.xml': $!\n");
|
||||
my $project;
|
||||
while (<POM>) {
|
||||
if (m/<artifactId>(\w+)<.artifactId>/o) {
|
||||
$project = $1;
|
||||
last;
|
||||
}
|
||||
}
|
||||
close(POM);
|
||||
|
||||
my $apikey = read_file('apikey.txt');
|
||||
|
||||
# Do we need to determine commit hash?
|
||||
unless ($commit_hash) {
|
||||
# determine git branch
|
||||
my $branch_name = ` git symbolic-ref -q HEAD `;
|
||||
chomp $branch_name;
|
||||
$branch_name =~ s|^refs/heads/||; # ${branch_name##refs/heads/}
|
||||
|
||||
# short-form commit hash on base branch (non-auto-update)
|
||||
$commit_hash ||= `git show --no-patch --format=%h`;
|
||||
die("Can't find commit hash\n") if ! defined $commit_hash;
|
||||
chomp $commit_hash;
|
||||
printf "Commit hash on '%s' branch: %s\n", $branch_name, $commit_hash;
|
||||
} else {
|
||||
printf "Using given commit hash: %s\n", $commit_hash;
|
||||
}
|
||||
|
||||
# build timestamp / commit timestamp on base branch
|
||||
my $timestamp = `git show --no-patch --format=%ct ${commit_hash}`;
|
||||
die("Can't determine commit timestamp\n") if ! defined $timestamp;
|
||||
$timestamp *= 1000; # Convert to milliseconds
|
||||
|
||||
# locate sha256 utility
|
||||
my $SHA256 = `which sha256sum || which sha256`;
|
||||
chomp $SHA256;
|
||||
die("Can't find sha256sum or sha256\n") unless length($SHA256) > 0;
|
||||
|
||||
# SHA256 of actual update file
|
||||
my $sha256 = `git show auto-update-${commit_hash}:${project}.update | ${SHA256} | head -c 64`;
|
||||
die("Can't calculate SHA256 of ${project}.update\n") unless $sha256 =~ m/(\S{64})/;
|
||||
chomp $sha256;
|
||||
|
||||
# long-form commit hash of HEAD on auto-update branch
|
||||
#my $update_hash = `git rev-parse refs/heads/auto-update-${commit_hash}`;
|
||||
my $update_hash = `git rev-parse origin/auto-update-${commit_hash}`;
|
||||
die("Can't find commit hash for HEAD on auto-update-${commit_hash} branch\n") if ! defined $update_hash;
|
||||
chomp $update_hash;
|
||||
|
||||
printf "Build timestamp (ms): %d / 0x%016x\n", $timestamp, $timestamp;
|
||||
printf "Auto-update commit hash: %s\n", $update_hash;
|
||||
printf "SHA256 of ${project}.update: %s\n", $sha256;
|
||||
|
||||
my $tx_type = 10;
|
||||
my $tx_timestamp = time() * 1000;
|
||||
my $tx_group_id = 1;
|
||||
my $service = 1;
|
||||
printf "\nARBITRARY(%d) transaction with timestamp %d, txGroupID %d and service %d\n", $tx_type, $tx_timestamp, $tx_group_id, $service;
|
||||
|
||||
my $data_hex = sprintf "%016x%s%s", $timestamp, $update_hash, $sha256;
|
||||
printf "\nARBITRARY transaction data payload: %s\n", $data_hex;
|
||||
|
||||
my $n_payments = 0;
|
||||
my $data_type = 1; # RAW_DATA
|
||||
my $data_length = length($data_hex) / 2; # two hex chars per byte
|
||||
my $fee = 0.01 * 1e8;
|
||||
my $nonce = 0;
|
||||
my $name_length = 0;
|
||||
my $identifier_length = 0;
|
||||
my $method = 0; # PUT
|
||||
my $secret_length = 0;
|
||||
my $compression = 0; # None
|
||||
my $metadata_hash_length = 0;
|
||||
|
||||
die("Something's wrong: data length is not 60 bytes!\n") if $data_length != 60;
|
||||
|
||||
my $pubkey = `curl --silent --url http://localhost:${port}/utils/publickey --data ${privkey}`;
|
||||
die("Can't convert private key to public key:\n$pubkey\n") unless $pubkey =~ m/^\w{44}$/;
|
||||
printf "\nPublic key: %s\n", $pubkey;
|
||||
|
||||
my $pubkey_hex = `curl --silent --url http://localhost:${port}/utils/frombase58 --data ${pubkey}`;
|
||||
die("Can't convert base58 public key to hex:\n$pubkey_hex\n") unless $pubkey_hex =~ m/^[A-Za-z0-9]{64}$/;
|
||||
printf "Public key hex: %s\n", $pubkey_hex;
|
||||
|
||||
my $address = `curl --silent --url http://localhost:${port}/addresses/convert/${pubkey}`;
|
||||
die("Can't convert base58 public key to address:\n$address\n") unless $address =~ m/^\w{33,34}$/;
|
||||
printf "Address: %s\n", $address;
|
||||
|
||||
my $reference = `curl --silent --url http://localhost:${port}/addresses/lastreference/${address}`;
|
||||
die("Can't fetch last reference for $address:\n$reference\n") unless $reference =~ m/^\w{87,88}$/;
|
||||
printf "Last reference: %s\n", $reference;
|
||||
|
||||
my $reference_hex = `curl --silent --url http://localhost:${port}/utils/frombase58 --data ${reference}`;
|
||||
die("Can't convert base58 reference to hex:\n$reference_hex\n") unless $reference_hex =~ m/^[A-Za-z0-9]{128}$/;
|
||||
printf "Last reference hex: %s\n", $reference_hex;
|
||||
|
||||
my $raw_tx_hex = sprintf("%08x%016x%08x%s%s%08x%08x%08x%08x%08x%08x%08x%08x%02x%08x%s%08x%08x%016x", $tx_type, $tx_timestamp, $tx_group_id, $reference_hex, $pubkey_hex, $nonce, $name_length, $identifier_length, $method, $secret_length, $compression, $n_payments, $service, $data_type, $data_length, $data_hex, $data_length, $metadata_hash_length, $fee);
|
||||
printf "\nRaw transaction hex:\n%s\n", $raw_tx_hex;
|
||||
|
||||
my $raw_tx = `curl --silent --url http://localhost:${port}/utils/tobase58/${raw_tx_hex}`;
|
||||
die("Can't convert raw transaction hex to base58:\n$raw_tx\n") unless $raw_tx =~ m/^\w{300,320}$/; # Roughly 305 to 320 base58 chars
|
||||
printf "\nRaw transaction (base58):\n%s\n", $raw_tx;
|
||||
|
||||
my $sign_data = qq|' { "privateKey": "${privkey}", "transactionBytes": "${raw_tx}" } '|;
|
||||
my $signed_tx = `curl --silent -H "accept: text/plain" -H "Content-Type: application/json" --url http://localhost:${port}/transactions/sign --data ${sign_data}`;
|
||||
die("Can't sign raw transaction:\n$signed_tx\n") unless $signed_tx =~ m/^\w{390,410}$/; # +90ish longer than $raw_tx
|
||||
printf "\nSigned transaction:\n%s\n", $signed_tx;
|
||||
|
||||
# Get the origin URL - So that we will be able to TEST the obtaining of the qortal.update...
|
||||
my $origin = `git remote get-url origin`;
|
||||
chomp $origin; # Remove any trailing newlines
|
||||
die("Unable to get github url for 'origin'?\n") unless $origin;
|
||||
|
||||
# Debug: Print the origin URL
|
||||
print "Full Origin URL: $origin\n";
|
||||
|
||||
# Extract the repository path (e.g., Qortal/qortal) NOTE - github is case-sensitive with repo names
|
||||
my $repo;
|
||||
if ($origin =~ m/[:\/]([\w\-]+\/[\w\-]+)\.git$/) {
|
||||
$repo = $1;
|
||||
print "Extracted direct repository path: $repo\n";
|
||||
if ($repo =~ m/^qortal\//i) {
|
||||
$repo =~ s/^qortal\//Qortal\//;
|
||||
print "Corrected repository path capitalization: $repo\n";
|
||||
}
|
||||
print "Please verify the direct repository path. Current: '$repo'\n";
|
||||
print "If incorrect, input the correct direct repository path (e.g., 'Qortal/qortal' or 'bob/qortal').NOTE - github is CASE SENSITIVE for repository urls... Press Enter to keep the extracted version: ";
|
||||
my $input = <STDIN>;
|
||||
if ($input =~ m/^qortal\//i) {
|
||||
$input =~ s/^qortal\//Qortal\//;
|
||||
print "Corrected repository path capitalization: $repo\n";
|
||||
}
|
||||
chomp $input;
|
||||
$repo = $input if $input; # Update repo if user provides input
|
||||
|
||||
} else {
|
||||
# Default to qortal/qortal if extraction fails
|
||||
$repo = "Qortal/qortal";
|
||||
print "Failed to extract repository path from origin URL. Using default: $repo\n";
|
||||
|
||||
# Prompt the user for confirmation or input
|
||||
print "Please verify the repository path. Current: '$repo'\n";
|
||||
print "If incorrect, input the correct repository path (e.g., 'Qortal/qortal' or 'BobsCodeburgers/qortal'). NOTE - GitHub is CASE SENSITIVE for repository urls... Press Enter to keep the default: ";
|
||||
my $input = <STDIN>;
|
||||
if ($input =~ m/^qortal\//i) {
|
||||
$input =~ s/^qortal\//Qortal\//;
|
||||
print "Corrected repository path capitalization: $repo\n";
|
||||
}
|
||||
chomp $input;
|
||||
$repo = $input if $input; # Update repo if user provides input
|
||||
}
|
||||
|
||||
# Debug: Print the final repository path
|
||||
print "Final direct repository path: $repo\n";
|
||||
|
||||
# Construct the update URL
|
||||
my $update_url = "https://github.com/${repo}/raw/${update_hash}/${project}.update";
|
||||
print "Final update URL: $update_url\n";
|
||||
|
||||
|
||||
my $fetch_result = `curl --silent -o /dev/null --location --range 0-1 --head --write-out '%{http_code}' --url ${update_url}`;
|
||||
die("\nUnable to fetch update from ${update_url}\n") if $fetch_result ne '200';
|
||||
printf "\nUpdate fetchable from ${update_url}\n";
|
||||
|
||||
# Flush STDOUT after every output
|
||||
$| = 1;
|
||||
print "\n";
|
||||
for (my $delay = 5; $delay > 0; --$delay) {
|
||||
printf "\rSubmitting transaction in %d second%s... CTRL-C to abort ", $delay, ($delay != 1 ? 's' : '');
|
||||
sleep 1;
|
||||
}
|
||||
|
||||
printf "\rSubmitting transaction NOW... \n";
|
||||
my $result = `curl --silent --url http://localhost:${port}/transactions/process --data ${signed_tx}`;
|
||||
chomp $result;
|
||||
die("Transaction wasn't accepted:\n$result\n") unless $result eq 'true';
|
||||
|
||||
my $decoded_tx = `curl --silent -H "Content-Type: application/json" --url http://localhost:${port}/transactions/decode --data ${signed_tx}`;
|
||||
printf "\nTransaction accepted:\n$decoded_tx\n";
|
234
publish-auto-update.py
Executable file
234
publish-auto-update.py
Executable file
@ -0,0 +1,234 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import argparse
|
||||
import subprocess
|
||||
import requests
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
import hashlib
|
||||
from pathlib import Path
|
||||
|
||||
def run(cmd, cwd=None, capture_output=True):
|
||||
result = subprocess.run(cmd, shell=True, cwd=cwd, capture_output=capture_output, text=True)
|
||||
if result.returncode != 0:
|
||||
print(f"Command failed: {cmd}\n{result.stderr}")
|
||||
sys.exit(1)
|
||||
return result.stdout.strip()
|
||||
|
||||
def get_project_name():
|
||||
pom = Path('pom.xml')
|
||||
if not pom.exists():
|
||||
sys.exit("pom.xml not found!")
|
||||
for line in pom.read_text().splitlines():
|
||||
if '<artifactId>' in line:
|
||||
return line.strip().split('>')[1].split('<')[0]
|
||||
sys.exit("artifactId not found in pom.xml")
|
||||
|
||||
def get_commit_info(commit_hash=None, dry_run=False):
|
||||
if not commit_hash:
|
||||
print("No commit hash provided, detecting most recent auto-update branch...")
|
||||
run("git fetch origin") # Ensure up-to-date
|
||||
|
||||
# Get latest auto-update branch by commit date
|
||||
branches = run("git for-each-ref --sort=-committerdate --format='%(refname:short)' refs/remotes/origin/")
|
||||
for branch in branches.splitlines():
|
||||
branch = branch.strip().strip("'")
|
||||
if branch.startswith("origin/auto-update-"):
|
||||
commit_hash = branch.replace("origin/auto-update-", "")
|
||||
print(f"Found latest auto-update branch: {branch}")
|
||||
break
|
||||
|
||||
if not commit_hash:
|
||||
sys.exit("No auto-update branches found.")
|
||||
|
||||
# Validate and get timestamp
|
||||
if not commit_exists(commit_hash):
|
||||
sys.exit(f"Commit hash '{commit_hash}' does not exist.")
|
||||
|
||||
timestamp = int(run(f"git show --no-patch --format=%ct {commit_hash}")) * 1000
|
||||
|
||||
# Use the remote branch hash if available
|
||||
try:
|
||||
update_hash = run(f"git rev-parse origin/auto-update-{commit_hash}")
|
||||
except SystemExit:
|
||||
print(f"⚠️ Warning: remote branch origin/auto-update-{commit_hash} not found, using commit hash itself.")
|
||||
update_hash = run(f"git rev-parse {commit_hash}")
|
||||
|
||||
return commit_hash, timestamp, update_hash
|
||||
|
||||
|
||||
def commit_exists(commit_hash):
|
||||
try:
|
||||
run(f"git cat-file -t {commit_hash}")
|
||||
return True
|
||||
except SystemExit:
|
||||
return False
|
||||
|
||||
def get_sha256(update_file_path):
|
||||
sha256 = hashlib.sha256()
|
||||
with open(update_file_path, 'rb') as f:
|
||||
sha256.update(f.read())
|
||||
return sha256.hexdigest()
|
||||
|
||||
def get_public_key(base58_privkey, port):
|
||||
r = requests.post(f"http://localhost:{port}/utils/publickey", data=base58_privkey)
|
||||
r.raise_for_status()
|
||||
return r.text
|
||||
|
||||
def get_hex_key(base58_key, port):
|
||||
r = requests.post(f"http://localhost:{port}/utils/frombase58", data=base58_key)
|
||||
r.raise_for_status()
|
||||
return r.text
|
||||
|
||||
def get_address(pubkey, port):
|
||||
r = requests.get(f"http://localhost:{port}/addresses/convert/{pubkey}")
|
||||
r.raise_for_status()
|
||||
return r.text
|
||||
|
||||
def get_reference(address, port):
|
||||
r = requests.get(f"http://localhost:{port}/addresses/lastreference/{address}")
|
||||
r.raise_for_status()
|
||||
return r.text
|
||||
|
||||
def to_base58(hex_str, port):
|
||||
r = requests.get(f"http://localhost:{port}/utils/tobase58/{hex_str}")
|
||||
r.raise_for_status()
|
||||
return r.text
|
||||
|
||||
def sign_transaction(privkey, tx_base58, port):
|
||||
payload = json.dumps({"privateKey": privkey, "transactionBytes": tx_base58})
|
||||
headers = {"Content-Type": "application/json"}
|
||||
r = requests.post(f"http://localhost:{port}/transactions/sign", data=payload, headers=headers)
|
||||
r.raise_for_status()
|
||||
return r.text
|
||||
|
||||
def process_transaction(signed_tx, port):
|
||||
r = requests.post(f"http://localhost:{port}/transactions/process", data=signed_tx)
|
||||
r.raise_for_status()
|
||||
return r.text == 'true'
|
||||
|
||||
def decode_transaction(signed_tx, port):
|
||||
r = requests.post(f"http://localhost:{port}/transactions/decode", data=signed_tx, headers={"Content-Type": "application/json"})
|
||||
r.raise_for_status()
|
||||
return r.text
|
||||
|
||||
def main():
|
||||
import getpass
|
||||
parser = argparse.ArgumentParser(description="Modern auto-update publisher for Qortal")
|
||||
parser.add_argument("arg1", nargs="?", help="Private key OR commit hash")
|
||||
parser.add_argument("arg2", nargs="?", help="Commit hash if arg1 was private key")
|
||||
parser.add_argument("--port", type=int, default=12391, help="API port")
|
||||
parser.add_argument("--dry-run", action="store_true", help="Simulate without submitting transaction")
|
||||
args = parser.parse_args()
|
||||
|
||||
# Handle combinations
|
||||
if args.arg1 and args.arg2:
|
||||
privkey = args.arg1
|
||||
commit_hash = args.arg2
|
||||
elif args.arg1 and not args.arg2:
|
||||
commit_hash = args.arg1
|
||||
privkey = getpass.getpass("Enter your Base58 private key: ")
|
||||
else:
|
||||
commit_hash = None # Will auto-resolve from HEAD
|
||||
privkey = getpass.getpass("Enter your Base58 private key: ")
|
||||
|
||||
# Switch to repo root
|
||||
git_root = run("git rev-parse --show-toplevel")
|
||||
os.chdir(git_root)
|
||||
|
||||
project = get_project_name()
|
||||
|
||||
# Resolve and verify commit
|
||||
commit_hash, timestamp, update_hash = get_commit_info(commit_hash, args.dry_run)
|
||||
if not commit_exists(commit_hash):
|
||||
sys.exit(f"Commit hash '{commit_hash}' does not exist in this repo.")
|
||||
|
||||
print(f"Commit: {commit_hash}, Timestamp: {timestamp}, Auto-update hash: {update_hash}")
|
||||
|
||||
|
||||
def get_sha256(update_file_path):
|
||||
sha256 = hashlib.sha256()
|
||||
with open(update_file_path, 'rb') as f:
|
||||
sha256.update(f.read())
|
||||
return sha256.hexdigest()
|
||||
|
||||
update_file = Path(f"{project}.update")
|
||||
|
||||
if not update_file.exists():
|
||||
print(f"{project}.update not found locally. Attempting to restore from branch auto-update-{commit_hash}...")
|
||||
try:
|
||||
restore_cmd = f"git show auto-update-{commit_hash}:{project}.update > {project}.update"
|
||||
run(restore_cmd)
|
||||
print(f"✓ Restored {project}.update from branch auto-update-{commit_hash}")
|
||||
except Exception as e:
|
||||
sys.exit(f"Failed to restore {project}.update: {e}")
|
||||
|
||||
# Final check to ensure the file was restored
|
||||
if not update_file.exists():
|
||||
sys.exit(f"{project}.update still not found after attempted restore")
|
||||
|
||||
|
||||
sha256 = get_sha256(update_file)
|
||||
print(f"Update SHA256: {sha256}")
|
||||
|
||||
if args.dry_run:
|
||||
print("\n--- DRY RUN ---")
|
||||
print(f"Would use timestamp: {timestamp}")
|
||||
print(f"Would use update hash: {update_hash}")
|
||||
print(f"Would use SHA256: {sha256}")
|
||||
sys.exit(0)
|
||||
|
||||
pubkey = get_public_key(privkey, args.port)
|
||||
pubkey_hex = get_hex_key(pubkey, args.port)
|
||||
address = get_address(pubkey, args.port)
|
||||
reference = get_reference(address, args.port)
|
||||
reference_hex = get_hex_key(reference, args.port)
|
||||
|
||||
data_hex = f"{timestamp:016x}{update_hash}{sha256}"
|
||||
if len(data_hex) != 120:
|
||||
sys.exit("Data hex length invalid!")
|
||||
|
||||
raw_tx_parts = [
|
||||
"0000000a", # type 10 ARBITRARY
|
||||
f"{int(time.time() * 1000):016x}", # current timestamp
|
||||
"00000001", # dev group ID
|
||||
reference_hex, # reference
|
||||
pubkey_hex, # pubkey
|
||||
"00000000", # nonce
|
||||
"00000000", # name length
|
||||
"00000000", # identifier length
|
||||
"00000000", # method (PUT)
|
||||
"00000000", # secret length
|
||||
"00000000", # compression
|
||||
"00000000", # number of payments
|
||||
"00000001", # service ID
|
||||
"01", # data type (RAW_DATA)
|
||||
f"{int(len(data_hex)//2):08x}", # data length
|
||||
data_hex, # payload
|
||||
f"{int(len(data_hex)//2):08x}", # repeated data length
|
||||
"00000000", # metadata hash length
|
||||
f"{int(0.01 * 1e8):016x}" # fee
|
||||
]
|
||||
tx_hex = "".join(raw_tx_parts)
|
||||
|
||||
|
||||
tx_base58 = to_base58(tx_hex, args.port)
|
||||
signed_tx = sign_transaction(privkey, tx_base58, args.port)
|
||||
|
||||
print("Submitting in 5 seconds... press CTRL+C to cancel")
|
||||
for i in range(5, 0, -1):
|
||||
print(f"{i}...", end='\r', flush=True)
|
||||
time.sleep(1)
|
||||
|
||||
if not process_transaction(signed_tx, args.port):
|
||||
sys.exit("Transaction submission failed")
|
||||
|
||||
decoded = decode_transaction(signed_tx, args.port)
|
||||
print("\nTransaction submitted successfully:")
|
||||
print(decoded)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
Loading…
x
Reference in New Issue
Block a user