added hub debian repo update script for older style repo - will update repo style in the future.
This commit is contained in:
173
backup.tx.py
Executable file
173
backup.tx.py
Executable file
@@ -0,0 +1,173 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import argparse
|
||||
import base64
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
import time
|
||||
import requests
|
||||
from hashlib import sha256
|
||||
from Crypto.Hash import RIPEMD
|
||||
|
||||
BASE58_ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"
|
||||
BASE58_MAP = {c: i for i, c in enumerate(BASE58_ALPHABET)}
|
||||
DEFAULT_FEE = 0.01
|
||||
BASE_URL = os.getenv("BASE_URL", "http://localhost:12391")
|
||||
|
||||
TRANSACTION_TYPES = {
|
||||
# Payments
|
||||
"payment": {"url": "payments/pay", "required": ["recipient", "amount"], "key_name": "senderPublicKey"},
|
||||
|
||||
# Groups
|
||||
"set_group": {"url": "groups/setdefault", "required": ["defaultGroupId"], "key_name": "creatorPublicKey"},
|
||||
"create_group": {"url": "groups/create", "required": ["groupName", "description", "isOpen", "approvalThreshold"], "defaults": {"minimumBlockDelay": 10, "maximumBlockDelay": 30}, "key_name": "creatorPublicKey"},
|
||||
"update_group": {"url": "groups/update", "required": ["groupId", "newOwner", "newDescription", "newIsOpen", "newApprovalThreshold"], "key_name": "ownerPublicKey"},
|
||||
"join_group": {"url": "groups/join", "required": ["groupId"], "key_name": "joinerPublicKey"},
|
||||
"leave_group": {"url": "groups/leave", "required": ["groupId"], "key_name": "leaverPublicKey"},
|
||||
"group_invite": {"url": "groups/invite", "required": ["groupId", "invitee"], "key_name": "adminPublicKey"},
|
||||
"group_kick": {"url": "groups/kick", "required": ["groupId", "member", "reason"], "key_name": "adminPublicKey"},
|
||||
"add_group_admin": {"url": "groups/addadmin", "required": ["groupId", "txGroupId", "member"], "key_name": "ownerPublicKey"},
|
||||
"remove_group_admin": {"url": "groups/removeadmin", "required": ["groupId", "txGroupId", "admin"], "key_name": "ownerPublicKey"},
|
||||
"group_approval": {"url": "groups/approval", "required": ["pendingSignature", "approval"], "key_name": "adminPublicKey"},
|
||||
|
||||
# Assets
|
||||
"issue_asset": {"url": "assets/issue", "required": ["assetName", "description", "quantity", "isDivisible"], "key_name": "issuerPublicKey"},
|
||||
"update_asset": {"url": "assets/update", "required": ["assetId", "newOwner"], "key_name": "ownerPublicKey"},
|
||||
"transfer_asset": {"url": "assets/transfer", "required": ["recipient", "amount", "assetId"], "key_name": "senderPublicKey"},
|
||||
"create_order": {"url": "assets/order", "required": ["haveAssetId", "wantAssetId", "amount", "price"], "key_name": "creatorPublicKey"},
|
||||
|
||||
# Names
|
||||
"register_name": {"url": "names/register", "required": ["name", "data"], "key_name": "registrantPublicKey"},
|
||||
"update_name": {"url": "names/update", "required": ["name", "newName", "newData"], "key_name": "ownerPublicKey"},
|
||||
|
||||
# Reward Shares
|
||||
"reward_share": {"url": "addresses/rewardshare", "required": ["recipient", "rewardSharePublicKey", "sharePercent"], "key_name": "minterPublicKey"},
|
||||
|
||||
# Arbitrary
|
||||
"arbitrary": {"url": "arbitrary", "required": ["service", "dataType", "data"], "key_name": "senderPublicKey"},
|
||||
|
||||
# Chat
|
||||
"chat": {"url": "chat", "required": ["data"], "optional": ["recipient", "isText", "isEncrypted"], "defaults": {"isText": "true"}, "key_name": "senderPublicKey", "pow_url": "chat/compute"},
|
||||
|
||||
# Misc
|
||||
"publicize": {"url": "addresses/publicize", "required": [], "key_name": "senderPublicKey", "pow_url": "addresses/publicize/compute"},
|
||||
|
||||
# Automated Transactions (AT)
|
||||
"deploy_at": {"url": "at", "required": ["name", "description", "aTType", "tags", "creationBytes", "amount"], "optional": ["assetId"], "defaults": {"assetId": 0}, "key_name": "creatorPublicKey"},
|
||||
|
||||
# Cross-chain trading
|
||||
"create_trade": {"url": "crosschain/tradebot/create", "required": ["qortAmount", "fundingQortAmount", "foreignAmount", "receivingAddress"], "optional": ["tradeTimeout", "foreignBlockchain"], "defaults": {"tradeTimeout": 1440, "foreignBlockchain": "LITECOIN"}, "key_name": "creatorPublicKey"},
|
||||
"trade_recipient": {"url": "crosschain/tradeoffer/recipient", "required": ["atAddress", "recipient"], "key_name": "creatorPublicKey", "remove": ["timestamp", "reference", "fee"]},
|
||||
"trade_secret": {"url": "crosschain/tradeoffer/secret", "required": ["atAddress", "secret"], "key_name": "recipientPublicKey", "remove": ["timestamp", "reference", "fee"]},
|
||||
|
||||
# Signing only
|
||||
"sign": {"url": "transactions/sign", "required": ["transactionBytes"]}
|
||||
}
|
||||
|
||||
|
||||
def encode_base58(hex_str):
|
||||
num = int(hex_str, 16)
|
||||
encode = ""
|
||||
while num > 0:
|
||||
num, rem = divmod(num, 58)
|
||||
encode = BASE58_ALPHABET[rem] + encode
|
||||
return encode
|
||||
|
||||
def decode_base58(b58_str):
|
||||
num = 0
|
||||
for char in b58_str:
|
||||
num *= 58
|
||||
num += BASE58_MAP[char]
|
||||
hex_str = hex(num)[2:]
|
||||
return hex_str.zfill(66)
|
||||
|
||||
def pubkey_to_address(pubkey):
|
||||
pubkey_bytes = bytes.fromhex(decode_base58(pubkey))
|
||||
pkh = RIPEMD.new(sha256(pubkey_bytes).digest()).hexdigest()
|
||||
raw = bytes.fromhex("3a" + pkh)
|
||||
checksum = sha256(sha256(raw).digest()).hexdigest()[:8]
|
||||
address_hex = "3a" + pkh + checksum
|
||||
return encode_base58(address_hex)
|
||||
|
||||
def api_call(endpoint, data=None, method='POST'):
|
||||
url = f"{BASE_URL}/{endpoint}"
|
||||
headers = {'Content-Type': 'application/json'}
|
||||
if method == 'GET':
|
||||
resp = requests.get(url)
|
||||
else:
|
||||
resp = requests.post(url, headers=headers, json=data)
|
||||
resp.raise_for_status()
|
||||
return resp.text
|
||||
|
||||
def build_raw(tx_type, priv_key, values):
|
||||
tx_info = TRANSACTION_TYPES[tx_type]
|
||||
pub_key = get_pub_key(priv_key)
|
||||
address = pubkey_to_address(pub_key)
|
||||
reference = get_last_reference(address)
|
||||
data = {
|
||||
"timestamp": int(time.time() * 1000),
|
||||
"reference": reference,
|
||||
"fee": DEFAULT_FEE,
|
||||
tx_info["key_name"]: pub_key,
|
||||
}
|
||||
for key in tx_info["required"]:
|
||||
data[key] = values[key]
|
||||
print(json.dumps(data, indent=2))
|
||||
return api_call(tx_info["url"], data)
|
||||
|
||||
def get_pub_key(privkey):
|
||||
result = subprocess.run([
|
||||
"openssl", "pkey", "-in", privkey, "-pubout", "-outform", "DER"
|
||||
], capture_output=True)
|
||||
pubkey_der = result.stdout.hex()
|
||||
return encode_base58(pubkey_der)
|
||||
|
||||
def get_last_reference(address):
|
||||
return api_call(f"addresses/lastreference/{address}", method='GET')
|
||||
|
||||
def sign_tx(privkey, raw_tx):
|
||||
data = {
|
||||
"privateKey": privkey,
|
||||
"transactionBytes": raw_tx
|
||||
}
|
||||
return api_call("transactions/sign", data)
|
||||
|
||||
def process_tx(signed_tx):
|
||||
return api_call("transactions/process", signed_tx)
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description="Qortal Transaction Tool")
|
||||
parser.add_argument("tx_type", help="Transaction type")
|
||||
parser.add_argument("privkey", help="Private key")
|
||||
parser.add_argument("params", nargs="*", help="Transaction parameters")
|
||||
parser.add_argument("-s", "--sign", action="store_true")
|
||||
parser.add_argument("-p", "--process", action="store_true")
|
||||
parser.add_argument("-d", "--debug", action="store_true")
|
||||
args = parser.parse_args()
|
||||
|
||||
tx_type = args.tx_type.lower()
|
||||
if tx_type not in TRANSACTION_TYPES:
|
||||
print(f"Unknown transaction type: {tx_type}")
|
||||
sys.exit(1)
|
||||
|
||||
required = TRANSACTION_TYPES[tx_type]["required"]
|
||||
if len(args.params) < len(required):
|
||||
print(f"Missing required parameters for {tx_type}: {required}")
|
||||
sys.exit(1)
|
||||
|
||||
values = dict(zip(required, args.params))
|
||||
raw = build_raw(tx_type, args.privkey, values)
|
||||
|
||||
if args.sign:
|
||||
signed = sign_tx(args.privkey, raw)
|
||||
print("Signed:", signed)
|
||||
if args.process:
|
||||
result = process_tx(signed)
|
||||
print("Processed:", result)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
@@ -83,6 +83,10 @@ server {
|
||||
error_log /var/log/nginx/forbidden.log debug;
|
||||
}
|
||||
|
||||
location ~* ^/(favicon\.ico|\.env|\.git|cgi-bin|actuator|login|robots\.txt|sitemap\.xml) {
|
||||
return 404;
|
||||
}
|
||||
|
||||
location / {
|
||||
proxy_hide_header Access-Control-Allow-Origin;
|
||||
|
||||
|
||||
190
publish-qortal-hub-deb.sh
Executable file
190
publish-qortal-hub-deb.sh
Executable file
@@ -0,0 +1,190 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
# -------- configurable defaults --------
|
||||
REPO_DIR="${REPO_DIR:-/home/hubdeb.qortal.org/public_html}"
|
||||
DEB_NAME="${DEB_NAME:-Qortal-Hub-Setup.deb}"
|
||||
DEB_SUBDIR="${DEB_SUBDIR:-deb-packages}"
|
||||
DEB_URL="${DEB_URL:-https://github.com/Qortal/Qortal-Hub/releases/latest/download/Qortal-Hub-Setup.deb}"
|
||||
|
||||
# Use your primary key fingerprint (recommended) or specific signing subkey id.
|
||||
# From your output: primary fpr = 20C64216BB5C080569F0F6BA2B4015FB935F5F2A
|
||||
SIGNING_KEY="${SIGNING_KEY:-20C64216BB5C080569F0F6BA2B4015FB935F5F2A}"
|
||||
|
||||
# Optionally override auto owner/group after publish. If empty, owner is inferred from
|
||||
# the directory above REPO_DIR and used as owner:owner.
|
||||
CHOWN_TO="${CHOWN_TO:-}"
|
||||
|
||||
# -------- helpers --------
|
||||
die() { echo "ERROR: $*" >&2; exit 1; }
|
||||
|
||||
need_cmd() {
|
||||
command -v "$1" >/dev/null 2>&1 || die "Missing required command: $1"
|
||||
}
|
||||
|
||||
require_root() {
|
||||
if [[ "${EUID:-$(id -u)}" -ne 0 ]]; then
|
||||
die "This script must be run as root. Re-run with sudo."
|
||||
fi
|
||||
}
|
||||
|
||||
sha256_file() {
|
||||
sha256sum "$1" | awk '{print $1}'
|
||||
}
|
||||
|
||||
# -------- usage --------
|
||||
usage() {
|
||||
cat <<EOF
|
||||
Usage:
|
||||
$0
|
||||
$0 /path/to/Qortal-Hub-Setup.deb
|
||||
|
||||
Environment overrides:
|
||||
REPO_DIR=... (default: $REPO_DIR)
|
||||
DEB_NAME=... (default: $DEB_NAME)
|
||||
DEB_SUBDIR=... (default: $DEB_SUBDIR)
|
||||
DEB_URL=... (default: $DEB_URL)
|
||||
SIGNING_KEY=... (default: $SIGNING_KEY)
|
||||
CHOWN_TO=... (optional override; default auto owner:owner)
|
||||
|
||||
Example:
|
||||
SIGNING_KEY=20C64216BB5C080569F0F6BA2B4015FB935F5F2A REPO_DIR=/home/hubdeb.qortal.org/public_html $0
|
||||
EOF
|
||||
}
|
||||
|
||||
# -------- main --------
|
||||
[[ "${1:-}" == "-h" || "${1:-}" == "--help" ]] && { usage; exit 0; }
|
||||
[[ $# -gt 1 ]] && { usage; exit 2; }
|
||||
require_root
|
||||
|
||||
SRC_DEB="${1:-}"
|
||||
TMP_DEB_DIR=""
|
||||
STAGE_DIR=""
|
||||
|
||||
need_cmd dpkg-deb
|
||||
need_cmd dpkg-scanpackages
|
||||
need_cmd apt-ftparchive
|
||||
need_cmd gpg
|
||||
need_cmd mktemp
|
||||
need_cmd rsync
|
||||
need_cmd sha256sum
|
||||
need_cmd gzip
|
||||
need_cmd curl
|
||||
need_cmd stat
|
||||
need_cmd install
|
||||
need_cmd chown
|
||||
need_cmd dirname
|
||||
|
||||
cleanup() { rm -rf "${TMP_DEB_DIR:-}" "${STAGE_DIR:-}"; }
|
||||
trap cleanup EXIT
|
||||
|
||||
# Validate repo dirs
|
||||
[[ -d "$REPO_DIR" ]] || die "REPO_DIR not found: $REPO_DIR"
|
||||
[[ -d "$REPO_DIR/$DEB_SUBDIR" ]] || die "Repo deb dir not found: $REPO_DIR/$DEB_SUBDIR"
|
||||
|
||||
# Determine ownership target from the directory above REPO_DIR unless overridden.
|
||||
if [[ -z "$CHOWN_TO" ]]; then
|
||||
REPO_PARENT="$(dirname "$REPO_DIR")"
|
||||
[[ -d "$REPO_PARENT" ]] || die "Parent directory not found: $REPO_PARENT"
|
||||
OWNER_NAME="$(stat -c '%U' "$REPO_PARENT")"
|
||||
[[ -n "$OWNER_NAME" && "$OWNER_NAME" != "UNKNOWN" ]] || die "Could not determine owner for: $REPO_PARENT"
|
||||
CHOWN_TO="${OWNER_NAME}:${OWNER_NAME}"
|
||||
fi
|
||||
|
||||
# Ensure signing key exists (public + secret)
|
||||
gpg --list-secret-keys --keyid-format LONG "$SIGNING_KEY" >/dev/null 2>&1 \
|
||||
|| die "Signing secret key not available in this user's keyring: $SIGNING_KEY"
|
||||
|
||||
# Resolve source deb:
|
||||
# - If arg provided, use it.
|
||||
# - Otherwise download latest release artifact into temp file.
|
||||
if [[ -n "$SRC_DEB" ]]; then
|
||||
[[ -f "$SRC_DEB" ]] || die "Deb not found: $SRC_DEB"
|
||||
[[ "$(basename "$SRC_DEB")" == "$DEB_NAME" ]] || die "Deb must be named exactly: $DEB_NAME (got: $(basename "$SRC_DEB"))"
|
||||
else
|
||||
TMP_DEB_DIR="$(mktemp -d)"
|
||||
SRC_DEB="$TMP_DEB_DIR/$DEB_NAME"
|
||||
echo "Downloading latest $DEB_NAME from:"
|
||||
echo " $DEB_URL"
|
||||
curl -fL --retry 3 --connect-timeout 20 -o "$SRC_DEB" "$DEB_URL"
|
||||
fi
|
||||
|
||||
# Validate deb integrity
|
||||
dpkg-deb --info "$SRC_DEB" >/dev/null
|
||||
|
||||
cd "$REPO_DIR"
|
||||
|
||||
# Create a staging directory on the same filesystem for atomic swaps
|
||||
STAGE_DIR="$(mktemp -d "$REPO_DIR/.stage.XXXXXX")"
|
||||
|
||||
# Copy current repo metadata into stage (not strictly required, but helps for atomic swap approach)
|
||||
# We'll write new metadata into STAGE_DIR then move into place.
|
||||
mkdir -p "$STAGE_DIR/$DEB_SUBDIR"
|
||||
|
||||
# Copy all .deb files from current repo into stage, then overwrite with new one
|
||||
# This keeps any other debs you may have, while replacing Qortal-Hub-Setup.deb.
|
||||
rsync -a --delete "$DEB_SUBDIR/" "$STAGE_DIR/$DEB_SUBDIR/"
|
||||
|
||||
# Compute old hash if existing, for logging
|
||||
OLD_HASH=""
|
||||
if [[ -f "$DEB_SUBDIR/$DEB_NAME" ]]; then
|
||||
OLD_HASH="$(sha256_file "$DEB_SUBDIR/$DEB_NAME" || true)"
|
||||
fi
|
||||
|
||||
# Put new .deb into stage
|
||||
install -m 0644 "$SRC_DEB" "$STAGE_DIR/$DEB_SUBDIR/$DEB_NAME"
|
||||
|
||||
NEW_HASH="$(sha256_file "$STAGE_DIR/$DEB_SUBDIR/$DEB_NAME")"
|
||||
|
||||
# If identical content, you can optionally no-op.
|
||||
if [[ -n "$OLD_HASH" && "$OLD_HASH" == "$NEW_HASH" ]]; then
|
||||
echo "No changes: $DEB_NAME is identical (sha256: $NEW_HASH). Still rebuilding metadata to be safe..."
|
||||
fi
|
||||
|
||||
# Build Packages + Packages.gz inside stage
|
||||
dpkg-scanpackages -m "$STAGE_DIR/$DEB_SUBDIR" /dev/null > "$STAGE_DIR/Packages"
|
||||
gzip -9c "$STAGE_DIR/Packages" > "$STAGE_DIR/Packages.gz"
|
||||
|
||||
# Build Release inside stage
|
||||
(
|
||||
cd "$STAGE_DIR"
|
||||
apt-ftparchive release . > Release
|
||||
|
||||
# Sign Release.gpg + InRelease
|
||||
gpg --batch --yes --default-key "$SIGNING_KEY" --armor --detach-sign --output Release.gpg Release
|
||||
gpg --batch --yes --default-key "$SIGNING_KEY" --clearsign --output InRelease Release
|
||||
)
|
||||
|
||||
# Atomic publish:
|
||||
# 1) Move current metadata+deb dir aside (fast)
|
||||
# 2) Move staged into place (fast)
|
||||
# 3) Remove old backup
|
||||
BACKUP_DIR="$REPO_DIR/.backup.$(date -u +%Y%m%dT%H%M%SZ)"
|
||||
mkdir -p "$BACKUP_DIR"
|
||||
|
||||
# Move current repo files that we manage into backup
|
||||
for p in "$DEB_SUBDIR" Packages Packages.gz Release Release.gpg InRelease; do
|
||||
if [[ -e "$REPO_DIR/$p" ]]; then
|
||||
mv "$REPO_DIR/$p" "$BACKUP_DIR/"
|
||||
fi
|
||||
done
|
||||
|
||||
# Move stage into place
|
||||
mv "$STAGE_DIR/$DEB_SUBDIR" "$REPO_DIR/$DEB_SUBDIR"
|
||||
mv "$STAGE_DIR/Packages" "$REPO_DIR/Packages"
|
||||
mv "$STAGE_DIR/Packages.gz" "$REPO_DIR/Packages.gz"
|
||||
mv "$STAGE_DIR/Release" "$REPO_DIR/Release"
|
||||
mv "$STAGE_DIR/Release.gpg" "$REPO_DIR/Release.gpg"
|
||||
mv "$STAGE_DIR/InRelease" "$REPO_DIR/InRelease"
|
||||
|
||||
# If you want to keep the backup, comment the next line
|
||||
rm -rf "$BACKUP_DIR"
|
||||
|
||||
# Ownership fixup on final repo path
|
||||
chown -R "$CHOWN_TO" "$REPO_DIR"
|
||||
|
||||
echo "Published $DEB_NAME"
|
||||
echo " sha256: $NEW_HASH"
|
||||
echo " repo: $REPO_DIR"
|
||||
echo " owner: $CHOWN_TO"
|
||||
echo " signed: $(date -u)"
|
||||
Reference in New Issue
Block a user