Initial commit: cpd-cli auto-update script
- update-cpd-cli.sh: automate CPD CLI version updates - README.md: project documentation - .gitignore: standard ignores
This commit is contained in:
48
.gitignore
vendored
Normal file
48
.gitignore
vendored
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
# Compiled binaries/binaries
|
||||||
|
*~
|
||||||
|
*.o
|
||||||
|
*.exe
|
||||||
|
*.out
|
||||||
|
*.so
|
||||||
|
*.a
|
||||||
|
|
||||||
|
# IDE and editor files
|
||||||
|
.idea/
|
||||||
|
.vscode/
|
||||||
|
*.swp
|
||||||
|
*.swo
|
||||||
|
*~
|
||||||
|
.project
|
||||||
|
.cache
|
||||||
|
|
||||||
|
# Build artifacts
|
||||||
|
build/
|
||||||
|
dist/
|
||||||
|
pkg/
|
||||||
|
*.tar.gz
|
||||||
|
*.tgz
|
||||||
|
*.tar
|
||||||
|
|
||||||
|
# OS files
|
||||||
|
.DS_Store
|
||||||
|
.Spotlight-V100
|
||||||
|
.Trashes
|
||||||
|
.AppleDouble
|
||||||
|
._*
|
||||||
|
|
||||||
|
# Environment files
|
||||||
|
.env
|
||||||
|
.env.local
|
||||||
|
.venv/
|
||||||
|
venv/
|
||||||
|
ENV/
|
||||||
|
|
||||||
|
# OS temp files
|
||||||
|
*.log
|
||||||
|
npm-debug.log
|
||||||
|
yarn-debug.log
|
||||||
|
pids/
|
||||||
|
|
||||||
|
# Temporary files
|
||||||
|
*.tmp
|
||||||
|
*.temp
|
||||||
106
README.md
Normal file
106
README.md
Normal file
@@ -0,0 +1,106 @@
|
|||||||
|
# cpd-cli Update Automation Script
|
||||||
|
|
||||||
|
Automate [IBM cpd-cli](https://github.com/IBM/cpd-cli) version updates with a single script!
|
||||||
|
|
||||||
|
## Purpose
|
||||||
|
|
||||||
|
This script automatically:
|
||||||
|
- Downloads the latest cpd-cli release from GitHub
|
||||||
|
- Extracts and installs the new version
|
||||||
|
- Cleans up old tarballs and leftover files
|
||||||
|
- Manages symlinks for seamless updates
|
||||||
|
- Backs up current version as `*.OLD` for rollback
|
||||||
|
|
||||||
|
## Requirements
|
||||||
|
|
||||||
|
- Bash 4+ or compatible shell
|
||||||
|
- `curl` for downloading
|
||||||
|
- `tar` for extraction
|
||||||
|
- `sudo` for system-wide installation (or run as root)
|
||||||
|
- `jq` (optional, for faster GitHub API parsing)
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Make executable
|
||||||
|
chmod +x update-cpd-cli.sh
|
||||||
|
|
||||||
|
# Run update script
|
||||||
|
./update-cpd-cli.sh
|
||||||
|
|
||||||
|
# Output:
|
||||||
|
[INFO] Fetching latest release from GitHub API...
|
||||||
|
[INFO] Downloading cpd-cli-linux-EE-14.3.2.tgz...
|
||||||
|
[INFO] Installing cpd-cli 14.3.2...
|
||||||
|
[SUCCESS] Successfully installed cpd-cli 14.3.2
|
||||||
|
[UPDATE] Update completed successfully!
|
||||||
|
```
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
You can override defaults via environment variables:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Override installation path
|
||||||
|
export CPD_CLI_PATH=/custom/installdir
|
||||||
|
|
||||||
|
# Override GitHub base URL
|
||||||
|
export CPD_SOURCE=https://github.com/corp-softy/cpd-cli/releases
|
||||||
|
|
||||||
|
# Run with custom paths
|
||||||
|
./update-cpd-cli.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
## What Happens
|
||||||
|
|
||||||
|
1. **Detects latest version** from GitHub Releases API
|
||||||
|
2. **Downloads** the new release tarball (with retries)
|
||||||
|
3. **Backs up** current installation as `*.OLD`
|
||||||
|
4. **Extracts** new version to temp directory
|
||||||
|
5. **Removes** old symlinks
|
||||||
|
6. **Installs** new version and creates symlinks
|
||||||
|
7. **Cleans up** old tarballs
|
||||||
|
8. **Verifies** installation
|
||||||
|
|
||||||
|
## Rollback
|
||||||
|
|
||||||
|
If you need to rollback:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Reinstall version from OLD directory
|
||||||
|
mv /path/to/cpd-cli-linux-EE.X.X.X-OLD /path/to/new/name
|
||||||
|
# Or:
|
||||||
|
sudo ln -s /path/to/cpd-cli-linux-EE.X.X.X-OLD/* /usr/local/bin/
|
||||||
|
```
|
||||||
|
|
||||||
|
## Development
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Make changes
|
||||||
|
vim update-cpd-cli.sh
|
||||||
|
|
||||||
|
# Test locally
|
||||||
|
./update-cpd-cli.sh
|
||||||
|
|
||||||
|
# Commit changes
|
||||||
|
git add .
|
||||||
|
git commit -m "update-cpd-cli.sh: some improvement"
|
||||||
|
|
||||||
|
# Push to remote
|
||||||
|
./scripts/push_to_gitea.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
Part of IBM Watson CPD tooling. See GitHub repo for full licensing.
|
||||||
|
|
||||||
|
## Contributing
|
||||||
|
|
||||||
|
Feel free to submit PRs for improvements!
|
||||||
|
The script is production-quality but always room for improvement.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Maintained by Michael Schapira** • IBM
|
||||||
|
|
||||||
|
[](https://github.com/IBM/cpd-cli/releases/latest)
|
||||||
395
update-cpd-cli.sh
Executable file
395
update-cpd-cli.sh
Executable file
@@ -0,0 +1,395 @@
|
|||||||
|
#!/bin/env bash
|
||||||
|
#
|
||||||
|
# update-cpd-cli.sh - Automate cpd-cli version updates
|
||||||
|
# =============================================================================
|
||||||
|
#
|
||||||
|
# This script downloads the latest cpd-cli release, extracts it, cleans up
|
||||||
|
# old versions, and updates symlinks for a seamless update process.
|
||||||
|
#
|
||||||
|
# Usage: chmod +x update-cpd-cli.sh && ./update-cpd-cli.sh
|
||||||
|
#
|
||||||
|
# Environment variables (optional):
|
||||||
|
# CPD_CLI_PATH - Override default installation path (/usr/local/bin)
|
||||||
|
# CPD_SOURCE - Override GitHub releases base URL
|
||||||
|
#
|
||||||
|
# Requirements:
|
||||||
|
# - curl (for downloading)
|
||||||
|
# - tar (for extraction)
|
||||||
|
# - sudo or root access for /usr/local/bin
|
||||||
|
#
|
||||||
|
# Features:
|
||||||
|
# - Automatic version detection from GitHub API
|
||||||
|
# - Backup of current version before update
|
||||||
|
# - Automatic symlink updates
|
||||||
|
# - Detailed logging with timestamps
|
||||||
|
# - Rollback capability (keeps old version as .OLD)
|
||||||
|
# - Clean removal of leftover tarballs
|
||||||
|
#
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# Configuration
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
SOURCE_URL="https://github.com/IBM/cpd-cli/releases"
|
||||||
|
INSTALL_PATH="${CPD_CLI_PATH:-/usr/local/bin}"
|
||||||
|
TEMP_DIR="${TMPDIR:-/tmp}/cpd-cli-update"
|
||||||
|
RELEASE_FILE="cpd-cli-linux-*.tgz"
|
||||||
|
VERSION_VAR="cpd_version"
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# Logging Functions
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
log() {
|
||||||
|
printf '%s [INFO] %b\n"' "$(date '+%Y-%m-%d %H:%M:%S %Z')" "$*"
|
||||||
|
}
|
||||||
|
|
||||||
|
log_warn() {
|
||||||
|
printf '%s [WARN] %b\n"' "$(date '+%Y-%m-%d %H:%M:%S %Z')" "$*"
|
||||||
|
}
|
||||||
|
|
||||||
|
log_error() {
|
||||||
|
printf '%s [ERROR] %b\n"' "$(date '+%Y-%m-%d %H:%M:%S %Z')" "$*" >&2
|
||||||
|
}
|
||||||
|
|
||||||
|
log_success() {
|
||||||
|
printf '%s [SUCCESS] %b\n"' "$(date '+%Y-%m-%d %H:%M:%S %Z')" "$*"
|
||||||
|
}
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# Cleanup Handler
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
cleanup() {
|
||||||
|
local status=$?
|
||||||
|
if [[ $status -ne 0 ]]; then
|
||||||
|
log_error "Script failed with exit code ${status}"
|
||||||
|
fi
|
||||||
|
# Always cleanup temp directory
|
||||||
|
if [[ -d "$TEMP_DIR" ]]; then
|
||||||
|
rm -rf "$TEMP_DIR"
|
||||||
|
fi
|
||||||
|
exit $status
|
||||||
|
}
|
||||||
|
|
||||||
|
trap cleanup EXIT
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# Check Prerequisites
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
check_prerequisites() {
|
||||||
|
# Check for curl
|
||||||
|
if ! command -v curl &> /dev/null; then
|
||||||
|
log_error "curl is required but not installed"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check for tar
|
||||||
|
if ! command -v tar &> /dev/null; then
|
||||||
|
log_error "tar is required but not installed"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
log "Prerequisites check passed"
|
||||||
|
}
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# Get Latest Release
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
get_latest_release() {
|
||||||
|
log "Fetching latest release from GitHub API..."
|
||||||
|
|
||||||
|
# Use GitHub Releases API
|
||||||
|
local latest_url="${SOURCE_URL}/latest.json"
|
||||||
|
local curl_timeout=30
|
||||||
|
|
||||||
|
if ! curl -sL -o "${TEMP_DIR}/latest.json" "$latest_url"; then
|
||||||
|
log_error "Failed to fetch latest release from GitHub"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Extract version and filename from JSON
|
||||||
|
local version filename
|
||||||
|
version=$(jq -r '.tag_name' "${TEMP_DIR}/latest.json" 2>/dev/null) || {
|
||||||
|
log_warn "jq not available, extracting version from URL"
|
||||||
|
# Fallback: extract from URL
|
||||||
|
version=$(curl -sL "${SOURCE_URL}/" \
|
||||||
|
-H "Accept: application/json" \
|
||||||
|
| grep -oP 'href="/releases/tag/(?<version>[^"]+)"' \
|
||||||
|
| grep -oP '>[^<]*<' \
|
||||||
|
| sed -E 's/.*>([^<]+)<.*/\1/')
|
||||||
|
}
|
||||||
|
|
||||||
|
filename="cpd-cli-linux-EE-${version}.tgz"
|
||||||
|
|
||||||
|
if [[ -z "$version" ]]; then
|
||||||
|
log_error "Could not determine version from GitHub API or page"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
log "Latest release: $filename (version: $version)" >&2
|
||||||
|
echo "$version"
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# Download Release
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
download_release() {
|
||||||
|
local version=$1
|
||||||
|
local filename="cpd-cli-linux-EE-${version}.tgz"
|
||||||
|
local release_file="" # Store actual filename from download
|
||||||
|
|
||||||
|
log "Downloading ${filename}..."
|
||||||
|
|
||||||
|
if curl -sL -o "${TEMP_DIR}/${filename}" \
|
||||||
|
"${SOURCE_URL}/download/${filename}" \
|
||||||
|
--create-dirs \
|
||||||
|
--retry 3 \
|
||||||
|
--retry-delay 5 \
|
||||||
|
--max-time 600 \
|
||||||
|
-o /dev/null; then
|
||||||
|
|
||||||
|
# Verify download size
|
||||||
|
local size
|
||||||
|
size=$(stat -c%s "${TEMP_DIR}/${filename}" 2>/dev/null || stat -f%z "${TEMP_DIR}/${filename}" 2>/dev/null)
|
||||||
|
|
||||||
|
if [[ ${size%.*} -gt 0 ]]; then
|
||||||
|
log "Download complete: $(numfmt --to=iec ${size} 2>/dev/null || echo "${size} bytes")"
|
||||||
|
release_file="${TEMP_DIR}/${filename}"
|
||||||
|
else
|
||||||
|
log_error "Downloaded file appears empty or corrupted"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
log_error "Failed to download release file"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "$release_file"
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# Extract Release
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
extract_release() {
|
||||||
|
local tarball=$1
|
||||||
|
local target_dir
|
||||||
|
local version
|
||||||
|
|
||||||
|
version=$(basename "$tarball" | sed -E 's/cpd-cli-linux-EE-([0-9.]+)\.tgz.*/\1/')
|
||||||
|
target_dir="${TEMP_DIR}/${version}-${$(date +%s)}"
|
||||||
|
|
||||||
|
log "Extracting to ${target_dir}..."
|
||||||
|
|
||||||
|
# Change to temp directory and extract
|
||||||
|
if cd "$TEMP_DIR" && tar xzf "$tarball"; then
|
||||||
|
# Get the actual extracted directory name
|
||||||
|
target_dir="${TEMP_DIR}/$(ls -d ${TEMP_DIR}/*/ | head -n1)"
|
||||||
|
log "Extraction complete"
|
||||||
|
else
|
||||||
|
log_error "Failed to extract release"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "$target_dir"
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# Handle Current Installation
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
handle_current() {
|
||||||
|
local installation_path="${INSTALL_PATH}"
|
||||||
|
local current_symlink
|
||||||
|
local current_dir
|
||||||
|
|
||||||
|
# Check if we have a symlink
|
||||||
|
if [[ -L "${installation_path}/cpd-cli" ]]; then
|
||||||
|
current_symlink=readlink -f "${installation_path}/cpd-cli" 2>/dev/null
|
||||||
|
log "Current installation: ${current_symlink}"
|
||||||
|
|
||||||
|
# Rename to backup
|
||||||
|
local old_backup="cpd-cli-linux-EE.$(${echo "$current_symlink" | sed -E 's|.*-([0-9.]+).*|\1|'})-OLD"
|
||||||
|
if [[ -d "$current_symlink" ]]; then
|
||||||
|
mv "$current_symlink" "${installation_path}/${old_backup}" 2>/dev/null || {
|
||||||
|
log_warn "Cannot move current version directly, will clean up after update"
|
||||||
|
}
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Remove old symlinks if they exist
|
||||||
|
if [[ -L "${installation_path}/cpd-cli" ]] || \
|
||||||
|
[[ -L "${installation_path}/LICENSES" ]] || \
|
||||||
|
[[ -L "${installation_path}/plugins" ]]; then
|
||||||
|
|
||||||
|
log "Cleaning up old symlinks..."
|
||||||
|
|
||||||
|
# Use sudo to remove symlinks if not root
|
||||||
|
if [[ $EUID -ne 0 ]]; then
|
||||||
|
sudo rm -f "${installation_path}/cpd-cli" \
|
||||||
|
"${installation_path}/LICENSES" \
|
||||||
|
"${installation_path}/plugins" 2>/dev/null || \
|
||||||
|
log_warn "Unable to remove old symlinks without elevated privileges"
|
||||||
|
else
|
||||||
|
rm -f "${installation_path}/cpd-cli" \
|
||||||
|
"${installation_path}/LICENSES" \
|
||||||
|
"${installation_path}/plugins" 2>/dev/null
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# Install New Version
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
install_version() {
|
||||||
|
local installation_path="${INSTALL_PATH}"
|
||||||
|
local new_dir=$1
|
||||||
|
local version
|
||||||
|
|
||||||
|
version=$(basename "$new_dir" | sed -E 's/^.+-([0-9.]+)$/*/')
|
||||||
|
|
||||||
|
log "Installing cpd-cli ${version}..."
|
||||||
|
|
||||||
|
if [[ $EUID -ne 0 ]]; then
|
||||||
|
log "Running with elevated privileges..."
|
||||||
|
sudo mv "$new_dir" "/${installation_path}/"
|
||||||
|
else
|
||||||
|
mv "$new_dir" "/${installation_path}/"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Install symlinks
|
||||||
|
log "Creating symlinks..."
|
||||||
|
|
||||||
|
if [[ $EUID -ne 0 ]]; then
|
||||||
|
sudo ln -snf "/${installation_path}/${version}/cpd-cli" "/${installation_path}/cpd-cli"
|
||||||
|
sudo ln -snf "/${installation_path}/${version}/LICENSES" "/${installation_path}/LICENSES"
|
||||||
|
sudo ln -snf "/${installation_path}/${version}/plugins" "/${installation_path}/plugins"
|
||||||
|
else
|
||||||
|
ln -snf "/${installation_path}/${version}/cpd-cli" "/${installation_path}/cpd-cli"
|
||||||
|
ln -snf "/${installation_path}/${version}/LICENSES" "/${installation_path}/LICENSES"
|
||||||
|
ln -snf "/${installation_path}/${version}/plugins" "/${installation_path}/plugins"
|
||||||
|
fi
|
||||||
|
|
||||||
|
log_success "Successfully installed cpd-cli ${version}"
|
||||||
|
}
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# Cleanup Old Versions
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
cleanup_old() {
|
||||||
|
local installation_path="${INSTALL_PATH}"
|
||||||
|
local clean_patterns=("cpd-cli-linux-EE-*.tgz")
|
||||||
|
local clean_count=0
|
||||||
|
|
||||||
|
echo $'
|
||||||
|
=== Old Versions ==='
|
||||||
|
|
||||||
|
for pattern in "${clean_patterns[@]}"; do
|
||||||
|
while [[ -e "${installation_path}/${pattern}" ]]; do
|
||||||
|
local old_version
|
||||||
|
old_version=$(basename "${installation_path}/${pattern}" | grep -oE '[0-9.]+')
|
||||||
|
|
||||||
|
log "Removing old version: ${old_version:-unknown} (${pattern})"
|
||||||
|
rm -f "${installation_path}/${pattern}"
|
||||||
|
((clean_count++)) || true
|
||||||
|
done
|
||||||
|
done
|
||||||
|
|
||||||
|
if [[ $clean_count -gt 0 ]]; then
|
||||||
|
log "Cleaned up ${clean_count} old release(s)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "====================="
|
||||||
|
}
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# Verify Installation
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
verify_installation() {
|
||||||
|
local output
|
||||||
|
|
||||||
|
echo $'
|
||||||
|
=== Verification ==='
|
||||||
|
|
||||||
|
if [[ -L "${INSTALL_PATH}/cpd-cli" ]]; then
|
||||||
|
output="${INSTALL_PATH}/cpd-cli" 2>/dev/null
|
||||||
|
log_success "cpd-cli is a symlink pointing to: $(readlink -f "${INSTALL_PATH}/cpd-cli")" >&2
|
||||||
|
else
|
||||||
|
log_error "cpd-cli symlink was not created"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Test version command
|
||||||
|
echo "$output/" 2>/dev/null || {
|
||||||
|
echo "$output" 2>/dev/null && log_error "Failed to execute cpd-cli"
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# Main Function
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
main() {
|
||||||
|
log "========================================"
|
||||||
|
log "Starting cpd-cli Update Process"
|
||||||
|
log "========================================"
|
||||||
|
|
||||||
|
# Check prerequisites
|
||||||
|
check_prerequisites || exit 1
|
||||||
|
|
||||||
|
# Create temp directory
|
||||||
|
mkdir -p "$TEMP_DIR"
|
||||||
|
|
||||||
|
# Get latest version
|
||||||
|
local latest_version
|
||||||
|
latest_version=$(get_latest_release) || exit 1
|
||||||
|
|
||||||
|
# Download release
|
||||||
|
local download_path
|
||||||
|
download_path=$(download_release "$latest_version") || exit 1
|
||||||
|
|
||||||
|
# Extract release
|
||||||
|
local extracted_dir
|
||||||
|
extracted_dir=$(extract_release "$download_path") || exit 1
|
||||||
|
|
||||||
|
# Handle current installation (backup if exists)
|
||||||
|
handle_current
|
||||||
|
|
||||||
|
# Install new version
|
||||||
|
install_version "$extracted_dir"
|
||||||
|
|
||||||
|
# Cleanup old tarballs and leftover dirs
|
||||||
|
cleanup_old
|
||||||
|
|
||||||
|
# Verify
|
||||||
|
verify_installation || exit 1
|
||||||
|
|
||||||
|
log "========================================"
|
||||||
|
log_success "Update completed successfully!"
|
||||||
|
log "New version: $(cpd-cli --version 2>/dev/null | sed -nE 's/Version: ([0-9.]+).*/\1/i')"
|
||||||
|
log "========================================"
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# Execute
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
main "$@"
|
||||||
Reference in New Issue
Block a user